@particle-academy/fancy-flow 0.44.0 → 0.46.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.
Files changed (78) hide show
  1. package/dist/{ConfigFieldRenderer-LotNHeG8.d.cts → ConfigFieldRenderer-Cyir8-B8.d.cts} +2 -2
  2. package/dist/{ConfigFieldRenderer-BHQDZer6.d.ts → ConfigFieldRenderer-iMkZj0VK.d.ts} +2 -2
  3. package/dist/{FlowViewer-nLqo7s2E.d.cts → FlowViewer-CY3_vHSg.d.cts} +1 -1
  4. package/dist/{FlowViewer-C4QAVgWv.d.ts → FlowViewer-DGW6t-80.d.ts} +1 -1
  5. package/dist/{capabilities-CkhKj_U_.d.ts → capabilities-DMUhnyZD.d.ts} +2 -2
  6. package/dist/{capabilities-BtJQTbnw.d.cts → capabilities-Dmd26aAs.d.cts} +2 -2
  7. package/dist/chunk-BHM5HGXH.js +79 -0
  8. package/dist/chunk-BHM5HGXH.js.map +1 -0
  9. package/dist/{chunk-DHUFUDEX.js → chunk-HFUBVT7W.js} +3 -3
  10. package/dist/{chunk-DHUFUDEX.js.map → chunk-HFUBVT7W.js.map} +1 -1
  11. package/dist/{chunk-VLATLVGH.js → chunk-L43CWD63.js} +129 -5
  12. package/dist/chunk-L43CWD63.js.map +1 -0
  13. package/dist/{chunk-A6RFLGWV.js → chunk-MH4ZW4DJ.js} +3 -3
  14. package/dist/{chunk-A6RFLGWV.js.map → chunk-MH4ZW4DJ.js.map} +1 -1
  15. package/dist/{chunk-QZTGV3ZL.js → chunk-P5SYH6M6.js} +3 -3
  16. package/dist/{chunk-QZTGV3ZL.js.map → chunk-P5SYH6M6.js.map} +1 -1
  17. package/dist/{chunk-WBXGZVLB.js → chunk-RMU6YCIQ.js} +11 -5
  18. package/dist/chunk-RMU6YCIQ.js.map +1 -0
  19. package/dist/durable/index.d.cts +490 -0
  20. package/dist/durable/index.d.ts +490 -0
  21. package/dist/durable.cjs +927 -0
  22. package/dist/durable.cjs.map +1 -0
  23. package/dist/durable.js +552 -0
  24. package/dist/durable.js.map +1 -0
  25. package/dist/engine.cjs +198 -12
  26. package/dist/engine.cjs.map +1 -1
  27. package/dist/engine.d.cts +44 -7
  28. package/dist/engine.d.ts +44 -7
  29. package/dist/engine.js +68 -12
  30. package/dist/engine.js.map +1 -1
  31. package/dist/fields/react-fancy.d.cts +4 -3
  32. package/dist/fields/react-fancy.d.ts +4 -3
  33. package/dist/index.cjs +133 -3
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +12 -10
  36. package/dist/index.d.ts +12 -10
  37. package/dist/index.js +13 -88
  38. package/dist/index.js.map +1 -1
  39. package/dist/layout/index.d.cts +1 -1
  40. package/dist/layout/index.d.ts +1 -1
  41. package/dist/llm/prism.d.cts +2 -2
  42. package/dist/llm/prism.d.ts +2 -2
  43. package/dist/llm/vercel-ai.d.cts +2 -2
  44. package/dist/llm/vercel-ai.d.ts +2 -2
  45. package/dist/pause-9iT4tCEV.d.cts +96 -0
  46. package/dist/pause-9iT4tCEV.d.ts +96 -0
  47. package/dist/registry/index.d.cts +7 -6
  48. package/dist/registry/index.d.ts +7 -6
  49. package/dist/{registry-C9KMVpn7.d.cts → registry-D_kyUZEf.d.cts} +1 -1
  50. package/dist/{registry-FGCBprcn.d.ts → registry-LHB3MlR9.d.ts} +1 -1
  51. package/dist/registry.cjs +133 -3
  52. package/dist/registry.cjs.map +1 -1
  53. package/dist/registry.js +4 -4
  54. package/dist/{run-cohort-BnP6ErHi.d.ts → run-cohort-Cyeny2Uu.d.ts} +3 -34
  55. package/dist/{run-cohort-Bjv2ZU6g.d.cts → run-cohort-DdPPowJ3.d.cts} +3 -34
  56. package/dist/run-flow-1KiXhy59.d.cts +55 -0
  57. package/dist/run-flow-CQvTDBYk.d.ts +55 -0
  58. package/dist/runtime/index.d.cts +5 -3
  59. package/dist/runtime/index.d.ts +5 -3
  60. package/dist/runtime.cjs +128 -2
  61. package/dist/runtime.cjs.map +1 -1
  62. package/dist/runtime.js +3 -3
  63. package/dist/schema/index.d.cts +1 -1
  64. package/dist/schema/index.d.ts +1 -1
  65. package/dist/screens.d.cts +2 -2
  66. package/dist/screens.d.ts +2 -2
  67. package/dist/screens.js +6 -6
  68. package/dist/{types-JViq8reb.d.ts → types--S2Kzx7w.d.cts} +3 -97
  69. package/dist/{types-B78Rm69S.d.cts → types-ChkCRonF.d.ts} +3 -97
  70. package/dist/types-Jx1TwehV.d.cts +247 -0
  71. package/dist/types-Jx1TwehV.d.ts +247 -0
  72. package/dist/ux.d.cts +3 -2
  73. package/dist/ux.d.ts +3 -2
  74. package/package.json +12 -2
  75. package/dist/chunk-VLATLVGH.js.map +0 -1
  76. package/dist/chunk-WBXGZVLB.js.map +0 -1
  77. package/dist/types-sOmpCitB.d.cts +0 -109
  78. package/dist/types-sOmpCitB.d.ts +0 -109
@@ -1,10 +1,121 @@
1
1
  import { resolveNodePorts } from './chunk-TITD5W4Y.js';
2
2
  import { getNodeKind, kindIds } from './chunk-PVROYW7C.js';
3
3
 
