¿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.memory | Base 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 |