¿Qué es ctx.memory?
ctx.memory es un almacenamiento key-value por sesión respaldado por Redis. Está disponible automáticamente cuando la petición viene de una conversación activa (tiene socketId) y el company tiene una API key de workflow configurada.
Casos de uso típicos:
Flujos multi-paso — recordar en qué paso está el usuario
Carritos de compras — acumular productos durante la conversación
Contadores — limitar intentos de login, tracking de reintentos
Preferencias temporales — idioma, formato, filtros durante la sesión
Inicio rápido
import { define , z } from "@jelou/functions" ;
export default define ({
name: "flujo-registro" ,
description: "Registro multi-paso con memoria de sesión" ,
input: z . object ({
respuesta: z . string (). optional (),
}) ,
handler : async ( input , ctx ) => {
if ( ! ctx . memory . available ) {
return { error: "Memoria no disponible fuera de una conversación" };
}
const paso = await ctx . memory . get ( "paso" , "inicio" );
if ( paso === "inicio" ) {
await ctx . memory . set ( "paso" , "nombre" , 3600 );
return { pregunta: "¿Cuál es tu nombre?" };
}
if ( paso === "nombre" ) {
await ctx . memory . set ( "nombre" , input . respuesta || "" , 3600 );
await ctx . memory . set ( "paso" , "email" , 3600 );
return { pregunta: "¿Cuál es tu email?" };
}
const nombre = await ctx . memory . get ( "nombre" , "" );
await ctx . memory . delete ( "paso" );
await ctx . memory . delete ( "nombre" );
return { completado: true , nombre , email: input . respuesta };
} ,
}) ;
Verificar disponibilidad
if ( ! ctx . memory . available ) {
ctx . log ( "Memory no disponible — sin socketId o API key de workflow" );
return { error: "memory_unavailable" };
}
ctx.memory.available es false cuando falta el socketId (peticiones fuera de una conversación) o la API key del workflow no está configurada. Llamar a métodos en un cliente no disponible lanza un Error.
Primitivos vs JSON
Usa set()/get() para valores simples y setJson()/getJson() para objetos:
await ctx . memory . set ( "paso" , "confirmacion" , 3600 );
const paso = await ctx . memory . get ( "paso" , "inicio" );
await ctx . memory . setJson ( "carrito" , { items: [], total: 0 }, 86400 );
const carrito = await ctx . memory . getJson ( "carrito" , { items: [], total: 0 });
El tipo de retorno de get() coincide con el tipo del valor por defecto:
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
TTL (tiempo de vida)
Todos los valores expiran automáticamente. El TTL se especifica en segundos.
Método TTL Máximo set()Opcional 86,400 (24h) setJson()Requerido 86,400 (24h)
await ctx . memory . set ( "paso" , "pago" ); // sin TTL explícito
await ctx . memory . set ( "paso" , "pago" , 1800 ); // expira en 30 min
await ctx . memory . setJson ( "carrito" , carrito , 86400 ); // expira en 24h (máximo)
Límites
Restricción Valor Longitud máxima de set() 255 caracteres TTL máximo 86,400 segundos (24h) Alcance Por sesión (socketId)
Valores de set() que excedan 255 caracteres lanzan un Error. Para datos más grandes, usa setJson().
Patrones comunes
Flujo multi-paso
Carrito de compras
Límite de intentos
handler : async ( input , ctx ) => {
const paso = await ctx . memory . get ( "paso" , "inicio" );
if ( paso === "inicio" ) {
await ctx . memory . set ( "paso" , "datos" , 3600 );
return { siguiente: "datos" };
}
if ( paso === "datos" ) {
await ctx . memory . set ( "nombre" , input . nombre , 3600 );
await ctx . memory . set ( "paso" , "confirmar" , 3600 );
return { siguiente: "confirmar" , nombre: input . nombre };
}
const nombre = await ctx . memory . get ( "nombre" , "" );
await ctx . memory . delete ( "paso" );
await ctx . memory . delete ( "nombre" );
return { completado: true , nombre };
}
interface Carrito {
items : Array <{ id : string ; nombre : string ; precio : number ; cantidad : number }>;
total : number ;
}
handler : async ( input , ctx ) => {
const vacio : Carrito = { items: [], total: 0 };
const carrito = await ctx . memory . getJson < Carrito >( "carrito" , vacio );
carrito . items . push ({
id: input . productoId ,
nombre: input . nombre ,
precio: input . precio ,
cantidad: input . cantidad ,
});
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_pin" , 0 );
if ( intentos >= 3 ) {
return { bloqueado: true , mensaje: "Demasiados intentos" };
}
const valido = input . pin === "1234" ;
if ( ! valido ) {
await ctx . memory . set ( "intentos_pin" , intentos + 1 , 1800 );
return { bloqueado: false , error: "PIN incorrecto" };
}
await ctx . memory . delete ( "intentos_pin" );
return { bloqueado: false , verificado: true };
}
Manejo de errores
import { MemoryApiError } from "@jelou/functions" ;
try {
await ctx . memory . set ( "paso" , "pago" , 3600 );
} catch ( err ) {
if ( err instanceof MemoryApiError ) {
ctx . log ( "Memory API falló" , { status: err . status , code: err . code });
if ( err . isRateLimit ()) {
return { error: "rate_limit" , retryAfter: 2 };
}
}
throw err ;
}
¿Cuándo usar ctx.memory vs una base de datos?
ctx.memoryBase de datos externa Alcance Por sesión (socketId) Global Persistencia Temporal (máx 24h) Permanente Configuración Cero — viene incluido Requiere connection string en secrets Ideal para Estado de conversación, cache temporal Datos de usuario, historial, config
Referencia completa Firma de cada método, tipos genéricos, MemoryApiError y mocks para testing.