@particle-academy/fancy-flow 0.72.0 → 0.72.1
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/dist/{chunk-57QTX3S3.js → chunk-3RXQGYGH.js} +3 -3
- package/dist/{chunk-57QTX3S3.js.map → chunk-3RXQGYGH.js.map} +1 -1
- package/dist/{chunk-RQP666FB.js → chunk-7DJ56PUA.js} +4 -4
- package/dist/{chunk-RQP666FB.js.map → chunk-7DJ56PUA.js.map} +1 -1
- package/dist/{chunk-C5K2DSZB.js → chunk-EX34GCKK.js} +7 -5
- package/dist/chunk-EX34GCKK.js.map +1 -0
- package/dist/{chunk-ZEDBF2V6.js → chunk-GQ5LEA7Q.js} +3 -3
- package/dist/{chunk-ZEDBF2V6.js.map → chunk-GQ5LEA7Q.js.map} +1 -1
- package/dist/{chunk-E3U3MBKI.js → chunk-K7KO3UJ3.js} +3 -3
- package/dist/{chunk-E3U3MBKI.js.map → chunk-K7KO3UJ3.js.map} +1 -1
- package/dist/{chunk-R3GQ72WA.js → chunk-LYYQO7EY.js} +4 -4
- package/dist/{chunk-R3GQ72WA.js.map → chunk-LYYQO7EY.js.map} +1 -1
- package/dist/{chunk-6TC44VCY.js → chunk-VAF2SWTC.js} +3 -3
- package/dist/{chunk-6TC44VCY.js.map → chunk-VAF2SWTC.js.map} +1 -1
- package/dist/durable/index.d.cts +45 -12
- package/dist/durable/index.d.ts +45 -12
- package/dist/durable.cjs +23 -11
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +19 -10
- package/dist/durable.js.map +1 -1
- package/dist/engine.cjs +5 -3
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +2 -2
- package/dist/engine.d.ts +2 -2
- package/dist/engine.js +4 -4
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +12 -12
- package/dist/registry.cjs +5 -3
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +2 -2
- package/dist/{run-cohort-DL9JzzPU.d.cts → run-cohort-DNpPa0mq.d.cts} +1 -1
- package/dist/{run-cohort-CjUBWg6X.d.ts → run-cohort-rgT_FX3V.d.ts} +1 -1
- package/dist/{run-flow-CxEGBOxd.d.ts → run-flow-BLP9rXfO.d.ts} +22 -1
- package/dist/{run-flow-B_8hgO5_.d.cts → run-flow-pW0PllaZ.d.cts} +22 -1
- package/dist/runtime/index.d.cts +3 -3
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime.cjs +5 -3
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +3 -3
- package/dist/schema.cjs +5 -3
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +3 -3
- package/dist/screens.cjs +5 -3
- package/dist/screens.cjs.map +1 -1
- package/dist/screens.js +4 -4
- package/dist/ux.cjs +5 -3
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-C5K2DSZB.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analysis/graph-connectivity.ts"],"names":[],"mappings":";;;AAsCO,SAAS,uBAAuB,KAAA,EAAiC;AACtE,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAM,GAAI,KAAA;AACzB,EAAA,MAAM,SAAwB,EAAC;AAE/B,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AACpC,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AACpC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,WAAA,CAAY,GAAA,CAAI,KAAK,MAAM,CAAA;AAC3B,IAAA,WAAA,CAAY,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,EAC7B;AAMA,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,KAAW,CAAA;AAEhC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,MAAA,IAAU,QAAA,CAAS,IAAI,CAAA,EAAG;AAE9B,IAAA,IAAI,CAAC,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA,EAAG;AAC1D,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,KAAA,EAAO,OAAA;AAAA,QACP,QAAQ,IAAA,CAAK,EAAA;AAAA,QACb,OAAA,EACE,CAAA,MAAA,EAAS,IAAA,CAAK,EAAE,CAAA,oSAAA;AAAA,OAInB,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC,CAAC,CAAA;AAEhD,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAEtC,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,KAAA,EAAO,OAAA;AAAA,MACP,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,OAAA,EACE,SAAS,IAAA,CAAK,EAAE,iBAAiB,IAAA,CAAK,MAAM,CAAA,6JAAA,EAET,IAAA,CAAK,MAAM,CAAA,kCAAA;AAAA,KACjD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,MAAA;AACT;AAyBO,SAAS,SAAS,IAAA,EAAyB;AAChD,EAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAClB,EAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,EAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,IAAA;AAE5B,EAAA,MAAM,IAAA,GAAO,YAAY,IAAI,CAAA;AAC7B,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,EAAA,OAAO,OAAA,CAAQ,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,KAAK,IAAA,CAAK,QAAA,KAAa,YAAA,IAAgB,IAAA,CAAK,QAAA,KAAa,QAAA;AAC/F;AAWA,SAAS,aAAa,IAAA,EAAyB;AAM7C,EAAA,MAAM,GAAA,GAAO,KAAK,IAAA,EAA8C,OAAA;AAChE,EAAA,IAAI,MAAM,OAAA,CAAQ,GAAG,CAAA,EAAG,OAAO,IAAI,MAAA,KAAW,CAAA;AAE9C,EAAA,MAAM,OAAO,IAAA,CAAK,IAAA,GAAO,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AAKlD,EAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAElB,EAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA,IAAK,IAAA,CAAK,QAAQ,MAAA,KAAW,CAAA;AAChE","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/analysis/graph-connectivity.ts"],"names":[],"mappings":";;;AAsCO,SAAS,uBAAuB,KAAA,EAAiC;AACtE,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAM,GAAI,KAAA;AACzB,EAAA,MAAM,SAAwB,EAAC;AAE/B,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AACpC,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AACpC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,WAAA,CAAY,GAAA,CAAI,KAAK,MAAM,CAAA;AAC3B,IAAA,WAAA,CAAY,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,EAC7B;AAMA,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,KAAW,CAAA;AAEhC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,MAAA,IAAU,QAAA,CAAS,IAAI,CAAA,EAAG;AAE9B,IAAA,IAAI,CAAC,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA,EAAG;AAC1D,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,KAAA,EAAO,OAAA;AAAA,QACP,QAAQ,IAAA,CAAK,EAAA;AAAA,QACb,OAAA,EACE,CAAA,MAAA,EAAS,IAAA,CAAK,EAAE,CAAA,oSAAA;AAAA,OAInB,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC,CAAC,CAAA;AAEhD,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAEtC,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,KAAA,EAAO,OAAA;AAAA,MACP,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,OAAA,EACE,SAAS,IAAA,CAAK,EAAE,iBAAiB,IAAA,CAAK,MAAM,CAAA,6JAAA,EAET,IAAA,CAAK,MAAM,CAAA,kCAAA;AAAA,KACjD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,MAAA;AACT;AAyBO,SAAS,SAAS,IAAA,EAAyB;AAChD,EAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAClB,EAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,EAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,IAAA;AAE5B,EAAA,MAAM,IAAA,GAAO,YAAY,IAAI,CAAA;AAC7B,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,EAAA,OAAO,OAAA,CAAQ,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,KAAK,IAAA,CAAK,QAAA,KAAa,YAAA,IAAgB,IAAA,CAAK,QAAA,KAAa,QAAA;AAC/F;AAWA,SAAS,aAAa,IAAA,EAAyB;AAM7C,EAAA,MAAM,GAAA,GAAO,KAAK,IAAA,EAA8C,OAAA;AAChE,EAAA,IAAI,MAAM,OAAA,CAAQ,GAAG,CAAA,EAAG,OAAO,IAAI,MAAA,KAAW,CAAA;AAE9C,EAAA,MAAM,OAAO,IAAA,CAAK,IAAA,GAAO,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AAKlD,EAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAElB,EAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA,IAAK,IAAA,CAAK,QAAQ,MAAA,KAAW,CAAA;AAChE","file":"chunk-VAF2SWTC.js","sourcesContent":["import { getNodeKind, kindIds } from \"../registry/registry\";\nimport type { FlowGraph, FlowNode } from \"../types\";\nimport type { ImportIssue } from \"../schema/workflow-schema\";\n\n/**\n * Refuse a graph whose nodes cannot take part in the workflow's dataflow.\n *\n * Two shapes, both of which import cleanly and then quietly do nothing. Neither\n * FAILS — which is what makes them worth refusing at authoring time, because a\n * run that reports success is the worst way for a workflow to be wrong. Both\n * were measured against the PHP twin's engine before this was written, and both\n * behave the same way here:\n *\n * ## 1. A FLOATING node — no inbound and no outbound edge\n *\n * It is NOT skipped. A node with no incoming edge is a root, so the topo sort\n * runs it: a three-node graph with one floating `log` executed `t,lonely,o`. It\n * runs disconnected — receiving nothing from the graph and reaching nobody in\n * it — which is precisely the state an author cannot see on a canvas.\n *\n * ## 2. An edge leaving a TERMINATOR\n *\n * A terminal kind — `output`, `log` — declares an EMPTY output port list. It\n * ends a chain. Measured: `t -> output -> log` imported clean and the `log` DID\n * run, with `{{ input }}` resolving to `\"\"`. `collectInputs` binds a payload\n * only when `\"<sourceId>:<handle>\"` exists, and a node publishing no ports never\n * creates that key — so the edge does not fail, it delivers nothing, and the\n * node downstream operates on a hole.\n *\n * That is the same silent-nothing the undelivered-edge diagnostic reports at run\n * time, but this one is decidable FROM THE DOCUMENT ALONE.\n *\n * ## What may float\n *\n * See {@link mayFloat}. Not only `note`: any `annotation` or `layout` kind (a\n * swimlane is never wired to anything — that is what a lane IS), and any kind\n * the registry does not know.\n */\nexport function checkGraphConnectivity(graph: FlowGraph): ImportIssue[] {\n const { nodes, edges } = graph;\n const issues: ImportIssue[] = [];\n\n const hasIncoming = new Set<string>();\n const hasOutgoing = new Set<string>();\n for (const edge of edges) {\n hasIncoming.add(edge.target);\n hasOutgoing.add(edge.source);\n }\n\n // A single-node graph is not \"floating\" — it is a graph with one step, which\n // is a legitimate (if small) workflow and what every graph looks like on the\n // way to a bigger one. Refusing it would make the editor unusable from the\n // first node placed.\n const single = nodes.length === 1;\n\n for (const node of nodes) {\n if (single || mayFloat(node)) continue;\n\n if (!hasIncoming.has(node.id) && !hasOutgoing.has(node.id)) {\n issues.push({\n level: \"error\",\n nodeId: node.id,\n message:\n `Node \"${node.id}\" is connected to nothing — no inbound edge and no outbound edge. ` +\n `It still RUNS (a node with no inbound edge is a root), but it receives nothing from ` +\n `the graph and reaches nobody in it, so it is either unwired or left behind by a ` +\n `deletion. Only a note, an annotation or a lane may float.`,\n });\n }\n }\n\n const byId = new Map(nodes.map((n) => [n.id, n]));\n\n for (const edge of edges) {\n const source = byId.get(edge.source);\n if (!source || !isTerminator(source)) continue;\n\n issues.push({\n level: \"error\",\n edgeId: edge.id,\n message:\n `Edge \"${edge.id}\" reads from \"${edge.source}\", which is a TERMINAL node and publishes ` +\n `no output ports at all. Nothing can ever travel this edge: it does not fail at run ` +\n `time, it delivers nothing, and \"${edge.target}\" runs anyway with an empty input.`,\n });\n }\n\n return issues;\n}\n\n/**\n * Which nodes are allowed to sit unconnected.\n *\n * Three answers, and the third is the one that took a second pass — it was\n * missed in the PHP twin's first release and shipped as 0.48.1:\n *\n * 1. **`note`**, across every id the kind answers to, so a graph saved with the\n * canonical `@particle-academy/note` stays an annotation rather than becoming\n * an unwireable node.\n * 2. **Any `annotation` or `layout` kind.** A host may register its own note,\n * and `@particle-academy/lane` is a swimlane the engine walks straight past.\n * Neither is a step and neither is ever wired.\n * 3. **A kind the registry has never heard of.** Not a loophole — the honest\n * answer. An unknown kind already produces its own issue, and we cannot know\n * whether it is a step, an annotation or a lane. Claiming it must be wired\n * would assert something unverifiable, and it lands hardest on the graphs\n * that deserve it least: a laned graph loaded by a runtime without `lane`\n * registered would report every swimlane twice, the second time wrongly.\n *\n * The note/annotation/layout half matches the test `run-flow` uses to skip a\n * node. The unknown-kind half deliberately does NOT — see the closing comment in\n * this file for why the two must not become one helper.\n */\nexport function mayFloat(node: FlowNode): boolean {\n const type = node.type;\n if (!type) return false;\n if (type === \"note\") return true;\n\n const kind = getNodeKind(type);\n if (!kind) return true;\n\n return kindIds(kind).includes(\"note\") || kind.category === \"annotation\" || kind.category === \"layout\";\n}\n\n/**\n * A kind that declares an EMPTY output list ends a chain.\n *\n * `[]` and `undefined` are different answers and only the first means this.\n * `undefined` is \"nobody declared what this publishes\", which resolves to `out`\n * and is most nodes in most graphs; `[]` is an explicit claim that there is\n * nothing to connect from. Reading them alike would refuse nearly every\n * workflow ever written.\n */\nfunction isTerminator(node: FlowNode): boolean {\n // A node carrying its own ports overrides its kind — the engine reads these\n // first, so an author who has said what this node publishes is believed.\n // (The PHP twin's importer DROPS node-level ports, so this branch is reachable\n // there only for a hand-built graph. Noted rather than smoothed over: the two\n // importers genuinely differ here.)\n const own = (node.data as { outputs?: unknown[] } | undefined)?.outputs;\n if (Array.isArray(own)) return own.length === 0;\n\n const kind = node.type ? getNodeKind(node.type) : null;\n\n // An unregistered kind falls back to `out` in the engine, so it is not a\n // terminator. Refusing here would break a host mid-registration, and would\n // use \"I do not know\" as evidence.\n if (!kind) return false;\n\n return Array.isArray(kind.outputs) && kind.outputs.length === 0;\n}\n\n/**\n * NOT shared with `frontier`'s / `run-flow`'s annotation test, deliberately.\n *\n * The two predicates look identical and have OPPOSITE safe defaults, which is\n * exactly how a shared helper becomes a bug:\n *\n * - **Here** an unknown kind may float. Being permissive costs nothing — the\n * unknown-kind issue already fires, and we would otherwise assert something\n * unverifiable.\n * - **In the engine** an unknown kind must NOT be treated as an annotation.\n * Being permissive there means SKIPPING a node the host meant to run, which\n * is silent and unrecoverable.\n *\n * So the note/annotation/layout half is the same question and the unknown half\n * is a different one. Collapsing them into one function would read as tidier and\n * would make a host's unregistered kind stop executing.\n */\n"]}
|
package/dist/durable/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { F as FlowGraph, R as RunEvent, c as RunIdentity, E as ExecutorRegistry, a as FlowNode, N as NodeExecutor, d as RunIdentityJson } from '../types-B-Syk9-M.cjs';
|
|
2
|
-
import { a as RunResult } from '../run-flow-
|
|
2
|
+
import { a as RunResult } from '../run-flow-pW0PllaZ.cjs';
|
|
3
3
|
import { b as PauseSignal } from '../pause-9iT4tCEV.cjs';
|
|
4
4
|
import '@xyflow/react';
|
|
5
5
|
|
|
@@ -199,12 +199,38 @@ declare const Frontier: {
|
|
|
199
199
|
* - every node already completed is fed back as `resumeOutputs`, so the engine
|
|
200
200
|
* republishes it on the same ports and routes exactly as it did the first
|
|
201
201
|
* time;
|
|
202
|
-
* - every node EXCEPT the target is bound,
|
|
203
|
-
* that
|
|
204
|
-
* `*` fallback, so the fence holds whatever a host registered;
|
|
202
|
+
* - every node EXCEPT the target is bound, through `RunOptions.nodeExecutors`,
|
|
203
|
+
* to a FENCE that runs nothing and publishes only a port no edge reads;
|
|
205
204
|
* - so the engine walks its own topological order, skips its own dead branches,
|
|
206
|
-
* collects the target's inputs its own way, runs the target
|
|
207
|
-
*
|
|
205
|
+
* collects the target's inputs its own way, and runs the target.
|
|
206
|
+
*
|
|
207
|
+
* ## Why the fence does not stop the walk
|
|
208
|
+
*
|
|
209
|
+
* It used to abort the run. The target's own inputs never depend on a fenced
|
|
210
|
+
* node -- the frontier dispatches a node only once every source is settled, and
|
|
211
|
+
* settled sources are resumed, not fenced -- but an UNRELATED node can precede
|
|
212
|
+
* the target in topological order. Two siblings dispatched together are exactly
|
|
213
|
+
* that: when `b`'s job started while `a` was still running, the replay aborted
|
|
214
|
+
* at `a`, never reached `b`, and the coordinator read "the replay ended without
|
|
215
|
+
* running me" as "the engine decided I am unreachable". `b` was recorded
|
|
216
|
+
* skipped, never ran, and the run completed as a success. It did not even take
|
|
217
|
+
* two workers: the frontier lists ready nodes in NODE order and the engine walks
|
|
218
|
+
* EDGE order, so a graph where those disagree about siblings sent in-process
|
|
219
|
+
* `runToCompletion` down the same path.
|
|
220
|
+
*
|
|
221
|
+
* Walking past fences makes that inference honest again: when the replay
|
|
222
|
+
* finishes without an output for the target, it is because the engine found
|
|
223
|
+
* every inbound edge dead.
|
|
224
|
+
*
|
|
225
|
+
* ## Why the fences are not registry entries
|
|
226
|
+
*
|
|
227
|
+
* The registry is one flat object, and a key in it is tried as a node's id AND
|
|
228
|
+
* as its kind. Fencing the node called `host_kind` by writing
|
|
229
|
+
* `executors["host_kind"]` fenced every node of kind `host_kind` too, so a
|
|
230
|
+
* durable run of that graph ran nothing and reported success. And the registry
|
|
231
|
+
* is what `ctx.executors` hands a `subflow` child, so a child node sharing an id
|
|
232
|
+
* with any parent node ran the parent's fence. `nodeExecutors` matches node ids
|
|
233
|
+
* only and is never handed down; the registry reaches the engine untouched.
|
|
208
234
|
*
|
|
209
235
|
* The target's output is `result.outputs[nodeId]`, and the ports it activated
|
|
210
236
|
* arrive as the engine's own `node-output` events. Nothing about routing is
|
|
@@ -220,12 +246,19 @@ declare const Frontier: {
|
|
|
220
246
|
*/
|
|
221
247
|
|
|
222
248
|
/**
|
|
223
|
-
* The abort reason
|
|
249
|
+
* The abort reason a boundary used to report.
|
|
224
250
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
251
|
+
* Nothing aborts with it any more (see "Why the fence does not stop the walk");
|
|
252
|
+
* {@link isBoundary} still recognises it so a caller that checks for it keeps
|
|
253
|
+
* working.
|
|
227
254
|
*/
|
|
228
255
|
declare const BOUNDARY = "fancy-flow:node-boundary";
|
|
256
|
+
/**
|
|
257
|
+
* The port a fenced node publishes on. No edge reads it, so everything
|
|
258
|
+
* downstream of a fenced node is dark in the replay -- which never matters to
|
|
259
|
+
* the target, whose sources are all settled.
|
|
260
|
+
*/
|
|
261
|
+
declare const FENCE_PORT = "fancy-flow:fenced";
|
|
229
262
|
type ReplayResult = {
|
|
230
263
|
result: RunResult;
|
|
231
264
|
/** node id -> the ports its output activated, from the engine's own events. */
|
|
@@ -243,8 +276,8 @@ type ReplayOptions = {
|
|
|
243
276
|
/**
|
|
244
277
|
* Replay `graph` up to and through `nodeId`.
|
|
245
278
|
*
|
|
246
|
-
* Pass `nodeId = null` to PROBE: every node is
|
|
247
|
-
*
|
|
279
|
+
* Pass `nodeId = null` to PROBE: every node is fenced, so nothing executes and
|
|
280
|
+
* the engine reports only what it can determine structurally — a cycle, and
|
|
248
281
|
* the ports each resumed output republishes on.
|
|
249
282
|
*/
|
|
250
283
|
declare function replayUpTo(graph: FlowGraph, nodeId: string | null, executors: ExecutorRegistry, options?: ReplayOptions): Promise<ReplayResult>;
|
|
@@ -528,4 +561,4 @@ declare class Coordinator {
|
|
|
528
561
|
private forward;
|
|
529
562
|
}
|
|
530
563
|
|
|
531
|
-
export { BOUNDARY, Coordinator, type CoordinatorOptions, type DurableRunResult, Frontier, type FrontierResult, InMemoryClaimStore, type NodeClaimStore, type NodeOutcome, NodeRunStatus, type NodeRunStatusValue, type NodeState, NotAwaitingHuman, type ReplayOptions, type ReplayResult, RetryPolicy, type RetryPolicyOptions, SETTLED, Submissions, UNSAFE_TO_REPLAY, durableApproval, durableUserInput, isBoundary, isSettled, replayUpTo };
|
|
564
|
+
export { BOUNDARY, Coordinator, type CoordinatorOptions, type DurableRunResult, FENCE_PORT, Frontier, type FrontierResult, InMemoryClaimStore, type NodeClaimStore, type NodeOutcome, NodeRunStatus, type NodeRunStatusValue, type NodeState, NotAwaitingHuman, type ReplayOptions, type ReplayResult, RetryPolicy, type RetryPolicyOptions, SETTLED, Submissions, UNSAFE_TO_REPLAY, durableApproval, durableUserInput, isBoundary, isSettled, replayUpTo };
|
package/dist/durable/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { F as FlowGraph, R as RunEvent, c as RunIdentity, E as ExecutorRegistry, a as FlowNode, N as NodeExecutor, d as RunIdentityJson } from '../types-B-Syk9-M.js';
|
|
2
|
-
import { a as RunResult } from '../run-flow-
|
|
2
|
+
import { a as RunResult } from '../run-flow-BLP9rXfO.js';
|
|
3
3
|
import { b as PauseSignal } from '../pause-9iT4tCEV.js';
|
|
4
4
|
import '@xyflow/react';
|
|
5
5
|
|
|
@@ -199,12 +199,38 @@ declare const Frontier: {
|
|
|
199
199
|
* - every node already completed is fed back as `resumeOutputs`, so the engine
|
|
200
200
|
* republishes it on the same ports and routes exactly as it did the first
|
|
201
201
|
* time;
|
|
202
|
-
* - every node EXCEPT the target is bound,
|
|
203
|
-
* that
|
|
204
|
-
* `*` fallback, so the fence holds whatever a host registered;
|
|
202
|
+
* - every node EXCEPT the target is bound, through `RunOptions.nodeExecutors`,
|
|
203
|
+
* to a FENCE that runs nothing and publishes only a port no edge reads;
|
|
205
204
|
* - so the engine walks its own topological order, skips its own dead branches,
|
|
206
|
-
* collects the target's inputs its own way, runs the target
|
|
207
|
-
*
|
|
205
|
+
* collects the target's inputs its own way, and runs the target.
|
|
206
|
+
*
|
|
207
|
+
* ## Why the fence does not stop the walk
|
|
208
|
+
*
|
|
209
|
+
* It used to abort the run. The target's own inputs never depend on a fenced
|
|
210
|
+
* node -- the frontier dispatches a node only once every source is settled, and
|
|
211
|
+
* settled sources are resumed, not fenced -- but an UNRELATED node can precede
|
|
212
|
+
* the target in topological order. Two siblings dispatched together are exactly
|
|
213
|
+
* that: when `b`'s job started while `a` was still running, the replay aborted
|
|
214
|
+
* at `a`, never reached `b`, and the coordinator read "the replay ended without
|
|
215
|
+
* running me" as "the engine decided I am unreachable". `b` was recorded
|
|
216
|
+
* skipped, never ran, and the run completed as a success. It did not even take
|
|
217
|
+
* two workers: the frontier lists ready nodes in NODE order and the engine walks
|
|
218
|
+
* EDGE order, so a graph where those disagree about siblings sent in-process
|
|
219
|
+
* `runToCompletion` down the same path.
|
|
220
|
+
*
|
|
221
|
+
* Walking past fences makes that inference honest again: when the replay
|
|
222
|
+
* finishes without an output for the target, it is because the engine found
|
|
223
|
+
* every inbound edge dead.
|
|
224
|
+
*
|
|
225
|
+
* ## Why the fences are not registry entries
|
|
226
|
+
*
|
|
227
|
+
* The registry is one flat object, and a key in it is tried as a node's id AND
|
|
228
|
+
* as its kind. Fencing the node called `host_kind` by writing
|
|
229
|
+
* `executors["host_kind"]` fenced every node of kind `host_kind` too, so a
|
|
230
|
+
* durable run of that graph ran nothing and reported success. And the registry
|
|
231
|
+
* is what `ctx.executors` hands a `subflow` child, so a child node sharing an id
|
|
232
|
+
* with any parent node ran the parent's fence. `nodeExecutors` matches node ids
|
|
233
|
+
* only and is never handed down; the registry reaches the engine untouched.
|
|
208
234
|
*
|
|
209
235
|
* The target's output is `result.outputs[nodeId]`, and the ports it activated
|
|
210
236
|
* arrive as the engine's own `node-output` events. Nothing about routing is
|
|
@@ -220,12 +246,19 @@ declare const Frontier: {
|
|
|
220
246
|
*/
|
|
221
247
|
|
|
222
248
|
/**
|
|
223
|
-
* The abort reason
|
|
249
|
+
* The abort reason a boundary used to report.
|
|
224
250
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
251
|
+
* Nothing aborts with it any more (see "Why the fence does not stop the walk");
|
|
252
|
+
* {@link isBoundary} still recognises it so a caller that checks for it keeps
|
|
253
|
+
* working.
|
|
227
254
|
*/
|
|
228
255
|
declare const BOUNDARY = "fancy-flow:node-boundary";
|
|
256
|
+
/**
|
|
257
|
+
* The port a fenced node publishes on. No edge reads it, so everything
|
|
258
|
+
* downstream of a fenced node is dark in the replay -- which never matters to
|
|
259
|
+
* the target, whose sources are all settled.
|
|
260
|
+
*/
|
|
261
|
+
declare const FENCE_PORT = "fancy-flow:fenced";
|
|
229
262
|
type ReplayResult = {
|
|
230
263
|
result: RunResult;
|
|
231
264
|
/** node id -> the ports its output activated, from the engine's own events. */
|
|
@@ -243,8 +276,8 @@ type ReplayOptions = {
|
|
|
243
276
|
/**
|
|
244
277
|
* Replay `graph` up to and through `nodeId`.
|
|
245
278
|
*
|
|
246
|
-
* Pass `nodeId = null` to PROBE: every node is
|
|
247
|
-
*
|
|
279
|
+
* Pass `nodeId = null` to PROBE: every node is fenced, so nothing executes and
|
|
280
|
+
* the engine reports only what it can determine structurally — a cycle, and
|
|
248
281
|
* the ports each resumed output republishes on.
|
|
249
282
|
*/
|
|
250
283
|
declare function replayUpTo(graph: FlowGraph, nodeId: string | null, executors: ExecutorRegistry, options?: ReplayOptions): Promise<ReplayResult>;
|
|
@@ -528,4 +561,4 @@ declare class Coordinator {
|
|
|
528
561
|
private forward;
|
|
529
562
|
}
|
|
530
563
|
|
|
531
|
-
export { BOUNDARY, Coordinator, type CoordinatorOptions, type DurableRunResult, Frontier, type FrontierResult, InMemoryClaimStore, type NodeClaimStore, type NodeOutcome, NodeRunStatus, type NodeRunStatusValue, type NodeState, NotAwaitingHuman, type ReplayOptions, type ReplayResult, RetryPolicy, type RetryPolicyOptions, SETTLED, Submissions, UNSAFE_TO_REPLAY, durableApproval, durableUserInput, isBoundary, isSettled, replayUpTo };
|
|
564
|
+
export { BOUNDARY, Coordinator, type CoordinatorOptions, type DurableRunResult, FENCE_PORT, Frontier, type FrontierResult, InMemoryClaimStore, type NodeClaimStore, type NodeOutcome, NodeRunStatus, type NodeRunStatusValue, type NodeState, NotAwaitingHuman, type ReplayOptions, type ReplayResult, RetryPolicy, type RetryPolicyOptions, SETTLED, Submissions, UNSAFE_TO_REPLAY, durableApproval, durableUserInput, isBoundary, isSettled, replayUpTo };
|
package/dist/durable.cjs
CHANGED
|
@@ -720,7 +720,7 @@ function resolveWorkflowProps(declared, passed) {
|
|
|
720
720
|
// src/runtime/run-flow.ts
|
|
721
721
|
async function runFlow(graph, executors, onEvent = () => {
|
|
722
722
|
}, options = {}) {
|
|
723
|
-
const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes } = options;
|
|
723
|
+
const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes, nodeExecutors } = options;
|
|
724
724
|
const run = options.run === void 0 ? void 0 : RunIdentity.from(options.run);
|
|
725
725
|
const outputs = {};
|
|
726
726
|
const portValues = /* @__PURE__ */ new Map();
|
|
@@ -808,7 +808,7 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
808
808
|
onEvent({ type: "node-status", nodeId: node.id, status: "running" });
|
|
809
809
|
announce(onEvent, node, "start");
|
|
810
810
|
const inputs = collectInputs(node, incoming, portValues, initialInputs, props, declaresProps);
|
|
811
|
-
const exec = pickExecutor(executors, node);
|
|
811
|
+
const exec = pickExecutor(executors, node, nodeExecutors);
|
|
812
812
|
if (!exec) {
|
|
813
813
|
const tried = executorLookupIds(node);
|
|
814
814
|
const msg = `No executor registered for kind=${node.type} \u2014 tried ${tried.map((id) => `"${id}"`).join(", ")}. Key your registry by one of those.`;
|
|
@@ -928,7 +928,9 @@ function executorLookupIds(node) {
|
|
|
928
928
|
ids.push("*");
|
|
929
929
|
return [...new Set(ids)];
|
|
930
930
|
}
|
|
931
|
-
function pickExecutor(executors, node) {
|
|
931
|
+
function pickExecutor(executors, node, nodeExecutors) {
|
|
932
|
+
const pinned = nodeExecutors && Object.prototype.hasOwnProperty.call(nodeExecutors, node.id) ? nodeExecutors[node.id] : void 0;
|
|
933
|
+
if (pinned) return pinned;
|
|
932
934
|
for (const id of executorLookupIds(node)) {
|
|
933
935
|
if (executors[id]) return executors[id];
|
|
934
936
|
}
|
|
@@ -2734,12 +2736,16 @@ var Frontier = {
|
|
|
2734
2736
|
|
|
2735
2737
|
// src/durable/replay.ts
|
|
2736
2738
|
var BOUNDARY = "fancy-flow:node-boundary";
|
|
2739
|
+
var FENCE_PORT = "fancy-flow:fenced";
|
|
2737
2740
|
async function replayUpTo(graph, nodeId, executors, options = {}) {
|
|
2738
|
-
const
|
|
2741
|
+
const fencedOff = /* @__PURE__ */ new Set();
|
|
2742
|
+
const fence = (ctx) => {
|
|
2743
|
+
fencedOff.add(ctx.node.id);
|
|
2744
|
+
return { __port: FENCE_PORT, value: null };
|
|
2745
|
+
};
|
|
2746
|
+
const fences = {};
|
|
2739
2747
|
for (const node of graph.nodes) {
|
|
2740
|
-
if (node.id !== nodeId)
|
|
2741
|
-
fenced[node.id] = (ctx) => ctx.abort(BOUNDARY);
|
|
2742
|
-
}
|
|
2748
|
+
if (node.id !== nodeId) fences[node.id] = fence;
|
|
2743
2749
|
}
|
|
2744
2750
|
const ports = {};
|
|
2745
2751
|
const collect = (event) => {
|
|
@@ -2749,12 +2755,17 @@ async function replayUpTo(graph, nodeId, executors, options = {}) {
|
|
|
2749
2755
|
}
|
|
2750
2756
|
options.onEvent?.(event);
|
|
2751
2757
|
};
|
|
2752
|
-
const result = await runFlow(graph,
|
|
2758
|
+
const result = await runFlow(graph, executors, collect, {
|
|
2753
2759
|
initialInputs: options.initialInputs ?? {},
|
|
2754
2760
|
resumeOutputs: options.resumeOutputs ?? {},
|
|
2755
2761
|
depth: options.depth ?? 0,
|
|
2756
|
-
run: options.run
|
|
2762
|
+
run: options.run,
|
|
2763
|
+
nodeExecutors: fences
|
|
2757
2764
|
});
|
|
2765
|
+
for (const id of fencedOff) {
|
|
2766
|
+
delete result.outputs[id];
|
|
2767
|
+
delete ports[id];
|
|
2768
|
+
}
|
|
2758
2769
|
return {
|
|
2759
2770
|
result,
|
|
2760
2771
|
ports,
|
|
@@ -2985,12 +2996,12 @@ var Coordinator = class {
|
|
|
2985
2996
|
await this.store.pause(this.runKey, nodeId, result.error ?? "");
|
|
2986
2997
|
return { nodeId, status: "paused", pause, claimed: true, attempt };
|
|
2987
2998
|
}
|
|
2988
|
-
if (isBoundary(result.error)) {
|
|
2999
|
+
if (isBoundary(result.error) || result.ok) {
|
|
2989
3000
|
await this.store.skip(this.runKey, nodeId);
|
|
2990
3001
|
return {
|
|
2991
3002
|
nodeId,
|
|
2992
3003
|
status: "skipped",
|
|
2993
|
-
error: "
|
|
3004
|
+
error: "the engine found no live inbound edge for this node",
|
|
2994
3005
|
claimed: true,
|
|
2995
3006
|
attempt
|
|
2996
3007
|
};
|
|
@@ -3166,6 +3177,7 @@ function cryptoRandom() {
|
|
|
3166
3177
|
|
|
3167
3178
|
exports.BOUNDARY = BOUNDARY;
|
|
3168
3179
|
exports.Coordinator = Coordinator;
|
|
3180
|
+
exports.FENCE_PORT = FENCE_PORT;
|
|
3169
3181
|
exports.Frontier = Frontier;
|
|
3170
3182
|
exports.InMemoryClaimStore = InMemoryClaimStore;
|
|
3171
3183
|
exports.NodeRunStatus = NodeRunStatus;
|