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

# Stockage

> Garder ce que votre bot apprend d’un redémarrage à l’autre, par serveur, avec une déclaration store()

Un store se déclare avec `store()`, dans n’importe quel fichier de `src/`, et s’exporte. Il garde des données d’un redémarrage à l’autre, typées par ce qu’il contient, et par serveur sauf si vous dites autrement.

```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: 'Avertit un membre',
  options: {
    who: { type: 'user', description: 'Qui', required: true },
    reason: { type: 'string', description: 'Pourquoi', 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} a maintenant ${list.length} avertissement(s).`);
  },
});
```

C’est tout. Le nom de l’export est le nom du store (`warnings`), et ses données vivent dans `data/warnings.json`, à la racine de votre projet. La commande tourne dans un serveur, donc le store lit et écrit les avertissements de ce serveur : la clé est `options.who.id`, rien de plus.

> [!NOTE]
>
> Tout ce qu’un store déclare et fait est dans la [référence](https://www.chapterjs.org/fr/reference/store.md).

## Par serveur, sans le dire

Un store est par serveur par défaut. Dans une fonction qui tourne dans un serveur (une commande, un bouton, un formulaire, un événement qui a un `guild`), le framework sait lequel, et le store travaille sur ses données : `warnings.get(member.id)` dans le serveur A et dans le serveur B lisent deux choses différentes, avec la même ligne.

Hors d’un serveur (une tâche, qui tourne pour tous les serveurs ; un événement de message privé ; du code qui regarde un autre serveur), dites lequel avec `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} membre(s) avec des avertissements`);
    }
  },
});
```

Un store par serveur utilisé là où aucun serveur n’est connu s’arrête avec un message qui dit d’utiliser `in()`. Pour des données identiques partout, déclarez le store avec `scope: 'global'` : il ne prend alors aucun serveur.

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

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

## Ce que fait un store

| Appel | Ce qu’il fait |
| - | - |
| `get(key)` | La valeur, ou `undefined` quand il n’y en a pas, ou qu’elle a expiré |
| `set(key, value, options?)` | Garde une valeur |
| `update(key, change, options?)` | Lit, change et garde d’un trait : `change` reçoit la valeur, ou `undefined`, et renvoie la nouvelle. Rien de ce qui est écrit entre-temps n’est perdu, même par un autre processus |
| `increment(key, by?, options?)` | Ajoute `by` (1 par défaut) à un nombre, depuis 0, d’un trait ; renvoie le nouveau nombre. Seulement sur un store de nombres |
| `delete(key)` | Oublie une valeur ; `true` quand il y en avait une |
| `getMany(keys)` | Les valeurs de plusieurs clés, dans leur ordre |
| `setMany([[key, value], ...], options?)` | Garde plusieurs valeurs ; chacune est vérifiée avant qu’aucune ne soit écrite |
| `deleteAll(prefix?)` | Oublie chaque valeur sous `prefix`, ou chaque valeur du serveur (du store, pour un store global) ; renvoie combien |
| `entries(prefix?)` | Chaque entrée (`key`, `value`, `expires`), ou celles dont la clé commence par `prefix` |
| `ttl(key)` | Combien de temps la valeur est encore gardée, en millisecondes : `null` pour toujours, `undefined` pour aucune |
| `in(guild)` | Le même store, pour un serveur |

Une clé est un texte, ou une liste de textes pour une clé en plusieurs parties : `[channel.id, message.id]`. `entries(channel.id)` liste alors ce que ce salon a.

