@archcode-io/engine 0.2.0-preview.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 (48) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +202 -0
  3. package/README.md +88 -0
  4. package/dist/src/capacity.d.ts +67 -0
  5. package/dist/src/capacity.js +227 -0
  6. package/dist/src/check.d.ts +21 -0
  7. package/dist/src/check.js +192 -0
  8. package/dist/src/compiled.d.ts +61 -0
  9. package/dist/src/compiled.js +91 -0
  10. package/dist/src/cst.d.ts +76 -0
  11. package/dist/src/cst.js +1 -0
  12. package/dist/src/diagnostics.d.ts +9 -0
  13. package/dist/src/diagnostics.js +1 -0
  14. package/dist/src/edit.d.ts +48 -0
  15. package/dist/src/edit.js +442 -0
  16. package/dist/src/index.d.ts +25 -0
  17. package/dist/src/index.js +24 -0
  18. package/dist/src/layout/balance.d.ts +34 -0
  19. package/dist/src/layout/balance.js +327 -0
  20. package/dist/src/layout/elk.d.ts +64 -0
  21. package/dist/src/layout/elk.js +267 -0
  22. package/dist/src/layout/host.d.ts +23 -0
  23. package/dist/src/layout/host.js +23 -0
  24. package/dist/src/layout/label.d.ts +49 -0
  25. package/dist/src/layout/label.js +113 -0
  26. package/dist/src/layout/measure.d.ts +11 -0
  27. package/dist/src/layout/measure.js +27 -0
  28. package/dist/src/layout/ortho.d.ts +54 -0
  29. package/dist/src/layout/ortho.js +206 -0
  30. package/dist/src/layout/route.d.ts +57 -0
  31. package/dist/src/layout/route.js +230 -0
  32. package/dist/src/lens.d.ts +83 -0
  33. package/dist/src/lens.js +377 -0
  34. package/dist/src/lexer.d.ts +7 -0
  35. package/dist/src/lexer.js +135 -0
  36. package/dist/src/model.d.ts +63 -0
  37. package/dist/src/model.js +114 -0
  38. package/dist/src/parser.d.ts +7 -0
  39. package/dist/src/parser.js +305 -0
  40. package/dist/src/render/svg.d.ts +56 -0
  41. package/dist/src/render/svg.js +289 -0
  42. package/dist/src/serialize.d.ts +10 -0
  43. package/dist/src/serialize.js +12 -0
  44. package/dist/src/tokens.d.ts +26 -0
  45. package/dist/src/tokens.js +15 -0
  46. package/dist/src/vocab.d.ts +38 -0
  47. package/dist/src/vocab.js +58 -0
  48. package/package.json +61 -0
