Для конечных пользователей (операторы, инженеры в UI): User-Guide.

Прикладной гайд «как использовать API», а не протокол по проводам. Wire-формат (пути, коды ошибок, envelope) — NOVA-Grid-API-Spec.md / NOVA-Grid-API-Spec-Backend.md. Соответствие TS↔HTTP в createHttpDataSourceNOVA-Grid-HTTP-Client.md. Семантика фильтров — NOVA-Grid-Filter-Data-Contract.md. Источник типов: packages/grid/src/data-source.ts.

1. Модель: DataSource<Row>

Grid никогда не обращается к сети напрямую — он работает с любым объектом, реализующим контракт DataSource<Row> (packages/grid/src/data-source.ts). Единственный обязательный метод — getPage; всё остальное опционально и включает/выключает соответствующие возможности UI (панель фильтров, групповые действия, сохранённые наборы и т.д. — К-10/К-11 в NOVA-Grid-TZ-v1.4.md §6).

В репозитории есть две готовые реализации (packages/grid/src):

Реализация Файл Когда использовать

createMockDataSource

mock-data-source.ts

демки, тесты, Storybook — реализует весь контракт, включая subscribe

createHttpDataSource

http-data-source.ts

реальный backend по протоколу /api/v1/{entity}/…​

2. Быстрый старт: mock-источник

import { createMockDataSource } from "@nova/grid";
import { Grid } from "@nova/kit";

// createMockDataSource(rows, options) — возвращает DataSource<Row>,
// расширенный рычагами insert/patch/remove/snapshot (не часть контракта,
// только для демо/тестов — MockDataSourceControls).
const dataSource = createMockDataSource<Item>(initialRows, {
  getId: (row) => String(row.id),
});

const grid = new Grid<Item>(host, {
  id: "my-grid",
  columns,
  dataSource,
  getRowId: (row) => String(row.id),
  pageSize: 50,
});
grid.start();

dataSource.insert(newRow); // источник «меняется сам по себе», Grid увидит это через getPage/subscribe

Мок сам решает фильтрацию/сортировку/поиск в памяти; при необходимости кастомной семантики полей передавайте evaluateFieldCondition, matchesSearch и т.п. (см. MockDataSourceOptions в mock-data-source.ts).

3. Подключение к реальному backend

import { createHttpDataSource } from "@nova/grid";

const dataSource = createHttpDataSource<Item>({
  baseUrl: "http://127.0.0.1:8028",
  entity: "items",              // слаг сущности, /api/v1/{entity}/...
  getApiKey: () => apiKey,      // вызывается заново перед каждым запросом
  lang: "ru",                   // опционально — прокидывается в getPage как query lang
});

const grid = new Grid<Item>(host, {
  id: "items-grid",
  columns,
  dataSource,
  getRowId: (row) => String(row.id),
  pageSize: 50,
  enableToolbar: true,
  enableRowSelection: true,
});
grid.start();

Рабочий полный пример — apps/showcase/pages/grid-live.ts (маршрут /grid-live, включая pre-flight-проверку соединения и обвязку rowEditing.onSave).

getApiKey — функция, а не строка: ключ можно ротировать между запросами без пересоздания dataSource.

4. Загрузка страницы: getPage

Grid сам вызывает getPage при скролле/пагинации/смене фильтров — вручную дёргать его не нужно, но полезно знать форму запроса/ответа для отладки или pre-flight-проверок:

const response = await dataSource.getPage({
  offset: 0,          // 0-based, сколько строк пропустить
  pageSize: 50,
  sort: [{ field: "updatedAt", direction: "desc" }],
  filters: [{ kind: "field", field: "status", operator: "eq", value: "normal" }],
  combinator: "and",   // "and" | "or", по умолчанию "and"
  search: "alpha",
  signal: abortController.signal, // опционально — отмена запроса
});
// response: { records, hasMore, total }

filters поддерживает два вида условий — FieldCondition (поле/оператор/ значение) и NamedCondition (именованное условие с параметрами); за их семантику отвечает конкретный источник (NOVA-Grid-Filter-Data-Contract.md). Как описать FilterSchema в TypeScript (поля, операторы, named, enum) — NOVA-Grid-Filter-Schema-Frontend.md.

