@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
@@ -745,6 +745,9 @@ async function runFlow(graph, executors, onEvent = () => {
745
745
  executors,
746
746
  depth,
747
747
  run,
748
+ // Structural executors derive a nested lane from the graph they are
749
+ // in; `for_each`'s `item` port cannot be implemented without it.
750
+ graph,
748
751
  terminal: terminalAccessorFor(node)
749
752
  })
750
753
  );
@@ -1199,6 +1202,7 @@ var branchExecutor = (ctx) => {
1199
1202
  const inputs = ctx.inputs;
1200
1203
  let taken;
1201
1204
  const raw = config.condition;
1205
+ validateRoutingExpression(ctx, raw, "branch", "condition");
1202
1206
  if (typeof raw === "string" && raw.trim() !== "") {
1203
1207
  taken = truthy(resolve(raw, inputs));
1204
1208
  warnIfUnresolved(ctx, raw, taken ? "true" : "false");
@@ -1253,14 +1257,102 @@ var mergeExecutor = (ctx) => {
1253
1257
  }
1254
1258
  return merged;
1255
1259
  };
1256
- var forEachExecutor = (ctx) => {
1257
- const source = resolve(configOf(ctx.node).source, ctx.inputs);
1260
+ var FOR_EACH_DEFAULT_MAX_ITEMS = 1e3;
1261
+ var FOR_EACH_HARD_MAX_ITEMS = 1e4;
1262
+ function reachable(adjacency, starts) {
1263
+ const seen = /* @__PURE__ */ new Set();
1264
+ const queue = [...starts];
1265
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
1266
+ const id = queue[cursor];
1267
+ if (seen.has(id)) continue;
1268
+ seen.add(id);
1269
+ for (const target of adjacency.get(id) ?? []) queue.push(target);
1270
+ }
1271
+ return seen;
1272
+ }
1273
+ function forEachLane(graph, nodeId) {
1274
+ const handleOf = (edge) => edge.sourceHandle ?? "out";
1275
+ const itemEdges = graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "item");
1276
+ if (itemEdges.length === 0) return null;
1277
+ const adjacency = /* @__PURE__ */ new Map();
1278
+ for (const edge of graph.edges) {
1279
+ const list = adjacency.get(edge.source) ?? [];
1280
+ list.push(edge.target);
1281
+ adjacency.set(edge.source, list);
1282
+ }
1283
+ const done = reachable(
1284
+ adjacency,
1285
+ graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "done").map((e) => e.target)
1286
+ );
1287
+ const body = new Set(
1288
+ [...reachable(adjacency, itemEdges.map((e) => e.target))].filter(
1289
+ (id) => !done.has(id) && id !== nodeId
1290
+ )
1291
+ );
1292
+ return {
1293
+ graph: {
1294
+ nodes: graph.nodes.filter((n) => body.has(n.id)),
1295
+ edges: graph.edges.filter((e) => body.has(e.source) && body.has(e.target)),
1296
+ ...graph.inputs === void 0 ? {} : { inputs: graph.inputs }
1297
+ },
1298
+ entries: itemEdges.filter((e) => body.has(e.target))
1299
+ };
1300
+ }
1301
+ var forEachExecutor = async (ctx) => {
1302
+ const config = configOf(ctx.node);
1303
+ const source = resolve(config.source, ctx.inputs);
1258
1304
  let items;
1259
1305
  if (Array.isArray(source)) items = source;
1260
1306
  else if (source === null || source === void 0) items = [];
1261
1307
  else if (typeof source === "object") items = Object.values(source);
1262
1308
  else items = [source];
1263
- return { items, count: items.length };
1309
+ const lane = ctx.graph ? forEachLane(ctx.graph, ctx.node.id) : null;
1310
+ if (lane === null || config.mode === "collect") {
1311
+ return { items, count: items.length };
1312
+ }
1313
+ if (lane.graph.nodes.length === 0) {
1314
+ ctx.abort(`for_each "${ctx.node.id}" has an item edge but its derived lane is empty`);
1315
+ }
1316
+ const maxItems = Number(config.maxItems ?? FOR_EACH_DEFAULT_MAX_ITEMS);
1317
+ if (!Number.isFinite(maxItems) || maxItems < 1 || maxItems > FOR_EACH_HARD_MAX_ITEMS) {
1318
+ ctx.abort(`for_each "${ctx.node.id}" maxItems must be between 1 and ${FOR_EACH_HARD_MAX_ITEMS}`);
1319
+ }
1320
+ if (items.length > maxItems) {
1321
+ ctx.abort(
1322
+ `for_each "${ctx.node.id}" resolved ${items.length} items exceeds its maxItems cap of ${maxItems}`
1323
+ );
1324
+ }
1325
+ const results = [];
1326
+ const failures = [];
1327
+ for (const [index, item] of items.entries()) {
1328
+ const initialInputs = {};
1329
+ for (const edge of lane.entries) {
1330
+ initialInputs[edge.target] = {
1331
+ ...initialInputs[edge.target] ?? {},
1332
+ [edge.targetHandle ?? "in"]: item
1333
+ };
1334
+ }
1335
+ const nested = await runFlow(lane.graph, { ...ctx.executors ?? {} }, () => {
1336
+ }, {
1337
+ initialInputs,
1338
+ depth: (ctx.depth ?? 0) + 1,
1339
+ // The index rides on the identity, so a node in iteration 3 cannot share
1340
+ // an idempotency key with the same node in iteration 4.
1341
+ run: ctx.run?.descend(ctx.node.id, index)
1342
+ });
1343
+ if (!nested.ok) {
1344
+ const reason = nested.error ?? "unknown error";
1345
+ if (decodePause(reason)) ctx.abort(reason);
1346
+ results.push(null);
1347
+ failures.push({ index, item, error: reason });
1348
+ continue;
1349
+ }
1350
+ results.push(nested.outputs);
1351
+ }
1352
+ return {
1353
+ __port: "done",
1354
+ value: { items, results, failures, count: items.length }
1355
+ };
1264
1356
  };
