Skip to content

@tabula-css/registry

Tabula のレジストリジェネレーター(2 バックエンドオラクル)と、merge・リンター・MCP サーバーが利用する Tailwind 非依存のレジストリリーダー。

インストール

ほとんどのプロジェクトは @tabula-css/registry に推移的にしか到達しません: @tabula-css/clitabula build を実行するためにこれに依存し、@tabula-css/merge はランタイムでその ./read エントリーポイントに依存します。生成された registry.json を自分で読み込むカスタムツール(カスタム lint ルール、スクリプト、エディタ拡張)を構築する場合にのみ直接インストールしてください — その場合は @tabula-css/registry/read のみをインポートしてください。

bash
npm install @tabula-css/registry

@tabula-css/registry/generate エントリーポイント(CLI が内部的に registry.json を生成するために使用します)は Tailwind 自身のツールチェーンを引き込みますが、@tabula-css/registry/read はそうしません。

概要

レジストリは、Tabula の閉じた語彙を具体化したものです — Concepts § the registry is ground truth を参照してください。このパッケージは、その両半分を意図的に切り離した 2 つの別々のサブパスエクスポートとして提供します。

  • @tabula-css/registry/generate は、Tailwind の独自のユーティリティ文法を再実装するのではなく、Tailwind 自身のコンパイル済み CSS 出力を、2 つの独立したバックエンド(デザインシステム API、または PostCSS プローブシートウォーク)のいずれかを通じてパースすることで registry.json を導出し、CI では適合性オラクルとして両者を相互にクロスチェックします。
  • @tabula-css/registry/read は、他のすべての利用者がインポートする Tailwind 非依存のリーダーです: @tabula-css/merge のランタイム、ESLint プラグインと stylelint プラグイン、そして MCP サーバーです。この半分のインポートグラフから Tailwind を排除しておくことは意図的な依存関係の法則です — Tailwind の API 破壊は 生成 を派手に壊すことがあっても、強制(enforcement)を静かに壊すことは決してあってはなりません。

エクスポート

@tabula-css/registry/read

ts
export function readRegistry(input: string | RegistryFile, options?: ReadOptions): RegistryReader;
export interface ReadOptions {
  /** Skip schema validation — only for a registry this process just produced. Default true. */
  readonly validate?: boolean;
}
export class RegistryReadError extends Error {
  readonly diagnostics: readonly Diagnostic[];
}
export function validateManifest(manifest: unknown): string | null;
export const REGISTRY_SCHEMA_VERSION: number; // 3

readRegistry() はレジストリを読み込んで検証し、RegistryReader を返します。input は、パース済みの RegistryFile オブジェクト、JSON 文字列、または registry.json へのファイルシステムパスのいずれかです(パスは「JSON らしく見えない」ことによってではなく、「1 行で、改行を含まず、4096 文字未満」という積極的な基準で認識されるため、切り詰められた、あるいは空のファイルは、分かりにくい生の ENOENT ではなく TAB-E303 診断になります)。不正な JSON、スキーマ違反、またはリーダーが理解できない registrySchemaVersion(TAB-E302)に対しては RegistryReadError を投げます。

ts
import { readRegistry } from '@tabula-css/registry/read';

const reader = readRegistry('.tabula/registry.json');
reader.has('bg-surface'); // true

validateManifest() は、パース済みの manifest.json を core のスキーマ(整合性のルート — Concepts を参照)に対して検証し、結合されたエラー文字列、または有効な場合は null を返します。

RegistryReader

ts
export class RegistryReader {
  readonly registry: RegistryFile;

  has(className: string): boolean;
  getClass(className: string): RegistryClass | undefined;
  isAtomic(className: string): boolean;
  classNames(): readonly string[];

  slotProperty(slot: number): string | undefined;
  classBySlot(slot: number): readonly string[];
  conflictsOf(className: string): readonly string[];

  exceptionOf(className: string): RegistryException | undefined;
  isException(className: string): boolean;
  exceptions(): Readonly<Record<string, RegistryException>>;

  isBanned(className: string): boolean;
  banOf(className: string): BanEntry | undefined;

  variantsTable(): Readonly<Record<string, RegistryVariant>>;
  variantProducts(): Readonly<Record<string, readonly string[]>>;
  hasChain(chainPrefix: string, family: string): boolean;
  hasChainClass(className: string): boolean;

  isStale(expectedSourceHash: string): boolean;
  isStaleAgainstManifest(manifest: ManifestFile): boolean;
}

