@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.
- package/LICENSE +709 -0
- package/README.md +27 -0
- package/dist/builder.d.ts +381 -0
- package/dist/builder.js +590 -0
- package/dist/children.d.ts +21 -0
- package/dist/children.js +45 -0
- package/dist/commands.d.ts +219 -0
- package/dist/commands.js +474 -0
- package/dist/compose.d.ts +19 -0
- package/dist/compose.js +246 -0
- package/dist/drawings.d.ts +13 -0
- package/dist/drawings.js +36 -0
- package/dist/eject.d.ts +20 -0
- package/dist/eject.js +260 -0
- package/dist/fishbone.d.ts +66 -0
- package/dist/fishbone.js +95 -0
- package/dist/git.d.ts +65 -0
- package/dist/git.js +159 -0
- package/dist/guards.d.ts +10 -0
- package/dist/guards.js +98 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +45 -0
- package/dist/labels.d.ts +5 -0
- package/dist/labels.js +10 -0
- package/dist/layout-defaults.d.ts +20 -0
- package/dist/layout-defaults.js +20 -0
- package/dist/mutate.d.ts +106 -0
- package/dist/mutate.js +547 -0
- package/dist/second-order.d.ts +39 -0
- package/dist/second-order.js +86 -0
- package/dist/text.d.ts +5 -0
- package/dist/text.js +25 -0
- package/dist/threat-model.d.ts +88 -0
- package/dist/threat-model.js +188 -0
- package/dist/types.d.ts +395 -0
- package/dist/types.js +27 -0
- package/dist/util.d.ts +11 -0
- package/dist/util.js +13 -0
- package/dist/validate.d.ts +19 -0
- package/dist/validate.js +736 -0
- package/dist/view/compile.d.ts +29 -0
- package/dist/view/compile.js +78 -0
- package/dist/view/edges.d.ts +4 -0
- package/dist/view/edges.js +118 -0
- package/dist/view/hierarchy.d.ts +41 -0
- package/dist/view/hierarchy.js +103 -0
- package/dist/view/layers.d.ts +7 -0
- package/dist/view/layers.js +17 -0
- package/dist/view/lod.d.ts +15 -0
- package/dist/view/lod.js +17 -0
- package/dist/view/scope.d.ts +23 -0
- package/dist/view/scope.js +106 -0
- package/dist/view/size.d.ts +8 -0
- package/dist/view/size.js +34 -0
- package/dist/view/tree.d.ts +12 -0
- package/dist/view/tree.js +147 -0
- package/dist/view/types.d.ts +68 -0
- package/dist/view/types.js +1 -0
- package/package.json +38 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
export interface TextRun {
|
|
2
|
+
text: string;
|
|
3
|
+
bold?: boolean;
|
|
4
|
+
italic?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const TEXT_ALIGNS: readonly ["left", "center", "right"];
|
|
7
|
+
export type TextAlign = (typeof TEXT_ALIGNS)[number];
|
|
8
|
+
export declare const FONT_SCALES: readonly ["sm", "md", "lg"];
|
|
9
|
+
export type FontScale = (typeof FONT_SCALES)[number];
|
|
10
|
+
export declare const SIDES: readonly ["top", "right", "bottom", "left"];
|
|
11
|
+
export type Side = (typeof SIDES)[number];
|
|
12
|
+
export declare const RELATION_SHAPES: readonly ["curved", "straight", "step"];
|
|
13
|
+
export declare const RELATION_LINES: readonly ["solid", "dashed", "dotted"];
|
|
14
|
+
export declare const RELATION_MARKERS: readonly ["arrow", "dot", "square", "diamond", "none"];
|
|
15
|
+
/** Default footprint for image nodes whose size is not known yet. Kept in
|
|
16
|
+
* core so the editor and the renderer cannot drift apart. */
|
|
17
|
+
export declare const DEFAULT_IMAGE_NODE_SIZE: {
|
|
18
|
+
readonly w: 160;
|
|
19
|
+
readonly h: 120;
|
|
20
|
+
};
|
|
21
|
+
/** Sentinel id the renderer's elk wrapper uses for its synthetic layout root.
|
|
22
|
+
* Kept in core so validation can refuse a model node that would collide with
|
|
23
|
+
* it — the renderer and validate() must agree on the exact string. */
|
|
24
|
+
export declare const RESERVED_NODE_ID = "__root__";
|
|
25
|
+
/** Node types whose containment children cannot be re-homed when the container
|
|
26
|
+
* dies — an orphaned activity lane or git commit fails validation until undone.
|
|
27
|
+
* Deleting one of these cascades to its subtree; every other container severs
|
|
28
|
+
* only. Kept in core so the editor UI and the command algebra agree. */
|
|
29
|
+
export declare const CASCADE_DELETE_TYPES: readonly ["activity-frame", "branch"];
|
|
30
|
+
export interface Column {
|
|
31
|
+
name: string;
|
|
32
|
+
/** SQL-ish type shown right-aligned in the row, e.g. 'uuid', 'int', 'text' */
|
|
33
|
+
type?: string;
|
|
34
|
+
/** primary-key member */
|
|
35
|
+
pk?: boolean;
|
|
36
|
+
/** this column is a foreign key (drives the FK marker + row-port edge origin) */
|
|
37
|
+
fk?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface DiagramNode {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
/** free-form kind resolved by the renderer's registry; omit for a bare,
|
|
43
|
+
* typeless node (just a label) */
|
|
44
|
+
type?: string;
|
|
45
|
+
icon?: string;
|
|
46
|
+
/** asset file ref (content-hash name in .diagrams/src/assets/), e.g. "a3f9c2d4e5f6.png";
|
|
47
|
+
* when set the node renders as the image itself */
|
|
48
|
+
image?: string;
|
|
49
|
+
/** ref to an SVG silhouette (same forms as `image`: a content-hash asset name
|
|
50
|
+
* or a `/library/…` path) rendered as a tintable CSS mask filled with the
|
|
51
|
+
* node's color, over an invisible box. Takes precedence over `image`. */
|
|
52
|
+
shape?: string;
|
|
53
|
+
/** navigation target attached to the node — a URL (published HTML opens it in
|
|
54
|
+
* a new tab) or a host-interpreted ref like an Obsidian [[wikilink]]; the
|
|
55
|
+
* model records it and never interprets it */
|
|
56
|
+
link?: string;
|
|
57
|
+
/** cross-diagram identity: nodes sharing a key merge into one entity when
|
|
58
|
+
* diagrams are composed via includes; inert otherwise */
|
|
59
|
+
key?: string;
|
|
60
|
+
/** URL or path (relative to the declaring source) of another diagram whose
|
|
61
|
+
* content this node contains after compile-time expansion */
|
|
62
|
+
include?: string;
|
|
63
|
+
/** which of the included diagram's planes supplies the grafted structure
|
|
64
|
+
* (default: its default plane); meaningful only beside `include` */
|
|
65
|
+
includePlane?: string;
|
|
66
|
+
/** carry the included diagram's planes over — namespaced, notations intact —
|
|
67
|
+
* so its content stays viewable in its own visual language; opt-in */
|
|
68
|
+
includePlanes?: boolean;
|
|
69
|
+
/** accent color (border/background tint); overrides the type registry look */
|
|
70
|
+
color?: string;
|
|
71
|
+
/** label text color; overrides the default (which follows `color` for C4/shape
|
|
72
|
+
* nodes). Independent of `color` so text can differ from the shape/border. */
|
|
73
|
+
textColor?: string;
|
|
74
|
+
/** implementation technology shown in the type subtitle, e.g. "Java/Spring"
|
|
75
|
+
* renders `[Container: Java/Spring]`; meaningful in any notation */
|
|
76
|
+
technology?: string;
|
|
77
|
+
/** STRIDE findings against this node (see Threat) */
|
|
78
|
+
threats?: Threat[];
|
|
79
|
+
description?: string;
|
|
80
|
+
/** rich multiline label; when present, name === rich.map(r => r.text).join('') */
|
|
81
|
+
rich?: TextRun[];
|
|
82
|
+
/** whole-label horizontal alignment (default 'left') */
|
|
83
|
+
textAlign?: TextAlign;
|
|
84
|
+
/** whole-label relative font size (default 'md') */
|
|
85
|
+
fontScale?: FontScale;
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
/** restrict this node to a single plane (a view-local box); omit = shared
|
|
88
|
+
* across every plane. Re-nesting a shared node on one plane (via a
|
|
89
|
+
* plane-tagged containment edge) does NOT change its membership. */
|
|
90
|
+
plane?: string;
|
|
91
|
+
/** transparent-sheet membership: the node shows only while this layer is
|
|
92
|
+
* active (like relations); omit = the always-on base sheet. */
|
|
93
|
+
layer?: string;
|
|
94
|
+
/** ER-table rows; rendered when `type` is 'db-table' (see m.table) */
|
|
95
|
+
columns?: Column[];
|
|
96
|
+
}
|
|
97
|
+
export interface ContainmentEdge {
|
|
98
|
+
parent: string;
|
|
99
|
+
child: string;
|
|
100
|
+
/** plane this edge belongs to; absent = the model's default (first-declared) plane */
|
|
101
|
+
plane?: string;
|
|
102
|
+
}
|
|
103
|
+
/** An alternative containment context ("transparent sheet") over the same entities. */
|
|
104
|
+
export interface DiagramPlane {
|
|
105
|
+
id: string;
|
|
106
|
+
name: string;
|
|
107
|
+
/** borrow another plane's containment instead of own edges (e.g. a flow view over architecture) */
|
|
108
|
+
containmentOf?: string;
|
|
109
|
+
/** layer ids activated by default when this plane is selected */
|
|
110
|
+
layers?: string[];
|
|
111
|
+
/** false = hide untagged (base) relations in this plane — only layer relations show */
|
|
112
|
+
baseRelations?: boolean;
|
|
113
|
+
/** visual language; absent = default look */
|
|
114
|
+
notation?: string;
|
|
115
|
+
/** shared node ids this plane hides, KEEPING their contents: a hidden box's
|
|
116
|
+
* children are promoted to where the box was, each with its own nesting
|
|
117
|
+
* intact. That is what composition needs — an umbrella hides the `include`
|
|
118
|
+
* wrapper to lift a whole service model into place. A node with `plane` set is
|
|
119
|
+
* already scoped, so it never belongs here. */
|
|
120
|
+
hides?: string[];
|
|
121
|
+
/** shared node ids this plane hides ALONG WITH everything inside them, however
|
|
122
|
+
* deep. A child with another still-visible parent survives (containment is a
|
|
123
|
+
* DAG). Use this to drop detail a view does not want — a database's tables —
|
|
124
|
+
* where `hides` would promote that detail to the top level instead. Hiding
|
|
125
|
+
* removes the hidden nodes' edges too, which is the point when a view is
|
|
126
|
+
* unreadable from edge density; `layout.export.collapsed` folds instead, and
|
|
127
|
+
* a folded box still anchors its children's edges. */
|
|
128
|
+
hidesTree?: string[];
|
|
129
|
+
}
|
|
130
|
+
/** Per-relation visual overrides (whiteboard-style); anything unset falls back
|
|
131
|
+
* to the kind registry's style and the layer tint. */
|
|
132
|
+
export interface RelationStyle {
|
|
133
|
+
/** path geometry; default 'curved' (bezier) */
|
|
134
|
+
shape?: (typeof RELATION_SHAPES)[number];
|
|
135
|
+
/** any CSS color; overrides the layer tint */
|
|
136
|
+
color?: string;
|
|
137
|
+
/** stroke width in px */
|
|
138
|
+
width?: number;
|
|
139
|
+
line?: (typeof RELATION_LINES)[number];
|
|
140
|
+
/** end marker at the arrow head; default 'arrow' */
|
|
141
|
+
end?: (typeof RELATION_MARKERS)[number];
|
|
142
|
+
/** pin which side of the source/target node the edge attaches to; unset = auto (facing side) */
|
|
143
|
+
fromSide?: Side;
|
|
144
|
+
toSide?: Side;
|
|
145
|
+
animated?: boolean;
|
|
146
|
+
/** bezier bend for 'curved'; default 0.25 */
|
|
147
|
+
curvature?: number;
|
|
148
|
+
/** which side a bowed arc bulges toward (same arrow direction); default 'left' */
|
|
149
|
+
bow?: 'left' | 'right';
|
|
150
|
+
}
|
|
151
|
+
export declare const EDGE_LABEL_SIDES: readonly ["top", "bottom", "center"];
|
|
152
|
+
export type EdgeLabelSide = (typeof EDGE_LABEL_SIDES)[number];
|
|
153
|
+
/** a positioned text label on a connector; a relation may carry several */
|
|
154
|
+
export interface EdgeLabel {
|
|
155
|
+
id: string;
|
|
156
|
+
text: string;
|
|
157
|
+
/** position along the edge, 0..1 (default 0.5) */
|
|
158
|
+
t?: number;
|
|
159
|
+
/** perpendicular alignment relative to the line (default 'center') */
|
|
160
|
+
side?: EdgeLabelSide;
|
|
161
|
+
}
|
|
162
|
+
export declare const STRIDE: readonly ["S", "T", "R", "I", "D", "E"];
|
|
163
|
+
export type StrideCategory = (typeof STRIDE)[number];
|
|
164
|
+
export declare const THREAT_STATUSES: readonly ["open", "mitigated", "accepted", "not-applicable"];
|
|
165
|
+
export type ThreatStatus = (typeof THREAT_STATUSES)[number];
|
|
166
|
+
export declare const THREAT_SEVERITIES: readonly ["low", "medium", "high", "critical"];
|
|
167
|
+
export type ThreatSeverity = (typeof THREAT_SEVERITIES)[number];
|
|
168
|
+
/** One STRIDE finding against the element that carries it. Threats live ON
|
|
169
|
+
* the node or relation (not in a model-wide list) so they follow it through
|
|
170
|
+
* delete, undo, `include` namespacing and eject without any cascade code;
|
|
171
|
+
* the register is derived (see threat-model.ts). */
|
|
172
|
+
export interface Threat {
|
|
173
|
+
/** unique within its element's list (`t1`, `t2`, … when synthesized) */
|
|
174
|
+
id: string;
|
|
175
|
+
category: StrideCategory;
|
|
176
|
+
title: string;
|
|
177
|
+
description?: string;
|
|
178
|
+
/** unset = unrated */
|
|
179
|
+
severity?: ThreatSeverity;
|
|
180
|
+
/** unset = 'open' */
|
|
181
|
+
status?: ThreatStatus;
|
|
182
|
+
mitigation?: string;
|
|
183
|
+
}
|
|
184
|
+
export interface DiagramRelation {
|
|
185
|
+
id: string;
|
|
186
|
+
from: string;
|
|
187
|
+
to: string;
|
|
188
|
+
kind: string;
|
|
189
|
+
label?: string;
|
|
190
|
+
/** positioned labels; when present, supersedes the legacy single `label`.
|
|
191
|
+
* Read both through relationLabels() (bridges a legacy `label` string). */
|
|
192
|
+
labels?: EdgeLabel[];
|
|
193
|
+
/** STRIDE findings against this flow (see Threat) */
|
|
194
|
+
threats?: Threat[];
|
|
195
|
+
style?: RelationStyle;
|
|
196
|
+
description?: string;
|
|
197
|
+
layer?: string;
|
|
198
|
+
/** causal-loop-diagram polarity: '+' = same-direction influence, '-' = opposing */
|
|
199
|
+
polarity?: Polarity;
|
|
200
|
+
/** causal-loop-diagram delay marker on the influence */
|
|
201
|
+
delay?: boolean;
|
|
202
|
+
/** FK column on `from` (the many/child side) — anchors the edge to that row */
|
|
203
|
+
fromColumn?: string;
|
|
204
|
+
/** referenced column on `to` (the one/parent side); defaults to the target PK */
|
|
205
|
+
toColumn?: string;
|
|
206
|
+
}
|
|
207
|
+
export interface DiagramLayer {
|
|
208
|
+
id: string;
|
|
209
|
+
name: string;
|
|
210
|
+
tint?: string;
|
|
211
|
+
}
|
|
212
|
+
/** A hand-written legend row, for meaning the diagram cannot infer. */
|
|
213
|
+
export interface LegendItem {
|
|
214
|
+
label: string;
|
|
215
|
+
/** swatch drawn as this node type's shape (registry-resolved, free-form) */
|
|
216
|
+
type?: string;
|
|
217
|
+
/** swatch drawn as this relation kind's line (registry-resolved, free-form) */
|
|
218
|
+
kind?: string;
|
|
219
|
+
/** tint for whichever swatch is drawn; on its own = a plain colour chip */
|
|
220
|
+
color?: string;
|
|
221
|
+
icon?: string;
|
|
222
|
+
}
|
|
223
|
+
export declare const LEGEND_SECTIONS: readonly ["layers", "kinds", "types"];
|
|
224
|
+
export type LegendSection = (typeof LEGEND_SECTIONS)[number];
|
|
225
|
+
export declare const LEGEND_POSITIONS: readonly ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
226
|
+
export type LegendPosition = (typeof LEGEND_POSITIONS)[number];
|
|
227
|
+
/** An opt-in key describing the diagram's own visual vocabulary. Rows are
|
|
228
|
+
* derived from what is actually drawn; `items` adds what cannot be inferred. */
|
|
229
|
+
export interface DiagramLegend {
|
|
230
|
+
/** default 'Legend' */
|
|
231
|
+
title?: string;
|
|
232
|
+
/** default 'bottom-right' — the only corner not already occupied by chrome */
|
|
233
|
+
position?: LegendPosition;
|
|
234
|
+
/** derived sections to include; default ['layers', 'kinds'] */
|
|
235
|
+
show?: LegendSection[];
|
|
236
|
+
/** hand-written rows, appended after the derived ones */
|
|
237
|
+
items?: LegendItem[];
|
|
238
|
+
}
|
|
239
|
+
export interface DiagramModel {
|
|
240
|
+
version: 1;
|
|
241
|
+
id: string;
|
|
242
|
+
name: string;
|
|
243
|
+
/** id of a renderer style preset pinned by this diagram (travels with the
|
|
244
|
+
* file); unknown ids are legal — the renderer falls back to the app-level
|
|
245
|
+
* preference */
|
|
246
|
+
style?: string;
|
|
247
|
+
/** visual language for the whole diagram (travels with the file); a plane's
|
|
248
|
+
* own `notation` wins where one is declared. Unlike `style`, unknown ids are
|
|
249
|
+
* rejected at validation (`unknown-notation`) — a closed vocabulary the
|
|
250
|
+
* renderer keys a `Record` on, not an open preset id */
|
|
251
|
+
notation?: string;
|
|
252
|
+
/** opt-in key for this diagram's visual vocabulary; absent = no legend */
|
|
253
|
+
legend?: DiagramLegend;
|
|
254
|
+
/** default accent colour per node type, so a composed diagram can carry a
|
|
255
|
+
* colour convention its included models know nothing about. `*` is the
|
|
256
|
+
* fallback for any type without an entry. A node's own `color` always wins,
|
|
257
|
+
* and an include's `typeColors` is dropped on graft: the host owns the look. */
|
|
258
|
+
typeColors?: Record<string, string>;
|
|
259
|
+
/** Class -> layer for relations that carry no `layer` of their own, so a
|
|
260
|
+
* composed diagram can put included relations on layers it declares (an
|
|
261
|
+
* umbrella cannot edit grafted relations). A rule matches when every field it
|
|
262
|
+
* names equals the relation's (`kind`, `style.color`); first match wins; an
|
|
263
|
+
* explicit `layer` always beats the rules. Presentation, so dropped from
|
|
264
|
+
* included models on graft: the host owns the look. */
|
|
265
|
+
layerRules?: LayerRule[];
|
|
266
|
+
nodes: DiagramNode[];
|
|
267
|
+
containment: ContainmentEdge[];
|
|
268
|
+
relations: DiagramRelation[];
|
|
269
|
+
layers: DiagramLayer[];
|
|
270
|
+
/** empty = single implicit plane (all containment, no switcher) */
|
|
271
|
+
planes: DiagramPlane[];
|
|
272
|
+
}
|
|
273
|
+
/** One `layerRules` entry. Set at least one of `kind` / `color`. */
|
|
274
|
+
export interface LayerRule {
|
|
275
|
+
kind?: string;
|
|
276
|
+
color?: string;
|
|
277
|
+
layer: string;
|
|
278
|
+
}
|
|
279
|
+
/** Per-plane automatic-layout tuning. Every field is optional; an absent field
|
|
280
|
+
* falls back to the renderer's tuned default (see layoutOptionsFor). Travels in
|
|
281
|
+
* the layout overlay so a published diagram inherits the author's choices. */
|
|
282
|
+
export interface LayoutSettings {
|
|
283
|
+
/** elk.algorithm — 'layered' (default) | 'force' | 'stress' | 'mrtree' | 'radial' | 'rectpacking' */
|
|
284
|
+
algorithm?: string;
|
|
285
|
+
/** elk.direction for layered — 'DOWN' | 'RIGHT' | 'LEFT' | 'UP'. Absent ⇒
|
|
286
|
+
* `defaultLayoutDirection(model)`: down, or right where activity frames are drawn. */
|
|
287
|
+
direction?: string;
|
|
288
|
+
/** base node-node spacing in px; between-layers spacing is derived from it */
|
|
289
|
+
spacing?: number;
|
|
290
|
+
/** how routed edges are DRAWN. Both follow the layout's own waypoints (which
|
|
291
|
+
* is what keeps a line off the boxes it was steered around): 'curved'
|
|
292
|
+
* (default) rounds the bends generously, 'orthogonal' keeps them tight. An
|
|
293
|
+
* edge whose endpoint was moved by hand floats as a bezier either way. */
|
|
294
|
+
edgeRouting?: 'curved' | 'orthogonal';
|
|
295
|
+
/** layered only: target width÷height. When set, elk wraps long chains onto
|
|
296
|
+
* several rows (`elk.layered.wrapping.strategy = MULTI_EDGE`) aiming at this
|
|
297
|
+
* ratio. Absent = no wrapping (one unbounded row/column, the default). */
|
|
298
|
+
aspectRatio?: number;
|
|
299
|
+
}
|
|
300
|
+
/** Editor-managed node positions, keyed by resolved containment plane. */
|
|
301
|
+
/** a label's place along its edge: `t` 0..1 from the source, and which side of
|
|
302
|
+
* the line it sits on (absent ⇒ centred on it) */
|
|
303
|
+
export interface EdgeLabelPlacement {
|
|
304
|
+
t: number;
|
|
305
|
+
side?: EdgeLabelSide;
|
|
306
|
+
}
|
|
307
|
+
/** a threat bubble's saved state — see LayoutOverlay.notes. `open` is only
|
|
308
|
+
* ever `true`: "closed" is spelled by omitting it, as `manual` spells
|
|
309
|
+
* "automatic", so there is one way to write each state. */
|
|
310
|
+
export interface NotePlacement {
|
|
311
|
+
dx: number;
|
|
312
|
+
dy: number;
|
|
313
|
+
open?: true;
|
|
314
|
+
}
|
|
315
|
+
export interface LayoutOverlay {
|
|
316
|
+
version: 1;
|
|
317
|
+
planes: Record<string, Record<string, {
|
|
318
|
+
x: number;
|
|
319
|
+
y: number;
|
|
320
|
+
}>>;
|
|
321
|
+
/** editor-resized node footprints (image nodes); plane-independent — a
|
|
322
|
+
* node's size is the same on every plane, unlike its position */
|
|
323
|
+
sizes?: Record<string, {
|
|
324
|
+
w: number;
|
|
325
|
+
h: number;
|
|
326
|
+
}>;
|
|
327
|
+
/** planes switched to manual layout (automatic layout off); keyed like
|
|
328
|
+
* `planes` (via layoutPlaneKey). Absent/omitted ⇒ automatic layout. */
|
|
329
|
+
manual?: Record<string, true>;
|
|
330
|
+
/** per-plane automatic-layout settings, keyed like `planes` (via
|
|
331
|
+
* layoutPlaneKey). Absent ⇒ tuned defaults everywhere. */
|
|
332
|
+
settings?: Record<string, LayoutSettings>;
|
|
333
|
+
/** the containers each plane OPENS with unfolded, keyed like `planes` (via
|
|
334
|
+
* layoutPlaneKey). Saved together with the positions, because a hand-placed
|
|
335
|
+
* interior only means something while its container is open: without this a
|
|
336
|
+
* saved arrangement reopens fully folded and has to be unfolded by hand to be
|
|
337
|
+
* seen again. A starting point, not a lock — the reader folds and unfolds
|
|
338
|
+
* freely from there. Absent ⇒ the plane rests fully folded. Ignored by the
|
|
339
|
+
* PNG export, which unfolds everything (see `export.collapsed`). */
|
|
340
|
+
unfolded?: Record<string, string[]>;
|
|
341
|
+
/** where a VIEWER slid an edge label (Alt+drag in view mode), keyed like
|
|
342
|
+
* `planes`, then by relation id, then by label id (`legacy` for a relation's
|
|
343
|
+
* plain `label`). Overrides the label's own `t`/`side` on that plane. It
|
|
344
|
+
* lives here rather than on the relation for the same reason positions do:
|
|
345
|
+
* it is "where I put it in this picture", and a generated diagram has no
|
|
346
|
+
* model file the studio may write. Editing a label's position in the model
|
|
347
|
+
* drops the entry (see applyCommand), so the document never loses to it. */
|
|
348
|
+
edgeLabels?: Record<string, Record<string, Record<string, EdgeLabelPlacement>>>;
|
|
349
|
+
/** one threat bubble's state in this picture: where it was dragged (an offset
|
|
350
|
+
* from its automatic anchor beside the element) and whether it is open.
|
|
351
|
+
* Absent entry = automatic spot, closed. The threat text itself stays on the
|
|
352
|
+
* element; this is only "how I left the bubble in this picture", the
|
|
353
|
+
* `edgeLabels` reasoning. Keyed like `planes`, then by `threatTargetKey`. */
|
|
354
|
+
notes?: Record<string, Record<string, NotePlacement>>;
|
|
355
|
+
/** how the PNG export should differ from the interactive page. Ignored by the
|
|
356
|
+
* interactive page, which opens as `unfolded` says and lets the reader
|
|
357
|
+
* unfold what they want. */
|
|
358
|
+
export?: {
|
|
359
|
+
/** node ids kept folded in PNG export; ignored by the interactive page.
|
|
360
|
+
* The exporter unfolds every container to get a full overview, which makes a
|
|
361
|
+
* view with hundreds of leaves an unreadable thumbnail. Listing a container
|
|
362
|
+
* here folds it back up for the image only. Folding is the right tool
|
|
363
|
+
* (rather than the plane's `hides`) because a folded box still ANCHORS its
|
|
364
|
+
* hidden children's edges, where a hidden node drops them. Ids that are not
|
|
365
|
+
* containers have no effect. */
|
|
366
|
+
collapsed?: string[];
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** One freehand stroke from the studio's pen. Coordinates are ABSOLUTE flow
|
|
370
|
+
* coordinates at the top level of the diagram (never drilled): a stroke is an
|
|
371
|
+
* overlay on the canvas, not a property of a node, so a re-layout can slide
|
|
372
|
+
* boxes out from under it — the accepted trade for "draw anywhere". */
|
|
373
|
+
export interface Stroke {
|
|
374
|
+
id: string;
|
|
375
|
+
/** flat `[x0, y0, x1, y1, …]`, integer-rounded at capture; length ≥ 2 and even.
|
|
376
|
+
* A single point is a tap, drawn as a dot by round caps. */
|
|
377
|
+
points: number[];
|
|
378
|
+
/** any CSS color; absent → the theme's ink token (`--dg-ink`) */
|
|
379
|
+
color?: string;
|
|
380
|
+
/** flow px; absent → DEFAULT_STROKE_WIDTH */
|
|
381
|
+
width?: number;
|
|
382
|
+
}
|
|
383
|
+
/** `<name>.drawings.json` — the second thing kept out of the model, in its own
|
|
384
|
+
* file rather than the layout overlay so a box nudge and a scribble never land
|
|
385
|
+
* in one hunk (see .claude/specs/2026-08-23-drawings-sidecar-design.md).
|
|
386
|
+
* Keyed exactly like `LayoutOverlay.planes` (layoutPlaneKey). */
|
|
387
|
+
export interface Drawings {
|
|
388
|
+
version: 1;
|
|
389
|
+
planes: Record<string, Stroke[]>;
|
|
390
|
+
}
|
|
391
|
+
/** Pen width when a stroke names none. In core so editor and renderer cannot drift. */
|
|
392
|
+
export declare const DEFAULT_STROKE_WIDTH = 3;
|
|
393
|
+
export declare const BUILTIN_NOTATIONS: readonly ["causal-loop", "git-graph", "c4", "second-order", "fishbone", "threat-model"];
|
|
394
|
+
export type NotationId = (typeof BUILTIN_NOTATIONS)[number];
|
|
395
|
+
export type Polarity = '+' | '-';
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
2
|
+
export const FONT_SCALES = ['sm', 'md', 'lg'];
|
|
3
|
+
export const SIDES = ['top', 'right', 'bottom', 'left'];
|
|
4
|
+
export const RELATION_SHAPES = ['curved', 'straight', 'step'];
|
|
5
|
+
export const RELATION_LINES = ['solid', 'dashed', 'dotted'];
|
|
6
|
+
export const RELATION_MARKERS = ['arrow', 'dot', 'square', 'diamond', 'none'];
|
|
7
|
+
/** Default footprint for image nodes whose size is not known yet. Kept in
|
|
8
|
+
* core so the editor and the renderer cannot drift apart. */
|
|
9
|
+
export const DEFAULT_IMAGE_NODE_SIZE = { w: 160, h: 120 };
|
|
10
|
+
/** Sentinel id the renderer's elk wrapper uses for its synthetic layout root.
|
|
11
|
+
* Kept in core so validation can refuse a model node that would collide with
|
|
12
|
+
* it — the renderer and validate() must agree on the exact string. */
|
|
13
|
+
export const RESERVED_NODE_ID = '__root__';
|
|
14
|
+
/** Node types whose containment children cannot be re-homed when the container
|
|
15
|
+
* dies — an orphaned activity lane or git commit fails validation until undone.
|
|
16
|
+
* Deleting one of these cascades to its subtree; every other container severs
|
|
17
|
+
* only. Kept in core so the editor UI and the command algebra agree. */
|
|
18
|
+
export const CASCADE_DELETE_TYPES = ['activity-frame', 'branch'];
|
|
19
|
+
export const EDGE_LABEL_SIDES = ['top', 'bottom', 'center'];
|
|
20
|
+
export const STRIDE = ['S', 'T', 'R', 'I', 'D', 'E'];
|
|
21
|
+
export const THREAT_STATUSES = ['open', 'mitigated', 'accepted', 'not-applicable'];
|
|
22
|
+
export const THREAT_SEVERITIES = ['low', 'medium', 'high', 'critical'];
|
|
23
|
+
export const LEGEND_SECTIONS = ['layers', 'kinds', 'types'];
|
|
24
|
+
export const LEGEND_POSITIONS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
|
|
25
|
+
/** Pen width when a stroke names none. In core so editor and renderer cannot drift. */
|
|
26
|
+
export const DEFAULT_STROKE_WIDTH = 3;
|
|
27
|
+
export const BUILTIN_NOTATIONS = ['causal-loop', 'git-graph', 'c4', 'second-order', 'fishbone', 'threat-model'];
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the corresponding source lives, for the AGPL section 13 offer.
|
|
3
|
+
*
|
|
4
|
+
* Shared rather than written out at each use: it appears in the studio chrome and in
|
|
5
|
+
* every page `publish` stamps, and a stale URL in a licence notice is a compliance
|
|
6
|
+
* defect, not a cosmetic one. Core owns it because both a browser app and the CLI need
|
|
7
|
+
* it, and core is the only thing both already depend on.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SOURCE_URL = "https://github.com/Ferroman/diagc";
|
|
10
|
+
/** Message extraction for `unknown` catch values, replacing `(e as Error).message`. */
|
|
11
|
+
export declare function errMessage(e: unknown): string;
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the corresponding source lives, for the AGPL section 13 offer.
|
|
3
|
+
*
|
|
4
|
+
* Shared rather than written out at each use: it appears in the studio chrome and in
|
|
5
|
+
* every page `publish` stamps, and a stale URL in a licence notice is a compliance
|
|
6
|
+
* defect, not a cosmetic one. Core owns it because both a browser app and the CLI need
|
|
7
|
+
* it, and core is the only thing both already depend on.
|
|
8
|
+
*/
|
|
9
|
+
export const SOURCE_URL = 'https://github.com/Ferroman/diagc';
|
|
10
|
+
/** Message extraction for `unknown` catch values, replacing `(e as Error).message`. */
|
|
11
|
+
export function errMessage(e) {
|
|
12
|
+
return e instanceof Error ? e.message : String(e);
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DiagramModel } from './types.js';
|
|
2
|
+
export interface ValidationIssue {
|
|
3
|
+
code: 'duplicate-node' | 'reserved-node-id' | 'duplicate-layer' | 'duplicate-plane' | 'duplicate-relation' | 'containment-cycle' | 'dangling-endpoint' | 'unknown-layer' | 'unknown-plane' | 'unknown-hidden-node' | 'redundant-hide' | 'invalid-plane' | 'invalid-style' | 'invalid-legend' | 'invalid-image' | 'invalid-shape' | 'invalid-link' | 'invalid-key' | 'duplicate-key' | 'invalid-include' | 'unknown-notation' | 'invalid-polarity' | 'invalid-delay' | 'invalid-rich' | 'invalid-align' | 'invalid-font-scale' | 'invalid-edge-label' | 'duplicate-column' | 'unknown-column' | 'git-link-endpoints' | 'git-commit-lane' | 'git-parents' | 'git-cycle' | 'git-commit-outside-lane' | 'git-gap' | 'git-stage-span' | 'activity-lane-parent' | 'activity-frame-children' | 'activity-region-parent' | 'so-no-decision' | 'so-cycle' | 'so-unreachable' | 'so-contained' | 'fb-no-effect' | 'fb-many-effects' | 'fb-unattached' | 'fb-misplaced' | 'fb-too-deep' | 'fb-contained' | 'invalid-threats' | 'threat-id' | 'threat-title' | 'threat-category' | 'threat-status' | 'threat-severity' | 'tm-flow-boundary';
|
|
4
|
+
message: string;
|
|
5
|
+
ref?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class DiagramValidationError extends Error {
|
|
8
|
+
readonly issues: ValidationIssue[];
|
|
9
|
+
constructor(issues: ValidationIssue[]);
|
|
10
|
+
}
|
|
11
|
+
/** content-hashed asset filename shape (hex hash + extension); shared with the
|
|
12
|
+
* studio dev middleware's asset naming (`apps/studio/vite-plugins/handlers.ts`) */
|
|
13
|
+
export declare const IMAGE_REF: RegExp;
|
|
14
|
+
/** Bundled library icons served verbatim from apps/studio/public/library/<pack>/.
|
|
15
|
+
* A fixed, traversal-free namespace (never passed to readAsset, which uses IMAGE_REF). */
|
|
16
|
+
export declare const LIBRARY_IMAGE_REF: RegExp;
|
|
17
|
+
/** shared-identity keys are slugs; also the composed id of a merged node */
|
|
18
|
+
export declare const KEY_PATTERN: RegExp;
|
|
19
|
+
export declare function validate(m: DiagramModel): ValidationIssue[];
|