Определение моделей данных для приложений Fabric

Fabric приложения используют декораторы TypeScript для определения моделей данных, создающих таблицы баз данных и API. Вы определяете каждую сущность как класс, украшенный @entity(), добавляйте декораторы полей для типов данных и сопоставляете связи между сущностями.

Рекомендации по авторизации и управлению доступом см. в разделе "Определение разрешений данных".

Необходимые условия

  • Проект Fabric Apps, созданный с помощью npm create @microsoft/rayfin@latest или инициализированный с помощью npx rayfin init.
  • Базовое понимание классов TypeScript и декораторов.

Определение сущности

Чтобы создать модель данных, добавьте декоратор @entity() к классу TypeScript. Затем импортируйте необходимые декораторы из @microsoft/rayfin-core:

import { entity, uuid, text, date } from '@microsoft/rayfin-core';

@entity()
export class Todo {
  @uuid() id!: string;
  @text() title!: string;
  @text({ optional: true }) description?: string;
  @date() createdAt!: Date;
  @date() updatedAt!: Date;
}

Эта сущность создает таблицу Todo со столбцами для id, , titleи descriptioncreatedAtupdatedAt.

Первичные ключи

Каждая сущность использует поле UUID string с именем id в качестве первичного ключа. Если вы не объявляете id явно, Fabric Apps автоматически добавляет его в схему.

  • Поле id является необязательным во время операций создания— сервер создает идентификатор UUID, если он не указан.
  • Вы можете указать собственный идентификатор UUID во время создания, если вы предпочитаете идентификаторы, созданные клиентом.
  • Составные первичные ключи и пользовательские имена ключей не поддерживаются.
@entity()
export class Note {
  @uuid() id!: string;  // UUID primary key, auto-generated when omitted
  @text() title!: string;
  @text() content!: string;
}

Поддерживаемые типы данных

Используйте эти декораторы для определения типов полей:

Декоратор Тип Description
@uuid() string Поле уникального идентификатора.
@text() string Текстовое поле с необязательными ограничениями длины.
@int() number Целочисленное поле.
@decimal() number Поле для десятичных или числовых значений.
@boolean() boolean Поле со значением «истина» или «ложь».
@date() Дата Поле даты и времени, сериализуется из строк ISO или объектов Date.
@email() string Текстовое поле с проверкой электронной почты.
@set() string Перечисляемый набор строковых литералов.

Пример с несколькими типами

import { entity, uuid, text, int, decimal, boolean, date, set } from '@microsoft/rayfin-core';

@entity()
export class Product {
  @uuid() id!: string;
  @text() name!: string;
  @decimal() price!: number;
  @int() stockQuantity!: number;
  @boolean() isAvailable!: boolean;
  @date() createdAt!: Date;
  @set('draft', 'published', 'archived') status!: 'draft' | 'published' | 'archived';
}

Модификаторы типов

Добавьте модификаторы в декораторы полей для настройки проверки и ограничений:

Модификатор Description
{ optional: true } Разрешить значения NULL. Поля требуются по умолчанию.
{ unique: true } Добавьте уникальное ограничение.
{ default: value } Задайте выражение значения по умолчанию.
{ max: n }, { min: n } Ограничения длины строки (максимальное и минимальное количество символов).
{ min: n }, { max: n } Ограничения числовых значений.

Замечание

Необязательный маркер TypeScript (? после имени свойства) влияет только на статический тип TypeScript. Он не делает столбец базы данных пустым. Чтобы сделать поле пустым, добавьте { optional: true } в декоратор. Используется ! для утверждения о том, что необходимое поле инициализировано платформой.

Пример с модификаторами

@entity()
export class User {
  @uuid() id!: string;
  @email({ unique: true }) email!: string;
  @text({ min: 3, max: 50 }) username!: string;
  @text({ optional: true, max: 500 }) bio?: string;
  @int({ min: 0, max: 150 }) age!: number;
  @boolean({ default: false }) isVerified!: boolean;
}

