fyflow-orchestration 0.7.0 → 0.8.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/AGENTS.md +1800 -200
- package/README.md +88 -104
- package/dist/browser/chunks/{chunk-YVOP3DIN.js → chunk-EUU44PHD.js} +2104 -283
- package/dist/browser/index.js +19 -9
- package/dist/browser/scheduler/index.js +101 -58
- package/dist/node/chunks/{chunk-YVOP3DIN.js → chunk-EUU44PHD.js} +2104 -283
- package/dist/node/index.js +19 -9
- package/dist/node/scheduler/index.js +101 -58
- package/dist/types/src/definition/assist.d.ts +29 -5
- package/dist/types/src/definition/condition.d.ts +49 -0
- package/dist/types/src/definition/regions.d.ts +12 -1
- package/dist/types/src/definition/registry.d.ts +52 -10
- package/dist/types/src/definition/schema.d.ts +20 -8
- package/dist/types/src/definition/types.d.ts +152 -23
- package/dist/types/src/definition/validate.d.ts +68 -20
- package/dist/types/src/definition/wiring.d.ts +58 -10
- package/dist/types/src/definition/witness.d.ts +12 -4
- package/dist/types/src/driver/driver.d.ts +772 -40
- package/dist/types/src/driver/listener.d.ts +7 -1
- package/dist/types/src/driver/log.d.ts +20 -5
- package/dist/types/src/driver/routing.d.ts +10 -5
- package/dist/types/src/fold/policies.d.ts +4 -5
- package/dist/types/src/fold/run-state.d.ts +245 -14
- package/dist/types/src/scheduler/bootstrap.d.ts +18 -26
- package/dist/types/src/scheduler/envelope-worker.d.ts +6 -5
- package/dist/types/src/scheduler/index.d.ts +2 -2
- package/dist/types/src/scheduler/runtime.d.ts +105 -32
- package/package.json +3 -3
package/dist/browser/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ASK_REFUSED_KEYS,
|
|
2
3
|
BUILT_IN_GATE_TYPES,
|
|
3
4
|
BUILT_IN_GROUP_TYPES,
|
|
4
5
|
BUILT_IN_NODE_TYPES,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
InvalidRunId,
|
|
22
23
|
InvalidTaskId,
|
|
23
24
|
KEY_PATH_RE,
|
|
25
|
+
MESSAGE_OPTION_KEYS,
|
|
24
26
|
MissingJoinPolicy,
|
|
25
27
|
MultipleRuns,
|
|
26
28
|
NODE_KEYS_DECLARED,
|
|
@@ -30,6 +32,7 @@ import {
|
|
|
30
32
|
NoMatchingEdge,
|
|
31
33
|
NotStarted,
|
|
32
34
|
POLICIES,
|
|
35
|
+
POOL_INSTANCE_KEYS,
|
|
33
36
|
POOL_SPEC_KEYS,
|
|
34
37
|
PREDICATE_GROUP_KEYS,
|
|
35
38
|
PREDICATE_LEAF_KEYS,
|
|
@@ -41,10 +44,12 @@ import {
|
|
|
41
44
|
RETRY_POLICY_KEYS,
|
|
42
45
|
RegistryError,
|
|
43
46
|
ResumeRefused,
|
|
47
|
+
RunControlRefused,
|
|
44
48
|
SCHEMA_KEYWORDS,
|
|
45
49
|
SCHEMA_TYPES,
|
|
46
50
|
SchemaRefused,
|
|
47
51
|
TASK_KEYS_DECLARED,
|
|
52
|
+
TERMINAL_EVENTS,
|
|
48
53
|
TRANSFORM_KEYS,
|
|
49
54
|
TypeDefaultRefused,
|
|
50
55
|
UncloneableResult,
|
|
@@ -90,11 +95,14 @@ import {
|
|
|
90
95
|
gateTypeCollisions,
|
|
91
96
|
gateTypeProblem,
|
|
92
97
|
gateTypesOf,
|
|
98
|
+
givenVerdict,
|
|
93
99
|
groupsNamedBy,
|
|
100
|
+
innermostMapOf,
|
|
94
101
|
innermostRegionOf,
|
|
95
102
|
isDerivation,
|
|
96
103
|
isSuspended,
|
|
97
104
|
isTerminal,
|
|
105
|
+
isTerminalEvent,
|
|
98
106
|
keyPathProblem,
|
|
99
107
|
languagesOf,
|
|
100
108
|
lastRecordFor,
|
|
@@ -122,6 +130,7 @@ import {
|
|
|
122
130
|
resolveRegistry,
|
|
123
131
|
resumeRefusal,
|
|
124
132
|
retryPolicyProblem,
|
|
133
|
+
routesOf,
|
|
125
134
|
sampleOf,
|
|
126
135
|
schemaProblem,
|
|
127
136
|
selectEdge,
|
|
@@ -134,7 +143,7 @@ import {
|
|
|
134
143
|
violatedRules,
|
|
135
144
|
witness,
|
|
136
145
|
workersOf
|
|
137
|
-
} from "./chunks/chunk-
|
|
146
|
+
} from "./chunks/chunk-EUU44PHD.js";
|
|
138
147
|
|
|
139
148
|
// src/definition/assist.ts
|
|
140
149
|
var TYPES = ["object", "array", "string", "number", "integer", "boolean", "null"];
|
|
@@ -174,8 +183,8 @@ async function tryTransform(registry, flow, nodeId, transform, options = {}) {
|
|
|
174
183
|
else copy.transform = { lang: transform.lang, expr: transform.expr };
|
|
175
184
|
return copy;
|
|
176
185
|
});
|
|
177
|
-
const
|
|
178
|
-
return
|
|
186
|
+
const { input, ...wiring } = options;
|
|
187
|
+
return await givenVerdict(registry, { ...flow, nodes }, nodeId, input, wiring);
|
|
179
188
|
}
|
|
180
189
|
function schemaOfValue(v) {
|
|
181
190
|
if (v === void 0) return void 0;
|
|
@@ -278,13 +287,8 @@ function signaturesOf(workers) {
|
|
|
278
287
|
}
|
|
279
288
|
var PATH_TYPES = ["*", ...TYPES];
|
|
280
289
|
var TS_TYPE_NAMES = [...TS_STRUCTURED, ...TS_NAMES.keys()];
|
|
281
|
-
|
|
282
|
-
// src/driver/events.ts
|
|
283
|
-
var TERMINAL_EVENTS = ["branch.arrived", "branch.exited", "branch.lost"];
|
|
284
|
-
function isTerminalEvent(e) {
|
|
285
|
-
return TERMINAL_EVENTS.includes(e.type);
|
|
286
|
-
}
|
|
287
290
|
export {
|
|
291
|
+
ASK_REFUSED_KEYS,
|
|
288
292
|
BUILT_IN_GATE_TYPES,
|
|
289
293
|
BUILT_IN_GROUP_TYPES,
|
|
290
294
|
BUILT_IN_NODE_TYPES,
|
|
@@ -307,6 +311,7 @@ export {
|
|
|
307
311
|
InvalidRunId,
|
|
308
312
|
InvalidTaskId,
|
|
309
313
|
KEY_PATH_RE,
|
|
314
|
+
MESSAGE_OPTION_KEYS,
|
|
310
315
|
MissingJoinPolicy,
|
|
311
316
|
MultipleRuns,
|
|
312
317
|
NODE_KEYS_DECLARED,
|
|
@@ -317,6 +322,7 @@ export {
|
|
|
317
322
|
NotStarted,
|
|
318
323
|
PATH_TYPES,
|
|
319
324
|
POLICIES,
|
|
325
|
+
POOL_INSTANCE_KEYS,
|
|
320
326
|
POOL_SPEC_KEYS,
|
|
321
327
|
PREDICATE_GROUP_KEYS,
|
|
322
328
|
PREDICATE_LEAF_KEYS,
|
|
@@ -328,6 +334,7 @@ export {
|
|
|
328
334
|
RETRY_POLICY_KEYS,
|
|
329
335
|
RegistryError,
|
|
330
336
|
ResumeRefused,
|
|
337
|
+
RunControlRefused,
|
|
331
338
|
SCHEMA_KEYWORDS,
|
|
332
339
|
SCHEMA_TYPES,
|
|
333
340
|
SchemaRefused,
|
|
@@ -380,7 +387,9 @@ export {
|
|
|
380
387
|
gateTypeCollisions,
|
|
381
388
|
gateTypeProblem,
|
|
382
389
|
gateTypesOf,
|
|
390
|
+
givenVerdict,
|
|
383
391
|
groupsNamedBy,
|
|
392
|
+
innermostMapOf,
|
|
384
393
|
innermostRegionOf,
|
|
385
394
|
isDerivation,
|
|
386
395
|
isSuspended,
|
|
@@ -414,6 +423,7 @@ export {
|
|
|
414
423
|
resolveRegistry,
|
|
415
424
|
resumeRefusal,
|
|
416
425
|
retryPolicyProblem,
|
|
426
|
+
routesOf,
|
|
417
427
|
sampleOf,
|
|
418
428
|
sampledOutput,
|
|
419
429
|
schemaOfValue,
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
BUILT_IN_NODE_TYPES,
|
|
3
3
|
ConfigRefused,
|
|
4
4
|
Driver,
|
|
5
|
+
InvalidDefinition,
|
|
5
6
|
InvalidTaskId,
|
|
6
7
|
TypeDefaultRefused,
|
|
7
8
|
checkGateTypes,
|
|
@@ -20,38 +21,23 @@ import {
|
|
|
20
21
|
poolsOf,
|
|
21
22
|
resolveRegistry,
|
|
22
23
|
resumeRefusal,
|
|
24
|
+
retryPolicyProblem,
|
|
23
25
|
step,
|
|
26
|
+
validateDefinition,
|
|
24
27
|
workersOf
|
|
25
|
-
} from "../chunks/chunk-
|
|
28
|
+
} from "../chunks/chunk-EUU44PHD.js";
|
|
26
29
|
|
|
27
30
|
// src/scheduler/bootstrap.ts
|
|
28
31
|
import { WorkerManager } from "fyflow-scheduler";
|
|
29
32
|
|
|
30
33
|
// fyflow-envelope-worker:envelope-worker
|
|
31
|
-
var envelope_worker_default = URL.createObjectURL(new Blob(['// src/scheduler/envelope-worker.ts\nvar NotAnEnvelope = class extends Error {\n};\nvar TransformFailed = class extends Error {\n lang;\n expr;\n code;\n constructor(transform, code, why) {\n super(`transform failed \\u2014 ${transform.lang} ${JSON.stringify(transform.expr)}${code !== void 0 ? ` [${code}]` : ""}: ${why}`);\n this.lang = transform.lang;\n this.expr = transform.expr;\n this.code = code;\n }\n};\nfunction unpackEnvelope(payload) {\n if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {\n throw new NotAnEnvelope(`typed pool: the payload is not an envelope { settings, input } \\u2014 got ${brief(payload)}`);\n }\n const p = payload;\n for (const k of ["settings", "input"]) {\n if (!(k in p)) throw new NotAnEnvelope(`typed pool: the payload is not an envelope \\u2014 it has no "${k}" key (keys: ${Object.keys(p).map((x) => `"${x}"`).join(", ") || "none"})`);\n }\n const settings = p.settings;\n if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {\n throw new NotAnEnvelope(`typed pool: the envelope\'s settings is not a plain object \\u2014 got ${brief(settings)}`);\n }\n const env = { settings, input: p.input };\n if ("transform" in p) {\n const t = p.transform;\n const wellFormed = typeof t === "object" && t !== null && !Array.isArray(t) && typeof t.lang === "string" && typeof t.expr === "string" && Object.keys(t).length === 2;\n if (!wellFormed) throw new NotAnEnvelope(`typed pool: the envelope\'s transform is not { lang, expr } \\u2014 got ${brief(p.transform)}`);\n env.transform = { lang: t.lang, expr: t.expr };\n }\n return env;\n}\nfunction brief(v) {\n const s = printable(v);\n return s.length > 120 ? `${s.slice(0, 117)}...` : s;\n}\nfunction printable(v) {\n try {\n const j = JSON.stringify(v);\n if (j !== void 0) return j;\n } catch {\n }\n try {\n return String(v);\n } catch {\n return `an unprintable ${typeof v}`;\n }\n}\nfunction codeOf(e) {\n const c = e?.code;\n return typeof c === "string" && c.length ? c : void 0;\n}\nfunction messageOf(e) {\n if (e instanceof Error) return e.message;\n try {\n return String(e);\n } catch {\n return printable(e);\n }\n}\nvar EnvelopeWorker = class {\n cfg;\n workerContext;\n inner = null;\n /**\n * **Set by `setup()` when the pool config names no worker \u2014 a type that only TRANSFORMS** (ADR 0075).\n * It is a SEPARATE field from `inner === null` on purpose, and that is the whole of this arm\'s\n * correctness: `inner` is null before `setup()` and after `teardown()` too, which are LIFECYCLE\n * states `run()` has refused by name since v0.17, so reading the absence off `inner` would turn\n * *run() before setup()* into *return the input untouched* \u2014 a run that silently hands a worker\'s\n * successor the wrong value instead of failing the task. Measured at W109\'s declaration step: before\n * this item BOTH of those states threw, and neither was an arm.\n */\n noInner = false;\n /**\n * The worker half of each language the pool config names, loaded in `setup()` \u2014 or the reason\n * it did not load, REMEMBERED so that `run()` can fail the task with it (the maintainer\'s call,\n * see `setup()`).\n */\n langs = /* @__PURE__ */ new Map();\n /** Each distinct `(lang, expr)` compiled ONCE per worker instance, never per task \u2014 P17 decision 3. */\n compiled = /* @__PURE__ */ new Map();\n /**\n * **Why this worker could not set up, REMEMBERED \u2014 or `null`** \u2014\n * [W141](../../doc/work-items/w141-a-worker-that-cannot-set-up-fails-its-tasks-and-the-registry-has-one-shape.md),\n * [Q266](../../doc/questions/q266.md), P31 decision 3: W61\'s road for a language, taken for the whole\n * of `setup()`. Set by `setup()` instead of throwing; read FIRST by `run()`, which fails every task\n * with it; cleared by `teardown()`, so a torn-down worker is in its constructed state again.\n */\n setupFailure = null;\n /**\n * Both arguments are kept, and the second is forwarded to the inner worker: the scheduler passes\n * its worker-level context here, and a worker that only accepts `config` silently loses the\n * ability to self-terminate (the scheduler\'s own reference, \xA74).\n */\n constructor(config, workerContext) {\n this.cfg = config;\n this.workerContext = workerContext;\n }\n /**\n * Loads the type\'s worker and runs ITS setup \u2014 a module with no default class is refused by\n * name, as at v0.17 \u2014 then the worker half of every language the pool config names, ONCE per\n * worker instance (P17 decision 3).\n *\n * **A language module that FAILS TO LOAD does not throw here; the failure is remembered and\n * every task whose transform names that language fails at `run()` with it.** Measured at W61\'s\n * declaration, both runtimes, inline and threaded: a wrapper whose `setup()` throws leaves every\n * task of the pool `pending` with `attempts: 0`, the pool restarts its worker, and the scheduler\n * emits nothing a driver could turn into a record \u2014 the failure is reported on the POOL as\n * `worker.setup.failed` and `task.requeue_required`, a worker error and not a task\'s, which is\n * P17 prediction 2\'s stated reversal condition. The maintainer chose this road at W61\'s\n * stop-and-ask (ADR 0051 rule 5): a URL that does not resolve, a module with no default compile\n * function, or an empty URL, each becomes a `TransformFailed` naming the language and the load\n * error, on exactly the tasks that need that language \u2014 a task with no transform, or one in a\n * language that did load, still runs on this worker.\n *\n * **And since W141 the same is true of the TYPE\'s own worker: this method never throws** \u2014 Q266,\n * P31 decision 3, W61\'s road taken for the whole of `setup()`. F1 is a fact about THIS method\n * throwing, whatever threw, and W141 measured it again on 0.5.2 through `createRuntime`: three\n * tasks on a worker whose `setup()` throws stay `pending` for ever in both pool modes, the pool\n * requeueing them on each worker failure until its restart limit, and the run\'s fold says\n * `running`. So every way the type\'s worker can fail to set up \u2014 the module does not load, it\n * exports no default class, its constructor throws, its own `setup()` throws \u2014 and the two\n * pool-config refusals this method made by name, are REMEMBERED in `setupFailure`, and `run()`\n * fails every task on this worker with it: an ordinary task failure, which the run routes per\n * ADR 0004 \u2014 down an `on: ["failed"]` edge on an optional node, parked `user_action` on a\n * non-optional one. **A constructed inner worker is torn down at once**, because the scheduler\n * tears down a worker whose `setup()` threw (its own reference, \xA76: *a failing `setup()` still\n * tears the worker down*) and this class is now the only thing that knows one did. The worker\n * stays up and fails every task until the pool is rebuilt: a setup that failed for a transient\n * reason is not retried here (W141\'s Out).\n */\n async setup() {\n this.setupFailure = null;\n try {\n await this.setupOrThrow();\n } catch (e) {\n this.setupFailure = messageOf(e);\n const inner = this.inner;\n this.inner = null;\n this.noInner = false;\n this.langs.clear();\n if (inner !== null) {\n try {\n await inner.teardown?.();\n } catch (t) {\n this.setupFailure += ` (and its teardown() after the failed setup threw too: ${messageOf(t)})`;\n }\n }\n }\n }\n /** What `setup()` did until W141, throws and all; `setup()` turns each throw into `setupFailure`. */\n async setupOrThrow() {\n if (this.cfg?.transformOnly === true) {\n if (this.cfg.worker !== void 0) {\n throw new Error(`envelope worker: the pool config says transformOnly AND names worker ${brief(this.cfg.worker)} \\u2014 a type either has an inner worker or only transforms`);\n }\n this.noInner = true;\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n return;\n }\n if (typeof this.cfg?.worker !== "string" || this.cfg.worker.length === 0) {\n throw new Error(`envelope worker: the pool config names no worker URL \\u2014 got ${brief(this.cfg)}`);\n }\n const workerUrl = this.cfg.worker;\n let mod;\n try {\n mod = await import(workerUrl);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} did not load: ${messageOf(e)}`);\n }\n if (typeof mod.default !== "function" || !mod.default.prototype) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} must export a default class`);\n }\n const Inner = mod.default;\n try {\n this.inner = new Inner(this.cfg.config ?? {}, this.workerContext);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its constructor: ${messageOf(e)}`);\n }\n try {\n await this.inner.setup?.();\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its setup(): ${messageOf(e)}`);\n }\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n }\n /** One language\'s worker half, or the Error that stands in for it \u2014 never a throw (see `setup()`). */\n async loadLanguage(name, url) {\n if (typeof url !== "string" || url.length === 0) {\n return new Error(`language "${name}" names no worker module URL in the pool config \\u2014 got ${brief(url)}`);\n }\n try {\n const lm = await import(url);\n if (typeof lm.default !== "function") {\n return new Error(`language "${name}"\'s worker module ${url} must export a default compile function (got ${typeof lm.default})`);\n }\n return lm.default;\n } catch (e) {\n return new Error(`language "${name}"\'s worker module ${url} did not load: ${messageOf(e)}`);\n }\n }\n /**\n * `FD-C5`: the inner worker receives `{ settings, input }` and nothing else. `FD-C6`: when the\n * envelope carries a `transform`, `input` is the evaluation of that expression over the value\n * received, with the node\'s settings bound as `$settings`, and the third key is consumed here.\n */\n async run(payload, context) {\n if (this.setupFailure !== null) {\n throw new Error(`${this.setupFailure} \\u2014 every task on this worker fails with this until the pool is rebuilt`);\n }\n if (this.inner === null && !this.noInner) throw new Error("envelope worker: run() before setup() \\u2014 the inner worker is not loaded");\n const env = unpackEnvelope(payload);\n if (this.noInner) {\n if (env.transform === void 0) {\n throw new NotAnEnvelope("typed pool: this pool`s type names no worker, so the envelope must carry a transform \\u2014 there is nothing else for the task to do (FD-V46 refuses such a node at construction)");\n }\n return await this.transform(env.transform, env.input, env.settings);\n }\n const input = env.transform === void 0 ? env.input : await this.transform(env.transform, env.input, env.settings);\n return await this.inner.run({ settings: env.settings, input }, context);\n }\n /** Evaluates one declared transform; every failure is a `TransformFailed` naming what failed and why. */\n async transform(t, input, settings) {\n const compile = this.langs.get(t.lang);\n if (compile === void 0) {\n const carried = Object.keys(this.cfg.languages ?? {}).map((x) => `"${x}"`).join(", ") || "no languages";\n throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \\u2014 the bootstrap helper writes an adapter URL for a type some node of which declares a transform, and needs the flow and the languages to do it`);\n }\n if (compile instanceof Error) {\n throw new TransformFailed(t, void 0, `${compile.message} \\u2014 every task transforming in "${t.lang}" on this worker fails with this until the pool is rebuilt`);\n }\n const key = JSON.stringify([t.lang, t.expr]);\n let compiled = this.compiled.get(key);\n if (compiled === void 0) {\n try {\n compiled = compile(t.expr);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), `the worker half of "${t.lang}" refused the expression the registry half accepted: ${messageOf(e)}`);\n }\n if (typeof compiled?.evaluate !== "function") {\n throw new TransformFailed(t, void 0, `the worker half of "${t.lang}" compiled to ${brief(compiled)}, which has no evaluate(input, settings)`);\n }\n this.compiled.set(key, compiled);\n }\n try {\n return await compiled.evaluate(input, settings);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), messageOf(e));\n }\n }\n async teardown() {\n await this.inner?.teardown?.();\n this.inner = null;\n this.noInner = false;\n this.setupFailure = null;\n this.langs.clear();\n this.compiled.clear();\n }\n};\nexport {\n NotAnEnvelope,\n TransformFailed,\n EnvelopeWorker as default,\n unpackEnvelope\n};\n'], { type: "text/javascript" }));
|
|
34
|
+
var envelope_worker_default = URL.createObjectURL(new Blob(['// src/scheduler/envelope-worker.ts\nvar NotAnEnvelope = class extends Error {\n};\nvar TransformFailed = class extends Error {\n lang;\n expr;\n code;\n constructor(transform, code, why) {\n super(`transform failed \\u2014 ${transform.lang} ${JSON.stringify(transform.expr)}${code !== void 0 ? ` [${code}]` : ""}: ${why}`);\n this.lang = transform.lang;\n this.expr = transform.expr;\n this.code = code;\n }\n};\nfunction unpackEnvelope(payload) {\n if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {\n throw new NotAnEnvelope(`typed pool: the payload is not an envelope { settings, input } \\u2014 got ${brief(payload)}`);\n }\n const p = payload;\n for (const k of ["settings", "input"]) {\n if (!(k in p)) throw new NotAnEnvelope(`typed pool: the payload is not an envelope \\u2014 it has no "${k}" key (keys: ${Object.keys(p).map((x) => `"${x}"`).join(", ") || "none"})`);\n }\n const settings = p.settings;\n if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {\n throw new NotAnEnvelope(`typed pool: the envelope\'s settings is not a plain object \\u2014 got ${brief(settings)}`);\n }\n const env = { settings, input: p.input };\n if ("transform" in p) {\n const t = p.transform;\n const wellFormed = typeof t === "object" && t !== null && !Array.isArray(t) && typeof t.lang === "string" && typeof t.expr === "string" && Object.keys(t).length === 2;\n if (!wellFormed) throw new NotAnEnvelope(`typed pool: the envelope\'s transform is not { lang, expr } \\u2014 got ${brief(p.transform)}`);\n env.transform = { lang: t.lang, expr: t.expr };\n }\n return env;\n}\nfunction brief(v) {\n const s = printable(v);\n return s.length > 120 ? `${s.slice(0, 117)}...` : s;\n}\nfunction printable(v) {\n try {\n const j = JSON.stringify(v);\n if (j !== void 0) return j;\n } catch {\n }\n try {\n return String(v);\n } catch {\n return `an unprintable ${typeof v}`;\n }\n}\nfunction codeOf(e) {\n const c = e?.code;\n return typeof c === "string" && c.length ? c : void 0;\n}\nfunction messageOf(e) {\n if (e instanceof Error) return e.message;\n try {\n return String(e);\n } catch {\n return printable(e);\n }\n}\nvar EnvelopeWorker = class {\n cfg;\n workerContext;\n inner = null;\n /**\n * **Set by `setup()` when the pool config names no worker \u2014 a type that only TRANSFORMS** (ADR 0075).\n * It is a SEPARATE field from `inner === null` on purpose, and that is the whole of this arm\'s\n * correctness: `inner` is null before `setup()` and after `teardown()` too, which are LIFECYCLE\n * states `run()` has refused by name since v0.17, so reading the absence off `inner` would turn\n * *run() before setup()* into *return the input untouched* \u2014 a run that silently hands a worker\'s\n * successor the wrong value instead of failing the task. Measured at W109\'s declaration step: before\n * this item BOTH of those states threw, and neither was an arm.\n */\n noInner = false;\n /**\n * The worker half of each language the pool config names, loaded in `setup()` \u2014 or the reason\n * it did not load, REMEMBERED so that `run()` can fail the task with it (the maintainer\'s call,\n * see `setup()`).\n */\n langs = /* @__PURE__ */ new Map();\n /** Each distinct `(lang, expr)` compiled ONCE per worker instance, never per task \u2014 P17 decision 3. */\n compiled = /* @__PURE__ */ new Map();\n /**\n * **Why this worker could not set up, REMEMBERED \u2014 or `null`** \u2014\n * [W141](../../doc/work-items/w141-a-worker-that-cannot-set-up-fails-its-tasks-and-the-registry-has-one-shape.md),\n * [Q266](../../doc/questions/q266.md), P31 decision 3: W61\'s road for a language, taken for the whole\n * of `setup()`. Set by `setup()` instead of throwing; read FIRST by `run()`, which fails every task\n * with it; cleared by `teardown()`, so a torn-down worker is in its constructed state again.\n */\n setupFailure = null;\n /**\n * Both arguments are kept, and the second is forwarded to the inner worker: the scheduler passes\n * its worker-level context here, and a worker that only accepts `config` silently loses the\n * ability to self-terminate (the scheduler\'s own reference, \xA74).\n */\n constructor(config, workerContext) {\n this.cfg = config;\n this.workerContext = workerContext;\n }\n /**\n * Loads the type\'s worker and runs ITS setup \u2014 a module with no default class is refused by\n * name, as at v0.17 \u2014 then the worker half of every language the pool config names, ONCE per\n * worker instance (P17 decision 3).\n *\n * **A language module that FAILS TO LOAD does not throw here; the failure is remembered and\n * every task whose transform names that language fails at `run()` with it.** Measured at W61\'s\n * declaration, both runtimes, inline and threaded: a wrapper whose `setup()` throws leaves every\n * task of the pool `pending` with `attempts: 0`, the pool restarts its worker, and the scheduler\n * emits nothing a driver could turn into a record \u2014 the failure is reported on the POOL as\n * `worker.setup.failed` and `task.requeue_required`, a worker error and not a task\'s, which is\n * P17 prediction 2\'s stated reversal condition. The maintainer chose this road at W61\'s\n * stop-and-ask (ADR 0051 rule 5): a URL that does not resolve, a module with no default compile\n * function, or an empty URL, each becomes a `TransformFailed` naming the language and the load\n * error, on exactly the tasks that need that language \u2014 a task with no transform, or one in a\n * language that did load, still runs on this worker.\n *\n * **And since W141 the same is true of the TYPE\'s own worker: this method never throws** \u2014 Q266,\n * P31 decision 3, W61\'s road taken for the whole of `setup()`. F1 is a fact about THIS method\n * throwing, whatever threw, and W141 measured it again on 0.5.2 through `createRuntime`: three\n * tasks on a worker whose `setup()` throws stay `pending` for ever in both pool modes, the pool\n * requeueing them on each worker failure until its restart limit, and the run\'s fold says\n * `running`. So every way the type\'s worker can fail to set up \u2014 the module does not load, it\n * exports no default class, its constructor throws, its own `setup()` throws \u2014 and the two\n * pool-config refusals this method made by name, are REMEMBERED in `setupFailure`, and `run()`\n * fails every task on this worker with it: an ordinary task failure, which the run routes per\n * ADR 0004 \u2014 down an `on: ["failed"]` edge on an optional node, parked `user_action` on a\n * non-optional one. **A constructed inner worker is torn down at once**, because the scheduler\n * tears down a worker whose `setup()` threw (its own reference, \xA76: *a failing `setup()` still\n * tears the worker down*) and this class is now the only thing that knows one did. The worker\n * stays up and fails every task until the pool is rebuilt: a setup that failed for a transient\n * reason is not retried here (W141\'s Out).\n */\n async setup() {\n this.setupFailure = null;\n try {\n await this.setupOrThrow();\n } catch (e) {\n this.setupFailure = messageOf(e);\n const inner = this.inner;\n this.inner = null;\n this.noInner = false;\n this.langs.clear();\n if (inner !== null) {\n try {\n await inner.teardown?.();\n } catch (t) {\n this.setupFailure += ` (and its teardown() after the failed setup threw too: ${messageOf(t)})`;\n }\n }\n }\n }\n /** What `setup()` did until W141, throws and all; `setup()` turns each throw into `setupFailure`. */\n async setupOrThrow() {\n if (this.cfg?.transformOnly === true) {\n if (this.cfg.worker !== void 0) {\n throw new Error(`envelope worker: the pool config says transformOnly AND names worker ${brief(this.cfg.worker)} \\u2014 a type either has an inner worker or only transforms`);\n }\n this.noInner = true;\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n return;\n }\n if (typeof this.cfg?.worker !== "string" || this.cfg.worker.length === 0) {\n throw new Error(`envelope worker: the pool config names no worker URL \\u2014 got ${brief(this.cfg)}`);\n }\n const workerUrl = this.cfg.worker;\n let mod;\n try {\n mod = await import(workerUrl);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} did not load: ${messageOf(e)}`);\n }\n if (typeof mod.default !== "function" || !mod.default.prototype) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} must export a default class`);\n }\n const Inner = mod.default;\n try {\n this.inner = new Inner(this.cfg.config ?? {}, this.workerContext);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its constructor: ${messageOf(e)}`);\n }\n try {\n await this.inner.setup?.();\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its setup(): ${messageOf(e)}`);\n }\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n }\n /** One language\'s worker half, or the Error that stands in for it \u2014 never a throw (see `setup()`). */\n async loadLanguage(name, url) {\n if (typeof url !== "string" || url.length === 0) {\n return new Error(`language "${name}" names no worker module URL in the pool config \\u2014 got ${brief(url)}`);\n }\n try {\n const lm = await import(url);\n if (typeof lm.default !== "function") {\n return new Error(`language "${name}"\'s worker module ${url} must export a default compile function (got ${typeof lm.default})`);\n }\n return lm.default;\n } catch (e) {\n return new Error(`language "${name}"\'s worker module ${url} did not load: ${messageOf(e)}`);\n }\n }\n /**\n * `FD-C5`: the inner worker receives `{ settings, input }` as its payload, and the scheduler\'s task context as its\n * second argument, passed through untouched (W159). `FD-C6`: when the\n * envelope carries a `transform`, `input` is the evaluation of that expression over the value\n * received, with the node\'s settings bound as `$settings`, and the third key is consumed here.\n */\n async run(payload, context) {\n if (this.setupFailure !== null) {\n throw new Error(`${this.setupFailure} \\u2014 every task on this worker fails with this until the pool is rebuilt`);\n }\n if (this.inner === null && !this.noInner) throw new Error("envelope worker: run() before setup() \\u2014 the inner worker is not loaded");\n const env = unpackEnvelope(payload);\n if (this.noInner) {\n if (env.transform === void 0) {\n throw new NotAnEnvelope("typed pool: this pool`s type names no worker, so the envelope must carry a transform \\u2014 there is nothing else for the task to do (FD-V46 refuses such a node at construction)");\n }\n return await this.transform(env.transform, env.input, env.settings);\n }\n const input = env.transform === void 0 ? env.input : await this.transform(env.transform, env.input, env.settings);\n return await this.inner.run({ settings: env.settings, input }, context);\n }\n /** Evaluates one declared transform; every failure is a `TransformFailed` naming what failed and why. */\n async transform(t, input, settings) {\n const compile = this.langs.get(t.lang);\n if (compile === void 0) {\n const carried = Object.keys(this.cfg.languages ?? {}).map((x) => `"${x}"`).join(", ") || "no languages";\n throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \\u2014 the bootstrap helper writes every language it is given into every pool, so this one was not in the registry the pool was built from`);\n }\n if (compile instanceof Error) {\n throw new TransformFailed(t, void 0, `${compile.message} \\u2014 every task transforming in "${t.lang}" on this worker fails with this until the pool is rebuilt`);\n }\n const key = JSON.stringify([t.lang, t.expr]);\n let compiled = this.compiled.get(key);\n if (compiled === void 0) {\n try {\n compiled = compile(t.expr);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), `the worker half of "${t.lang}" refused the expression the registry half accepted: ${messageOf(e)}`);\n }\n if (typeof compiled?.evaluate !== "function") {\n throw new TransformFailed(t, void 0, `the worker half of "${t.lang}" compiled to ${brief(compiled)}, which has no evaluate(input, settings)`);\n }\n this.compiled.set(key, compiled);\n }\n try {\n return await compiled.evaluate(input, settings);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), messageOf(e));\n }\n }\n async teardown() {\n await this.inner?.teardown?.();\n this.inner = null;\n this.noInner = false;\n this.setupFailure = null;\n this.langs.clear();\n this.compiled.clear();\n }\n};\nexport {\n NotAnEnvelope,\n TransformFailed,\n EnvelopeWorker as default,\n unpackEnvelope\n};\n'], { type: "text/javascript" }));
|
|
32
35
|
|
|
33
36
|
// src/scheduler/bootstrap.ts
|
|
34
37
|
var ENVELOPE_WORKER_URL = envelope_worker_default;
|
|
35
38
|
var BootstrapError = class extends Error {
|
|
36
39
|
};
|
|
37
40
|
var defaultCreatePool = (scriptUrl, options) => new WorkerManager(scriptUrl, options);
|
|
38
|
-
function transformLanguagesByType(flows) {
|
|
39
|
-
const out = /* @__PURE__ */ new Map();
|
|
40
|
-
for (const flow of flows) {
|
|
41
|
-
for (const n of flow.nodes) {
|
|
42
|
-
if (n.kind !== "task") continue;
|
|
43
|
-
const t = n;
|
|
44
|
-
if (t.type === void 0 || t.transform === void 0) continue;
|
|
45
|
-
let set = out.get(t.type);
|
|
46
|
-
if (set === void 0) {
|
|
47
|
-
set = /* @__PURE__ */ new Set();
|
|
48
|
-
out.set(t.type, set);
|
|
49
|
-
}
|
|
50
|
-
set.add(t.transform.lang);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return out;
|
|
54
|
-
}
|
|
55
41
|
function bootstrap(registry, groups = {}, options = {}) {
|
|
56
42
|
checkRegistry(registry);
|
|
57
43
|
const workers = workersOf(registry.workers);
|
|
@@ -68,7 +54,7 @@ function bootstrap(registry, groups = {}, options = {}) {
|
|
|
68
54
|
for (const [key, inst] of named) {
|
|
69
55
|
const entry = types.get(inst.type);
|
|
70
56
|
const def = isDerivation(entry) ? entry : nodeTypeFromDeclaration(entry);
|
|
71
|
-
derived[key] = derivePool(key, inst.type, def, def.worker === void 0 ? void 0 : workers.get(def.worker), inst.config, groupKeys, ConfigRefused);
|
|
57
|
+
derived[key] = derivePool(key, inst.type, def, def.worker === void 0 ? void 0 : workers.get(def.worker), inst.config, groupKeys, ConfigRefused, inst.pool);
|
|
72
58
|
}
|
|
73
59
|
for (const [name, entry] of types) if (builtIn(name)) one(name, name, entry, (isDerivation(entry) ? entry : nodeTypeFromDeclaration(entry)).config, TypeDefaultRefused);
|
|
74
60
|
const namedByPools = /* @__PURE__ */ new Set();
|
|
@@ -79,28 +65,18 @@ function bootstrap(registry, groups = {}, options = {}) {
|
|
|
79
65
|
}
|
|
80
66
|
}
|
|
81
67
|
const adapters = languagesOf(options.languages);
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const urls = {};
|
|
88
|
-
for (const lang of [...langs].sort()) {
|
|
89
|
-
const adapter = adapters.get(lang);
|
|
90
|
-
if (adapter === void 0) {
|
|
91
|
-
throw new BootstrapError(`node type "${name}" has a node declaring transform.lang "${lang}", and no adapter was given for it (given: ${[...adapters.keys()].sort().map((x) => `"${x}"`).join(", ") || "none"}) \u2014 the wrapper would meet the transform with nothing to evaluate it`);
|
|
92
|
-
}
|
|
93
|
-
if (typeof adapter.worker !== "string" || adapter.worker.length === 0) {
|
|
94
|
-
throw new BootstrapError(`language "${lang}" names no worker module URL \u2014 LanguageAdapter.worker is the URL of the module whose default export compiles and evaluates it; a function cannot cross to a thread`);
|
|
95
|
-
}
|
|
96
|
-
urls[lang] = adapter.worker;
|
|
68
|
+
let languages;
|
|
69
|
+
for (const lang of [...adapters.keys()].sort()) {
|
|
70
|
+
const adapter = adapters.get(lang);
|
|
71
|
+
if (typeof adapter?.worker !== "string" || adapter.worker.length === 0) {
|
|
72
|
+
throw new BootstrapError(`language "${lang}" names no worker module URL \u2014 LanguageAdapter.worker is the URL of the module whose default export compiles and evaluates it; a function cannot cross to a thread`);
|
|
97
73
|
}
|
|
98
|
-
|
|
74
|
+
(languages ??= {})[lang] = adapter.worker;
|
|
99
75
|
}
|
|
100
76
|
const createPool = options.createPool ?? defaultCreatePool;
|
|
101
77
|
const workerPools = {};
|
|
102
78
|
for (const [key, p] of Object.entries(derived)) {
|
|
103
|
-
workerPools[key] = createPool(ENVELOPE_WORKER_URL, poolOptionsOf(p.spec, p.definition.inline, p.worker?.url,
|
|
79
|
+
workerPools[key] = createPool(ENVELOPE_WORKER_URL, poolOptionsOf(p.spec, p.definition.inline, p.worker?.url, languages === void 0 ? void 0 : { ...languages }));
|
|
104
80
|
}
|
|
105
81
|
return { workerPools, resourceGroups: { ...groups }, derived };
|
|
106
82
|
}
|
|
@@ -122,10 +98,11 @@ var GroupUnusable = class extends RuntimeError {
|
|
|
122
98
|
};
|
|
123
99
|
var GroupNotBuildable = class extends RuntimeError {
|
|
124
100
|
};
|
|
125
|
-
var LanguageNotBuilt = class extends RuntimeError {
|
|
126
|
-
};
|
|
127
101
|
var RunIdInUse = class extends RuntimeError {
|
|
128
102
|
};
|
|
103
|
+
var LimitKeyMissing = class extends RuntimeError {
|
|
104
|
+
};
|
|
105
|
+
var KEYED_RULE = "FD-R6";
|
|
129
106
|
var BUILT_IN_GROUP_FACTORIES = {
|
|
130
107
|
ConcurrentLimitGroup: (config, key) => new ConcurrentLimitGroup(config.limit, key),
|
|
131
108
|
RateLimitGroup: (config, key) => new RateLimitGroup(config.windows, key),
|
|
@@ -161,6 +138,10 @@ function checkGroup(key, typeName, group) {
|
|
|
161
138
|
return group;
|
|
162
139
|
}
|
|
163
140
|
function createRuntime(document, code = {}, options = {}) {
|
|
141
|
+
if (options.defaultRetryPolicy !== void 0) {
|
|
142
|
+
const problem = retryPolicyProblem(options.defaultRetryPolicy);
|
|
143
|
+
if (problem !== null) throw new InvalidDefinition(["FD-V30"], `defaultRetryPolicy is not a RetryPolicy \u2014 FD-V30: ${problem}`);
|
|
144
|
+
}
|
|
164
145
|
const resolved = resolveRegistry(document, code);
|
|
165
146
|
const suppliedPredicateNames = code.predicates === void 0 ? [] : code.predicates instanceof Map ? [...code.predicates.keys()] : code.predicates instanceof Set ? [...code.predicates] : Array.isArray(code.predicates) ? [...code.predicates] : Object.keys(code.predicates);
|
|
166
147
|
for (const [what, named, given] of [
|
|
@@ -192,7 +173,7 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
192
173
|
const nodeTypes = {};
|
|
193
174
|
for (const [name, def] of resolved.nodeTypes) nodeTypes[name] = def;
|
|
194
175
|
const pools = {};
|
|
195
|
-
for (const [key, p] of resolved.pools) if (key !== p.type) pools[key] = { type: p.type, config: p.config };
|
|
176
|
+
for (const [key, p] of resolved.pools) if (key !== p.type) pools[key] = { type: p.type, config: p.config, ...p.pool !== void 0 ? { pool: p.pool } : {} };
|
|
196
177
|
const languages = {};
|
|
197
178
|
for (const [name, adapter] of resolved.languages) languages[name] = adapter;
|
|
198
179
|
const namedByPools = {};
|
|
@@ -201,8 +182,7 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
201
182
|
}
|
|
202
183
|
const built = bootstrap({ workers, nodeTypes, pools }, namedByPools, {
|
|
203
184
|
...options.createPool !== void 0 ? { createPool: options.createPool } : {},
|
|
204
|
-
languages
|
|
205
|
-
flows: options.flows ?? []
|
|
185
|
+
languages
|
|
206
186
|
});
|
|
207
187
|
const scheduler = (options.createScheduler ?? ((p, g) => new FyflowScheduler(p, g)))(built.workerPools, groups);
|
|
208
188
|
const predicates = /* @__PURE__ */ new Map();
|
|
@@ -220,7 +200,40 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
220
200
|
languages,
|
|
221
201
|
...code.gateTypes !== void 0 ? { gateTypes: code.gateTypes } : {}
|
|
222
202
|
};
|
|
223
|
-
const
|
|
203
|
+
const validatorRegistry = {
|
|
204
|
+
predicates: new Set(predicates.keys()),
|
|
205
|
+
groups: new Set(registry.groups ?? []),
|
|
206
|
+
nodeTypes: nodeTypesOf(registry.nodeTypes),
|
|
207
|
+
languages: languagesOf(registry.languages),
|
|
208
|
+
workers: workersOf(registry.workers),
|
|
209
|
+
pools: poolsOf(registry.pools),
|
|
210
|
+
...registry.gateTypes !== void 0 ? { gateTypes: registry.gateTypes } : {}
|
|
211
|
+
};
|
|
212
|
+
const keyedByBuiltIn = /* @__PURE__ */ new Set();
|
|
213
|
+
for (const [key, g] of resolved.groups) if (g.type === "KeyedRateLimitGroup") keyedByBuiltIn.add(key);
|
|
214
|
+
const keyedFindings = (flow) => {
|
|
215
|
+
const nodes = flow?.nodes;
|
|
216
|
+
if (keyedByBuiltIn.size === 0 || typeof nodes?.[Symbol.iterator] !== "function") return [];
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const node of nodes) {
|
|
219
|
+
if (typeof node !== "object" || node === null) continue;
|
|
220
|
+
const n = node;
|
|
221
|
+
if (n.kind !== "task") continue;
|
|
222
|
+
const poolKey = typeof n.pool === "string" ? n.pool : typeof n.type === "string" ? n.type : void 0;
|
|
223
|
+
const fromPool = poolKey === void 0 ? [] : resolved.pools.get(poolKey)?.spec.groups ?? [];
|
|
224
|
+
const fromNode = Array.isArray(n.workerGroups) ? n.workerGroups.filter((g) => typeof g === "string") : [];
|
|
225
|
+
const reached = [.../* @__PURE__ */ new Set([...fromPool, ...fromNode])].filter((g) => keyedByBuiltIn.has(g)).sort();
|
|
226
|
+
if (reached.length === 0) continue;
|
|
227
|
+
const at = typeof n.id === "string" ? n.id : null;
|
|
228
|
+
out.push({
|
|
229
|
+
rule: KEYED_RULE,
|
|
230
|
+
at,
|
|
231
|
+
detail: `node ${at === null ? "<unnamed>" : q(at)} reaches keyed group${reached.length > 1 ? "s" : ""} ${reached.map(q).join(", ")}, built by the built-in KeyedRateLimitGroup factory with no keyFrom \u2014 so every task of it needs a limitKey, which nothing sets unless start is given a createTask, and without one the scheduler's addTask throws "Missing limit key". Two roads work: pass start a createTask that sets limitKey on the task it builds, or declare the group with a code group type (code.groupTypes) whose create() builds a KeyedRateLimitGroup with a keyFrom that derives the key. A key derived from the flow itself is Q248, deferred`
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
};
|
|
236
|
+
const check = (flow) => [...validateDefinition(flow, validatorRegistry), ...keyedFindings(flow)];
|
|
224
237
|
let minted = 0;
|
|
225
238
|
const inUse = /* @__PURE__ */ new Set();
|
|
226
239
|
const router = settlementRouter(scheduler);
|
|
@@ -237,6 +250,7 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
237
250
|
scheduler,
|
|
238
251
|
pools: built.derived,
|
|
239
252
|
groups,
|
|
253
|
+
check,
|
|
240
254
|
start(flow, payload, startOptions = {}) {
|
|
241
255
|
return openRun(flow, payload, startOptions, null);
|
|
242
256
|
},
|
|
@@ -252,14 +266,12 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
252
266
|
};
|
|
253
267
|
return runtime;
|
|
254
268
|
function openRun(flow, payload, startOptions, from) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
}
|
|
269
|
+
const findings = check(flow);
|
|
270
|
+
const definition = findings.filter((f) => f.rule !== KEYED_RULE);
|
|
271
|
+
if (definition.length) throw InvalidDefinition.fromFindings(flow.flowId, definition);
|
|
272
|
+
const keyed = startOptions.createTask === void 0 ? findings.filter((f) => f.rule === KEYED_RULE) : [];
|
|
273
|
+
if (keyed.length) {
|
|
274
|
+
throw new LimitKeyMissing(keyed[0].at ?? "<document>", `flow "${flow.flowId}" cannot start with no createTask \u2014 ` + keyed.map((f) => `${f.rule} at ${f.at ?? "<document>"}: ${f.detail}`).join("; ") + " (ADR 0097)");
|
|
263
275
|
}
|
|
264
276
|
const runId = startOptions.runId ?? mint();
|
|
265
277
|
if (inUse.has(runId)) {
|
|
@@ -275,10 +287,33 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
275
287
|
const settleIfTerminal = () => {
|
|
276
288
|
if (settled || !isTerminal(state)) return;
|
|
277
289
|
settled = true;
|
|
290
|
+
if (state.outcome === "interrupted" && stopping.size > 0) {
|
|
291
|
+
const at = state;
|
|
292
|
+
const fin = () => {
|
|
293
|
+
settle(at);
|
|
294
|
+
router.release(runId);
|
|
295
|
+
};
|
|
296
|
+
void Promise.allSettled([...stopping]).then(fin);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
278
299
|
settle(state);
|
|
279
300
|
router.release(runId);
|
|
280
301
|
};
|
|
281
|
-
const
|
|
302
|
+
const view = router.forRun(runId);
|
|
303
|
+
const stopping = /* @__PURE__ */ new Set();
|
|
304
|
+
const viewCancel = view.cancel;
|
|
305
|
+
const scheduler2 = typeof viewCancel === "function" ? {
|
|
306
|
+
...view,
|
|
307
|
+
cancel: (id, cancelOptions) => {
|
|
308
|
+
const p = viewCancel(id, cancelOptions);
|
|
309
|
+
stopping.add(p);
|
|
310
|
+
const drop = () => {
|
|
311
|
+
stopping.delete(p);
|
|
312
|
+
};
|
|
313
|
+
p.then(drop, drop);
|
|
314
|
+
return p;
|
|
315
|
+
}
|
|
316
|
+
} : view;
|
|
282
317
|
const driver = new Driver(flow, {
|
|
283
318
|
runId,
|
|
284
319
|
scheduler: scheduler2,
|
|
@@ -306,8 +341,13 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
306
341
|
queueMicrotask(settleIfTerminal);
|
|
307
342
|
startOptions.onFoldInput?.(input, seq);
|
|
308
343
|
},
|
|
344
|
+
...startOptions.onHeader !== void 0 ? { onHeader: startOptions.onHeader } : {},
|
|
309
345
|
...startOptions.onRegionEvent !== void 0 ? { onRegionEvent: startOptions.onRegionEvent } : {},
|
|
310
346
|
...startOptions.onRunFailure !== void 0 ? { onRunFailure: startOptions.onRunFailure } : {},
|
|
347
|
+
// **Spread only when given** (`RN-R42`): the driver subscribes the pass-through events for a callback present, so an
|
|
348
|
+
// `undefined` forwarded here must stay ABSENT — and on a runtime the scheduler is subscribed once the first run asks.
|
|
349
|
+
...startOptions.onTaskMessage !== void 0 ? { onTaskMessage: startOptions.onTaskMessage } : {},
|
|
350
|
+
...startOptions.onTaskProgress !== void 0 ? { onTaskProgress: startOptions.onTaskProgress } : {},
|
|
311
351
|
...startOptions.onUnsatisfiableJoin !== void 0 ? { onUnsatisfiableJoin: startOptions.onUnsatisfiableJoin } : {},
|
|
312
352
|
...startOptions.onUnmintedTask !== void 0 ? { onUnmintedTask: startOptions.onUnmintedTask } : {},
|
|
313
353
|
...startOptions.onNonconformingResult !== void 0 ? { onNonconformingResult: startOptions.onNonconformingResult } : {},
|
|
@@ -321,7 +361,7 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
321
361
|
router.release(runId);
|
|
322
362
|
throw e;
|
|
323
363
|
}
|
|
324
|
-
return { driver, done, header: driver.checkpoint().header };
|
|
364
|
+
return { driver, done, header: driver.checkpoint().header, stop: (stopOptions) => driver.stop(stopOptions) };
|
|
325
365
|
}
|
|
326
366
|
}
|
|
327
367
|
function settlementRouter(scheduler) {
|
|
@@ -329,7 +369,8 @@ function settlementRouter(scheduler) {
|
|
|
329
369
|
const every = /* @__PURE__ */ new Map();
|
|
330
370
|
const deliver = (name) => (e) => {
|
|
331
371
|
const detail = "detail" in e ? e.detail : void 0;
|
|
332
|
-
const
|
|
372
|
+
const d = typeof detail === "object" && detail !== null ? detail : void 0;
|
|
373
|
+
const id = typeof d?.id === "string" ? d.id : d?.taskId;
|
|
333
374
|
let runId = null;
|
|
334
375
|
if (typeof id === "string") {
|
|
335
376
|
try {
|
|
@@ -355,7 +396,9 @@ function settlementRouter(scheduler) {
|
|
|
355
396
|
};
|
|
356
397
|
return {
|
|
357
398
|
forRun(runId) {
|
|
399
|
+
const cancel = scheduler.cancel;
|
|
358
400
|
return {
|
|
401
|
+
...typeof cancel === "function" ? { cancel: (id, options) => cancel.call(scheduler, id, options) } : {},
|
|
359
402
|
addTask: (task, options) => scheduler.addTask(task, options),
|
|
360
403
|
addEventListener(name, listener) {
|
|
361
404
|
let mine = byRun.get(runId);
|
|
@@ -589,7 +632,8 @@ var EnvelopeWorker = class {
|
|
|
589
632
|
}
|
|
590
633
|
}
|
|
591
634
|
/**
|
|
592
|
-
* `FD-C5`: the inner worker receives `{ settings, input }` and
|
|
635
|
+
* `FD-C5`: the inner worker receives `{ settings, input }` as its payload, and the scheduler's task context as its
|
|
636
|
+
* second argument, passed through untouched (W159). `FD-C6`: when the
|
|
593
637
|
* envelope carries a `transform`, `input` is the evaluation of that expression over the value
|
|
594
638
|
* received, with the node's settings bound as `$settings`, and the third key is consumed here.
|
|
595
639
|
*/
|
|
@@ -613,7 +657,7 @@ var EnvelopeWorker = class {
|
|
|
613
657
|
const compile = this.langs.get(t.lang);
|
|
614
658
|
if (compile === void 0) {
|
|
615
659
|
const carried = Object.keys(this.cfg.languages ?? {}).map((x) => `"${x}"`).join(", ") || "no languages";
|
|
616
|
-
throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \u2014 the bootstrap helper writes
|
|
660
|
+
throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \u2014 the bootstrap helper writes every language it is given into every pool, so this one was not in the registry the pool was built from`);
|
|
617
661
|
}
|
|
618
662
|
if (compile instanceof Error) {
|
|
619
663
|
throw new TransformFailed(t, void 0, `${compile.message} \u2014 every task transforming in "${t.lang}" on this worker fails with this until the pool is rebuilt`);
|
|
@@ -654,13 +698,12 @@ export {
|
|
|
654
698
|
GROUP_MEMBERS,
|
|
655
699
|
GroupNotBuildable,
|
|
656
700
|
GroupUnusable,
|
|
657
|
-
|
|
701
|
+
LimitKeyMissing,
|
|
658
702
|
NotAnEnvelope,
|
|
659
703
|
RunIdInUse,
|
|
660
704
|
RuntimeError,
|
|
661
705
|
TransformFailed,
|
|
662
706
|
bootstrap,
|
|
663
707
|
createRuntime,
|
|
664
|
-
transformLanguagesByType,
|
|
665
708
|
unpackEnvelope
|
|
666
709
|
};
|