@diagc/core 0.1.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 (59) hide show
  1. package/LICENSE +709 -0
  2. package/README.md +27 -0
  3. package/dist/builder.d.ts +381 -0
  4. package/dist/builder.js +590 -0
  5. package/dist/children.d.ts +21 -0
  6. package/dist/children.js +45 -0
  7. package/dist/commands.d.ts +219 -0
  8. package/dist/commands.js +474 -0
  9. package/dist/compose.d.ts +19 -0
  10. package/dist/compose.js +246 -0
  11. package/dist/drawings.d.ts +13 -0
  12. package/dist/drawings.js +36 -0
  13. package/dist/eject.d.ts +20 -0
  14. package/dist/eject.js +260 -0
  15. package/dist/fishbone.d.ts +66 -0
  16. package/dist/fishbone.js +95 -0
  17. package/dist/git.d.ts +65 -0
  18. package/dist/git.js +159 -0
  19. package/dist/guards.d.ts +10 -0
  20. package/dist/guards.js +98 -0
  21. package/dist/index.d.ts +25 -0
  22. package/dist/index.js +45 -0
  23. package/dist/labels.d.ts +5 -0
  24. package/dist/labels.js +10 -0
  25. package/dist/layout-defaults.d.ts +20 -0
  26. package/dist/layout-defaults.js +20 -0
  27. package/dist/mutate.d.ts +106 -0
  28. package/dist/mutate.js +547 -0
  29. package/dist/second-order.d.ts +39 -0
  30. package/dist/second-order.js +86 -0
  31. package/dist/text.d.ts +5 -0
  32. package/dist/text.js +25 -0
  33. package/dist/threat-model.d.ts +88 -0
  34. package/dist/threat-model.js +188 -0
  35. package/dist/types.d.ts +395 -0
  36. package/dist/types.js +27 -0
  37. package/dist/util.d.ts +11 -0
  38. package/dist/util.js +13 -0
  39. package/dist/validate.d.ts +19 -0
  40. package/dist/validate.js +736 -0
  41. package/dist/view/compile.d.ts +29 -0
  42. package/dist/view/compile.js +78 -0
  43. package/dist/view/edges.d.ts +4 -0
  44. package/dist/view/edges.js +118 -0
  45. package/dist/view/hierarchy.d.ts +41 -0
  46. package/dist/view/hierarchy.js +103 -0
  47. package/dist/view/layers.d.ts +7 -0
  48. package/dist/view/layers.js +17 -0
  49. package/dist/view/lod.d.ts +15 -0
  50. package/dist/view/lod.js +17 -0
  51. package/dist/view/scope.d.ts +23 -0
  52. package/dist/view/scope.js +106 -0
  53. package/dist/view/size.d.ts +8 -0
  54. package/dist/view/size.js +34 -0
  55. package/dist/view/tree.d.ts +12 -0
  56. package/dist/view/tree.js +147 -0
  57. package/dist/view/types.d.ts +68 -0
  58. package/dist/view/types.js +1 -0
  59. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @diagc/core
