Хранилище данных плагина
Помимо взаимодействия с публичным API Трекера, плагину доступно собственное JSON-хранилище — storageApi. Оно работает на стороне платформы, поэтому данные не зависят от устройства пользователя и доступны во всех вкладках, где открыт плагин.
Хранилище удобно для настроек плагина, кешей справочников и любых данных, которые не нужно сохранять в Трекере как сущность. Область видимости записи можно ограничить организацией, текущим пользователем или отдельным ресурсом Трекера.
Контексты
Каждая запись хранится в контексте — он определяет область видимости данных.
Доступны следующие контексты:
orgShared— данные общие на всю организацию. Запись видят все пользователи плагина в этой организации. Подходит для общих настроек плагина и кешей справочников.user— персональные данные текущего пользователя. Идентификатор пользователя передавать не нужно: платформа определяет его по авторизованной сессии. Подходит для личных настроек интерфейса, выбранных фильтров и состояния элементов.resource— данные конкретного ресурса Трекера. Ресурс задается обязательным параметромresourceId, напримерissue:MYQUEUE-123,queue:MYQUEUE,board:42,project:<идентификатор>,portfolio:<идентификатор>илиgoal:<идентификатор>. Запись общая для пользователей, которые обращаются к тому же ресурсу.
Во всех контекстах право текущего пользователя изменять прочитанную запись указано в поле canWrite.
Общая запись
Контекст orgShared — это общая запись на всю организацию. Не сохраняйте в ней персональные данные пользователя и помните, что параллельные процессы записи видят изменения друг друга. Для совместной работы используйте версионирование и merge-семантику patch.
Ресурсная запись
Контекст resource не привязан к пользователю. Все процессы записи с одинаковыми resourceId и bucket видят изменения друг друга. Передавайте канонический идентификатор ресурса с типом: issue:<ключ>, queue:<ключ>, board:<идентификатор>, project:<идентификатор>, portfolio:<идентификатор> или goal:<идентификатор>.
Базовое использование
Объект storageApi экспортируется из SDK:
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 },
});
// Персональные настройки текущего пользователя
await storageApi.user.patch({
bucket: 'preferences',
data: { compactMode: true },
});
// Данные, связанные с конкретной задачей
await storageApi.resource.patch({
resourceId: 'issue:MYQUEUE-123',
bucket: 'panel',
data: { collapsed: true },
});
Бакеты
Внутри одного контекста плагин может держать несколько независимых записей — каждая идентифицируется параметром 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 | undefined` | Платформа подставляет '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; // полное содержимое записи (включая чужие поля)
storageApi.user.get(bucket?)
Возвращает персональную запись текущего пользователя по бакету или null, если записи еще нет. Платформа определяет пользователя по авторизованной сессии, поэтому идентификатор пользователя в метод не передается.
Параметры и возвращаемое значение совпадают с storageApi.orgShared.get.
const record = await storageApi.user.get('preferences');
if (record) {
console.log(record.data); // настройки только текущего пользователя
}
storageApi.user.patch(options)
Применяет merge-patch к персональной записи текущего пользователя. Параметры, версионирование и возвращаемое значение совпадают с storageApi.orgShared.patch.
const updated = await storageApi.user.patch({
bucket: 'preferences',
data: { compactMode: true },
});
storageApi.resource.get(options)
Возвращает запись конкретного ресурса Трекера или null, если записи еще нет.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
resourceId |
string |
— | Канонический идентификатор ресурса Трекера |
bucket |
`string | undefined` | Платформа подставляет 'default' |
Возвращает: Promise<StorageRecord | null>
const record = await storageApi.resource.get({
resourceId: 'issue:MYQUEUE-123',
bucket: 'panel',
});
storageApi.resource.patch(options)
Применяет merge-patch к записи конкретного ресурса. В отличие от orgShared и user, параметр resourceId обязателен в каждом вызове.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
resourceId |
string |
— | Канонический идентификатор ресурса Трекера |
bucket |
`string | undefined` | Платформа подставляет 'default' |
data |
Record<string, unknown> |
— | Merge-doc; null удаляет поле |
version |
`number | undefined` | auto-resolve |
Возвращает: Promise<StorageRecord> — полная запись после применения патча.
const updated = await storageApi.resource.patch({
resourceId: 'issue:MYQUEUE-123',
bucket: 'panel',
data: { collapsed: false },
});
Версионирование
Хранилище работает по схеме оптимистических обновлений: у каждой записи есть version, и patch должен передавать ожидаемую версию. Если в хранилище уже есть более свежая версия, операция завершается с ошибкой VERSION_CONFLICT.
Поведение patch зависит от того, передаете ли вы version:
-
С явным
version— отправляется один запрос. Любая ошибка, включаяVERSION_CONFLICT, пробрасывается вызывающему. Используйте этот режим, если приложение само держит актуальную версию и должно реагировать на конфликт — например, показать пользователю «данные изменились, перечитайте». Для создания записи на пустом бакете передавайтеversion: 0. -
Без
version— SDK сначала читает текущую версию черезget(для пустого бакета берется0), затем выполняетpatch. НаVERSION_CONFLICTцикл повторяется до двух раз, каждый повтор заново читает версию. Любая другая ошибка пробрасывается мгновенно. В худшем случае SDK обращается к платформе 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/tracker-plugin-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/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;
}
}
Типы
import type {
StorageRecord,
StorageContextType,
StorageGetPayload,
StoragePatchPayload,
} from '@weavix/tracker-plugin-sdk-react';
StorageRecord
Запись в хранилище.
type StorageRecord = {
/** Внутренний составной ключ записи (контекст + бакет). */
key: Array<
| { organization_shared: string }
| { user: string; bucket: string }
| { resource_id: string; service_type: string; bucket: string }
>;
/** Версия записи. Передавайте ее в patch для оптимистических обновлений. */
version: number;
/** Полезные данные записи. */
data: Record<string, unknown>;
/** Может ли текущий пользователь делать patch. */
canWrite: boolean;
/** Дата создания записи (ISO 8601). */
createdAt: string;
/** Дата последнего обновления (ISO 8601). */
updatedAt: string;
};
StorageContextType
type StorageContextType = 'orgShared' | 'user' | 'resource';
Ограничения
- Максимальный размер записи — 256 KiB после применения операции. Превышение —
DATA_TOO_LARGE. - Длина
bucket— от 1 до 64 символов. Разрешены латинские буквы, цифры, точка, дефис и подчеркивание. Невалидный ключ —BAD_KEY. - В контексте
resourceпараметрresourceIdобязателен, не может быть пустым и должен быть не длиннее 256 символов. patchбез явнойversionделает до 2 повторов наVERSION_CONFLICT. Дальше ошибка пробрасывается вызывающему.