@particle-academy/fancy-flow 0.72.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- 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 +138 -13
- package/dist/durable/index.d.ts +138 -13
- package/dist/durable.cjs +75 -13
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +69 -12
- 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 +2 -2
- 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
|
|
|
@@ -108,6 +108,10 @@ declare class InMemoryClaimStore implements NodeClaimStore {
|
|
|
108
108
|
/**
|
|
109
109
|
* Drop a paused node's claim so a recorded answer can re-run it.
|
|
110
110
|
*
|
|
111
|
+
* A PAUSED row holds one of the run's dispatch slots (see `selectDispatch`),
|
|
112
|
+
* so this is also what frees the slot: until the row is released, a serial
|
|
113
|
+
* run's `advance()` hands out nothing, the gate included.
|
|
114
|
+
*
|
|
111
115
|
* Not part of the interface: resuming a human gate is the host's decision and
|
|
112
116
|
* its storage's business. Provided here because the in-memory store is also
|
|
113
117
|
* what the tests resume through.
|
|
@@ -180,6 +184,57 @@ declare const Frontier: {
|
|
|
180
184
|
settleSkips(store: NodeClaimStore, runKey: string, skipped: readonly string[]): Promise<string[]>;
|
|
181
185
|
};
|
|
182
186
|
|
|
187
|
+
/**
|
|
188
|
+
* How many of ONE run's nodes may be held at once, and which ready nodes go next.
|
|
189
|
+
*
|
|
190
|
+
* ## Serial is the default
|
|
191
|
+
*
|
|
192
|
+
* A queued run hands a node to the queue only once the node before it has
|
|
193
|
+
* settled: one node of a run held at a time, in the graph's own declaration
|
|
194
|
+
* order. Parallel dispatch of a ready frontier is something a host ASKS for,
|
|
195
|
+
* with `maxConcurrent` on the {@link Coordinator}.
|
|
196
|
+
*
|
|
197
|
+
* | `maxConcurrent` | meaning |
|
|
198
|
+
* |---|---|
|
|
199
|
+
* | unset | **1**: serial |
|
|
200
|
+
* | `N >= 1` | up to N of the run's nodes held at once |
|
|
201
|
+
* | {@link UNLIMITED_CONCURRENCY} (`0`) | the whole ready frontier |
|
|
202
|
+
* | anything else | refused, by name |
|
|
203
|
+
*
|
|
204
|
+
* A negative number is refused rather than read as "unlimited". Under a serial
|
|
205
|
+
* default, a typo that silently turned a run parallel is the failure to avoid.
|
|
206
|
+
*
|
|
207
|
+
* ## Held means claimed OR paused
|
|
208
|
+
*
|
|
209
|
+
* A node parked on a person keeps its slot. A pause does not park the whole run
|
|
210
|
+
* in this runtime: `advance()` is called whenever any job settles, and without
|
|
211
|
+
* this rule it would hand out the gate's siblings while the person is still
|
|
212
|
+
* deciding.
|
|
213
|
+
*
|
|
214
|
+
* ## Measured against held work, not the batch
|
|
215
|
+
*
|
|
216
|
+
* Two nodes settling at once each trigger an `advance()` on a real queue. A cap
|
|
217
|
+
* applied to one batch would let each of them dispatch its own quota, so the
|
|
218
|
+
* budget is `maxConcurrent - held`, counted off the claim rows.
|
|
219
|
+
*
|
|
220
|
+
* This is the TypeScript member of a three-runtime contract. The
|
|
221
|
+
* `flow/durable-dispatch` conformance suite pins it, and names
|
|
222
|
+
* `fancy-flow-php`'s `DispatchLimit` and the Python runtime's
|
|
223
|
+
* `fancy_flow.durable.select_dispatch` as the other two implementations.
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/** Dispatch the whole ready frontier. Named so a host never writes a bare `0`. */
|
|
227
|
+
declare const UNLIMITED_CONCURRENCY = 0;
|
|
228
|
+
/**
|
|
229
|
+
* The ready nodes that may be dispatched now, in the order given.
|
|
230
|
+
*
|
|
231
|
+
* `ready` comes from `Frontier.compute`, in declaration order, and that order is
|
|
232
|
+
* kept: this slices, it never sorts. `state` must already include any skips the
|
|
233
|
+
* frontier just settled. Skips are never held, so that changes no count, but the
|
|
234
|
+
* state must describe the run as it is after the decision.
|
|
235
|
+
*/
|
|
236
|
+
declare function selectDispatch(ready: readonly string[], state: Record<string, NodeState>, maxConcurrent: number): string[];
|
|
237
|
+
|
|
183
238
|
/**
|
|
184
239
|
* Run ONE node of a graph — through the real engine, not around it.
|
|
185
240
|
*
|
|
@@ -199,12 +254,38 @@ declare const Frontier: {
|
|
|
199
254
|
* - every node already completed is fed back as `resumeOutputs`, so the engine
|
|
200
255
|
* republishes it on the same ports and routes exactly as it did the first
|
|
201
256
|
* time;
|
|
202
|
-
* - every node EXCEPT the target is bound,
|
|
203
|
-
* that
|
|
204
|
-
* `*` fallback, so the fence holds whatever a host registered;
|
|
257
|
+
* - every node EXCEPT the target is bound, through `RunOptions.nodeExecutors`,
|
|
258
|
+
* to a FENCE that runs nothing and publishes only a port no edge reads;
|
|
205
259
|
* - 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
|
-
*
|
|
260
|
+
* collects the target's inputs its own way, and runs the target.
|
|
261
|
+
*
|
|
262
|
+
* ## Why the fence does not stop the walk
|
|
263
|
+
*
|
|
264
|
+
* It used to abort the run. The target's own inputs never depend on a fenced
|
|
265
|
+
* node -- the frontier dispatches a node only once every source is settled, and
|
|
266
|
+
* settled sources are resumed, not fenced -- but an UNRELATED node can precede
|
|
267
|
+
* the target in topological order. Two siblings dispatched together are exactly
|
|
268
|
+
* that: when `b`'s job started while `a` was still running, the replay aborted
|
|
269
|
+
* at `a`, never reached `b`, and the coordinator read "the replay ended without
|
|
270
|
+
* running me" as "the engine decided I am unreachable". `b` was recorded
|
|
271
|
+
* skipped, never ran, and the run completed as a success. It did not even take
|
|
272
|
+
* two workers: the frontier lists ready nodes in NODE order and the engine walks
|
|
273
|
+
* EDGE order, so a graph where those disagree about siblings sent in-process
|
|
274
|
+
* `runToCompletion` down the same path.
|
|
275
|
+
*
|
|
276
|
+
* Walking past fences makes that inference honest again: when the replay
|
|
277
|
+
* finishes without an output for the target, it is because the engine found
|
|
278
|
+
* every inbound edge dead.
|
|
279
|
+
*
|
|
280
|
+
* ## Why the fences are not registry entries
|
|
281
|
+
*
|
|
282
|
+
* The registry is one flat object, and a key in it is tried as a node's id AND
|
|
283
|
+
* as its kind. Fencing the node called `host_kind` by writing
|
|
284
|
+
* `executors["host_kind"]` fenced every node of kind `host_kind` too, so a
|
|
285
|
+
* durable run of that graph ran nothing and reported success. And the registry
|
|
286
|
+
* is what `ctx.executors` hands a `subflow` child, so a child node sharing an id
|
|
287
|
+
* with any parent node ran the parent's fence. `nodeExecutors` matches node ids
|
|
288
|
+
* only and is never handed down; the registry reaches the engine untouched.
|
|
208
289
|
*
|
|
209
290
|
* The target's output is `result.outputs[nodeId]`, and the ports it activated
|
|
210
291
|
* arrive as the engine's own `node-output` events. Nothing about routing is
|
|
@@ -220,12 +301,19 @@ declare const Frontier: {
|
|
|
220
301
|
*/
|
|
221
302
|
|
|
222
303
|
/**
|
|
223
|
-
* The abort reason
|
|
304
|
+
* The abort reason a boundary used to report.
|
|
224
305
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
306
|
+
* Nothing aborts with it any more (see "Why the fence does not stop the walk");
|
|
307
|
+
* {@link isBoundary} still recognises it so a caller that checks for it keeps
|
|
308
|
+
* working.
|
|
227
309
|
*/
|
|
228
310
|
declare const BOUNDARY = "fancy-flow:node-boundary";
|
|
311
|
+
/**
|
|
312
|
+
* The port a fenced node publishes on. No edge reads it, so everything
|
|
313
|
+
* downstream of a fenced node is dark in the replay -- which never matters to
|
|
314
|
+
* the target, whose sources are all settled.
|
|
315
|
+
*/
|
|
316
|
+
declare const FENCE_PORT = "fancy-flow:fenced";
|
|
229
317
|
type ReplayResult = {
|
|
230
318
|
result: RunResult;
|
|
231
319
|
/** node id -> the ports its output activated, from the engine's own events. */
|
|
@@ -243,8 +331,8 @@ type ReplayOptions = {
|
|
|
243
331
|
/**
|
|
244
332
|
* Replay `graph` up to and through `nodeId`.
|
|
245
333
|
*
|
|
246
|
-
* Pass `nodeId = null` to PROBE: every node is
|
|
247
|
-
*
|
|
334
|
+
* Pass `nodeId = null` to PROBE: every node is fenced, so nothing executes and
|
|
335
|
+
* the engine reports only what it can determine structurally — a cycle, and
|
|
248
336
|
* the ports each resumed output republishes on.
|
|
249
337
|
*/
|
|
250
338
|
declare function replayUpTo(graph: FlowGraph, nodeId: string | null, executors: ExecutorRegistry, options?: ReplayOptions): Promise<ReplayResult>;
|
|
@@ -363,7 +451,10 @@ declare function durableApproval(submissions: Submissions): NodeExecutor;
|
|
|
363
451
|
*
|
|
364
452
|
* `advance()`
|
|
365
453
|
* Ask the frontier what is unblocked, settle the skip cascade, and report the
|
|
366
|
-
*
|
|
454
|
+
* node ids that may be dispatched NOW. A queue adapter dispatches one job per
|
|
455
|
+
* id. By default that is at most one id: a run holds one node at a time, and
|
|
456
|
+
* the next node is handed out only when the one before it settles. See
|
|
457
|
+
* {@link CoordinatorOptions.maxConcurrent}.
|
|
367
458
|
*
|
|
368
459
|
* `runNode()`
|
|
369
460
|
* Claim one node, replay the graph through the real engine fenced to that
|
|
@@ -387,6 +478,12 @@ declare function durableApproval(submissions: Submissions): NodeExecutor;
|
|
|
387
478
|
* A human gate returns `paused`. `runToCompletion` returns immediately when it
|
|
388
479
|
* sees one — it does not spin, sleep or poll. The run is parked in the store,
|
|
389
480
|
* the process is free, and a recorded answer is what starts the next job.
|
|
481
|
+
*
|
|
482
|
+
* A paused node keeps its dispatch slot, so under any finite `maxConcurrent`
|
|
483
|
+
* (serial, by default) a queue adapter's `advance()` hands out nothing
|
|
484
|
+
* alongside a gate while the person decides. Resuming releases the row — see
|
|
485
|
+
* `InMemoryClaimStore.release` — which frees the slot for the gate to run
|
|
486
|
+
* again.
|
|
390
487
|
*/
|
|
391
488
|
|
|
392
489
|
/** What happened to one node. */
|
|
@@ -434,6 +531,19 @@ type CoordinatorOptions = {
|
|
|
434
531
|
initialInputs?: Record<string, Record<string, unknown>>;
|
|
435
532
|
retry?: RetryPolicy;
|
|
436
533
|
onEvent?: (event: RunEvent) => void;
|
|
534
|
+
/**
|
|
535
|
+
* How many of this run's nodes may be HELD at once. Held means CLAIMED by a
|
|
536
|
+
* worker or PAUSED on a person: a paused gate keeps its slot.
|
|
537
|
+
*
|
|
538
|
+
* **Defaults to `1`: serial.** `advance()` hands out one node, and the next
|
|
539
|
+
* only once that one has settled, in the graph's declaration order.
|
|
540
|
+
*
|
|
541
|
+
* A positive integer raises the cap. `UNLIMITED_CONCURRENCY` (`0`) hands out
|
|
542
|
+
* the whole ready frontier at once, which is what every queued run did before
|
|
543
|
+
* this option existed. A negative, fractional or non-numeric value throws
|
|
544
|
+
* here, at construction.
|
|
545
|
+
*/
|
|
546
|
+
maxConcurrent?: number;
|
|
437
547
|
};
|
|
438
548
|
declare class Coordinator {
|
|
439
549
|
readonly graph: FlowGraph;
|
|
@@ -442,12 +552,21 @@ declare class Coordinator {
|
|
|
442
552
|
readonly store: NodeClaimStore;
|
|
443
553
|
readonly initialInputs: Record<string, Record<string, unknown>>;
|
|
444
554
|
readonly retry: RetryPolicy;
|
|
555
|
+
/** The dispatch cap `advance()` applies. `0` is unlimited. */
|
|
556
|
+
readonly maxConcurrent: number;
|
|
445
557
|
private readonly onEvent?;
|
|
446
558
|
constructor(options: CoordinatorOptions);
|
|
447
559
|
get runKey(): string;
|
|
448
560
|
/**
|
|
449
561
|
* Which nodes may be dispatched right now.
|
|
450
562
|
*
|
|
563
|
+
* The ready frontier, cut to the run's {@link maxConcurrent} budget: the
|
|
564
|
+
* first `maxConcurrent - held` ready ids in declaration order, where `held`
|
|
565
|
+
* counts CLAIMED and PAUSED rows. Under the serial default that is at most one
|
|
566
|
+
* id, and none while a node is still running or a gate is waiting on a person.
|
|
567
|
+
* An empty result with work held is a throttled run, not a stalled one: the
|
|
568
|
+
* held node's settle is what calls this again.
|
|
569
|
+
*
|
|
451
570
|
* Also settles the skip cascade, because a skip is a decision the frontier
|
|
452
571
|
* just made and a second caller must not make it again.
|
|
453
572
|
*
|
|
@@ -474,6 +593,12 @@ declare class Coordinator {
|
|
|
474
593
|
/**
|
|
475
594
|
* Drive the graph here, in this process, one node at a time.
|
|
476
595
|
*
|
|
596
|
+
* It asks `advance()` exactly as a queue adapter does, so it runs nodes in the
|
|
597
|
+
* order a queued run under the same `maxConcurrent` would dispatch them. A run
|
|
598
|
+
* that finishes produces the same outputs under every limit. A run that stops
|
|
599
|
+
* on a pause or a failure can have run a different set of nodes before it
|
|
600
|
+
* stopped, because the order among nodes that are ready together differs.
|
|
601
|
+
*
|
|
477
602
|
* Every checkpoint is written exactly as a queued run writes it, so a crash
|
|
478
603
|
* mid-loop resumes from the same place a crashed worker would.
|
|
479
604
|
*
|
|
@@ -528,4 +653,4 @@ declare class Coordinator {
|
|
|
528
653
|
private forward;
|
|
529
654
|
}
|
|
530
655
|
|
|
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 };
|
|
656
|
+
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, UNLIMITED_CONCURRENCY, UNSAFE_TO_REPLAY, durableApproval, durableUserInput, isBoundary, isSettled, replayUpTo, selectDispatch };
|
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
|
|
|
@@ -108,6 +108,10 @@ declare class InMemoryClaimStore implements NodeClaimStore {
|
|
|
108
108
|
/**
|
|
109
109
|
* Drop a paused node's claim so a recorded answer can re-run it.
|
|
110
110
|
*
|
|
111
|
+
* A PAUSED row holds one of the run's dispatch slots (see `selectDispatch`),
|
|
112
|
+
* so this is also what frees the slot: until the row is released, a serial
|
|
113
|
+
* run's `advance()` hands out nothing, the gate included.
|
|
114
|
+
*
|
|
111
115
|
* Not part of the interface: resuming a human gate is the host's decision and
|
|
112
116
|
* its storage's business. Provided here because the in-memory store is also
|
|
113
117
|
* what the tests resume through.
|
|
@@ -180,6 +184,57 @@ declare const Frontier: {
|
|
|
180
184
|
settleSkips(store: NodeClaimStore, runKey: string, skipped: readonly string[]): Promise<string[]>;
|
|
181
185
|
};
|
|
182
186
|
|
|
187
|
+
/**
|
|
188
|
+
* How many of ONE run's nodes may be held at once, and which ready nodes go next.
|
|
189
|
+
*
|
|
190
|
+
* ## Serial is the default
|
|
191
|
+
*
|
|
192
|
+
* A queued run hands a node to the queue only once the node before it has
|
|
193
|
+
* settled: one node of a run held at a time, in the graph's own declaration
|
|
194
|
+
* order. Parallel dispatch of a ready frontier is something a host ASKS for,
|
|
195
|
+
* with `maxConcurrent` on the {@link Coordinator}.
|
|
196
|
+
*
|
|
197
|
+
* | `maxConcurrent` | meaning |
|
|
198
|
+
* |---|---|
|
|
199
|
+
* | unset | **1**: serial |
|
|
200
|
+
* | `N >= 1` | up to N of the run's nodes held at once |
|
|
201
|
+
* | {@link UNLIMITED_CONCURRENCY} (`0`) | the whole ready frontier |
|
|
202
|
+
* | anything else | refused, by name |
|
|
203
|
+
*
|
|
204
|
+
* A negative number is refused rather than read as "unlimited". Under a serial
|
|
205
|
+
* default, a typo that silently turned a run parallel is the failure to avoid.
|
|
206
|
+
*
|
|
207
|
+
* ## Held means claimed OR paused
|
|
208
|
+
*
|
|
209
|
+
* A node parked on a person keeps its slot. A pause does not park the whole run
|
|
210
|
+
* in this runtime: `advance()` is called whenever any job settles, and without
|
|
211
|
+
* this rule it would hand out the gate's siblings while the person is still
|
|
212
|
+
* deciding.
|
|
213
|
+
*
|
|
214
|
+
* ## Measured against held work, not the batch
|
|
215
|
+
*
|
|
216
|
+
* Two nodes settling at once each trigger an `advance()` on a real queue. A cap
|
|
217
|
+
* applied to one batch would let each of them dispatch its own quota, so the
|
|
218
|
+
* budget is `maxConcurrent - held`, counted off the claim rows.
|
|
219
|
+
*
|
|
220
|
+
* This is the TypeScript member of a three-runtime contract. The
|
|
221
|
+
* `flow/durable-dispatch` conformance suite pins it, and names
|
|
222
|
+
* `fancy-flow-php`'s `DispatchLimit` and the Python runtime's
|
|
223
|
+
* `fancy_flow.durable.select_dispatch` as the other two implementations.
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/** Dispatch the whole ready frontier. Named so a host never writes a bare `0`. */
|
|
227
|
+
declare const UNLIMITED_CONCURRENCY = 0;
|
|
228
|
+
/**
|
|
229
|
+
* The ready nodes that may be dispatched now, in the order given.
|
|
230
|
+
*
|
|
231
|
+
* `ready` comes from `Frontier.compute`, in declaration order, and that order is
|
|
232
|
+
* kept: this slices, it never sorts. `state` must already include any skips the
|
|
233
|
+
* frontier just settled. Skips are never held, so that changes no count, but the
|
|
234
|
+
* state must describe the run as it is after the decision.
|
|
235
|
+
*/
|
|
236
|
+
declare function selectDispatch(ready: readonly string[], state: Record<string, NodeState>, maxConcurrent: number): string[];
|
|
237
|
+
|
|
183
238
|
/**
|
|
184
239
|
* Run ONE node of a graph — through the real engine, not around it.
|
|
185
240
|
*
|
|
@@ -199,12 +254,38 @@ declare const Frontier: {
|
|
|
199
254
|
* - every node already completed is fed back as `resumeOutputs`, so the engine
|
|
200
255
|
* republishes it on the same ports and routes exactly as it did the first
|
|
201
256
|
* time;
|
|
202
|
-
* - every node EXCEPT the target is bound,
|
|
203
|
-
* that
|
|
204
|
-
* `*` fallback, so the fence holds whatever a host registered;
|
|
257
|
+
* - every node EXCEPT the target is bound, through `RunOptions.nodeExecutors`,
|
|
258
|
+
* to a FENCE that runs nothing and publishes only a port no edge reads;
|
|
205
259
|
* - 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
|
-
*
|
|
260
|
+
* collects the target's inputs its own way, and runs the target.
|
|
261
|
+
*
|
|
262
|
+
* ## Why the fence does not stop the walk
|
|
263
|
+
*
|
|
264
|
+
* It used to abort the run. The target's own inputs never depend on a fenced
|
|
265
|
+
* node -- the frontier dispatches a node only once every source is settled, and
|
|
266
|
+
* settled sources are resumed, not fenced -- but an UNRELATED node can precede
|
|
267
|
+
* the target in topological order. Two siblings dispatched together are exactly
|
|
268
|
+
* that: when `b`'s job started while `a` was still running, the replay aborted
|
|
269
|
+
* at `a`, never reached `b`, and the coordinator read "the replay ended without
|
|
270
|
+
* running me" as "the engine decided I am unreachable". `b` was recorded
|
|
271
|
+
* skipped, never ran, and the run completed as a success. It did not even take
|
|
272
|
+
* two workers: the frontier lists ready nodes in NODE order and the engine walks
|
|
273
|
+
* EDGE order, so a graph where those disagree about siblings sent in-process
|
|
274
|
+
* `runToCompletion` down the same path.
|
|
275
|
+
*
|
|
276
|
+
* Walking past fences makes that inference honest again: when the replay
|
|
277
|
+
* finishes without an output for the target, it is because the engine found
|
|
278
|
+
* every inbound edge dead.
|
|
279
|
+
*
|
|
280
|
+
* ## Why the fences are not registry entries
|
|
281
|
+
*
|
|
282
|
+
* The registry is one flat object, and a key in it is tried as a node's id AND
|
|
283
|
+
* as its kind. Fencing the node called `host_kind` by writing
|
|
284
|
+
* `executors["host_kind"]` fenced every node of kind `host_kind` too, so a
|
|
285
|
+
* durable run of that graph ran nothing and reported success. And the registry
|
|
286
|
+
* is what `ctx.executors` hands a `subflow` child, so a child node sharing an id
|
|
287
|
+
* with any parent node ran the parent's fence. `nodeExecutors` matches node ids
|
|
288
|
+
* only and is never handed down; the registry reaches the engine untouched.
|
|
208
289
|
*
|
|
209
290
|
* The target's output is `result.outputs[nodeId]`, and the ports it activated
|
|
210
291
|
* arrive as the engine's own `node-output` events. Nothing about routing is
|
|
@@ -220,12 +301,19 @@ declare const Frontier: {
|
|
|
220
301
|
*/
|
|
221
302
|
|
|
222
303
|
/**
|
|
223
|
-
* The abort reason
|
|
304
|
+
* The abort reason a boundary used to report.
|
|
224
305
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
306
|
+
* Nothing aborts with it any more (see "Why the fence does not stop the walk");
|
|
307
|
+
* {@link isBoundary} still recognises it so a caller that checks for it keeps
|
|
308
|
+
* working.
|
|
227
309
|
*/
|
|
228
310
|
declare const BOUNDARY = "fancy-flow:node-boundary";
|
|
311
|
+
/**
|
|
312
|
+
* The port a fenced node publishes on. No edge reads it, so everything
|
|
313
|
+
* downstream of a fenced node is dark in the replay -- which never matters to
|
|
314
|
+
* the target, whose sources are all settled.
|
|
315
|
+
*/
|
|
316
|
+
declare const FENCE_PORT = "fancy-flow:fenced";
|
|
229
317
|
type ReplayResult = {
|
|
230
318
|
result: RunResult;
|
|
231
319
|
/** node id -> the ports its output activated, from the engine's own events. */
|
|
@@ -243,8 +331,8 @@ type ReplayOptions = {
|
|
|
243
331
|
/**
|
|
244
332
|
* Replay `graph` up to and through `nodeId`.
|
|
245
333
|
*
|
|
246
|
-
* Pass `nodeId = null` to PROBE: every node is
|
|
247
|
-
*
|
|
334
|
+
* Pass `nodeId = null` to PROBE: every node is fenced, so nothing executes and
|
|
335
|
+
* the engine reports only what it can determine structurally — a cycle, and
|
|
248
336
|
* the ports each resumed output republishes on.
|
|
249
337
|
*/
|
|
250
338
|
declare function replayUpTo(graph: FlowGraph, nodeId: string | null, executors: ExecutorRegistry, options?: ReplayOptions): Promise<ReplayResult>;
|
|
@@ -363,7 +451,10 @@ declare function durableApproval(submissions: Submissions): NodeExecutor;
|
|
|
363
451
|
*
|
|
364
452
|
* `advance()`
|
|
365
453
|
* Ask the frontier what is unblocked, settle the skip cascade, and report the
|
|
366
|
-
*
|
|
454
|
+
* node ids that may be dispatched NOW. A queue adapter dispatches one job per
|
|
455
|
+
* id. By default that is at most one id: a run holds one node at a time, and
|
|
456
|
+
* the next node is handed out only when the one before it settles. See
|
|
457
|
+
* {@link CoordinatorOptions.maxConcurrent}.
|
|
367
458
|
*
|
|
368
459
|
* `runNode()`
|
|
369
460
|
* Claim one node, replay the graph through the real engine fenced to that
|
|
@@ -387,6 +478,12 @@ declare function durableApproval(submissions: Submissions): NodeExecutor;
|
|
|
387
478
|
* A human gate returns `paused`. `runToCompletion` returns immediately when it
|
|
388
479
|
* sees one — it does not spin, sleep or poll. The run is parked in the store,
|
|
389
480
|
* the process is free, and a recorded answer is what starts the next job.
|
|
481
|
+
*
|
|
482
|
+
* A paused node keeps its dispatch slot, so under any finite `maxConcurrent`
|
|
483
|
+
* (serial, by default) a queue adapter's `advance()` hands out nothing
|
|
484
|
+
* alongside a gate while the person decides. Resuming releases the row — see
|
|
485
|
+
* `InMemoryClaimStore.release` — which frees the slot for the gate to run
|
|
486
|
+
* again.
|
|
390
487
|
*/
|
|
391
488
|
|
|
392
489
|
/** What happened to one node. */
|
|
@@ -434,6 +531,19 @@ type CoordinatorOptions = {
|
|
|
434
531
|
initialInputs?: Record<string, Record<string, unknown>>;
|
|
435
532
|
retry?: RetryPolicy;
|
|
436
533
|
onEvent?: (event: RunEvent) => void;
|
|
534
|
+
/**
|
|
535
|
+
* How many of this run's nodes may be HELD at once. Held means CLAIMED by a
|
|
536
|
+
* worker or PAUSED on a person: a paused gate keeps its slot.
|
|
537
|
+
*
|
|
538
|
+
* **Defaults to `1`: serial.** `advance()` hands out one node, and the next
|
|
539
|
+
* only once that one has settled, in the graph's declaration order.
|
|
540
|
+
*
|
|
541
|
+
* A positive integer raises the cap. `UNLIMITED_CONCURRENCY` (`0`) hands out
|
|
542
|
+
* the whole ready frontier at once, which is what every queued run did before
|
|
543
|
+
* this option existed. A negative, fractional or non-numeric value throws
|
|
544
|
+
* here, at construction.
|
|
545
|
+
*/
|
|
546
|
+
maxConcurrent?: number;
|
|
437
547
|
};
|
|
438
548
|
declare class Coordinator {
|
|
439
549
|
readonly graph: FlowGraph;
|
|
@@ -442,12 +552,21 @@ declare class Coordinator {
|
|
|
442
552
|
readonly store: NodeClaimStore;
|
|
443
553
|
readonly initialInputs: Record<string, Record<string, unknown>>;
|
|
444
554
|
readonly retry: RetryPolicy;
|
|
555
|
+
/** The dispatch cap `advance()` applies. `0` is unlimited. */
|
|
556
|
+
readonly maxConcurrent: number;
|
|
445
557
|
private readonly onEvent?;
|
|
446
558
|
constructor(options: CoordinatorOptions);
|
|
447
559
|
get runKey(): string;
|
|
448
560
|
/**
|
|
449
561
|
* Which nodes may be dispatched right now.
|
|
450
562
|
*
|
|
563
|
+
* The ready frontier, cut to the run's {@link maxConcurrent} budget: the
|
|
564
|
+
* first `maxConcurrent - held` ready ids in declaration order, where `held`
|
|
565
|
+
* counts CLAIMED and PAUSED rows. Under the serial default that is at most one
|
|
566
|
+
* id, and none while a node is still running or a gate is waiting on a person.
|
|
567
|
+
* An empty result with work held is a throttled run, not a stalled one: the
|
|
568
|
+
* held node's settle is what calls this again.
|
|
569
|
+
*
|
|
451
570
|
* Also settles the skip cascade, because a skip is a decision the frontier
|
|
452
571
|
* just made and a second caller must not make it again.
|
|
453
572
|
*
|
|
@@ -474,6 +593,12 @@ declare class Coordinator {
|
|
|
474
593
|
/**
|
|
475
594
|
* Drive the graph here, in this process, one node at a time.
|
|
476
595
|
*
|
|
596
|
+
* It asks `advance()` exactly as a queue adapter does, so it runs nodes in the
|
|
597
|
+
* order a queued run under the same `maxConcurrent` would dispatch them. A run
|
|
598
|
+
* that finishes produces the same outputs under every limit. A run that stops
|
|
599
|
+
* on a pause or a failure can have run a different set of nodes before it
|
|
600
|
+
* stopped, because the order among nodes that are ready together differs.
|
|
601
|
+
*
|
|
477
602
|
* Every checkpoint is written exactly as a queued run writes it, so a crash
|
|
478
603
|
* mid-loop resumes from the same place a crashed worker would.
|
|
479
604
|
*
|
|
@@ -528,4 +653,4 @@ declare class Coordinator {
|
|
|
528
653
|
private forward;
|
|
529
654
|
}
|
|
530
655
|
|
|
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 };
|
|
656
|
+
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, UNLIMITED_CONCURRENCY, UNSAFE_TO_REPLAY, durableApproval, durableUserInput, isBoundary, isSettled, replayUpTo, selectDispatch };
|