# Хранилище и настройки

> Постоянное хранилище ключ/значение для карточки и для пакета, а также чтение и изменение формы настроек, которую приложение рисует по вашему манифесту.

Source: https://docs.neurosquad.ai/ru/card-sdk/api/storage-settings

## Хранилище

У страницы карточки нет `localStorage`, `indexedDB` и cookies — в её песочнице нет источника
(origin), к которому их можно привязать. Вместо них — `card.storage`: значения JSON, которые
приложение атомарно хранит на диске и которые переживают перезагрузки, усыпление и перезапуски.
Разрешение не нужно.

```ts
// This card on the canvas (instance scope).
const draft = await card.storage.get('draft', '')          // string, '' when missing
await card.storage.set('draft', `${draft}\nmore`)
await card.storage.set('runs', [{ at: Date.now(), failed: 3 }])
const runs = await card.storage.get<{ at: number; failed: number }[]>('runs')  // undefined when missing
await card.storage.delete('draft')
const keys = await card.storage.keys('run:')               // keys starting with a prefix
const { bytes, quota } = await card.storage.usage()
render(runs?.length, keys, bytes, quota)

// Shared by every copy of this card, on every canvas (package scope).
await card.storage.package.set('lastSync', Date.now())
card.storage.onChange(({ key, byInstance }) => {
  if (key === 'lastSync') render(`updated by card ${byInstance}`)
})
```

| Метод | Результат |
| --- | --- |
| `get(key)` | Значение или `undefined`. |
| `get(key, fallback)` | Значение или `fallback`. Результат типизирован по запасному значению. |
| `set(key, value)` | Сохраняет любое значение JSON. |
| `delete(key)` | |
| `keys(prefix?)` | `string[]` |
| `clear()` | Удаляет все ключи этой области. |
| `usage()` | `{ bytes, quota, keys }` |
| `onChange(handler)` | Другая копия этой карточки записала ключ **пакета**: `{ scope, key, byInstance }`. |

Те же методы есть у `card.storage.package`.

**Лимиты:** ключи до 256 символов, значения до 1 МБ, не больше 10 000 ключей; 5 МБ на карточку,
20 МБ на пакет. Превышение — `QUOTA_EXCEEDED`. Запись доходит до диска примерно за четверть секунды и
сбрасывается при выходе из приложения.

**Что с ним происходит:** хранилище карточки удаляется вместе с карточкой. Хранилище пакета
переживает удаление пакета, если пользователь не отметит **Also delete its saved data and secrets**.
Обновления хранилище не трогают — держите сохранённые структуры читаемыми для новых версий (храните
поле `version`, если можете их поменять).

> Сохраняйтесь до выгрузки: карточку, скрытую какое-то время, усыпляют, и её страница выбрасывается.
> Записывайте то, что ещё в памяти, в [`card.lifecycle.onSuspend`](https://docs.neurosquad.ai/ru/card-sdk/api/environment#lifecycle).

## Настройки

Объявите поля в [`settings`](https://docs.neurosquad.ai/ru/card-sdk/manifest#settings) манифеста; приложение нарисует форму (из
**⋯ → Settings…** карточки или по вызову `card.settings.open()`), проверит её и сохранит значения.
Ваша карточка их читает:

```ts
const command = card.settings.value<string>('command') ?? 'npm test'
const all = card.settings.values                         // defaults filled in, secrets never included
const hasKey = card.settings.hasSecret('apiKey')         // true when the user saved one

card.settings.onChange((snapshot) => {
  render(snapshot.values['command'], snapshot.secrets['apiKey'])
})

// Change your own non-secret settings (a toggle in your UI, say). Validated like the form.
await card.settings.set({ compact: true })

// Open the form on the card (only while it is visible).
if (!hasKey) await card.settings.open()

render(command, all)
```

| Член | Что это |
| --- | --- |
| `values` | Текущие значения, со значениями по умолчанию. Поддерживаются в актуальном виде. |
| `secrets` | `Record<key, boolean>`: какие секретные настройки заданы. |
| `value(key)` | Одно значение, тип задаёте вы. |
| `hasSecret(key)` | Задана ли секретная настройка. |
| `get()` | Свежий `SettingsSnapshot` из приложения. |
| `set(values)` | Меняет несекретные настройки; все копии карточки получают `settings.changed`. Неверные значения — `INVALID_PARAMS`. Не больше 60 в минуту. |
| `open()` | Открывает форму настроек на карточке. `NOT_VISIBLE`, если карточки нет на экране. |
| `onChange(handler)` | Пользователь сохранил форму, или `set` что-то поменял. |

**Области.** У поля с `"scope": "package"` одно значение на все копии вашей карточки; остальные —
свои у каждой карточки.

**Секреты.** Поле `secret` вводится только в собственном диалоге приложения — в форме настроек его
строка показывает лишь «задан» или «не задан» и открывает этот диалог на всё окно, — и хранится
приложением в зашифрованном виде. Карточка никогда не может его прочитать — ни через `values`, ни
каким-либо методом. Чтобы им воспользоваться, вставьте `{{secret:<key>}}` в
[заголовок сетевого запроса](https://docs.neurosquad.ai/ru/card-sdk/api/network#secrets); приложение подставит значение на выходе,
и только для хостов в интернете, выданных вашей карточке (никогда для `localhost`). Не делайте
внутри карточки собственное поле «вставьте ваш API-ключ»: тогда утечь ключ сможет уже по вашей вине,
а пользователям сказано, что NeuroSquad никогда не спрашивает ключи внутри карточки.
