fyflow-orchestration 0.10.0 → 0.11.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 +173 -5
- package/README.md +1 -1
- package/dist/browser/chunks/{chunk-NA6QQAZ2.js → chunk-5CVSWDCB.js} +79 -12
- package/dist/browser/index.js +539 -3
- package/dist/browser/scheduler/index.js +1 -1
- package/dist/node/chunks/{chunk-NA6QQAZ2.js → chunk-5CVSWDCB.js} +79 -12
- package/dist/node/index.js +539 -3
- package/dist/node/scheduler/index.js +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/src/definition/definition-schema.d.ts +23 -0
- package/dist/types/src/definition/types.d.ts +2 -1
- package/dist/types/src/definition/validate.d.ts +41 -2
- package/dist/types/src/definition/wiring.d.ts +10 -0
- package/dist/types/src/driver/driver.d.ts +1 -1
- package/dist/types/src/fold/run-state.d.ts +17 -0
- package/package.json +2 -1
|
@@ -416,7 +416,10 @@ function evaluateDeclared(when, scope, unknownOp) {
|
|
|
416
416
|
// src/definition/validate.ts
|
|
417
417
|
var ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
418
418
|
var KINDS = ["task", "fanout", "map", "join", "end", "gate", "ask"];
|
|
419
|
+
var ID_RE_COPY = new RegExp(ID_RE.source, ID_RE.flags);
|
|
420
|
+
var KINDS_COPY = Object.freeze([...KINDS]);
|
|
419
421
|
var ROUTABLE = ["done", "failed"];
|
|
422
|
+
var ROUTABLE_STATES = [...ROUTABLE];
|
|
420
423
|
var DOC_KEYS = ["flowId", "version", "entry", "nodes"];
|
|
421
424
|
var NODE_KEYS = {
|
|
422
425
|
// `retryPolicy` and `workerGroups` since v0.16 (W58): what a node says about its OWN task,
|
|
@@ -451,6 +454,11 @@ var TASK_KEYS_DECLARED = [...NODE_KEYS.task.req, ...NODE_KEYS.task.opt];
|
|
|
451
454
|
var NODE_KEYS_DECLARED = Object.fromEntries(
|
|
452
455
|
Object.entries(NODE_KEYS).map(([kind, k]) => [kind, [...k.req, ...k.opt]])
|
|
453
456
|
);
|
|
457
|
+
var NODE_KEYS_REQUIRED = Object.fromEntries(
|
|
458
|
+
Object.entries(NODE_KEYS).map(([kind, k]) => [kind, [...k.req]])
|
|
459
|
+
);
|
|
460
|
+
var EDGE_KEYS_REQUIRED = [...EDGE_KEYS.req];
|
|
461
|
+
var DOC_KEYS_DECLARED = [...DOC_KEYS];
|
|
454
462
|
var RETRY_POLICY_KEYS = ["maxRetries", "backoffMs"];
|
|
455
463
|
function retryPolicyProblem(v) {
|
|
456
464
|
if (typeof v !== "object" || v === null || Array.isArray(v)) {
|
|
@@ -958,10 +966,35 @@ function referenceTarget(ref) {
|
|
|
958
966
|
}
|
|
959
967
|
return null;
|
|
960
968
|
}
|
|
969
|
+
var ERROR_PART = "!error";
|
|
961
970
|
function aimed(s, form) {
|
|
962
971
|
const conditional = s.endsWith("?");
|
|
963
|
-
const
|
|
964
|
-
|
|
972
|
+
const body = conditional ? s.slice(0, -1) : s;
|
|
973
|
+
const error = body.endsWith(ERROR_PART);
|
|
974
|
+
const node = error ? body.slice(0, -ERROR_PART.length) : body;
|
|
975
|
+
if (!ID_RE.test(node)) return null;
|
|
976
|
+
return error ? { form, node, conditional, part: "error" } : { form, node, conditional };
|
|
977
|
+
}
|
|
978
|
+
var spelled = (t, conditional) => `${t.node}${t.part === "error" ? ERROR_PART : ""}${conditional ? "?" : ""}`;
|
|
979
|
+
function bangProblem(ref) {
|
|
980
|
+
const array = Array.isArray(ref) && ref.length === 1;
|
|
981
|
+
const one = array ? ref[0] : ref;
|
|
982
|
+
if (typeof one !== "string" || one.startsWith("$") || !one.includes("!")) return null;
|
|
983
|
+
const wrap = (s) => JSON.stringify(array ? [s] : s);
|
|
984
|
+
const at = one.indexOf("!");
|
|
985
|
+
const before = one.slice(0, at);
|
|
986
|
+
const after = one.slice(at + 1);
|
|
987
|
+
const afterBare = after.endsWith("?") ? after.slice(0, -1) : after;
|
|
988
|
+
if (afterBare === "error" && before.endsWith("?") && ID_RE.test(before.slice(0, -1))) {
|
|
989
|
+
return `${JSON.stringify(ref)} carries "?" before "!error", and the suffix goes last: write ${wrap(`${before.slice(0, -1)}!error?`)}`;
|
|
990
|
+
}
|
|
991
|
+
if (before === "") {
|
|
992
|
+
return `${JSON.stringify(ref)} names no node before "!": write the node whose error you mean first, as ${wrap(`<node>!error${one.endsWith("?") ? "?" : ""}`)}`;
|
|
993
|
+
}
|
|
994
|
+
if (afterBare !== "error" && ID_RE.test(before)) {
|
|
995
|
+
return `${JSON.stringify(ref)} names "${after}" after "!", and the one part of a node's outcome a reference may name is its error: write ${wrap(`${before}!error${after.endsWith("?") ? "?" : ""}`)}, or ${wrap(`${before}${after.endsWith("?") ? "?" : ""}`)} for its result`;
|
|
996
|
+
}
|
|
997
|
+
return null;
|
|
965
998
|
}
|
|
966
999
|
var PAYLOAD_TOKEN = "$payload";
|
|
967
1000
|
var ITEM_TOKEN = "$item";
|
|
@@ -1252,10 +1285,13 @@ function validateDefinition(doc, registry) {
|
|
|
1252
1285
|
if (!ID_RE.test(k)) add("FD-V26", where, `needs key ${JSON.stringify(k)} is not [A-Za-z0-9_-]{1,64}`);
|
|
1253
1286
|
const target = referenceTarget(ref);
|
|
1254
1287
|
const dollar = target === null ? dollarProblem(ref) : null;
|
|
1288
|
+
const bang = target === null && dollar === null ? bangProblem(ref) : null;
|
|
1255
1289
|
if (dollar !== null) {
|
|
1256
1290
|
add("FD-V26", where, `needs[${JSON.stringify(k)}]: ${dollar}`);
|
|
1291
|
+
} else if (bang !== null) {
|
|
1292
|
+
add("FD-V26", where, `needs[${JSON.stringify(k)}]: ${bang}`);
|
|
1257
1293
|
} else if (target === null) {
|
|
1258
|
-
add("FD-V26", where, `needs[${JSON.stringify(k)}] is ${JSON.stringify(ref)}, which is not "." , "$payload", "$item", a node id with an optional "?" suffix, or a one-element array holding one`);
|
|
1294
|
+
add("FD-V26", where, `needs[${JSON.stringify(k)}] is ${JSON.stringify(ref)}, which is not "." , "$payload", "$item", a node id with an optional "!error" and an optional "?" suffix, or a one-element array holding one`);
|
|
1259
1295
|
}
|
|
1260
1296
|
}
|
|
1261
1297
|
}
|
|
@@ -1318,10 +1354,13 @@ function validateDefinition(doc, registry) {
|
|
|
1318
1354
|
if (!ID_RE.test(k)) add("FD-V26", at, `edge needs key ${JSON.stringify(k)} is not [A-Za-z0-9_-]{1,64}`);
|
|
1319
1355
|
const target = referenceTarget(ref);
|
|
1320
1356
|
const dollar = target === null ? dollarProblem(ref) : null;
|
|
1357
|
+
const bang = target === null && dollar === null ? bangProblem(ref) : null;
|
|
1321
1358
|
if (dollar !== null) {
|
|
1322
1359
|
add("FD-V26", at, `edge needs[${JSON.stringify(k)}]: ${dollar}`);
|
|
1360
|
+
} else if (bang !== null) {
|
|
1361
|
+
add("FD-V26", at, `edge needs[${JSON.stringify(k)}]: ${bang}`);
|
|
1323
1362
|
} else if (target === null) {
|
|
1324
|
-
add("FD-V26", at, `edge needs[${JSON.stringify(k)}] is ${JSON.stringify(ref)}, which is not "$payload", "$item", a node id with an optional "?" suffix or a one-element array holding one`);
|
|
1363
|
+
add("FD-V26", at, `edge needs[${JSON.stringify(k)}] is ${JSON.stringify(ref)}, which is not "$payload", "$item", a node id with an optional "!error" and an optional "?" suffix or a one-element array holding one`);
|
|
1325
1364
|
} else if (target.form === "self") {
|
|
1326
1365
|
add("FD-V26", at, `edge needs[${JSON.stringify(k)}] is ".", which an EDGE may not use: what a predicate would have received anyway is its node's own outcome, and \`ctx.outcome\` already carries it`);
|
|
1327
1366
|
}
|
|
@@ -1502,14 +1541,14 @@ function validateDefinition(doc, registry) {
|
|
|
1502
1541
|
add(
|
|
1503
1542
|
"FD-V29",
|
|
1504
1543
|
at,
|
|
1505
|
-
`${site}[${JSON.stringify(k)}] names "${
|
|
1544
|
+
`${site}[${JSON.stringify(k)}] names "${spelled(t, false)}" unconditionally, and a run can reach this node without "${target}" having run \u2014 a decision routes around it, or it is inside a region whose branch may be lost before it is entered. \`FD-V27\` checked that this node is reachable FROM it, which is weaker. Write "${spelled(t, true)}"${t.form === "all" ? " inside the array" : ""} if you mean *if it ran*, and read the key as ${t.form === "all" ? "a possibly-empty array" : "possibly absent"}; otherwise move the reference to a node every path reaches`
|
|
1506
1545
|
);
|
|
1507
1546
|
}
|
|
1508
1547
|
const many = regions.some((r) => r.opensWith === "map" && r.members.has(target) && !r.members.has(n.id));
|
|
1509
1548
|
if (many && t.form === "one") {
|
|
1510
|
-
add("FD-V28", at, `${site}[${JSON.stringify(k)}] names "${
|
|
1549
|
+
add("FD-V28", at, `${site}[${JSON.stringify(k)}] names "${spelled(t, false)}" as one value, but it is inside a map region this node is outside of, so it produces one output per ITEM; write ["${spelled(t, false)}"]`);
|
|
1511
1550
|
} else if (!many && t.form === "all") {
|
|
1512
|
-
add("FD-V28", at, `${site}[${JSON.stringify(k)}] names ["${
|
|
1551
|
+
add("FD-V28", at, `${site}[${JSON.stringify(k)}] names ["${spelled(t, false)}"] as a list, but it produces exactly one output on this node's branch path; write "${spelled(t, false)}"`);
|
|
1513
1552
|
}
|
|
1514
1553
|
}
|
|
1515
1554
|
}
|
|
@@ -2025,6 +2064,7 @@ var typesOf = (s) => {
|
|
|
2025
2064
|
var widen = (ts) => ts.flatMap((t) => t === "integer" ? ["integer"] : t === "number" ? ["number", "integer"] : [t]);
|
|
2026
2065
|
var canon = (v) => JSON.stringify(v);
|
|
2027
2066
|
var BRANCH_KEY = /^\d+(\.\d+)*$/;
|
|
2067
|
+
var ERROR_SCHEMA = Object.freeze({ type: ["string", "null"] });
|
|
2028
2068
|
function fits(P, C, path = "$") {
|
|
2029
2069
|
const p = P;
|
|
2030
2070
|
const c = C;
|
|
@@ -2419,6 +2459,11 @@ var Wiring = class {
|
|
|
2419
2459
|
value = a.sample?.value;
|
|
2420
2460
|
src = a.sample?.source ?? "witness";
|
|
2421
2461
|
dot.push(key);
|
|
2462
|
+
} else if (t.part === "error") {
|
|
2463
|
+
schema2 = ERROR_SCHEMA;
|
|
2464
|
+
const w = sampleOf(ERROR_SCHEMA);
|
|
2465
|
+
value = w.value;
|
|
2466
|
+
src = w.source;
|
|
2422
2467
|
} else {
|
|
2423
2468
|
const p = this.produced(t.node);
|
|
2424
2469
|
if (p.schema === void 0) return { ...base, unchecked: `needs.${key} names "${t.node}", which ${p.unchecked}` };
|
|
@@ -2538,6 +2583,7 @@ async function judgeTransform(v, node, transform, type, consumer, consumerWhy, l
|
|
|
2538
2583
|
for (const input of inputs) results.push(await c.evaluate(input, settings));
|
|
2539
2584
|
} catch (e) {
|
|
2540
2585
|
v.kind = "transform-fails";
|
|
2586
|
+
v.cause = "threw";
|
|
2541
2587
|
v.why = `the transform threw on the ${sourceWord(sample.source)}: ${e instanceof Error ? e.message : String(e)}`;
|
|
2542
2588
|
v.sample = { ...sample, result: e instanceof Error ? e.message : String(e) };
|
|
2543
2589
|
return;
|
|
@@ -2551,6 +2597,7 @@ async function judgeTransform(v, node, transform, type, consumer, consumerWhy, l
|
|
|
2551
2597
|
const bad = results.map((r, i) => [i, findings(consumer, r)]).find(([, f]) => f.length > 0);
|
|
2552
2598
|
if (bad) {
|
|
2553
2599
|
v.kind = "transform-fails";
|
|
2600
|
+
v.cause = "schema";
|
|
2554
2601
|
v.why = `the transform's result on the ${sourceWord(sample.source)}${perItem ? ` (item ${bad[0]})` : ""} fails the consumer's inputSchema \u2014 ${describeFindings(bad[1])}`;
|
|
2555
2602
|
} else {
|
|
2556
2603
|
v.kind = "transform-passes";
|
|
@@ -3046,6 +3093,11 @@ function sameData(a, b) {
|
|
|
3046
3093
|
const kb = Object.keys(rb).filter((k) => rb[k] !== void 0);
|
|
3047
3094
|
return ka.length === kb.length && ka.every((k) => Object.hasOwn(rb, k) && sameData(ra[k], rb[k]));
|
|
3048
3095
|
}
|
|
3096
|
+
function storedAskedAt(e) {
|
|
3097
|
+
if (e === void 0 || e === null || typeof e !== "object" || e.kind !== "asked") return void 0;
|
|
3098
|
+
const t = e.observedAt;
|
|
3099
|
+
return typeof t === "number" ? t : void 0;
|
|
3100
|
+
}
|
|
3049
3101
|
function isFed(e) {
|
|
3050
3102
|
return e.kind === "outcome" || e.kind === "resolution" || e.kind === "resumed" || e.kind === "interrupted" || e.kind === "paused" || e.kind === "unpaused" || e.kind === "attempted";
|
|
3051
3103
|
}
|
|
@@ -5408,7 +5460,7 @@ var Driver = class _Driver {
|
|
|
5408
5460
|
}
|
|
5409
5461
|
/**
|
|
5410
5462
|
* **One task's entry, made what its effective outcome now says** — `outputs()`'s catch-up, for a task one of whose records
|
|
5411
|
-
* was just read. An entry is `{ path, result }` with `result` the record's own object (`RN-R13`); a node's list is kept
|
|
5463
|
+
* was just read. An entry is `{ path, result, error }` with `result` the record's own object (`RN-R13`) and `error` its string or `null` (v0.49, ADR 0131); a node's list is kept
|
|
5412
5464
|
* in its tasks' FIRST-RECORD order, the order the rebuild grouped them in.
|
|
5413
5465
|
*
|
|
5414
5466
|
* **`!eff` — a suspended node contributes no entry.** The first record of a `user_action` task has none, and its
|
|
@@ -5441,7 +5493,7 @@ var Driver = class _Driver {
|
|
|
5441
5493
|
}
|
|
5442
5494
|
return;
|
|
5443
5495
|
}
|
|
5444
|
-
const entry = { path: t.path, result: eff.result };
|
|
5496
|
+
const entry = { path: t.path, result: eff.result, error: eff.error };
|
|
5445
5497
|
if (t.entry !== null && list !== void 0) {
|
|
5446
5498
|
list[list.indexOf(t.entry)] = entry;
|
|
5447
5499
|
this.reindex(c, t.nodeId, t.entry, entry);
|
|
@@ -5616,6 +5668,9 @@ var Driver = class _Driver {
|
|
|
5616
5668
|
}
|
|
5617
5669
|
const c = this.outputsCache;
|
|
5618
5670
|
c.examined += t.form === "all" ? visible.length : 1;
|
|
5671
|
+
if (t.part === "error") {
|
|
5672
|
+
return t.form === "all" ? visible.map((e) => e.error) : visible[0].error;
|
|
5673
|
+
}
|
|
5619
5674
|
return t.form === "all" ? visible.map((e) => this.sharedOf(c, e)) : this.sharedOf(c, visible[0]);
|
|
5620
5675
|
}
|
|
5621
5676
|
/** **An output entry's shared copy**, made at its first read and kept on the entry (`OutputEntry.shared`) — counted. */
|
|
@@ -5782,7 +5837,10 @@ var Driver = class _Driver {
|
|
|
5782
5837
|
throw new DuplicateTaskId(`"${taskId}" was already asked in run "${runId}" \u2014 an ask's id is minted once per path (ID-R1)`);
|
|
5783
5838
|
}
|
|
5784
5839
|
this.asked.add(taskId);
|
|
5785
|
-
|
|
5840
|
+
const replay = this.replay;
|
|
5841
|
+
const at = this.inputs.length;
|
|
5842
|
+
const observedAt = replay !== null && at < replay.stored.length ? storedAskedAt(replay.stored[at]) : this.now();
|
|
5843
|
+
this.append(observedAt === void 0 ? { kind: "asked", taskId, input: cloneValue(received) } : { kind: "asked", taskId, input: cloneValue(received), observedAt });
|
|
5786
5844
|
if (this.shut()) return;
|
|
5787
5845
|
const region = this.regionOf(node.id);
|
|
5788
5846
|
if (region) {
|
|
@@ -6676,7 +6734,9 @@ var Accumulator = class _Accumulator {
|
|
|
6676
6734
|
state: "suspended",
|
|
6677
6735
|
result: null,
|
|
6678
6736
|
error: null,
|
|
6679
|
-
input: structuredClone(input.input)
|
|
6737
|
+
input: structuredClone(input.input),
|
|
6738
|
+
// `RN-R36` v0.35 (W208, ADR 0130): WHEN it was asked, where the element says — and only there.
|
|
6739
|
+
...input.observedAt !== void 0 ? { askedAt: input.observedAt } : {}
|
|
6680
6740
|
});
|
|
6681
6741
|
return this.withOutcome();
|
|
6682
6742
|
}
|
|
@@ -6744,7 +6804,8 @@ var Accumulator = class _Accumulator {
|
|
|
6744
6804
|
state: r.resolvedAs,
|
|
6745
6805
|
result: r.result,
|
|
6746
6806
|
error: r.error,
|
|
6747
|
-
...existing !== void 0 && "input" in existing ? { input: existing.input } : {}
|
|
6807
|
+
...existing !== void 0 && "input" in existing ? { input: existing.input } : {},
|
|
6808
|
+
...existing?.askedAt !== void 0 ? { askedAt: existing.askedAt } : {}
|
|
6748
6809
|
});
|
|
6749
6810
|
}
|
|
6750
6811
|
this.runId = this.runId ?? runId;
|
|
@@ -6920,10 +6981,16 @@ export {
|
|
|
6920
6981
|
findings,
|
|
6921
6982
|
describeFindings,
|
|
6922
6983
|
readKeyPath,
|
|
6984
|
+
ID_RE_COPY,
|
|
6985
|
+
KINDS_COPY,
|
|
6986
|
+
ROUTABLE_STATES,
|
|
6923
6987
|
ASK_REFUSED_KEYS,
|
|
6924
6988
|
EDGE_KEYS_DECLARED,
|
|
6925
6989
|
TASK_KEYS_DECLARED,
|
|
6926
6990
|
NODE_KEYS_DECLARED,
|
|
6991
|
+
NODE_KEYS_REQUIRED,
|
|
6992
|
+
EDGE_KEYS_REQUIRED,
|
|
6993
|
+
DOC_KEYS_DECLARED,
|
|
6927
6994
|
RETRY_POLICY_KEYS,
|
|
6928
6995
|
retryPolicyProblem,
|
|
6929
6996
|
BUILT_IN_NODE_TYPES,
|