5. Метаданные для UI фильтров

Опциональные методы — Grid включает соответствующие части тулбара, только если источник их реализует:

const schema = await dataSource.getFilterSchema?.();   // доступные поля/операторы
const hints = await dataSource.getColumnHints?.();      // ширины/порядок/pinning по умолчанию
const options = await dataSource.getEnumValues?.({
  field: "status",
  filters: [],   // остальные активные условия — для контекстных значений
  search: "nor",
});

Подробный гайд по сборке объекта схемы — NOVA-Grid-Filter-Schema-Frontend.md. == 6. Персональные настройки колонок: columnSettings

columnSettings (get/save) — часть контракта DataSource<Row> (data-source.ts:171) и backend его реализует (GET/PUT /api/v1/{entity}/column-settings). createHttpDataSource реализует columnSettings через DTO-мост (ColumnSettings.columns ↔ wire-ключ columnSettings, см. NOVA-Grid-HTTP-Client.md); мок (createMockDataSource) реализует то же в памяти и годится как эталон поведения.

7. Изменение записи: create / update / delete

Каждый метод — опционален (источник может быть read-only) и возвращает OperationResult<T>, а не бросает исключение при отказе в праве:

type OperationResult<T> =
  | { status: "ok"; data: T }
  | { status: "forbidden"; message?: string };
const result = await dataSource.update!(rowId, { status: "critical" });
if (result.status === "forbidden") {
  throw new Error(result.message ?? "Изменение отклонено сервером");
}

Реальные сетевые/авторизационные ошибки (4xx/5xx) — это исключение HttpDataSourceError (см. §10), а не status: "forbidden": различие между «транспорт отказал» и «бизнес-правило отказало» важно для UI-обработки.

8. Массовые операции: bulkUpdate / bulkDelete

Принимают не список id, а декларативную выборку SelectionDescriptor — это позволяет применить операцию к «всем, кроме N» без пересылки списка:

await dataSource.bulkUpdate!(
  { mode: "all", exclude: ["5", "9"], filters: [], search: "" },
  { status: "critical" },
);

await dataSource.bulkDelete!({ mode: "manual", include: ["1", "2", "3"] });

Оба возвращают OperationResult<{ affected: number }>.

9. Сохранённые наборы: savedSets

const sets = await dataSource.savedSets?.list();
const result = await dataSource.savedSets?.save?.({
  name: "Критичные EU",
  scope: "personal",
  filters: [...],
  search: "",
  sort: [...],
  columns: [...],
});
if (result?.status === "ok") {
  await dataSource.savedSets?.delete?.(result.data.id);
}

save/delete дополнительно опциональны внутри savedSets — источник вправе отдавать наборы только на чтение.

10. Обработка ошибок HTTP-источника

createHttpDataSource бросает HttpDataSourceError на любой не-2xx ответ:

import { HttpDataSourceError } from "@nova/grid";

try {
  await dataSource.getPage(request);
} catch (err) {
  if (err instanceof HttpDataSourceError) {
    // err.status, err.code (например "UNAUTHORIZED"), err.message, err.method, err.url
  }
  throw err;
}

AbortError (отмена через request.signal) долетает как есть — Grid полагается на это в собственном load-controller и не должен гаситься здесь.

11. Realtime: subscribe

Часть контракта (точка расширения на будущее), но не реализована в createHttpDataSource — на живом backend нет маршрута /events (SSE). Мок реализует subscribe полностью — удобно для демо push-обновлений. На реальном backend полагайтесь на обычный poll/reload после write.

12. Куда дальше

Вопрос Документ

Точные пути, коды ошибок, параметры запроса

NOVA-Grid-API-Spec.md

То же с точки зрения backend + черновик OpenAPI

NOVA-Grid-API-Spec-Backend.md

Соответствие DataSource<Row> ↔ HTTP построчно

NOVA-Grid-HTTP-Client.md

Семантика операторов фильтрации, FilterSchema

NOVA-Grid-Filter-Data-Contract.md

Как описать схему фильтров в TS (frontend)

NOVA-Grid-Filter-Schema-Frontend.md

Общая архитектура Grid-компонента

NOVA-Grid-Architecture.md

Что не реализовано и запланировано

NOVA-Grid-API-Next-Steps.md