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

Помимо взаимодействия с публичным API Трекера, плагину доступно собственное JSON-хранилище — storageApi. Оно работает на стороне хоста, поэтому данные не зависят от устройства пользователя и доступны во всех вкладках, где открыт плагин.

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

Контексты

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

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

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

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

Общая запись

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

Базовое использование

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

import { storageApi } from '@weavix/sdk-react';
// или
import { storageApi } from '@weavix/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 },
});

Бакеты

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

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

Если bucket не передан, хост подставит 'default'. Невалидный по формату или длине ключ приведет к ошибке BAD_KEY.

API

storageApi.orgShared.get(bucket?)

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

Параметры:

Параметр Тип По умолчанию Описание
bucket `string undefined` хост подставляет 'default'

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

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)

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

Параметры:

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

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

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

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

Версионирование

Хранилище работает по схеме оптимистических обновлений: у каждой записи есть version, и patch должен передавать ожидаемую версию. Если в хосте уже более свежая версия — операция отклоняется с VERSION_CONFLICT.

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

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

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

Когда какой режим выбирать

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

Право на запись

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

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

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

Коды ошибок

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

import {
    VERSION_CONFLICT,
    DATA_TOO_LARGE,
    BAD_KEY,
} from '@weavix/sdk-react';
Константа Код Когда
VERSION_CONFLICT 1010 Переданный version не совпал с текущим
DATA_TOO_LARGE 1011 Размер записи после операции превысил 256 KiB
BAD_KEY 1012 bucket не прошел валидацию формата или длины

Общие коды ошибок API (валидация, scope и т.д.) — в разделе Коды ошибок API.

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

import {
    storageApi,
    PluginActionError,
    VERSION_CONFLICT,
} from '@weavix/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;
    }
}

Типы

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

StorageRecord

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

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

type StorageContextType = 'orgShared';

Ограничения

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