@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/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
+ }