レジストリのすべての利用者が、registry.json を手作業でインデックスする代わりに使う、型付けされたアクセサーサーフェスです。特筆すべきものをいくつか挙げます。

  • conflictsOf(className) は、base 条件の下で className と少なくとも 1 つの正規スロットを共有するすべてのクラス名を返します — これは、事前計算済みのインデックスであり、cn()resolve() がそのスロット所有ロジックを組み立てる土台です。
  • isAtomic(className) は、あるクラスがアトミックなリセットユーティリティ(sr-onlynot-prose など)かどうかを報告します — これはどのスロットもキーとせず、マージによって破棄されたり shadow-resolve されたりすることは決してありません。
  • isStale(expectedSourceHash) / isStaleAgainstManifest(manifest) は、レジストリに記録された sourceHash を新しく計算したものと比較します(Concepts の staleness triangle)。これが、tabula doctor と MCP サーバーのドリフトチェックが、レジストリの再ビルドが必要かどうかを判断する方法です。
  • variantProducts() / hasChain(chainPrefix, family) / hasChainClass(className) は、バリアント閉包(スキーマ v3)のアクセサーです。variantProducts() は宣言済みのプロダクト(チェーンプレフィックス → ソート済みファミリーリスト。レガシーなレジストリでは {})を返します。hasChain() は、cn() がバリアントチェーンが登録済みかどうかを判定するために呼び出すランタイムのメンバーシップゲートです(familyvariantProducts[chainPrefix] に含まれていれば true)。hasChainClass() は、tabula except add --chain によって発行された厳密な単一クラスのチェーン例外について同じ問いに答えます。この 3 つはすべて null プロトタイプで堅牢化されているため、ユーザー制御下のプレフィックスやクラス名('__proto__''constructor')が継承されたキーに誤って一致することは決してありません。

registry 上のすべてのマップ(classesvariantscustomPropertiesexceptions など)は内部的に null プロトタイプで再構築されるため、reader.registry.classes['constructor'] のようなルックアップが誤って Object.prototype 自身の constructor 関数に解決されてしまうことはありません — このリーダーにおける文字列キーのルックアップはすべて own-property セーフです。

@tabula-css/registry/generate

ts
export interface BuildRegistryOptions {
  readonly tokensJson: string;
  readonly configJson: string;
  readonly profileVersion?: string;
  readonly backend?: 'A' | 'B'; // default 'B', the oracle
  readonly packageVersions?: Readonly<Record<string, string>>;
}
export interface BuildRegistryResult {
  readonly ok: boolean;
  readonly diagnostics: readonly Diagnostic[];
  readonly result?: BackendResult;
  readonly model?: ResolvedModel;
}
export function buildRegistry(opts: BuildRegistryOptions): Promise<BuildRegistryResult>;

export interface ConformanceResult {
  readonly equal: boolean;
  readonly a: BackendResult;
  readonly b: BackendResult;
  readonly diff?: string; // the first differing JSON path, when they diverge
}
export function checkConformance(opts: BuildRegistryOptions): Promise<ConformanceResult>;

buildRegistry() は、@tabula-css/tokens の解決済みトークンパイプラインを実行した後、1 つのバックエンドを実行してコミット対象のアーティファクト(registry.jsonprofile.cssmanifest.json)を生成します。fail closed です: トークンパイプラインがエラーを報告した場合、どのバックエンドも実行されず、何も生成されません。これは tabula build が内部で呼び出しているものです。

ts
import { readFileSync } from 'node:fs';
import { buildRegistry } from '@tabula-css/registry/generate';

const { ok, result, diagnostics } = await buildRegistry({
  tokensJson: readFileSync('tokens/base.tokens.json', 'utf8'),
  configJson: readFileSync('tabula.config.json', 'utf8'),
});

checkConformance() は同じ入力に対して 両方の バックエンドを実行し、それらのレジストリがバイト単位で同一(正規化された JSON)であることを表明します — これは、Backend A のデザインシステム経路と Backend B の実ビルド経路が静かに乖離しないことを保証するために CI が実行する適合性オラクルです。

より低レベルな用途(主に @tabula-css/cli 自身のビルドパイプライン)向けに、./generate からは他にも次がエクスポートされています: assembleRegistry(解決済みモデルとウォークされた CSS の結果を、コミット対象の 3 つのアーティファクトに変換します)、buildProbeCss/walkProbeCss(PostCSS プローブシートバックエンドの構成要素)、BackendResult 型。REGISTRY_SCHEMA_VERSION もここから再エクスポートされているため、ジェネレーターとリーダーがどのバージョンを見ているかについて食い違うことはありません。

関連パッケージ

  • @tabula-css/tokens — ジェネレーターがレジストリに変換する ResolvedModel を生成します。
  • @tabula-css/mergeRegistryReader から自身を構成するランタイム。
  • @tabula-css/core — このリーダーが検証対象とする registrySchema/manifestSchema
  • Concepts — レジストリのクラスエントリがどのようなものか、そしてなぜレジストリが ground truth なのか。

Released under the MIT License.