Чтение и запись данных с помощью GraphQL в приложениях Fabric

Fabric Apps предоставляет типобезопасный клиент GraphQL, который позволяет выполнять операции создания, чтения, обновления и удаления без написания запросов вручную. Клиент автоматически создает GraphQL из вызовов методов и возвращает типизированные сущности на основе определений модели данных.

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

  • Проект Fabric Apps с определенными моделями данных. См. раздел "Определение моделей данных".
  • Серверные службы, работающие локально или развернутые в Fabric.

Инициализация клиента

Создайте экземпляр RayfinClient с URL-адресом вашего бэкенда, публикуемым ключом и типом схемы:

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',
});

Аргумент универсального типа позволяет TypeScript предоставлять автозавершение и проверку типов для всех операций с данными.

Чтение данных

Доступ к коллекциям сущностей с помощью client.data.<EntityName>. API fluent предоставляет методы для запроса, фильтрации, сортировки и разбиения на страницы.

Получить все записи

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');

Возвращает полную сущность или 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(),
});

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

Обновить записи

update() Используйте метод для изменения существующих записей. Передайте объект фильтра и объект, содержащий поля для обновления:

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

Обновление связей

Чтобы изменить связь, передайте новую связанную сущность или просто её идентификатор:

// 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.

Текущие ограничения

  • Метод count() недоступен для клиента fluent. Выберите минимальные поля и используйте results.length вместо этого.
  • Связи "многие ко многим" не поддерживаются. Используйте явно заданную сущность связи с двумя @one() декораторами навигации.
  • Свойство totalCount у PagedResult не заполняется на стороне сервера.