De quién es el perfil
- La clave es el remitente canónico (
ctx.sender, elsenderIdque resolvió el provider), así que la misma persona ve los mismos datos en su DM y en todos los grupos. - Vive dentro del namespace de store del bot (tenant / entorno / bot / provider / conexión): dos bots que comparten backend no ven los perfiles del otro.
Fuera de handlers y dentro de flujos
userOf(ctx) lanza UserDataError('UNAVAILABLE') de forma síncrona si el
contexto no lo construyó un bot. En bot.conversation(name, async (c, ctx) => ...) el
ctx es un MessageContext completo, así que ctx.user funciona directo.
API
ctx.user en sí es el namespace 'default'.
Paths y valores
- Un path se parte en cada punto. El primer segmento es la clave de
primer nivel, la unidad que el store guarda de forma atómica; los
segmentos siguientes viven dentro de su valor JSON. Los arrays se indexan
(
'items.0') y solo pueden crecer por el final (índice ≤ longitud). - Se rechazan con
UserDataError('INVALID_PATH'): segmentos vacíos,__proto__/prototype/constructor, caracteres de control y claves de más de 255 caracteres. Los namespaces tienen de 1 a 128 caracteres, sin caracteres de control (INVALID_NAMESPACE). - Los valores son JSON: se copian vía JSON al escribir (un
Datepasa a string ISO) y cada lectura devuelve una copia nueva. BigInt, funciones, símbolos y ciclos rechazan conINVALID_VALUE. - Ids, namespaces y claves se comparan como strings exactos: mayúsculas, acentos y espacios al principio o al final cuentan, en todos los stores.
TTL (modelo Redis)
- Con
ttl, la clave de primer nivel expirattldespués de esa escritura, aunque el path sea anidado. - Un
setde primer nivel sinttlreemplaza el valor y quita la expiración. - Una escritura anidada sin
ttl(incluidodelete('a.b')) conserva la expiración actual. - El TTL debe ser positivo y finito (si no,
INVALID_TTL); los milisegundos fraccionarios se redondean hacia arriba. - Una clave expirada se lee como ausente al instante. Se borra físicamente
con Redis (expiración nativa) o con
bot.purgeExpiredUserData(), que las escrituras disparan como mucho una vez por minuto. Para bots con poco tráfico, prográmalo:bot.every('1h', () => bot.purgeExpiredUserData()).
Lotes y concurrencia
update()valida todos los paths, valores, índices de array y elttlantes de escribir nada, así que un lote inválido no escribe nada. Todos los paths de una misma clave de primer nivel van en una sola escritura. El lote completo no es atómico: si el store falla en una clave, otras pueden haberse guardado ya; la llamada rechaza solo cuando todas las escrituras han terminado.- La cola FIFO por chat no serializa a un usuario que escribe desde
varios chats, así que
ctx.userserializa en el proceso todas las escrituras de un usuario (set/update/delete/cleary el borrado de sujeto), en orden de llamada. - Con varias réplicas sobre un mismo store, las escrituras de primer nivel siguen siendo atómicas (gana la última), pero dos réplicas que escriben paths anidados de la misma clave a la vez pueden perder una actualización.
- Las lecturas no esperan a las escrituras: haz
awaitde tus escrituras antes de volver a leerlas.
Stores
Todos los stores incluidos lo soportan:MemoryStore (core), SQLiteStore y
RedisStore, PostgresStore, MysqlStore y MongoStore. En
Store persistente están las tablas y
claves que usa cada uno.
Un IStore propio sin los métodos opcionales de datos de usuario hace que
ctx.user rechace con UserDataError('UNSUPPORTED_STORE'); la exportación
y el borrado de sujeto siguen funcionando (userData: [] / 0).
supportsUserData(store) (de @botmesh/core/spi) te dice en qué caso estás.
Para implementarlo, añade getUserData, setUserData, deleteUserData,
listUserData y purgeExpiredUserData; el repositorio de botmesh comprueba
cada store incluido con la misma suite compartida
(test-utils/user-data-conformance.ts).
Privacidad: exportación y borrado
Errores
UserDataError tiene un code: INVALID_PATH, INVALID_NAMESPACE,
INVALID_VALUE, INVALID_TTL, INVALID_USER, UNSUPPORTED_STORE o
UNAVAILABLE.