@luispm/zflow-graph 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 +21 -0
- package/README.md +287 -0
- package/SECURITY.md +67 -0
- package/dist/adapters/yjs.esm.js +238 -0
- package/dist/adapters/yjs.esm.min.js +2 -0
- package/dist/adapters/yjs.umd.js +246 -0
- package/dist/webgl-renderer.esm.js +376 -0
- package/dist/webgl-renderer.esm.min.js +2 -0
- package/dist/webgl-renderer.umd.js +384 -0
- package/dist/zflow.esm.js +4365 -0
- package/dist/zflow.esm.js.map +1 -0
- package/dist/zflow.esm.min.js +2 -0
- package/dist/zflow.umd.js +4375 -0
- package/dist/zflow.umd.js.map +1 -0
- package/dist/zflow.umd.min.js +2 -0
- package/dist/zflow.wasm +0 -0
- package/docs/01-getting-started.md +210 -0
- package/docs/02-runtime.md +257 -0
- package/docs/03-kinds.md +282 -0
- package/docs/04-performance.md +218 -0
- package/docs/05-plugins.md +235 -0
- package/docs/06-multiplayer.md +180 -0
- package/docs/07-recipes.md +340 -0
- package/docs/08-api.md +436 -0
- package/docs/README.md +61 -0
- package/package.json +100 -0
package/docs/08-api.md
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# 8 · API Reference
|
|
2
|
+
|
|
3
|
+
Every public method, grouped by capability. For event payloads, see [Events](#events).
|
|
4
|
+
|
|
5
|
+
## Lifecycle
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
static async ZFlow.create(opts) → ZFlow
|
|
9
|
+
```
|
|
10
|
+
Async constructor. Returns a ready ZFlow instance.
|
|
11
|
+
|
|
12
|
+
**Options:**
|
|
13
|
+
- `container: HTMLElement` (required) — host element
|
|
14
|
+
- `wasmUrl: string` OR `wasmBytes: Uint8Array | ArrayBuffer` (one required)
|
|
15
|
+
- `theme: 'dark' | 'light'` (default `'dark'`)
|
|
16
|
+
- `edgeStyle: 'bezier' | 'orthogonal'` (default `'bezier'`)
|
|
17
|
+
- `snapToGrid: boolean` (default `false`)
|
|
18
|
+
- `gridSize: number` (default `20`)
|
|
19
|
+
- `contextMenu: boolean` (default `true`)
|
|
20
|
+
- `keyboard: boolean` (default `true`) — install keyboard shortcuts
|
|
21
|
+
- `minimap: boolean` (default `false`)
|
|
22
|
+
- `animateEdges: boolean` (default `false`)
|
|
23
|
+
- `edgeFlowSpeed: number` (default `60`)
|
|
24
|
+
- `inlineMarkdown: boolean` (default `true`)
|
|
25
|
+
- `hoverPreview: boolean` (default `false`)
|
|
26
|
+
- `webglThreshold: number` (default `2000`) — auto-enable GL above this
|
|
27
|
+
- `stopOnError: boolean` (default `false`)
|
|
28
|
+
- `dblclickEditsTitle: boolean` (default `true`)
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
flow.dispose()
|
|
32
|
+
```
|
|
33
|
+
Detach all listeners, remove canvas, release WASM views.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Mutation
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
flow.addNode(spec) → number // returns node id or -1
|
|
41
|
+
flow.addEdge(spec) → number // returns edge id or -1
|
|
42
|
+
flow.deleteSelection()
|
|
43
|
+
flow.moveNode(id, x, y)
|
|
44
|
+
flow.duplicateSelection(dx = 40, dy = 40)
|
|
45
|
+
flow.addNodesBulk(specs) → number[] // bulk insert (returns ids in order)
|
|
46
|
+
flow.addEdgesBulk(specs) → number[]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**`addNode` spec fields:**
|
|
50
|
+
- `kind: string | number` — kind name or index
|
|
51
|
+
- `x`, `y` — world coords (default 0, 0)
|
|
52
|
+
- `w`, `h` — size (defaults from kind)
|
|
53
|
+
- `nin`, `nout` — override port counts
|
|
54
|
+
- `title`, `color`, `description`, `tags`, `status`, `progress`
|
|
55
|
+
- `image`, `checked`, `tasks`, `icon`, `links`
|
|
56
|
+
- `portIn`, `portOut` — per-node port label override
|
|
57
|
+
- `animate: false` — skip pop-in animation
|
|
58
|
+
|
|
59
|
+
**`addEdge` spec fields:**
|
|
60
|
+
- `from: number` — source node id (required)
|
|
61
|
+
- `to: number` — target node id (required)
|
|
62
|
+
- `fp: number` — source port index (default 0)
|
|
63
|
+
- `tp: number` — target port index (default 0)
|
|
64
|
+
- `label: string` — edge label
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Selection
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
flow.setSelected(id, on)
|
|
72
|
+
flow.toggleSelected(id)
|
|
73
|
+
flow.clearSelection()
|
|
74
|
+
flow.selectAll()
|
|
75
|
+
flow.getSelection() → number[]
|
|
76
|
+
flow.nodeCount() → number
|
|
77
|
+
flow.edgeCount() → number
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Rich content
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
flow.setNodeTitle(id, title)
|
|
86
|
+
flow.setNodeColor(id, hex)
|
|
87
|
+
flow.setNodeDescription(id, md) // supports inline markdown
|
|
88
|
+
flow.setNodeTags(id, tags) // string[]
|
|
89
|
+
flow.setNodeStatus(id, status) // 'ok' | 'running' | 'error' | 'warn' | 'idle' | ...
|
|
90
|
+
flow.setNodeProgress(id, p) // 0..1
|
|
91
|
+
flow.setNodeImage(id, url)
|
|
92
|
+
flow.setNodeChecked(id, bool)
|
|
93
|
+
flow.setNodeTasks(id, [{text, done}, ...])
|
|
94
|
+
flow.setNodeIcon(id, glyph)
|
|
95
|
+
flow.setNodeLinks(id, [{url, label}, ...])
|
|
96
|
+
flow.setPortInLabels(id, labels)
|
|
97
|
+
flow.setPortOutLabels(id, labels)
|
|
98
|
+
flow.setEdgeLabel(eid, label)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Visual
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
flow.setEdgeStyle('bezier' | 'orthogonal')
|
|
107
|
+
flow.setSnapToGrid(bool)
|
|
108
|
+
flow.setTheme('dark' | 'light')
|
|
109
|
+
flow.toggleTheme()
|
|
110
|
+
flow.setMinimap(bool)
|
|
111
|
+
flow.setHoverPreview(bool)
|
|
112
|
+
flow.setPathHighlight(bool)
|
|
113
|
+
flow.setEdgeAnimated(edgeId, bool)
|
|
114
|
+
flow.setAllEdgesAnimated(bool)
|
|
115
|
+
flow.setEdgeWaypoints(edgeId, points)
|
|
116
|
+
flow.clearEdgeWaypoints(edgeId)
|
|
117
|
+
|
|
118
|
+
flow.bringToFront(ids?)
|
|
119
|
+
flow.sendToBack(ids?)
|
|
120
|
+
flow.zoomTo(zoom)
|
|
121
|
+
flow.panTo(x, y)
|
|
122
|
+
flow.fitView(padding = 80)
|
|
123
|
+
flow.runAutoLayout() // Sugiyama hierarchical
|
|
124
|
+
flow.runForceLayout(maxFrames = 220)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Locks & read-only
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
flow.lockNode(id, on = true)
|
|
133
|
+
flow.isLocked(id) → boolean
|
|
134
|
+
flow.setReadOnly(on) // disables mutations
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Sticky notes
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
flow.addNote(x, y, text = '', opts = {}) → number // returns note id
|
|
143
|
+
flow.deleteNote(noteId)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Frames (groups)
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
flow.addFrame(x, y, w, h, label = 'Group', color = '#5b8def') → { id, ... }
|
|
152
|
+
flow.groupSelection(label = 'Group')
|
|
153
|
+
flow.deleteFrame(frameId)
|
|
154
|
+
flow.toggleFrameCollapse(frameIdx)
|
|
155
|
+
flow.isFrameCollapsed(frameIdx) → boolean
|
|
156
|
+
flow.enterSubflow(frameId) // dim everything outside
|
|
157
|
+
flow.exitSubflow()
|
|
158
|
+
flow.registerSubflowFromFrame(frameId, opts) → kindName
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Bookmarks
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
flow.setBookmark(slot, nodeId?) // slot = 1..9
|
|
167
|
+
flow.jumpBookmark(slot)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Multi-cursor presence
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
flow.setRemoteCursor(userId, x, y, name?, color?)
|
|
176
|
+
flow.clearRemoteCursors()
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
(Wire to your real CRDT via the [Yjs adapter](./06-multiplayer.md) instead of calling these directly.)
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Kinds
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
flow.registerKind(spec) → number // returns kind index
|
|
187
|
+
flow.setKindExecutor(kindName, fn) → previousFn
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Spec fields: see [Designing Kinds](./03-kinds.md).
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Runtime
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
flow.run({ from?, filter?, signal? }) → Promise<{ executed, errors, values }>
|
|
198
|
+
flow.runFrom(nodeId) → Promise<result>
|
|
199
|
+
flow.runFrame(frameId) → Promise<result>
|
|
200
|
+
flow.stop()
|
|
201
|
+
flow.startLoop(intervalMs = 500)
|
|
202
|
+
flow.stopLoop()
|
|
203
|
+
flow.isRunning() → boolean
|
|
204
|
+
flow.setRunStepDelay(ms)
|
|
205
|
+
flow.setMemoization(bool)
|
|
206
|
+
flow.clearRuntimeState()
|
|
207
|
+
|
|
208
|
+
flow.setNodeInput(id, outputs) // inject a node's output without running it
|
|
209
|
+
flow.setNodeParams(id, params)
|
|
210
|
+
flow.getNodeParams(id) → params
|
|
211
|
+
flow.getNodeValue(id) → output
|
|
212
|
+
|
|
213
|
+
flow.evalExpression(expr, extraScope?) → result
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Debugging
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
flow.setBreakpoint(id, on = true)
|
|
220
|
+
flow.toggleBreakpoint(id)
|
|
221
|
+
flow.clearBreakpoints()
|
|
222
|
+
flow.setStepMode(on)
|
|
223
|
+
flow.stepOver()
|
|
224
|
+
flow.resume()
|
|
225
|
+
flow.isPaused() → boolean
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Streaming metrics
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
flow.pushNodeMetric(id, value)
|
|
232
|
+
flow.clearNodeMetric(id)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Validation
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
flow.validateConnection(fromN, fp, toN, tp) → string | null // null = ok
|
|
241
|
+
flow.setConnectionValidator(fn)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Algorithms
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
flow.shortestPath(from, to) → edgeIds[]
|
|
250
|
+
flow.criticalPath() → edgeIds[]
|
|
251
|
+
flow.findSCCs() → nodeIds[][]
|
|
252
|
+
flow.findCycles() → edgeIds[]
|
|
253
|
+
flow.colorByDegree()
|
|
254
|
+
flow.clearNodeColors()
|
|
255
|
+
flow.setReachableFrom(nodeId)
|
|
256
|
+
flow.clearReachable()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Serialization
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
flow.toJSON() → object
|
|
265
|
+
flow.loadJSON(data)
|
|
266
|
+
flow.exportSVG() → string
|
|
267
|
+
flow.exportPNG() → Promise<Blob>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Imports
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
flow.importMermaid(text)
|
|
276
|
+
flow.importDot(text)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Plugins
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
flow.use(plugin) → dispose fn
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
See [Plugin System](./05-plugins.md).
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Search
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
flow.search(query) → nodeIds[]
|
|
295
|
+
flow.jumpToSearchHit(idx)
|
|
296
|
+
flow.clearSearch()
|
|
297
|
+
flow.openSearch() // opens the search UI
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Command palette
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
flow.openCommandPalette() // toggles
|
|
306
|
+
flow.registerTemplate(name, builder)
|
|
307
|
+
flow.insertTemplate(name, x, y) → id
|
|
308
|
+
flow.listTemplates() → string[]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Undo / redo
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
flow.undo()
|
|
317
|
+
flow.redo()
|
|
318
|
+
flow.snapshot() // manual save point
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## WebGL
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
await flow.enableWebGL(force = false) → bool
|
|
327
|
+
flow.disableWebGL()
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Inline editor
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
flow.editNodeExpression(nodeId, field = 'title') // 'title' | 'desc'
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Opens a floating editor with autocomplete for `{{node_X.value}}` expressions and live preview.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Drag-from-palette
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
flow.makeDraggable(domElement, spec)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Wires a DOM element so dragging it into the canvas creates a node of the given kind.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Events
|
|
353
|
+
|
|
354
|
+
```js
|
|
355
|
+
flow.on(event, callback) // returns nothing; pass same fn to remove
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| Event | Payload |
|
|
359
|
+
| ---------------------- | ---------------------------------------------- |
|
|
360
|
+
| `change` | (none) |
|
|
361
|
+
| `select` | nodeIds[] |
|
|
362
|
+
| `node:dblclick` | nodeId |
|
|
363
|
+
| `edge:dblclick` | edgeId |
|
|
364
|
+
| `canvas:dblclick` | { x, y } (world coords) |
|
|
365
|
+
| `theme` | 'dark' | 'light' |
|
|
366
|
+
| `renderer` | 'canvas2d' | 'webgl' |
|
|
367
|
+
| `connection:rejected` | { fromN, fromP, toN, toP, reason } |
|
|
368
|
+
| `plugin:installed` | plugin name or object |
|
|
369
|
+
| `palette:drop` | { id, x, y, spec } |
|
|
370
|
+
| `run:start` | { order: nodeIds[] } |
|
|
371
|
+
| `run:done` | { executed, errors, values } |
|
|
372
|
+
| `run:paused` | { nodeId } |
|
|
373
|
+
| `node:exec` | { id, inputs } |
|
|
374
|
+
| `node:emit` | { id, outputs } — per emission incl. streaming |
|
|
375
|
+
| `node:done` | { id, outputs } |
|
|
376
|
+
| `node:error` | { id, error } |
|
|
377
|
+
| `node:retry` | { id, attempt, error } |
|
|
378
|
+
| `node:cached` | { id } |
|
|
379
|
+
| `node:log` | { id, args } |
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Direct WASM access
|
|
384
|
+
|
|
385
|
+
For advanced cases you can call WASM exports directly via `flow.w.*`:
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
flow.w.hitTestNode(x, y) → nodeId | -1
|
|
389
|
+
flow.w.hitTestPort(x, y, tol) → packed | -1
|
|
390
|
+
flow.w.queryRect(minX, minY, maxX, maxY) → count
|
|
391
|
+
flow.w.nodeCount_() → number
|
|
392
|
+
flow.w.edgeCount_() → number
|
|
393
|
+
flow.w.nodeCap() → number
|
|
394
|
+
flow.w.edgeCap() → number
|
|
395
|
+
flow.w.snapshot()
|
|
396
|
+
flow.w.undo() → 0 | 1
|
|
397
|
+
flow.w.redo() → 0 | 1
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`flow.V` exposes the typed-array views:
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
flow.V.posX // Float32Array (length = cap)
|
|
404
|
+
flow.V.posY
|
|
405
|
+
flow.V.sizeW
|
|
406
|
+
flow.V.sizeH
|
|
407
|
+
flow.V.kind // Uint8Array
|
|
408
|
+
flow.V.nIn
|
|
409
|
+
flow.V.nOut
|
|
410
|
+
flow.V.selected
|
|
411
|
+
flow.V.edgeFromN // Uint32Array
|
|
412
|
+
flow.V.edgeToN
|
|
413
|
+
flow.V.edgeFromP // Uint8Array
|
|
414
|
+
flow.V.edgeToP
|
|
415
|
+
flow.V.edgeSel
|
|
416
|
+
flow.V.queryRes // Uint32Array (results from queryRect)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Reading is always safe (zero-copy). Writing to these arrays bypasses event emission and dirty tracking — use the high-level methods unless you know what you're doing.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Constants
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
flow.kinds // array of kind specs in registration order
|
|
427
|
+
flow.kindByName // Map: name → index
|
|
428
|
+
flow.cam // { x, y, zoom } — read-write, but use panTo/zoomTo for animation
|
|
429
|
+
flow.canvas // the canvas element
|
|
430
|
+
flow.container // the host element
|
|
431
|
+
flow.options // mutable options object
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
That's the full surface. ~230 public methods + 16 events. Search by `Ctrl+F` in the docs to find anything.
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# zflow-graph · Documentation
|
|
2
|
+
|
|
3
|
+
Guides ordered from "I want to play" to "I want to ship".
|
|
4
|
+
|
|
5
|
+
## Tutorials (read in order)
|
|
6
|
+
1. [Getting Started](./01-getting-started.md) — install, first graph, first run · **15 min**
|
|
7
|
+
2. [The Runtime](./02-runtime.md) — make your graph actually compute things · **20 min**
|
|
8
|
+
3. [Designing Kinds](./03-kinds.md) — schemas, ports, async, retry, streaming · **20 min**
|
|
9
|
+
4. [Performance at Scale](./04-performance.md) — WebGL, bulk ops, LOD, 100k nodes · **15 min**
|
|
10
|
+
|
|
11
|
+
## Guides (read as needed)
|
|
12
|
+
5. [Plugin System](./05-plugins.md) — lifecycle hooks, recipes
|
|
13
|
+
6. [Multiplayer (Yjs)](./06-multiplayer.md) — real-time co-editing
|
|
14
|
+
7. [Recipes](./07-recipes.md) — worked examples you can paste
|
|
15
|
+
|
|
16
|
+
## Reference
|
|
17
|
+
8. [API Reference](./08-api.md) — every public method, every event
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What zflow-graph actually is
|
|
22
|
+
|
|
23
|
+
A graph editor *and* a runtime in one ES module. You can:
|
|
24
|
+
|
|
25
|
+
- **Build editors** — the user drags nodes, connects ports, edits properties
|
|
26
|
+
- **Run the graph** — each node has an `execute()` function; the runtime walks topology and propagates values through edges
|
|
27
|
+
- **Embed both** — your app is the editor for end-users *and* the runtime that runs their work
|
|
28
|
+
|
|
29
|
+
This is the same shape as n8n, ComfyUI, Unreal Blueprints, Scratch, Node-RED. The difference: zflow-graph is **a library**, not a product. It runs anywhere you have a DOM and a WASM-capable JS engine.
|
|
30
|
+
|
|
31
|
+
## When to use it
|
|
32
|
+
|
|
33
|
+
| You want to build… | Use zflow? |
|
|
34
|
+
| ------------------------------------------- | :--------: |
|
|
35
|
+
| A workflow automation tool (n8n / Zapier) | ✅ |
|
|
36
|
+
| A visual ML pipeline editor (ComfyUI clone) | ✅ |
|
|
37
|
+
| A live data dashboard with node graph | ✅ |
|
|
38
|
+
| A diagram editor (Drawio replacement) | ✅ |
|
|
39
|
+
| A game-engine blueprint editor | ✅ |
|
|
40
|
+
| A simple flowchart for docs | ⚠️ overkill |
|
|
41
|
+
| A whiteboard / sketching tool | ❌ (use tldraw) |
|
|
42
|
+
| A code diff visualizer | ❌ (use d3) |
|
|
43
|
+
|
|
44
|
+
## When NOT to use it
|
|
45
|
+
|
|
46
|
+
- You need IE11 support. zflow needs ES2020 and WASM.
|
|
47
|
+
- Your graph is purely static / read-only and < 100 nodes. SVG is fine for that.
|
|
48
|
+
- You need server-side rendering. zflow renders in the browser (or Electron/Tauri/WebView2 — same shape).
|
|
49
|
+
- You want to obfuscate the source against piracy. JavaScript isn't obfuscatable. See [SECURITY.md](../SECURITY.md).
|
|
50
|
+
|
|
51
|
+
## The mental model in 3 sentences
|
|
52
|
+
|
|
53
|
+
1. The graph lives in a **WASM core** (Zig). JS holds zero-copy views into it.
|
|
54
|
+
2. Each **node** has a **kind**. Kinds are templates: shape, color, ports, and an optional `execute()` body.
|
|
55
|
+
3. When you call `flow.run()`, the runtime walks nodes in topological order, calls `execute(ctx, inputs)`, and propagates outputs through edges.
|
|
56
|
+
|
|
57
|
+
Everything else — multiplayer, undo, layouts, sub-flows, animations — is built on those three primitives.
|
|
58
|
+
|
|
59
|
+
## A note on the docs style
|
|
60
|
+
|
|
61
|
+
These are **executable docs**. Every code block is a real snippet you can paste into an HTML file with zflow imported. No pseudocode. If a snippet doesn't work as written, that's a bug in the docs or the library — report it.
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@luispm/zflow-graph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "WASM-powered node-edge graph editor + runtime. No framework. 100k nodes at 60fps. Built-in execution engine, Yjs multiplayer, WebGL renderer.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/zflow.umd.js",
|
|
7
|
+
"module": "./dist/zflow.esm.js",
|
|
8
|
+
"browser": "./dist/zflow.umd.min.js",
|
|
9
|
+
"unpkg": "./dist/zflow.umd.min.js",
|
|
10
|
+
"jsdelivr": "./dist/zflow.umd.min.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/zflow.esm.js",
|
|
14
|
+
"require": "./dist/zflow.umd.js",
|
|
15
|
+
"default": "./dist/zflow.esm.js"
|
|
16
|
+
},
|
|
17
|
+
"./webgl": {
|
|
18
|
+
"import": "./dist/webgl-renderer.esm.js",
|
|
19
|
+
"require": "./dist/webgl-renderer.umd.js"
|
|
20
|
+
},
|
|
21
|
+
"./adapters/yjs": {
|
|
22
|
+
"import": "./dist/adapters/yjs.esm.js",
|
|
23
|
+
"require": "./dist/adapters/yjs.umd.js"
|
|
24
|
+
},
|
|
25
|
+
"./wasm": "./dist/zflow.wasm",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/zflow.esm.js",
|
|
30
|
+
"dist/zflow.esm.js.map",
|
|
31
|
+
"dist/zflow.esm.min.js",
|
|
32
|
+
"dist/zflow.esm.min.js.map",
|
|
33
|
+
"dist/zflow.umd.js",
|
|
34
|
+
"dist/zflow.umd.js.map",
|
|
35
|
+
"dist/zflow.umd.min.js",
|
|
36
|
+
"dist/zflow.umd.min.js.map",
|
|
37
|
+
"dist/zflow.wasm",
|
|
38
|
+
"dist/webgl-renderer.esm.js",
|
|
39
|
+
"dist/webgl-renderer.esm.min.js",
|
|
40
|
+
"dist/webgl-renderer.umd.js",
|
|
41
|
+
"dist/adapters/yjs.esm.js",
|
|
42
|
+
"dist/adapters/yjs.esm.min.js",
|
|
43
|
+
"dist/adapters/yjs.umd.js",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"SECURITY.md",
|
|
47
|
+
"docs/"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build:wasm": "zig build",
|
|
51
|
+
"build:js": "rollup -c",
|
|
52
|
+
"build": "npm run build:wasm && npm run build:js",
|
|
53
|
+
"dev": "rollup -c -w",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"prepublishOnly": "npm run test && npm run build",
|
|
58
|
+
"serve": "python -m http.server 8765"
|
|
59
|
+
},
|
|
60
|
+
"keywords": [
|
|
61
|
+
"graph",
|
|
62
|
+
"editor",
|
|
63
|
+
"node",
|
|
64
|
+
"edge",
|
|
65
|
+
"diagram",
|
|
66
|
+
"flowchart",
|
|
67
|
+
"react-flow-alternative",
|
|
68
|
+
"wasm",
|
|
69
|
+
"webgl",
|
|
70
|
+
"visual-programming",
|
|
71
|
+
"no-code",
|
|
72
|
+
"workflow",
|
|
73
|
+
"canvas",
|
|
74
|
+
"interactive"
|
|
75
|
+
],
|
|
76
|
+
"author": "Luis G. <luis.padre21@gmail.com>",
|
|
77
|
+
"license": "MIT",
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "https://github.com/luisg/zflow-graph"
|
|
81
|
+
},
|
|
82
|
+
"homepage": "https://github.com/luisg/zflow-graph#readme",
|
|
83
|
+
"bugs": {
|
|
84
|
+
"url": "https://github.com/luisg/zflow-graph/issues"
|
|
85
|
+
},
|
|
86
|
+
"engines": {
|
|
87
|
+
"node": ">=18"
|
|
88
|
+
},
|
|
89
|
+
"sideEffects": false,
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
92
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
93
|
+
"jsdom": "^24.0.0",
|
|
94
|
+
"rollup": "^4.18.0",
|
|
95
|
+
"vitest": "^1.6.0"
|
|
96
|
+
},
|
|
97
|
+
"peerDependenciesMeta": {
|
|
98
|
+
"yjs": { "optional": true }
|
|
99
|
+
}
|
|
100
|
+
}
|