O que é ctx.memory?
ctx.memory é um armazenamento chave-valor por sessão baseado em Redis. Está disponível automaticamente quando a requisição vem de uma conversa ativa (possui um socketId) e a empresa tem uma chave de API de workflow configurada.
Casos de uso típicos:
- Fluxos multi-etapa — lembrar em qual etapa o usuário está
- Carrinhos de compra — acumular produtos durante a conversa
- Contadores — limitar tentativas de login, rastreamento de retentativas
- Preferências temporárias — idioma, formato, filtros durante a sessão
Quick start
import { define, z } from "@jelou/functions";
export default define({
name: "registration-flow",
description: "Multi-step registration with session memory",
input: z.object({
answer: z.string().optional(),
}),
handler: async (input, ctx) => {
if (!ctx.memory.available) {
return { error: "Memory not available outside of a conversation" };
}
const step = await ctx.memory.get("step", "start");
if (step === "start") {
await ctx.memory.set("step", "name", 3600);
return { question: "Qual é o seu nome?" };
}
if (step === "name") {
await ctx.memory.set("name", input.answer || "", 3600);
await ctx.memory.set("step", "email", 3600);
return { question: "Qual é o seu e-mail?" };
}
const name = await ctx.memory.get("name", "");
await ctx.memory.delete("step");
await ctx.memory.delete("name");
return { completed: true, name, email: input.answer };
},
});
Verificar disponibilidade
if (!ctx.memory.available) {
ctx.log("Memory not available — no socketId or workflow API key");
return { error: "memory_unavailable" };
}
ctx.memory.available é false quando o socketId está ausente (requisições fora de uma conversa) ou a chave de API de workflow não está configurada. Chamar métodos em um cliente indisponível lança um Error.
Primitivos vs JSON
Use set()/get() para valores simples e setJson()/getJson() para objetos:
await ctx.memory.set("step", "confirmation", 3600);
const step = await ctx.memory.get("step", "start");
await ctx.memory.setJson("cart", { items: [], total: 0 }, 86400);
const cart = await ctx.memory.getJson("cart", { items: [], total: 0 });
O tipo de retorno de get() corresponde ao tipo do valor padrão:
const name = await ctx.memory.get("name", "anonymous"); // string
const attempts = await ctx.memory.get("attempts", 0); // number
const verified = await ctx.memory.get("verified", false); // boolean
TTL (time to live)
Todos os valores expiram automaticamente. O TTL é especificado em segundos.
| Método | TTL | Máximo |
|---|
set() | Opcional | 86.400 (24h) |
setJson() | Obrigatório | 86.400 (24h) |
await ctx.memory.set("step", "payment"); // no explicit TTL
await ctx.memory.set("step", "payment", 1800); // expires in 30 min
await ctx.memory.setJson("cart", cart, 86400); // expires in 24h (maximum)
Limites
| Restrição | Valor |
|---|
Comprimento máximo de set() | 255 caracteres |
| TTL máximo | 86.400 segundos (24h) |
| Escopo | Por sessão (socketId) |
Valores de set() que excedem 255 caracteres lançam um Error. Para dados maiores, use setJson().
Padrões comuns
Fluxo multi-etapa
Carrinho de compras
Limite de tentativas
handler: async (input, ctx) => {
const step = await ctx.memory.get("step", "start");
if (step === "start") {
await ctx.memory.set("step", "data", 3600);
return { next: "data" };
}
if (step === "data") {
await ctx.memory.set("name", input.name, 3600);
await ctx.memory.set("step", "confirm", 3600);
return { next: "confirm", name: input.name };
}
const name = await ctx.memory.get("name", "");
await ctx.memory.delete("step");
await ctx.memory.delete("name");
return { completed: true, name };
}
interface Cart {
items: Array<{ id: string; name: string; price: number; quantity: number }>;
total: number;
}
handler: async (input, ctx) => {
const empty: Cart = { items: [], total: 0 };
const cart = await ctx.memory.getJson<Cart>("cart", empty);
cart.items.push({
id: input.productId,
name: input.name,
price: input.price,
quantity: input.quantity,
});
cart.total = cart.items.reduce(
(sum, i) => sum + i.price * i.quantity, 0
);
await ctx.memory.setJson("cart", cart, 86400);
return { cart };
}
handler: async (input, ctx) => {
const attempts = await ctx.memory.get("pin_attempts", 0);
if (attempts >= 3) {
return { blocked: true, message: "Too many attempts" };
}
const valid = input.pin === "1234";
if (!valid) {
await ctx.memory.set("pin_attempts", attempts + 1, 1800);
return { blocked: false, error: "Incorrect PIN" };
}
await ctx.memory.delete("pin_attempts");
return { blocked: false, verified: true };
}
Tratamento de erros
import { MemoryApiError } from "@jelou/functions";
try {
await ctx.memory.set("step", "payment", 3600);
} catch (err) {
if (err instanceof MemoryApiError) {
ctx.log("Memory API failed", { status: err.status, code: err.code });
if (err.isRateLimit()) {
return { error: "rate_limit", retryAfter: 2 };
}
}
throw err;
}
Quando usar ctx.memory vs um banco de dados?
| ctx.memory | Banco de dados externo |
|---|
| Escopo | Por sessão (socketId) | Global |
| Persistência | Temporária (máx 24h) | Permanente |
| Configuração | Zero — incluído | Requer connection string nos secrets |
| Ideal para | Estado da conversa, cache temporário | Dados de usuários, histórico, config |