Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Fabric Apps menyediakan klien GraphQL yang aman terhadap tipe, yang memungkinkan Anda melakukan operasi membuat, membaca, memperbarui, dan menghapus tanpa menulis kueri secara manual. Klien menghasilkan GraphQL secara otomatis dari panggilan metode Anda dan mengembalikan entitas yang diketik berdasarkan definisi model data Anda.
Prasyarat
- Proyek Fabric Apps dengan model data yang ditentukan. Lihat Menentukan model data.
- Layanan backend yang berjalan secara lokal atau disebarkan ke Fabric.
Menginisialisasi klien
Instansiasikan RayfinClient dengan URL backend Anda, kunci publik, dan tipe skema:
import { RayfinClient } from '@microsoft/rayfin-client';
import type { UniversalAppSchema } from '@rayfin-app/shared';
const client = new RayfinClient<UniversalAppSchema>({
baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
});
Argumen jenis generik memungkinkan TypeScript menyediakan pelengkapan otomatis dan pemeriksaan jenis untuk semua operasi data.
Contoh-contoh ini berasumsi bahwa Anda mengaktifkan Data dan mendaftarkan Note serta Notebook dalam kontrak packages/data/src/index.ts dan shared schema milik Universal App. Dalam template datar, impor tipe skema Anda dari rayfin/data/schema.ts sebagai gantinya. Lihat Menentukan model data.
Membaca data
Akses koleksi entitas melalui client.data.<EntityName>. API fasih menyediakan metode untuk mengkueri, memfilter, mengurutkan, dan penomoran halaman.
Ambil satu halaman rekaman
execute() Mengembalikan hanya satu halaman, dengan 100 catatan secara default. Ia tidak mengembalikan metadata kursor atau mengambil halaman-halaman selanjutnya. Gunakan paginasi maju untuk daftar yang dapat berkembang lebih dari satu halaman.
const notes = await client.data.Note.select([
'id',
'title',
'content',
'createdAt',
'isPinned',
]).execute();
Ambil satu data berdasarkan kunci primer
const note = await client.data.Note.findById('00000000-0000-0000-0000-000000000000');
findById() Mengembalikan null jika tidak ada catatan yang ada. Implementasi yang dirilis hanya memilih kunci utama, jadi jangan berasumsi bahwa field entitas lain sudah terisi. Pilih kolom secara eksplisit saat Anda membutuhkan nilainya:
const notes = await client.data.Note.select(['id', 'title', 'content'])
.where({ id: { eq: '00000000-0000-0000-0000-000000000000' } })
.first(1)
.execute();
const note = notes[0] ?? null;
Filter catatan
where() Gunakan metode untuk memfilter hasil:
const pinnedNotes = await client.data.Note.select([
'id',
'title',
'isPinned',
])
.where({ isPinned: { eq: true } })
.execute();
Operator penyaring
| Operator | Deskripsi | Example |
|---|---|---|
eq |
Sama dengan | { status: { eq: 'active' } } |
neq |
Tidak sama | { status: { neq: 'archived' } } |
gt |
Lebih besar dari | { age: { gt: 18 } } |
gte |
Lebih besar dari atau sama dengan | { age: { gte: 21 } } |
lt |
Kurang dari | { price: { lt: 100 } } |
lte |
Kurang dari atau sama dengan | { price: { lte: 50 } } |
contains |
Mengandung substring | { title: { contains: 'draft' } } |
Urutkan hasil
Gunakan orderBy() untuk mengurutkan hasil kueri:
const notes = await client.data.Note.select([
'id',
'title',
'createdAt',
])
.orderBy({ createdAt: 'desc' })
.execute();
Urutkan menurut beberapa kolom:
const notes = await client.data.Note.select([
'id',
'title',
'isPinned',
'createdAt',
])
.orderBy({ isPinned: 'desc' })
.orderBy({ createdAt: 'desc' })
.execute();
Menavigasi hubungan
Saat Anda mendefinisikan relasi dengan dekorator @one() dan @many(), Anda dapat menyertakan bidang entitas terkait dalam kueri yang sama:
const notes = await client.data.Note.select([
'id',
'title',
'content',
'notebook.id',
'notebook.name',
])
.execute();
Setiap catatan menyertakan data notebook terkait tanpa memerlukan kueri terpisah.
Buat paginasi untuk kumpulan hasil yang besar
Gunakan penomoran halaman berbasis kursor untuk daftar besar:
const page = await client.data.Note.select([
'id',
'title',
'createdAt',
])
.orderBy({ createdAt: 'desc' })
.orderBy({ id: 'asc' })
.first(25)
.executePaginated();
console.log('Items:', page.items);
console.log('Has next page:', page.hasNextPage);
console.log('End cursor:', page.endCursor);
Ambil halaman berikutnya menggunakan kursor:
if (page.hasNextPage) {
if (!page.endCursor) {
throw new Error('The next page is missing its cursor.');
}
const nextPage = await client.data.Note.select([
'id',
'title',
'createdAt',
])
.orderBy({ createdAt: 'desc' })
.orderBy({ id: 'asc' })
.first(25)
.after(page.endCursor)
.executePaginated();
}
Nota
Backend tidak mengisi properti totalCount pada tipe PagedResult. Nilai page.items.length hanya mencakup halaman saat ini, bukan semua catatan yang cocok.
Untuk memproses setiap catatan yang cocok, lanjutkan hingga hasNextPage adalah false. Biarkan pilihan, filter, dan urutan pengurutan tetap sama antar permintaan.
let cursor: string | undefined;
let matchingRecords = 0;
while (true) {
let query = client.data.Note.select(['id'])
.where({ isArchived: { eq: false } })
.orderBy({ id: 'asc' })
.first(100);
if (cursor !== undefined) {
query = query.after(cursor);
}
const page = await query.executePaginated();
matchingRecords += page.items.length;
if (!page.hasNextPage) {
break;
}
if (!page.endCursor) {
throw new Error('The next page is missing its cursor.');
}
cursor = page.endCursor;
}
console.log('Records read across all pages:', matchingRecords);
Paging tidak menghasilkan snapshot di seluruh permintaan. Perubahan yang terjadi secara bersamaan dapat memengaruhi rekaman yang Anda baca dan hitung.
Agregasikan dan kelompokkan data
Gunakan aggregate() untuk total sisi server dan groupBy() untuk ringkasan per grup. Metode fluen count() mandiri tidak tersedia, tetapi count didukung dalam spesifikasi agregasi.
Kelima operasi agregat (sum, avg, min, max, dan count) membutuhkan bidang numerik. Jangan menghitung UUID id atau bidang teks. Sebagai contoh, kueri berikut mengasumsikan entitas terdaftar Product dengan bidang stockQuantity numerik yang diperlukan:
const totals = await client.data.Product.where({ isAvailable: { eq: true } })
.aggregate({
matchingProducts: { count: 'stockQuantity' },
totalStock: { sum: 'stockQuantity' },
averageStock: { avg: 'stockQuantity' },
lowestStock: { min: 'stockQuantity' },
highestStock: { max: 'stockQuantity' },
})
.execute();
console.log(totals[0].aggregations.matchingProducts);
count menghitung nilai non-null dari bidang numerik yang ditentukan. Gunakan bidang numerik yang diperlukan untuk menghitung baris yang cocok. Jika entitas Anda tidak memiliki bidang yang sesuai, hitung pengenal di semua halaman seperti yang ditunjukkan sebelumnya, bukan hanya satu halaman.
Untuk total yang dikelompokkan:
const groups = await client.data.Product.groupBy(['status'])
.aggregate({
matchingProducts: { count: 'stockQuantity' },
totalStock: { sum: 'stockQuantity' },
})
.execute();
for (const row of groups) {
console.log(row.fields.status, row.aggregations.totalStock);
}
Hasil agregasi adalah array dengan fields dan aggregations objek. Total keseluruhan memiliki satu baris dengan objek fields yang kosong.
count mengembalikan number; operasi lainnya mengembalikan number | null untuk input kosong atau semua null.
Terapkan where() sebelum pengagregasian. Jangan menggabungkan agregasi dengan pemilihan baris, pengurutan, atau paging melalui select(), orderBy(), first(), atau after().
Membuat rekaman
create() Gunakan metode untuk menyisipkan rekaman baru:
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',
});
Metode mengembalikan rekaman yang dibuat, termasuk id yang dibuat secara otomatis. Gunakan operasi read eksplisit yang dipilih saat Anda membutuhkan field tersimpan tambahan.
Membuat catatan dengan relasi
Saat membuat entitas yang memiliki hubungan, berikan objek terkait penuh atau objek hanya dengan kunci primer:
// Option 1: Pass just the ID
const note = await client.data.Note.create({
title: 'Weekly summary',
content: 'Summary of this week',
notebook: { id: '22222222-3333-4444-5555-666666666666' },
isPinned: false,
isArchived: false,
createdAt: new Date(),
updatedAt: new Date(),
});
// Option 2: Select the related fields you need before creating the note.
const notebooks = await client.data.Notebook.select(['id', 'name'])
.where({ id: { eq: '22222222-3333-4444-5555-666666666666' } })
.first(1)
.execute();
const notebook = notebooks[0];
if (!notebook) {
throw new Error('Notebook not found.');
}
const noteWithNotebook = await client.data.Note.create({
title: 'Weekly summary',
content: 'Summary of this week',
notebook,
isPinned: false,
isArchived: false,
createdAt: new Date(),
updatedAt: new Date(),
});
Kedua bentuk menghasilkan hasil yang sama. Gunakan bentuk pertama ketika Anda sudah mengetahui ID entitas terkait dan ingin menghindari pengambilan data tambahan.
Klien menggunakan kunci utama objek terkait untuk mengatur hubungan. Meneruskan objek tidak membuat atau memperbarui notebook terkait.
Memperbarui rekaman
Gunakan metode update() untuk mengubah record yang sudah ada. Berikan objek filter dan objek yang berisi bidang-bidang yang akan diperbarui:
await client.data.Note.update(
{ id: 'note-123' },
{
title: 'Updated title',
updatedAt: new Date(),
}
);
Perbarui hubungan
Untuk mengubah hubungan, berikan entitas terkait baru atau hanya ID-nya:
// Move a note to a different notebook
await client.data.Note.update(
{ id: 'note-123' },
{ notebook: { id: 'new-notebook-789' } }
);
Hapus rekaman
delete() Gunakan metode untuk menghapus rekaman yang cocok dengan filter:
await client.data.Note.delete({ id: 'note-123' });
Metode ini diselesaikan ketika backend mengonfirmasi penghapusan. Jika tidak ada rekaman yang cocok dengan filter, metode masih berhasil.
Buat atau perbarui rekaman
Gunakan upsert(where, create, update) untuk membuat rekaman yang belum ada atau memperbarui yang sudah ada. Berikan ID yang sama dalam pencarian dan input buat:
const id = '11111111-2222-3333-4444-555555555555';
const now = new Date();
await client.data.Note.upsert(
{ id },
{
id,
title: 'Daily summary',
content: 'Summary of today',
isPinned: false,
isArchived: false,
createdAt: now,
updatedAt: now,
user_id: 'user-123',
},
{ title: 'Daily summary', updatedAt: now },
);
Helper ini melakukan pembacaan yang diikuti dengan pembuatan atau pembaruan terpisah. Itu bukan atomik, dan panggilan bersamaan dapat mengalami race condition. Jangan gunakan ini sebagai jaminan transaksi atau konkurensi.
Menangani autentikasi
Masuk sebelum melakukan operasi data yang dilindungi. Untuk backend yang telah di-deploy, gunakan alur sign-in Microsoft Entra yang telah dikonfigurasi aplikasi. Contoh email dan kata sandi berikut hanya untuk backend lokal:
await client.auth.signIn({ email, password });
// All subsequent data calls include authentication context
const notes = await client.data.Note.select(['id', 'title']).execute();
Klien secara otomatis melampirkan sesi autentikasi ke semua panggilan API data. Anda tidak perlu mengirimkan token secara manual.
Praktik terbaik
- Pilih hanya bidang yang diperlukan – Ambil hanya bidang yang Anda gunakan untuk mengurangi ukuran payload dan meningkatkan performa.
-
Gunakan penomoran halaman untuk daftar besar – Hindari mengambil ribuan rekaman sekaligus dengan menggunakan
first()danexecutePaginated(). - Kueri hubungan batch – Sertakan bidang entitas terkait dalam kueri yang sama daripada membuat permintaan terpisah.
- Cache data yang sering diakses – Menyimpan data referensi statis dalam memori untuk mengurangi panggilan API.
Keterbatasan saat ini
- Metode mandiri
count()tidak tersedia. Gunakan agregasi bidang numerik, atau hitung catatan di seluruh halaman. Panjang array hanya menghitung catatan yang dikembalikan di halaman tersebut. - Relasi banyak-ke-banyak tidak didukung. Gunakan entitas penghubung eksplisit dengan dua dekorator navigasi
@one(). - Properti
totalCountpadaPagedResulttidak diisi oleh backend.