Skip to content

@tabula-css/registry

Tabula의 레지스트리 생성기(두 개의 백엔드를 갖는 오라클)와, merge·린터·MCP 서버가 사용하는 Tailwind-프리 레지스트리 리더입니다.

설치

대부분의 프로젝트는 @tabula-css/registry에 전이적으로만 도달합니다: @tabula-css/clitabula build를 실행하기 위해 이를 의존하며, @tabula-css/merge는 런타임에 그 ./read 진입점을 의존합니다. 생성된 registry.json을 직접 읽는 커스텀 도구(커스텀 린트 규칙, 스크립트, 에디터 확장)를 만드는 경우에만 직접 설치하십시오 — 그럴 때는 @tabula-css/registry/read만 import하십시오.

bash
npm install @tabula-css/registry

@tabula-css/registry/generate 진입점(CLI가 registry.json을 생성하기 위해 내부적으로 사용)은 Tailwind 자체의 툴체인을 끌어들이지만, @tabula-css/registry/read는 결코 그렇지 않습니다.

개요

레지스트리는 Tabula의 닫힌 어휘를 구체화한 것입니다 — 핵심 개념 § 레지스트리는 근거 있는 사실이다를 참고하십시오. 이 패키지는 그 이야기의 두 축을 의도적으로 분리된 두 개의 서브패스 내보내기로 제공합니다.

  • **@tabula-css/registry/generate**는 Tailwind의 유틸리티 문법을 재구현하는 대신, Tailwind 자체의 컴파일된 CSS 출력을 두 개의 독립적인 백엔드(디자인 시스템 API, 또는 PostCSS 프로브-시트 순회) 중 하나를 통해 파싱하여 registry.json을 도출하며, CI에서는 부합성 오라클(conformance oracle)로서 서로 교차 검증됩니다.
  • **@tabula-css/registry/read**는 다른 모든 소비자가 import하는 Tailwind-프리 리더입니다: @tabula-css/merge의 런타임, ESLint 및 stylelint 플러그인, MCP 서버가 그렇습니다. 이 절반의 import 그래프에서 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처럼 보이지 않는다"는 식이 아니라, 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의 스키마(무결성의 근원 — 핵심 개념 참고)에 대해 검증하며, 유효하면 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 condition) 아래에서 className과 적어도 하나의 정규 슬롯을 공유하는 모든 클래스 이름을 반환합니다 — cn()resolve()가 그 슬롯 소유권 로직을 쌓아 올리는 사전 계산된 인덱스입니다.
  • isAtomic(className)은 클래스가 원자적 리셋 유틸리티(sr-only, not-prose 등)인지 보고합니다 — 이런 클래스는 어떤 슬롯도 소유하지 않으며 병합에 의해 결코 폐기되거나 그림자 처리(shadow-resolve)되지 않습니다.
  • isStale(expectedSourceHash) / isStaleAgainstManifest(manifest)는 레지스트리에 기록된 sourceHash를 새로 계산한 값과 비교합니다(핵심 개념의 신선도(staleness) 삼각형) — 이는 tabula doctor와 MCP 서버의 드리프트(drift) 검사가 레지스트리를 다시 빌드해야 하는지 판단하는 방식입니다.
  • variantProducts() / hasChain(chainPrefix, family) / hasChainClass(className)는 변형-폐쇄성 접근자입니다(스키마 v3). variantProducts()는 선언된 프로덕트(체인 접두사 → 정렬된 패밀리 목록, 레거시 레지스트리에서는 {})를 반환합니다; hasChain()cn()이 변형 체인이 등록되었는지 판단하기 위해 호출하는 런타임 멤버십 게이트입니다(familyvariantProducts[chainPrefix] 안에 있으면 참); hasChainClass()tabula except add --chain이 발행한 정확한 단일 클래스 체인 예외에 대해 같은 질문에 답합니다. 셋 모두 null-프로토타입으로 강화되어 있으므로, 사용자가 제어하는 접두사나 클래스 이름('__proto__', 'constructor')이 상속된 키와 실수로 일치할 수 없습니다.

registry의 모든 맵(classes, variants, customProperties, exceptions 등)은 내부적으로 null 프로토타입으로 다시 빌드되므로, reader.registry.classes['constructor'] 같은 조회가 실수로 Object.prototype 자체의 constructor 함수로 리졸브되지 않습니다 — 이 리더 전체에서 문자열 키 조회는 모두 고유 속성 안전(own-property-safe)합니다.

@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의 리졸브된 토큰 파이프라인을 실행한 뒤 한 백엔드를 실행하여 커밋 대상 아티팩트(registry.json, profile.css, manifest.json)를 산출합니다. 실패 시 폐쇄적으로 동작합니다: 토큰 파이프라인이 오류를 보고하면 어떤 백엔드도 실행되지 않고 아무것도 산출되지 않습니다. 이는 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 기준으로) 바이트 단위로 동일한지 확인합니다 — 백엔드 A의 디자인 시스템 경로와 백엔드 B의 실제 빌드 경로가 조용히 어긋나지 않도록 CI가 실행하는 부합성 오라클입니다.

./generate에서 함께 내보내는 것으로, 주로 @tabula-css/cli 자체의 빌드 파이프라인이 사용하는 저수준 요소들이 있습니다: assembleRegistry(리졸브된 모델과 순회된 CSS 결과를 세 개의 커밋 대상 아티팩트로 바꿈), buildProbeCss/walkProbeCss(PostCSS 프로브-시트 백엔드의 구성 요소), BackendResult 타입. REGISTRY_SCHEMA_VERSION도 여기서 다시 내보내어, 생성기와 리더가 어떤 버전을 보고 있는지에 대해 결코 어긋나지 않도록 합니다.

함께 보기

  • @tabula-css/tokens — 생성기가 레지스트리로 바꾸는 ResolvedModel을 산출합니다.
  • @tabula-css/mergeRegistryReader로부터 스스로를 구성하는 런타임.
  • @tabula-css/core — 이 리더가 대조하여 검증하는 registrySchema/manifestSchema.
  • 핵심 개념 — 레지스트리 클래스 항목의 모습과, 레지스트리가 왜 근거 있는 사실인지.

Released under the MIT License.