@particle-academy/fancy-flow 0.77.1 → 0.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/{FlowViewer-Ak8kbY0e.d.cts → FlowViewer-BqYQl2vS.d.cts} +1 -1
  2. package/dist/{FlowViewer-BAV29Zpi.d.ts → FlowViewer-_Fq8AX9V.d.ts} +1 -1
  3. package/dist/{HumanPrompt-xYPCL2jO.d.cts → HumanPrompt-Sl1wD9Bx.d.cts} +2 -2
  4. package/dist/{HumanPrompt-Ge_ccCgS.d.ts → HumanPrompt-_SKIdSpx.d.ts} +2 -2
  5. package/dist/{chunk-T6WIIGSK.js → chunk-I5EPWHNM.js} +3 -3
  6. package/dist/{chunk-T6WIIGSK.js.map → chunk-I5EPWHNM.js.map} +1 -1
  7. package/dist/{chunk-EGXA4SYJ.js → chunk-J66LOLJR.js} +4 -4
  8. package/dist/{chunk-EGXA4SYJ.js.map → chunk-J66LOLJR.js.map} +1 -1
  9. package/dist/{chunk-G5RAGPVD.js → chunk-KMONOPAK.js} +3 -3
  10. package/dist/{chunk-G5RAGPVD.js.map → chunk-KMONOPAK.js.map} +1 -1
  11. package/dist/{chunk-ARSOSEJV.js → chunk-LBE4L3DS.js} +4 -4
  12. package/dist/{chunk-ARSOSEJV.js.map → chunk-LBE4L3DS.js.map} +1 -1
  13. package/dist/{chunk-EEIWAIXW.js → chunk-OZHBAXMA.js} +125 -8
  14. package/dist/chunk-OZHBAXMA.js.map +1 -0
  15. package/dist/{chunk-2VI24UPB.js → chunk-QMJVUT7C.js} +3 -3
  16. package/dist/{chunk-2VI24UPB.js.map → chunk-QMJVUT7C.js.map} +1 -1
  17. package/dist/{chunk-CQAMEPPI.js → chunk-R4G7SPLD.js} +3 -3
  18. package/dist/{chunk-CQAMEPPI.js.map → chunk-R4G7SPLD.js.map} +1 -1
  19. package/dist/connectors.d.cts +2 -2
  20. package/dist/connectors.d.ts +2 -2
  21. package/dist/durable/index.d.cts +2 -2
  22. package/dist/durable/index.d.ts +2 -2
  23. package/dist/durable.cjs +123 -6
  24. package/dist/durable.cjs.map +1 -1
  25. package/dist/durable.js +1 -1
  26. package/dist/engine.cjs +123 -6
  27. package/dist/engine.cjs.map +1 -1
  28. package/dist/engine.d.cts +6 -6
  29. package/dist/engine.d.ts +6 -6
  30. package/dist/engine.js +4 -4
  31. package/dist/fields/react-fancy.d.cts +3 -3
  32. package/dist/fields/react-fancy.d.ts +3 -3
  33. package/dist/index.cjs +123 -6
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +10 -10
  36. package/dist/index.d.ts +10 -10
  37. package/dist/index.js +12 -12
  38. package/dist/layout/index.d.cts +1 -1
  39. package/dist/layout/index.d.ts +1 -1
  40. package/dist/llm/prism.d.cts +1 -1
  41. package/dist/llm/prism.d.ts +1 -1
  42. package/dist/llm/vercel-ai.d.cts +1 -1
  43. package/dist/llm/vercel-ai.d.ts +1 -1
  44. package/dist/registry/index.d.cts +5 -5
  45. package/dist/registry/index.d.ts +5 -5
  46. package/dist/{registry-JUcN_xWK.d.ts → registry-C4GcH583.d.ts} +1 -1
  47. package/dist/{registry-B6SiTaLZ.d.cts → registry-DWIY3bMP.d.cts} +1 -1
  48. package/dist/registry.cjs +123 -6
  49. package/dist/registry.cjs.map +1 -1
  50. package/dist/registry.js +2 -2
  51. package/dist/{run-cohort-rgT_FX3V.d.ts → run-cohort-DAfhZm-p.d.ts} +2 -2
  52. package/dist/{run-cohort-DNpPa0mq.d.cts → run-cohort-DRJvUWO9.d.cts} +2 -2
  53. package/dist/{run-flow-pW0PllaZ.d.cts → run-flow-Bdrn4lQV.d.cts} +1 -1
  54. package/dist/{run-flow-BLP9rXfO.d.ts → run-flow-D2iOjWsj.d.ts} +1 -1
  55. package/dist/runtime/index.d.cts +5 -5
  56. package/dist/runtime/index.d.ts +5 -5
  57. package/dist/runtime.cjs +123 -6
  58. package/dist/runtime.cjs.map +1 -1
  59. package/dist/runtime.js +3 -3
  60. package/dist/schema/index.d.cts +1 -1
  61. package/dist/schema/index.d.ts +1 -1
  62. package/dist/schema.cjs +123 -6
  63. package/dist/schema.cjs.map +1 -1
  64. package/dist/schema.js +3 -3
  65. package/dist/screens.cjs +123 -6
  66. package/dist/screens.cjs.map +1 -1
  67. package/dist/screens.d.cts +2 -2
  68. package/dist/screens.d.ts +2 -2
  69. package/dist/screens.js +4 -4
  70. package/dist/terminal/fancy-term-host.d.cts +1 -1
  71. package/dist/terminal/fancy-term-host.d.ts +1 -1
  72. package/dist/{types-BdkXxjFh.d.cts → types-B1jftRa9.d.cts} +1 -1
  73. package/dist/{types-CbekKMMO.d.ts → types-D5SBBpbe.d.ts} +1 -1
  74. package/dist/{types-B-Syk9-M.d.ts → types-E48Dpesq.d.cts} +20 -0
  75. package/dist/{types-B-Syk9-M.d.cts → types-E48Dpesq.d.ts} +20 -0
  76. package/dist/ux.cjs +123 -6
  77. package/dist/ux.cjs.map +1 -1
  78. package/dist/ux.d.cts +2 -2
  79. package/dist/ux.d.ts +2 -2
  80. package/dist/ux.js +1 -1
  81. package/package.json +2 -2
  82. package/dist/chunk-EEIWAIXW.js.map +0 -1
