ctx.memory provee almacenamiento key-value por sesión respaldado por Redis.
Disponibilidad
if ( ! ctx . memory . available ) {
ctx . log ( "Memory API no disponible — faltan socketId o API key del workflow" );
return { error: "memory_unavailable" };
}
ctx.memory.available requiere socketId y una API key del workflow configurada. Llamar a métodos en un cliente no disponible lanza un Error.
¿String o JSON?
¿Tu valor es un string, número o boolean?
→ Sí: usa set() / get()
→ No (objeto, array): usa setJson() / getJson()
Métodos
Método Firma Retorna Descripción getget(key, defaultValue)Promise<string | number | boolean>Lee un valor primitivo getJsongetJson<T>(key, defaultValue)Promise<T>Lee un valor JSON tipado setset(key, value, ttl?)Promise<void>Guarda un valor primitivo setJsonsetJson(key, value, ttl)Promise<void>Guarda un objeto JSON (TTL requerido) deletedelete(key)Promise<void>Elimina una clave
get(key, defaultValue)
Lee un valor primitivo. El tipo del retorno coincide con el tipo del defaultValue:
const nombre = await ctx . memory . get ( "nombre" , "anónimo" ); // string
const intentos = await ctx . memory . get ( "intentos" , 0 ); // number
const verificado = await ctx . memory . get ( "verificado" , false ); // boolean
Si la clave no existe, retorna el defaultValue.
getJson<T>(key, defaultValue)
Lee un valor JSON con tipado genérico:
interface Carrito {
items : Array <{ id : string ; cantidad : number }>;
total : number ;
}
const carritoVacio : Carrito = { items: [], total: 0 };
const carrito = await ctx . memory . getJson < Carrito >( "carrito" , carritoVacio );
set(key, value, ttl?)
Guarda un valor primitivo. El TTL es opcional (en segundos).
await ctx . memory . set ( "paso" , "confirmacion" );
await ctx . memory . set ( "paso" , "confirmacion" , 3600 );
await ctx . memory . set ( "intentos" , 3 , 1800 );
await ctx . memory . set ( "verificado" , true );
setJson(key, value, ttl)
Guarda un objeto JSON. El TTL es requerido (en segundos).
await ctx . memory . setJson ( "carrito" , {
items: [{ id: "SKU-001" , cantidad: 2 }],
total: 49.98 ,
}, 86400 );
delete(key)
Elimina una clave de la memoria.
await ctx . memory . delete ( "carrito" );
Límites
Constante Valor Descripción MAX_PRIMITIVE_LENGTH255 Máximo de caracteres para valores con set() MAX_TTL_SECONDS86,400 TTL máximo (24 horas)
setJson() requiere TTL porque almacena datos potencialmente grandes. Para valores primitivos con set(), el TTL es opcional.
Ejemplos prácticos
Carrito de compras
Contador de intentos
Flujo multi-paso
interface Carrito {
items : Array <{ id : string ; nombre : string ; cantidad : number ; precio : number }>;
total : number ;
}
const carritoVacio : Carrito = { items: [], total: 0 };
handler : async ( input , ctx ) => {
const carrito = await ctx . memory . getJson < Carrito >( "carrito" , carritoVacio );
carrito . items . push ({
id: input . productoId ,
nombre: input . nombre ,
cantidad: input . cantidad ,
precio: input . precio ,
});
carrito . total = carrito . items . reduce (( sum , i ) => sum + i . precio * i . cantidad , 0 );
await ctx . memory . setJson ( "carrito" , carrito , 86400 );
return { carrito };
}
handler : async ( input , ctx ) => {
const intentos = await ctx . memory . get ( "intentos_login" , 0 );
if ( intentos >= 3 ) {
return { bloqueado: true , mensaje: "Demasiados intentos. Intenta en 30 minutos." };
}
const valido = await verificarCredenciales ( input . usuario , input . clave );
if ( ! valido ) {
await ctx . memory . set ( "intentos_login" , intentos + 1 , 1800 );
return { bloqueado: false , error: "credenciales_invalidas" };
}
await ctx . memory . delete ( "intentos_login" );
return { bloqueado: false , token: generarToken ( input . usuario ) };
}
handler : async ( input , ctx ) => {
const paso = await ctx . memory . get ( "paso" , "inicio" );
if ( paso === "inicio" ) {
await ctx . memory . set ( "paso" , "datos_personales" , 3600 );
return { siguiente: "datos_personales" , mensaje: "Ingresa tu nombre" };
}
if ( paso === "datos_personales" ) {
await ctx . memory . set ( "nombre" , input . nombre , 3600 );
await ctx . memory . set ( "paso" , "confirmacion" , 3600 );
return { siguiente: "confirmacion" , mensaje: "¿Confirmas tu registro?" };
}
const nombre = await ctx . memory . get ( "nombre" , "" );
await ctx . memory . delete ( "paso" );
await ctx . memory . delete ( "nombre" );
return { completado: true , mensaje: `¡Bienvenido, ${ nombre } !` };
}
MemoryApiError
Se lanza cuando la API de memoria retorna una respuesta no-2xx.
import { MemoryApiError } from "@jelou/functions" ;
try {
await ctx . memory . set ( "clave" , "valor" , 3600 );
} catch ( err ) {
if ( err instanceof MemoryApiError ) {
ctx . log ( "Memory API falló" , { status: err . status , code: err . code });
if ( err . isRateLimit ()) {
ctx . log ( "Rate limit alcanzado, reintentando..." );
}
if ( err . isAuth ()) {
ctx . log ( "Problema de autenticación con la API de workflow" );
}
}
}
Propiedad / Método Tipo Descripción statusnumberCódigo HTTP codestringCódigo de error messagestringMensaje descriptivo isRateLimit()booleantrue cuando status === 429isAuth()booleantrue cuando status === 401 o 403
ctx.memory vs base de datos externa
Criterio ctx.memoryBase de datos externa Alcance Por sesión (socketId) Global Persistencia Temporal (máx 24h TTL) Permanente Configuración Cero — viene incluido Requiere connection string Ideal para Estado de conversación, cache de sesión Datos de usuario, historial, configuración
Session-scoped : los datos están ligados al socketId de la conversación actual. No se comparten entre sesiones.
TTL requerido en setJson : olvidar el TTL causa un error de compilación TypeScript.
Límite de 255 caracteres en set(): valores más largos son truncados. Para datos extensos usa setJson().
Testing
import { assertEquals } from "jsr:@std/assert" ;
import { createMockContext , createMockMemoryClient } from "@jelou/functions/testing" ;
const mockMemory = createMockMemoryClient ({
store: { paso: "inicio" , intentos: 0 },
});
const ctx = createMockContext ({ memory: mockMemory });
Deno . test ( "lee valor existente" , async () => {
const paso = await ctx . memory . get ( "paso" , "desconocido" );
assertEquals ( paso , "inicio" );
});
Deno . test ( "escribe y lee nuevo valor" , async () => {
await ctx . memory . set ( "paso" , "confirmacion" , 3600 );
const paso = await ctx . memory . get ( "paso" , "desconocido" );
assertEquals ( paso , "confirmacion" );
});
Deno . test ( "inspecciona llamadas" , async () => {
mockMemory . reset ();
await ctx . memory . set ( "clave" , "valor" );
assertEquals ( mockMemory . calls . length , 1 );
assertEquals ( mockMemory . calls [ 0 ]. method , "set" );
});
Deno . test ( "JSON tipado" , async () => {
await ctx . memory . setJson ( "carrito" , { items: [], total: 0 }, 3600 );
const carrito = await ctx . memory . getJson ( "carrito" , { items: [], total: 0 });
assertEquals ( carrito . total , 0 );
});
createMockMemoryClient() Referencia completa del mock de memoria.