Deux `update()` en même temps sur la même clé ne se perdent jamais l’un l’autre, qu’ils tournent dans un processus, dans [plusieurs](https://www.chapterjs.org/fr/run/start.md#gros-bots), ou sur plusieurs machines qui partagent un Redis : `change` peut alors tourner une seconde fois, sur la valeur que l’autre a gardée, donc gardez-le un simple calcul (aucun message envoyé depuis lui). Pour un compteur, `increment()` le dit en un mot :

```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('C’était votre 100e message ici !');
  },
});
```

Une valeur est du JSON : textes, nombres, booléens, listes et objets simples. Une `Date`, une `Map` ou l’instance d’une classe ne reviendrait pas pareille, donc `set()` les refuse et dit quoi garder à la place (un id, un texte, un nombre). Chaque appel renvoie une promesse.

> [!TIP]
>
> Pour garder un score par membre dans l’ordre (xp, niveaux, meilleurs temps) et montrer qui est premier, utilisez un [classement](https://www.chapterjs.org/fr/rankings.md).

## Expiration

`expires` dit combien de temps une valeur est gardée : sur le store, pour chaque valeur, ou sur un `set()` ou un `update()`. Une durée est un nombre et une unité, `s`, `m`, `h` ou `d`, comme `'10m'` ou `'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: 'Vous envoie un code qui dure dix 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: `Votre code : ${code}`, ephemeral: true });
  },
});
```

Une valeur expirée a disparu : `get()` répond `undefined`, `entries()` la laisse de côté, et le fichier l’oublie. `{ expires: null }` garde une valeur pour toujours dans un store qui expire ; une `Date` fixe le moment lui-même. `update()` garde l’expiration que la valeur avait, sauf si vous en donnez une autre.

## Où sont les données

Un fichier par store, `data/<nom>.json`, dans le dossier de votre projet. Il est écrit peu après chaque changement, d’un seul bloc, pour qu’un plantage ne laisse jamais un fichier à moitié écrit ; ce qui est en attente quand le bot s’arrête est écrit à ce moment-là. Le dossier est dans le `.gitignore` d’un nouveau projet : ce sont vos données, pas votre code.

Le bot garde les données en mémoire et le fichier en est la copie, donc un store est pour ce qui tient en mémoire : réglages, scores, avertissements, qui a ouvert quoi. Pour des millions de lignes, utilisez une base de données.

En production sur [plusieurs processus](https://www.chapterjs.org/fr/run/start.md#gros-bots), les données ont un seul foyer, le processus que vous avez lancé : les autres le lui demandent, donc une commande sur n’importe quel shard lit ce que les autres ont écrit. Rien à configurer.

> [!WARNING]
>
> `chapterjs dev` et `chapterjs start` dans le même dossier partagent le même `data/`. Avec deux bots dans un dossier, ce que vous essayez en dev est ce que la production lit.

## Dans Redis

Un store peut garder ses données dans Redis plutôt que dans un fichier : dites `storage: 'redis'` en le déclarant, et donnez l’adresse de votre Redis dans `.env`. Rien d’autre ne change : mêmes appels, mêmes clés, même portée, même 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` est l’adresse, `redis://hôte:port` (`rediss://` pour une connexion TLS, `/1` à la fin pour une base autre que la première) ; un utilisateur et un mot de passe peuvent y être (`redis://user:password@host:6379`), ou le mot de passe dans `REDIS_PASSWORD`, qui l’emporte. Seuls les stores qui disent `storage: 'redis'` y vont : avec `REDIS_URL` réglé et aucun store de ce genre, rien n’est même connecté. Le bot se connecte au démarrage et dit quels stores il garde là :

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

Chaque valeur est une clé, `sessions:<id du serveur>:<clé>` (`sessions:<clé>` pour un store global), avec le JSON de la valeur et son expiration, donc vous pouvez la regarder avec `redis-cli`. Dans `chapterjs dev`, chaque clé commence par `dev:`, pour qu’un bot de développement et un bot de production sur un même Redis ne lisent jamais les données l’un de l’autre. Donnez quand même à chacun son Redis, comme vous donnez à chacun son token : un Redis partagé, c’est une panne partagée, et la même mémoire.

Redis est d’abord pour ce qui expire : sessions, codes, délais, brouillons, ce que plusieurs processus ou [plusieurs machines](https://www.chapterjs.org/fr/run/start.md#gros-bots) doivent partager, et ce qui doit survivre à un hébergeur sans disque persistant. Des données durables y marchent aussi, avec une chose à savoir : les données ne suivent pas le store. Un store qui passe d’un fichier à Redis repart de ce que Redis contient, et `data/<nom>.json` reste sur le disque, sans être lu.

Un store gardé dans Redis sans `REDIS_URL` est signalé au chargement des fichiers, et refuse de servir tant qu’il n’est pas réglé. Un Redis qui ne répond pas arrête `chapterjs dev` et `chapterjs start` avant qu’ils n’atteignent Discord, avec la raison. Une fois le bot lancé, un Redis qui disparaît est dit une fois avec `⚠`, le bot continue (les appels des stores gardés là échouent, la personne apprend que quelque chose s’est mal passé), la connexion est retentée toute seule avec une attente qui grandit, et `↻` dit quand il est revenu. `chapterjs test` n’a besoin d’aucun Redis : un store gardé là tourne en mémoire, comme les autres.

## En développement

Enregistrer un fichier garde les données : un store est lié à son nom, pas au fichier. Deux stores du même nom dans deux fichiers sont signalés, et le second est mis de côté : ils partageraient un fichier. Supprimez `data/<nom>.json` pour repartir d’un store vide.

> [card] [Référence de store()](https://www.chapterjs.org/fr/reference/store.md)
>
> Chaque champ d’un store, et ce que chaque appel renvoie.

> [card] [Tâches](https://www.chapterjs.org/fr/tasks.md)
>
> Quelque chose à faire avec ce que le bot garde, régulièrement.


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