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.
src/commands/warn.ts
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.
Tout ce qu’un store déclare et fait est dans la référence.
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 unguild), 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() :
src/tasks/digest.ts
in(). Pour des données identiques partout, déclarez le store avec scope: 'global' : il ne prend alors aucun serveur.
src/settings.ts
Ce que fait un store
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, 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 :
src/events/count-messages.ts
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.
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'.
src/commands/verify.ts
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, 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.
Dans Redis
Un store peut garder ses données dans Redis plutôt que dans un fichier : ditesstorage: '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.
src/sessions.ts
.env
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à :
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 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. Supprimezdata/<nom>.json pour repartir d’un store vide.
Référence de store()
Chaque champ d’un store, et ce que chaque appel renvoie.
Tâches
Quelque chose à faire avec ce que le bot garde, régulièrement.