レジストリの照会

このページ

これは、プラグインレジストリを利用して独自のソフトウェアを構築するための上級トピックです。EmDash サイトにプラグインをインストールするだけであれば、ここで説明する内容は一切不要です。設定でレジストリを有効にし、管理ダッシュボードを使ってください。

レジストリのディスカバリ側は、公開の読み取り専用 API です。@emdash-cms/registry-client パッケージがこれをラップしているため、EmDash の外部でプラグインディレクトリ、検索ページ、リリースフィードを構築できます。このクライアントは、fetch が利用できる場所ならどこでも動作します。Node、Workers、ブラウザー、Astro サイトのいずれでも使えます。Astro サイトでは、@emdash-cms/registry-loader を使って、同じデータをライブコンテンツコレクションとして公開できます。

インストール

discovery サブパスには、認証や OAuth の依存関係は含まれません。クライアントをインストールし、正確なバージョンに固定します。

npm install @emdash-cms/[email protected]

パッケージの一覧表示と解決

次の Astro ページは、レジストリ内のすべてのプラグインを一覧表示します。

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

現在検証済みのハンドルは、アグリゲーターが解決した場合に含まれます。DID 形式はフォールバックとして保持し、後のレスポンスでハンドルが提供されたら、現在のハンドル URL にリダイレクトしてください。パブリッシャーがハンドルを変更しても、DID とパッケージスラッグは安定したパッケージの識別子であり続けます。

完全一致の q の値には、ハンドル、DID、またはそのどちらかに /slug を続けたものを指定できます。たとえば、@example.com/my-gallery は 1 つのパッケージを選択し、example.com はそのパブリッシャーのパッケージを返します。

Astro ライブローダーの使用

@emdash-cms/registry-loader をインストールし、src/live.config.ts でローダーを登録します。

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

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

件数を絞った結果には getLiveCollection("plugins", { q, limit }) を、1 つのパッケージには getLiveEntry("plugins", { publisher, slug }) を使います。一覧にアイコンなどのリリースデータが必要な場合は、コレクションのフィルターに includeLatestRelease: true を追加します。これは、公開済みリリースを持つパッケージごとに追加のレジストリリクエストが 1 回発生し、リリースを 3 秒以内に読み込めなかったエントリーは latestRelease なしで返されます。サイトに Astro のキャッシュプロバイダーがある場合は、返されたキャッシュヒントを Astro.cache.set() に渡します。Astro のライブコレクションはページネーションのメタデータを返さないため、インターフェースで次のカーソルが必要な場合は DiscoveryClient.searchPackages() を直接使用してください。

パッケージの詳細ページでは、DID とスラッグでプラグインを取得し、続けて最新リリースを取得します。

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

ディスカバリメソッド

このクライアントは、アグリゲーターのクエリごとに 1 つのメソッドを公開しています。

  • searchPackages({ q, capability?, limit?, cursor? }) — 自由テキスト検索。特定のアクセスカテゴリを宣言しているパッケージに絞り込むこともできます。{ packages, cursor? } を返します。
  • resolvePackage({ handle, slug }) — ハンドルとスラッグからパッケージを解決します。
  • getPackage({ did, slug }) — DID とスラッグでパッケージを取得します。
  • listReleases({ did, package, limit?, cursor? }) — セマンティックバージョンの降順でのリリース。yank(取り下げ)されたリリースも含みます。
  • getLatestRelease({ did, package }) — アグリゲーターが選択した、yank されていない最も高いリリース。

getPackage() と resolvePackage() は、historicalReleaseCount と releaseHistoryComplete を返すことがあります。これらのフィールドは、アグリゲーターが保持している運用上の履歴を表すもので、 パブリッシャーが署名したメタデータではありません。件数が 1 であることを最初のリリースとして扱うのは、 releaseHistoryComplete が true の場合に限ります。証拠が欠けている、または不完全な場合に、リリース経過期間のポリシーを 回避してはなりません。

getPackageStatus() と resolvePackageStatus() は、対応するパッケージクエリをラップし、 安全な ListingUnavailable レスポンスを { status: "unavailable" } にマッピングします。成功した結果は { status: "passed", value } になります。インデックス済みだが利用できないリストと、存在しないパッケージを区別する必要がある ユーザーインターフェースで、パブリッシャーが制御するエラー内容をレンダリングせずに済むよう、これらのメソッドを使用してください。

リリースを表示または選択する前に、取り下げヘルパーを使用します。

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 になります。無効なラベルデータは フェイルクローズ(fail closed)で処理されます。withdrawn と malformed はどちらも true になります。

信頼できないレコードの処理

アグリゲーターは、自身が作成していないレコードを中継する、信頼できないインデックスです。そのため、クライアントは境界ですべてのレコードを検証します。ここから、次の 2 つのルールが導かれます。

  • profile と release フィールドは null になることがあります。 中継されたレコードが検証に失敗した場合、クライアントは呼び出し全体を失敗させるのではなく、そのレコードを null として返します。これにより、1 件の不正なレコードによって検索ページが空になることを防げます。pkg.profile?.name や latest.release?.artifacts.package を読み取る前に、必ず null チェックを行ってください。
  • レンダリングの前に、URL スキームを自分で検証してください。 検証が確認するのは構造であり、URL の安全性ではありません。uri フィールドには javascript: スキームが含まれる可能性があります。レジストリが提供する URL を href や src に入れる前に、独自の http/https の許可リストを適用してください。

2xx 以外のレスポンスは、ClientResponseError(パッケージから再エクスポートされています)をスローします。これは .error、.description、.status、.headers を持ちます。リファレンスアグリゲーターは、必要なすべてのポジティブラベルソースによって承認された、正確な CID のリビジョンのみを返します。atproto-accept-labelers ヘッダーは、リクエストとキャッシュの識別のために、設定済みの素の DID を宣言します。アグリゲーターは宣言を検証しますが、アグリゲーターに設定されたポリシーが引き続き優先されます。

ホスト互換性によるフィルタリング

リリースは、requires ブロックで環境要件(EmDash や Astro のバージョン範囲)を宣言できます。@emdash-cms/registry-client/env サブパスがそれらを評価するため、ディレクトリ側で、特定のホストで動作しないリリースを示せます。

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

次に読むべきもの