---
metadata:
  - name: generator
    content: Diplodoc Platform v5.54.5
  - property: og:type
    content: article
  - property: article:section
    content: Платформа плагинов
  - property: og:title
    content: Хранилище данных плагина
  - property: article:tag
    content: Техническая инструкция
alternate:
  - https://yandex.ru/support/tracker/en/plugins/storage.md
  - https://yandex.ru/support/tracker/ru/plugins/storage.md
  - href: ru/plugins/storage.md
    type: text/markdown
    title: Markdown version
  - href: ../llms.txt
    type: text/markdown
    title: llms.txt
---
> **Documentation Index:** Fetch the complete configuration index at https://yandex.ru/support/tracker/ru/llms.txt


# Хранилище данных плагина

Помимо взаимодействия с [публичным API Трекера](https://yandex.ru/support/tracker/ru/plugins/publicApi.md), плагину доступно собственное JSON-хранилище — `storageApi`. Оно работает на стороне платформы, поэтому данные не зависят от устройства пользователя и доступны во всех вкладках, где открыт плагин.

Хранилище удобно для пользовательских настроек плагина, кешей справочников и любых данных, которые плагину нужно держать рядом с пользователем, но не сохранять в Трекере как сущность.

## Контексты {#contexts}

Каждая запись хранится в **контексте** — он определяет область видимости данных.

На сегодня доступен единственный контекст:

- **`orgShared`** — данные общие на всю организацию. Запись видят все пользователи плагина в этой организации. Право на запись платформа указывает в поле `canWrite` каждой прочитанной записи.

В будущем список контекстов может расшириться — например, контекст уровня пользователя. API спроектирован так, что добавление нового контекста не сломает существующий код.

{% note warning "Общая запись" %}

Контекст `orgShared` — это **общая** запись на всю организацию. Не сохраняйте в ней персональные данные пользователя и помните, что параллельные процессы записи видят изменения друг друга. Для совместной работы используйте [версионирование](#versioning) и merge-семантику `patch`.

{% endnote %}

## Базовое использование {#basic-usage}

Объект `storageApi` экспортируется из SDK:

```typescript
import { storageApi } from '@weavix/tracker-plugin-sdk-react';
// или
import { storageApi } from '@weavix/tracker-plugin-sdk-core';

// Чтение
const record = await storageApi.orgShared.get('settings');
// record: { data: { theme: 'dark' }, version: 5, canWrite: true, ... } | null

// Создание записи на пустом бакете
await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { theme: 'dark', notifications: true },
    version: 0,
});

// Обновление без знания текущей версии — SDK сам прочитает ее и повторит запрос при конфликтах
await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { count: 42 },
});

// Удаление отдельного поля — передайте null
await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { theme: null },
});
```

## Бакеты {#buckets}

Внутри одного контекста плагин может держать несколько независимых записей — каждая идентифицируется параметром `bucket`. Это просто строковый ключ. Запись с одним бакетом не пересекается с записью под другим.

```typescript
await storageApi.orgShared.patch({ bucket: 'settings', data: { theme: 'dark' } });
await storageApi.orgShared.patch({ bucket: 'cache', data: { fetchedAt: Date.now() } });
```

Если `bucket` не передан, платформа подставит `'default'`. Ключ некорректного формата или длины приведет к ошибке [`BAD_KEY`](#errors).

## API {#api}

### storageApi.orgShared.get(bucket?) {#storage-api-org-shared-get}

Возвращает текущую запись по бакету или `null`, если записи еще нет.

**Параметры:**

| Параметр | Тип                   | По умолчанию                  | Описание                     |
| -------- | --------------------- | ----------------------------- | ---------------------------- |
| `bucket` | `string | undefined` | Платформа подставляет `'default'` | Ключ записи внутри контекста |

**Возвращает:** `Promise<StorageRecord | null>`

```typescript
const record = await storageApi.orgShared.get('settings');

if (record) {
    console.log(record.data); // содержимое записи
    console.log(record.version); // текущая версия (для оптимистических обновлений)
    console.log(record.canWrite); // есть ли у текущего пользователя право писать
} else {
    // записи еще нет — ее можно создать через patch с version: 0
}
```

### storageApi.orgShared.patch(options) {#storage-api-org-shared-patch}

Merge-patch: поля из `data` накладываются на текущую запись поверх, значение `null` удаляет ключ. Возвращает полный объединенный `StorageRecord` с новой версией — включая поля, дописанные параллельными процессами записи.

**Параметры:**

| Параметр  | Тип                       | По умолчанию                 | Описание                       |
| --------- | ------------------------- | ---------------------------- | ------------------------------ |
| `bucket`  | `string`                  | Платформа подставляет `'default'` | Ключ записи                    |
| `data`    | `Record<string, unknown>` | —                            | Merge-doc; `null` удаляет поле |
| `version` | `number | undefined`     | auto-resolve                 | Ожидаемая текущая версия       |

**Возвращает:** `Promise<StorageRecord>` — полная запись после применения патча.

```typescript
const updated = await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { theme: 'dark' },
});

updated.version; // версия после патча
updated.data; // полное содержимое записи (включая чужие поля)
```

## Версионирование {#versioning}

Хранилище работает по схеме оптимистических обновлений: у каждой записи есть `version`, и `patch` должен передавать ожидаемую версию. Если в хранилище уже есть более свежая версия, операция завершается с ошибкой [`VERSION_CONFLICT`](#errors).

Поведение `patch` зависит от того, передаете ли вы `version`:

- **С явным `version`** — отправляется один запрос. Любая ошибка, включая `VERSION_CONFLICT`, пробрасывается вызывающему. Используйте этот режим, если приложение само держит актуальную версию и должно реагировать на конфликт — например, показать пользователю «данные изменились, перечитайте». Для создания записи на пустом бакете передавайте `version: 0`.

- **Без `version`** — SDK сначала читает текущую версию через `get` (для пустого бакета берется `0`), затем выполняет `patch`. На `VERSION_CONFLICT` цикл повторяется до двух раз, каждый повтор заново читает версию. Любая другая ошибка пробрасывается мгновенно. В худшем случае SDK обращается к платформе 6 раз: выполняет 3 пары запросов GET и PATCH. При каждом повторе в консоль записывается `console.warn`.

{% note info "Когда какой режим выбирать" %}

- Передавайте `version` явно, если вы загрузили запись на экран и пользователь ее редактирует — так конфликт с другим писателем не будет потерян молча.
- Не передавайте `version`, если изменение не зависит от того, что было раньше — например, инкрементируете счетчик или дописываете новое поле.

{% endnote %}

## Право на запись {#can-write}

Поле `canWrite` в `StorageRecord` показывает, может ли текущий пользователь делать `patch` в этой записи. Используйте его, чтобы заранее показать пользователю, что у него нет прав, не дожидаясь ошибки платформы.

```typescript
const record = await storageApi.orgShared.get('settings');

if (!record?.canWrite) {
    // спрятать или отключить элементы редактирования
}
```

## Коды ошибок {#errors}

Все ошибки `storageApi` — экземпляры `PluginActionError` с числовыми кодами. Константы экспортируются из SDK:

```typescript
import {
    VERSION_CONFLICT,
    DATA_TOO_LARGE,
    BAD_KEY,
} from '@weavix/tracker-plugin-sdk-react';
```

| Константа          | Код    | Когда                                                       |
| ------------------ | ------ | ----------------------------------------------------------- |
| `VERSION_CONFLICT` | `1010` | Переданный `version` не совпал с текущим                    |
| `DATA_TOO_LARGE`   | `1011` | Размер записи после операции превысил 256 KiB               |
| `BAD_KEY`          | `1012` | `bucket` не прошел валидацию формата или длины              |

Общие коды ошибок API (валидация, scope и т.д.) — в разделе [Коды ошибок API](https://yandex.ru/support/tracker/ru/plugins/publicApi.md#codeErrors).

**Пример обработки `VERSION_CONFLICT`:**

```typescript
import {
    storageApi,
    PluginActionError,
    VERSION_CONFLICT,
} from '@weavix/tracker-plugin-sdk-react';

try {
    await storageApi.orgShared.patch({
        bucket: 'settings',
        data: { theme: 'dark' },
        version: knownVersion,
    });
} catch (e) {
    if (e instanceof PluginActionError && e.code === VERSION_CONFLICT) {
        // данные изменились — перечитайте запись и предложите пользователю объединить изменения
        const fresh = await storageApi.orgShared.get('settings');
        // ...
    } else {
        throw e;
    }
}
```

## Типы {#types}

```typescript
import type {
    StorageRecord,
    StorageContextType,
    StorageGetPayload,
    StoragePatchPayload,
} from '@weavix/tracker-plugin-sdk-react';
```

### StorageRecord {#storage-record}

Запись в хранилище.

```typescript
type StorageRecord = {
    /** Внутренний составной ключ записи (контекст + бакет). */
    key: Array<{ organization_shared: string }>;
    /** Версия записи. Передавайте ее в patch для оптимистических обновлений. */
    version: number;
    /** Полезные данные записи. */
    data: Record<string, unknown>;
    /** Может ли текущий пользователь делать patch. */
    canWrite: boolean;
    /** Дата создания записи (ISO 8601). */
    createdAt: string;
    /** Дата последнего обновления (ISO 8601). */
    updatedAt: string;
};
```

### StorageContextType {#storage-context-type}

```typescript
type StorageContextType = 'orgShared';
```

## Ограничения {#limits}

- Максимальный размер записи — **256 KiB** после применения операции. Превышение — `DATA_TOO_LARGE`.
- Платформа проверяет формат и длину ключа бакета. Невалидный ключ — `BAD_KEY`.
- `patch` без явной `version` делает до 2 повторов на `VERSION_CONFLICT`. Дальше ошибка пробрасывается вызывающему.
