@canonical/anatomy-dsl 0.2.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.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # Anatomy DSL
2
+
3
+ A YAML-based DSL for representing design system component anatomies — platform-agnostic structural primitives that precede implementation.
4
+
5
+ ## Quick Example
6
+
7
+ ```yaml
8
+ ---
9
+ node:
10
+ uri: global.component.button
11
+ styles:
12
+ layout.type: flow
13
+ layout.direction: horizontal
14
+ layout.align: center
15
+ spacing.internal: spacing/medium
16
+ appearance.background: color/surface/button
17
+ appearance.radius: radius/button
18
+ edges:
19
+ - node:
20
+ uri: global.subcomponent.button-icon
21
+ relation:
22
+ cardinality: "0..1"
23
+ slotName: icon
24
+ - node:
25
+ role: label text
26
+ relation:
27
+ cardinality: "1"
28
+ slotName: default
29
+ ```
30
+
31
+ Style values are **design token paths** (`spacing/medium`, `color/surface/button`) — forward-slash delimited references resolved at runtime against the active theme. Primitives like `flow` and `center` are used for layout semantics that don't vary across themes.
32
+
33
+ ## Install
34
+
35
+ ```sh
36
+ bun add -D @canonical/anatomy-dsl
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```typescript
42
+ import { parse } from "yaml";
43
+ import { parseAnatomyYAML, anatomyToTTL } from "@canonical/anatomy-dsl";
44
+
45
+ const raw = parse(readFileSync("Button.anatomy.yaml", "utf8"));
46
+ const spec = parseAnatomyYAML(raw);
47
+ const ttl = anatomyToTTL(spec);
48
+ ```
49
+
50
+ The button example above produces:
51
+
52
+ ```turtle
53
+ @prefix : <http://anatomy-dsl.example.org/ontology#> .
54
+
55
+ [] a :Specification ;
56
+ :rootNode [
57
+ a :NamedNode ;
58
+ :uri "global.component.button" ;
59
+ :hasStyle
60
+ [ :styleKey "layout.type" ; :styleValue "flow" ] ,
61
+ [ :styleKey "layout.direction" ; :styleValue "horizontal" ] ,
62
+ [ :styleKey "layout.align" ; :styleValue "center" ] ,
63
+ [ :styleKey "spacing.internal" ; :styleValue "spacing/medium" ] ,
64
+ [ :styleKey "appearance.background" ; :styleValue "color/surface/button" ] ,
65
+ [ :styleKey "appearance.radius" ; :styleValue "radius/button" ] ;
66
+ :hasEdge [
67
+ a :Edge ;
68
+ :edgeTarget [
69
+ a :NamedNode ;
70
+ :uri "global.subcomponent.button-icon"
71
+ ] ;
72
+ :hasRelation [
73
+ a :Relation ;
74
+ :cardinality "0..1" ;
75
+ :slotName "icon"
76
+ ]
77
+ ] , [
78
+ a :Edge ;
79
+ :edgeTarget [
80
+ a :AnonymousNode ;
81
+ :role "label text"
82
+ ] ;
83
+ :hasRelation [
84
+ a :Relation ;
85
+ :cardinality "1" ;
86
+ :slotName "default"
87
+ ]
88
+ ]
89
+ ] .
90
+ ```
91
+
92
+ ## API
93
+
94
+ ### `parseAnatomyYAML(raw: unknown): Specification`
95
+
96
+ Converts a parsed YAML object (from any YAML library) into the typed `Specification` structure. Handles field mapping between the YAML format and the TypeScript types.
97
+
98
+ ### `anatomyToTTL(spec: Specification): string`
99
+
100
+ Pure function. Takes a `Specification`, returns a Turtle (RDF) string. No I/O, no side effects.
101
+
102
+ ### Types
103
+
104
+ All types mirror the [OWL ontology](definitions/ontology.ttl) exactly:
105
+
106
+ | Type | Description |
107
+ |------|-------------|
108
+ | `Specification` | Root — contains exactly one `NamedNode` |
109
+ | `NamedNode` | Design system entity with a `uri` |
110
+ | `AnonymousNode` | Structural element with a `role` |
111
+ | `Node` | `NamedNode \| AnonymousNode` (discriminated on `type`) |
112
+ | `Edge` | Reified parent→child relationship |
113
+ | `Relation` | Cardinality and optional slot name |
114
+ | `Style` | Reified key-value tuple |
115
+ | `Switch` | Polymorphic position (discriminator: `props \| internal \| override`) |
116
+ | `SwitchCase` | One alternative within a switch |
117
+
118
+ ## Repository Structure
119
+
120
+ ```
121
+ definitions/ Turtle ontology (OWL) + SHACL shapes
122
+ schemas/ JSON Schema for validating .anatomy.yaml files
123
+ docs/ API reference (merged WD404 + WD404.1)
124
+ examples/ Example anatomy files (YAML + Turtle pairs)
125
+ src/ TypeScript types, parser, and transform
126
+ ```
127
+
128
+ ## Scope
129
+
130
+ The Anatomy DSL describes **structure only**. It does not handle:
131
+
132
+ - **Prop mapping** — which props a component accepts and how they map to behaviour
133
+ - **State or state machines** — component states, transitions, or interaction logic
134
+ - **Modifier descriptions** — only design token references are supported, not semantic modifier definitions
135
+
136
+ ## Design Notes
137
+
138
+ Styles are modelled as reified key-value tuples (`hasStyle [ styleKey "…" ; styleValue "…" ]`). This keeps the ontology open-ended while remaining lossless. Frequently used style keys may be promoted to first-class datatype properties in a future version.
139
+
140
+ ## Specification Status
141
+
142
+ | Index | Title | Status |
143
+ |---------|--------------------------|----------------|
144
+ | [WD404](https://docs.google.com/document/d/1eFr-SNsAZyidnZzpWp1Jeegiat_SSM7mOW_G8p3nXo8/edit?tab=t.pndvuecem8cf) | Anatomy DSL | Approved |
145
+ | [WD404.1](https://docs.google.com/document/d/1eFr-SNsAZyidnZzpWp1Jeegiat_SSM7mOW_G8p3nXo8/edit?tab=t.pndvuecem8cf) | Anatomy DSL — Addendum 1 | Pending Review |
@@ -0,0 +1,2 @@
1
+ export { parseAnatomyYAML } from "./parse.js";
2
+ export { anatomyToTTL } from "./transform.js";
@@ -0,0 +1,72 @@
1
+ export function parseAnatomyYAML(raw) {
2
+ const doc = raw;
3
+ return {
4
+ root: toNamedNode(doc.node),
5
+ };
6
+ }
7
+ function toNamedNode(raw) {
8
+ return {
9
+ type: "named",
10
+ uri: raw.uri,
11
+ ...(raw.styles ? { styles: toStyles(raw.styles) } : {}),
12
+ ...(raw.edges ? { edges: raw.edges.map(toEdge) } : {}),
13
+ };
14
+ }
15
+ function toNode(raw) {
16
+ if ("uri" in raw && raw.uri) {
17
+ return toNamedNode(raw);
18
+ }
19
+ const anon = raw;
20
+ return {
21
+ type: "anonymous",
22
+ role: anon.role,
23
+ ...(anon.styles ? { styles: toStyles(anon.styles) } : {}),
24
+ ...(anon.edges ? { edges: anon.edges.map(toEdge) } : {}),
25
+ };
26
+ }
27
+ function toStyles(raw) {
28
+ return Object.entries(raw).map(([key, value]) => ({
29
+ key,
30
+ value: String(value),
31
+ }));
32
+ }
33
+ function toEdge(raw) {
34
+ let target;
35
+ if (raw.switch) {
36
+ target = toSwitch(raw.switch);
37
+ }
38
+ else if (raw.node) {
39
+ target = toNode(raw.node);
40
+ }
41
+ else if (raw.uri) {
42
+ target = { type: "named", uri: raw.uri };
43
+ }
44
+ else {
45
+ throw new Error("Edge must have node, uri, or switch");
46
+ }
47
+ return {
48
+ target,
49
+ relation: toRelation(raw.relation),
50
+ };
51
+ }
52
+ function toSwitch(raw) {
53
+ return {
54
+ discriminator: raw.on,
55
+ cases: raw.cases.map(toSwitchCase),
56
+ };
57
+ }
58
+ function toSwitchCase(raw) {
59
+ if (raw.uri !== undefined) {
60
+ const node = { type: "named", uri: raw.uri };
61
+ return { value: raw.uri, node };
62
+ }
63
+ const node = toNode(raw.node);
64
+ const value = node.type === "named" ? node.uri : node.role;
65
+ return { value, node };
66
+ }
67
+ function toRelation(raw) {
68
+ return {
69
+ cardinality: raw.cardinality,
70
+ ...(raw.slotName ? { slotName: raw.slotName } : {}),
71
+ };
72
+ }
@@ -0,0 +1,116 @@
1
+ const PREFIX = "@prefix : <http://anatomy-dsl.example.org/ontology#> .";
2
+ const INDENT = " ";
3
+ export function anatomyToTTL(spec) {
4
+ const lines = [PREFIX, ""];
5
+ lines.push("[] a :Specification ;");
6
+ lines.push(`${INDENT}:rootNode [`);
7
+ writeNode(lines, spec.root, 2);
8
+ lines.push(`${INDENT}] .`);
9
+ return `${lines.join("\n")}\n`;
10
+ }
11
+ function writeNode(lines, node, depth) {
12
+ if (node.type === "named") {
13
+ writeNamedNode(lines, node, depth);
14
+ }
15
+ else {
16
+ writeAnonymousNode(lines, node, depth);
17
+ }
18
+ const styles = node.styles ?? [];
19
+ const edges = node.edges ?? [];
20
+ if (styles.length > 0) {
21
+ writeStyles(lines, styles, depth, edges.length === 0);
22
+ }
23
+ if (edges.length > 0) {
24
+ writeEdges(lines, edges, depth);
25
+ }
26
+ }
27
+ function writeNamedNode(lines, node, depth) {
28
+ const indent = INDENT.repeat(depth);
29
+ lines.push(`${indent}a :NamedNode ;`);
30
+ const hasMore = (node.styles && node.styles.length > 0) ||
31
+ (node.edges && node.edges.length > 0);
32
+ lines.push(`${indent}:uri "${node.uri}"${hasMore ? " ;" : ""}`);
33
+ }
34
+ function writeAnonymousNode(lines, node, depth) {
35
+ const indent = INDENT.repeat(depth);
36
+ lines.push(`${indent}a :AnonymousNode ;`);
37
+ const hasMore = (node.styles && node.styles.length > 0) ||
38
+ (node.edges && node.edges.length > 0);
39
+ lines.push(`${indent}:role "${node.role}"${hasMore ? " ;" : ""}`);
40
+ }
41
+ function writeStyles(lines, styles, depth, isLast) {
42
+ const indent = INDENT.repeat(depth);
43
+ const innerIndent = INDENT.repeat(depth + 1);
44
+ lines.push(`${indent}:hasStyle`);
45
+ for (const [i, style] of styles.entries()) {
46
+ const sep = i < styles.length - 1 ? " ," : isLast ? "" : " ;";
47
+ lines.push(`${innerIndent}[ :styleKey "${style.key}" ; :styleValue "${style.value}" ]${sep}`);
48
+ }
49
+ }
50
+ function writeEdges(lines, edges, depth) {
51
+ const indent = INDENT.repeat(depth);
52
+ for (const [i, edge] of edges.entries()) {
53
+ if (i === 0) {
54
+ lines.push(`${indent}:hasEdge [`);
55
+ }
56
+ else {
57
+ lines.push(`${indent}] , [`);
58
+ }
59
+ writeEdge(lines, edge, depth + 1);
60
+ }
61
+ lines.push(`${indent}]`);
62
+ }
63
+ function writeEdge(lines, edge, depth) {
64
+ const indent = INDENT.repeat(depth);
65
+ lines.push(`${indent}a :Edge ;`);
66
+ if (isSwitch(edge.target)) {
67
+ lines.push(`${indent}:edgeSwitch [`);
68
+ writeSwitch(lines, edge.target, depth + 1);
69
+ lines.push(`${indent}] ;`);
70
+ }
71
+ else {
72
+ lines.push(`${indent}:edgeTarget [`);
73
+ writeNode(lines, edge.target, depth + 1);
74
+ lines.push(`${indent}] ;`);
75
+ }
76
+ writeRelation(lines, edge, depth);
77
+ }
78
+ function writeSwitch(lines, sw, depth) {
79
+ const indent = INDENT.repeat(depth);
80
+ lines.push(`${indent}a :Switch ;`);
81
+ lines.push(`${indent}:discriminator "${sw.discriminator}" ;`);
82
+ for (const [i, sc] of sw.cases.entries()) {
83
+ if (i === 0) {
84
+ lines.push(`${indent}:hasCase [`);
85
+ }
86
+ else {
87
+ lines.push(`${indent}] , [`);
88
+ }
89
+ writeSwitchCase(lines, sc, depth + 1);
90
+ }
91
+ lines.push(`${indent}]`);
92
+ }
93
+ function writeSwitchCase(lines, sc, depth) {
94
+ const indent = INDENT.repeat(depth);
95
+ lines.push(`${indent}a :SwitchCase ;`);
96
+ lines.push(`${indent}:caseNode [`);
97
+ writeNode(lines, sc.node, depth + 1);
98
+ lines.push(`${indent}]`);
99
+ }
100
+ function writeRelation(lines, edge, depth) {
101
+ const indent = INDENT.repeat(depth);
102
+ lines.push(`${indent}:hasRelation [`);
103
+ const inner = INDENT.repeat(depth + 1);
104
+ lines.push(`${inner}a :Relation ;`);
105
+ if (edge.relation.slotName) {
106
+ lines.push(`${inner}:cardinality "${edge.relation.cardinality}" ;`);
107
+ lines.push(`${inner}:slotName "${edge.relation.slotName}"`);
108
+ }
109
+ else {
110
+ lines.push(`${inner}:cardinality "${edge.relation.cardinality}"`);
111
+ }
112
+ lines.push(`${indent}]`);
113
+ }
114
+ function isSwitch(target) {
115
+ return "discriminator" in target;
116
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { parseAnatomyYAML } from "./parse.js";
2
+ export { anatomyToTTL } from "./transform.js";
3
+ export type { AnonymousNode, Edge, NamedNode, Node, Relation, Specification, Style, Switch, SwitchCase, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { parseAnatomyYAML } from "./parse.js";
2
+ export { anatomyToTTL } from "./transform.js";
@@ -0,0 +1,2 @@
1
+ import type { Specification } from "./types.js";
2
+ export declare function parseAnatomyYAML(raw: unknown): Specification;
package/dist/parse.js ADDED
@@ -0,0 +1,72 @@
1
+ export function parseAnatomyYAML(raw) {
2
+ const doc = raw;
3
+ return {
4
+ root: toNamedNode(doc.node),
5
+ };
6
+ }
7
+ function toNamedNode(raw) {
8
+ return {
9
+ type: "named",
10
+ uri: raw.uri,
11
+ ...(raw.styles ? { styles: toStyles(raw.styles) } : {}),
12
+ ...(raw.edges ? { edges: raw.edges.map(toEdge) } : {}),
13
+ };
14
+ }
15
+ function toNode(raw) {
16
+ if ("uri" in raw && raw.uri) {
17
+ return toNamedNode(raw);
18
+ }
19
+ const anon = raw;
20
+ return {
21
+ type: "anonymous",
22
+ role: anon.role,
23
+ ...(anon.styles ? { styles: toStyles(anon.styles) } : {}),
24
+ ...(anon.edges ? { edges: anon.edges.map(toEdge) } : {}),
25
+ };
26
+ }
27
+ function toStyles(raw) {
28
+ return Object.entries(raw).map(([key, value]) => ({
29
+ key,
30
+ value: String(value),
31
+ }));
32
+ }
33
+ function toEdge(raw) {
34
+ let target;
35
+ if (raw.switch) {
36
+ target = toSwitch(raw.switch);
37
+ }
38
+ else if (raw.node) {
39
+ target = toNode(raw.node);
40
+ }
41
+ else if (raw.uri) {
42
+ target = { type: "named", uri: raw.uri };
43
+ }
44
+ else {
45
+ throw new Error("Edge must have node, uri, or switch");
46
+ }
47
+ return {
48
+ target,
49
+ relation: toRelation(raw.relation),
50
+ };
51
+ }
52
+ function toSwitch(raw) {
53
+ return {
54
+ discriminator: raw.on,
55
+ cases: raw.cases.map(toSwitchCase),
56
+ };
57
+ }
58
+ function toSwitchCase(raw) {
59
+ if (raw.uri !== undefined) {
60
+ const node = { type: "named", uri: raw.uri };
61
+ return { value: raw.uri, node };
62
+ }
63
+ const node = toNode(raw.node);
64
+ const value = node.type === "named" ? node.uri : node.role;
65
+ return { value, node };
66
+ }
67
+ function toRelation(raw) {
68
+ return {
69
+ cardinality: raw.cardinality,
70
+ ...(raw.slotName ? { slotName: raw.slotName } : {}),
71
+ };
72
+ }
@@ -0,0 +1,2 @@
1
+ import type { Specification } from "./types.js";
2
+ export declare function anatomyToTTL(spec: Specification): string;
@@ -0,0 +1,114 @@
1
+ const PREFIX = "@prefix : <http://anatomy-dsl.example.org/ontology#> .";
2
+ const INDENT = " ";
3
+ export function anatomyToTTL(spec) {
4
+ const lines = [PREFIX, ""];
5
+ lines.push("[] a :Specification ;");
6
+ lines.push(`${INDENT}:rootNode [`);
7
+ writeNode(lines, spec.root, 2);
8
+ lines.push(`${INDENT}] .`);
9
+ return `${lines.join("\n")}\n`;
10
+ }
11
+ function writeNode(lines, node, depth) {
12
+ if (node.type === "named") {
13
+ writeNamedNode(lines, node, depth);
14
+ }
15
+ else {
16
+ writeAnonymousNode(lines, node, depth);
17
+ }
18
+ const styles = node.styles ?? [];
19
+ const edges = node.edges ?? [];
20
+ if (styles.length > 0) {
21
+ writeStyles(lines, styles, depth, edges.length === 0);
22
+ }
23
+ if (edges.length > 0) {
24
+ writeEdges(lines, edges, depth);
25
+ }
26
+ }
27
+ function writeNamedNode(lines, node, depth) {
28
+ const indent = INDENT.repeat(depth);
29
+ lines.push(`${indent}a :NamedNode ;`);
30
+ const hasMore = (node.styles && node.styles.length > 0) || (node.edges && node.edges.length > 0);
31
+ lines.push(`${indent}:uri "${node.uri}"${hasMore ? " ;" : ""}`);
32
+ }
33
+ function writeAnonymousNode(lines, node, depth) {
34
+ const indent = INDENT.repeat(depth);
35
+ lines.push(`${indent}a :AnonymousNode ;`);
36
+ const hasMore = (node.styles && node.styles.length > 0) || (node.edges && node.edges.length > 0);
37
+ lines.push(`${indent}:role "${node.role}"${hasMore ? " ;" : ""}`);
38
+ }
39
+ function writeStyles(lines, styles, depth, isLast) {
40
+ const indent = INDENT.repeat(depth);
41
+ const innerIndent = INDENT.repeat(depth + 1);
42
+ lines.push(`${indent}:hasStyle`);
43
+ for (const [i, style] of styles.entries()) {
44
+ const sep = i < styles.length - 1 ? " ," : isLast ? "" : " ;";
45
+ lines.push(`${innerIndent}[ :styleKey "${style.key}" ; :styleValue "${style.value}" ]${sep}`);
46
+ }
47
+ }
48
+ function writeEdges(lines, edges, depth) {
49
+ const indent = INDENT.repeat(depth);
50
+ for (const [i, edge] of edges.entries()) {
51
+ if (i === 0) {
52
+ lines.push(`${indent}:hasEdge [`);
53
+ }
54
+ else {
55
+ lines.push(`${indent}] , [`);
56
+ }
57
+ writeEdge(lines, edge, depth + 1);
58
+ }
59
+ lines.push(`${indent}]`);
60
+ }
61
+ function writeEdge(lines, edge, depth) {
62
+ const indent = INDENT.repeat(depth);
63
+ lines.push(`${indent}a :Edge ;`);
64
+ if (isSwitch(edge.target)) {
65
+ lines.push(`${indent}:edgeSwitch [`);
66
+ writeSwitch(lines, edge.target, depth + 1);
67
+ lines.push(`${indent}] ;`);
68
+ }
69
+ else {
70
+ lines.push(`${indent}:edgeTarget [`);
71
+ writeNode(lines, edge.target, depth + 1);
72
+ lines.push(`${indent}] ;`);
73
+ }
74
+ writeRelation(lines, edge, depth);
75
+ }
76
+ function writeSwitch(lines, sw, depth) {
77
+ const indent = INDENT.repeat(depth);
78
+ lines.push(`${indent}a :Switch ;`);
79
+ lines.push(`${indent}:discriminator "${sw.discriminator}" ;`);
80
+ for (const [i, sc] of sw.cases.entries()) {
81
+ if (i === 0) {
82
+ lines.push(`${indent}:hasCase [`);
83
+ }
84
+ else {
85
+ lines.push(`${indent}] , [`);
86
+ }
87
+ writeSwitchCase(lines, sc, depth + 1);
88
+ }
89
+ lines.push(`${indent}]`);
90
+ }
91
+ function writeSwitchCase(lines, sc, depth) {
92
+ const indent = INDENT.repeat(depth);
93
+ lines.push(`${indent}a :SwitchCase ;`);
94
+ lines.push(`${indent}:caseNode [`);
95
+ writeNode(lines, sc.node, depth + 1);
96
+ lines.push(`${indent}]`);
97
+ }
98
+ function writeRelation(lines, edge, depth) {
99
+ const indent = INDENT.repeat(depth);
100
+ lines.push(`${indent}:hasRelation [`);
101
+ const inner = INDENT.repeat(depth + 1);
102
+ lines.push(`${inner}a :Relation ;`);
103
+ if (edge.relation.slotName) {
104
+ lines.push(`${inner}:cardinality "${edge.relation.cardinality}" ;`);
105
+ lines.push(`${inner}:slotName "${edge.relation.slotName}"`);
106
+ }
107
+ else {
108
+ lines.push(`${inner}:cardinality "${edge.relation.cardinality}"`);
109
+ }
110
+ lines.push(`${indent}]`);
111
+ }
112
+ function isSwitch(target) {
113
+ return "discriminator" in target;
114
+ }
@@ -0,0 +1,3 @@
1
+ export { parseAnatomyYAML } from "./parse.js";
2
+ export { anatomyToTTL } from "./transform.js";
3
+ export type { AnonymousNode, Edge, NamedNode, Node, Relation, Specification, Style, Switch, SwitchCase, } from "./types.js";
@@ -0,0 +1,2 @@
1
+ import type { Specification } from "./types.js";
2
+ export declare function parseAnatomyYAML(raw: unknown): Specification;
@@ -0,0 +1,2 @@
1
+ import type { Specification } from "./types.js";
2
+ export declare function anatomyToTTL(spec: Specification): string;
@@ -0,0 +1,36 @@
1
+ export interface Specification {
2
+ root: NamedNode;
3
+ }
4
+ export interface NamedNode {
5
+ type: "named";
6
+ uri: string;
7
+ styles?: Style[];
8
+ edges?: Edge[];
9
+ }
10
+ export interface AnonymousNode {
11
+ type: "anonymous";
12
+ role: string;
13
+ styles?: Style[];
14
+ edges?: Edge[];
15
+ }
16
+ export type Node = NamedNode | AnonymousNode;
17
+ export interface Edge {
18
+ target: Node | Switch;
19
+ relation: Relation;
20
+ }
21
+ export interface Relation {
22
+ cardinality: string;
23
+ slotName?: string;
24
+ }
25
+ export interface Style {
26
+ key: string;
27
+ value: string;
28
+ }
29
+ export interface Switch {
30
+ discriminator: "props" | "internal" | "override";
31
+ cases: SwitchCase[];
32
+ }
33
+ export interface SwitchCase {
34
+ value: string;
35
+ node: Node;
36
+ }
@@ -0,0 +1,36 @@
1
+ export interface Specification {
2
+ root: NamedNode;
3
+ }
4
+ export interface NamedNode {
5
+ type: "named";
6
+ uri: string;
7
+ styles?: Style[];
8
+ edges?: Edge[];
9
+ }
10
+ export interface AnonymousNode {
11
+ type: "anonymous";
12
+ role: string;
13
+ styles?: Style[];
14
+ edges?: Edge[];
15
+ }
16
+ export type Node = NamedNode | AnonymousNode;
17
+ export interface Edge {
18
+ target: Node | Switch;
19
+ relation: Relation;
20
+ }
21
+ export interface Relation {
22
+ cardinality: string;
23
+ slotName?: string;
24
+ }
25
+ export interface Style {
26
+ key: string;
27
+ value: string;
28
+ }
29
+ export interface Switch {
30
+ discriminator: "props" | "internal" | "override";
31
+ cases: SwitchCase[];
32
+ }
33
+ export interface SwitchCase {
34
+ value: string;
35
+ node: Node;
36
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@canonical/anatomy-dsl",
3
+ "description": "Anatomy DSL meta-model: TypeScript types mirroring the OWL ontology, and a YAML-to-Turtle transform",
4
+ "version": "0.2.0",
5
+ "type": "module",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types/index.d.ts",
11
+ "import": "./dist/esm/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "license": "LGPL-3.0",
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "check": "biome check && tsc --noEmit",
21
+ "check:fix": "biome check --write",
22
+ "check:ts": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "test:coverage": "vitest run --coverage"
26
+ },
27
+ "devDependencies": {
28
+ "@biomejs/biome": "^2.4.6",
29
+ "@canonical/biome-config": "^0.17.1",
30
+ "@canonical/typescript-config": "^0.17.1",
31
+ "expect-type": "^1.3.0",
32
+ "typescript": "^5.9.3",
33
+ "vitest": "^4.0.18",
34
+ "yaml": "^2.7.1"
35
+ }
36
+ }