在 Fabric 應用程式中使用 GraphQL 讀寫資料

Fabric Apps 提供一個型別安全的 GraphQL 用戶端,讓你能在不寫原始查詢的情況下執行建立、讀取、更新和刪除操作。 客戶端會自動從你的方法呼叫產生 GraphQL,並根據你的資料模型定義回傳類型化的實體。

先決條件

  • 一個定義資料模型的 Fabric Apps 專案。 參見 定義資料模型
  • 在本機執行或部署到 Fabric 的後端服務。

初始化用戶端

使用後端 URL、可發布金鑰和結構描述類型來將 RayfinClient 例項化:

import { RayfinClient } from '@microsoft/rayfin-client';
import type { Note } from '../rayfin/data/Note';
import type { Notebook } from '../rayfin/data/Notebook';

type AppSchema = { 
  Note: Note;
  Notebook: Notebook;
};

const client = new RayfinClient<AppSchema>({
  baseUrl: import.meta.env.VITE_RAYFIN_API_URL ?? 'http://localhost:5168',
  publishableKey: 'pk-your-project-key',
});

generic type 參數使 TypeScript 能為所有資料操作提供自動補全與型別檢查。

讀取資料

透過 client.data.<EntityName> 存取實體集合。 流暢 API 提供查詢、篩選、排序及分頁的方法。

取得所有紀錄

const notes = await client.data.Note.select([
  'id',
  'title',
  'content',
  'createdAt',
  'isPinned',
]).execute();

以主鍵取得單一記錄

const note = await client.data.Note.findByPk('00000000-0000-0000-0000-000000000000');

這會回傳完整的實體;如果不存在具有該 ID 的記錄,則回傳 null

篩選記錄

使用以下 where() 方法篩選結果:

const pinnedNotes = await client.data.Note.select([
  'id',
  'title',
  'isPinned',
])
  .where({ isPinned: { eq: true } })
  .execute();

濾波器運算元

Operator Description Example
eq 等於 { status: { eq: 'active' } }
ne 不是平等的 { status: { ne: 'archived' } }
gt 大於 { age: { gt: 18 } }
gte 大於或等於 { age: { gte: 21 } }
lt 小於 { price: { lt: 100 } }
lte 小於或等於 { price: { lte: 50 } }
contains 包含子字串 { title: { contains: 'draft' } }

排序結果

用於 orderBy() 排序查詢結果:

const notes = await client.data.Note.select([
  'id',
  'title',
  'createdAt',
])
  .orderBy({ createdAt: 'desc' })
  .execute();

依多欄排序:

const notes = await client.data.Note.select([
  'id',
  'title',
  'isPinned',
  'createdAt',
])
  .orderBy({ isPinned: 'desc' })
  .orderBy({ createdAt: 'desc' })
  .execute();

當你使用 @one()@many() 裝飾器定義關聯時,可以在同一個查詢中包含相關實體的欄位:

const notes = await client.data.Note.select([
  'id',
  'title',
  'content',
  'notebook.id',
  'notebook.name',
  'notebook.color',
])
  .execute();

每筆筆記都包含其對應的筆記本資料,無需另行查詢。

將大型結果集分頁

對於大型清單,請使用游標分頁:

const page = await client.data.Note.select([
  'id',
  'title',
  'createdAt',
])
  .orderBy({ createdAt: 'desc' })
  .first(25)
  .executePaginated();

console.log('Items:', page.items);
console.log('Has next page:', page.hasNextPage);
console.log('End cursor:', page.endCursor);

使用游標讀取下一頁:

if (page.hasNextPage) {
  const nextPage = await client.data.Note.select([
    'id',
    'title',
    'createdAt',
  ])
    .orderBy({ createdAt: 'desc' })
    .first(25)
    .after(page.endCursor)
    .executePaginated();
}

備註

totalCount 屬性會顯示於 PagedResult 類型中,但後端不會填入值。 請使用 items.length 以計算當前頁面的結果。

建立紀錄

使用 create() 此方法插入新紀錄:

const newNote = await client.data.Note.create({
  title: 'Meeting notes',
  content: 'Discussion points from the team sync',
  isPinned: false,
  isArchived: false,
  createdAt: new Date(),
  updatedAt: new Date(),
  user_id: 'user-123',
});

該方法會回傳已建立的實體,所有欄位皆已填入值,包括自動產生的 id

建立帶有關聯性的紀錄

建立具有關聯的實體時,請傳入完整的關聯物件,或僅包含主鍵的物件:

// Option 1: Pass just the ID
const note = await client.data.Note.create({
  title: 'Weekly summary',
  content: 'Summary of this week',
  notebook: { id: 'notebook-456' },
  isPinned: false,
  isArchived: false,
  createdAt: new Date(),
  updatedAt: new Date(),
});

// Option 2: Pass the full object
const notebook = await client.data.Notebook.findByPk('notebook-456');
const note = await client.data.Note.create({
  title: 'Weekly summary',
  content: 'Summary of this week',
  notebook: notebook,
  isPinned: false,
  isArchived: false,
  createdAt: new Date(),
  updatedAt: new Date(),
});

兩種形式產生相同的結果。 當你已經知道相關實體的 ID,且想避免額外擷取一次時,請使用第一種形式。

更新記錄

使用該 update() 方法修改現有紀錄。 傳入一個篩選物件,以及一個包含要更新欄位的物件:

await client.data.Note.update(
  { id: 'note-123' },
  {
    title: 'Updated title',
    updatedAt: new Date(),
  }
);

更新關係

要更改關係,請傳遞新的相關實體或僅傳遞其 ID:

// Move a note to a different notebook
await client.data.Note.update(
  { id: 'note-123' },
  { notebook: { id: 'new-notebook-789' } }
);

刪除記錄

使用 delete() 以下方法移除與篩選條件相符的紀錄:

await client.data.Note.delete({ id: 'note-123' });

當後端確認刪除時,方法就會解決。 如果沒有紀錄與濾波器相符,該方法仍然成功。

處理身份驗證

啟用認證後,請在執行資料操作前登入:

await client.auth.signIn({ email, password });

// All subsequent data calls include authentication context
const notes = await client.data.Note.select(['id', 'title']).execute();

用戶端會自動將認證會話附加到所有資料 API 呼叫中。 你不需要手動傳遞代幣。

最佳做法

  • 只選擇需要的欄位 ——只取得你用來減少有效載荷大小並提升效能的欄位。
  • 大型清單請使用分頁——避免一次取得數千筆記錄,請使用 first()executePaginated()
  • 批次關係查詢 – 在同一查詢中包含相關的實體欄位,而非分別提出請求。
  • 快取頻繁存取的資料 ——將靜態參考資料儲存在記憶體中,以減少 API 呼叫。

目前的限制

  • 無法在 Fluent 用戶端中使用 count() 方法。 選擇最小欄位並改用 results.length
  • 很多關係都不被支持。 使用具有兩個 @one() 導覽修飾詞的顯式聯結實體。
  • PagedResult 上的 totalCount 屬性不會由後端填入。