4
+ // src/runtime/run-identity.ts
5
+ function escapeSegment(value) {
6
+ return value.replace(/%/g, "%25").replace(/\//g, "%2F").replace(/#/g, "%23");
7
+ }
8
+ function renderSegment(value, occurrence) {
9
+ const escaped = escapeSegment(value);
10
+ return occurrence === void 0 || occurrence === null ? escaped : `${escaped}#${occurrence}`;
11
+ }
12
+ function instant(value) {
13
+ const ms = Date.parse(value);
14
+ if (Number.isNaN(ms)) {
15
+ throw new Error(
16
+ `RunIdentity: firstAttemptAt is not a parseable timestamp: ${JSON.stringify(value)}`
17
+ );
18
+ }
19
+ return ms;
20
+ }
21
+ var RunIdentity = class _RunIdentity {
22
+ constructor(runKey, path = [], attempt = 1, firstAttemptAt = (/* @__PURE__ */ new Date()).toISOString()) {
23
+ if (!runKey || runKey.trim() === "") {
24
+ throw new Error("RunIdentity: runKey must be a non-empty string.");
25
+ }
26
+ this.runKey = runKey;
27
+ this.path = Object.freeze([...path]);
28
+ this.attempt = Math.max(1, Math.trunc(attempt));
29
+ this.firstAttemptAt = firstAttemptAt;
30
+ Object.freeze(this);
31
+ }
32
+ /**
33
+ * The identity of one execution of one node — stable across retries of that
34
+ * execution, distinct from every other execution of the same node.
35
+ *
36
+ * Pass `occurrence` when an executor runs the same node more than once at the
37
+ * same level (a loop body, one item of a fan-out it drives itself).
38
+ */
39
+ stepKey(nodeId, occurrence) {
40
+ return `${this.runKey}:${[...this.path, renderSegment(nodeId, occurrence)].join("/")}`;
41
+ }
42
+ /**
43
+ * A child identity for work nested inside this step.
44
+ *
45
+ * `subflow` pushes the invoking node's id, so a node inside the child graph
46
+ * cannot collide with a same-named node in the parent. Attempt and
47
+ * `firstAttemptAt` are carried down unchanged: the nested work happens inside
48
+ * this step's attempt, and shares its clock.
49
+ */
50
+ descend(segment, occurrence) {
51
+ return new _RunIdentity(
52
+ this.runKey,
53
+ [...this.path, renderSegment(segment, occurrence)],
54
+ this.attempt,
55
+ this.firstAttemptAt
56
+ );
57
+ }
58
+ /** A copy on a different attempt, first-attempt clock preserved. */
59
+ withAttempt(attempt, firstAttemptAt) {
60
+ return new _RunIdentity(
61
+ this.runKey,
62
+ this.path,
63
+ attempt,
64
+ firstAttemptAt ?? this.firstAttemptAt
65
+ );
66
+ }
67
+ /**
68
+ * May this attempt reuse the step key and still be deduplicated?
69
+ *
70
+ * Providers forget idempotency keys — Stripe after 24 hours. Past that
71
+ * window, resending the key creates a second charge and sending a fresh one
72
+ * creates a second charge, so **the caller must refuse rather than pick
73
+ * between them**: a loud stuck run beats a silent double write.
74
+ *
75
+ * `true` on attempt 1 whatever the elapsed time — nothing was sent on an
76
+ * earlier attempt, so there is nothing for the provider to have forgotten.
77
+ * That is what lets a run park on a human gate for a week and then write.
78
+ *
79
+ * `windowSeconds: null` means the provider does not expire keys. `0` means
80
+ * it does not dedupe at all, so no retry may reuse a key — it is a real
81
+ * window, not an absent one, and the two must not be conflated: reading `0`
82
+ * as `null` turns "this provider does not dedupe" into "this provider
83
+ * dedupes forever", which is the more dangerous of the two by a distance.
84
+ */
85
+ isReplaySafe(windowSeconds, now = /* @__PURE__ */ new Date()) {
86
+ if (this.attempt <= 1) return true;
87
+ if (windowSeconds === null || windowSeconds === void 0) return true;
88
+ if (windowSeconds <= 0) return false;
89
+ const nowMs = typeof now === "string" ? instant(now) : now.getTime();
90
+ const elapsedSeconds = Math.max(0, (nowMs - instant(this.firstAttemptAt)) / 1e3);
91
+ return elapsedSeconds <= windowSeconds;
92
+ }
93
+ toJSON() {
94
+ return {
95
+ runKey: this.runKey,
96
+ path: [...this.path],
97
+ attempt: this.attempt,
98
+ firstAttemptAt: this.firstAttemptAt
99
+ };
100
+ }
101
+ /** Rebuild from a queue payload. */
102
+ static from(value) {
103
+ if (value instanceof _RunIdentity) return value;
104
+ if (typeof value === "string") return new _RunIdentity(value);
105
+ return new _RunIdentity(
106
+ value.runKey,
107
+ value.path ?? [],
108
+ value.attempt ?? 1,
109
+ value.firstAttemptAt ?? (/* @__PURE__ */ new Date()).toISOString()
110
+ );
111
+ }
112
+ };
113
+
4
114
  // src/runtime/run-flow.ts
5
115
  async function runFlow(graph, executors, onEvent = () => {
6
116
  }, options = {}) {
7
- const { signal, initialInputs = {}, timeoutMs, depth = 0 } = options;
117
+ const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {} } = options;
118
+ const run = options.run === void 0 ? void 0 : RunIdentity.from(options.run);
8
119
  const outputs = {};
9
120
  const portValues = /* @__PURE__ */ new Map();
10
121
  const completed = /* @__PURE__ */ new Set();
@@ -22,6 +133,18 @@ async function runFlow(graph, executors, onEvent = () => {
22
133
  for (const node of order) {
23
134
  if (signal?.aborted) throw new Error("aborted");
24
135
  if (errors.length) break;
136
+ if (Object.prototype.hasOwnProperty.call(resumeOutputs, node.id)) {
137
+ const stored = resumeOutputs[node.id];
138
+ outputs[node.id] = stored;
139
+ const activated = activatedPorts(node, stored);
140
+ for (const portId of activated.ports) {
141
+ portValues.set(`${node.id}:${portId}`, activated.value);
142
+ onEvent({ type: "node-output", nodeId: node.id, portId, value: activated.value });
143
+ }
144
+ completed.add(node.id);
145
+ onEvent({ type: "node-status", nodeId: node.id, status: "done", text: "resumed" });
146
+ continue;
147
+ }
25
148
  const incoming = incomingByNode.get(node.id) ?? [];
26
149
  if (incoming.length > 0) {
27
150
  const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? "out"}`));
@@ -61,7 +184,8 @@ async function runFlow(graph, executors, onEvent = () => {
61
184
  throw new Error(reason ?? "aborted");
62
185
  },
63
186
  emit: onEvent,
64
- depth
187
+ depth,
188
+ run
65
189
  })
66
190
  );
67
191
  outputs[node.id] = result;
@@ -153,6 +277,6 @@ function activatedPorts(node, result) {
153
277
  return { ports: declared?.length ? declared : ["out"], value: result };
154
278
  }
155
279
 
156
- export { runFlow };
157
- //# sourceMappingURL=chunk-VLATLVGH.js.map
158
- //# sourceMappingURL=chunk-VLATLVGH.js.map
280
+ export { RunIdentity, escapeSegment, runFlow };
281
+ //# sourceMappingURL=chunk-L43CWD63.js.map
282
+ //# sourceMappingURL=chunk-L43CWD63.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/run-identity.ts","../src/runtime/run-flow.ts"],"names":[],"mappings":"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","file":"chunk-L43CWD63.js","sourcesContent":["/**\n * Who is running, which step this is, and how many times it has been tried.\n *\n * ## Why an engine needs this at all\n *\n * A node that WRITES to somebody else's system — charge a card, send a message,\n * open a pull request — can only survive a retry if the retry carries the same\n * idempotency key the first attempt did. Otherwise the provider treats the\n * second call as a new request and the customer is charged twice.\n *\n * Until this existed the executor context was `{ node, inputs, emit, abort }`,\n * which is not enough to derive one. Both obvious fallbacks are worse than\n * sending no key at all:\n *\n * - **the node id alone** is stable across retries, and also across RUNS — two\n * legitimate payments share a key and the provider silently collapses the\n * second into the first. A payment that never happened, reported as success;\n * - **a fresh random value** is unique per run, and also per ATTEMPT — a retry\n * creates a second charge, which is the thing being avoided.\n *\n * ## What actually identifies a step\n *\n * Not `(run, node)`. A node legitimately executes more than once inside one\n * run: once per subflow invocation, once per iteration of a loop an executor\n * drives itself. `(run, node)` would give every one of those the same key, and\n * a provider would honour exactly one of them.\n *\n * So a step is identified by the **path of invocations that led to it**, plus\n * an optional **occurrence** for repetition at the same level:\n *\n * ```text\n * runKey \":\" segment (\"/\" segment)* segment := escape(id) [\"#\" occurrence]\n * ```\n *\n * And the part that is easy to get backwards: **`attempt` is NOT in the key.**\n * It is carried here for logging and for {@link RunIdentity.isReplaySafe}, and\n * putting it in the key would restore the exact bug the key exists to prevent.\n *\n * Pinned cross-runtime by `shared/flow-run-identity` in\n * `@particle-academy/fancy-conformance`.\n */\n\n/** The wire shape — what a queue job payload carries. */\nexport type RunIdentityJson = {\n runKey: string;\n path?: string[];\n attempt?: number;\n firstAttemptAt?: string;\n};\n\n/**\n * Escape one segment so the composition is injective.\n *\n * `%` FIRST, or the escaping is not reversible: escaping `/` before `%` turns a\n * literal `a%2Fb` into the same text as the escaped form of `a/b`, which is the\n * collision this exists to prevent, reintroduced by its own fix.\n */\nexport function escapeSegment(value: string): string {\n // Not `replaceAll` — the package targets ES2020, and this must produce the\n // identical string on every runtime that reads the conformance table.\n return value.replace(/%/g, \"%25\").replace(/\\//g, \"%2F\").replace(/#/g, \"%23\");\n}\n\nfunction renderSegment(value: string, occurrence?: number | null): string {\n const escaped = escapeSegment(value);\n // `occurrence === 0` is a real occurrence. A truthiness check here silently\n // collapses iteration 0 into the un-iterated key.\n return occurrence === undefined || occurrence === null ? escaped : `${escaped}#${occurrence}`;\n}\n\nfunction instant(value: string): number {\n const ms = Date.parse(value);\n if (Number.isNaN(ms)) {\n throw new Error(\n `RunIdentity: firstAttemptAt is not a parseable timestamp: ${JSON.stringify(value)}`,\n );\n }\n return ms;\n}\n\n/**\n * A run, a position inside it, and how many times this position has been tried.\n *\n * Immutable. {@link descend} returns a new identity rather than mutating, so an\n * executor cannot change what its siblings see.\n */\nexport class RunIdentity {\n /** Stable for the whole run: same across retries, resumes, workers and hosts. */\n readonly runKey: string;\n\n /**\n * Enclosing invocation segments, outermost first, ALREADY RENDERED.\n *\n * Empty at the top level. A subflow pushes the invoking node's id; an\n * executor that loops pushes `id#i`.\n */\n readonly path: readonly string[];\n\n /**\n * 1-based attempt of THIS logical step. Never part of the key.\n *\n * The durable driver sets it from the node's claim row, which is exact. A\n * plain in-process `runFlow` gets whatever the host passed, which is\n * run-scoped and therefore conservative — see `isReplaySafe`.\n */\n readonly attempt: number;\n\n /** ISO-8601 UTC instant of attempt 1 of this step. */\n readonly firstAttemptAt: string;\n\n constructor(\n runKey: string,\n path: readonly string[] = [],\n attempt = 1,\n firstAttemptAt: string = new Date().toISOString(),\n ) {\n if (!runKey || runKey.trim() === \"\") {\n throw new Error(\"RunIdentity: runKey must be a non-empty string.\");\n }\n this.runKey = runKey;\n this.path = Object.freeze([...path]);\n this.attempt = Math.max(1, Math.trunc(attempt));\n this.firstAttemptAt = firstAttemptAt;\n Object.freeze(this);\n }\n\n /**\n * The identity of one execution of one node — stable across retries of that\n * execution, distinct from every other execution of the same node.\n *\n * Pass `occurrence` when an executor runs the same node more than once at the\n * same level (a loop body, one item of a fan-out it drives itself).\n */\n stepKey(nodeId: string, occurrence?: number | null): string {\n return `${this.runKey}:${[...this.path, renderSegment(nodeId, occurrence)].join(\"/\")}`;\n }\n\n /**\n * A child identity for work nested inside this step.\n *\n * `subflow` pushes the invoking node's id, so a node inside the child graph\n * cannot collide with a same-named node in the parent. Attempt and\n * `firstAttemptAt` are carried down unchanged: the nested work happens inside\n * this step's attempt, and shares its clock.\n */\n descend(segment: string, occurrence?: number | null): RunIdentity {\n return new RunIdentity(\n this.runKey,\n [...this.path, renderSegment(segment, occurrence)],\n this.attempt,\n this.firstAttemptAt,\n );\n }\n\n /** A copy on a different attempt, first-attempt clock preserved. */\n withAttempt(attempt: number, firstAttemptAt?: string): RunIdentity {\n return new RunIdentity(\n this.runKey,\n this.path,\n attempt,\n firstAttemptAt ?? this.firstAttemptAt,\n );\n }\n\n /**\n * May this attempt reuse the step key and still be deduplicated?\n *\n * Providers forget idempotency keys — Stripe after 24 hours. Past that\n * window, resending the key creates a second charge and sending a fresh one\n * creates a second charge, so **the caller must refuse rather than pick\n * between them**: a loud stuck run beats a silent double write.\n *\n * `true` on attempt 1 whatever the elapsed time — nothing was sent on an\n * earlier attempt, so there is nothing for the provider to have forgotten.\n * That is what lets a run park on a human gate for a week and then write.\n *\n * `windowSeconds: null` means the provider does not expire keys. `0` means\n * it does not dedupe at all, so no retry may reuse a key — it is a real\n * window, not an absent one, and the two must not be conflated: reading `0`\n * as `null` turns \"this provider does not dedupe\" into \"this provider\n * dedupes forever\", which is the more dangerous of the two by a distance.\n */\n isReplaySafe(windowSeconds: number | null | undefined, now: Date | string = new Date()): boolean {\n if (this.attempt <= 1) return true;\n if (windowSeconds === null || windowSeconds === undefined) return true;\n if (windowSeconds <= 0) return false;\n\n const nowMs = typeof now === \"string\" ? instant(now) : now.getTime();\n // Clock skew between two workers must not turn a legitimate retry into a\n // refusal, so a negative elapsed clamps to zero.\n const elapsedSeconds = Math.max(0, (nowMs - instant(this.firstAttemptAt)) / 1000);\n\n // Inclusive: a key written at T is remembered THROUGH T + window.\n return elapsedSeconds <= windowSeconds;\n }\n\n toJSON(): Required<RunIdentityJson> {\n return {\n runKey: this.runKey,\n path: [...this.path],\n attempt: this.attempt,\n firstAttemptAt: this.firstAttemptAt,\n };\n }\n\n /** Rebuild from a queue payload. */\n static from(value: RunIdentity | RunIdentityJson | string): RunIdentity {\n if (value instanceof RunIdentity) return value;\n if (typeof value === \"string\") return new RunIdentity(value);\n return new RunIdentity(\n value.runKey,\n value.path ?? [],\n value.attempt ?? 1,\n value.firstAttemptAt ?? new Date().toISOString(),\n );\n }\n}\n","import type {\n ExecutorRegistry,\n FlowEdge,\n FlowGraph,\n FlowNode,\n NodeExecutor,\n RunEvent,\n} from \"../types\";\n// Both modules are React-free by design — the `/engine` entry must not pull in\n// React. Import them directly rather than via the `registry` barrel, which\n// re-exports the RegistryNode component.\nimport { getNodeKind, kindIds } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\nimport { RunIdentity, type RunIdentityJson } from \"./run-identity\";\n\nexport type RunOptions = {\n /** Stop the run after this many ms. Default: no timeout. */\n timeoutMs?: number;\n /** Abort signal — host can cancel the run. */\n signal?: AbortSignal;\n /** Initial inputs supplied to entry-point nodes (no incoming edges). */\n initialInputs?: Record<string, Record<string, unknown>>;\n /** Nesting depth — set by `subflow` when it runs a child graph. */\n depth?: number;\n /**\n * Who is running, so a writing node can derive a stable idempotency key.\n *\n * A bare string is taken as the run key. **Deliberately not defaulted:** a\n * minted-per-call key would change on every whole-run retry, which is exactly\n * the failure an idempotency key exists to prevent — so a host that has not\n * supplied one gets `ctx.run === undefined` and a connector that declines to\n * write blind, rather than a plausible-looking key that double-charges.\n */\n run?: RunIdentity | RunIdentityJson | string;\n /**\n * Outputs already checkpointed for this run, keyed by node id.\n *\n * A node present here is **republished, not re-executed** — its stored value\n * goes back onto the same ports, so downstream routing reproduces exactly\n * what it did the first time. This is what makes a per-node durable driver\n * possible without a second copy of the routing rules, and it is how the PHP\n * and Python runtimes have always resumed.\n */\n resumeOutputs?: Record<string, unknown>;\n};\n\nexport type RunResult = {\n ok: boolean;\n /** Outputs collected per node, keyed by node id. */\n outputs: Record<string, unknown>;\n /** Error captured if any node threw. */\n error?: string;\n};\n\n/**\n * runFlow — topological execution of a FlowGraph against an ExecutorRegistry.\n *\n * Each node runs once, when all upstream nodes have produced outputs on the\n * connected ports. Decision nodes (or any executor that returns `{ branch:\n * 'true' }`) can short-circuit specific output ports — only edges leaving\n * an \"active\" port propagate to downstream nodes.\n *\n * Cycles are detected and abort the run with an error.\n *\n * The `onEvent` callback receives a stream of `RunEvent`s — wire it to a\n * status feed, log panel, or store.\n */\nexport async function runFlow(\n graph: FlowGraph,\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent) => void = () => {},\n options: RunOptions = {},\n): Promise<RunResult> {\n const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {} } = options;\n const run = options.run === undefined ? undefined : RunIdentity.from(options.run);\n const outputs: Record<string, unknown> = {};\n const portValues = new Map<string, unknown>(); // key: `${nodeId}:${portId}`\n const completed = new Set<string>();\n const errors: string[] = [];\n\n // Topological order via Kahn's algorithm. We allow nodes to run as soon\n // as their incoming edges' source ports have produced values, so the\n // order here is just a deterministic baseline used for cycle detection.\n const order = topoSort(graph);\n if (order === null) {\n const msg = \"Cycle detected in flow graph — aborting.\";\n onEvent({ type: \"run-error\", error: msg });\n return { ok: false, outputs, error: msg };\n }\n\n const incomingByNode = indexIncoming(graph.edges);\n const timer = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;\n\n onEvent({ type: \"run-start\" });\n\n try {\n for (const node of order) {\n if (signal?.aborted) throw new Error(\"aborted\");\n if (errors.length) break;\n\n // A checkpointed node is republished, never re-executed. Publishing the\n // stored value back onto its own ports is what makes the resume exact:\n // the branch it took, the ports it lit and the inputs its successors\n // collect are all reproduced by the engine's own rules rather than by a\n // driver's recollection of them.\n if (Object.prototype.hasOwnProperty.call(resumeOutputs, node.id)) {\n const stored = resumeOutputs[node.id];\n outputs[node.id] = stored;\n const activated = activatedPorts(node, stored);\n for (const portId of activated.ports) {\n portValues.set(`${node.id}:${portId}`, activated.value);\n onEvent({ type: \"node-output\", nodeId: node.id, portId, value: activated.value });\n }\n completed.add(node.id);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"done\", text: \"resumed\" });\n continue;\n }\n\n const incoming = incomingByNode.get(node.id) ?? [];\n\n // Run a node once any upstream branch reaches it. We iterate in\n // topological order, so by the time we reach this node every upstream\n // node has been processed — each incoming edge is therefore *settled*\n // (active or dead, never still-pending). Requiring ALL incoming edges to\n // be active wrongly skipped MERGE POINTS: when a Decision routes down one\n // branch, the other branch's edge stays dead forever, so an `every` check\n // skipped the shared continuation node and halted the run after the first\n // branch (#1). Run when AT LEAST ONE incoming edge is active —\n // collectInputs() only reads from the active ones. A genuine parallel\n // join still works: in topo order both of its inputs are already active.\n if (incoming.length > 0) {\n const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? \"out\"}`));\n if (!anyActive) {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"skipped\" });\n continue;\n }\n }\n\n // Notes/annotations + layout (lane/pool) nodes are visual only — never\n // executed and never fed to runners. Their config (a note's text, a lane's\n // title) stays in the document for editors + MCP tools, but the engine\n // walks straight past them. Edges cross lanes freely, so grouping never\n // affects topology.\n const visualKind = getNodeKind(node.type ?? \"\");\n const isLayout = visualKind?.category === \"layout\";\n const isAnnotation = node.type === \"note\" || visualKind?.category === \"annotation\";\n if (isLayout || isAnnotation) {\n onEvent({\n type: \"node-status\",\n nodeId: node.id,\n status: \"idle\",\n text: isLayout ? \"lane\" : \"annotation\",\n });\n continue;\n }\n\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"running\" });\n\n const inputs = collectInputs(node, incoming, portValues, initialInputs);\n const exec = pickExecutor(executors, node);\n if (!exec) {\n const msg = `No executor registered for kind=${node.type}`;\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n\n try {\n const result = await Promise.resolve(\n exec({\n node,\n inputs,\n abort: (reason) => { throw new Error(reason ?? \"aborted\"); },\n emit: onEvent,\n depth,\n run,\n }),\n );\n outputs[node.id] = result;\n\n // Decide which output ports were activated. Three conventions:\n // 1) If result is `{ __port: \"out\", value: x }`, only that port emits.\n // 2) If result has `branch: <portId>`, only that port emits (decision sugar).\n // 3) Otherwise, the value is published on every declared output port.\n const activated = activatedPorts(node, result);\n for (const portId of activated.ports) {\n portValues.set(`${node.id}:${portId}`, activated.value);\n onEvent({ type: \"node-output\", nodeId: node.id, portId, value: activated.value });\n }\n completed.add(node.id);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"done\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n }\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const ok = errors.length === 0;\n onEvent({ type: \"run-end\", ok });\n return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };\n}\n\nfunction indexIncoming(edges: FlowEdge[]): Map<string, FlowEdge[]> {\n const map = new Map<string, FlowEdge[]>();\n for (const e of edges) {\n const list = map.get(e.target) ?? [];\n list.push(e);\n map.set(e.target, list);\n }\n return map;\n}\n\nfunction topoSort(graph: FlowGraph): FlowNode[] | null {\n const inDegree = new Map<string, number>();\n for (const n of graph.nodes) inDegree.set(n.id, 0);\n for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);\n const queue: string[] = [];\n for (const [id, d] of inDegree) if (d === 0) queue.push(id);\n const ordered: string[] = [];\n while (queue.length) {\n const id = queue.shift()!;\n ordered.push(id);\n for (const e of graph.edges) {\n if (e.source !== id) continue;\n const next = (inDegree.get(e.target) ?? 0) - 1;\n inDegree.set(e.target, next);\n if (next === 0) queue.push(e.target);\n }\n }\n if (ordered.length !== graph.nodes.length) return null;\n const byId = new Map(graph.nodes.map((n) => [n.id, n]));\n return ordered.map((id) => byId.get(id)!).filter(Boolean);\n}\n\nfunction collectInputs(\n node: FlowNode,\n incoming: FlowEdge[],\n portValues: Map<string, unknown>,\n initial: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n const inputs: Record<string, unknown> = { ...(initial[node.id] ?? {}) };\n for (const e of incoming) {\n const portId = e.targetHandle ?? \"in\";\n const key = `${e.source}:${e.sourceHandle ?? \"out\"}`;\n // A branch that never fired has no entry in portValues. Assigning its\n // `undefined` would CLOBBER the value the branch that DID fire already\n // contributed on this same handle — emptying every merge point downstream\n // of a decision, silently, with the run still reporting success.\n //\n // This is the other half of the merge-point bug above: that fix stopped the\n // shared continuation node being skipped, but it still arrived with nothing\n // whenever a later inactive edge happened to be last in `incoming`.\n if (!portValues.has(key)) continue;\n inputs[portId] = portValues.get(key);\n }\n return inputs;\n}\n\nfunction pickExecutor(\n executors: ExecutorRegistry,\n node: FlowNode,\n): NodeExecutor | undefined {\n if (executors[node.id]) return executors[node.id];\n if (node.type && executors[node.type]) return executors[node.type];\n\n // Try every id the kind answers to. Kinds are namespaced (`@fancy/switch_case`)\n // while a host may have bound its executor under the bare name it used before\n // — or vice versa. Without this, the rename would silently stop matching and\n // the node would fall through to `*` or simply not run: a breaking change\n // wearing the costume of a rename.\n const kind = node.type ? getNodeKind(node.type) : null;\n if (kind) {\n for (const id of kindIds(kind)) {\n if (executors[id]) return executors[id];\n }\n }\n\n return executors[\"*\"];\n}\n\nfunction activatedPorts(node: FlowNode, result: unknown): { ports: string[]; value: unknown } {\n if (result && typeof result === \"object\") {\n const r = result as Record<string, unknown>;\n if (typeof r.__port === \"string\") {\n return { ports: [r.__port], value: r.value };\n }\n if (typeof r.branch === \"string\") {\n return { ports: [r.branch], value: r.value ?? r };\n }\n }\n // Resolve through the shared helper so the ports the runtime activates are\n // the same ones the canvas drew — including config-driven ports, which the\n // node's `data` does not carry. Falls back to a lone `out` when a node\n // declares nothing.\n const kind = getNodeKind((node.data as any)?.kind ?? node.type ?? \"\") ?? undefined;\n const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);\n return { ports: declared?.length ? declared : [\"out\"], value: result };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { applyNodeChanges, applyEdgeChanges, addEdge } from './chunk-OWENS2H5.js';
2
- import { runFlow } from './chunk-VLATLVGH.js';
2
+ import { runFlow } from './chunk-L43CWD63.js';
3
3
  import { getNodeKind } from './chunk-PVROYW7C.js';
4
4
  import { useState, useRef, useCallback, useMemo } from 'react';
5
5
 
@@ -231,5 +231,5 @@ function useFlowHistory(flow) {
231
231
  }
232
232
 
233
233
  export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState };
234
- //# sourceMappingURL=chunk-A6RFLGWV.js.map
235
- //# sourceMappingURL=chunk-A6RFLGWV.js.map
234
+ //# sourceMappingURL=chunk-MH4ZW4DJ.js.map
235
+ //# sourceMappingURL=chunk-MH4ZW4DJ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime/use-flow-run.ts","../src/runtime/use-flow-state.ts","../src/runtime/history.ts","../src/runtime/use-flow-history.ts"],"names":["useState","useCallback","useRef"],"mappings":"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","file":"chunk-A6RFLGWV.js","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\nimport { getNodeKind } from \"../registry/registry\";\nimport type {\n ExecutorRegistry,\n FlowGraph,\n NodeRunStatus,\n RunEvent,\n} from \"../types\";\n\nexport type FlowRunFeedEntry = {\n id: string;\n at: number;\n level: \"info\" | \"warn\" | \"error\" | \"status\";\n text: string;\n nodeId?: string;\n detail?: unknown;\n};\n\nexport type UseFlowRunReturn = {\n /** Status keyed by nodeId — drive the UI overlay from this. */\n statuses: Record<string, NodeRunStatus>;\n /** Per-node status text (e.g. error message). */\n statusText: Record<string, string | undefined>;\n /** Latest computed output value per node (for reactive kinds). */\n outputs: Record<string, unknown>;\n /** Live event log (capped to last N). */\n feed: FlowRunFeedEntry[];\n /** Whether a run is currently in progress. */\n running: boolean;\n /** Last run result, or null. */\n lastResult: RunResult | null;\n /** Kick off a run with the provided graph + executors. */\n run: (graph: FlowGraph, executors: ExecutorRegistry, options?: RunOptions) => Promise<RunResult>;\n /** Cancel the current run (if any). */\n cancel: () => void;\n /** Reset all runtime state (statuses, feed, lastResult). */\n reset: () => void;\n};\n\nexport type UseFlowRunOptions = {\n /** Cap the in-memory feed to this many entries. Default 200. */\n maxFeed?: number;\n};\n\n/**\n * useFlowRun — drives `runFlow` + maintains observability state. Pair with\n * `applyStatusesToNodes` (below) before passing nodes to `<FlowCanvas>` so\n * the per-node status badge renders.\n */\nexport function useFlowRun({ maxFeed = 200 }: UseFlowRunOptions = {}): UseFlowRunReturn {\n const [statuses, setStatuses] = useState<Record<string, NodeRunStatus>>({});\n const [statusText, setStatusText] = useState<Record<string, string | undefined>>({});\n const [outputs, setOutputs] = useState<Record<string, unknown>>({});\n const [feed, setFeed] = useState<FlowRunFeedEntry[]>([]);\n const [running, setRunning] = useState(false);\n const [lastResult, setLastResult] = useState<RunResult | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const handleEvent = useCallback(\n (e: RunEvent) => {\n switch (e.type) {\n case \"node-status\":\n setStatuses((s) => ({ ...s, [e.nodeId]: e.status }));\n setStatusText((t) => ({ ...t, [e.nodeId]: e.text }));\n appendFeed({ level: \"status\", text: `${e.nodeId} → ${e.status}${e.text ? ` (${e.text})` : \"\"}`, nodeId: e.nodeId });\n break;\n case \"node-output\":\n setOutputs((o) => ({ ...o, [e.nodeId]: e.value }));\n appendFeed({ level: \"info\", text: `${e.nodeId}.${e.portId} = ${preview(e.value)}`, nodeId: e.nodeId, detail: e.value });\n break;\n case \"log\":\n appendFeed({ level: e.level, text: e.message, nodeId: e.nodeId, detail: e.detail });\n break;\n case \"run-start\":\n appendFeed({ level: \"info\", text: \"▶ run started\" });\n break;\n case \"run-end\":\n appendFeed({ level: e.ok ? \"info\" : \"error\", text: e.ok ? \"✓ run complete\" : \"✗ run failed\" });\n break;\n case \"run-error\":\n appendFeed({ level: \"error\", text: e.error });\n break;\n }\n function appendFeed(partial: Omit<FlowRunFeedEntry, \"id\" | \"at\">) {\n setFeed((f) => {\n const entry: FlowRunFeedEntry = { id: `${Date.now()}_${f.length}`, at: Date.now(), ...partial };\n const next = [...f, entry];\n return next.length > maxFeed ? next.slice(next.length - maxFeed) : next;\n });\n }\n },\n [maxFeed],\n );\n\n const run = useCallback(\n async (graph: FlowGraph, executors: ExecutorRegistry, options: RunOptions = {}) => {\n if (running) {\n return { ok: false, outputs: {}, error: \"another run is already in progress\" } satisfies RunResult;\n }\n const controller = new AbortController();\n abortRef.current = controller;\n // Reset previous statuses for the nodes we're about to run.\n const idleStatuses: Record<string, NodeRunStatus> = {};\n for (const n of graph.nodes) idleStatuses[n.id] = \"idle\";\n setStatuses(idleStatuses);\n setStatusText({});\n setOutputs({});\n setRunning(true);\n try {\n const result = await runFlow(graph, executors, handleEvent, { ...options, signal: controller.signal });\n setLastResult(result);\n return result;\n } finally {\n setRunning(false);\n abortRef.current = null;\n }\n },\n [handleEvent, running],\n );\n\n const cancel = useCallback(() => abortRef.current?.abort(), []);\n\n const reset = useCallback(() => {\n setStatuses({});\n setStatusText({});\n setOutputs({});\n setFeed([]);\n setLastResult(null);\n }, []);\n\n return { statuses, statusText, outputs, feed, running, lastResult, run, cancel, reset };\n}\n\n/**\n * Write each reactive kind's latest run output into its `data.output`, so its\n * card can render the computed value live. Non-reactive kinds are returned\n * untouched. Pair with `applyStatusesToNodes` before rendering.\n */\nexport function applyOutputsToNodes<TNode extends { id: string; type?: string; data: any }>(\n nodes: TNode[],\n outputs: Record<string, unknown>,\n): TNode[] {\n return nodes.map((n) => {\n const kind = getNodeKind((n.data as any)?.kind ?? n.type ?? \"\");\n if (!kind?.reactive || !(n.id in outputs)) return n;\n return { ...n, data: { ...n.data, output: outputs[n.id] } };\n });\n}\n\n/** Merge runtime statuses into nodes for rendering. */\nexport function applyStatusesToNodes<TNode extends { id: string; data: any }>(\n nodes: TNode[],\n statuses: Record<string, NodeRunStatus>,\n statusText: Record<string, string | undefined>,\n): TNode[] {\n return nodes.map((n) => ({\n ...n,\n data: {\n ...n.data,\n status: statuses[n.id] ?? n.data?.status ?? \"idle\",\n statusText: statusText[n.id] ?? n.data?.statusText,\n },\n }));\n}\n\nfunction preview(v: unknown): string {\n try {\n const s = JSON.stringify(v);\n return s && s.length > 60 ? s.slice(0, 57) + \"…\" : (s ?? String(v));\n } catch {\n return String(v);\n }\n}\n","import { useCallback, useState } from \"react\";\nimport {\n addEdge,\n applyEdgeChanges,\n applyNodeChanges,\n type Connection,\n type Edge,\n type EdgeChange,\n type NodeChange,\n} from \"@xyflow/react\";\nimport type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\n\nexport type UseFlowStateReturn = {\n nodes: FlowNode[];\n edges: FlowEdge[];\n setNodes: React.Dispatch<React.SetStateAction<FlowNode[]>>;\n setEdges: React.Dispatch<React.SetStateAction<FlowEdge[]>>;\n /**\n * Replace nodes AND edges atomically in a single commit. Use this for any op\n * that touches both (delete-with-edge-prune, undo/redo restore, setGraph):\n * calling `setNodes` then `setEdges` is NOT atomic in controlled mode (each\n * closes over a stale `value`), so the second write clobbers the first.\n */\n setGraph: (graph: FlowGraph) => void;\n onNodesChange: (changes: NodeChange[]) => void;\n onEdgesChange: (changes: EdgeChange[]) => void;\n onConnect: (connection: Connection) => void;\n /** Snapshot the current graph (suitable for serialization). */\n toGraph: () => FlowGraph;\n};\n\n/**\n * useFlowState — React Flow's standard controlled-state plumbing in one hook.\n * Spread into <FlowCanvas>.\n */\nexport function useFlowState(initial: FlowGraph): UseFlowStateReturn {\n const [nodes, setNodes] = useState<FlowNode[]>(initial.nodes);\n const [edges, setEdges] = useState<FlowEdge[]>(initial.edges);\n\n const onNodesChange = useCallback((changes: NodeChange[]) => {\n setNodes((ns) => applyNodeChanges(changes, ns) as FlowNode[]);\n }, []);\n const onEdgesChange = useCallback((changes: EdgeChange[]) => {\n setEdges((es) => applyEdgeChanges(changes, es));\n }, []);\n const onConnect = useCallback((connection: Connection) => {\n setEdges((es) => addEdge(connection, es) as Edge[]);\n }, []);\n\n const setGraph = useCallback((graph: FlowGraph) => {\n // Two useState writes in one event are batched, so this IS atomic here.\n setNodes(graph.nodes);\n setEdges(graph.edges);\n }, []);\n\n const toGraph = useCallback(() => ({ nodes, edges }), [nodes, edges]);\n\n return { nodes, edges, setNodes, setEdges, setGraph, onNodesChange, onEdgesChange, onConnect, toGraph };\n}\n","import type { FlowGraph } from \"../types\";\n\n/**\n * Undo/redo history — a pure, React-free snapshot controller. The editor pushes\n * a graph snapshot BEFORE each committing mutation; `undo`/`redo` swap snapshots\n * between the past/future stacks. Kept free of React so it can be unit-tested\n * and reused by any host (or a headless driver).\n *\n * Coalescing (collapsing a burst of setState calls from one logical op into a\n * single undo step) is the caller's job — see `useFlowHistory`.\n */\nexport type HistoryController = {\n /** Record `graph` as an undo point. Clears the redo stack (new branch). */\n push: (graph: FlowGraph) => void;\n /** Pop the last undo point; `current` is pushed onto the redo stack. */\n undo: (current: FlowGraph) => FlowGraph | null;\n /** Pop the last redo point; `current` is pushed back onto the undo stack. */\n redo: (current: FlowGraph) => FlowGraph | null;\n canUndo: () => boolean;\n canRedo: () => boolean;\n clear: () => void;\n /** Test/inspection helper — sizes of the two stacks. */\n size: () => { past: number; future: number };\n};\n\nexport function createHistory(limit = 100): HistoryController {\n let past: FlowGraph[] = [];\n let future: FlowGraph[] = [];\n\n return {\n push(graph) {\n past.push(graph);\n if (past.length > limit) past.shift();\n future = [];\n },\n undo(current) {\n const prev = past.pop();\n if (prev === undefined) return null;\n future.push(current);\n return prev;\n },\n redo(current) {\n const next = future.pop();\n if (next === undefined) return null;\n past.push(current);\n return next;\n },\n canUndo: () => past.length > 0,\n canRedo: () => future.length > 0,\n clear() {\n past = [];\n future = [];\n },\n size: () => ({ past: past.length, future: future.length }),\n };\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { EdgeChange, NodeChange } from \"@xyflow/react\";\nimport type { FlowGraph } from \"../types\";\nimport { createHistory } from \"./history\";\nimport type { UseFlowStateReturn } from \"./use-flow-state\";\n\nexport type UseFlowHistoryReturn = {\n /** The flow sink wrapped so committing mutations snapshot for undo first. */\n flow: UseFlowStateReturn;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n /** Snapshot the current graph as an undo point (before a programmatic edit). */\n capture: () => void;\n /** Wire to `<FlowCanvas onNodeDragStart>` — snapshots the pre-drag graph. */\n onNodeDragStart: () => void;\n};\n\n/**\n * useFlowHistory — the commit/undo pipeline. Wraps a flow sink (the uncontrolled\n * `useFlowState` hook OR the controlled adapter) so every *committing* mutation\n * records a snapshot first, giving undo/redo without the editor threading\n * history through each call site. This is the single interception point the\n * two-sink architecture otherwise lacks.\n *\n * Granularity: a burst of setState calls from one logical op (e.g. a\n * delete = setNodes+setEdges) is coalesced into ONE undo step; transient\n * interactive changes (drag-move, dimension-measure, selection) are NOT captured\n * — a drag is captured once at `onNodeDragStart` instead.\n */\nexport function useFlowHistory(flow: UseFlowStateReturn): UseFlowHistoryReturn {\n const history = useRef(createHistory()).current;\n const restoring = useRef(false);\n const coalesced = useRef(false);\n const [, bump] = useState(0);\n const rerender = useCallback(() => bump((n) => n + 1), []);\n\n // Latest graph via a ref, so capture never reads a stale snapshot.\n const graphRef = useRef<FlowGraph>({ nodes: flow.nodes, edges: flow.edges });\n graphRef.current = { nodes: flow.nodes, edges: flow.edges };\n\n const capture = useCallback(() => {\n if (restoring.current || coalesced.current) return;\n coalesced.current = true;\n queueMicrotask(() => {\n coalesced.current = false;\n });\n history.push(graphRef.current);\n rerender();\n }, [history, rerender]);\n\n const restore = useCallback(\n (g: FlowGraph | null) => {\n if (!g) return;\n restoring.current = true;\n flow.setGraph(g);\n queueMicrotask(() => {\n restoring.current = false;\n });\n rerender();\n },\n [flow, rerender],\n );\n\n const undo = useCallback(() => restore(history.undo(graphRef.current)), [history, restore]);\n const redo = useCallback(() => restore(history.redo(graphRef.current)), [history, restore]);\n\n const wrapped = useMemo<UseFlowStateReturn>(\n () => ({\n ...flow,\n setNodes: (next) => {\n capture();\n flow.setNodes(next);\n },\n setEdges: (next) => {\n capture();\n flow.setEdges(next);\n },\n setGraph: (g) => {\n capture();\n flow.setGraph(g);\n },\n onNodesChange: (changes: NodeChange[]) => {\n // Only structural removes commit; position/dimensions/select are transient.\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onNodesChange(changes);\n },\n onEdgesChange: (changes: EdgeChange[]) => {\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onEdgesChange(changes);\n },\n onConnect: (conn) => {\n capture();\n flow.onConnect(conn);\n },\n }),\n [flow, capture],\n );\n\n return {\n flow: wrapped,\n undo,\n redo,\n canUndo: history.canUndo(),\n canRedo: history.canRedo(),\n capture,\n onNodeDragStart: capture,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/runtime/use-flow-run.ts","../src/runtime/use-flow-state.ts","../src/runtime/history.ts","../src/runtime/use-flow-history.ts"],"names":["useState","useCallback","useRef"],"mappings":"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","file":"chunk-MH4ZW4DJ.js","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\nimport { getNodeKind } from \"../registry/registry\";\nimport type {\n ExecutorRegistry,\n FlowGraph,\n NodeRunStatus,\n RunEvent,\n} from \"../types\";\n\nexport type FlowRunFeedEntry = {\n id: string;\n at: number;\n level: \"info\" | \"warn\" | \"error\" | \"status\";\n text: string;\n nodeId?: string;\n detail?: unknown;\n};\n\nexport type UseFlowRunReturn = {\n /** Status keyed by nodeId — drive the UI overlay from this. */\n statuses: Record<string, NodeRunStatus>;\n /** Per-node status text (e.g. error message). */\n statusText: Record<string, string | undefined>;\n /** Latest computed output value per node (for reactive kinds). */\n outputs: Record<string, unknown>;\n /** Live event log (capped to last N). */\n feed: FlowRunFeedEntry[];\n /** Whether a run is currently in progress. */\n running: boolean;\n /** Last run result, or null. */\n lastResult: RunResult | null;\n /** Kick off a run with the provided graph + executors. */\n run: (graph: FlowGraph, executors: ExecutorRegistry, options?: RunOptions) => Promise<RunResult>;\n /** Cancel the current run (if any). */\n cancel: () => void;\n /** Reset all runtime state (statuses, feed, lastResult). */\n reset: () => void;\n};\n\nexport type UseFlowRunOptions = {\n /** Cap the in-memory feed to this many entries. Default 200. */\n maxFeed?: number;\n};\n\n/**\n * useFlowRun — drives `runFlow` + maintains observability state. Pair with\n * `applyStatusesToNodes` (below) before passing nodes to `<FlowCanvas>` so\n * the per-node status badge renders.\n */\nexport function useFlowRun({ maxFeed = 200 }: UseFlowRunOptions = {}): UseFlowRunReturn {\n const [statuses, setStatuses] = useState<Record<string, NodeRunStatus>>({});\n const [statusText, setStatusText] = useState<Record<string, string | undefined>>({});\n const [outputs, setOutputs] = useState<Record<string, unknown>>({});\n const [feed, setFeed] = useState<FlowRunFeedEntry[]>([]);\n const [running, setRunning] = useState(false);\n const [lastResult, setLastResult] = useState<RunResult | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const handleEvent = useCallback(\n (e: RunEvent) => {\n switch (e.type) {\n case \"node-status\":\n setStatuses((s) => ({ ...s, [e.nodeId]: e.status }));\n setStatusText((t) => ({ ...t, [e.nodeId]: e.text }));\n appendFeed({ level: \"status\", text: `${e.nodeId} → ${e.status}${e.text ? ` (${e.text})` : \"\"}`, nodeId: e.nodeId });\n break;\n case \"node-output\":\n setOutputs((o) => ({ ...o, [e.nodeId]: e.value }));\n appendFeed({ level: \"info\", text: `${e.nodeId}.${e.portId} = ${preview(e.value)}`, nodeId: e.nodeId, detail: e.value });\n break;\n case \"log\":\n appendFeed({ level: e.level, text: e.message, nodeId: e.nodeId, detail: e.detail });\n break;\n case \"run-start\":\n appendFeed({ level: \"info\", text: \"▶ run started\" });\n break;\n case \"run-end\":\n appendFeed({ level: e.ok ? \"info\" : \"error\", text: e.ok ? \"✓ run complete\" : \"✗ run failed\" });\n break;\n case \"run-error\":\n appendFeed({ level: \"error\", text: e.error });\n break;\n }\n function appendFeed(partial: Omit<FlowRunFeedEntry, \"id\" | \"at\">) {\n setFeed((f) => {\n const entry: FlowRunFeedEntry = { id: `${Date.now()}_${f.length}`, at: Date.now(), ...partial };\n const next = [...f, entry];\n return next.length > maxFeed ? next.slice(next.length - maxFeed) : next;\n });\n }\n },\n [maxFeed],\n );\n\n const run = useCallback(\n async (graph: FlowGraph, executors: ExecutorRegistry, options: RunOptions = {}) => {\n if (running) {\n return { ok: false, outputs: {}, error: \"another run is already in progress\" } satisfies RunResult;\n }\n const controller = new AbortController();\n abortRef.current = controller;\n // Reset previous statuses for the nodes we're about to run.\n const idleStatuses: Record<string, NodeRunStatus> = {};\n for (const n of graph.nodes) idleStatuses[n.id] = \"idle\";\n setStatuses(idleStatuses);\n setStatusText({});\n setOutputs({});\n setRunning(true);\n try {\n const result = await runFlow(graph, executors, handleEvent, { ...options, signal: controller.signal });\n setLastResult(result);\n return result;\n } finally {\n setRunning(false);\n abortRef.current = null;\n }\n },\n [handleEvent, running],\n );\n\n const cancel = useCallback(() => abortRef.current?.abort(), []);\n\n const reset = useCallback(() => {\n setStatuses({});\n setStatusText({});\n setOutputs({});\n setFeed([]);\n setLastResult(null);\n }, []);\n\n return { statuses, statusText, outputs, feed, running, lastResult, run, cancel, reset };\n}\n\n/**\n * Write each reactive kind's latest run output into its `data.output`, so its\n * card can render the computed value live. Non-reactive kinds are returned\n * untouched. Pair with `applyStatusesToNodes` before rendering.\n */\nexport function applyOutputsToNodes<TNode extends { id: string; type?: string; data: any }>(\n nodes: TNode[],\n outputs: Record<string, unknown>,\n): TNode[] {\n return nodes.map((n) => {\n const kind = getNodeKind((n.data as any)?.kind ?? n.type ?? \"\");\n if (!kind?.reactive || !(n.id in outputs)) return n;\n return { ...n, data: { ...n.data, output: outputs[n.id] } };\n });\n}\n\n/** Merge runtime statuses into nodes for rendering. */\nexport function applyStatusesToNodes<TNode extends { id: string; data: any }>(\n nodes: TNode[],\n statuses: Record<string, NodeRunStatus>,\n statusText: Record<string, string | undefined>,\n): TNode[] {\n return nodes.map((n) => ({\n ...n,\n data: {\n ...n.data,\n status: statuses[n.id] ?? n.data?.status ?? \"idle\",\n statusText: statusText[n.id] ?? n.data?.statusText,\n },\n }));\n}\n\nfunction preview(v: unknown): string {\n try {\n const s = JSON.stringify(v);\n return s && s.length > 60 ? s.slice(0, 57) + \"…\" : (s ?? String(v));\n } catch {\n return String(v);\n }\n}\n","import { useCallback, useState } from \"react\";\nimport {\n addEdge,\n applyEdgeChanges,\n applyNodeChanges,\n type Connection,\n type Edge,\n type EdgeChange,\n type NodeChange,\n} from \"@xyflow/react\";\nimport type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\n\nexport type UseFlowStateReturn = {\n nodes: FlowNode[];\n edges: FlowEdge[];\n setNodes: React.Dispatch<React.SetStateAction<FlowNode[]>>;\n setEdges: React.Dispatch<React.SetStateAction<FlowEdge[]>>;\n /**\n * Replace nodes AND edges atomically in a single commit. Use this for any op\n * that touches both (delete-with-edge-prune, undo/redo restore, setGraph):\n * calling `setNodes` then `setEdges` is NOT atomic in controlled mode (each\n * closes over a stale `value`), so the second write clobbers the first.\n */\n setGraph: (graph: FlowGraph) => void;\n onNodesChange: (changes: NodeChange[]) => void;\n onEdgesChange: (changes: EdgeChange[]) => void;\n onConnect: (connection: Connection) => void;\n /** Snapshot the current graph (suitable for serialization). */\n toGraph: () => FlowGraph;\n};\n\n/**\n * useFlowState — React Flow's standard controlled-state plumbing in one hook.\n * Spread into <FlowCanvas>.\n */\nexport function useFlowState(initial: FlowGraph): UseFlowStateReturn {\n const [nodes, setNodes] = useState<FlowNode[]>(initial.nodes);\n const [edges, setEdges] = useState<FlowEdge[]>(initial.edges);\n\n const onNodesChange = useCallback((changes: NodeChange[]) => {\n setNodes((ns) => applyNodeChanges(changes, ns) as FlowNode[]);\n }, []);\n const onEdgesChange = useCallback((changes: EdgeChange[]) => {\n setEdges((es) => applyEdgeChanges(changes, es));\n }, []);\n const onConnect = useCallback((connection: Connection) => {\n setEdges((es) => addEdge(connection, es) as Edge[]);\n }, []);\n\n const setGraph = useCallback((graph: FlowGraph) => {\n // Two useState writes in one event are batched, so this IS atomic here.\n setNodes(graph.nodes);\n setEdges(graph.edges);\n }, []);\n\n const toGraph = useCallback(() => ({ nodes, edges }), [nodes, edges]);\n\n return { nodes, edges, setNodes, setEdges, setGraph, onNodesChange, onEdgesChange, onConnect, toGraph };\n}\n","import type { FlowGraph } from \"../types\";\n\n/**\n * Undo/redo history — a pure, React-free snapshot controller. The editor pushes\n * a graph snapshot BEFORE each committing mutation; `undo`/`redo` swap snapshots\n * between the past/future stacks. Kept free of React so it can be unit-tested\n * and reused by any host (or a headless driver).\n *\n * Coalescing (collapsing a burst of setState calls from one logical op into a\n * single undo step) is the caller's job — see `useFlowHistory`.\n */\nexport type HistoryController = {\n /** Record `graph` as an undo point. Clears the redo stack (new branch). */\n push: (graph: FlowGraph) => void;\n /** Pop the last undo point; `current` is pushed onto the redo stack. */\n undo: (current: FlowGraph) => FlowGraph | null;\n /** Pop the last redo point; `current` is pushed back onto the undo stack. */\n redo: (current: FlowGraph) => FlowGraph | null;\n canUndo: () => boolean;\n canRedo: () => boolean;\n clear: () => void;\n /** Test/inspection helper — sizes of the two stacks. */\n size: () => { past: number; future: number };\n};\n\nexport function createHistory(limit = 100): HistoryController {\n let past: FlowGraph[] = [];\n let future: FlowGraph[] = [];\n\n return {\n push(graph) {\n past.push(graph);\n if (past.length > limit) past.shift();\n future = [];\n },\n undo(current) {\n const prev = past.pop();\n if (prev === undefined) return null;\n future.push(current);\n return prev;\n },\n redo(current) {\n const next = future.pop();\n if (next === undefined) return null;\n past.push(current);\n return next;\n },\n canUndo: () => past.length > 0,\n canRedo: () => future.length > 0,\n clear() {\n past = [];\n future = [];\n },\n size: () => ({ past: past.length, future: future.length }),\n };\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { EdgeChange, NodeChange } from \"@xyflow/react\";\nimport type { FlowGraph } from \"../types\";\nimport { createHistory } from \"./history\";\nimport type { UseFlowStateReturn } from \"./use-flow-state\";\n\nexport type UseFlowHistoryReturn = {\n /** The flow sink wrapped so committing mutations snapshot for undo first. */\n flow: UseFlowStateReturn;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n /** Snapshot the current graph as an undo point (before a programmatic edit). */\n capture: () => void;\n /** Wire to `<FlowCanvas onNodeDragStart>` — snapshots the pre-drag graph. */\n onNodeDragStart: () => void;\n};\n\n/**\n * useFlowHistory — the commit/undo pipeline. Wraps a flow sink (the uncontrolled\n * `useFlowState` hook OR the controlled adapter) so every *committing* mutation\n * records a snapshot first, giving undo/redo without the editor threading\n * history through each call site. This is the single interception point the\n * two-sink architecture otherwise lacks.\n *\n * Granularity: a burst of setState calls from one logical op (e.g. a\n * delete = setNodes+setEdges) is coalesced into ONE undo step; transient\n * interactive changes (drag-move, dimension-measure, selection) are NOT captured\n * — a drag is captured once at `onNodeDragStart` instead.\n */\nexport function useFlowHistory(flow: UseFlowStateReturn): UseFlowHistoryReturn {\n const history = useRef(createHistory()).current;\n const restoring = useRef(false);\n const coalesced = useRef(false);\n const [, bump] = useState(0);\n const rerender = useCallback(() => bump((n) => n + 1), []);\n\n // Latest graph via a ref, so capture never reads a stale snapshot.\n const graphRef = useRef<FlowGraph>({ nodes: flow.nodes, edges: flow.edges });\n graphRef.current = { nodes: flow.nodes, edges: flow.edges };\n\n const capture = useCallback(() => {\n if (restoring.current || coalesced.current) return;\n coalesced.current = true;\n queueMicrotask(() => {\n coalesced.current = false;\n });\n history.push(graphRef.current);\n rerender();\n }, [history, rerender]);\n\n const restore = useCallback(\n (g: FlowGraph | null) => {\n if (!g) return;\n restoring.current = true;\n flow.setGraph(g);\n queueMicrotask(() => {\n restoring.current = false;\n });\n rerender();\n },\n [flow, rerender],\n );\n\n const undo = useCallback(() => restore(history.undo(graphRef.current)), [history, restore]);\n const redo = useCallback(() => restore(history.redo(graphRef.current)), [history, restore]);\n\n const wrapped = useMemo<UseFlowStateReturn>(\n () => ({\n ...flow,\n setNodes: (next) => {\n capture();\n flow.setNodes(next);\n },\n setEdges: (next) => {\n capture();\n flow.setEdges(next);\n },\n setGraph: (g) => {\n capture();\n flow.setGraph(g);\n },\n onNodesChange: (changes: NodeChange[]) => {\n // Only structural removes commit; position/dimensions/select are transient.\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onNodesChange(changes);\n },\n onEdgesChange: (changes: EdgeChange[]) => {\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onEdgesChange(changes);\n },\n onConnect: (conn) => {\n capture();\n flow.onConnect(conn);\n },\n }),\n [flow, capture],\n );\n\n return {\n flow: wrapped,\n undo,\n redo,\n canUndo: history.canUndo(),\n canRedo: history.canRedo(),\n capture,\n onNodeDragStart: capture,\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { runFlow } from './chunk-VLATLVGH.js';
1
+ import { runFlow } from './chunk-L43CWD63.js';
2
2
 
3
3
  // src/runtime/run-cohort.ts
4
4
  async function runCohort(flows, executors, onEvent = () => {
@@ -46,5 +46,5 @@ function errorText(error) {
46
46
  }
47
47
 
48
48
  export { runCohort };
49
- //# sourceMappingURL=chunk-QZTGV3ZL.js.map
50
- //# sourceMappingURL=chunk-QZTGV3ZL.js.map
49
+ //# sourceMappingURL=chunk-P5SYH6M6.js.map
50
+ //# sourceMappingURL=chunk-P5SYH6M6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime/run-cohort.ts"],"names":[],"mappings":";;;AAoFA,eAAsB,SAAA,CACpB,KAAA,EACA,SAAA,EACA,OAAA,GAAoD,MAAM;AAAC,CAAA,EAC3D,OAAA,GAAyB,EAAC,EACD;AACzB,EAAA,MAAM,EAAE,MAAA,GAAS,gBAAA,EAAkB,OAAO,MAAA,EAAQ,GAAG,YAAW,GAAI,OAAA;AAEpE,EAAA,IAAI,WAAW,UAAA,EAAY;AACzB,IAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,MACb,KAAA,CAAM,GAAA,CAAI,OAAO,IAAA,EAAM,KAAA,MAAW;AAAA,QAChC,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,QACvE;AAAA,OACF,CAAE;AAAA,KACJ;AAAA,EACF;AAEA,EAAA,MAAM,UAA0B,EAAC;AAEjC,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AAC3C,IAAA,IAAI,MAAA,KAAW,oBAAoB,KAAA,EAAO;AACxC,MAAA,MAAM,GAAA,GAAM,MAAM,MAAA,GAAS,IAAA,EAAM,KAAK,CAAA,IAAK,sCAAA;AAC3C,MAAA,IAAI,MAAA;AAEJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,MAClC,SAAS,KAAA,EAAO;AAGd,QAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,UACX,EAAA,EAAI,KAAA;AAAA,UACJ,SAAS,EAAC;AAAA,UACV,KAAA;AAAA,UACA,OAAA,EAAS,IAAA;AAAA,UACT,aAAA,EAAe,CAAA,8BAAA,EAAiC,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,SACjE,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,EAAA,EAAI,KAAA,EAAO,OAAA,EAAS,EAAC,EAAG,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,aAAA,EAAe,GAAA,IAAO,CAAA;AACnF,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,MACvE;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,OAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC9D","file":"chunk-QZTGV3ZL.js","sourcesContent":["import type { ExecutorRegistry, FlowGraph, RunEvent } from \"../types\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\n\n/**\n * How the flows one trigger fired should treat each other.\n *\n * - `serial-guarded` (default) — run in the declared order, one at a time,\n * re-checking `guard` immediately before each. This is the safe default\n * because it is the only one that notices when an earlier flow invalidated\n * the state a later one was fired for.\n * - `serial` — ordered, one at a time, unguarded.\n * - `parallel` — all at once. Ordering is incidental and collisions are yours to\n * handle; correct for fan-outs that share no state, and only those.\n */\nexport type CohortPolicy = \"serial-guarded\" | \"serial\" | \"parallel\";\n\n/**\n * The precondition re-checked just before each flow starts.\n *\n * Return `false` (or throw) to skip that flow. The engine cannot know what\n * \"still valid\" means for your data, so it asks you — right before the run,\n * not at dispatch, because the whole hazard is what changed in between.\n */\nexport type CohortGuard = (\n flow: FlowGraph,\n index: number,\n) => boolean | Promise<boolean>;\n\nexport type CohortOptions = RunOptions & {\n policy?: CohortPolicy;\n guard?: CohortGuard;\n /** Why the guard said no — recorded on the skipped result. */\n reason?: (flow: FlowGraph, index: number) => string;\n};\n\nexport type CohortResult = RunResult & {\n index: number;\n /** True when the flow never ran because its guard did not pass. */\n skipped?: boolean;\n skippedReason?: string;\n};\n\n/**\n * runCohort — run every flow that one trigger event fired, as a group.\n *\n * ## Why this exists\n *\n * `runFlow` runs one graph. A host that fans a single webhook, schedule, or\n * record change out to several flows usually loops it — and a loop, or worse a\n * `Promise.all`, has no answer for the case that actually bites: one of those\n * flows deletes or mutates the record they were ALL fired for. The others then\n * run against state that is no longer there and resolve `ok: true`, having done\n * nothing. Nothing throws. Nothing is logged. That silent success is the failure\n * mode this function exists to remove.\n *\n * ## What it does\n *\n * Runs the flows in the order you declared, one at a time, and calls `guard`\n * immediately before each. A flow whose guard does not pass is returned with\n * `skipped: true` and a reason instead of being run — and the cohort carries on\n * to the next one.\n *\n * ```ts\n * const results = await runCohort([enrich, archive, notify], executors, undefined, {\n * initialInputs: { t: { deal } }, // snapshotted once, shared by all\n * guard: async () => Boolean(await findDeal(deal.id)),\n * reason: () => `deal ${deal.id} no longer exists`,\n * });\n * ```\n *\n * If `archive` deletes the deal, `notify` comes back skipped with that reason\n * rather than notifying about nothing.\n *\n * ## Failure does not cancel the cohort\n *\n * A flow that fails is reported and the next one still runs. \"The flow before me\n * threw\" is not an answer to \"is my input still there\" — the guard is, and it\n * gets asked either way.\n *\n * The Laravel twin (`FancyFlow::dispatchCohort()` in `fancy-flow-php`) is the\n * same contract across a queue, where each run is durable and hands on to its\n * successor when it settles. Same policies, same guard semantics, same\n * fail-closed rule.\n */\nexport async function runCohort(\n flows: FlowGraph[],\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent, index: number) => void = () => {},\n options: CohortOptions = {},\n): Promise<CohortResult[]> {\n const { policy = \"serial-guarded\", guard, reason, ...runOptions } = options;\n\n if (policy === \"parallel\") {\n return Promise.all(\n flows.map(async (flow, index) => ({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n })),\n );\n }\n\n const results: CohortResult[] = [];\n\n for (const [index, flow] of flows.entries()) {\n if (policy === \"serial-guarded\" && guard) {\n const why = () => reason?.(flow, index) ?? \"trigger precondition no longer holds\";\n let passes: boolean;\n\n try {\n passes = await guard(flow, index);\n } catch (error) {\n // Fail CLOSED. A guard that cannot answer is not permission to proceed:\n // a skip is visible and re-runnable, a run over missing state is neither.\n results.push({\n ok: false,\n outputs: {},\n index,\n skipped: true,\n skippedReason: `guard could not be evaluated: ${errorText(error)}`,\n });\n continue;\n }\n\n if (!passes) {\n results.push({ ok: false, outputs: {}, index, skipped: true, skippedReason: why() });\n continue;\n }\n }\n\n results.push({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n });\n }\n\n return results;\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
1
+ {"version":3,"sources":["../src/runtime/run-cohort.ts"],"names":[],"mappings":";;;AAoFA,eAAsB,SAAA,CACpB,KAAA,EACA,SAAA,EACA,OAAA,GAAoD,MAAM;AAAC,CAAA,EAC3D,OAAA,GAAyB,EAAC,EACD;AACzB,EAAA,MAAM,EAAE,MAAA,GAAS,gBAAA,EAAkB,OAAO,MAAA,EAAQ,GAAG,YAAW,GAAI,OAAA;AAEpE,EAAA,IAAI,WAAW,UAAA,EAAY;AACzB,IAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,MACb,KAAA,CAAM,GAAA,CAAI,OAAO,IAAA,EAAM,KAAA,MAAW;AAAA,QAChC,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,QACvE;AAAA,OACF,CAAE;AAAA,KACJ;AAAA,EACF;AAEA,EAAA,MAAM,UAA0B,EAAC;AAEjC,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AAC3C,IAAA,IAAI,MAAA,KAAW,oBAAoB,KAAA,EAAO;AACxC,MAAA,MAAM,GAAA,GAAM,MAAM,MAAA,GAAS,IAAA,EAAM,KAAK,CAAA,IAAK,sCAAA;AAC3C,MAAA,IAAI,MAAA;AAEJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,MAClC,SAAS,KAAA,EAAO;AAGd,QAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,UACX,EAAA,EAAI,KAAA;AAAA,UACJ,SAAS,EAAC;AAAA,UACV,KAAA;AAAA,UACA,OAAA,EAAS,IAAA;AAAA,UACT,aAAA,EAAe,CAAA,8BAAA,EAAiC,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,SACjE,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,EAAA,EAAI,KAAA,EAAO,OAAA,EAAS,EAAC,EAAG,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,aAAA,EAAe,GAAA,IAAO,CAAA;AACnF,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,MACvE;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,OAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC9D","file":"chunk-P5SYH6M6.js","sourcesContent":["import type { ExecutorRegistry, FlowGraph, RunEvent } from \"../types\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\n\n/**\n * How the flows one trigger fired should treat each other.\n *\n * - `serial-guarded` (default) — run in the declared order, one at a time,\n * re-checking `guard` immediately before each. This is the safe default\n * because it is the only one that notices when an earlier flow invalidated\n * the state a later one was fired for.\n * - `serial` — ordered, one at a time, unguarded.\n * - `parallel` — all at once. Ordering is incidental and collisions are yours to\n * handle; correct for fan-outs that share no state, and only those.\n */\nexport type CohortPolicy = \"serial-guarded\" | \"serial\" | \"parallel\";\n\n/**\n * The precondition re-checked just before each flow starts.\n *\n * Return `false` (or throw) to skip that flow. The engine cannot know what\n * \"still valid\" means for your data, so it asks you — right before the run,\n * not at dispatch, because the whole hazard is what changed in between.\n */\nexport type CohortGuard = (\n flow: FlowGraph,\n index: number,\n) => boolean | Promise<boolean>;\n\nexport type CohortOptions = RunOptions & {\n policy?: CohortPolicy;\n guard?: CohortGuard;\n /** Why the guard said no — recorded on the skipped result. */\n reason?: (flow: FlowGraph, index: number) => string;\n};\n\nexport type CohortResult = RunResult & {\n index: number;\n /** True when the flow never ran because its guard did not pass. */\n skipped?: boolean;\n skippedReason?: string;\n};\n\n/**\n * runCohort — run every flow that one trigger event fired, as a group.\n *\n * ## Why this exists\n *\n * `runFlow` runs one graph. A host that fans a single webhook, schedule, or\n * record change out to several flows usually loops it — and a loop, or worse a\n * `Promise.all`, has no answer for the case that actually bites: one of those\n * flows deletes or mutates the record they were ALL fired for. The others then\n * run against state that is no longer there and resolve `ok: true`, having done\n * nothing. Nothing throws. Nothing is logged. That silent success is the failure\n * mode this function exists to remove.\n *\n * ## What it does\n *\n * Runs the flows in the order you declared, one at a time, and calls `guard`\n * immediately before each. A flow whose guard does not pass is returned with\n * `skipped: true` and a reason instead of being run — and the cohort carries on\n * to the next one.\n *\n * ```ts\n * const results = await runCohort([enrich, archive, notify], executors, undefined, {\n * initialInputs: { t: { deal } }, // snapshotted once, shared by all\n * guard: async () => Boolean(await findDeal(deal.id)),\n * reason: () => `deal ${deal.id} no longer exists`,\n * });\n * ```\n *\n * If `archive` deletes the deal, `notify` comes back skipped with that reason\n * rather than notifying about nothing.\n *\n * ## Failure does not cancel the cohort\n *\n * A flow that fails is reported and the next one still runs. \"The flow before me\n * threw\" is not an answer to \"is my input still there\" — the guard is, and it\n * gets asked either way.\n *\n * The Laravel twin (`FancyFlow::dispatchCohort()` in `fancy-flow-php`) is the\n * same contract across a queue, where each run is durable and hands on to its\n * successor when it settles. Same policies, same guard semantics, same\n * fail-closed rule.\n */\nexport async function runCohort(\n flows: FlowGraph[],\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent, index: number) => void = () => {},\n options: CohortOptions = {},\n): Promise<CohortResult[]> {\n const { policy = \"serial-guarded\", guard, reason, ...runOptions } = options;\n\n if (policy === \"parallel\") {\n return Promise.all(\n flows.map(async (flow, index) => ({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n })),\n );\n }\n\n const results: CohortResult[] = [];\n\n for (const [index, flow] of flows.entries()) {\n if (policy === \"serial-guarded\" && guard) {\n const why = () => reason?.(flow, index) ?? \"trigger precondition no longer holds\";\n let passes: boolean;\n\n try {\n passes = await guard(flow, index);\n } catch (error) {\n // Fail CLOSED. A guard that cannot answer is not permission to proceed:\n // a skip is visible and re-runnable, a run over missing state is neither.\n results.push({\n ok: false,\n outputs: {},\n index,\n skipped: true,\n skippedReason: `guard could not be evaluated: ${errorText(error)}`,\n });\n continue;\n }\n\n if (!passes) {\n results.push({ ok: false, outputs: {}, index, skipped: true, skippedReason: why() });\n continue;\n }\n }\n\n results.push({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n });\n }\n\n return results;\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
@@ -1,9 +1,9 @@
1
+ import { RichInputPreview } from './chunk-F5RPRB7A.js';
1
2
  import { NodeResizer, NodeToolbar, Handle, Position } from './chunk-OWENS2H5.js';
2
3
  import { getWorkflowResolver, isResolutionFailure, getLlmClient } from './chunk-USL4FMFU.js';
3
- import { runFlow } from './chunk-VLATLVGH.js';
4
+ import { runFlow } from './chunk-L43CWD63.js';
4
5
  import { resolveNodePorts, nodeConfig } from './chunk-TITD5W4Y.js';
5
6
  import { getNodeKind, categoryAccent, registerNodeKind, listNodeKinds, kindIds } from './chunk-PVROYW7C.js';
6
- import { RichInputPreview } from './chunk-F5RPRB7A.js';
7
7
  import { memo, createContext, useMemo, useState, useRef, useEffect, useContext, createElement } from 'react';
8
8
  import { jsxs, jsx } from 'react/jsx-runtime';
9
9
 
@@ -102,7 +102,13 @@ var subflowExecutor = async (ctx) => {
102
102
  // require no configuration at all.
103
103
  __parent: ctx.inputs
104
104
  },
105
- depth: depth + 1
105
+ depth: depth + 1,
106
+ // Push THIS node onto the identity path, so a node inside the child graph
107
+ // cannot share an idempotency key with a same-named node in the parent —
108
+ // or with the same child graph invoked from a different parent node.
109
+ // Attempt and the first-attempt clock ride down unchanged: the child's
110
+ // work happens inside this node's attempt.
111
+ run: ctx.run?.descend(ctx.node.id)
106
112
  }
107
113
  );
108
114
  if (!result.ok) {
@@ -1264,5 +1270,5 @@ function buildNodeTypes() {
1264
1270
  }
1265
1271
 
1266
1272
  export { ANY_PORT_TYPE, BUILTIN_KINDS, DEFAULT_MAX_DEPTH, FlowEditorProvider, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, declaredRoutes, defaultPortCompatibility, llmRouterExecutor, registerBuiltinKinds, resolveFallbackPort, subflowExecutor, subflowMode, subflowPorts, useFlowEditor, useFlowEditorOptional };
1267
- //# sourceMappingURL=chunk-WBXGZVLB.js.map
1268
- //# sourceMappingURL=chunk-WBXGZVLB.js.map
1273
+ //# sourceMappingURL=chunk-RMU6YCIQ.js.map
1274
+ //# sourceMappingURL=chunk-RMU6YCIQ.js.map