Fabric 앱에서 GraphQL을 사용하여 데이터 읽기 및 쓰기

Fabric 앱은 원시 쿼리를 작성하지 않고도 만들기, 읽기, 업데이트 및 삭제 작업을 수행할 수 있는 형식이 안전한 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',
});

제네릭 형식 인수를 사용하면 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');

이렇게 하면 전체 엔터티가 반환되거나 null 해당 ID가 있는 레코드가 없는 경우 반환됩니다.

레코드 필터링

메서드를 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();

각 노트에는 별도의 쿼리 없이 연결된 Notebook 데이터가 포함됩니다.

큰 결과 집합 페이지 매김

큰 목록에 커서 기반 페이지 매김을 사용합니다.

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 호출을 줄이기 위해 메모리에 정적 참조 데이터를 저장합니다.

현재 제한 사항

  • count() 메서드는 Fluent 클라이언트에서 사용할 수 없습니다. 최소 필드를 선택하고 대신 사용합니다 results.length .
  • 다대다 관계는 지원되지 않습니다. 두 개의 @one() 탐색 데코레이터가 있는 명시적 조인 엔터티를 사용합니다.
  • PagedResulttotalCount 속성은 백엔드에서 값이 채워지지 않습니다.