查詢註冊表

本頁內容

這是一個進階主題,介紹如何針對外掛註冊表建置你自己的軟體。如果你只是想在 EmDash 站台上安裝外掛,則不需要這些內容——在設定中啟用註冊表,並使用管理後台即可。

註冊表的探索部分是一個公開的唯讀 API。@emdash-cms/registry-client 套件對其進行了封裝,因此你可以在 EmDash 之外建置外掛目錄、搜尋頁或發布動態。該用戶端可以在任何支援 fetch 的地方執行——Node、Workers、瀏覽器或 Astro 站台。Astro 站台可以使用 @emdash-cms/registry-loader,透過 live 內容集合來公開同樣的資料。

安裝

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>

當聚合器解析出目前已驗證的 handle 時,它就會出現。請保留 DID 形式作為回退,並在後續回應提供 handle 時將其重新導向到目前的 handle URL。當發布者變更 handle 時,DID 和套件 slug 仍然是穩定的套件識別。

精確的 q 值可以是 handle、DID,或二者之一後接 /slug。例如,@example.com/my-gallery 選中一個套件,而 example.com 回傳該發布者的所有套件。

使用 Astro live loader

安裝 @emdash-cms/registry-loader,然後在 src/live.config.ts 中註冊該 loader:

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

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

使用 getLiveCollection("plugins", { q, limit }) 取得有限數量的結果,使用 getLiveEntry("plugins", { publisher, slug }) 取得單一套件。當清單需要圖示等發布資料時,請在集合篩選器中加入 includeLatestRelease: true;每個已發布版本的套件都會多產生一次註冊表請求,而無法在 3 秒內載入其發布版本的項目會在沒有 latestRelease 的情況下回傳。當站台有 Astro 快取提供者時,請將回傳的快取提示傳給 Astro.cache.set()。Astro 的 live 集合不回傳分頁中繼資料,因此當介面需要下一個游標時,請直接使用 DiscoveryClient.searchPackages()。

套件詳細頁透過 DID 和 slug 取得外掛,然後取得最新發布版本:

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

探索方法

該用戶端為每個聚合器查詢提供一個方法:

  • searchPackages({ q, capability?, limit?, cursor? }) — 自由文字搜尋,可選擇性地篩選為宣告了給定存取類別的套件。回傳 { packages, cursor? }。
  • resolvePackage({ handle, slug }) — 透過 handle 和 slug 解析套件。
  • getPackage({ did, slug }) — 透過 DID 和 slug 取得套件。
  • listReleases({ did, package, limit?, cursor? }) — 依語意化版本降序排列的發布版本,包括已撤回(yanked)的版本。
  • getLatestRelease({ did, package }) — 聚合器選出的、未被撤回的最高發布版本。

getPackage() 和 resolvePackage() 可能回傳 historicalReleaseCount 和 releaseHistoryComplete。這些欄位描述的是聚合器保留的營運歷史, 而不是發布者簽署的中繼資料。只有當 releaseHistoryComplete 為 true 時, 才可將計數為 1 視為首次發布。缺少或不完整的證據不得繞過發布時長 原則。

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。

處理不可信記錄

聚合器是一個不可信的索引,它會轉發並非由它建立的記錄,因此用戶端會在邊界處驗證每一筆記錄。由此得出兩條規則:

  • profile 和 release 欄位可能為 null。 當轉發的記錄驗證失敗時,用戶端會將其呈現為 null,而不是讓整個呼叫失敗,這樣一筆格式錯誤的記錄就不會讓整個搜尋頁變空白。在讀取 pkg.profile?.name 或 latest.release?.artifacts.package 之前,請務必進行空值檢查。
  • 算繪之前請自行驗證 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);
}

接下來讀什麼