package/dist/schema.js CHANGED
@@ -1,6 +1,6 @@
1
- export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './chunk-ARSOSEJV.js';
2
- import './chunk-G5RAGPVD.js';
3
- import './chunk-EEIWAIXW.js';
1
+ export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './chunk-LBE4L3DS.js';
2
+ import './chunk-KMONOPAK.js';
3
+ import './chunk-OZHBAXMA.js';
4
4
  import './chunk-2QXKDLGT.js';
5
5
  //# sourceMappingURL=schema.js.map
6
6
  //# sourceMappingURL=schema.js.map
package/dist/screens.cjs CHANGED
@@ -9921,6 +9921,9 @@ async function runFlow(graph, executors, onEvent = () => {
9921
9921
  executors,
9922
9922
  depth,
9923
9923
  run,
9924
+ // Structural executors derive a nested lane from the graph they are
9925
+ // in; `for_each`'s `item` port cannot be implemented without it.
9926
+ graph,
9924
9927
  terminal: terminalAccessorFor(node)
9925
9928
  })
9926
9929
  );
@@ -10355,6 +10358,7 @@ var branchExecutor = (ctx) => {
10355
10358
  const inputs = ctx.inputs;
10356
10359
  let taken;
10357
10360
  const raw = config.condition;
10361
+ validateRoutingExpression(ctx, raw, "branch", "condition");
10358
10362
  if (typeof raw === "string" && raw.trim() !== "") {
10359
10363
  taken = truthy(resolve(raw, inputs));
10360
10364
  warnIfUnresolved(ctx, raw, taken ? "true" : "false");
@@ -10409,14 +10413,102 @@ var mergeExecutor = (ctx) => {
10409
10413
  }
10410
10414
  return merged;
10411
10415
  };
10412
- var forEachExecutor = (ctx) => {
10413
- const source = resolve(configOf(ctx.node).source, ctx.inputs);
10416
+ var FOR_EACH_DEFAULT_MAX_ITEMS = 1e3;
10417
+ var FOR_EACH_HARD_MAX_ITEMS = 1e4;
10418
+ function reachable(adjacency, starts) {
10419
+ const seen = /* @__PURE__ */ new Set();
10420
+ const queue = [...starts];
10421
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
10422
+ const id2 = queue[cursor];
10423
+ if (seen.has(id2)) continue;
10424
+ seen.add(id2);
10425
+ for (const target of adjacency.get(id2) ?? []) queue.push(target);
10426
+ }
10427
+ return seen;
10428
+ }
10429
+ function forEachLane(graph, nodeId) {
10430
+ const handleOf = (edge) => edge.sourceHandle ?? "out";
10431
+ const itemEdges = graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "item");
10432
+ if (itemEdges.length === 0) return null;
10433
+ const adjacency = /* @__PURE__ */ new Map();
10434
+ for (const edge of graph.edges) {
10435
+ const list = adjacency.get(edge.source) ?? [];
10436
+ list.push(edge.target);
10437
+ adjacency.set(edge.source, list);
10438
+ }
10439
+ const done = reachable(
10440
+ adjacency,
10441
+ graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "done").map((e) => e.target)
10442
+ );
10443
+ const body = new Set(
10444
+ [...reachable(adjacency, itemEdges.map((e) => e.target))].filter(
10445
+ (id2) => !done.has(id2) && id2 !== nodeId
10446
+ )
10447
+ );
10448
+ return {
10449
+ graph: {
10450
+ nodes: graph.nodes.filter((n) => body.has(n.id)),
10451
+ edges: graph.edges.filter((e) => body.has(e.source) && body.has(e.target)),
10452
+ ...graph.inputs === void 0 ? {} : { inputs: graph.inputs }
10453
+ },
10454
+ entries: itemEdges.filter((e) => body.has(e.target))
10455
+ };
10456
+ }
10457
+ var forEachExecutor = async (ctx) => {
10458
+ const config = configOf(ctx.node);
10459
+ const source = resolve(config.source, ctx.inputs);
10414
10460
  let items;
10415
10461
  if (Array.isArray(source)) items = source;
10416
10462
  else if (source === null || source === void 0) items = [];
10417
10463
  else if (typeof source === "object") items = Object.values(source);
10418
10464
  else items = [source];
10419
- return { items, count: items.length };
10465
+ const lane = ctx.graph ? forEachLane(ctx.graph, ctx.node.id) : null;
10466
+ if (lane === null || config.mode === "collect") {
10467
+ return { items, count: items.length };
10468
+ }
10469
+ if (lane.graph.nodes.length === 0) {
10470
+ ctx.abort(`for_each "${ctx.node.id}" has an item edge but its derived lane is empty`);
10471
+ }
10472
+ const maxItems = Number(config.maxItems ?? FOR_EACH_DEFAULT_MAX_ITEMS);
10473
+ if (!Number.isFinite(maxItems) || maxItems < 1 || maxItems > FOR_EACH_HARD_MAX_ITEMS) {
10474
+ ctx.abort(`for_each "${ctx.node.id}" maxItems must be between 1 and ${FOR_EACH_HARD_MAX_ITEMS}`);
10475
+ }
10476
+ if (items.length > maxItems) {
10477
+ ctx.abort(
10478
+ `for_each "${ctx.node.id}" resolved ${items.length} items exceeds its maxItems cap of ${maxItems}`
10479
+ );
10480
+ }
10481
+ const results = [];
10482
+ const failures = [];
10483
+ for (const [index2, item] of items.entries()) {
10484
+ const initialInputs = {};
10485
+ for (const edge of lane.entries) {
10486
+ initialInputs[edge.target] = {
10487
+ ...initialInputs[edge.target] ?? {},
10488
+ [edge.targetHandle ?? "in"]: item
10489
+ };
10490
+ }
10491
+ const nested = await runFlow(lane.graph, { ...ctx.executors ?? {} }, () => {
10492
+ }, {
10493
+ initialInputs,
10494
+ depth: (ctx.depth ?? 0) + 1,
10495
+ // The index rides on the identity, so a node in iteration 3 cannot share
10496
+ // an idempotency key with the same node in iteration 4.
10497
+ run: ctx.run?.descend(ctx.node.id, index2)
10498
+ });
10499
+ if (!nested.ok) {
10500
+ const reason = nested.error ?? "unknown error";
10501
+ if (decodePause(reason)) ctx.abort(reason);
10502
+ results.push(null);
10503
+ failures.push({ index: index2, item, error: reason });
10504
+ continue;
10505
+ }
10506
+ results.push(nested.outputs);
10507
+ }
10508
+ return {
10509
+ __port: "done",
10510
+ value: { items, results, failures, count: items.length }
10511
+ };
10420
10512
  };
