@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/runtime.js CHANGED
@@ -1,7 +1,7 @@
1
- export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-2VI24UPB.js';
2
- export { runCohort } from './chunk-CQAMEPPI.js';
1
+ export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-QMJVUT7C.js';
2
+ export { runCohort } from './chunk-R4G7SPLD.js';
3
3
  import './chunk-OWENS2H5.js';
4
- export { RunIdentity, escapeSegment, runFlow } from './chunk-EEIWAIXW.js';
4
+ export { RunIdentity, escapeSegment, runFlow } from './chunk-OZHBAXMA.js';
5
5
  import './chunk-2QXKDLGT.js';
6
6
  //# sourceMappingURL=runtime.js.map
7
7
  //# sourceMappingURL=runtime.js.map
@@ -1,4 +1,4 @@
1
- import { G as WorkflowInput, P as PortDescriptor, F as FlowGraph } from '../types-B-Syk9-M.cjs';
1
+ import { G as WorkflowInput, P as PortDescriptor, F as FlowGraph } from '../types-E48Dpesq.cjs';
2
2
  import '@xyflow/react';
3
3
 
4
4
  /** Schema version. Bump on breaking shape changes; add migrations as needed. */
@@ -1,4 +1,4 @@
1
- import { G as WorkflowInput, P as PortDescriptor, F as FlowGraph } from '../types-B-Syk9-M.js';
1
+ import { G as WorkflowInput, P as PortDescriptor, F as FlowGraph } from '../types-E48Dpesq.js';
2
2
  import '@xyflow/react';
3
3
 
4
4
  /** Schema version. Bump on breaking shape changes; add migrations as needed. */
package/dist/schema.cjs CHANGED
@@ -724,6 +724,9 @@ async function runFlow(graph, executors, onEvent = () => {
724
724
  executors,
725
725
  depth,
726
726
  run,
727
+ // Structural executors derive a nested lane from the graph they are
728
+ // in; `for_each`'s `item` port cannot be implemented without it.
729
+ graph,
727
730
  terminal: terminalAccessorFor(node)
728
731
  })
729
732
  );