2
+
3
+ The diagram model behind [`diagc`](https://www.npmjs.com/package/diagc): the `DiagramModel` types, `validate()`, the builder DSL, and the view compiler. No React, no filesystem — it does not know it is going to be drawn.
4
+
5
+ ```bash
6
+ npm i -D @diagc/core
7
+ ```
8
+
9
+ Install it alongside `diagc` to get types and completion when authoring `.diagram.ts` files:
10
+
11
+ ```ts
12
+ import { model } from '@diagc/core';
13
+
14
+ const m = model('acme', { name: 'Acme platform' });
15
+ const web = m.node('web', { name: 'Web app', type: 'service' });
16
+ m.relate(web, m.node('db', { name: 'Postgres', type: 'database' }), { kind: 'reads' });
17
+
18
+ export default m;
19
+ ```
20
+
21
+ `diagc compile` executes the file and validates whatever it exports, so the CLI works with or without this package installed — it only changes what your editor knows.
22
+
23
+ Docs and the full model reference: <https://github.com/Ferroman/diagc>
24
+
25
+ ## License
26
+
27
+ MIT
@@ -0,0 +1,381 @@
1
+ import type { Column, DiagramLegend, DiagramModel, EdgeLabel, FontScale, LayerRule, NotationId, Polarity, RelationStyle, TextAlign, TextRun, Threat } from './types.js';
2
+ import { type FishbonePreset } from './fishbone.js';
3
+ import { type Valence } from './second-order.js';
4
+ import { type ThreatTarget } from './threat-model.js';
5
+ export interface NodeOpts {
6
+ type?: string;
7
+ name?: string;
8
+ icon?: string;
9
+ /** silhouette-mask ref (see DiagramNode.shape) */
10
+ shape?: string;
11
+ /** image ref for image-typed nodes (see DiagramNode.image) */
12
+ image?: string;
13
+ /** fill/accent color (see DiagramNode.color) */
14
+ color?: string;
15
+ /** label color override (see DiagramNode.textColor) */
16
+ textColor?: string;
17
+ /** implementation technology (see DiagramNode.technology) */
18
+ technology?: string;
19
+ /** navigation target (see DiagramNode.link) */
20
+ link?: string;
21
+ description?: string;
22
+ /** rich-text label runs (see DiagramNode.rich) */
23
+ rich?: TextRun[];
24
+ /** label alignment (see DiagramNode.textAlign) */
25
+ textAlign?: TextAlign;
26
+ /** label size step (see DiagramNode.fontScale) */
27
+ fontScale?: FontScale;
28
+ metadata?: Record<string, unknown>;
29
+ /** cross-diagram identity (see DiagramNode.key) */
30
+ key?: string;
31
+ /** compose another diagram's content under this node (see DiagramNode.include) */
32
+ include?: string;
33
+ /** structure plane to graft from the include (see DiagramNode.includePlane) */
34
+ includePlane?: string;
35
+ /** carry the include's planes over (see DiagramNode.includePlanes) */
36
+ includePlanes?: boolean;
37
+ /** restrict this node to a single plane (see DiagramNode.plane) */
38
+ plane?: string;
39
+ /** transparent-sheet membership (see DiagramNode.layer) */
40
+ layer?: string;
41
+ /** ER-table rows (see DiagramNode.columns) */
42
+ columns?: Column[];
43
+ /** STRIDE findings (see DiagramNode.threats) */
44
+ threats?: Threat[];
45
+ }
46
+ export interface RelateOpts {
47
+ kind: string;
48
+ /** explicit relation id; when omitted the builder synthesizes `${from}->${to}#${n}`.
49
+ * The pair counter advances either way, so a later un-id'd relation on the same
50
+ * pair gets the same suffix it would have gotten without the override. */
51
+ id?: string;
52
+ label?: string;
53
+ /** positioned edge labels (see DiagramRelation.labels) */
54
+ labels?: EdgeLabel[];
55
+ style?: RelationStyle;
56
+ description?: string;
57
+ layer?: string;
58
+ polarity?: Polarity;
59
+ delay?: boolean;
60
+ /** FK column on the source table (see DiagramRelation.fromColumn) */
61
+ fromColumn?: string;
62
+ /** referenced column on the target table (see DiagramRelation.toColumn) */
63
+ toColumn?: string;
64
+ /** STRIDE findings (see DiagramRelation.threats) */
65
+ threats?: Threat[];
66
+ }
67
+ /** A threat as authored: the id is synthesized (`t1`, `t2`, …) unless given. */
68
+ export type ThreatOpts = Omit<Threat, 'id'> & {
69
+ id?: string;
70
+ };
71
+ export interface ContainsOpts {
72
+ /** plane the containment belongs to; defaults to the model's first-declared plane */
73
+ plane?: string;
74
+ }
75
+ export declare class NodeRef {
76
+ readonly id: string;
77
+ private readonly builder;
78
+ constructor(id: string, builder: ModelBuilder);
79
+ /** children, optionally followed by a trailing `{ plane }` options object */
80
+ contains(...args: (NodeRef | ContainsOpts)[]): this;
81
+ /** A STRIDE finding against this element. On every node ref, not just the
82
+ * threat-model ones: threat-modelling an existing C4 or ER diagram annotates
83
+ * the nodes it already has. */
84
+ threat(opts: ThreatOpts): this;
85
+ }
86
+ export interface CommitOpts {
87
+ /** default `${branchId}-${n}`, n = this branch's 1-based commit count */
88
+ id?: string;
89
+ /** the label drawn above the circle; absent = untagged (name '') */
90
+ tag?: string;
91
+ /** start a new segment branched off this commit (on another branch) — no
92
+ * `commit` link from the lane's previous commit */
93
+ from?: CommitRef;
94
+ /** empty columns to leave before this commit (`metadata.gap`) */
95
+ gap?: number;
96
+ color?: string;
97
+ }
98
+ export type MergeOpts = Omit<CommitOpts, 'from'>;
99
+ export declare class CommitRef extends NodeRef {
100
+ readonly branch: BranchRef;
101
+ constructor(id: string, builder: ModelBuilder, branch: BranchRef);
102
+ }
103
+ /** One lane of a git graph. `commit()` chains from the lane's last commit; `merge()`
104
+ * adds a commit that absorbs another lane's. Both hand back CommitRefs, which are
105
+ * NodeRefs — relate them, layer them, describe them like any node. */
106
+ export declare class BranchRef extends NodeRef {
107
+ private readonly m;
108
+ private count;
109
+ private latest;
110
+ constructor(id: string, m: ModelBuilder);
111
+ commit(tagOrOpts?: string | CommitOpts): CommitRef;
112
+ merge(src: CommitRef, opts?: MergeOpts): CommitRef;
113
+ private create;
114
+ }
115
+ export interface StageOpts {
116
+ /** the label drawn at the top of the frame; defaults to the id */
117
+ name?: string;
118
+ /** the commit whose column the frame starts at */
119
+ from: CommitRef;
120
+ /** the commit whose column it ends at (inclusive); defaults to `from` */
121
+ to?: CommitRef;
122
+ color?: string;
123
+ }
124
+ export declare class GitGraphBuilder {
125
+ private readonly m;
126
+ constructor(m: ModelBuilder);
127
+ /** A named frame across every lane, spanning the columns of `from`…`to` —
128
+ * a phase of the history ("Development", "Release candidates"). Declare it
129
+ * after the commits it names. */
130
+ stage(id: string, opts: StageOpts): NodeRef;
131
+ /** lanes are drawn top-to-bottom in the order they are declared */
132
+ branch(id: string, opts?: {
133
+ name?: string;
134
+ color?: string;
135
+ }): BranchRef;
136
+ }
137
+ export interface ConsequenceOpts {
138
+ /** good, bad or neutral (the default) — picks the node type */
139
+ valence?: Valence;
140
+ /** a label on the arrow that leads here */
141
+ label?: string;
142
+ description?: string;
143
+ color?: string;
144
+ }
145
+ /** A decision or a consequence. `then()` IS the method of second-order
146
+ * thinking — "and then what?" — so a chain of calls reads as the reasoning. */
147
+ export declare class ConsequenceRef extends NodeRef {
148
+ private readonly m;
149
+ constructor(id: string, m: ModelBuilder);
150
+ /** what follows from this: a new consequence, and the arrow that leads to it */
151
+ then(id: string, name?: string, opts?: ConsequenceOpts): ConsequenceRef;
152
+ /** join two branches: this also leads to a consequence declared elsewhere */
153
+ leadsTo(to: NodeRef, opts?: {
154
+ label?: string;
155
+ }): this;
156
+ }
157
+ export declare class SecondOrderBuilder {
158
+ private readonly m;
159
+ constructor(m: ModelBuilder);
160
+ /** the root of a tree; several decisions share one set of bands */
161
+ decision(id: string, name?: string, opts?: Omit<ConsequenceOpts, 'valence' | 'label'>): ConsequenceRef;
162
+ }
163
+ export interface FishboneOpts {
164
+ description?: string;
165
+ color?: string;
166
+ }
167
+ /** A cause (level 2) or a sub-cause (level 3). The level rides on the ref so a
168
+ * fourth `.cause()` fails at build time, where the author is, rather than as a
169
+ * validation issue at compile time. */
170
+ export declare class CauseRef extends NodeRef {
171
+ private readonly m;
172
+ private readonly level;
173
+ constructor(id: string, m: ModelBuilder, level: 2 | 3);
174
+ /** a sub-cause of this cause, and the arrow from it to here */
175
+ cause(id: string, name?: string, opts?: FishboneOpts): CauseRef;
176
+ }
177
+ export declare class CategoryRef extends NodeRef {
178
+ private readonly m;
179
+ constructor(id: string, m: ModelBuilder);
180
+ /** a cause on this bone, and the arrow from it to here */
181
+ cause(id: string, name?: string, opts?: FishboneOpts): CauseRef;
182
+ }
183
+ export declare class FishboneBuilder {
184
+ private readonly m;
185
+ private readonly effect;
186
+ constructor(m: ModelBuilder, effect: NodeRef);
187
+ /** a major bone, and the arrow from it to the effect */
188
+ category(id: string, name?: string, opts?: FishboneOpts): CategoryRef;
189
+ /** the bones of a standard set, keyed by their slug ids (`presetId`) */
190
+ categories(preset: FishbonePreset): Record<string, CategoryRef>;
191
+ }
192
+ /** A data flow: a relation ref that takes threats, the way a NodeRef does. */
193
+ export declare class FlowRef {
194
+ readonly id: string;
195
+ private readonly m;
196
+ constructor(id: string, m: ModelBuilder);
197
+ threat(opts: ThreatOpts): this;
198
+ }
199
+ /** A DFD element's node options, minus the two its helper already supplies:
200
+ * `type` from the helper itself, `name` from its second argument. */
201
+ export type ElementOpts = Omit<NodeOpts, 'type' | 'name'>;
202
+ /** The four DFD element kinds plus flows. Everything it hands back is an
203
+ * ordinary NodeRef/FlowRef, so the rest of the builder — contains, relate,
204
+ * layers, threat — composes with it unchanged. */
205
+ export declare class ThreatModelBuilder {
206
+ private readonly m;
207
+ constructor(m: ModelBuilder);
208
+ private element;
209
+ /** an external entity: a user, a third party, anything outside the system */
210
+ entity(id: string, name?: string, opts?: ElementOpts): NodeRef;
211
+ /** a process: something the system does with the data */
212
+ process(id: string, name?: string, opts?: ElementOpts): NodeRef;
213
+ /** a data store: where the data rests */
214
+ store(id: string, name?: string, opts?: ElementOpts): NodeRef;
215
+ /** a trust boundary: nest elements with `.contains()` */
216
+ boundary(id: string, name?: string, opts?: ElementOpts): NodeRef;
217
+ /** a data flow; a string is its label */
218
+ flow(from: NodeRef, to: NodeRef, labelOrOpts?: string | Omit<RelateOpts, 'kind'>): FlowRef;
219
+ }
220
+ export interface ActivityElementOpts {
221
+ color?: string;
222
+ }
223
+ /** Shared element surface of a lane and a region: each helper creates a typed
224
+ * node contained by this scope and hands back a plain NodeRef, so everything
225
+ * composes with the rest of the builder (relate, layers, contains). */
226
+ export declare abstract class ActivityScope extends NodeRef {
227
+ protected readonly m: ModelBuilder;
228
+ private counters;
229
+ constructor(id: string, m: ModelBuilder);
230
+ /** `${scopeId}-<suffix>` for the first of a kind, `-<n>` after — deterministic
231
+ * from declaration order, so layout-overlay keys stay stable. */
232
+ protected autoId(suffix: string): string;
233
+ protected element(id: string, type: string, name: string, opts?: ActivityElementOpts): NodeRef;
234
+ action(id: string, name: string, opts?: ActivityElementOpts): NodeRef;
235
+ object(id: string, name: string, opts?: ActivityElementOpts): NodeRef;
236
+ send(id: string, name: string, opts?: ActivityElementOpts): NodeRef;
237
+ receive(id: string, name: string, opts?: ActivityElementOpts): NodeRef;
238
+ note(id: string, text: string): NodeRef;
239
+ decision(id?: string, name?: string): NodeRef;
240
+ bar(id?: string): NodeRef;
241
+ start(id?: string): NodeRef;
242
+ end(id?: string): NodeRef;
243
+ }
244
+ export declare class RegionRef extends ActivityScope {
245
+ }
246
+ export declare class LaneRef extends ActivityScope {
247
+ /** interruptible region: a dashed container inside this lane */
248
+ region(id?: string, name?: string): RegionRef;
249
+ }
250
+ /** One activity diagram: the frame node itself (this IS its NodeRef) plus lane
251
+ * and cross-lane flow helpers. Call m.activity() once per frame — several
252
+ * frames coexist on one canvas. */
253
+ export declare class ActivityBuilder extends NodeRef {
254
+ private readonly b;
255
+ constructor(id: string, b: ModelBuilder);
256
+ /** lanes are drawn top-to-bottom in the order they are declared */
257
+ lane(id: string, opts?: {
258
+ name?: string;
259
+ color?: string;
260
+ }): LaneRef;
261
+ flow(from: NodeRef, to: NodeRef, label?: string): this;
262
+ objectFlow(from: NodeRef, to: NodeRef, label?: string): this;
263
+ interrupt(from: NodeRef, to: NodeRef, label?: string): this;
264
+ noteLink(note: NodeRef, target: NodeRef): this;
265
+ }
266
+ export declare class ModelBuilder {
267
+ private readonly id;
268
+ private readonly name;
269
+ private nodes;
270
+ private containment;
271
+ private relations;
272
+ private layers;
273
+ private planes;
274
+ private legendConfig;
275
+ private typeColorMap;
276
+ private layerRuleList;
277
+ private modelNotation;
278
+ private modelStyle;
279
+ private pairCounters;
280
+ private git;
281
+ private so;
282
+ private fb;
283
+ private tm;
284
+ constructor(id: string, name: string);
285
+ node(id: string, opts?: NodeOpts): NodeRef;
286
+ /** ER table: a node of type 'db-table' carrying `columns`. */
287
+ table(id: string, opts: Omit<NodeOpts, 'type'> & {
288
+ columns: Column[];
289
+ }): NodeRef;
290
+ /**
291
+ * Foreign key: a `kind:'fk'` relation from `from.fromColumn` to `to.toColumn`.
292
+ * `toColumn` defaults to the target table's single primary-key column; declare
293
+ * the target table (with its PK) before calling.
294
+ */
295
+ fk(from: NodeRef, fromColumn: string, to: NodeRef, toColumn?: string, opts?: Omit<RelateOpts, 'kind' | 'fromColumn' | 'toColumn'>): this;
296
+ /** internal — used by NodeRef */
297
+ addContainment(parent: string, child: string, plane?: string): void;
298
+ /** internal — appends a threat to the node or relation `target` names; used by
299
+ * NodeRef.threat() and FlowRef.threat() */
300
+ addThreat(target: ThreatTarget, opts: ThreatOpts): void;
301
+ relate(from: NodeRef, to: NodeRef, opts: RelateOpts): this;
302
+ /** internal — like relate(), but returns the new relation's id (FlowRef needs
303
+ * it to hang threats off the flow) */
304
+ addRelation(from: NodeRef, to: NodeRef, opts: RelateOpts): string;
305
+ layer(id: string, opts?: {
306
+ name?: string;
307
+ tint?: string;
308
+ }): this;
309
+ plane(id: string, opts?: {
310
+ name?: string;
311
+ containmentOf?: string;
312
+ layers?: string[];
313
+ baseRelations?: boolean;
314
+ notation?: NotationId;
315
+ hides?: string[];
316
+ hidesTree?: string[];
317
+ }): this;
318
+ /**
319
+ * Declare this model a git graph: a plane with the `git-graph` notation that
320
+ * must be the default (first-declared) plane, so the lanes' containment needs
321
+ * no plane tag. Returns the builder for lanes; see BranchRef.
322
+ */
323
+ gitGraph(opts?: {
324
+ plane?: string;
325
+ name?: string;
326
+ }): GitGraphBuilder;
327
+ /**
328
+ * Declare a second-order thinking diagram. With no `plane` the NOTATION is
329
+ * model-wide (nothing about it needs a plane — the notation is flat); name a
330
+ * plane to keep it beside other views of the same model.
331
+ */
332
+ secondOrder(opts?: {
333
+ plane?: string;
334
+ name?: string;
335
+ }): SecondOrderBuilder;
336
+ /**
337
+ * Declare a fishbone diagram: the effect at the head, then `.category()` /
338
+ * `.cause()` to hang bones on it. With no `plane` the NOTATION is model-wide
339
+ * (the notation is flat); name a plane to keep it beside other views of the
340
+ * same model. The effect's own options ride in `opts` too.
341
+ */
342
+ fishbone(id: string, name?: string, opts?: FishboneOpts & {
343
+ plane?: string;
344
+ planeName?: string;
345
+ }): FishboneBuilder;
346
+ /**
347
+ * Declare a threat model (STRIDE data-flow diagram). With no `plane` the
348
+ * NOTATION is model-wide; name a plane to threat-model an existing
349
+ * architecture beside its other views — the plane holds its own boundary
350
+ * containment over the same nodes.
351
+ */
352
+ threatModel(opts?: {
353
+ plane?: string;
354
+ name?: string;
355
+ }): ThreatModelBuilder;
356
+ /** Declare an activity diagram: a framed swimlane flow. Repeatable — each
357
+ * call is one frame; frames are ordinary containers on whatever plane the
358
+ * model uses (no notation, no plane creation). */
359
+ activity(id: string, opts?: {
360
+ name?: string;
361
+ }): ActivityBuilder;
362
+ /** Declare a legend. Bare `legend()` means derived sections only. */
363
+ legend(opts?: DiagramLegend): this;
364
+ /** Default accent colour per node type; `*` is the fallback for the rest.
365
+ * The one way to colour nodes a composed diagram did not author. Successive
366
+ * calls merge, last wins per key; a node's own `color` still wins over both. */
367
+ typeColors(map: Record<string, string>): this;
368
+ /** Put unlayered relations on layers by class (`kind` and/or `style.color`),
369
+ * first match wins. The one way a composed diagram can layer relations an
370
+ * include brought in. Successive calls append; an explicit relation `layer`
371
+ * still beats every rule. */
372
+ layerRules(rules: LayerRule[]): this;
373
+ /** pin the whole diagram's visual language (see DiagramModel.notation) */
374
+ notation(id: string): this;
375
+ /** pin the model-level renderer style preset (see DiagramModel.style) */
376
+ style(id: string): this;
377
+ toJSON(): DiagramModel;
378
+ }
379
+ export declare function model(id: string, opts?: {
380
+ name?: string;
381
+ }): ModelBuilder;