Interroger le registre

Sur cette page

Il s’agit d’un sujet avancé pour créer votre propre logiciel autour du registre de plugins. Si vous voulez seulement installer des plugins sur un site EmDash, rien de tout cela n’est nécessaire : activez le registre dans votre configuration et utilisez le tableau de bord d’administration.

Le volet découverte du registre est une API publique en lecture seule. Le package @emdash-cms/registry-client l’encapsule, ce qui vous permet de créer un annuaire de plugins, une page de recherche ou un flux de releases en dehors d’EmDash. Le client s’exécute partout où fetch est disponible : Node, Workers, le navigateur ou un site Astro. Les sites Astro peuvent utiliser @emdash-cms/registry-loader pour exposer les mêmes données via une collection de contenu live.

Installer

Le sous-chemin discovery n’embarque aucune dépendance d’authentification ni d’OAuth. Installez le client et épinglez-le à une version exacte :

npm install @emdash-cms/[email protected]

Lister et résoudre les packages

La page Astro suivante liste tous les plugins d’un registre :

---
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";

const discovery = new DiscoveryClient({
  aggregatorUrl: "https://registry.emdashcms.com",
});

const { packages } = await discovery.searchPackages({ q: "", limit: 50 });
---

<ul>
  {
    packages.map((pkg) => (
      <li>
        <a href={`/plugins/${pkg.handle ? `@${pkg.handle}` : pkg.did}/${pkg.slug}`}>
          {pkg.profile?.name ?? pkg.slug}
        </a>
        {pkg.latestVersion && <span>v{pkg.latestVersion}</span>}
        <p>{pkg.profile?.description}</p>
      </li>
    ))
  }
</ul>

Le handle vérifié actuel est présent lorsque l’agrégateur l’a résolu. Conservez la forme DID comme solution de repli et redirigez-la vers l’URL du handle actuel lorsqu’une réponse ultérieure en fournit un. Un DID et le slug du package restent l’identité stable du package lorsqu’un éditeur change de handle.

Une valeur q exacte peut être un handle, un DID, ou l’une ou l’autre de ces identités suivie de /slug. Par exemple, @example.com/my-gallery sélectionne un seul package et example.com renvoie les packages de cet éditeur.

Utiliser le live loader Astro

Installez @emdash-cms/registry-loader, puis enregistrez le loader dans src/live.config.ts :

import { registryLoader } from "@emdash-cms/registry-loader";
import { defineLiveCollection } from "astro:content";

export const collections = {
	plugins: defineLiveCollection({ loader: registryLoader() }),
};

Utilisez getLiveCollection("plugins", { q, limit }) pour des résultats bornés et getLiveEntry("plugins", { publisher, slug }) pour un seul package. Ajoutez includeLatestRelease: true au filtre de la collection lorsqu’une liste a besoin de données de release comme les icônes ; cela coûte une requête supplémentaire au registre par package ayant une release publiée, et une entrée dont la release ne peut pas être chargée en 3 secondes est renvoyée sans latestRelease. Transmettez l’indication de cache renvoyée à Astro.cache.set() lorsque le site dispose d’un fournisseur de cache Astro. Les collections live d’Astro ne renvoient pas de métadonnées de pagination ; utilisez donc directement DiscoveryClient.searchPackages() lorsque l’interface a besoin du curseur suivant.

Une page de détail de package récupère un plugin à partir de son DID et de son slug, puis récupère la dernière release :

import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";

const discovery = new DiscoveryClient({
  aggregatorUrl: "https://registry.emdashcms.com",
});

export async function getPlugin(did: string, slug: string) {
  const pkg = await discovery.getPackage({ did, slug });
  const latest = await discovery.getLatestRelease({
    did: pkg.did,
    package: pkg.slug,
  });
  return { pkg, latest };
}

Méthodes de découverte

