Модели
@virentia/core/models - модельный слой для доменных сущностей. Сущность описывается один раз - поля, связи, поведение; загрузка с сервера, поиск, редактирование и сериализация обратно используют это одно описание.
Используйте его, когда приложение работает со списками серверных сущностей: задачи, заказы, документы, элементы ленты. Слой убирает привычную рутинную обвязку - ручной маппинг между JSON и сторами, бухгалтерию id для связанных сущностей и самописную фильтрацию по массивам.
Установка
Модели входят в пакет ядра как сабпас. Схемы полей работают на TypeBox, объявленном опциональным peer-пакетом - его ставят только приложения, импортирующие сабпас моделей:
pnpm add @virentia/core @sinclair/typeboximport { collection, f, staticModel } from "@virentia/core/models";Приложения, которые @virentia/core/models не импортируют, обходятся без @sinclair/typebox - он для них не ставится и не попадает в бандл.
Как устроен раздел
| Паттерн | Используйте, когда |
|---|---|
| Поля и данные | У сущности типизированные поля, приходящие из JSON и уходящие в него. |
| Трейты | Несколько моделей делят поля и поведение. |
| Коллекции и экземпляры | Сущностям нужен дом: создание, мерж, жизненный цикл. |
| Виды моделей | Выбор между staticModel, model и обычным owner. |
| Связи | Сущности ссылаются на другие или владеют дочерними. |
| Запросы и индексы | Спискам нужны фильтрация, сортировка и быстрый поиск. |
| Объединения | Один список смешивает сущности разных моделей. |
| Загрузка и сериализация | Экземпляры сериализуются назад, id приходит с сервера позже. |
| UI-биндинги | Компоненты рендерят запросы и экземпляры напрямую. |
Первая модель
Опишите сущность, создайте коллекцию в скоупе, загрузите JSON, запросите и сериализуйте обратно.
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 из ядра, - на странице Виды моделей.
Связанные разделы
- Поля и данные - как декларации
f.*описывают и хранение, и JSON. - Коллекции и экземпляры - API коллекции и жизненный цикл экземпляра.
- UI-биндинги - рендер запросов и экземпляров в React и Vue.