10421
10513
  var manualTriggerExecutor = (ctx) => ctx.inputs;
10422
10514
  var outputExecutor = (ctx) => {
@@ -10436,6 +10528,7 @@ var switchCaseExecutor = (ctx) => {
10436
10528
  const config = configOf(ctx.node);
10437
10529
  const inputs = ctx.inputs;
10438
10530
  const expression = config.value;
10531
+ validateRoutingExpression(ctx, expression, "switch_case", "value");
10439
10532
  const value = text(resolve(expression, inputs));
10440
10533
  const cases = config.cases ?? {};
10441
10534
  const matched = Object.prototype.hasOwnProperty.call(cases, value);
@@ -10443,6 +10536,21 @@ var switchCaseExecutor = (ctx) => {
10443
10536
  warnIfUnresolved(ctx, expression, port, "value");
10444
10537
  return { __port: port, value: inputs.in ?? inputs };
10445
10538
  };
10539
+ function validateRoutingExpression(ctx, value, kind, field) {
10540
+ if (typeof value !== "string" || value.trim() === "") return;
10541
+ const bare = value.trim();
10542
+ const subject = `${kind} "${ctx.node.id}" ${field} "${bare}"`;
10543
+ if (!value.includes("{{")) {
10544
+ ctx.abort(`${subject} is not an expression -- wrap it: {{ ${bare} }}`);
10545
+ }
10546
+ let open = 0;
10547
+ for (const [delimiter] of value.matchAll(/\{\{|\}\}/g)) {
10548
+ open = delimiter === "{{" ? open + 1 : Math.max(0, open - 1);
10549
+ }
10550
+ if (open > 0) {
10551
+ ctx.abort(`${subject} has an unclosed expression -- close every {{ with }}`);
10552
+ }
10553
+ }
10446
10554
 
10447
10555
  // src/registry/terminal-nodes.ts
10448
10556
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -10934,7 +11042,7 @@ var KINDS = [
10934
11042
  key: "condition",
10935
11043
  label: "Raw expression (advanced)",
10936
11044
  example: "{{ $json.active && $json.score > 10 }}",
10937
- description: "Escape hatch for logic the builder can't express. Overrides the conditions above when set."
11045
+ description: "Escape hatch for logic the builder can't express. Overrides the conditions above when set. String values must be {{ }}-wrapped expressions; bare strings and unclosed templates abort the run."
10938
11046
  }
10939
11047
  ]
10940
11048
  },