Le client expose une méthode par requête de l’agrégateur :

  • searchPackages({ q, capability?, limit?, cursor? }) — recherche en texte libre, éventuellement filtrée sur les packages déclarant une catégorie d’accès donnée. Renvoie { packages, cursor? }.
  • resolvePackage({ handle, slug }) — résout un package à partir d’un handle et d’un slug.
  • getPackage({ did, slug }) — récupère un package à partir de son DID et de son slug.
  • listReleases({ did, package, limit?, cursor? }) — releases par ordre décroissant de version sémantique, y compris les releases retirées (yanked).
  • getLatestRelease({ did, package }) — la release la plus élevée non retirée, sélectionnée par l’agrégateur.

getPackage() et resolvePackage() peuvent renvoyer historicalReleaseCount et releaseHistoryComplete. Ces champs décrivent l’historique opérationnel conservé par l’agrégateur, et non des métadonnées signées par l’éditeur. Ne traitez un compteur égal à un comme une première release que lorsque releaseHistoryComplete vaut true. Une preuve manquante ou incomplète ne doit pas contourner une politique d’ancienneté des releases.

getPackageStatus() et resolvePackageStatus() encapsulent leurs requêtes de package correspondantes et convertissent la réponse sûre ListingUnavailable en { status: "unavailable" }. Un résultat réussi possède { status: "passed", value }. Utilisez ces méthodes dans une interface utilisateur qui doit distinguer une fiche indexée mais indisponible d’un package inexistant, sans afficher de contenu d’erreur contrôlé par l’éditeur.

Utilisez l’assistant de retrait avant d’afficher ou de sélectionner une release :

import {
	DiscoveryClient,
	type ValidatedReleaseView,
} from "@emdash-cms/registry-client/discovery";
import { evaluateRegistryReleaseWithdrawal } from "@emdash-cms/registry-client/withdrawal";

const discovery = new DiscoveryClient({
	aggregatorUrl: "https://registry.emdashcms.com",
});

export function canShowRelease(release: ValidatedReleaseView) {
	const result = evaluateRegistryReleaseWithdrawal(release, discovery.labelerPolicy);
	return release.release !== null && !result.withdrawn;
}

withdrawn vaut true lorsque les étiquettes applicables retirent la release de l’usage. Des données d’étiquette invalides échouent de manière fermée (fail closed) : withdrawn et malformed valent alors tous deux true.

Gérer les enregistrements non fiables

L’agrégateur est un index non fiable qui relaie des enregistrements qu’il n’a pas créés ; le client valide donc chacun d’eux à la frontière. Deux règles en découlent :

  • Les champs profile et release peuvent valoir null. Lorsqu’un enregistrement relayé échoue à la validation, le client le renvoie sous forme de null plutôt que de faire échouer tout l’appel, afin qu’un enregistrement mal formé ne vide pas une page de recherche. Vérifiez toujours la nullité avant de lire pkg.profile?.name ou latest.release?.artifacts.package.
  • Validez vous-même les schémas d’URL avant l’affichage. La validation vérifie la structure, pas la sûreté des URL : un champ uri peut porter un schéma javascript:. Appliquez votre propre liste d’autorisation http/https avant de placer une URL fournie par le registre dans un href ou un src.

Une réponse non 2xx lève ClientResponseError (réexporté depuis le package), qui porte .error, .description, .status et .headers. L’agrégateur de référence ne renvoie que des révisions à CID exact approuvées par chaque source d’étiquettes positives requise. Un en-tête atproto-accept-labelers déclare des DID simples configurés pour l’identité de la requête et du cache. L’agrégateur valide la déclaration, mais sa politique configurée reste prépondérante.

Filtrer par compatibilité d’hôte

Une release peut déclarer des exigences d’environnement (une plage de versions d’EmDash ou d’Astro) dans son bloc requires. Le sous-chemin @emdash-cms/registry-client/env les évalue, de sorte qu’un annuaire peut signaler les releases qui ne fonctionneront pas sur un hôte donné :

import { checkEnvCompatibility, hostEnvFromVersions } from "@emdash-cms/registry-client/env";
import type { ValidatedReleaseView } from "@emdash-cms/registry-client/discovery";

const host = hostEnvFromVersions("0.37.0", "7.0.0");

// Pass a getLatestRelease() result. The returned array is empty when the
// release runs on this host.
export function envMismatches(latest: ValidatedReleaseView) {
  return checkEnvCompatibility(latest.release?.requires, host);
}

Suite de lecture