@@ -1158,6 +1161,7 @@ var branchExecutor = (ctx) => {
1158
1161
  const inputs = ctx.inputs;
1159
1162
  let taken;
1160
1163
  const raw = config.condition;
1164
+ validateRoutingExpression(ctx, raw, "branch", "condition");
1161
1165
  if (typeof raw === "string" && raw.trim() !== "") {
1162
1166
  taken = truthy(resolve(raw, inputs));
1163
1167
  warnIfUnresolved(ctx, raw, taken ? "true" : "false");
@@ -1212,14 +1216,102 @@ var mergeExecutor = (ctx) => {
1212
1216
  }
1213
1217
  return merged;
1214
1218
  };
1215
- var forEachExecutor = (ctx) => {
1216
- const source = resolve(configOf(ctx.node).source, ctx.inputs);
1219
+ var FOR_EACH_DEFAULT_MAX_ITEMS = 1e3;
1220
+ var FOR_EACH_HARD_MAX_ITEMS = 1e4;
1221
+ function reachable(adjacency, starts) {
1222
+ const seen = /* @__PURE__ */ new Set();
1223
+ const queue = [...starts];
1224
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
1225
+ const id = queue[cursor];
1226
+ if (seen.has(id)) continue;
1227
+ seen.add(id);
1228
+ for (const target of adjacency.get(id) ?? []) queue.push(target);
1229
+ }
1230
+ return seen;
1231
+ }
1232
+ function forEachLane(graph, nodeId) {
1233
+ const handleOf = (edge) => edge.sourceHandle ?? "out";
1234
+ const itemEdges = graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "item");
1235
+ if (itemEdges.length === 0) return null;
1236
+ const adjacency = /* @__PURE__ */ new Map();
1237
+ for (const edge of graph.edges) {
1238
+ const list = adjacency.get(edge.source) ?? [];
1239
+ list.push(edge.target);
1240
+ adjacency.set(edge.source, list);
1241
+ }
1242
+ const done = reachable(
1243
+ adjacency,
1244
+ graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "done").map((e) => e.target)
1245
+ );
1246
+ const body = new Set(
1247
+ [...reachable(adjacency, itemEdges.map((e) => e.target))].filter(
1248
+ (id) => !done.has(id) && id !== nodeId
1249
+ )
1250
+ );
1251
+ return {
1252
+ graph: {
1253
+ nodes: graph.nodes.filter((n) => body.has(n.id)),
1254
+ edges: graph.edges.filter((e) => body.has(e.source) && body.has(e.target)),
1255
+ ...graph.inputs === void 0 ? {} : { inputs: graph.inputs }
1256
+ },
1257
+ entries: itemEdges.filter((e) => body.has(e.target))
1258
+ };
1259
+ }
1260
+ var forEachExecutor = async (ctx) => {
1261
+ const config = configOf(ctx.node);
1262
+ const source = resolve(config.source, ctx.inputs);
1217
1263
  let items;
1218
1264
  if (Array.isArray(source)) items = source;
1219
1265
  else if (source === null || source === void 0) items = [];
1220
1266
  else if (typeof source === "object") items = Object.values(source);
1221
1267
  else items = [source];
1222
- return { items, count: items.length };
1268
+ const lane = ctx.graph ? forEachLane(ctx.graph, ctx.node.id) : null;
1269
+ if (lane === null || config.mode === "collect") {
1270
+ return { items, count: items.length };
1271
+ }
1272
+ if (lane.graph.nodes.length === 0) {
1273
+ ctx.abort(`for_each "${ctx.node.id}" has an item edge but its derived lane is empty`);
1274
+ }
1275
+ const maxItems = Number(config.maxItems ?? FOR_EACH_DEFAULT_MAX_ITEMS);
1276
+ if (!Number.isFinite(maxItems) || maxItems < 1 || maxItems > FOR_EACH_HARD_MAX_ITEMS) {
1277
+ ctx.abort(`for_each "${ctx.node.id}" maxItems must be between 1 and ${FOR_EACH_HARD_MAX_ITEMS}`);
1278
+ }
1279
+ if (items.length > maxItems) {
1280
+ ctx.abort(
1281
+ `for_each "${ctx.node.id}" resolved ${items.length} items exceeds its maxItems cap of ${maxItems}`
1282
+ );
1283
+ }
1284
+ const results = [];
1285
+ const failures = [];
1286
+ for (const [index, item] of items.entries()) {
1287
+ const initialInputs = {};
1288
+ for (const edge of lane.entries) {
1289
+ initialInputs[edge.target] = {
1290
+ ...initialInputs[edge.target] ?? {},
1291
+ [edge.targetHandle ?? "in"]: item
1292
+ };
1293
+ }
1294
+ const nested = await runFlow(lane.graph, { ...ctx.executors ?? {} }, () => {
1295
+ }, {
1296
+ initialInputs,
1297
+ depth: (ctx.depth ?? 0) + 1,
1298
+ // The index rides on the identity, so a node in iteration 3 cannot share
1299
+ // an idempotency key with the same node in iteration 4.
1300
+ run: ctx.run?.descend(ctx.node.id, index)
1301
+ });
1302
+ if (!nested.ok) {
1303
+ const reason = nested.error ?? "unknown error";
1304
+ if (decodePause(reason)) ctx.abort(reason);
1305
+ results.push(null);
1306
+ failures.push({ index, item, error: reason });
1307
+ continue;
1308
+ }
1309
+ results.push(nested.outputs);
1310
+ }
1311
+ return {
1312
+ __port: "done",
1313
+ value: { items, results, failures, count: items.length }
1314
+ };
1223
1315
  };
1224
1316
  var manualTriggerExecutor = (ctx) => ctx.inputs;
