@canonical/anatomy-dsl 0.5.0 → 0.5.1

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.
@@ -0,0 +1,4 @@
1
+ export { parseAnatomyYAML } from "./parse.js";
2
+ export { PLACEHOLDER_SEGMENTS, STYLE_KEYS, takesToken, } from "./registry.generated.js";
3
+ export { anatomyToTTL } from "./transform.js";
4
+ export { AnatomyValueError, authoredSpelling, classifyElement, liftSymbols, parseStyleValue, RULES, } from "./value.js";
@@ -0,0 +1,138 @@
1
+ import { parseDocument } from "yaml";
2
+ import { authoredSpelling, liftSymbols } from "./value.js";
3
+ /**
4
+ * The one lift every consumer shares. It is implemented in `./value.js`,
5
+ * beside the grammar it belongs to, and re-exported here because the parser
6
+ * is where a reader looks for it.
7
+ */
8
+ export { liftSymbols } from "./value.js";
9
+ /**
10
+ * Parse an anatomy document.
11
+ *
12
+ * Give it the YAML text — which is what design-system holds, one
13
+ * `ds:anatomyDsl` literal per block — and it is parsed here, with the
14
+ * `yaml` package's Document API; that is why `yaml` is a runtime dependency
15
+ * and not a devDependency. An already-parsed value is still accepted, for a
16
+ * caller that has one.
17
+ */
18
+ export function parseAnatomyYAML(raw) {
19
+ const doc = (typeof raw === "string" ? parseDocument(raw).toJS() : raw);
20
+ if (doc === null || typeof doc !== "object" || doc.node === undefined) {
21
+ throw new Error("An anatomy document is a mapping with one `node` key");
22
+ }
23
+ return {
24
+ root: toNamedNode(doc.node),
25
+ };
26
+ }
27
+ function toNamedNode(raw) {
28
+ return {
29
+ type: "named",
30
+ uri: raw.uri,
31
+ ...(raw.projection ? { projection: toProjection(raw.projection) } : {}),
32
+ ...(raw.props ? { props: toProps(raw.props) } : {}),
33
+ ...(raw.styles ? { styles: toStyles(raw.styles) } : {}),
34
+ ...(raw.edges ? { edges: raw.edges.map(toEdge) } : {}),
35
+ };
36
+ }
37
+ function toNode(raw) {
38
+ if ("uri" in raw && raw.uri) {
39
+ return toNamedNode(raw);
40
+ }
41
+ if ("props" in raw) {
42
+ throw new Error("Props are only allowed on named nodes");
43
+ }
44
+ const anon = raw;
45
+ return {
46
+ type: "anonymous",
47
+ role: anon.role,
48
+ ...(anon.projection ? { projection: toProjection(anon.projection) } : {}),
49
+ ...(anon.styles ? { styles: toStyles(anon.styles) } : {}),
50
+ ...(anon.edges ? { edges: anon.edges.map(toEdge) } : {}),
51
+ };
52
+ }
53
+ function toProjection(raw) {
54
+ if (raw.on === undefined && raw.field === undefined) {
55
+ throw new Error("Projection must have at least one of on, field");
56
+ }
57
+ return {
58
+ ...(raw.on !== undefined ? { on: raw.on } : {}),
59
+ ...(raw.field !== undefined ? { field: raw.field } : {}),
60
+ };
61
+ }
62
+ function toStyles(raw) {
63
+ return Object.entries(raw).map(([rawKey, value]) => {
64
+ const [key, state, ...rest] = rawKey.split("@");
65
+ if (rest.length > 0) {
66
+ throw new Error(`Compound states are not yet supported: ${rawKey}`);
67
+ }
68
+ if (state === "") {
69
+ throw new Error(`Style key has an empty state: ${rawKey}`);
70
+ }
71
+ if (state === "default") {
72
+ throw new Error(`The unmarked key is the default state — drop "@default": ${rawKey}`);
73
+ }
74
+ return {
75
+ key: key,
76
+ // The authored spelling is kept verbatim as evidence; the symbols are
77
+ // lifted from it by the one lift every consumer shares (§4.1).
78
+ value: authoredSpelling(value),
79
+ symbols: liftSymbols(value, key),
80
+ ...(state !== undefined ? { state } : {}),
81
+ };
82
+ });
83
+ }
84
+ function toProps(raw) {
85
+ return Object.entries(raw).map(([name, value]) => ({
86
+ name,
87
+ value: String(value),
88
+ }));
89
+ }
90
+ function toEdge(raw) {
91
+ let target;
92
+ if (raw.switch) {
93
+ target = toSwitch(raw.switch);
94
+ }
95
+ else if (raw.node) {
96
+ target = toNode(raw.node);
97
+ }
98
+ else if (raw.uri) {
99
+ target = { type: "named", uri: raw.uri };
100
+ }
101
+ else {
102
+ throw new Error("Edge must have node, uri, or switch");
103
+ }
104
+ return {
105
+ target,
106
+ relation: toRelation(raw.relation),
107
+ };
108
+ }
109
+ function toSwitch(raw) {
110
+ return {
111
+ discriminator: raw.on,
112
+ cases: raw.cases.map(toSwitchCase),
113
+ };
114
+ }
115
+ function toSwitchCase(raw) {
116
+ if (raw.uri !== undefined) {
117
+ const node = { type: "named", uri: raw.uri };
118
+ return { value: raw.uri, node };
119
+ }
120
+ const node = toNode(raw.node);
121
+ const value = node.type === "named" ? node.uri : node.role;
122
+ return { value, node };
123
+ }
124
+ function toRelation(raw) {
125
+ return {
126
+ cardinality: raw.cardinality,
127
+ ...(raw.slotName ? { slotName: raw.slotName } : {}),
128
+ ...(raw.projection
129
+ ? { projection: toRelationProjection(raw.projection) }
130
+ : {}),
131
+ };
132
+ }
133
+ function toRelationProjection(raw) {
134
+ if (!raw.field) {
135
+ throw new Error("Relation projection requires a field");
136
+ }
137
+ return { field: raw.field };
138
+ }
@@ -0,0 +1,465 @@
1
+ /**
2
+ * GENERATED by `bun run generate:registry` from definitions/style-keys.yaml.
3
+ * Do not edit: change the roster and regenerate. The Turtle form of the same
4
+ * facts is definitions/registry.ttl, which also carries the roster's
5
+ * provenance, and CI diffs both.
6
+ */
7
+ /** Segments that mark a value as a placeholder rather than a symbol (§4.1). */
8
+ export const PLACEHOLDER_SEGMENTS = [
9
+ "sth",
10
+ "tbd",
11
+ "xxx",
12
+ "todo",
13
+ ];
14
+ /** One entry per canonical style key. The roster is closed. */
15
+ export const STYLE_KEYS = {
16
+ "appearance.background": {
17
+ valueKind: "either",
18
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
19
+ },
20
+ "appearance.background.image": {
21
+ valueKind: "primitive",
22
+ tokenNamespace: [],
23
+ },
24
+ "appearance.background.position": {
25
+ valueKind: "primitive",
26
+ tokenNamespace: [],
27
+ },
28
+ "appearance.background.repeat": {
29
+ valueKind: "primitive",
30
+ tokenNamespace: [],
31
+ },
32
+ "appearance.background.size": {
33
+ valueKind: "primitive",
34
+ tokenNamespace: [],
35
+ },
36
+ "appearance.border.block.start.color": {
37
+ valueKind: "token",
38
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
39
+ },
40
+ "appearance.border.bottom.color": {
41
+ valueKind: "token",
42
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
43
+ },
44
+ "appearance.border.color": {
45
+ valueKind: "either",
46
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
47
+ },
48
+ "appearance.border.inline.end.color": {
49
+ valueKind: "token",
50
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
51
+ },
52
+ "appearance.border.style": {
53
+ valueKind: "primitive",
54
+ tokenNamespace: [],
55
+ },
56
+ "appearance.border.top.width": {
57
+ valueKind: "either",
58
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
59
+ },
60
+ "appearance.border.width": {
61
+ valueKind: "either",
62
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
63
+ },
64
+ "appearance.native": {
65
+ valueKind: "primitive",
66
+ tokenNamespace: [],
67
+ },
68
+ "appearance.opacity": {
69
+ valueKind: "primitive",
70
+ tokenNamespace: [],
71
+ },
72
+ "appearance.outline.color": {
73
+ valueKind: "either",
74
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
75
+ },
76
+ "appearance.outline.offset": {
77
+ valueKind: "either",
78
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
79
+ },
80
+ "appearance.outline.style": {
81
+ valueKind: "primitive",
82
+ tokenNamespace: [],
83
+ },
84
+ "appearance.outline.width": {
85
+ valueKind: "either",
86
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
87
+ },
88
+ "appearance.radius": {
89
+ valueKind: "either",
90
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
91
+ },
92
+ "appearance.visibility": {
93
+ valueKind: "primitive",
94
+ tokenNamespace: [],
95
+ },
96
+ "interaction.cursor": {
97
+ valueKind: "primitive",
98
+ tokenNamespace: [],
99
+ },
100
+ "interaction.pointerEvents": {
101
+ valueKind: "primitive",
102
+ tokenNamespace: [],
103
+ },
104
+ "interaction.resize": {
105
+ valueKind: "primitive",
106
+ tokenNamespace: [],
107
+ },
108
+ "interaction.select": {
109
+ valueKind: "primitive",
110
+ tokenNamespace: [],
111
+ },
112
+ "layout.align": {
113
+ valueKind: "primitive",
114
+ tokenNamespace: [],
115
+ },
116
+ "layout.alignContent": {
117
+ valueKind: "primitive",
118
+ tokenNamespace: [],
119
+ },
120
+ "layout.alignSelf": {
121
+ valueKind: "primitive",
122
+ tokenNamespace: [],
123
+ },
124
+ "layout.boxSizing": {
125
+ valueKind: "primitive",
126
+ tokenNamespace: [],
127
+ },
128
+ "layout.container": {
129
+ valueKind: "primitive",
130
+ tokenNamespace: [],
131
+ },
132
+ "layout.direction": {
133
+ valueKind: "primitive",
134
+ tokenNamespace: [],
135
+ },
136
+ "layout.flex.basis": {
137
+ valueKind: "either",
138
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
139
+ },
140
+ "layout.flex.grow": {
141
+ valueKind: "primitive",
142
+ tokenNamespace: [],
143
+ },
144
+ "layout.flex.shrink": {
145
+ valueKind: "primitive",
146
+ tokenNamespace: [],
147
+ },
148
+ "layout.grid.area": {
149
+ valueKind: "primitive",
150
+ tokenNamespace: [],
151
+ },
152
+ "layout.grid.autoFlow": {
153
+ valueKind: "primitive",
154
+ tokenNamespace: [],
155
+ },
156
+ "layout.grid.autoRows": {
157
+ valueKind: "primitive",
158
+ tokenNamespace: [],
159
+ },
160
+ "layout.grid.column": {
161
+ valueKind: "primitive",
162
+ tokenNamespace: [],
163
+ },
164
+ "layout.grid.columns": {
165
+ valueKind: "primitive",
166
+ tokenNamespace: [],
167
+ },
168
+ "layout.grid.row": {
169
+ valueKind: "primitive",
170
+ tokenNamespace: [],
171
+ },
172
+ "layout.grid.rows": {
173
+ valueKind: "primitive",
174
+ tokenNamespace: [],
175
+ },
176
+ "layout.justify": {
177
+ valueKind: "primitive",
178
+ tokenNamespace: [],
179
+ },
180
+ "layout.listStyle": {
181
+ valueKind: "primitive",
182
+ tokenNamespace: [],
183
+ },
184
+ "layout.objectFit": {
185
+ valueKind: "primitive",
186
+ tokenNamespace: [],
187
+ },
188
+ "layout.offset.block.end": {
189
+ valueKind: "token",
190
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
191
+ },
192
+ "layout.offset.block.start": {
193
+ valueKind: "token",
194
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
195
+ },
196
+ "layout.offset.bottom": {
197
+ valueKind: "either",
198
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
199
+ },
200
+ "layout.offset.inline.start": {
201
+ valueKind: "token",
202
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
203
+ },
204
+ "layout.offset.left": {
205
+ valueKind: "either",
206
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
207
+ },
208
+ "layout.offset.right": {
209
+ valueKind: "either",
210
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
211
+ },
212
+ "layout.offset.top": {
213
+ valueKind: "either",
214
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
215
+ },
216
+ "layout.order": {
217
+ valueKind: "primitive",
218
+ tokenNamespace: [],
219
+ },
220
+ "layout.overflow": {
221
+ valueKind: "primitive",
222
+ tokenNamespace: [],
223
+ },
224
+ "layout.overflow.block": {
225
+ valueKind: "primitive",
226
+ tokenNamespace: [],
227
+ },
228
+ "layout.overflow.inline": {
229
+ valueKind: "primitive",
230
+ tokenNamespace: [],
231
+ },
232
+ "layout.overscroll": {
233
+ valueKind: "primitive",
234
+ tokenNamespace: [],
235
+ },
236
+ "layout.position": {
237
+ valueKind: "primitive",
238
+ tokenNamespace: [],
239
+ },
240
+ "layout.stack": {
241
+ valueKind: "primitive",
242
+ tokenNamespace: [],
243
+ },
244
+ "layout.table.collapse": {
245
+ valueKind: "primitive",
246
+ tokenNamespace: [],
247
+ },
248
+ "layout.table.layout": {
249
+ valueKind: "primitive",
250
+ tokenNamespace: [],
251
+ },
252
+ "layout.table.spacing": {
253
+ valueKind: "primitive",
254
+ tokenNamespace: [],
255
+ },
256
+ "layout.type": {
257
+ valueKind: "primitive",
258
+ tokenNamespace: [],
259
+ },
260
+ "layout.verticalAlign": {
261
+ valueKind: "primitive",
262
+ tokenNamespace: [],
263
+ },
264
+ "layout.wrap": {
265
+ valueKind: "primitive",
266
+ tokenNamespace: [],
267
+ },
268
+ "motion.duration": {
269
+ valueKind: "either",
270
+ tokenNamespace: ["motion.", "modifier.motion.", "surface.motion."],
271
+ },
272
+ "motion.easing": {
273
+ valueKind: "either",
274
+ tokenNamespace: ["motion.", "modifier.motion.", "surface.motion."],
275
+ },
276
+ "motion.property": {
277
+ valueKind: "primitive",
278
+ tokenNamespace: [],
279
+ },
280
+ "size.block": {
281
+ valueKind: "either",
282
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
283
+ },
284
+ "size.height": {
285
+ valueKind: "either",
286
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
287
+ },
288
+ "size.inline": {
289
+ valueKind: "token",
290
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
291
+ },
292
+ "size.max.block": {
293
+ valueKind: "token",
294
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
295
+ },
296
+ "size.max.height": {
297
+ valueKind: "either",
298
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
299
+ },
300
+ "size.max.width": {
301
+ valueKind: "either",
302
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
303
+ },
304
+ "size.min.block": {
305
+ valueKind: "token",
306
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
307
+ },
308
+ "size.min.height": {
309
+ valueKind: "token",
310
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
311
+ },
312
+ "size.min.width": {
313
+ valueKind: "either",
314
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
315
+ },
316
+ "size.width": {
317
+ valueKind: "either",
318
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
319
+ },
320
+ "spacing.external.block.end": {
321
+ valueKind: "either",
322
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
323
+ },
324
+ "spacing.external.block.start": {
325
+ valueKind: "either",
326
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
327
+ },
328
+ "spacing.external.bottom": {
329
+ valueKind: "either",
330
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
331
+ },
332
+ "spacing.external.inline.end": {
333
+ valueKind: "either",
334
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
335
+ },
336
+ "spacing.external.inline.start": {
337
+ valueKind: "either",
338
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
339
+ },
340
+ "spacing.external.left": {
341
+ valueKind: "either",
342
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
343
+ },
344
+ "spacing.external.right": {
345
+ valueKind: "either",
346
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
347
+ },
348
+ "spacing.external.top": {
349
+ valueKind: "either",
350
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
351
+ },
352
+ "spacing.gap": {
353
+ valueKind: "either",
354
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
355
+ },
356
+ "spacing.gap.block": {
357
+ valueKind: "token",
358
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
359
+ },
360
+ "spacing.gap.inline": {
361
+ valueKind: "token",
362
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
363
+ },
364
+ "spacing.internal.block.end": {
365
+ valueKind: "either",
366
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
367
+ },
368
+ "spacing.internal.block.start": {
369
+ valueKind: "either",
370
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
371
+ },
372
+ "spacing.internal.bottom": {
373
+ valueKind: "either",
374
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
375
+ },
376
+ "spacing.internal.inline.end": {
377
+ valueKind: "either",
378
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
379
+ },
380
+ "spacing.internal.inline.start": {
381
+ valueKind: "either",
382
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
383
+ },
384
+ "spacing.internal.left": {
385
+ valueKind: "either",
386
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
387
+ },
388
+ "spacing.internal.top": {
389
+ valueKind: "either",
390
+ tokenNamespace: ["spacing.", "modifier.spacing.", "surface.spacing."],
391
+ },
392
+ "typography.align": {
393
+ valueKind: "primitive",
394
+ tokenNamespace: [],
395
+ },
396
+ "typography.color": {
397
+ valueKind: "either",
398
+ tokenNamespace: ["color.", "modifier.color.", "surface.color."],
399
+ },
400
+ "typography.decoration": {
401
+ valueKind: "primitive",
402
+ tokenNamespace: [],
403
+ },
404
+ "typography.decoration.offset": {
405
+ valueKind: "token",
406
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
407
+ },
408
+ "typography.decoration.thickness": {
409
+ valueKind: "either",
410
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
411
+ },
412
+ "typography.direction": {
413
+ valueKind: "primitive",
414
+ tokenNamespace: [],
415
+ },
416
+ "typography.font": {
417
+ valueKind: "either",
418
+ tokenNamespace: ["typography.", "modifier.typography.", "surface.typography."],
419
+ },
420
+ "typography.fontFamily": {
421
+ valueKind: "either",
422
+ tokenNamespace: ["typography.", "modifier.typography.", "surface.typography."],
423
+ },
424
+ "typography.letterSpacing": {
425
+ valueKind: "either",
426
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
427
+ },
428
+ "typography.lineHeight": {
429
+ valueKind: "either",
430
+ tokenNamespace: ["typography.", "modifier.typography.", "surface.typography."],
431
+ },
432
+ "typography.overflow": {
433
+ valueKind: "primitive",
434
+ tokenNamespace: [],
435
+ },
436
+ "typography.size": {
437
+ valueKind: "either",
438
+ tokenNamespace: ["dimension.", "modifier.dimension.", "surface.dimension."],
439
+ },
440
+ "typography.variant.caps": {
441
+ valueKind: "primitive",
442
+ tokenNamespace: [],
443
+ },
444
+ "typography.variant.numeric": {
445
+ valueKind: "primitive",
446
+ tokenNamespace: [],
447
+ },
448
+ "typography.weight": {
449
+ valueKind: "either",
450
+ tokenNamespace: ["typography.", "modifier.typography.", "surface.typography."],
451
+ },
452
+ "typography.whiteSpace": {
453
+ valueKind: "primitive",
454
+ tokenNamespace: [],
455
+ },
456
+ "typography.wrap": {
457
+ valueKind: "primitive",
458
+ tokenNamespace: [],
459
+ },
460
+ };
461
+ /** Whether the key admits a token symbol at all. */
462
+ export function takesToken(key) {
463
+ const entry = STYLE_KEYS[key];
464
+ return entry !== undefined && entry.valueKind !== "primitive";
465
+ }
@@ -0,0 +1,181 @@
1
+ import { takesToken } from "./registry.generated.js";
2
+ const PREFIX = "@prefix : <https://anatomy.canonical.com/> .";
3
+ /** The token graph's namespace, bound where a symbol is consumed. */
4
+ const DT_PREFIX = "@prefix dt: <https://dt.canonical.com/> .";
5
+ const INDENT = " ";
6
+ export function anatomyToTTL(spec) {
7
+ const lines = [];
8
+ lines.push("[] a :Specification ;");
9
+ lines.push(`${INDENT}:rootNode [`);
10
+ writeNode(lines, spec.root, 2);
11
+ lines.push(`${INDENT}] .`);
12
+ const body = lines.join("\n");
13
+ // `dt:` is bound only where the document consumes a symbol: an anatomy of
14
+ // pure structure — a switch and its cases, and nothing else — binds a
15
+ // prefix it never uses otherwise, which every RDF linter reports.
16
+ const header = body.includes(":consumes") ? [PREFIX, DT_PREFIX] : [PREFIX];
17
+ return `${header.join("\n")}\n\n${body}\n`;
18
+ }
19
+ function writeNode(lines, node, depth) {
20
+ if (node.type === "named") {
21
+ writeNamedNode(lines, node, depth);
22
+ }
23
+ else {
24
+ writeAnonymousNode(lines, node, depth);
25
+ }
26
+ const props = node.type === "named" ? (node.props ?? []) : [];
27
+ const styles = node.styles ?? [];
28
+ const edges = node.edges ?? [];
29
+ if (node.projection) {
30
+ writeProjection(lines, node.projection, depth, props.length === 0 && styles.length === 0 && edges.length === 0);
31
+ }
32
+ if (props.length > 0) {
33
+ writeProps(lines, props, depth, styles.length === 0 && edges.length === 0);
34
+ }
35
+ if (styles.length > 0) {
36
+ writeStyles(lines, styles, depth, edges.length === 0);
37
+ }
38
+ if (edges.length > 0) {
39
+ writeEdges(lines, edges, depth);
40
+ }
41
+ }
42
+ function writeNamedNode(lines, node, depth) {
43
+ const indent = INDENT.repeat(depth);
44
+ lines.push(`${indent}a :NamedNode ;`);
45
+ const hasMore = node.projection !== undefined ||
46
+ (node.props && node.props.length > 0) ||
47
+ (node.styles && node.styles.length > 0) ||
48
+ (node.edges && node.edges.length > 0);
49
+ lines.push(`${indent}:uri "${node.uri}"${hasMore ? " ;" : ""}`);
50
+ }
51
+ function writeProps(lines, props, depth, isLast) {
52
+ const indent = INDENT.repeat(depth);
53
+ const innerIndent = INDENT.repeat(depth + 1);
54
+ lines.push(`${indent}:hasProp`);
55
+ for (const [i, prop] of props.entries()) {
56
+ const sep = i < props.length - 1 ? " ," : isLast ? "" : " ;";
57
+ lines.push(`${innerIndent}[ a :Prop ; :propName "${prop.name}" ; :propValue "${prop.value}" ]${sep}`);
58
+ }
59
+ }
60
+ function writeAnonymousNode(lines, node, depth) {
61
+ const indent = INDENT.repeat(depth);
62
+ lines.push(`${indent}a :AnonymousNode ;`);
63
+ const hasMore = node.projection !== undefined ||
64
+ (node.styles && node.styles.length > 0) ||
65
+ (node.edges && node.edges.length > 0);
66
+ lines.push(`${indent}:role "${node.role}"${hasMore ? " ;" : ""}`);
67
+ }
68
+ function writeProjection(lines, projection, depth, isLast) {
69
+ const indent = INDENT.repeat(depth);
70
+ lines.push(`${indent}:hasProjection ${projectionTerm(projection)}${isLast ? "" : " ;"}`);
71
+ }
72
+ function projectionTerm(projection) {
73
+ const parts = ["a :Projection"];
74
+ if (projection.on !== undefined) {
75
+ parts.push(`:projectionType "${projection.on}"`);
76
+ }
77
+ if (projection.field !== undefined) {
78
+ parts.push(`:projectionField "${projection.field}"`);
79
+ }
80
+ return `[ ${parts.join(" ; ")} ]`;
81
+ }
82
+ function writeStyles(lines, styles, depth, isLast) {
83
+ const indent = INDENT.repeat(depth);
84
+ const innerIndent = INDENT.repeat(depth + 1);
85
+ lines.push(`${indent}:hasStyle`);
86
+ for (const [i, style] of styles.entries()) {
87
+ const sep = i < styles.length - 1 ? " ," : isLast ? "" : " ;";
88
+ const statePart = style.state !== undefined ? ` :styleState "${style.state}" ;` : "";
89
+ lines.push(`${innerIndent}[ a :Style ; :styleKey "${style.key}" ;${statePart} :styleValue "${style.value}"${consumesPart(style)} ]${sep}`);
90
+ }
91
+ }
92
+ /**
93
+ * THE SEAM. On a style tuple whose key the registry says takes a token,
94
+ * `:consumes ( dt:a dt:b … )` — an rdf:List of the consumed symbols in
95
+ * fallback order, whose head is the primary symbol. `:styleValue` keeps the
96
+ * authored spelling verbatim beside it, terminal literal included, so the
97
+ * evidence and the queryable form travel together.
98
+ *
99
+ * Nothing is emitted for a key the registry calls `primitive`, and nothing
100
+ * for an `either` key whose value is one primitive — there is no symbol to
101
+ * consume. A token-kind key with no symbols emits none either, and
102
+ * StyleShape's generated disjunction is what turns that into a violation
103
+ * rather than a silence.
104
+ */
105
+ function consumesPart(style) {
106
+ if (style.symbols.length === 0 || !takesToken(style.key))
107
+ return "";
108
+ const members = style.symbols.map((symbol) => `dt:${symbol}`).join(" ");
109
+ return ` ; :consumes ( ${members} )`;
110
+ }
111
+ function writeEdges(lines, edges, depth) {
112
+ const indent = INDENT.repeat(depth);
113
+ for (const [i, edge] of edges.entries()) {
114
+ if (i === 0) {
115
+ lines.push(`${indent}:hasEdge [`);
116
+ }
117
+ else {
118
+ lines.push(`${indent}] , [`);
119
+ }
120
+ writeEdge(lines, edge, depth + 1);
121
+ }
122
+ lines.push(`${indent}]`);
123
+ }
124
+ function writeEdge(lines, edge, depth) {
125
+ const indent = INDENT.repeat(depth);
126
+ lines.push(`${indent}a :Edge ;`);
127
+ if (isSwitch(edge.target)) {
128
+ lines.push(`${indent}:edgeSwitch [`);
129
+ writeSwitch(lines, edge.target, depth + 1);
130
+ lines.push(`${indent}] ;`);
131
+ }
132
+ else {
133
+ lines.push(`${indent}:edgeTarget [`);
134
+ writeNode(lines, edge.target, depth + 1);
135
+ lines.push(`${indent}] ;`);
136
+ }
137
+ writeRelation(lines, edge, depth);
138
+ }
139
+ function writeSwitch(lines, sw, depth) {
140
+ const indent = INDENT.repeat(depth);
141
+ lines.push(`${indent}a :Switch ;`);
142
+ lines.push(`${indent}:discriminator "${sw.discriminator}" ;`);
143
+ for (const [i, sc] of sw.cases.entries()) {
144
+ if (i === 0) {
145
+ lines.push(`${indent}:hasCase [`);
146
+ }
147
+ else {
148
+ lines.push(`${indent}] , [`);
149
+ }
150
+ writeSwitchCase(lines, sc, depth + 1);
151
+ }
152
+ lines.push(`${indent}]`);
153
+ }
154
+ function writeSwitchCase(lines, sc, depth) {
155
+ const indent = INDENT.repeat(depth);
156
+ lines.push(`${indent}a :SwitchCase ;`);
157
+ lines.push(`${indent}:caseNode [`);
158
+ writeNode(lines, sc.node, depth + 1);
159
+ lines.push(`${indent}]`);
160
+ }
161
+ function writeRelation(lines, edge, depth) {
162
+ const indent = INDENT.repeat(depth);
163
+ lines.push(`${indent}:hasRelation [`);
164
+ const inner = INDENT.repeat(depth + 1);
165
+ const { relation } = edge;
166
+ const predicates = ["a :Relation", `:cardinality "${relation.cardinality}"`];
167
+ if (relation.slotName) {
168
+ predicates.push(`:slotName "${relation.slotName}"`);
169
+ }
170
+ if (relation.projection) {
171
+ predicates.push(`:hasProjection ${projectionTerm(relation.projection)}`);
172
+ }
173
+ for (const [i, predicate] of predicates.entries()) {
174
+ const sep = i < predicates.length - 1 ? " ;" : "";
175
+ lines.push(`${inner}${predicate}${sep}`);
176
+ }
177
+ lines.push(`${indent}]`);
178
+ }
179
+ function isSwitch(target) {
180
+ return "discriminator" in target;
181
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The style value form and its grammar (ADR J §4.1, AT.01–AT.03).
3
+ *
4
+ * A value is a **symbol**, a **primitive**, or a **sequence** whose elements
5
+ * are symbols with at most one primitive, and only as the last element. The
6
+ * sequence is the fallback order: a one-to-one transcription of the
7
+ * implementation's `var(a, var(b, …))` chain, terminal literal included,
8
+ * because an implementation generated or checked from the anatomy has to be
9
+ * able to write the whole chain from what the anatomy says.
10
+ *
11
+ * The grammar runs over the PARSED document — a scalar or a sequence of
12
+ * scalars — and never over raw text. A value is classified by its SHAPE:
13
+ * whether the author quoted a scalar decides nothing, because every outcome
14
+ * quoting was for is already the shape's. A scalar that satisfies the symbol
15
+ * grammar is a symbol however it was written (`"color.text"` is
16
+ * `color.text`); one that satisfies no primitive form either is the ADR's
17
+ * `Quoted` — any text, which is what a literal tail holding a space or a
18
+ * slash needs (`1 / -1`, `*`).
19
+ *
20
+ * What is rejected is the notation this release retires: a slash path, the
21
+ * trailing `?` marker, `$root`, and a primitive anywhere but last. Each is a
22
+ * typed error naming the value and the rule.
23
+ */
24
+ /** A symbol: the dotted spelling of a `dt:TokenSymbol`, verbatim. */
25
+ export const SYMBOL = /^[a-z]+(\.[A-Za-z0-9]+)+$/;
26
+ /**
27
+ * A CSS keyword. The ADR's production is `[a-zA-Z]+`, which admits `flow`,
28
+ * `currentColor`, `inherit`, `auto` and `normal` — and none of
29
+ * `not-allowed`, `space-between`, `inline-flex`, `flex-start` or
30
+ * `border-box`, which the reference implementations use throughout. So the
31
+ * production is widened to the shape of a CSS keyword: hyphen-joined words.
32
+ * The widening admits no symbol, since a symbol carries a dot and never a
33
+ * hyphen.
34
+ */
35
+ export const KEYWORD = /^[a-zA-Z]+(-[a-zA-Z]+)*$/;
36
+ export const NUMBER = /^-?[0-9]+(\.[0-9]+)?$/;
37
+ export const DIMENSION = /^-?[0-9]+(\.[0-9]+)?([a-z]+|%)$/;
38
+ export const COLOR = /^#[0-9a-fA-F]{3,8}$/;
39
+ /**
40
+ * The retired path notation: slash-delimited segments, with or without the
41
+ * optional marker. Anchored on the whole scalar, so a literal that merely
42
+ * holds a slash between spaces (`1 / -1`, a grid line) is untouched — the
43
+ * slash ban is on symbol spellings, never on literals.
44
+ */
45
+ export const RETIRED_PATH = /^[A-Za-z0-9-]+(\/[A-Za-z0-9-]+)+\??$/;
46
+ /** The retired optional marker: a trailing `?` meaning "may be undefined". */
47
+ export const RETIRED_MARKER = /\?$/;
48
+ /** The retired root segment, in either spelling. */
49
+ export const RETIRED_ROOT = /(^|[./])\$?root([./]|$)/;
50
+ /** A value the grammar refuses, naming the value and the rule it broke. */
51
+ export class AnatomyValueError extends Error {
52
+ value;
53
+ rule;
54
+ key;
55
+ constructor(value, rule, key) {
56
+ super(`${key === undefined ? "style value" : `style value of ${key}`} ${JSON.stringify(value)} is rejected: ${rule}`);
57
+ this.value = value;
58
+ this.rule = rule;
59
+ this.key = key;
60
+ this.name = "AnatomyValueError";
61
+ }
62
+ }
63
+ /** The rules a value can break, as the errors name them. */
64
+ export const RULES = {
65
+ slashPath: "the slash-delimited token path is retired — write the symbol's own dotted name",
66
+ marker: "the trailing `?` marker is retired — an element that resolves nowhere is a register row and a comment, not a sigil",
67
+ root: "`root` and `$root` are not value segments — the symbol is the dotted name without them",
68
+ primitiveNotLast: "a primitive may only end a value: the sequence is the fallback order and a literal is what the chain ends in",
69
+ singleton: "a sequence is a fallback order and needs two or more elements — write the scalar on its own",
70
+ empty: "a value may not be empty",
71
+ nested: "a value is a scalar or a sequence of scalars, never nested",
72
+ };
73
+ function scalarText(raw, key) {
74
+ if (raw === null || raw === undefined) {
75
+ throw new AnatomyValueError(String(raw), RULES.empty, key);
76
+ }
77
+ if (typeof raw === "object") {
78
+ throw new AnatomyValueError(JSON.stringify(raw), RULES.nested, key);
79
+ }
80
+ return String(raw);
81
+ }
82
+ /** Classify one scalar, rejecting the retired notation. */
83
+ export function classifyElement(raw, key) {
84
+ const text = scalarText(raw, key);
85
+ if (text === "")
86
+ throw new AnatomyValueError(text, RULES.empty, key);
87
+ if (RETIRED_PATH.test(text)) {
88
+ throw new AnatomyValueError(text, RULES.slashPath, key);
89
+ }
90
+ if (RETIRED_MARKER.test(text)) {
91
+ throw new AnatomyValueError(text, RULES.marker, key);
92
+ }
93
+ if (RETIRED_ROOT.test(text)) {
94
+ throw new AnatomyValueError(text, RULES.root, key);
95
+ }
96
+ if (SYMBOL.test(text))
97
+ return { kind: "symbol", text };
98
+ if (KEYWORD.test(text))
99
+ return { kind: "primitive", text, form: "keyword" };
100
+ if (NUMBER.test(text))
101
+ return { kind: "primitive", text, form: "number" };
102
+ if (DIMENSION.test(text)) {
103
+ return { kind: "primitive", text, form: "dimension" };
104
+ }
105
+ if (COLOR.test(text))
106
+ return { kind: "primitive", text, form: "color" };
107
+ // The ADR's `Quoted`: any text. A tail that holds a space or a slash, or
108
+ // that a bare YAML would read as something else, is written quoted and
109
+ // arrives here as itself.
110
+ return { kind: "primitive", text, form: "quoted" };
111
+ }
112
+ /**
113
+ * Parse a style value out of what the YAML parser holds for it: a scalar, or
114
+ * a sequence of scalars.
115
+ */
116
+ export function parseStyleValue(raw, key) {
117
+ if (Array.isArray(raw)) {
118
+ if (raw.length === 0) {
119
+ throw new AnatomyValueError("[]", RULES.empty, key);
120
+ }
121
+ if (raw.length === 1) {
122
+ throw new AnatomyValueError(authoredSpelling(raw), RULES.singleton, key);
123
+ }
124
+ const elements = raw.map((element) => classifyElement(element, key));
125
+ for (const [i, element] of elements.entries()) {
126
+ if (element.kind === "primitive" && i !== elements.length - 1) {
127
+ throw new AnatomyValueError(element.text, RULES.primitiveNotLast, key);
128
+ }
129
+ }
130
+ return { elements, list: true };
131
+ }
132
+ return { elements: [classifyElement(raw, key)], list: false };
133
+ }
134
+ /**
135
+ * The authored spelling, kept verbatim on `anatomy:styleValue` as the
136
+ * evidence of what the reference says: a scalar as it stands, a sequence in
137
+ * its flow form.
138
+ */
139
+ export function authoredSpelling(raw) {
140
+ if (Array.isArray(raw)) {
141
+ return `[${raw.map((element) => String(element)).join(", ")}]`;
142
+ }
143
+ return String(raw);
144
+ }
145
+ /**
146
+ * The symbols a value consumes, in fallback order — the one lift every
147
+ * consumer shares (§4.1). A primitive is not a symbol: it resolves against
148
+ * nothing by design, so it is neither lifted here nor asked to resolve.
149
+ */
150
+ export function liftSymbols(raw, key) {
151
+ return parseStyleValue(raw, key)
152
+ .elements.filter((element) => element.kind === "symbol")
153
+ .map((element) => element.text);
154
+ }
@@ -0,0 +1,5 @@
1
+ export { parseAnatomyYAML } from "./parse.js";
2
+ export { PLACEHOLDER_SEGMENTS, STYLE_KEYS, type StyleKeyEntry, takesToken, type ValueKind, } from "./registry.generated.js";
3
+ export { anatomyToTTL } from "./transform.js";
4
+ export type { AnonymousNode, Edge, NamedNode, Node, Projection, Prop, Relation, RelationProjection, Specification, Style, Switch, SwitchCase, } from "./types.js";
5
+ export { AnatomyValueError, authoredSpelling, classifyElement, liftSymbols, type PrimitiveForm, parseStyleValue, RULES, type StyleValue, type ValueElement, } from "./value.js";
@@ -0,0 +1,17 @@
1
+ import type { Specification } from "./types.js";
2
+ /**
3
+ * The one lift every consumer shares. It is implemented in `./value.js`,
4
+ * beside the grammar it belongs to, and re-exported here because the parser
5
+ * is where a reader looks for it.
6
+ */
7
+ export { liftSymbols } from "./value.js";
8
+ /**
9
+ * Parse an anatomy document.
10
+ *
11
+ * Give it the YAML text — which is what design-system holds, one
12
+ * `ds:anatomyDsl` literal per block — and it is parsed here, with the
13
+ * `yaml` package's Document API; that is why `yaml` is a runtime dependency
14
+ * and not a devDependency. An already-parsed value is still accepted, for a
15
+ * caller that has one.
16
+ */
17
+ export declare function parseAnatomyYAML(raw: string | unknown): Specification;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * GENERATED by `bun run generate:registry` from definitions/style-keys.yaml.
3
+ * Do not edit: change the roster and regenerate. The Turtle form of the same
4
+ * facts is definitions/registry.ttl, which also carries the roster's
5
+ * provenance, and CI diffs both.
6
+ */
7
+ export type ValueKind = "token" | "primitive" | "either";
8
+ export interface StyleKeyEntry {
9
+ /** What the key admits: a symbol, a primitive, or either. */
10
+ readonly valueKind: ValueKind;
11
+ /** Dotted prefixes whose symbols the key admits, channels included. */
12
+ readonly tokenNamespace: readonly string[];
13
+ }
14
+ /** Segments that mark a value as a placeholder rather than a symbol (§4.1). */
15
+ export declare const PLACEHOLDER_SEGMENTS: readonly string[];
16
+ /** One entry per canonical style key. The roster is closed. */
17
+ export declare const STYLE_KEYS: Record<string, StyleKeyEntry>;
18
+ /** Whether the key admits a token symbol at all. */
19
+ export declare function takesToken(key: string): boolean;
@@ -0,0 +1,2 @@
1
+ import type { Specification } from "./types.js";
2
+ export declare function anatomyToTTL(spec: Specification): string;
@@ -0,0 +1,86 @@
1
+ export interface Specification {
2
+ root: NamedNode;
3
+ }
4
+ export interface NamedNode {
5
+ type: "named";
6
+ uri: string;
7
+ projection?: Projection;
8
+ props?: Prop[];
9
+ styles?: Style[];
10
+ edges?: Edge[];
11
+ }
12
+ export interface AnonymousNode {
13
+ type: "anonymous";
14
+ role: string;
15
+ projection?: Projection;
16
+ styles?: Style[];
17
+ edges?: Edge[];
18
+ }
19
+ export type Node = NamedNode | AnonymousNode;
20
+ export interface Edge {
21
+ target: Node | Switch;
22
+ relation: Relation;
23
+ }
24
+ export interface Relation {
25
+ cardinality: string;
26
+ slotName?: string;
27
+ projection?: RelationProjection;
28
+ }
29
+ /**
30
+ * Fragment-style binding of a node to graph data: a type condition (`on`),
31
+ * a field path (`field`), or both — never neither.
32
+ */
33
+ export type Projection = {
34
+ on: string;
35
+ field?: string;
36
+ } | {
37
+ on?: string;
38
+ field: string;
39
+ };
40
+ /**
41
+ * Traversal populating a slot: the field is required. Target typing lives on
42
+ * the child node's own `on`, never on the relation.
43
+ */
44
+ export interface RelationProjection {
45
+ field: string;
46
+ }
47
+ export interface Style {
48
+ key: string;
49
+ /**
50
+ * The authored spelling, verbatim: a scalar as written, a sequence in its
51
+ * flow form (`[modifier.color.text, color.text]`). It is the evidence of
52
+ * what the reference says, and `anatomy:styleValue` carries it unchanged.
53
+ */
54
+ value: string;
55
+ /**
56
+ * The symbols the value consumes, in fallback order — `liftSymbols` of the
57
+ * authored value. Empty for a value that is one primitive. The transform
58
+ * emits these as `anatomy:consumes`, an rdf:List, on a tuple whose key the
59
+ * registry says takes a token.
60
+ */
61
+ symbols: string[];
62
+ /**
63
+ * Interaction state this value applies in (`hover`, `active`, `focus`,
64
+ * `disabled`, `selected`). Absent = the default state. Authored as an
65
+ * `@state` suffix on the style key (`appearance.background@hover`).
66
+ */
67
+ state?: string;
68
+ }
69
+ /**
70
+ * A pinned property value: the anatomy fixes one prop of the referenced
71
+ * component at this tree position. References a property defined in the
72
+ * design system ontology — the DSL never defines the prop surface itself.
73
+ * Named nodes only: anonymous nodes have no prop surface to pin.
74
+ */
75
+ export interface Prop {
76
+ name: string;
77
+ value: string;
78
+ }
79
+ export interface Switch {
80
+ discriminator: "props" | "internal" | "override";
81
+ cases: SwitchCase[];
82
+ }
83
+ export interface SwitchCase {
84
+ value: string;
85
+ node: Node;
86
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The style value form and its grammar (ADR J §4.1, AT.01–AT.03).
3
+ *
4
+ * A value is a **symbol**, a **primitive**, or a **sequence** whose elements
5
+ * are symbols with at most one primitive, and only as the last element. The
6
+ * sequence is the fallback order: a one-to-one transcription of the
7
+ * implementation's `var(a, var(b, …))` chain, terminal literal included,
8
+ * because an implementation generated or checked from the anatomy has to be
9
+ * able to write the whole chain from what the anatomy says.
10
+ *
11
+ * The grammar runs over the PARSED document — a scalar or a sequence of
12
+ * scalars — and never over raw text. A value is classified by its SHAPE:
13
+ * whether the author quoted a scalar decides nothing, because every outcome
14
+ * quoting was for is already the shape's. A scalar that satisfies the symbol
15
+ * grammar is a symbol however it was written (`"color.text"` is
16
+ * `color.text`); one that satisfies no primitive form either is the ADR's
17
+ * `Quoted` — any text, which is what a literal tail holding a space or a
18
+ * slash needs (`1 / -1`, `*`).
19
+ *
20
+ * What is rejected is the notation this release retires: a slash path, the
21
+ * trailing `?` marker, `$root`, and a primitive anywhere but last. Each is a
22
+ * typed error naming the value and the rule.
23
+ */
24
+ /** A symbol: the dotted spelling of a `dt:TokenSymbol`, verbatim. */
25
+ export declare const SYMBOL: RegExp;
26
+ /**
27
+ * A CSS keyword. The ADR's production is `[a-zA-Z]+`, which admits `flow`,
28
+ * `currentColor`, `inherit`, `auto` and `normal` — and none of
29
+ * `not-allowed`, `space-between`, `inline-flex`, `flex-start` or
30
+ * `border-box`, which the reference implementations use throughout. So the
31
+ * production is widened to the shape of a CSS keyword: hyphen-joined words.
32
+ * The widening admits no symbol, since a symbol carries a dot and never a
33
+ * hyphen.
34
+ */
35
+ export declare const KEYWORD: RegExp;
36
+ export declare const NUMBER: RegExp;
37
+ export declare const DIMENSION: RegExp;
38
+ export declare const COLOR: RegExp;
39
+ /**
40
+ * The retired path notation: slash-delimited segments, with or without the
41
+ * optional marker. Anchored on the whole scalar, so a literal that merely
42
+ * holds a slash between spaces (`1 / -1`, a grid line) is untouched — the
43
+ * slash ban is on symbol spellings, never on literals.
44
+ */
45
+ export declare const RETIRED_PATH: RegExp;
46
+ /** The retired optional marker: a trailing `?` meaning "may be undefined". */
47
+ export declare const RETIRED_MARKER: RegExp;
48
+ /** The retired root segment, in either spelling. */
49
+ export declare const RETIRED_ROOT: RegExp;
50
+ export type PrimitiveForm = "keyword" | "number" | "dimension" | "color" | "quoted";
51
+ export type ValueElement = {
52
+ kind: "symbol";
53
+ text: string;
54
+ } | {
55
+ kind: "primitive";
56
+ text: string;
57
+ form: PrimitiveForm;
58
+ };
59
+ /** A parsed style value: its elements in fallback order, and how it reads. */
60
+ export interface StyleValue {
61
+ elements: ValueElement[];
62
+ /** True when the value was authored as a sequence. */
63
+ list: boolean;
64
+ }
65
+ /** A value the grammar refuses, naming the value and the rule it broke. */
66
+ export declare class AnatomyValueError extends Error {
67
+ readonly value: string;
68
+ readonly rule: string;
69
+ readonly key?: string | undefined;
70
+ constructor(value: string, rule: string, key?: string | undefined);
71
+ }
72
+ /** The rules a value can break, as the errors name them. */
73
+ export declare const RULES: {
74
+ readonly slashPath: "the slash-delimited token path is retired — write the symbol's own dotted name";
75
+ readonly marker: "the trailing `?` marker is retired — an element that resolves nowhere is a register row and a comment, not a sigil";
76
+ readonly root: "`root` and `$root` are not value segments — the symbol is the dotted name without them";
77
+ readonly primitiveNotLast: "a primitive may only end a value: the sequence is the fallback order and a literal is what the chain ends in";
78
+ readonly singleton: "a sequence is a fallback order and needs two or more elements — write the scalar on its own";
79
+ readonly empty: "a value may not be empty";
80
+ readonly nested: "a value is a scalar or a sequence of scalars, never nested";
81
+ };
82
+ /** Classify one scalar, rejecting the retired notation. */
83
+ export declare function classifyElement(raw: unknown, key?: string): ValueElement;
84
+ /**
85
+ * Parse a style value out of what the YAML parser holds for it: a scalar, or
86
+ * a sequence of scalars.
87
+ */
88
+ export declare function parseStyleValue(raw: unknown, key?: string): StyleValue;
89
+ /**
90
+ * The authored spelling, kept verbatim on `anatomy:styleValue` as the
91
+ * evidence of what the reference says: a scalar as it stands, a sequence in
92
+ * its flow form.
93
+ */
94
+ export declare function authoredSpelling(raw: unknown): string;
95
+ /**
96
+ * The symbols a value consumes, in fallback order — the one lift every
97
+ * consumer shares (§4.1). A primitive is not a symbol: it resolves against
98
+ * nothing by design, so it is neither lifted here nor asked to resolve.
99
+ */
100
+ export declare function liftSymbols(raw: unknown, key?: string): string[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canonical/anatomy-dsl",
3
3
  "description": "Anatomy DSL meta-model: TypeScript types mirroring the OWL ontology, and a YAML-to-Turtle transform",
4
- "version": "0.5.0",
4
+ "version": "0.5.1",
5
5
  "type": "module",
6
6
  "module": "dist/esm/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -19,6 +19,7 @@
19
19
  "license": "LGPL-3.0",
20
20
  "scripts": {
21
21
  "build": "tsc -p tsconfig.build.json",
22
+ "prepack": "bun run build",
22
23
  "check": "biome check && tsc --noEmit",
23
24
  "check:fix": "biome check --write",
24
25
  "check:ts": "tsc --noEmit",