Lire et écrire des données avec GraphQL dans Fabric Apps

Fabric Apps fournit un client GraphQL de type sécurisé qui vous permet d’effectuer des opérations de création, de lecture, de mise à jour et de suppression sans écrire de requêtes brutes. Le client génère automatiquement GraphQL à partir de vos appels de méthode et retourne des entités typées en fonction de vos définitions de modèle de données.

Prerequisites

  • Projet Fabric Apps avec des modèles de données définis. Consultez Définir des modèles de données.
  • Les services back-end s’exécutant localement ou déployés sur Fabric.

Initialiser le client

Initialisez RayfinClient avec l’URL de votre backend, votre clé publique et le type de schéma :

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

L’argument de type générique permet à TypeScript de fournir la saisie semi-automatique et la vérification de type pour toutes les opérations de données.

Lire les données

Accéder aux collections d’entités via client.data.<EntityName>. L’API Fluent fournit des méthodes pour interroger, filtrer, trier et paginer.

Récupérer tous les enregistrements

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

Récupérer un enregistrement unique par clé primaire

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

Cette opération renvoie l’entité complète ou null si aucun enregistrement avec cet ID n’existe.

Filtrer les enregistrements

Utilisez la where() méthode pour filtrer les résultats :

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

Opérateurs de filtre

Operator Description Example
eq Égale { status: { eq: 'active' } }
ne N’est pas égal à { status: { ne: 'archived' } }
gt Supérieur à { age: { gt: 18 } }
gte Supérieur ou égal à { age: { gte: 21 } }
lt Inférieur à { price: { lt: 100 } }
lte Inférieur ou égal à { price: { lte: 50 } }
contains Contient la sous-chaîne { title: { contains: 'draft' } }

Trier les résultats

Permet orderBy() de trier les résultats de la requête :

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

Trier par plusieurs colonnes :

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

Lorsque vous définissez des relations avec les décorateurs @one() et @many(), vous pouvez inclure des champs de l’entité associée dans la même requête :

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

Chaque note inclut les données de son carnet associé sans nécessiter de requête distincte.

Paginer les jeux de résultats volumineux

Utilisez la pagination basée sur le curseur pour les grandes listes :

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

Récupérez la page suivante à l’aide du curseur :

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

Note

La totalCount propriété apparaît sur le PagedResult type, mais n’est pas remplie par le serveur principal. Utilisez items.length pour compter les résultats de la page actuelle.

Créer des enregistrements

Utilisez la create() méthode pour insérer de nouveaux enregistrements :

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

La méthode retourne l’entité créée avec tous les champs renseignés, y compris l’autogénéré id.

Créer des enregistrements associés

Lors de la création d’entités qui ont des relations, transmettez l’objet associé complet ou un objet avec uniquement la clé primaire :

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

Les deux formulaires produisent le même résultat. Utilisez le premier formulaire lorsque vous connaissez déjà l’ID de l’entité associée et souhaitez éviter une extraction supplémentaire.

Mettre à jour les enregistrements

Utilisez la update() méthode pour modifier les enregistrements existants. Transmettez un objet de filtre et un objet contenant les champs à mettre à jour :

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

Mettre à jour les relations

Pour modifier une relation, transmettez la nouvelle entité associée ou simplement son ID :

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

Supprimer les enregistrements

Utilisez la delete() méthode pour supprimer les enregistrements correspondant à un filtre :

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

La méthode se résout lorsque le back-end confirme la suppression. Si aucun enregistrement ne correspond au filtre, la méthode réussit toujours.

Gérer l’authentification

Lorsque l’authentification est activée, connectez-vous avant d’effectuer des opérations de données :

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

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

Le client attache automatiquement la session d’authentification à tous les appels d’API de données. Vous n’avez pas besoin de passer manuellement des jetons.

Bonnes pratiques

  • Sélectionnez uniquement les champs nécessaires : récupérez uniquement les champs que vous utilisez pour réduire la taille de charge utile et améliorer les performances.
  • Utilisez la pagination pour les grandes listes : évitez d’extraire des milliers d’enregistrements à la fois à l’aide first() et executePaginated().
  • Requêtes de relation batch : incluez des champs d’entité connexes dans la même requête plutôt que d’effectuer des requêtes distinctes.
  • Mettre en cache les données fréquemment sollicitées : stockez des données de référence statiques en mémoire pour réduire les appels d’API.

Limitations actuelles

  • La count() méthode n’est pas disponible sur le client Fluent. Sélectionnez le minimum de champs et utilisez results.length à la place.
  • Les relations plusieurs-à-plusieurs ne sont pas prises en charge. Utilisez une entité de jointure explicite avec deux @one() décorateurs de navigation.
  • La propriété totalCount de PagedResult n’est pas renseignée par le backend.