1225
1317
  var outputExecutor = (ctx) => {
@@ -1239,6 +1331,7 @@ var switchCaseExecutor = (ctx) => {
1239
1331
  const config = configOf(ctx.node);
1240
1332
  const inputs = ctx.inputs;
1241
1333
  const expression = config.value;
1334
+ validateRoutingExpression(ctx, expression, "switch_case", "value");
1242
1335
  const value = text(resolve(expression, inputs));
1243
1336
  const cases = config.cases ?? {};
1244
1337
  const matched = Object.prototype.hasOwnProperty.call(cases, value);
@@ -1246,6 +1339,21 @@ var switchCaseExecutor = (ctx) => {
1246
1339
  warnIfUnresolved(ctx, expression, port, "value");
1247
1340
  return { __port: port, value: inputs.in ?? inputs };
1248
1341
  };
1342
+ function validateRoutingExpression(ctx, value, kind, field) {
1343
+ if (typeof value !== "string" || value.trim() === "") return;
1344
+ const bare = value.trim();
1345
+ const subject = `${kind} "${ctx.node.id}" ${field} "${bare}"`;
1346
+ if (!value.includes("{{")) {
1347
+ ctx.abort(`${subject} is not an expression -- wrap it: {{ ${bare} }}`);
1348
+ }
1349
+ let open = 0;
1350
+ for (const [delimiter] of value.matchAll(/\{\{|\}\}/g)) {
1351
+ open = delimiter === "{{" ? open + 1 : Math.max(0, open - 1);
1352
+ }
1353
+ if (open > 0) {
1354
+ ctx.abort(`${subject} has an unclosed expression -- close every {{ with }}`);
1355
+ }
1356
+ }
1249
1357
 
1250
1358
  // src/registry/terminal-nodes.ts
1251
1359
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -1737,7 +1845,7 @@ var KINDS = [
1737
1845
  key: "condition",
1738
1846
  label: "Raw expression (advanced)",
1739
1847
  example: "{{ $json.active && $json.score > 10 }}",
1740
- description: "Escape hatch for logic the builder can't express. Overrides the conditions above when set."
1848
+ 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."
1741
1849
  }
1742
1850
  ]
1743
1851
  },
@@ -1757,7 +1865,7 @@ var KINDS = [
1757
1865
  // moves the ports on the canvas and the ports the runtime activates.
1758
1866
  outputs: (config) => casePorts(config?.cases),
1759
1867
  configSchema: [
1760
- { type: "expression", key: "value", label: "Switch on", example: "{{ $json.kind }}", required: true },
1868
+ { 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 },
1761
1869
  {
1762
1870
  type: "keyvalue",
1763
1871
  key: "cases",
@@ -1776,6 +1884,15 @@ var KINDS = [
1776
1884
  name: "@particle-academy/for_each",
1777
1885
  outputShape: [
1778
1886
  { path: "items", type: "array" },
1887
+ // `results` and `failures` are the WIRED shape -- what a lane produces
1888
+ // when the `item` port is connected. Declared unconditionally because a
1889
+ // declaration describes the kind, not one graph's use of it, and because
1890
+ // the shared table requires them: fancy-conformance 0.30.0 added
1891
+ // `results` and 0.31.0 added `failures`, both BREAKING, both so that a
1892
+ // runtime which has not implemented iteration fails here rather than
1893
+ // reporting surface parity it does not have.
1894
+ { path: "results", type: "array" },
1895
+ { path: "failures", type: "array" },
1779
1896
  { path: "count", type: "number" }
1780
1897
  ],
1781
1898
  aliases: ["for_each", "@fancy/for_each"],
@@ -1784,7 +1901,7 @@ var KINDS = [
1784
1901
  executor: forEachExecutor,
1785
1902
  category: "logic",
1786
1903
  label: "For Each",
1787
- description: "Publishes the resolved list and its size on BOTH `item` and `done`. Fan-out as DATA, not as jobs -- nothing runs per item.",
1904
+ 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.",
1788
1905
  icon: "\u21BB",
1789
1906
  inputs: [{ id: "in" }],
1790
1907
  outputs: [{ id: "item", label: "item" }, { id: "done", label: "done" }],