@jarenjs/mermaid 0.34.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 +282 -0
- package/dist/types/ast.d.ts +210 -0
- package/dist/types/component/index.d.ts +81 -0
- package/dist/types/errors.d.ts +27 -0
- package/dist/types/index.d.ts +97 -0
- package/dist/types/interactive.d.ts +36 -0
- package/dist/types/layout/flowchart.d.ts +12 -0
- package/dist/types/layout/sequence.d.ts +12 -0
- package/dist/types/layout/state.d.ts +19 -0
- package/dist/types/parser/class.d.ts +11 -0
- package/dist/types/parser/config.d.ts +61 -0
- package/dist/types/parser/er.d.ts +11 -0
- package/dist/types/parser/flowchart.d.ts +26 -0
- package/dist/types/parser/gantt.d.ts +11 -0
- package/dist/types/parser/index.d.ts +22 -0
- package/dist/types/parser/pie.d.ts +11 -0
- package/dist/types/parser/sequence.d.ts +16 -0
- package/dist/types/parser/state.d.ts +22 -0
- package/dist/types/plugin.d.ts +50 -0
- package/dist/types/render/error.d.ts +23 -0
- package/dist/types/render/flowchart.d.ts +18 -0
- package/dist/types/render/index.d.ts +23 -0
- package/dist/types/render/misc.d.ts +54 -0
- package/dist/types/render/sequence.d.ts +16 -0
- package/dist/types/styles.d.ts +81 -0
- package/dist/types/theme.d.ts +47 -0
- package/dist/types/to-mermaid.d.ts +20 -0
- package/dist/types/utils.d.ts +47 -0
- package/docs/MERMAID-FORMAT.md +242 -0
- package/package.json +84 -0
- package/schemas/jaren-mermaid-ast.schema.json +78 -0
- package/schemas/jaren-workflow.schema.json +28 -0
- package/src/ast.js +252 -0
- package/src/component/index.js +109 -0
- package/src/errors.js +35 -0
- package/src/index.js +155 -0
- package/src/interactive.js +244 -0
- package/src/layout/flowchart.js +352 -0
- package/src/layout/sequence.js +178 -0
- package/src/layout/state.js +65 -0
- package/src/parser/class.js +90 -0
- package/src/parser/config.js +215 -0
- package/src/parser/er.js +86 -0
- package/src/parser/flowchart.js +413 -0
- package/src/parser/gantt.js +49 -0
- package/src/parser/index.js +122 -0
- package/src/parser/pie.js +32 -0
- package/src/parser/sequence.js +156 -0
- package/src/parser/state.js +137 -0
- package/src/plugin.js +76 -0
- package/src/render/error.js +55 -0
- package/src/render/flowchart.js +249 -0
- package/src/render/index.js +93 -0
- package/src/render/misc.js +135 -0
- package/src/render/sequence.js +152 -0
- package/src/styles.js +181 -0
- package/src/theme.js +180 -0
- package/src/to-mermaid.js +317 -0
- package/src/utils.js +64 -0
- package/styles/mermaid.css +115 -0
- package/stylesheets/dag-to-flowchart.jslt.json +62 -0
- package/stylesheets/flowchart-to-dag.jslt.json +29 -0
- package/stylesheets/state-to-workflow.jslt.json +26 -0
- package/stylesheets/workflow-to-state.jslt.json +41 -0
package/src/ast.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The Mermaid AST vocabulary: node constructors and walkers.
|
|
4
|
+
*
|
|
5
|
+
* Every node is a plain JSON object born from a constructor so that all
|
|
6
|
+
* nodes of a type share one hidden class — property access stays
|
|
7
|
+
* monomorphic and the structural hash is deterministic (MERMAID-FORMAT
|
|
8
|
+
* §4). The AST is deliberately **geometry-free**:
|
|
9
|
+
* layout is a separate pass, so the AST is a faithful, lossless
|
|
10
|
+
* semantic model of the diagram's *meaning* — a directed graph
|
|
11
|
+
* (flowchart), an ordered interaction (sequence), a finite state machine
|
|
12
|
+
* (state) — that other engines project via JSLT without ever seeing a
|
|
13
|
+
* coordinate.
|
|
14
|
+
*
|
|
15
|
+
* Member order is fixed per constructor; keep it stable so the FNV-1a
|
|
16
|
+
* hash of `JSON.stringify` is reproducible and the round-trip printer
|
|
17
|
+
* (`to-mermaid.js`) is a fixed point.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** The diagram-document format version this package produces. */
|
|
21
|
+
export const MERMAID_VERSION = '0.1';
|
|
22
|
+
|
|
23
|
+
//#region envelope --------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} DiagramDocument
|
|
27
|
+
* @property {string} $mermaid format version
|
|
28
|
+
* @property {string} diagram diagram type (`flowchart`, `sequence`, …)
|
|
29
|
+
* @property {any} config plain-JSON config (theme, per-type options)
|
|
30
|
+
* @property {any} ast the type-specific, geometry-free AST
|
|
31
|
+
* @property {{ hash: string, direction: string|null, title: string|null }} meta
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Assemble the shared DiagramDocument envelope. Fresh per parse,
|
|
36
|
+
* never mutated after return — share-friendly for JSLT/patch.
|
|
37
|
+
* @param {string} diagram
|
|
38
|
+
* @param {any} config
|
|
39
|
+
* @param {any} ast
|
|
40
|
+
* @param {{ hash: string, direction: string|null, title: string|null }} meta
|
|
41
|
+
* @returns {DiagramDocument}
|
|
42
|
+
*/
|
|
43
|
+
export function diagramDocument(diagram, config, ast, meta) {
|
|
44
|
+
return { $mermaid: MERMAID_VERSION, diagram, config, ast, meta };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region flowchart -------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {'rect'|'round'|'stadium'|'subroutine'|'cylinder'|'circle'
|
|
52
|
+
* |'doublecircle'|'diamond'|'hexagon'|'parallelogram'|'parallelogram_alt'
|
|
53
|
+
* |'trapezoid'|'trapezoid_alt'|'asymmetric'} FlowShape
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A flowchart vertex.
|
|
58
|
+
* @param {string} id
|
|
59
|
+
* @param {string} label
|
|
60
|
+
* @param {FlowShape} shape
|
|
61
|
+
* @returns {{ id: string, label: string, shape: FlowShape }}
|
|
62
|
+
*/
|
|
63
|
+
export function flowNode(id, label, shape) {
|
|
64
|
+
return { id, label, shape };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A flowchart edge (link). `stroke` is the line style, `head`/`tail`
|
|
69
|
+
* the endpoint markers, `length` the dash count (preserved so the
|
|
70
|
+
* canonical printer is a fixed point), `label` the optional edge text.
|
|
71
|
+
* @param {string} from
|
|
72
|
+
* @param {string} to
|
|
73
|
+
* @param {'solid'|'thick'|'dotted'} stroke
|
|
74
|
+
* @param {'none'|'arrow'|'circle'|'cross'} head
|
|
75
|
+
* @param {'none'|'arrow'|'circle'|'cross'} tail
|
|
76
|
+
* @param {number} length
|
|
77
|
+
* @param {string|null} label
|
|
78
|
+
* @returns {object}
|
|
79
|
+
*/
|
|
80
|
+
export function flowEdge(from, to, stroke, head, tail, length, label) {
|
|
81
|
+
return { from, to, stroke, head, tail, length, label };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A subgraph grouping.
|
|
86
|
+
* @param {string} id
|
|
87
|
+
* @param {string} label
|
|
88
|
+
* @param {string|null} direction
|
|
89
|
+
* @param {string[]} nodes ids of member nodes
|
|
90
|
+
* @returns {object}
|
|
91
|
+
*/
|
|
92
|
+
export function flowSubgraph(id, label, direction, nodes) {
|
|
93
|
+
return { id, label, direction, nodes };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A `classDef` style class.
|
|
98
|
+
* @param {string} name
|
|
99
|
+
* @param {string} styles semicolon-separated CSS declarations
|
|
100
|
+
* @returns {object}
|
|
101
|
+
*/
|
|
102
|
+
export function flowClassDef(name, styles) {
|
|
103
|
+
return { name, styles };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A `class`/`:::` assignment of a style class to a node.
|
|
108
|
+
* @param {string} node
|
|
109
|
+
* @param {string} name
|
|
110
|
+
* @returns {object}
|
|
111
|
+
*/
|
|
112
|
+
export function flowClass(node, name) {
|
|
113
|
+
return { node, name };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A `style` inline-style assignment to a node.
|
|
118
|
+
* @param {string} node
|
|
119
|
+
* @param {string} styles
|
|
120
|
+
* @returns {object}
|
|
121
|
+
*/
|
|
122
|
+
export function flowStyle(node, styles) {
|
|
123
|
+
return { node, styles };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The flowchart AST root.
|
|
128
|
+
* @param {string} direction
|
|
129
|
+
* @param {object[]} nodes
|
|
130
|
+
* @param {object[]} edges
|
|
131
|
+
* @param {object[]} subgraphs
|
|
132
|
+
* @param {object[]} classDefs
|
|
133
|
+
* @param {object[]} classes
|
|
134
|
+
* @param {object[]} styles
|
|
135
|
+
* @returns {object}
|
|
136
|
+
*/
|
|
137
|
+
export function flowchartAst(direction, nodes, edges, subgraphs, classDefs, classes, styles) {
|
|
138
|
+
return { direction, nodes, edges, subgraphs, classDefs, classes, styles };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region sequence --------------------------------------------------
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* A declared participant/actor.
|
|
146
|
+
* @param {string} id
|
|
147
|
+
* @param {string} label
|
|
148
|
+
* @param {'participant'|'actor'} kind
|
|
149
|
+
* @returns {object}
|
|
150
|
+
*/
|
|
151
|
+
export function seqParticipant(id, label, kind) {
|
|
152
|
+
return { id, label, kind };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A message statement.
|
|
157
|
+
* @param {string} from
|
|
158
|
+
* @param {string} to
|
|
159
|
+
* @param {string} text
|
|
160
|
+
* @param {'solid'|'dotted'} line
|
|
161
|
+
* @param {'arrow'|'open'|'cross'|'point'} head
|
|
162
|
+
* @param {'activate'|'deactivate'|null} activation `+`/`-` shorthand
|
|
163
|
+
* @returns {object}
|
|
164
|
+
*/
|
|
165
|
+
export function seqMessage(from, to, text, line, head, activation) {
|
|
166
|
+
return { kind: 'message', from, to, text, line, head, activation };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A note statement.
|
|
171
|
+
* @param {'left of'|'right of'|'over'} placement
|
|
172
|
+
* @param {string[]} actors
|
|
173
|
+
* @param {string} text
|
|
174
|
+
* @returns {object}
|
|
175
|
+
*/
|
|
176
|
+
export function seqNote(placement, actors, text) {
|
|
177
|
+
return { kind: 'note', placement, actors, text };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* An explicit activate/deactivate statement.
|
|
182
|
+
* @param {'activate'|'deactivate'} kind
|
|
183
|
+
* @param {string} actor
|
|
184
|
+
* @returns {object}
|
|
185
|
+
*/
|
|
186
|
+
export function seqActivation(kind, actor) {
|
|
187
|
+
return { kind, actor };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A block statement (loop/opt/alt/par/critical/break). Single-branch
|
|
192
|
+
* blocks (loop/opt/critical/break) carry one branch; alt/par carry one
|
|
193
|
+
* per `else`/`and` section. `branches` is always present so the shape
|
|
194
|
+
* is monomorphic.
|
|
195
|
+
* @param {'loop'|'opt'|'alt'|'par'|'critical'|'break'} blockType
|
|
196
|
+
* @param {{ label: string, statements: object[] }[]} branches
|
|
197
|
+
* @returns {object}
|
|
198
|
+
*/
|
|
199
|
+
export function seqBlock(blockType, branches) {
|
|
200
|
+
return { kind: 'block', blockType, branches };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The sequence AST root.
|
|
205
|
+
* @param {object[]} participants explicitly declared participants
|
|
206
|
+
* @param {object[]} statements ordered interaction statements
|
|
207
|
+
* @param {boolean} autonumber
|
|
208
|
+
* @returns {object}
|
|
209
|
+
*/
|
|
210
|
+
export function sequenceAst(participants, statements, autonumber) {
|
|
211
|
+
return { participants, statements, autonumber };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region generic AST for secondary/placeholder diagrams ------------
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A geometry-free "lines" AST for diagram types that parse-accept but
|
|
219
|
+
* are not yet laid out (the secondary types). Preserves the raw body
|
|
220
|
+
* lines so `toMermaid` round-trips and the coverage scorecard can be
|
|
221
|
+
* honest.
|
|
222
|
+
* @param {string} diagram
|
|
223
|
+
* @param {string[]} lines
|
|
224
|
+
* @returns {object}
|
|
225
|
+
*/
|
|
226
|
+
export function rawAst(diagram, lines) {
|
|
227
|
+
return { diagram, lines };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region walkers ---------------------------------------------------
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Walk the ordered statements of a sequence AST (descending into block
|
|
235
|
+
* branches), calling `visitor(stmt)` pre-order.
|
|
236
|
+
* @param {object[]} statements
|
|
237
|
+
* @param {(stmt: any) => void} visitor
|
|
238
|
+
*/
|
|
239
|
+
export function walkSequence(statements, visitor) {
|
|
240
|
+
for (let i = 0; i < statements.length; i++) {
|
|
241
|
+
const stmt = statements[i];
|
|
242
|
+
visitor(stmt);
|
|
243
|
+
if (/** @type {any} */ (stmt).kind === 'block') {
|
|
244
|
+
const branches = /** @type {any} */ (stmt).branches;
|
|
245
|
+
for (let b = 0; b < branches.length; b++) {
|
|
246
|
+
walkSequence(branches[b].statements, visitor);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The Mermaid VISUAL COMPONENT — part two of the package.
|
|
4
|
+
*
|
|
5
|
+
* Everything below this line is presentation glue; the engine
|
|
6
|
+
* (`@jarenjs/mermaid`) neither knows nor needs any of it. Mirrors
|
|
7
|
+
* `@jarenjs/md`'s `createMdComponent` field-for-field:
|
|
8
|
+
*
|
|
9
|
+
* - `createMermaidComponent()` — a memoized `view()` projection for
|
|
10
|
+
* `@jarenjs/app` viewModels (reference-stable, so an unchanged source
|
|
11
|
+
* patches in O(1) — the O(change) contract), `effects` entries for
|
|
12
|
+
* the app effect registry (`mermaid-render`, `mermaid-load`), and a
|
|
13
|
+
* `hydrate()` pass kept for API symmetry (a no-op in v1: the render is
|
|
14
|
+
* already complete);
|
|
15
|
+
* - `styles/mermaid.css` — the component stylesheet.
|
|
16
|
+
*
|
|
17
|
+
* The boundary is one-way: the component imports the engine, never the
|
|
18
|
+
* reverse.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { createProjectionMemo } from '@jarenjs/view/helpers';
|
|
22
|
+
import { compileMermaid, diagramToVnode } from '../index.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} MermaidComponentOptions
|
|
26
|
+
* @property {any} [theme] theme name or override object
|
|
27
|
+
* @property {number} [memoLimit] LRU size for the source-string memo (default 32)
|
|
28
|
+
* @property {string | URL} [base] base URL for `mermaid-load`
|
|
29
|
+
* @property {typeof globalThis.fetch} [fetch] fetch implementation for `mermaid-load`
|
|
30
|
+
* @property {(err: any) => void} [onHydrateError]
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {object} MermaidComponent
|
|
34
|
+
* @property {(source: string) => any} compile memoized compile
|
|
35
|
+
* @property {(sourceOrDoc: any) => any} view memoized vnode projection
|
|
36
|
+
* @property {Record<string, (props: any, dispatch: any) => any>} effects
|
|
37
|
+
* @property {(container: any) => void} hydrate no-op in v1
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create the Mermaid component.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const mermaid = createMermaidComponent();
|
|
45
|
+
* createApp(appDoc, {
|
|
46
|
+
* effects: { ...mermaid.effects },
|
|
47
|
+
* viewModel: (state) => ({ ...state, diagram: mermaid.view(state.source) }),
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* @param {MermaidComponentOptions} [options]
|
|
51
|
+
* @returns {MermaidComponent}
|
|
52
|
+
*/
|
|
53
|
+
export function createMermaidComponent(options = {}) {
|
|
54
|
+
const compileOptions = { theme: options.theme };
|
|
55
|
+
|
|
56
|
+
const { compile, view } = createProjectionMemo({
|
|
57
|
+
memoLimit: options.memoLimit ?? 32,
|
|
58
|
+
compile: (source) => compileMermaid(source, compileOptions),
|
|
59
|
+
toVnode: (compiled) => compiled.toVnode(),
|
|
60
|
+
docToVnode: (doc) => diagramToVnode(doc, compileOptions),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
compile,
|
|
65
|
+
|
|
66
|
+
view,
|
|
67
|
+
|
|
68
|
+
effects: {
|
|
69
|
+
/**
|
|
70
|
+
* Render an in-state source to an SVG vnode and dispatch it:
|
|
71
|
+
* `{ run: 'mermaid-render', with: { source, done } }`.
|
|
72
|
+
*/
|
|
73
|
+
'mermaid-render': (props, dispatch) => {
|
|
74
|
+
dispatch(props.done, compile(String(props.source ?? '')).toVnode());
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Fetch a `.mmd`/`.mermaid` URL and dispatch the DiagramDocument:
|
|
79
|
+
* `{ run: 'mermaid-load', with: { url, done, error? } }`.
|
|
80
|
+
*/
|
|
81
|
+
'mermaid-load': (props, dispatch) => {
|
|
82
|
+
const f = options.fetch ?? globalThis.fetch;
|
|
83
|
+
const url = options.base ? new URL(String(props.url), options.base).href : String(props.url);
|
|
84
|
+
return f(url).then(
|
|
85
|
+
(res) => {
|
|
86
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
|
|
87
|
+
return res.text();
|
|
88
|
+
},
|
|
89
|
+
).then(
|
|
90
|
+
(text) => dispatch(props.done, compile(text).doc),
|
|
91
|
+
(err) => {
|
|
92
|
+
if (props.error !== undefined) {
|
|
93
|
+
dispatch(props.error, { url, message: String(err?.message ?? err) });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Render is complete; hydrate is reserved for optional
|
|
104
|
+
// client-only enhancements (pan/zoom) that are out of scope for v1.
|
|
105
|
+
hydrate() {
|
|
106
|
+
/* no-op */
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The engine's one error type. Parsers raise it with a 1-based
|
|
4
|
+
* `line`/`column`; the render path (`diagramToVnode`/`renderMermaid`)
|
|
5
|
+
* catches it and emits a clear error vnode instead of throwing, so
|
|
6
|
+
* rendering is always total.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class MermaidParseError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} message
|
|
12
|
+
* @param {number} [line] 1-based line number
|
|
13
|
+
* @param {number} [column] 1-based column number
|
|
14
|
+
*/
|
|
15
|
+
constructor(message, line = 0, column = 0) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'MermaidParseError';
|
|
18
|
+
/** @type {number} */
|
|
19
|
+
this.line = line;
|
|
20
|
+
/** @type {number} */
|
|
21
|
+
this.column = column;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The `fail(message, position)` idiom from `packages/json/src/path.js`,
|
|
27
|
+
* adapted to line/column. Throws; never returns.
|
|
28
|
+
* @param {string} message
|
|
29
|
+
* @param {number} line
|
|
30
|
+
* @param {number} [column]
|
|
31
|
+
* @returns {never}
|
|
32
|
+
*/
|
|
33
|
+
export function fail(message, line, column = 0) {
|
|
34
|
+
throw new MermaidParseError(message, line, column);
|
|
35
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file `@jarenjs/mermaid` — a native, headless Mermaid clone. This is
|
|
4
|
+
* the **engine** (part one): pure functions over data — text ⇄ AST ⇄
|
|
5
|
+
* pure-vnode SVG — that know only the `@jarenjs/view` vnode shape. It
|
|
6
|
+
* imports nothing from the component, `@jarenjs/app`, the DOM or
|
|
7
|
+
* `@jarenjs/md` (the two-layer rule).
|
|
8
|
+
*
|
|
9
|
+
* The pipeline mirrors `@jarenjs/md`:
|
|
10
|
+
*
|
|
11
|
+
* source ──parseMermaid──▶ DiagramDocument (geometry-free JSON AST)
|
|
12
|
+
* │
|
|
13
|
+
* ┌─────────────────┼───────────────────┐
|
|
14
|
+
* ▼ ▼ ▼
|
|
15
|
+
* toMermaid() layoutDiagram() JSLT / query
|
|
16
|
+
* (canonical → diagramToVnode() (the AST is an
|
|
17
|
+
* round-trip) (pure-vnode SVG) ordinary document)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { renderToString, createDomRenderer } from '@jarenjs/view';
|
|
21
|
+
import { parseMermaid } from './parser/index.js';
|
|
22
|
+
import { toMermaid } from './to-mermaid.js';
|
|
23
|
+
import { diagramToVnode, layoutDiagram } from './render/index.js';
|
|
24
|
+
import { hashContent } from './utils.js';
|
|
25
|
+
|
|
26
|
+
export { parseMermaid } from './parser/index.js';
|
|
27
|
+
export { toMermaid } from './to-mermaid.js';
|
|
28
|
+
export { parseMermaidConfig } from './parser/config.js';
|
|
29
|
+
export { layoutDiagram, diagramToVnode } from './render/index.js';
|
|
30
|
+
export { createTheme, THEMES, HOST_VARS } from './theme.js';
|
|
31
|
+
export { sanitizeHref } from '@jarenjs/view/helpers';
|
|
32
|
+
export { MermaidParseError } from './errors.js';
|
|
33
|
+
export { hashContent } from './utils.js';
|
|
34
|
+
export {
|
|
35
|
+
MERMAID_VERSION, diagramDocument, walkSequence,
|
|
36
|
+
flowNode, flowEdge, sequenceAst, flowchartAst,
|
|
37
|
+
} from './ast.js';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convenience: parse → layout → render, error-safe, in one call.
|
|
41
|
+
* @param {string} source
|
|
42
|
+
* @param {{ theme?: any }} [options]
|
|
43
|
+
* @returns {any} an SVG vnode
|
|
44
|
+
*/
|
|
45
|
+
export function renderMermaid(source, options = {}) {
|
|
46
|
+
return diagramToVnode(source, options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {object} CompiledMermaid
|
|
51
|
+
* @property {import('./ast.js').DiagramDocument} doc the parsed document
|
|
52
|
+
* @property {() => any} toVnode cached pure-vnode SVG
|
|
53
|
+
* @property {() => string} toSvgString cached standalone SVG string (SSR)
|
|
54
|
+
* @property {() => string} toText canonical Mermaid text (`toMermaid`)
|
|
55
|
+
* @property {(visitor: (stmt: any) => void) => void} walk walk the AST
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parse once and return a bundle of cached projections.
|
|
60
|
+
* `toVnode`/`toSvgString`/`toText` each compute at most once; the
|
|
61
|
+
* vnode is returned by reference on repeat calls, so an unchanged
|
|
62
|
+
* document patches in O(1) through the view reconciler.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} source
|
|
65
|
+
* @param {{ theme?: any, [k: string]: any }} [options]
|
|
66
|
+
* @returns {CompiledMermaid}
|
|
67
|
+
*/
|
|
68
|
+
export function compileMermaid(source, options = {}) {
|
|
69
|
+
let doc = null;
|
|
70
|
+
let parseError = null;
|
|
71
|
+
try {
|
|
72
|
+
doc = parseMermaid(source, options);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
parseError = err;
|
|
76
|
+
}
|
|
77
|
+
let vnode;
|
|
78
|
+
let svg;
|
|
79
|
+
let text;
|
|
80
|
+
let layout;
|
|
81
|
+
return {
|
|
82
|
+
doc,
|
|
83
|
+
parseError,
|
|
84
|
+
toVnode() {
|
|
85
|
+
// Error-safe: a parse failure renders the error vnode, which
|
|
86
|
+
// `diagramToVnode` produces when handed the original source.
|
|
87
|
+
if (vnode === undefined) vnode = diagramToVnode(doc ?? source, options);
|
|
88
|
+
return vnode;
|
|
89
|
+
},
|
|
90
|
+
toSvgString() {
|
|
91
|
+
if (svg === undefined) svg = renderToString(this.toVnode());
|
|
92
|
+
return svg;
|
|
93
|
+
},
|
|
94
|
+
toText() {
|
|
95
|
+
if (text === undefined) text = doc !== null ? toMermaid(doc) : source;
|
|
96
|
+
return text;
|
|
97
|
+
},
|
|
98
|
+
toLayout() {
|
|
99
|
+
// The editor's hit-testing substrate: the pure PositionedDiagram
|
|
100
|
+
// (nodes with x/y/w/h, edges with routed points), cached like the
|
|
101
|
+
// other projections — same compiled document, reference-equal
|
|
102
|
+
// scene. Null for types without a geometric layout, and for
|
|
103
|
+
// documents that failed to parse.
|
|
104
|
+
if (layout === undefined) layout = doc !== null ? layoutDiagram(doc) : null;
|
|
105
|
+
return layout;
|
|
106
|
+
},
|
|
107
|
+
walk(visitor) {
|
|
108
|
+
if (doc !== null) walkDoc(doc, visitor);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A host-DOM renderer (mirrors `@jarenjs/md`'s `createMdRenderer`).
|
|
115
|
+
* Owns a `@jarenjs/view` DOM renderer over `container` and patches the
|
|
116
|
+
* rendered SVG on each `render(docOrSource)` call. `createDomRenderer`
|
|
117
|
+
* touches no DOM until it is handed a container, so importing it keeps
|
|
118
|
+
* the engine host-agnostic.
|
|
119
|
+
*
|
|
120
|
+
* @param {{ container: any, document?: any, theme?: any, [k: string]: any }} config
|
|
121
|
+
* @returns {(docOrSource: any) => void}
|
|
122
|
+
*/
|
|
123
|
+
export function createMermaidRenderer(config) {
|
|
124
|
+
const render = createDomRenderer(config.container, { document: config.document });
|
|
125
|
+
return (docOrSource) => {
|
|
126
|
+
render(diagramToVnode(docOrSource, config));
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Walk a document's AST statements (sequence blocks descend).
|
|
132
|
+
* @param {import('./ast.js').DiagramDocument} doc
|
|
133
|
+
* @param {(stmt: any) => void} visitor
|
|
134
|
+
*/
|
|
135
|
+
function walkDoc(doc, visitor) {
|
|
136
|
+
const ast = doc.ast;
|
|
137
|
+
if (doc.diagram === 'sequence') {
|
|
138
|
+
const walk = (stmts) => {
|
|
139
|
+
for (const s of stmts) {
|
|
140
|
+
visitor(s);
|
|
141
|
+
if (s.kind === 'block') for (const b of s.branches) walk(b.statements);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
walk(ast.statements);
|
|
145
|
+
}
|
|
146
|
+
else if (doc.diagram === 'flowchart') {
|
|
147
|
+
for (const n of ast.nodes) visitor(n);
|
|
148
|
+
for (const e of ast.edges) visitor(e);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
visitor(ast);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { hashContent as contentHash };
|