@@ -0,0 +1,192 @@
1
+ import { diag } from './diagnostics.js';
2
+ import { ATTR_KEYS, IFACE_VERBS, OBJECT_KINDS, PLACEMENT_KINDS, RESERVED_KINDS, RUN_KEYS, VERBS } from './vocab.js';
3
+ /**
4
+ * Semantic diagnostics (spec §17.10). The parser only knows shapes; this pass
5
+ * knows names. It never changes the document and never stops a render — the
6
+ * lens still draws what it can — it only says what a reader would trip on.
7
+ *
8
+ * AC100 duplicate id error
9
+ * AC101 unresolved reference warning
10
+ * AC102 ambiguous reference warning
11
+ * AC103 reserved block kind info (rule · decision · board · profile — kept, not interpreted)
12
+ * AC104 unknown attribute info (`x-…` keys are silent: they are yours)
13
+ * AC105 `archcode <version>` pragma warning (unknown version, or not the first statement)
14
+ * AC106 `at` without a moment error
15
+ * AC107 relation between placement blocks warning (a node does not `call` a cluster — put things inside)
16
+ * AC108 `run` outside a placement block error
17
+ * AC109 `run` of something that cannot run warning (an actor, an external, a topic)
18
+ */
19
+ export const LANGUAGE_VERSIONS = new Set(['0.1', '0.2']);
20
+ const RUNNABLE = new Set(['service', 'webapp', 'app', 'gateway', 'broker', 'datastore', 'cache', 'function', 'job', 'component']);
21
+ const isMoment = (v) => /^(\d{4}(-\d{2}){0,2}|\+\d+[dwmy])$/.test(v);
22
+ /** Every full id a reference could mean, from where it is written. */
23
+ function candidates(ref, scope, m) {
24
+ if (m.objects.has(ref))
25
+ return [ref];
26
+ let s = scope;
27
+ while (s) {
28
+ const cand = `${s}.${ref}`;
29
+ if (m.objects.has(cand))
30
+ return [cand];
31
+ const dot = s.lastIndexOf('.');
32
+ s = dot < 0 ? undefined : s.slice(0, dot);
33
+ }
34
+ return [...m.objects.keys()].filter(k => k.endsWith(`.${ref}`));
35
+ }
36
+ export function check(doc, m) {
37
+ const out = [];
38
+ // Strictness follows the stage (§17.4): a sketch or a retired thing is not
39
+ // scolded at all, a proposal only warned. The stage a line sits under is the
40
+ // stage of the innermost declaration that carries one.
41
+ const stageOfLine = new Map();
42
+ const stageWalk = (nodes, inherited) => {
43
+ for (const n of nodes) {
44
+ if (n.type !== 'decl')
45
+ continue;
46
+ const d = n;
47
+ const own = d.inline.find(a => a.key === 'stage')?.values[0]
48
+ ?? (d.body ?? []).filter(c => c.type === 'attr').map(c => c).find(a => a.key === 'stage')?.values[0];
49
+ const stage = own ?? inherited;
50
+ const first = d.tokens[0]?.line ?? 0, last = d.tokens[d.tokens.length - 1]?.line ?? first;
51
+ if (stage)
52
+ for (let l = first; l <= last; l++)
53
+ stageOfLine.set(l, stage);
54
+ stageWalk(d.body ?? [], stage);
55
+ }
56
+ };
57
+ stageWalk(doc.body, undefined);
58
+ const line = (n) => n.tokens[0]?.line ?? 0;
59
+ const col = (n) => n.tokens[0]?.col ?? 1;
60
+ // Reference lookups only ever see model objects, so a name that resolves
61
+ // is fine wherever it was written; a name that does not is reported once.
62
+ const refCheck = (ref, scope, what, n) => {
63
+ if (!ref || /^\d/.test(ref))
64
+ return;
65
+ const c = candidates(ref, scope, m);
66
+ if (c.length === 0)
67
+ out.push(diag('warning', 'AC101', `${what} \`${ref}\` is not declared anywhere`, line(n), col(n)));
68
+ else if (c.length > 1)
69
+ out.push(diag('warning', 'AC102', `${what} \`${ref}\` could be ${c.join(' or ')} — write the full path`, line(n), col(n)));
70
+ };
71
+ // ---- pragma: first statement or nowhere ----
72
+ let first = true;
73
+ const seen = new Map();
74
+ const walk = (nodes, parent, parentKind, depth) => {
75
+ for (const n of nodes) {
76
+ if (n.type === 'blank')
77
+ continue;
78
+ if (n.type === 'attr' && n.key === 'archcode' && depth === 0) {
79
+ const v = n.values[0] ?? '';
80
+ if (!first)
81
+ out.push(diag('warning', 'AC105', '`archcode <version>` belongs at the top of the document', line(n), col(n)));
82
+ else if (!LANGUAGE_VERSIONS.has(v))
83
+ out.push(diag('warning', 'AC105', `archcode ${v || '?'} — this engine reads ${[...LANGUAGE_VERSIONS].join(', ')}`, line(n), col(n)));
84
+ first = false;
85
+ continue;
86
+ }
87
+ first = false;
88
+ if (n.type === 'decl') {
89
+ const d = n;
90
+ const id = parent && !d.id.includes('.') ? `${parent}.${d.id}` : d.id;
91
+ if (seen.has(id))
92
+ out.push(diag('error', 'AC100', `\`${id}\` is declared twice (first at line ${seen.get(id)})`, line(n), col(n)));
93
+ else
94
+ seen.set(id, line(n));
95
+ if (RESERVED_KINDS.has(d.kind)) {
96
+ out.push(diag('info', 'AC103', `\`${d.kind}\` is reserved in v0.2 — kept as written, not interpreted`, line(n), col(n)));
97
+ continue; // its body is not the model's business
98
+ }
99
+ if (d.kind === 'view')
100
+ continue; // the view vocabulary is checked by the picture, not here
101
+ for (const a of d.inline)
102
+ attrCheck(a, parent, d.kind);
103
+ for (const c of d.body ?? [])
104
+ if (c.type === 'attr')
105
+ attrCheck(c, parent, d.kind);
106
+ walk(d.body ?? [], id, d.kind, depth + 1);
107
+ continue;
108
+ }
109
+ if (n.type === 'rel') {
110
+ const r = n;
111
+ const from = r.subject ?? parent ?? '';
112
+ if (r.subject)
113
+ refCheck(r.subject, parent, 'subject', n);
114
+ else if (!parent)
115
+ out.push(diag('warning', 'AC101', `\`${r.verb}\` at top level needs a subject: \`checkout ${r.verb} ${r.object}\``, line(n), col(n)));
116
+ refCheck(r.object, parent, `${r.verb} target`, n);
117
+ for (const v of r.via ?? [])
118
+ refCheck(v, parent, 'via', n);
119
+ const kindOf = (ref) => { const c = candidates(ref, parent, m); return c.length === 1 ? m.objects.get(c[0])?.kind : undefined; };
120
+ const fk = kindOf(from), tk = kindOf(r.object);
121
+ if (fk && tk && PLACEMENT_KINDS.has(fk) && PLACEMENT_KINDS.has(tk))
122
+ out.push(diag('warning', 'AC107', `a ${fk} does not \`${r.verb}\` a ${tk} — nest it (\`${tk} … { ${fk} … }\`) or \`run\` something on it`, line(n), col(n)));
123
+ for (const c of r.body ?? [])
124
+ if (c.type === 'attr')
125
+ attrCheck(c, parent, 'relation');
126
+ continue;
127
+ }
128
+ if (n.type === 'run') {
129
+ const r = n;
130
+ if (!parentKind || !PLACEMENT_KINDS.has(parentKind))
131
+ out.push(diag('error', 'AC108', '`run` only makes sense inside an env, segment, cluster, managed service or node', line(n), col(n)));
132
+ for (const ref of r.refs) {
133
+ refCheck(ref, parent, 'run target', n);
134
+ const c = candidates(ref, parent, m);
135
+ const k = c.length === 1 ? m.objects.get(c[0])?.kind : undefined;
136
+ if (k && !RUNNABLE.has(k))
137
+ out.push(diag('warning', 'AC109', `\`${ref}\` is a ${k} — it has nowhere to run`, line(n), col(n)));
138
+ }
139
+ for (const a of r.attrs)
140
+ if (!RUN_KEYS.has(a.key) && !a.key.startsWith('x-'))
141
+ out.push(diag('info', 'AC104', `\`${a.key}\` is not a sizing key (${[...RUN_KEYS].join(', ')})`, line(n), col(n)));
142
+ for (const c of r.body ?? [])
143
+ if (c.type === 'attr')
144
+ attrCheck(c, parent, 'run');
145
+ continue;
146
+ }
147
+ if (n.type === 'iface') {
148
+ const f = n;
149
+ if (f.subject)
150
+ refCheck(f.subject, parent, 'subject', n);
151
+ else if (!parent)
152
+ out.push(diag('warning', 'AC101', `\`${f.verb}\` at top level needs a subject: \`checkout ${f.verb} ${f.kind} …\``, line(n), col(n)));
153
+ continue;
154
+ }
155
+ if (n.type === 'attr' && depth === 0) {
156
+ const a = n;
157
+ // a lone verb line at the top means someone forgot the subject
158
+ if (VERBS.has(a.key) || IFACE_VERBS.has(a.key))
159
+ out.push(diag('warning', 'AC101', `\`${a.key}\` needs a subject at top level`, line(n), col(n)));
160
+ else if (!ATTR_KEYS.has(a.key) && !a.key.startsWith('x-') && !OBJECT_KINDS.has(a.key))
161
+ out.push(diag('info', 'AC104', `\`${a.key}\` is not something the document knows — an object kind or an attribute?`, line(n), col(n)));
162
+ }
163
+ }
164
+ };
165
+ const attrCheck = (a, scope, ownerKind) => {
166
+ if (a.key === 'at') {
167
+ if (!a.values[0] || !isMoment(a.values[0]))
168
+ out.push(diag('error', 'AC106', '`at` needs a moment first: `at 2026-12 cpu 12`, `at +1y replicas 8`', line(a), col(a)));
169
+ return;
170
+ }
171
+ if (a.key === 'owned_by') {
172
+ refCheck(a.values[0] ?? '', scope, 'owned_by', a);
173
+ return;
174
+ }
175
+ if (a.key === 'publisher' || a.key === 'subscriber' || a.key === 'via') {
176
+ for (const v of a.values)
177
+ refCheck(v, scope, a.key, a);
178
+ return;
179
+ }
180
+ if (!ATTR_KEYS.has(a.key) && !a.key.startsWith('x-') && !VERBS.has(a.key))
181
+ out.push(diag('info', 'AC104', `\`${a.key}\` is not a known attribute of a ${ownerKind} — kept as written (prefix \`x-\` to say it is yours)`, line(a), col(a)));
182
+ };
183
+ walk(doc.body, undefined, undefined, 0);
184
+ return out.filter(d => {
185
+ const st = stageOfLine.get(d.line);
186
+ if (st === 'sketch' || st === 'retired')
187
+ return false;
188
+ if (st === 'proposed' && d.severity === 'error')
189
+ d.severity = 'warning';
190
+ return true;
191
+ }).sort((a, b) => a.line - b.line || a.col - b.col);
192
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The compiled form (spec header: `arch.json`, schema `archcode/v1`) — the
3
+ * model as plain data, and the same data written as YAML.
4
+ *
5
+ * This is a projection of the model, not a second syntax: comments, spacing
6
+ * and the order of attributes on a line are gone, ids are full paths, every
7
+ * attribute value is a list. It is what a tool that does not want to parse
8
+ * ArchCode reads, and what the docs show under the YAML and JSON tabs.
9
+ * Authoring in YAML or JSON (reading them back) is reserved (spec §15).
10
+ */
11
+ import type { Model } from './model.js';
12
+ export interface CompiledObject {
13
+ id: string;
14
+ kind: string;
15
+ name?: string;
16
+ parent?: string;
17
+ attrs?: Record<string, string[]>;
18
+ interfaces?: {
19
+ verb: string;
20
+ kind: string;
21
+ pointer?: string;
22
+ rev?: string;
23
+ label?: string;
24
+ attrs?: Record<string, string[]>;
25
+ }[];
26
+ timeline?: {
27
+ at: string;
28
+ attrs: Record<string, string[]>;
29
+ }[];
30
+ }
31
+ export interface CompiledRelation {
32
+ from: string;
33
+ verb: string;
34
+ to: string;
35
+ over?: string;
36
+ via?: string[];
37
+ spec?: string;
38
+ as?: string;
39
+ port?: string;
40
+ label?: string;
41
+ attrs?: Record<string, string[]>;
42
+ }
43
+ export interface CompiledPlacement {
44
+ host: string;
45
+ run: string;
46
+ attrs?: Record<string, string[]>;
47
+ timeline?: {
48
+ at: string;
49
+ attrs: Record<string, string[]>;
50
+ }[];
51
+ }
52
+ export interface Compiled {
53
+ archcode: string;
54
+ objects: CompiledObject[];
55
+ relations: CompiledRelation[];
56
+ placements: CompiledPlacement[];
57
+ }
58
+ /** The model as data. `version` is the language version the document declared, or the engine's. */
59
+ export declare function toCompiled(m: Model, version?: string): Compiled;
60
+ export declare const toJSON: (m: Model, version?: string) => string;
61
+ export declare function toYAML(m: Model, version?: string): string;
@@ -0,0 +1,91 @@
1
+ const nonEmpty = (o) => (Object.keys(o).length ? o : undefined);
2
+ const clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
3
+ function object(o) {
4
+ return clean({
5
+ id: o.id, kind: o.kind, name: o.name, parent: o.parent,
6
+ attrs: nonEmpty(o.attrs),
7
+ interfaces: o.interfaces.length ? o.interfaces.map((f) => clean({ verb: f.verb, kind: f.kind, pointer: f.pointer, rev: f.rev, label: f.label, attrs: nonEmpty(f.attrs) })) : undefined,
8
+ timeline: o.timeline.length ? o.timeline.map(m => ({ at: m.when, attrs: m.attrs })) : undefined,
9
+ });
10
+ }
11
+ function relation(r) {
12
+ return clean({ from: r.from, verb: r.verb, to: r.to, over: r.over, via: r.via?.length ? r.via : undefined, spec: r.spec, as: r.as, port: r.port, label: r.label, attrs: nonEmpty(r.attrs) });
13
+ }
14
+ function placement(p) {
15
+ return clean({ host: p.host, run: p.ref, attrs: nonEmpty(p.attrs), timeline: p.timeline.length ? p.timeline.map(m => ({ at: m.when, attrs: m.attrs })) : undefined });
16
+ }
17
+ /** The model as data. `version` is the language version the document declared, or the engine's. */
18
+ export function toCompiled(m, version = '0.2') {
19
+ return {
20
+ archcode: version,
21
+ objects: [...m.objects.values()].map(object),
22
+ relations: m.relations.map(relation),
23
+ placements: m.placements.map(placement),
24
+ };
25
+ }
26
+ export const toJSON = (m, version) => JSON.stringify(toCompiled(m, version), null, 2) + '\n';
27
+ // ---- YAML: a small emitter for the shapes above (maps, lists, strings) ----
28
+ // Every value in the compiled form is a string. A YAML reader must get the same
29
+ // string back, so anything it would read as a number, a boolean or a tag is quoted;
30
+ // a token with a unit or a date (`8Gi`, `2027-01`, `0.7/2`) is a string either way.
31
+ const PLAIN = /^[A-Za-z0-9_][\w.\-\/+]*$/;
32
+ const RESERVED = new Set(['true', 'false', 'null', 'yes', 'no', 'on', 'off', '~']);
33
+ const scalar = (v) => (PLAIN.test(v) && !RESERVED.has(v.toLowerCase()) && !/^-?\d+(\.\d+)?$/.test(v) && !/^[\d.]+$/.test(v) ? v : JSON.stringify(v));
34
+ const flow = (list) => `[${list.map(scalar).join(', ')}]`;
35
+ function attrsYaml(attrs, indent) {
36
+ return Object.entries(attrs).map(([k, v]) => `${indent}${scalar(k)}: ${flow(v)}`);
37
+ }
38
+ export function toYAML(m, version) {
39
+ const c = toCompiled(m, version);
40
+ const out = [`archcode: "${c.archcode}"`];
41
+ const kv = (indent, k, v) => (v === undefined ? [] : [`${indent}${k}: ${scalar(v)}`]);
42
+ out.push('objects:');
43
+ for (const o of c.objects) {
44
+ out.push(` - id: ${scalar(o.id)}`, ` kind: ${o.kind}`, ...kv(' ', 'name', o.name), ...kv(' ', 'parent', o.parent));
45
+ if (o.attrs) {
46
+ out.push(' attrs:');
47
+ out.push(...attrsYaml(o.attrs, ' '));
48
+ }
49
+ if (o.interfaces) {
50
+ out.push(' interfaces:');
51
+ for (const f of o.interfaces) {
52
+ out.push(` - verb: ${f.verb}`, ` kind: ${f.kind}`, ...kv(' ', 'pointer', f.pointer), ...kv(' ', 'rev', f.rev), ...kv(' ', 'label', f.label));
53
+ if (f.attrs) {
54
+ out.push(' attrs:');
55
+ out.push(...attrsYaml(f.attrs, ' '));
56
+ }
57
+ }
58
+ }
59
+ if (o.timeline) {
60
+ out.push(' timeline:');
61
+ for (const t of o.timeline) {
62
+ out.push(` - at: ${scalar(t.at)}`, ' attrs:');
63
+ out.push(...attrsYaml(t.attrs, ' '));
64
+ }
65
+ }
66
+ }
67
+ out.push(c.relations.length ? 'relations:' : 'relations: []');
68
+ for (const r of c.relations) {
69
+ out.push(` - from: ${scalar(r.from)}`, ` verb: ${r.verb}`, ` to: ${scalar(r.to)}`, ...kv(' ', 'over', r.over), ...(r.via ? [` via: ${flow(r.via)}`] : []), ...kv(' ', 'spec', r.spec), ...kv(' ', 'as', r.as), ...kv(' ', 'port', r.port), ...kv(' ', 'label', r.label));
70
+ if (r.attrs) {
71
+ out.push(' attrs:');
72
+ out.push(...attrsYaml(r.attrs, ' '));
73
+ }
74
+ }
75
+ out.push(c.placements.length ? 'placements:' : 'placements: []');
76
+ for (const p of c.placements) {
77
+ out.push(` - host: ${scalar(p.host)}`, ` run: ${scalar(p.run)}`);
78
+ if (p.attrs) {
79
+ out.push(' attrs:');
80
+ out.push(...attrsYaml(p.attrs, ' '));
81
+ }
82
+ if (p.timeline) {
83
+ out.push(' timeline:');
84
+ for (const t of p.timeline) {
85
+ out.push(` - at: ${scalar(t.at)}`, ' attrs:');
86
+ out.push(...attrsYaml(t.attrs, ' '));
87
+ }
88
+ }
89
+ }
90
+ return out.join('\n') + '\n';
91
+ }
@@ -0,0 +1,76 @@
1
+ import type { Token } from './tokens.js';
2
+ /**
3
+ * Concrete syntax tree. Every node owns a contiguous slice of the token stream,
4
+ * so serialization is concatenation and round-trip is byte-exact by construction.
5
+ */
6
+ export type Node = Doc | Decl | Attr | Rel | Run | Iface | Raw | Blank;
7
+ export interface Base {
8
+ tokens: Token[];
9
+ }
10
+ export interface Doc extends Base {
11
+ type: 'doc';
12
+ body: Node[];
13
+ /** The EOF token, which carries any trailing whitespace or comment. */
14
+ tail: Token[];
15
+ }
16
+ /** `service checkout "Checkout" tech Go { … }` */
17
+ export interface Decl extends Base {
18
+ type: 'decl';
19
+ kind: string;
20
+ id: string;
21
+ name?: string;
22
+ inline: Attr[];
23
+ body?: Node[];
24
+ }
25
+ /** `tech Go` · `tags core, sales` */
26
+ export interface Attr extends Base {
27
+ type: 'attr';
28
+ key: string;
29
+ values: string[];
30
+ }
31
+ /** `checkout writes orders_db over SQL via gw "label"` */
32
+ export interface Rel extends Base {
33
+ type: 'rel';
34
+ subject?: string;
35
+ verb: string;
36
+ canonicalVerb: string;
37
+ object: string;
38
+ over?: string;
39
+ via?: string[];
40
+ spec?: string;
41
+ as?: string;
42
+ port?: string;
43
+ label?: string;
44
+ body?: Node[];
45
+ }
46
+ /**
47
+ * `exposes http openapi://checkout.yaml @9f3c2e1` · `stores schema sql://payments.dbml` · `exposes cli`
48
+ * An interface the enclosing object offers (spec §4.3): the kind names the
49
+ * standard, the pointer is an address, never content; `@rev` is the revision
50
+ * the declaration was made against.
51
+ */
52
+ export interface Iface extends Base {
53
+ type: 'iface';
54
+ subject?: string;
55
+ verb: 'exposes' | 'stores';
56
+ kind: string;
57
+ pointer?: string;
58
+ rev?: string;
59
+ label?: string;
60
+ body?: Node[];
61
+ }
62
+ /** `run checkout, fulfillment replicas 6 cpu 2 mem 4Gi { at 2026-12 replicas 12 }` */
63
+ export interface Run extends Base {
64
+ type: 'run';
65
+ refs: string[];
66
+ attrs: Attr[];
67
+ body?: Node[];
68
+ }
69
+ /** The body of a reserved or `view` block: kept as tokens, read by nobody here. */
70
+ export interface Raw extends Base {
71
+ type: 'raw';
72
+ }
73
+ /** A line that carries no statement: blank, or comment-only. */
74
+ export interface Blank extends Base {
75
+ type: 'blank';
76
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export type Severity = 'error' | 'warning' | 'info';
2
+ export interface Diagnostic {
3
+ severity: Severity;
4
+ code: string;
5
+ message: string;
6
+ line: number;
7
+ col: number;
8
+ }
9
+ export declare const diag: (severity: Severity, code: string, message: string, line: number, col: number) => Diagnostic;
@@ -0,0 +1 @@
1
+ export const diag = (severity, code, message, line, col) => ({ severity, code, message, line, col });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Append one statement written in the notation itself — a declaration or a
3
+ * relation typed into the canvas terminal. The line goes in verbatim, before
4
+ * the first `view`/`board` block, so what the person typed is what the file
5
+ * says; nothing is normalised on the way in.
6
+ */
7
+ export declare function insertStatement(src: string, line: string): string;
8
+ export declare function addObject(src: string, o: {
9
+ kind: string;
10
+ id: string;
11
+ name?: string;
12
+ attrs?: Record<string, string>;
13
+ }): string;
14
+ export declare function deleteObject(src: string, id: string): string;
15
+ /** Rename everywhere: the declaration and every reference to it. */
16
+ export declare function renameObject(src: string, oldId: string, newId: string): string;
17
+ export declare function setDisplayName(src: string, id: string, name: string): string;
18
+ export declare function setAttribute(src: string, id: string, key: string, value: string | null): string;
19
+ export declare function addRelation(src: string, from: string, verb: string, to: string, over?: string): string;
20
+ /**
21
+ * Move one end of a relation to another object — the text edit behind
22
+ * dropping an arrow's end on a different card. Only the token that names the
23
+ * end changes; `over`, `via`, the label and the body stay as written. Matching
24
+ * is by local id at either end, as `deleteRelation` does.
25
+ */
26
+ export declare function retargetRelation(src: string, from: string, verb: string, to: string, end: 'from' | 'to', newId: string): string;
27
+ /** `a calls b` → `a uses b`: only the verb changes, everything else stays as written. */
28
+ export declare function setRelationVerb(src: string, from: string, verb: string, to: string, newVerb: string): string;
29
+ /**
30
+ * Set, change or drop (`value` null) an inline fact of a relation: `over`,
31
+ * `via`, `port`, `spec`, `as`, or the quoted `label`. A fact that is there is
32
+ * rewritten where it stands; a new one goes at the end of the statement's
33
+ * first line, before the body. `via` takes a comma-separated list.
34
+ */
35
+ export declare function setRelationAttr(src: string, from: string, verb: string, to: string, key: 'over' | 'via' | 'port' | 'spec' | 'as' | 'label', value: string | null): string;
36
+ export declare function deleteRelation(src: string, from: string, verb: string, to: string): string;
37
+ export declare function moveOut(src: string, id: string): string;
38
+ /** Move a declaration inside a system's block, creating the block if needed. */
39
+ export declare function moveInto(src: string, id: string, parentId: string): string;
40
+ /**
41
+ * Place a logical object on a host: `run <ref> [sizing]` goes into the host's
42
+ * body. A host without a body gets one; a one-line body opens up.
43
+ */
44
+ export declare function addRun(src: string, hostId: string, ref: string, sizing?: string): string;
45
+ /** Take a logical object off a host. A `run a, b` line loses one name; a `run a` line goes. */
46
+ export declare function deleteRun(src: string, hostId: string, ref: string): string;
47
+ /** Re-size a placement: `run checkout` → `run checkout replicas 3 cpu 2`. A shared line is split first. */
48
+ export declare function setRunSizing(src: string, hostId: string, ref: string, sizing: string): string;