Fabric Apps 使用 TypeScript 裝飾器來定義產生資料庫資料表與 API 的資料模型。 你將每個實體定義為使用 @entity() 裝飾的類別,為資料型別新增欄位裝飾器,並對應實體之間的關係。
關於授權與存取控制的指引,請參閱 定義資料權限。
先決條件
- 一個使用
npm create @microsoft/rayfin@latest建立或以npx rayfin init初始化的 Fabric Apps 專案。 - 對 TypeScript 類別和裝飾工具的基本了解。
定義實體
要建立資料模型,請將 decorator 加入 @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、description、createdAt 和 updatedAt 欄位。
主鍵
每個實體都使用一個 string UUID id 欄位作為其主鍵。 如果你沒有明確宣告 id,Fabric Apps 會自動把它加入 schema。
- 在建立操作中,該
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() |
字串 | 唯一識別碼欄位。 |
@text() |
字串 | 具有可選長度限制的文字欄位。 |
@int() |
編號 | 整數場。 |
@decimal() |
編號 | 十進位欄位或數值欄位。 |
@boolean() |
布爾值 | 真偽欄位。 |
@date() |
Date | 日期與時間欄位,從 ISO 字串或 Date 物件序列化。 |
@email() |
字串 | 帶有電子郵件驗證的文字欄位。 |
@set() |
字串 | 列舉的字串文字集合。 |
包含多種類型的範例
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 類型。 它不會讓資料庫欄位變成可空的。 若要讓欄位可為 null,請在裝飾器中加入 { 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 Apps 會自動產生外鍵欄位。
-
一對多——在父項上使用
@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 Apps 不支援指向系統實體的 @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 Proxy:
import type { Note } from './Note.js';
import type { Notebook } from './Notebook.js';
export type NotesAppSchema = {
Note: Note;
Notebook: Notebook;
};
每次建立新實體時,都會更新這個類型。
套用結構描述變更
定義或修改實體後,將變更套用到資料庫中:
將更新後的結構部署到 Fabric:
npx rayfin up db apply如果結構變更包含破壞性操作(刪除欄位、重新命名資料表),CLI 會警告你並拒絕繼續。 使用
--force來略過安全檢查:npx rayfin up db apply --force
備註
使用 --force 可能會導致資料遺失。 在進行前,請仔細審查列出的操作內容。
最佳做法
- 只有在需要讀取或設定程式碼時才定義外鍵欄位——Fabric Apps 會自動由導航裝飾器產生。
- 在實體檔案中使用含有
.js副檔名的相對匯入,讓產生的 ESM JavaScript 能夠正確解析。 - 關於授權模式與基於角色的存取,請參見 定義資料權限。
故障排除
缺少的關聯
如果關係未顯示在 API 中,請確認以下事項:
- 導航裝飾器(
@one()或@many())也在場。 - 該實體註冊於
rayfin/data/schema.ts。