> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Memória de sessão

> Salve e leia estado por conversa com ctx.memory: fluxos multi-etapa, carrinhos, contadores e dados temporários sem banco de dados externo.

## 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

```typescript index.ts theme={null}
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

```typescript theme={null}
if (!ctx.memory.available) {
  ctx.log("Memory not available — no socketId or workflow API key");
  return { error: "memory_unavailable" };
}
```

<Warning>
  `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`.
</Warning>

## Primitivos vs JSON

Use `set()`/`get()` para valores simples e `setJson()`/`getJson()` para objetos:

```typescript theme={null}
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:

```typescript theme={null}
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) |

```typescript theme={null}
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

<Tabs>
  <Tab title="Fluxo multi-etapa">
    ```typescript theme={null}
    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 };
    }
    ```
  </Tab>

  <Tab title="Carrinho de compras">
    ```typescript theme={null}
    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 };
    }
    ```
  </Tab>

  <Tab title="Limite de tentativas">
    ```typescript theme={null}
    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 };
    }
    ```
  </Tab>
</Tabs>

## Tratamento de erros

```typescript theme={null}
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 |