@@ -10954,7 +11062,7 @@ var KINDS = [
10954
11062
  // moves the ports on the canvas and the ports the runtime activates.
10955
11063
  outputs: (config) => casePorts(config?.cases),
10956
11064
  configSchema: [
10957
- { type: "expression", key: "value", label: "Switch on", example: "{{ $json.kind }}", required: true },
11065
+ { type: "expression", key: "value", label: "Switch on", description: "String values must be {{ }}-wrapped expressions; bare strings and unclosed templates abort the run.", example: "{{ $json.kind }}", required: true },
10958
11066
  {
10959
11067
  type: "keyvalue",
10960
11068
  key: "cases",
@@ -10973,6 +11081,15 @@ var KINDS = [
10973
11081
  name: "@particle-academy/for_each",
10974
11082
  outputShape: [
10975
11083
  { path: "items", type: "array" },
11084
+ // `results` and `failures` are the WIRED shape -- what a lane produces
11085
+ // when the `item` port is connected. Declared unconditionally because a
11086
+ // declaration describes the kind, not one graph's use of it, and because
11087
+ // the shared table requires them: fancy-conformance 0.30.0 added
11088
+ // `results` and 0.31.0 added `failures`, both BREAKING, both so that a
11089
+ // runtime which has not implemented iteration fails here rather than
11090
+ // reporting surface parity it does not have.
11091
+ { path: "results", type: "array" },
11092
+ { path: "failures", type: "array" },
10976
11093
  { path: "count", type: "number" }
10977
11094
  ],
10978
11095
  aliases: ["for_each", "@fancy/for_each"],
@@ -10981,7 +11098,7 @@ var KINDS = [
10981
11098
  executor: forEachExecutor,
10982
11099
  category: "logic",
10983
11100
  label: "For Each",
10984
- description: "Publishes the resolved list and its size on BOTH `item` and `done`. Fan-out as DATA, not as jobs -- nothing runs per item.",
11101
+ description: "Wire `item` to a lane and it runs once per item, aggregating `results` and `failures` on `done`. Leave `item` unwired (or set `mode: collect`) and it just publishes the list and its size -- one node, one checkpoint.",
10985
11102
  icon: "\u21BB",
10986
11103
  inputs: [{ id: "in" }],
10987
11104
  outputs: [{ id: "item", label: "item" }, { id: "done", label: "done" }],