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

# Storage

> Keep what your bot learns from one restart to the next, per server, with a store() declaration

A store is declared with `store()`, in any file of `src/`, and exported. It keeps data from one restart to the next, typed by what it holds, and per server unless you say otherwise.

```ts src/commands/warn.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command, store } from 'chapterjs';

interface Warning {
  reason: string;
  by: string;
  at: number;
}

export const warnings = store<Warning[]>();

export default command({
  name: 'warn',
  description: 'Warns a member',
  options: {
    who: { type: 'user', description: 'Who', required: true },
    reason: { type: 'string', description: 'Why', required: true },
  },
  async run({ interaction, options, user }) {
    const list = await warnings.update(options.who.id, (list = []) => [
      ...list,
      { reason: options.reason, by: user.id, at: Date.now() },
    ]);
    await interaction.reply(`${options.who} now has ${list.length} warning(s).`);
  },
});
```

That is all. The name of the export is the name of the store (`warnings`), and its data lives in `data/warnings.json`, at the root of your project. The command runs in a server, so the store reads and writes the warnings of that server: the key is `options.who.id`, nothing more.

> [!NOTE]
>
> Everything a store declares and does is in the [reference](https://www.chapterjs.org/reference/store.md).

## Per server, without saying so

A store is per server by default. In a function that runs in a server (a command, a button, a form, an event that has a `guild`), the framework knows which server, and the store works on its data: `warnings.get(member.id)` in server A and in server B read two different things, with the same line.

Outside a server (a task, which runs for every server; an event of a private message; code that looks at another server), say which one with `in()`:

```ts src/tasks/digest.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { task } from 'chapterjs';
import { warnings } from '../commands/warn';

export default task({
  cron: '0 9 * * 1',
  async run({ guilds }) {
    for (const guild of guilds.values()) {
      const entries = await warnings.in(guild).entries();
      console.log(`${guild.name}: ${entries.length} member(s) with warnings`);
    }
  },
});
```

A store per server used where no server is known stops with a message saying to use `in()`. For data that is the same everywhere, declare the store with `scope: 'global'`: it then takes no server at all.

```ts src/settings.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { store } from 'chapterjs';

export const settings = store<{ maintenance: boolean }>({ scope: 'global' });
```

## What a store does

| Call | What it does |
| - | - |
| `get(key)` | The value, or `undefined` when there is none, or it expired |
| `set(key, value, options?)` | Keeps a value |
| `update(key, change, options?)` | Reads, changes and keeps in one go: `change` receives the value, or `undefined`, and returns the new one. Nothing written meanwhile is lost, even by another process |
| `increment(key, by?, options?)` | Adds `by` (1 by default) to a number, from 0, in one go; resolves with the new number. Only on a store of numbers |
| `delete(key)` | Forgets a value; `true` when there was one |
| `getMany(keys)` | The values of several keys, in their order |
| `setMany([[key, value], ...], options?)` | Keeps several values; every one is checked before any is written |
| `deleteAll(prefix?)` | Forgets every value under `prefix`, or every value of the server (of the store, for a global one); resolves with how many |
| `entries(prefix?)` | Every entry (`key`, `value`, `expires`), or the ones whose key starts with `prefix` |
| `ttl(key)` | How long the value is still kept, in milliseconds: `null` for forever, `undefined` for none |
| `in(guild)` | The same store, for one server |

A key is a text, or a list of texts for a key made of several parts: `[channel.id, message.id]`. `entries(channel.id)` then lists what that channel has.

Two `update()` at once on the same key never lose one another, whether they run in one process, in [several](https://www.chapterjs.org/run/start.md#large-bots), or on several machines sharing a Redis: `change` may then run a second time, on the value the other one kept, so keep it a plain computation (no message sent from it). For a counter, `increment()` says it in one word:

```ts src/events/count-messages.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event, store } from 'chapterjs';

export const sent = store<number>({ storage: 'redis' });

export default event({
  name: 'messageCreate',
  async run({ message }) {
    const count = await sent.increment(message.author.id);
    if (count === 100) await message.reply('That was your 100th message here!');
  },
});
```

A value is JSON: texts, numbers, booleans, lists and plain objects. A `Date`, a `Map` or a class instance would not come back the same, so `set()` refuses them and says what to keep instead (an id, a text, a number). Every call returns a promise.

> [!TIP]
>
> To keep a score per member in order (xp, levels, best times) and show who is first, use a [ranking](https://www.chapterjs.org/rankings.md).

## Expiration

`expires` says how long a value is kept: on the store, for every value, or on one `set()` or `update()`. A duration is a number and a unit, `s`, `m`, `h` or `d`, like `'10m'` or `'30d'`.

```ts src/commands/verify.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command, store } from 'chapterjs';

export const codes = store<string>({ expires: '10m' });

export default command({
  name: 'verify',
  description: 'Sends you a code that lasts ten minutes',
  async run({ interaction, user }) {
    const code = Math.random().toString(36).slice(2, 8);
    await codes.set(user.id, code);
    await codes.set(['last', user.id], code, { expires: '1d' });
    await interaction.reply({ content: `Your code: ${code}`, ephemeral: true });
  },
});
```

An expired value is gone: `get()` answers `undefined`, `entries()` leaves it out, and the file forgets it. `{ expires: null }` keeps one value forever in a store that expires; a `Date` sets the moment itself. `update()` keeps the expiration the value had, unless you give another.

## Where the data is

One file per store, `data/<name>.json`, in the folder of your project. It is written shortly after each change, in one piece, so a crash never leaves a half-written file; what is pending when the bot stops is written then. The folder is in the `.gitignore` of a new project: it is your data, not your code.

The bot keeps the data in memory and the file is its copy, so a store is for what fits in memory: settings, scores, warnings, who opened what. For millions of rows, use a database.

In production on [several processes](https://www.chapterjs.org/run/start.md#large-bots), the data has one home, the process you started: the others ask it, so a command on any shard reads what the others wrote. Nothing to set up.

> [!WARNING]
>
> `chapterjs dev` and `chapterjs start` in the same folder share the same `data/`. With two bots in one folder, what you try in dev is what production reads.

## In Redis

A store can keep its data in Redis instead of a file: say `storage: 'redis'` when you declare it, and give the address of your Redis in `.env`. Nothing else changes: same calls, same keys, same scope, same expiration.

```ts src/sessions.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { store } from 'chapterjs';

interface Session {
  startedAt: number;
}

export const sessions = store<Session>({ storage: 'redis', expires: '1h' });
```

```bash .env theme={"theme":{"light":"github-light","dark":"vesper"}}
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=
```

`REDIS_URL` is the address, `redis://host:port` (`rediss://` for a TLS connection, `/1` at the end for a database other than the first); a user and a password can be in it (`redis://user:password@host:6379`), or the password in `REDIS_PASSWORD`, which wins. Only the stores that say `storage: 'redis'` go there: with `REDIS_URL` set and no such store, nothing is even connected. The bot connects when it starts and says which stores it keeps there:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
ℹ The store sessions is kept in Redis at localhost:6379
```

Each value is one key, `sessions:<server id>:<key>` (`sessions:<key>` for a global store), with the JSON of the value and its expiration, so you can look at it with `redis-cli`. In `chapterjs dev`, every key starts with `dev:`, so a development bot and a production bot on one Redis never read each other's data. Still, give each its own Redis, as you give each its own token: a shared one is a shared outage, and the same memory.

Redis is for what expires, first: sessions, codes, cooldowns, drafts, what several processes or [several machines](https://www.chapterjs.org/run/start.md#large-bots) must share, and what must survive a host without a persistent disk. Durable data works there too, with one thing to know: the data does not move with the store. A store that goes from a file to Redis starts from what Redis has, and `data/<name>.json` stays on disk, unread.

A store kept in Redis without `REDIS_URL` is reported when the files load, and refuses to serve until it is set. A Redis that does not answer stops `chapterjs dev` and `chapterjs start` before they reach Discord, with the reason. Once the bot runs, a Redis that goes away is said once with `⚠`, the bot keeps running (the calls of the stores kept there fail, the person is told something went wrong), the connection is tried again by itself with a growing wait, and `↻` says when it is back. `chapterjs test` needs no Redis: a store kept there runs in memory, like the others.

## In development

Saving a file keeps the data: a store is tied to its name, not to the file. Two stores with the same name in two files are reported, and the later one is left out: they would share a file. Delete `data/<name>.json` to start a store empty.

> [card] [store() reference](https://www.chapterjs.org/reference/store.md)
>
> Every field of a store, and what each call returns.

> [card] [Tasks](https://www.chapterjs.org/tasks.md)
>
> Something to do with what the bot keeps, every so often.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.