Interrogare il registro

In questa pagina

Questo è un argomento avanzato per creare il tuo software basato sul registro dei plugin. Se vuoi solo installare plugin su un sito EmDash, non ti serve nulla di tutto questo: abilita il registro nella tua configurazione e usa la dashboard di amministrazione.

La parte di discovery del registro è un’API pubblica di sola lettura. Il package @emdash-cms/registry-client la incapsula, così puoi creare una directory di plugin, una pagina di ricerca o un feed di release al di fuori di EmDash. Il client funziona ovunque sia disponibile fetch: Node, Workers, il browser o un sito Astro. I siti Astro possono usare @emdash-cms/registry-loader per esporre gli stessi dati tramite una content collection live.

Installare

Il sottopercorso discovery non include dipendenze di autenticazione o OAuth. Installa il client e bloccalo a una versione esatta:

npm install @emdash-cms/[email protected]

Elencare e risolvere i package

La seguente pagina Astro elenca tutti i plugin di un registro:

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

L’handle verificato corrente è presente quando l’aggregatore lo ha risolto. Mantieni la forma DID come fallback e reindirizzala all’URL dell’handle corrente quando una risposta successiva ne fornisce uno. Un DID e lo slug del package restano l’identità stabile del package quando un publisher cambia handle.

Un valore q esatto può essere un handle, un DID oppure una delle due identità seguita da /slug. Ad esempio, @example.com/my-gallery seleziona un singolo package e example.com restituisce i package di quel publisher.

Usare il live loader Astro

Installa @emdash-cms/registry-loader, poi registra il loader in src/live.config.ts:

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

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

Usa getLiveCollection("plugins", { q, limit }) per risultati limitati e getLiveEntry("plugins", { publisher, slug }) per un singolo package. Aggiungi includeLatestRelease: true al filtro della collection quando un elenco richiede dati di release come le icone; costa una richiesta aggiuntiva al registro per ogni package che ha una release pubblicata, e una voce la cui release non può essere caricata entro 3 secondi viene restituita senza latestRelease. Passa il suggerimento di cache restituito a Astro.cache.set() quando il sito ha un provider di cache Astro. Le collection live di Astro non restituiscono metadati di paginazione, quindi usa direttamente DiscoveryClient.searchPackages() quando l’interfaccia ha bisogno del cursore successivo.

Una pagina di dettaglio del package recupera un plugin tramite il suo DID e il suo slug, poi recupera l’ultima 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 };
}

Metodi di discovery

Il client espone un metodo per ogni query dell’aggregatore:

  • searchPackages({ q, capability?, limit?, cursor? }) — ricerca a testo libero, eventualmente filtrata sui package che dichiarano una determinata categoria di accesso. Restituisce { packages, cursor? }.
  • resolvePackage({ handle, slug }) — risolve un package da un handle e uno slug.
  • getPackage({ did, slug }) — recupera un package tramite il suo DID e il suo slug.
  • listReleases({ did, package, limit?, cursor? }) — release in ordine decrescente di versione semantica, comprese le release ritirate (yanked).
  • getLatestRelease({ did, package }) — la release più alta non ritirata selezionata dall’aggregatore.

getPackage() e resolvePackage() possono restituire historicalReleaseCount e releaseHistoryComplete. Questi campi descrivono la cronologia operativa conservata dall’aggregatore, non metadati firmati dal publisher. Considera un conteggio pari a uno come prima release solo quando releaseHistoryComplete è true. Prove mancanti o incomplete non devono aggirare una policy sull’età delle release.

getPackageStatus() e resolvePackageStatus() incapsulano le rispettive query sul package e mappano la risposta sicura ListingUnavailable su { status: "unavailable" }. Un risultato positivo ha { status: "passed", value }. Usa questi metodi in un’interfaccia utente che deve distinguere una scheda indicizzata ma non disponibile da un package inesistente senza renderizzare contenuti di errore controllati dal publisher.

Usa l’helper di ritiro prima di mostrare o selezionare una 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 è true quando le etichette applicabili rimuovono la release dall’uso. Dati di etichetta non validi falliscono in modo chiuso (fail closed): withdrawn e malformed sono entrambi true.

Gestire i record non attendibili

L’aggregatore è un indice non attendibile che inoltra record da lui non creati, quindi il client convalida ciascuno di essi al confine. Ne derivano due regole:

  • I campi profile e release possono essere null. Quando un record inoltrato non supera la convalida, il client lo restituisce come null invece di far fallire l’intera chiamata, così un record malformato non svuota una pagina di ricerca. Controlla sempre i valori null prima di leggere pkg.profile?.name o latest.release?.artifacts.package.
  • Convalida tu stesso gli schemi degli URL prima di renderizzare. La convalida verifica la struttura, non la sicurezza degli URL: un campo uri può contenere uno schema javascript:. Applica la tua allowlist http/https prima di inserire qualsiasi URL fornito dal registro in un href o src.

Una risposta non 2xx genera ClientResponseError (riesportato dal package), che contiene .error, .description, .status e .headers. L’aggregatore di riferimento restituisce solo revisioni con CID esatto approvate da ogni fonte di etichette positive richiesta. Un header atproto-accept-labelers dichiara DID semplici configurati per l’identità della richiesta e della cache. L’aggregatore convalida la dichiarazione, ma la sua policy configurata resta quella che prevale.

Filtrare per compatibilità dell’host

Una release può dichiarare requisiti di ambiente (un intervallo di versioni di EmDash o Astro) nel suo blocco requires. Il sottopercorso @emdash-cms/registry-client/env li valuta, così una directory può segnalare le release che non funzioneranno su un determinato host:

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

Cosa leggere dopo