Skip to content

Модели

@virentia/core/models - модельный слой для доменных сущностей. Сущность описывается один раз - поля, связи, поведение; загрузка с сервера, поиск, редактирование и сериализация обратно используют это одно описание.

Используйте его, когда приложение работает со списками серверных сущностей: задачи, заказы, документы, элементы ленты. Слой убирает привычную рутинную обвязку - ручной маппинг между JSON и сторами, бухгалтерию id для связанных сущностей и самописную фильтрацию по массивам.

Установка

Модели входят в пакет ядра как сабпас. Схемы полей работают на TypeBox, объявленном опциональным peer-пакетом - его ставят только приложения, импортирующие сабпас моделей:

sh
pnpm add @virentia/core @sinclair/typebox
ts
import { collection, f, staticModel } from "@virentia/core/models";

Приложения, которые @virentia/core/models не импортируют, обходятся без @sinclair/typebox - он для них не ставится и не попадает в бандл.

Как устроен раздел

ПаттернИспользуйте, когда
Поля и данныеУ сущности типизированные поля, приходящие из JSON и уходящие в него.
ТрейтыНесколько моделей делят поля и поведение.
Коллекции и экземплярыСущностям нужен дом: создание, мерж, жизненный цикл.
Виды моделейВыбор между staticModel, model и обычным owner.
СвязиСущности ссылаются на другие или владеют дочерними.
Запросы и индексыСпискам нужны фильтрация, сортировка и быстрый поиск.
ОбъединенияОдин список смешивает сущности разных моделей.
Загрузка и сериализацияЭкземпляры сериализуются назад, id приходит с сервера позже.
UI-биндингиКомпоненты рендерят запросы и экземпляры напрямую.

Первая модель

Опишите сущность, создайте коллекцию в скоупе, загрузите JSON, запросите и сериализуйте обратно.

ts
import { scope, scoped } from "@virentia/core";
import { collection, f, staticModel } from "@virentia/core/models";

const Todo = staticModel({
  data: {
    title: f.string(),
    done: f.boolean(false).indexed(),
  },
});

const appScope = scope();

scoped(appScope, () => {
  const todos = collection(Todo);

  todos.add([
    { id: "1", title: "написать доку" },
    { id: "2", title: "выпустить релиз", done: true },
  ]);

  todos.count;                              // 2
  todos.where(Todo.done.eq(false)).ids;     // ["1"]

  const first = todos.get("1")!;

  first.done.value = true;
  todos.where(Todo.done.eq(false)).count;   // 0

  first.json(); // { id: "1", title: "написать доку", done: true }
});

Что происходит:

  • staticModel объявляет сущность один раз; Todo.done - дескриптор поля для запросов;
  • collection(Todo) - get-or-create на скоуп: тот же вызов возвращает ту же коллекцию, разные скоупы держат независимые данные;
  • add валидирует JSON и создаёт экземпляры; известный id мержится, а не дублируется;
  • first.done.value - обычная запись в стор: индекс и все запросы обновляются сразу;
  • json() сериализует через те же декларации полей, что разобрали вход.

model и staticModel

Оба вида одинаково объявляют поля, трейты и связи и одинаково используются. Разница - в том, как строятся экземпляры:

  • staticModel создаёт юниты один раз и разделяет их; экземпляр - дешёвый скоуп над общими юнитами. Берите для многочисленных однородных сущностей - элементов списков, лент, строк таблиц.
  • model выполняет setup на каждый экземпляр и может создавать юниты динамически. Берите для немногих индивидуальных сущностей - экранов, редакторов, долгоживущих рабочих областей.

Если сомневаетесь - начинайте со staticModel. Полное сравнение - включая случаи, когда лучше любой модели подходит обычный owner из ядра, - на странице Виды моделей.

Связанные разделы