@tabula-css/registry
Tabula의 레지스트리 생성기(두 개의 백엔드를 갖는 오라클)와, merge·린터·MCP 서버가 사용하는 Tailwind-프리 레지스트리 리더입니다.
설치
대부분의 프로젝트는 @tabula-css/registry에 전이적으로만 도달합니다: @tabula-css/cli는 tabula build를 실행하기 위해 이를 의존하며, @tabula-css/merge는 런타임에 그 ./read 진입점을 의존합니다. 생성된 registry.json을 직접 읽는 커스텀 도구(커스텀 린트 규칙, 스크립트, 에디터 확장)를 만드는 경우에만 직접 설치하십시오 — 그럴 때는 @tabula-css/registry/read만 import하십시오.
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
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; // 3readRegistry()는 레지스트리를 로드하고 검증하여 RegistryReader를 반환합니다. input은 파싱된 RegistryFile 객체, JSON 문자열, registry.json으로의 파일시스템 경로 중 하나입니다(경로는 "JSON처럼 보이지 않는다"는 식이 아니라, 4096자 미만의 개행 없는 한 줄이라는 긍정적 기준으로 인식되므로, 잘리거나 비어 있는 파일은 헷갈리는 원시 ENOENT 대신 TAB-E303 진단을 받습니다). 잘못된 JSON, 스키마 위반, 또는 리더가 이해하지 못하는 registrySchemaVersion(TAB-E302)에 대해서는 RegistryReadError를 던집니다.
import { readRegistry } from '@tabula-css/registry/read';
const reader = readRegistry('.tabula/registry.json');
reader.has('bg-surface'); // truevalidateManifest()는 파싱된 manifest.json을 core의 스키마(무결성의 근원 — 핵심 개념 참고)에 대해 검증하며, 유효하면 null을, 그렇지 않으면 합쳐진 오류 문자열을 반환합니다.
RegistryReader
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()이 변형 체인이 등록되었는지 판단하기 위해 호출하는 런타임 멤버십 게이트입니다(family가variantProducts[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
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가 내부적으로 호출하는 함수입니다.
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/merge—RegistryReader로부터 스스로를 구성하는 런타임.@tabula-css/core— 이 리더가 대조하여 검증하는registrySchema/manifestSchema.- 핵심 개념 — 레지스트리 클래스 항목의 모습과, 레지스트리가 왜 근거 있는 사실인지.