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

# store()

> Everything store() declares and does: the scope, the expiration, the keys, and each call

`store()` declares a store, from any file of `src/`, and the name of its export is the name of the store. It takes an object, `StoreConfig`, or nothing.

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

export const warnings = store<string[]>({ expires: '30d' });
```

> [!NOTE]
>
> How to use a store, where its data is and how expiration works is in the [guide](https://www.chapterjs.org/storage.md). This page lists what exists.

## store()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
store<T>(config?: StoreConfig): Store<T>
```

Declares a store: data the bot keeps from one restart to the next, typed by what it holds. Export the result from any file of `src/`: the name of the export is the name of the store, and its file in `data/` (or its keys in Redis, with `storage: 'redis'`). Per server unless `scope: 'global'` is given, with an expiration if `expires` is.

## StoreConfig

What a file gives to `store()`.

**scope** `StoreScope`

`'guild'` (the default): the data is kept per server, and a function that runs in a server reads and writes the data of that server, with nothing to say. `'global'`: one set of data for the whole bot.

**storage** `StoreStorage`

Where the data is kept: `'file'` (the default), a file in the `data/` folder of the project, or `'redis'`, the Redis of `REDIS_URL` in `.env`. Only stores that say `'redis'` write anything there.

**expires** `Duration`

How long a value is kept, unless `set()` says otherwise: a number and a unit, `s`, `m`, `h` or `d`, like `'10m'`, `'1d'` or `'1h30m'`. Kept forever by default.

### StoreScope

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
type StoreScope = 'guild' | 'global';
```

Where the data of a store is kept apart: per server, or one for the whole bot.

### StoreStorage

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
type StoreStorage = 'file' | 'redis';
```

Where a store keeps its data: what `storage` of its config names.

## Store

What `store()` returns: a declaration the framework finds in the exports of a file. It is a `ScopedStore` for the server the code runs in, plus:

### in()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
in(guild: Guild | string): ScopedStore<T>
```

The same store, for one server: what a task, or a function that runs outside a server, uses to read and write the data of that server.

## ScopedStore

A store, read and written for one server or for the whole bot.

### get()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
get(key: StoreKey): Promise<T | undefined>
```

The value at `key`, or `undefined` when there is none, or it expired.

### set()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
set(key: StoreKey, value: T, options?: StoreSetOptions): Promise<void>
```

Keeps `value` at `key`. The value must be JSON: no `Date`, `Map` or class instance.

### update()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
update(key: StoreKey, change: (current: T | undefined) => T, options?: StoreSetOptions): Promise<T>
```

Reads the value at `key` and keeps what `change` returns, in one go: nothing written meanwhile is lost, even by another process. `change` receives `undefined` when there is no value yet, and may run again when the value changed in between: keep it a plain computation. The expiration stays what it was, unless `options` say otherwise.

### increment()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
increment(this: ScopedStore<number>, key: StoreKey, by?: number, options?: StoreSetOptions): Promise<number>
```

Adds `by` (1 by default, negative to take away) to the number at `key`, starting from 0, in one go like `update()`, and resolves with the new number. Only for a store of numbers.

### delete()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
delete(key: StoreKey): Promise<boolean>
```

Forgets the value at `key`. `true` when there was one.

### getMany()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
getMany(keys: readonly StoreKey[]): Promise<(T | undefined)[]>
```

The values at several keys at once, in their order: `undefined` for a key that has none.

### setMany()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
setMany(values: readonly (readonly [key: StoreKey, value: T])[], options?: StoreSetOptions): Promise<void>
```

Keeps several values at once, each with the same `options`. Every value is checked before anything is written.

### deleteAll()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
deleteAll(prefix?: StoreKey): Promise<number>
```

Forgets every value whose key starts with `prefix`, or every value of the store (of the server, for a store per server) without one. Resolves with how many there were.

### entries()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
entries(prefix?: StoreKey): Promise<StoreEntry<T>[]>
```

Every entry, or the ones whose key starts with `prefix`, in no particular order. Expired ones are left out.

### ttl()

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
ttl(key: StoreKey): Promise<number | null | undefined>
```

How long the value at `key` is still kept, in milliseconds: `null` when it is kept forever, `undefined` when there is no value.

## StoreKey

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
type StoreKey = string | readonly string[];
```