1265
1357
  var manualTriggerExecutor = (ctx) => ctx.inputs;
1266
1358
  var outputExecutor = (ctx) => {
@@ -1280,6 +1372,7 @@ var switchCaseExecutor = (ctx) => {
1280
1372
  const config = configOf(ctx.node);
1281
1373
  const inputs = ctx.inputs;
1282
1374
  const expression = config.value;
1375
+ validateRoutingExpression(ctx, expression, "switch_case", "value");
1283
1376
  const value = text(resolve(expression, inputs));
1284
1377
  const cases = config.cases ?? {};
1285
1378
  const matched = Object.prototype.hasOwnProperty.call(cases, value);
@@ -1287,6 +1380,21 @@ var switchCaseExecutor = (ctx) => {
1287
1380
  warnIfUnresolved(ctx, expression, port, "value");
1288
1381
  return { __port: port, value: inputs.in ?? inputs };
1289
1382
  };
1383
+ function validateRoutingExpression(ctx, value, kind, field) {
1384
+ if (typeof value !== "string" || value.trim() === "") return;
1385
+ const bare = value.trim();
1386
+ const subject = `${kind} "${ctx.node.id}" ${field} "${bare}"`;
1387
+ if (!value.includes("{{")) {
1388
+ ctx.abort(`${subject} is not an expression -- wrap it: {{ ${bare} }}`);
1389
+ }
1390
+ let open = 0;
1391
+ for (const [delimiter] of value.matchAll(/\{\{|\}\}/g)) {
1392
+ open = delimiter === "{{" ? open + 1 : Math.max(0, open - 1);
1393
+ }
1394
+ if (open > 0) {
1395
+ ctx.abort(`${subject} has an unclosed expression -- close every {{ with }}`);
1396
+ }
1397
+ }
1290
1398
 
1291
1399
  // src/registry/terminal-nodes.ts
1292
1400
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -1778,7 +1886,7 @@ var KINDS = [
1778
1886
  key: "condition",
1779
1887
  label: "Raw expression (advanced)",
1780
1888
  example: "{{ $json.active && $json.score > 10 }}",
1781
- description: "Escape hatch for logic the builder can't express. Overrides the conditions above when set."
1889
+ 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."
1782
1890
  }
1783
1891
  ]
1784
1892
  },
