@texuli/spec 0.0.0-stage → 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -2
  3. package/dist/build.d.ts +47 -0
  4. package/dist/build.js +65 -0
  5. package/dist/canonical.d.ts +17 -0
  6. package/dist/canonical.js +131 -0
  7. package/dist/catalog.d.ts +41 -0
  8. package/dist/catalog.js +45 -0
  9. package/dist/codegen/index.d.ts +13 -0
  10. package/dist/codegen/index.js +12 -0
  11. package/dist/codegen/kotlin.d.ts +18 -0
  12. package/dist/codegen/kotlin.js +328 -0
  13. package/dist/codegen/shared.d.ts +35 -0
  14. package/dist/codegen/shared.js +69 -0
  15. package/dist/codegen/typescript.d.ts +10 -0
  16. package/dist/codegen/typescript.js +167 -0
  17. package/dist/conformance.d.ts +20 -0
  18. package/dist/conformance.js +124 -0
  19. package/dist/dialect.d.ts +56 -0
  20. package/dist/dialect.js +264 -0
  21. package/dist/format.d.ts +9 -0
  22. package/dist/format.js +48 -0
  23. package/dist/index.d.ts +17 -0
  24. package/dist/index.js +13 -0
  25. package/dist/validate.d.ts +25 -0
  26. package/dist/validate.js +118 -0
  27. package/dist/version.d.ts +2 -0
  28. package/dist/version.js +2 -0
  29. package/generated/typescript/blueprint.d.ts +83 -0
  30. package/generated/typescript/blueprint.js +3 -0
  31. package/generated/typescript/catalog-schema.d.ts +33 -0
  32. package/generated/typescript/catalog-schema.js +3 -0
  33. package/generated/typescript/diagnostics.d.ts +12 -0
  34. package/generated/typescript/diagnostics.js +3 -0
  35. package/generated/typescript/manifest.d.ts +9 -0
  36. package/generated/typescript/manifest.js +3 -0
  37. package/generated/typescript/vocabulary.d.ts +943 -0
  38. package/generated/typescript/vocabulary.js +138 -0
  39. package/package.json +26 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dailybot, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # @texuli/spec
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The schema-first blueprint contract (spec/Compose design, plan B1): the
4
+ platform-neutral source of truth for blueprint structure, the catalog
5
+ vocabulary, capability tiers and diagnostics. TypeScript and Kotlin are
6
+ deterministic projections generated from it, and a conformance harness keeps
7
+ everything from drifting.
8
+
9
+ **Status: in development, not published.** Layer 0, no dependencies,
10
+ framework-free. Kotlin is generated as committed text by this package's
11
+ TypeScript generator; no Kotlin toolchain runs here, and nothing is compiled
12
+ or published to Maven.
13
+
14
+ ## Layout
15
+
16
+ | Path | What it holds |
17
+ | --- | --- |
18
+ | `schemas/` | The normative JSON schemas: `blueprint` (envelope v1 and the node tree), `catalog`, `diagnostics`, `manifest` |
19
+ | `catalog/components.json` | The closed vocabulary, projected from the built catalog: id, label, summary, group, placement (`app`/`content`), emit tiers, capability and props |
20
+ | `catalog/capabilities.json` | Reviewed capability claims. Empty: no id is audited portable yet, so every capability is `unknown` (ADR-003) |
21
+ | `fixtures/golden/` | Blueprints that must validate: a report, a dashboard and a proactive deck (fictional data) |
22
+ | `fixtures/invalid/` | Blueprints that must fail with exact diagnostic codes, one or more per code |
23
+ | `manifest.json` | The spec version, the dialect and a SHA-256 per normative file |
24
+ | `generated/typescript/`, `generated/kotlin/` | The projections. Never edit them |
25
+ | `generated-manifest.json` | A SHA-256 per generated file |
26
+
27
+ ## The dialect
28
+
29
+ Schemas use a closed subset of JSON Schema 2020-12 (`DIALECT_KEYWORDS`):
30
+ `type`, `properties`, `required`, `additionalProperties`, `items`, `enum`,
31
+ `const`, string, number and array bounds, `pattern`, and local `$ref` /
32
+ `$defs`. Objects must be closed (`additionalProperties: false`) or explicit
33
+ maps. Anything else fails `checkSchemaDocument`, and generation refuses it.
34
+
35
+ ## Exports
36
+
37
+ - `@texuli/spec`, the runtime half:
38
+ - the generated types (`BlueprintEnvelope`, `SpecCatalog`, `SpecValidation` …);
39
+ - `SPEC_COMPONENT_IDS` and `SpecComponentProps`;
40
+ - `validateInstance` (the dialect validator);
41
+ - `validateBlueprint` (the target-neutral blueprint check: envelope
42
+ schema, closed ids and props, prop kinds and enums, required props, no
43
+ platform-extension props from data, the portable profile failing closed,
44
+ and an optional catalog binding);
45
+ - `canonicalJson` and a synchronous `sha256`.
46
+ - `@texuli/spec/codegen`, build time: `buildSpec`, `projectCatalog`,
47
+ `checkConformance`, `formatJson`.
48
+
49
+ URL policy, tree limits, grammar and rendering stay in the runtime validator,
50
+ `@texuli/catalog/validate`.
51
+
52
+ ## Projections
53
+
54
+ - **Web extensions.** Props of kind `slot`, `event` or `unknown`, and the
55
+ `class`, `className`, `style` and chart `renderer` props, are marked
56
+ `extension: "web"`. They are left out of `SpecComponentProps`, and data
57
+ that sets them is refused.
58
+ - **Kotlin** (`com.texuli.spec`, Kotlin 1.9+ for `entries` and
59
+ `data object`): data classes, closed enums and static decoders over a
60
+ generated `JsonValue` model, with no library dependency. Decoders check
61
+ JSON types, required and unknown fields, enums and constants. Patterns
62
+ and limits are the shared validator's job.
63
+ - **Stamp.** Every generated file carries the spec version and a digest of
64
+ the normative files. There are no timestamps, and output is
65
+ byte-identical for the same inputs in any order.
66
+
67
+ ## Regenerate
68
+
69
+ ```bash
70
+ pnpm exec tsx packages/spec/scripts/bootstrap-catalog.ts # refresh catalog/components.json from the built catalog
71
+ pnpm exec tsx packages/spec/scripts/generate.ts # write manifests and projections
72
+ pnpm exec tsx packages/spec/scripts/generate.ts --check # fail on drift
73
+ ```
74
+
75
+ ## Conformance
76
+
77
+ `pnpm vitest run packages/spec` runs the harness. Drift is a failed gate:
78
+ - every schema stays in the dialect;
79
+ - the manifest digests match the normative files;
80
+ - regenerating gives byte-identical files, none missing or extra;
81
+ - the golden fixtures validate and the invalid fixtures fail with exactly
82
+ their codes, covering every diagnostic code;
83
+ - `components.json` equals the projection of the live catalog, and the
84
+ blueprint schema matches the envelope `@texuli/catalog` reads;
85
+ - generation is deterministic and refuses schemas outside the dialect;
86
+ - the Kotlin text declares its package, balances its brackets, defines
87
+ every type it references and decodes closed.
88
+
89
+ When the catalog changes, refresh `components.json`, regenerate and review
90
+ the diff.
91
+
92
+ ## Not yet
93
+
94
+ - Behavior contracts, token roles and the token-to-Compose generator (B1
95
+ steps 2 and 5).
96
+ - Making the spec authoritative for the web packages (B1 step 3 cutover);
97
+ today the catalog manifests are the bootstrap input.
98
+ - Compiling the Kotlin, and the native runtime (B2).
@@ -0,0 +1,47 @@
1
+ import type { Stamp } from './codegen/shared.js';
2
+ import { SPEC_VERSION } from './version.js';
3
+ export { SPEC_VERSION };
4
+ /** Where normative files live inside the package. */
5
+ export declare const NORMATIVE_DIRS: readonly ["schemas/", "catalog/", "fixtures/"];
6
+ /** The schema documents, their generated names and output files. */
7
+ export declare const SCHEMA_UNITS: readonly [{
8
+ readonly name: "blueprint";
9
+ readonly prefix: "Blueprint";
10
+ readonly ts: "blueprint.ts";
11
+ readonly kt: "Blueprint.kt";
12
+ }, {
13
+ readonly name: "catalog";
14
+ readonly prefix: "Spec";
15
+ readonly ts: "catalog-schema.ts";
16
+ readonly kt: "CatalogSchema.kt";
17
+ }, {
18
+ readonly name: "diagnostics";
19
+ readonly prefix: "Spec";
20
+ readonly ts: "diagnostics.ts";
21
+ readonly kt: "Diagnostics.kt";
22
+ }, {
23
+ readonly name: "manifest";
24
+ readonly prefix: "Spec";
25
+ readonly ts: "manifest.ts";
26
+ readonly kt: "Manifest.kt";
27
+ }];
28
+ export declare const TS_OUT = "generated/typescript/";
29
+ export declare const KOTLIN_OUT = "generated/kotlin/com/texuli/spec/";
30
+ export interface SpecBuild {
31
+ /** `manifest.json`: the spec version and a digest per normative file. */
32
+ manifest: string;
33
+ /** The stamp every generated file carries. */
34
+ stamp: Stamp;
35
+ /** Generated path → content. */
36
+ generated: Readonly<Record<string, string>>;
37
+ /** `generated-manifest.json`: a digest per generated file. */
38
+ generatedManifest: string;
39
+ }
40
+ /**
41
+ * Build the whole spec from its normative files (path → exact text): the
42
+ * manifest, every TypeScript and Kotlin projection and the generated
43
+ * manifest. Pure and deterministic: the same files give byte-identical
44
+ * output, whatever order they are passed in. Throws when a schema leaves
45
+ * the dialect.
46
+ */
47
+ export declare function buildSpec(files: Readonly<Record<string, string>>, specVersion?: string): SpecBuild;
package/dist/build.js ADDED
@@ -0,0 +1,65 @@
1
+ import { canonicalJson, sha256 } from "./canonical.js";
2
+ import { kotlinJsonRuntime, schemaToKotlin, vocabularyToKotlin } from "./codegen/kotlin.js";
3
+ import { schemaToTypeScript, vocabularyToTypeScript } from "./codegen/typescript.js";
4
+ import { DIALECT_URI } from "./dialect.js";
5
+ import { formatJson } from "./format.js";
6
+ import { SPEC_VERSION } from "./version.js";
7
+ export { SPEC_VERSION };
8
+ /** Where normative files live inside the package. */
9
+ export const NORMATIVE_DIRS = ['schemas/', 'catalog/', 'fixtures/'];
10
+ /** The schema documents, their generated names and output files. */
11
+ export const SCHEMA_UNITS = [
12
+ { name: 'blueprint', prefix: 'Blueprint', ts: 'blueprint.ts', kt: 'Blueprint.kt' },
13
+ { name: 'catalog', prefix: 'Spec', ts: 'catalog-schema.ts', kt: 'CatalogSchema.kt' },
14
+ { name: 'diagnostics', prefix: 'Spec', ts: 'diagnostics.ts', kt: 'Diagnostics.kt' },
15
+ { name: 'manifest', prefix: 'Spec', ts: 'manifest.ts', kt: 'Manifest.kt' },
16
+ ];
17
+ export const TS_OUT = 'generated/typescript/';
18
+ export const KOTLIN_OUT = 'generated/kotlin/com/texuli/spec/';
19
+ /**
20
+ * Build the whole spec from its normative files (path → exact text): the
21
+ * manifest, every TypeScript and Kotlin projection and the generated
22
+ * manifest. Pure and deterministic: the same files give byte-identical
23
+ * output, whatever order they are passed in. Throws when a schema leaves
24
+ * the dialect.
25
+ */
26
+ export function buildSpec(files, specVersion = SPEC_VERSION) {
27
+ const paths = Object.keys(files).sort();
28
+ for (const path of paths) {
29
+ if (!NORMATIVE_DIRS.some((dir) => path.startsWith(dir)) || !path.endsWith('.json')) {
30
+ throw new Error(`${path}: not a normative spec file`);
31
+ }
32
+ }
33
+ const manifestData = {
34
+ specVersion,
35
+ dialect: DIALECT_URI,
36
+ files: Object.fromEntries(paths.map((path) => [path, sha256(files[path])])),
37
+ };
38
+ const stamp = { specVersion, specDigest: sha256(canonicalJson(manifestData)) };
39
+ const read = (path) => {
40
+ const text = files[path];
41
+ if (text === undefined)
42
+ throw new Error(`${path}: missing`);
43
+ return JSON.parse(text);
44
+ };
45
+ const generated = {};
46
+ for (const unit of SCHEMA_UNITS) {
47
+ const source = `schemas/${unit.name}.schema.json`;
48
+ const schemaUnit = { source, schema: read(source), prefix: unit.prefix };
49
+ generated[`${TS_OUT}${unit.ts}`] = schemaToTypeScript(schemaUnit, stamp);
50
+ generated[`${KOTLIN_OUT}${unit.kt}`] = schemaToKotlin(schemaUnit, stamp);
51
+ }
52
+ const catalog = read('catalog/components.json');
53
+ generated[`${TS_OUT}vocabulary.ts`] = vocabularyToTypeScript(catalog, stamp);
54
+ generated[`${KOTLIN_OUT}Vocabulary.kt`] = vocabularyToKotlin(catalog, stamp);
55
+ generated[`${KOTLIN_OUT}Json.kt`] = kotlinJsonRuntime(stamp);
56
+ const sorted = Object.fromEntries(Object.keys(generated)
57
+ .sort()
58
+ .map((path) => [path, generated[path]]));
59
+ const generatedManifest = formatJson({
60
+ specVersion,
61
+ dialect: DIALECT_URI,
62
+ files: Object.fromEntries(Object.entries(sorted).map(([path, text]) => [path, sha256(text)])),
63
+ });
64
+ return { manifest: formatJson(manifestData), stamp, generated: sorted, generatedManifest };
65
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Deterministic serialization and hashing, with no runtime dependency: the
3
+ * same input gives the same bytes and digest in every runtime.
4
+ */
5
+ /**
6
+ * Canonical JSON: object keys sorted by UTF-16 code unit at every depth, no
7
+ * whitespace, `undefined` fields dropped. Throws on values JSON cannot carry
8
+ * exactly (functions, non-finite numbers, cycles).
9
+ */
10
+ export declare function canonicalJson(value: unknown): string;
11
+ /**
12
+ * Pretty canonical JSON for committed files: sorted keys, two-space indent,
13
+ * a trailing newline. Byte-stable for the same data.
14
+ */
15
+ export declare function prettyJson(value: unknown): string;
16
+ /** Lowercase hex SHA-256 of a string's UTF-8 bytes (FIPS 180-4), synchronous. */
17
+ export declare function sha256(text: string): string;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Deterministic serialization and hashing, with no runtime dependency: the
3
+ * same input gives the same bytes and digest in every runtime.
4
+ */
5
+ /**
6
+ * Canonical JSON: object keys sorted by UTF-16 code unit at every depth, no
7
+ * whitespace, `undefined` fields dropped. Throws on values JSON cannot carry
8
+ * exactly (functions, non-finite numbers, cycles).
9
+ */
10
+ export function canonicalJson(value) {
11
+ return encode(value, new Set());
12
+ }
13
+ function encode(value, stack) {
14
+ if (value === null)
15
+ return 'null';
16
+ if (typeof value === 'string' || typeof value === 'boolean')
17
+ return JSON.stringify(value);
18
+ if (typeof value === 'number') {
19
+ if (!Number.isFinite(value))
20
+ throw new TypeError('canonicalJson: non-finite number');
21
+ return JSON.stringify(value);
22
+ }
23
+ if (typeof value !== 'object')
24
+ throw new TypeError(`canonicalJson: ${typeof value} value`);
25
+ if (stack.has(value))
26
+ throw new TypeError('canonicalJson: cycle');
27
+ stack.add(value);
28
+ let out;
29
+ if (Array.isArray(value)) {
30
+ out = `[${value.map((item) => encode(item === undefined ? null : item, stack)).join(',')}]`;
31
+ }
32
+ else {
33
+ const record = value;
34
+ const keys = Object.keys(record)
35
+ .filter((key) => record[key] !== undefined)
36
+ .sort();
37
+ out = `{${keys.map((key) => `${JSON.stringify(key)}:${encode(record[key], stack)}`).join(',')}}`;
38
+ }
39
+ stack.delete(value);
40
+ return out;
41
+ }
42
+ /**
43
+ * Pretty canonical JSON for committed files: sorted keys, two-space indent,
44
+ * a trailing newline. Byte-stable for the same data.
45
+ */
46
+ export function prettyJson(value) {
47
+ return `${JSON.stringify(JSON.parse(canonicalJson(value)), null, 2)}\n`;
48
+ }
49
+ const K = new Uint32Array([
50
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
51
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
52
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
53
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
54
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
55
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
56
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
57
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
58
+ ]);
59
+ /** UTF-8 bytes of a string (no `TextEncoder` needed). */
60
+ function utf8(text) {
61
+ const bytes = [];
62
+ for (const char of text) {
63
+ const code = char.codePointAt(0) ?? 0;
64
+ if (code < 0x80)
65
+ bytes.push(code);
66
+ else if (code < 0x800)
67
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
68
+ else if (code < 0x10000)
69
+ bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
70
+ else
71
+ bytes.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
72
+ }
73
+ return bytes;
74
+ }
75
+ /** Lowercase hex SHA-256 of a string's UTF-8 bytes (FIPS 180-4), synchronous. */
76
+ export function sha256(text) {
77
+ const bytes = utf8(text);
78
+ const bitLength = bytes.length * 8;
79
+ bytes.push(0x80);
80
+ while (bytes.length % 64 !== 56)
81
+ bytes.push(0);
82
+ const high = Math.floor(bitLength / 0x100000000);
83
+ for (const word of [high, bitLength >>> 0]) {
84
+ bytes.push((word >>> 24) & 0xff, (word >>> 16) & 0xff, (word >>> 8) & 0xff, word & 0xff);
85
+ }
86
+ const hash = new Uint32Array([
87
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
88
+ ]);
89
+ const w = new Uint32Array(64);
90
+ const rotr = (x, n) => (x >>> n) | (x << (32 - n));
91
+ for (let offset = 0; offset < bytes.length; offset += 64) {
92
+ for (let i = 0; i < 16; i += 1) {
93
+ const at = offset + i * 4;
94
+ w[i] =
95
+ ((bytes[at] ?? 0) << 24) |
96
+ ((bytes[at + 1] ?? 0) << 16) |
97
+ ((bytes[at + 2] ?? 0) << 8) |
98
+ (bytes[at + 3] ?? 0);
99
+ }
100
+ for (let i = 16; i < 64; i += 1) {
101
+ const a = w[i - 15] ?? 0;
102
+ const b = w[i - 2] ?? 0;
103
+ const s0 = rotr(a, 7) ^ rotr(a, 18) ^ (a >>> 3);
104
+ const s1 = rotr(b, 17) ^ rotr(b, 19) ^ (b >>> 10);
105
+ w[i] = ((w[i - 16] ?? 0) + s0 + (w[i - 7] ?? 0) + s1) >>> 0;
106
+ }
107
+ let [a, b, c, d, e, f, g, h] = hash;
108
+ for (let i = 0; i < 64; i += 1) {
109
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
110
+ const ch = (e & f) ^ (~e & g);
111
+ const t1 = (h + s1 + ch + (K[i] ?? 0) + (w[i] ?? 0)) >>> 0;
112
+ const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
113
+ const maj = (a & b) ^
114
+ (a & c) ^
115
+ (b & c);
116
+ const t2 = (s0 + maj) >>> 0;
117
+ h = g;
118
+ g = f;
119
+ f = e;
120
+ e = (d + t1) >>> 0;
121
+ d = c;
122
+ c = b;
123
+ b = a;
124
+ a = (t1 + t2) >>> 0;
125
+ }
126
+ const round = [a, b, c, d, e, f, g, h];
127
+ for (let i = 0; i < 8; i += 1)
128
+ hash[i] = ((hash[i] ?? 0) + (round[i] ?? 0)) >>> 0;
129
+ }
130
+ return Array.from(hash, (word) => word.toString(16).padStart(8, '0')).join('');
131
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The spec's catalog vocabulary (`catalog/components.json`) and its
3
+ * projection from a built catalog. The projection is the bootstrap step of
4
+ * spec/Compose design §2.3; the parity check reruns it to catch drift.
5
+ */
6
+ import type { SpecCatalog, SpecComponent, SpecProp } from '../generated/typescript/catalog-schema.js';
7
+ export type { SpecCatalog, SpecComponent, SpecProp };
8
+ export declare const CAPABILITIES: readonly ["portable-core", "web-only", "native-only", "unknown"];
9
+ export type Capability = SpecComponent['capability'];
10
+ export type SpecPropKind = SpecProp['kind'];
11
+ /** Prop kinds that cannot cross the shared boundary: they become web extensions. */
12
+ export declare const WEB_ONLY_KINDS: readonly SpecPropKind[];
13
+ /**
14
+ * Props that are web implementation details whatever their kind (spec/Compose
15
+ * design §6): CSS hooks and the chart renderer choice (`svg`, `canvas`, `auto`).
16
+ */
17
+ export declare const WEB_ONLY_PROP_NAMES: readonly string[];
18
+ /** The fields of a built catalog the projection reads (no dependency on the catalog package). */
19
+ export interface BuiltCatalogLike {
20
+ entries: readonly {
21
+ id: string;
22
+ label: string;
23
+ summary: string;
24
+ group: string;
25
+ tier: 'app' | 'content';
26
+ emit: readonly string[];
27
+ props: readonly {
28
+ name: string;
29
+ kind: SpecPropKind;
30
+ required?: boolean;
31
+ enumValues?: readonly string[];
32
+ }[];
33
+ }[];
34
+ }
35
+ /**
36
+ * Project a built catalog into the spec vocabulary. Deterministic: components
37
+ * sorted by id, props by name, emit tiers sorted. Capability is `unknown`
38
+ * unless `capabilities` (the reviewed `catalog/capabilities.json`) records an
39
+ * audited value (ADR-003: no id is portable until audited).
40
+ */
41
+ export declare function projectCatalog(catalog: BuiltCatalogLike, catalogHash: string, capabilities?: Readonly<Record<string, Capability>>): SpecCatalog;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The spec's catalog vocabulary (`catalog/components.json`) and its
3
+ * projection from a built catalog. The projection is the bootstrap step of
4
+ * spec/Compose design §2.3; the parity check reruns it to catch drift.
5
+ */
6
+ export const CAPABILITIES = ['portable-core', 'web-only', 'native-only', 'unknown'];
7
+ /** Prop kinds that cannot cross the shared boundary: they become web extensions. */
8
+ export const WEB_ONLY_KINDS = ['slot', 'event', 'unknown'];
9
+ /**
10
+ * Props that are web implementation details whatever their kind (spec/Compose
11
+ * design §6): CSS hooks and the chart renderer choice (`svg`, `canvas`, `auto`).
12
+ */
13
+ export const WEB_ONLY_PROP_NAMES = ['class', 'className', 'style', 'renderer'];
14
+ /**
15
+ * Project a built catalog into the spec vocabulary. Deterministic: components
16
+ * sorted by id, props by name, emit tiers sorted. Capability is `unknown`
17
+ * unless `capabilities` (the reviewed `catalog/capabilities.json`) records an
18
+ * audited value (ADR-003: no id is portable until audited).
19
+ */
20
+ export function projectCatalog(catalog, catalogHash, capabilities = {}) {
21
+ const byName = (a, b) => (a.name < b.name ? -1 : 1);
22
+ const components = [...catalog.entries]
23
+ .sort((a, b) => (a.id < b.id ? -1 : 1))
24
+ .map((entry) => ({
25
+ id: entry.id,
26
+ label: entry.label,
27
+ summary: entry.summary,
28
+ group: entry.group,
29
+ placement: entry.tier,
30
+ emit: [...entry.emit].sort(),
31
+ capability: Object.hasOwn(capabilities, entry.id)
32
+ ? capabilities[entry.id]
33
+ : 'unknown',
34
+ props: [...entry.props].sort(byName).map((prop) => ({
35
+ name: prop.name,
36
+ kind: prop.kind,
37
+ ...(prop.required === true ? { required: true } : {}),
38
+ ...(prop.enumValues ? { enumValues: [...prop.enumValues] } : {}),
39
+ ...(WEB_ONLY_KINDS.includes(prop.kind) || WEB_ONLY_PROP_NAMES.includes(prop.name)
40
+ ? { extension: 'web' }
41
+ : {}),
42
+ })),
43
+ }));
44
+ return { catalogHash, components };
45
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@texuli/spec/codegen` — build-time tooling: deterministic generation of
3
+ * the manifests and the TypeScript and Kotlin projections, the catalog
4
+ * projection, the committed-file JSON format and the conformance harness.
5
+ * Pure functions: callers read and write the files.
6
+ */
7
+ export { buildSpec, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, type SpecBuild, TS_OUT, } from '../build.js';
8
+ export { prettyJson } from '../canonical.js';
9
+ export { type BuiltCatalogLike, projectCatalog } from '../catalog.js';
10
+ export { type ConformanceIssue, checkConformance } from '../conformance.js';
11
+ export { formatJson, LINE_WIDTH } from '../format.js';
12
+ export { KOTLIN_PACKAGE, kotlinString } from './kotlin.js';
13
+ export type { SchemaUnit, Stamp } from './shared.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@texuli/spec/codegen` — build-time tooling: deterministic generation of
3
+ * the manifests and the TypeScript and Kotlin projections, the catalog
4
+ * projection, the committed-file JSON format and the conformance harness.
5
+ * Pure functions: callers read and write the files.
6
+ */
7
+ export { buildSpec, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, TS_OUT, } from "../build.js";
8
+ export { prettyJson } from "../canonical.js";
9
+ export { projectCatalog } from "../catalog.js";
10
+ export { checkConformance } from "../conformance.js";
11
+ export { formatJson, LINE_WIDTH } from "../format.js";
12
+ export { KOTLIN_PACKAGE, kotlinString } from "./kotlin.js";
@@ -0,0 +1,18 @@
1
+ import type { SpecCatalog } from '../catalog.js';
2
+ import { type SchemaUnit, type Stamp } from './shared.js';
3
+ /**
4
+ * Deterministic Kotlin projections of the spec, written as committed text
5
+ * (no Kotlin toolchain here): data classes, closed enums and static
6
+ * decoders over a small `JsonValue` model, with no library dependency.
7
+ * Decoders check JSON types, required and unknown fields, enums and
8
+ * constants; patterns and limits stay with the shared validator.
9
+ */
10
+ export declare const KOTLIN_PACKAGE = "com.texuli.spec";
11
+ /** A Kotlin string literal (escapes backslash, quote, dollar and controls). */
12
+ export declare function kotlinString(value: string): string;
13
+ /** One schema document as a Kotlin file of data classes, enums and decoders. */
14
+ export declare function schemaToKotlin(unit: SchemaUnit, stamp: Stamp): string;
15
+ /** The JSON model and decode helpers every generated decoder uses. */
16
+ export declare function kotlinJsonRuntime(stamp: Stamp): string;
17
+ /** The catalog vocabulary as a Kotlin registry of `SpecComponent` values. */
18
+ export declare function vocabularyToKotlin(catalog: SpecCatalog, stamp: Stamp): string;