A key of a store: a text, or a list of texts for a key made of several parts (`[channel.id, message.id]`), which `entries()` can then list by prefix.

## StoreSetOptions

What `set()` and `update()` take besides the value.

**expires** `Duration | Date | null`

How long the value is kept: a duration like `'10m'`, a `Date`, or `null` to keep it forever. The `expires` of the store by default.

## StoreEntry

One entry of a store, as `entries()` lists them.

**key** `readonly string[]` *required*

The key, every part of it (the server left out for a store per server).

**value** `T` *required*

The value.

**expires** `Date | null` *required*

When the value expires, or `null` when it is kept forever.

## Where the data lives

One file per store, `data/<name>.json` at the root of the project: `{ "version": 1, "entries": [[key, value, expiresAt], ...] }`, with `expiresAt` in milliseconds since the epoch or `null`. Written shortly after a change, in one piece (a temporary file, then a rename), and when the bot stops. Expired entries are dropped when the file is read, when they are asked for, and every minute.

In production on several processes, the process you started keeps the data and answers the others over IPC.

With `storage: 'redis'`, one Redis key per value: `<name>:<part>:<part>` (the server id is the first part of a store per server; `:`, `*`, `?`, `[`, `]`, `\` and `%` inside a part are written `%XX`), its value the JSON of the value, its expiration set on the key (`PX`), so Redis forgets it alone. `chapterjs dev` puts `dev:` in front of every key. `entries()` is a `SCAN` on the prefix. Every process reaches Redis itself. `update()` and `increment()` are a transaction (`WATCH`, `MULTI`, `EXEC`), run again on the new value when another process wrote the key in between, on a connection of their own (at most 4 at once per process, besides the main one); the updates of one key in one process wait for each other. The address comes from `REDIS_URL` and `REDIS_PASSWORD` (see the [CLI reference](https://www.chapterjs.org/reference/cli.md#env)); a Redis that does not answer stops `dev` and `start` before Discord is reached, and the connection comes back by itself once it was made.

## Errors

| When | Message |
| - | - |
| `update()` never got through: the value changed 50 times meanwhile | `The value of hits changed 50 times while update() was trying to change it: it is written too often by too many at once. Keep what changes this often under several keys.` |
| `increment()` on a value that is not a number | `increment() adds to a number, and the value at ["a"] is a string: use update() to change it.` |
| `setMany()` with a value that is not JSON | `the value of item 1 is a Date, which JSON can't hold: …` (nothing is written) |
| A store per server is used where no server is known | `<name> is a store per server, and this code does not run in one: use <name>.in(guild) to say which server, or declare the store with scope: 'global'.` |
| A store that is not exported is used | `This store was not loaded by ChapterJS: a store is exported from a file of src/ (export const name = store()), and used once the bot started.` |
| A value is not JSON | `the value.list[0].at is a Date, which JSON can't hold: a store keeps JSON (texts, numbers, booleans, lists and plain objects). Keep what you need of it: an id, a text, a number.` |
| A key is empty, or not made of texts | `A key of a store is a text that is not empty, or a list of such texts with at least one.` |
| Two stores have one name | `✗ src/b.ts (hits) There is already a store named hits, in src/a.ts: the name of the export is the name of the store, so rename one of them.` |
| A file of `data/` was changed by hand | `data/hits.json is not a file ChapterJS wrote: fix it, or delete it to start the store empty.` |
| A store says `storage: 'redis'` and `REDIS_URL` is not set | `✗ src/sessions.ts (sessions) sessions is kept in Redis (storage: 'redis'), but REDIS_URL is not set: add it to .env (like redis://localhost:6379) and start again, or remove storage: 'redis' to keep its data in data/.` |
| Redis does not answer when the bot starts | `✗ Redis at localhost:6379 can't be reached: nothing answers there. Start Redis, or check REDIS_URL in .env.` |
| Redis refuses the password, or asks for one | `✗ Redis at localhost:6379 refused the password: check REDIS_PASSWORD (or the password in REDIS_URL) in .env.` / `✗ Redis at localhost:6379 asks for a password: set REDIS_PASSWORD (or the password in REDIS_URL) in .env.` |
| Redis goes away while the bot runs | `⚠ Redis at localhost:6379 closed the connection. Trying again every few seconds.` then `↻ Redis at localhost:6379 is back` |


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