Определение связей

Используйте декораторы @one() и @many() для определения навигационных свойств между сущностями. Приложения Fabric автоматически создают столбцы внешнего ключа при создании связей.

  • Один-ко-многим — используйте @many() в родительском элементе и @one() в дочернем элементе.
  • Многие к одному — используйте @one() в дочернем элементе, чтобы ссылаться на родительский элемент.
  • Отношения «многие ко многим» не поддерживаются — используйте вместо этого явную связующую сущность.

Пример связи "один ко многим"

import { entity, uuid, text, date, one, many } from '@microsoft/rayfin-core';

@entity()
export class Notebook {
  @uuid() id!: string;
  @text() name!: string;
  @date() createdAt!: Date;
  @many(() => Note) notes?: Note[];
}

@entity()
export class Note {
  @uuid() id!: string;
  @text() title!: string;
  @text() content!: string;
  @date() createdAt!: Date;
  @text() notebook_id!: string;
  @one(() => Notebook) notebook?: Notebook;
}

При определении @one(() => Notebook) для сущности Note Fabric Apps автоматически создает столбец внешнего ключа notebook_id. Объявите поле внешнего ключа явным образом, только если вы планируете считывать или задавать его в коде приложения.

Соглашение об именовании внешних ключей

При определении поля внешнего ключа используйте соглашение об именовании {property}_id:

@entity()
export class Note {
  @uuid() id!: string;
  @text() notebook_id!: string;      // Foreign key field
  @one(() => Notebook) notebook?: Notebook;  // Navigation property
}

Пользовательские названия внешних ключей (параметры foreignKey, targetKey) не поддерживаются.

Эталонные системные сущности

Fabric Приложения не поддерживают связи @one(), указывающие на системные сущности, такие как встроенная сущность USER. Чтобы связать строку с вошедшим в систему пользователем, добавьте обычное поле user_id типа @text() и заполните его из утверждений аутентификации (обычно claims.sub):

@entity()
export class Task {
  @uuid() id!: string;
  @text() title!: string;
  @text() user_id!: string;  // System user ID from claims.sub
}

Фильтруйте строки по user_id в политиках ролей, чтобы обеспечить доступ для каждого пользователя.

Регистрация сущностей в схеме

Добавьте все классы сущностей в rayfin/data/schema.ts, чтобы клиент мог сгенерировать прокси GraphQL:

import type { Note } from './Note.js';
import type { Notebook } from './Notebook.js';

export type NotesAppSchema = {
  Note: Note;
  Notebook: Notebook;
};

Обновляйте этот тип каждый раз, когда вы создаёте новую сущность.

Применение изменений схемы

После определения или изменения сущностей примените изменения к базе данных:

  1. Разверните обновленную схему в Fabric:

    npx rayfin up db apply
    
  2. Если изменение схемы включает деструктивные операции (удаление столбцов, переименование таблиц), интерфейс командной строки предупреждает вас и отказывается продолжить. Используйте --force для переопределения проверки безопасности:

    npx rayfin up db apply --force
    

Замечание

Использование --force может привести к потере данных. Внимательно просмотрите перечисленные операции, прежде чем продолжить.

Лучшие практики

  • Определяйте поля внешних ключей только тогда, когда вам нужно читать или задавать их в коде — Fabric Apps автоматически создаёт их на основе декораторов навигации.
  • Используйте относительные импорты с расширениями .js в файлах сущностей, чтобы сгенерированный JavaScript ESM корректно разрешался.
  • Шаблоны авторизации и доступ на основе ролей см. в разделе "Определение разрешений данных".

Troubleshooting

Отсутствующие связи

Если связи не отображаются в API, убедитесь, что:

  • Декоратор навигации (@one() или @many()) присутствует.
  • Объект зарегистрирован в rayfin/data/schema.ts.