@@ -1798,7 +1906,7 @@ var KINDS = [
1798
1906
  // moves the ports on the canvas and the ports the runtime activates.
1799
1907
  outputs: (config) => casePorts(config?.cases),
1800
1908
  configSchema: [
1801
- { type: "expression", key: "value", label: "Switch on", example: "{{ $json.kind }}", required: true },
1909
+ { 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 },
1802
1910
  {
1803
1911
  type: "keyvalue",
1804
1912
  key: "cases",
@@ -1817,6 +1925,15 @@ var KINDS = [
1817
1925
  name: "@particle-academy/for_each",
1818
1926
  outputShape: [
1819
1927
  { path: "items", type: "array" },
1928
+ // `results` and `failures` are the WIRED shape -- what a lane produces
1929
+ // when the `item` port is connected. Declared unconditionally because a
1930
+ // declaration describes the kind, not one graph's use of it, and because
1931
+ // the shared table requires them: fancy-conformance 0.30.0 added
1932
+ // `results` and 0.31.0 added `failures`, both BREAKING, both so that a
1933
+ // runtime which has not implemented iteration fails here rather than
1934
+ // reporting surface parity it does not have.
1935
+ { path: "results", type: "array" },
1936
+ { path: "failures", type: "array" },
1820
1937
  { path: "count", type: "number" }
1821
1938
  ],
1822
1939
  aliases: ["for_each", "@fancy/for_each"],
@@ -1825,7 +1942,7 @@ var KINDS = [
1825
1942
  executor: forEachExecutor,
1826
1943
  category: "logic",
1827
1944
  label: "For Each",
1828
- description: "Publishes the resolved list and its size on BOTH `item` and `done`. Fan-out as DATA, not as jobs -- nothing runs per item.",
1945
+ 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.",
1829
1946
  icon: "\u21BB",
1830
1947
  inputs: [{ id: "in" }],
1831
1948
  outputs: [{ id: "item", label: "item" }, { id: "done", label: "done" }],
@@ -2894,5 +3011,5 @@ function describeExpressionGrammar() {
2894
3011
  }
2895
3012
 
2896
3013
  export { BUILTIN_KIND_DATA, DEFAULT_MAX_DEPTH, LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, RunIdentity, UnresolvedPathError, applyKindSchemaOverlay, availableVariables, baseVariables, categoryAccent, clearKindSchemaOverlays, clearNodeKindOverrides, declaredRoutes, decodePause, defaultConfigFor, describeExpressionGrammar, encodePause, ensureBuiltinKinds, escapeSegment, evaluateConfig, evaluateExpression, getNodeKind, humanInputFields, isPause, kindIds, listNodeKinds, llmRouterExecutor, nodeConfig, onNodeKindsChanged, outputFieldsFor, overrideNodeKind, pauseForHuman, registerBuiltinKindInternal, registerNodeKind, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePath, resolvePortSpec, runFlow, subflowExecutor, subflowMode, subflowPorts, text, truthy, tryResolvePath, undeliveredEdgeWarnings, unregisterNodeKind, validateConfig };
2897
- //# sourceMappingURL=chunk-EEIWAIXW.js.map
2898
- //# sourceMappingURL=chunk-EEIWAIXW.js.map
3014
+ //# sourceMappingURL=chunk-OZHBAXMA.js.map
3015
+ //# sourceMappingURL=chunk-OZHBAXMA.js.map