@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
@@ -1,6 +1,6 @@
1
- export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-Ak8kbY0e.cjs';
1
+ export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-BqYQl2vS.cjs';
2
2
  import 'react';
3
- import './types-B-Syk9-M.cjs';
3
+ import './types-E48Dpesq.cjs';
4
4
  import '@xyflow/react';
5
5
 
6
6
  /**
package/dist/screens.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-BAV29Zpi.js';
1
+ export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-_Fq8AX9V.js';
2
2
  import 'react';
3
- import './types-B-Syk9-M.js';
3
+ import './types-E48Dpesq.js';
4
4
  import '@xyflow/react';
5
5
 
6
6
  /**
package/dist/screens.js CHANGED
@@ -1,9 +1,9 @@
1
- import { FlowViewer } from './chunk-EGXA4SYJ.js';
2
- export { FlowViewer } from './chunk-EGXA4SYJ.js';
3
- import './chunk-T6WIIGSK.js';
1
+ import { FlowViewer } from './chunk-J66LOLJR.js';
2
+ export { FlowViewer } from './chunk-J66LOLJR.js';
3
+ import './chunk-I5EPWHNM.js';
4
4
  import './chunk-F5RPRB7A.js';
5
5
  import './chunk-OWENS2H5.js';
6
- import './chunk-EEIWAIXW.js';
6
+ import './chunk-OZHBAXMA.js';
7
7
  import './chunk-2QXKDLGT.js';
8
8
  import { registerSchemaComponents } from '@particle-academy/fancy-screens';
9
9
 
@@ -1,4 +1,4 @@
1
- import { T as TerminalHost } from '../types-B-Syk9-M.cjs';
1
+ import { T as TerminalHost } from '../types-E48Dpesq.cjs';
2
2
  import '@xyflow/react';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { T as TerminalHost } from '../types-B-Syk9-M.js';
1
+ import { T as TerminalHost } from '../types-E48Dpesq.js';
2
2
  import '@xyflow/react';
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import { ReactNode, ComponentType } from 'react';
2
- import { F as FlowGraph, a as FlowNode, P as PortDescriptor, N as NodeExecutor } from './types-B-Syk9-M.cjs';
2
+ import { F as FlowGraph, a as FlowNode, P as PortDescriptor, N as NodeExecutor } from './types-E48Dpesq.cjs';
3
3
  import { NodeProps } from '@xyflow/react';
4
4
  import { P as PauseAwaiting } from './pause-9iT4tCEV.cjs';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { ReactNode, ComponentType } from 'react';
2
- import { F as FlowGraph, a as FlowNode, P as PortDescriptor, N as NodeExecutor } from './types-B-Syk9-M.js';
2
+ import { F as FlowGraph, a as FlowNode, P as PortDescriptor, N as NodeExecutor } from './types-E48Dpesq.js';
3
3
  import { NodeProps } from '@xyflow/react';
4
4
  import { P as PauseAwaiting } from './pause-9iT4tCEV.js';
5
5
 
@@ -538,6 +538,26 @@ type NodeExecutor<TIn = Record<string, unknown>, TOut = unknown> = (ctx: {
538
538
  * invent a key. See `RunIdentity`.
539
539
  */
540
540
  run?: RunIdentity;
541
+ /**
542
+ * The graph this node belongs to.
543
+ *
544
+ * A STRUCTURAL executor needs it: `for_each`'s `item` port fans out over a
545
+ * lane derived from the graph — the nodes reachable from `item`, stopping
546
+ * at anything also reachable from `done` — and that lane cannot be computed
547
+ * from `node` and `inputs` alone.
548
+ *
549
+ * Without it the `item` port was accepted by the schema, drawn by the
550
+ * editor, and silently ignored at run time on this runtime: downstream
551
+ * nodes ran ONCE against the whole collection instead of once per item,
552
+ * with no error. The PHP twin derives the same lane from `$ctx->graph` and
553
+ * has since 0.6; parity was measured against a reference graph where the
554
+ * two disagreed.
555
+ *
556
+ * Optional, because a host may construct a context by hand. An executor
557
+ * that needs it must degrade rather than throw — `for_each` falls back to
558
+ * publishing the collection, which is what it did everywhere before.
559
+ */
560
+ graph?: FlowGraph;
541
561
  /**
542
562
  * The terminal this node's lane owns, if it is inside a terminal lane.
543
563
  *
@@ -538,6 +538,26 @@ type NodeExecutor<TIn = Record<string, unknown>, TOut = unknown> = (ctx: {
538
538
  * invent a key. See `RunIdentity`.
539
539
  */
540
540
  run?: RunIdentity;
541
+ /**
542
+ * The graph this node belongs to.
543
+ *
544
+ * A STRUCTURAL executor needs it: `for_each`'s `item` port fans out over a
545
+ * lane derived from the graph — the nodes reachable from `item`, stopping
546
+ * at anything also reachable from `done` — and that lane cannot be computed
547
+ * from `node` and `inputs` alone.
548
+ *
549
+ * Without it the `item` port was accepted by the schema, drawn by the
550
+ * editor, and silently ignored at run time on this runtime: downstream
551
+ * nodes ran ONCE against the whole collection instead of once per item,
552
+ * with no error. The PHP twin derives the same lane from `$ctx->graph` and
553
+ * has since 0.6; parity was measured against a reference graph where the
554
+ * two disagreed.
555
+ *
556
+ * Optional, because a host may construct a context by hand. An executor
557
+ * that needs it must degrade rather than throw — `for_each` falls back to
558
+ * publishing the collection, which is what it did everywhere before.
559
+ */
560
+ graph?: FlowGraph;
541
561
  /**
542
562
  * The terminal this node's lane owns, if it is inside a terminal lane.
543
563
  *
package/dist/ux.cjs CHANGED
@@ -729,6 +729,9 @@ async function runFlow(graph, executors, onEvent = () => {
729
729
  executors,
730
730
  depth,
731
731
  run,
732
+ // Structural executors derive a nested lane from the graph they are
733
+ // in; `for_each`'s `item` port cannot be implemented without it.
734
+ graph,
732
735
  terminal: terminalAccessorFor(node)
733
736
  })
734
737
  );
@@ -1163,6 +1166,7 @@ var branchExecutor = (ctx) => {
1163
1166
  const inputs = ctx.inputs;
1164
1167
  let taken;
1165
1168
  const raw = config.condition;
1169
+ validateRoutingExpression(ctx, raw, "branch", "condition");
1166
1170
  if (typeof raw === "string" && raw.trim() !== "") {
1167
1171
  taken = truthy(resolve(raw, inputs));
1168
1172
  warnIfUnresolved(ctx, raw, taken ? "true" : "false");
@@ -1217,14 +1221,102 @@ var mergeExecutor = (ctx) => {
1217
1221
  }
1218
1222
  return merged;
1219
1223
  };
1220
- var forEachExecutor = (ctx) => {
1221
- const source = resolve(configOf(ctx.node).source, ctx.inputs);
1224
+ var FOR_EACH_DEFAULT_MAX_ITEMS = 1e3;
1225
+ var FOR_EACH_HARD_MAX_ITEMS = 1e4;
1226
+ function reachable(adjacency, starts) {
1227
+ const seen = /* @__PURE__ */ new Set();
1228
+ const queue = [...starts];
1229
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
1230
+ const id = queue[cursor];
1231
+ if (seen.has(id)) continue;
1232
+ seen.add(id);
1233
+ for (const target of adjacency.get(id) ?? []) queue.push(target);
1234
+ }
1235
+ return seen;
1236
+ }
1237
+ function forEachLane(graph, nodeId) {
1238
+ const handleOf = (edge) => edge.sourceHandle ?? "out";
1239
+ const itemEdges = graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "item");
1240
+ if (itemEdges.length === 0) return null;
1241
+ const adjacency = /* @__PURE__ */ new Map();
1242
+ for (const edge of graph.edges) {
1243
+ const list = adjacency.get(edge.source) ?? [];
1244
+ list.push(edge.target);
1245
+ adjacency.set(edge.source, list);
1246
+ }
1247
+ const done = reachable(
1248
+ adjacency,
1249
+ graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "done").map((e) => e.target)
1250
+ );
1251
+ const body = new Set(
1252
+ [...reachable(adjacency, itemEdges.map((e) => e.target))].filter(
1253
+ (id) => !done.has(id) && id !== nodeId
1254
+ )
1255
+ );
1256
+ return {
1257
+ graph: {
1258
+ nodes: graph.nodes.filter((n) => body.has(n.id)),
1259
+ edges: graph.edges.filter((e) => body.has(e.source) && body.has(e.target)),
1260
+ ...graph.inputs === void 0 ? {} : { inputs: graph.inputs }
1261
+ },
1262
+ entries: itemEdges.filter((e) => body.has(e.target))
1263
+ };
1264
+ }
1265
+ var forEachExecutor = async (ctx) => {
1266
+ const config = configOf(ctx.node);
1267
+ const source = resolve(config.source, ctx.inputs);
1222
1268
  let items;
1223
1269
  if (Array.isArray(source)) items = source;
1224
1270
  else if (source === null || source === void 0) items = [];
1225
1271
  else if (typeof source === "object") items = Object.values(source);
1226
1272
  else items = [source];
1227
- return { items, count: items.length };
1273
+ const lane = ctx.graph ? forEachLane(ctx.graph, ctx.node.id) : null;
1274
+ if (lane === null || config.mode === "collect") {
1275
+ return { items, count: items.length };
1276
+ }
1277
+ if (lane.graph.nodes.length === 0) {
1278
+ ctx.abort(`for_each "${ctx.node.id}" has an item edge but its derived lane is empty`);
1279
+ }
1280
+ const maxItems = Number(config.maxItems ?? FOR_EACH_DEFAULT_MAX_ITEMS);
1281
+ if (!Number.isFinite(maxItems) || maxItems < 1 || maxItems > FOR_EACH_HARD_MAX_ITEMS) {
1282
+ ctx.abort(`for_each "${ctx.node.id}" maxItems must be between 1 and ${FOR_EACH_HARD_MAX_ITEMS}`);
1283
+ }
1284
+ if (items.length > maxItems) {
1285
+ ctx.abort(
1286
+ `for_each "${ctx.node.id}" resolved ${items.length} items exceeds its maxItems cap of ${maxItems}`
1287
+ );
1288
+ }
1289
+ const results = [];
1290
+ const failures = [];
1291
+ for (const [index, item] of items.entries()) {
1292
+ const initialInputs = {};
1293
+ for (const edge of lane.entries) {
1294
+ initialInputs[edge.target] = {
1295
+ ...initialInputs[edge.target] ?? {},
1296
+ [edge.targetHandle ?? "in"]: item
1297
+ };
1298
+ }
1299
+ const nested = await runFlow(lane.graph, { ...ctx.executors ?? {} }, () => {
1300
+ }, {
1301
+ initialInputs,
1302
+ depth: (ctx.depth ?? 0) + 1,
1303
+ // The index rides on the identity, so a node in iteration 3 cannot share
1304
+ // an idempotency key with the same node in iteration 4.
1305
+ run: ctx.run?.descend(ctx.node.id, index)
1306
+ });
1307
+ if (!nested.ok) {
1308
+ const reason = nested.error ?? "unknown error";
1309
+ if (decodePause(reason)) ctx.abort(reason);
1310
+ results.push(null);
1311
+ failures.push({ index, item, error: reason });
1312
+ continue;
1313
+ }
1314
+ results.push(nested.outputs);
1315
+ }
1316
+ return {
1317
+ __port: "done",
1318
+ value: { items, results, failures, count: items.length }
1319
+ };
1228
1320
  };
1229
1321
  var manualTriggerExecutor = (ctx) => ctx.inputs;
1230
1322
  var outputExecutor = (ctx) => {
@@ -1244,6 +1336,7 @@ var switchCaseExecutor = (ctx) => {
1244
1336
  const config = configOf(ctx.node);
1245
1337
  const inputs = ctx.inputs;
1246
1338
  const expression = config.value;
1339
+ validateRoutingExpression(ctx, expression, "switch_case", "value");
1247
1340
  const value = text(resolve(expression, inputs));
1248
1341
  const cases = config.cases ?? {};
1249
1342
  const matched = Object.prototype.hasOwnProperty.call(cases, value);
@@ -1251,6 +1344,21 @@ var switchCaseExecutor = (ctx) => {
1251
1344
  warnIfUnresolved(ctx, expression, port, "value");
1252
1345
  return { __port: port, value: inputs.in ?? inputs };
1253
1346
  };
1347
+ function validateRoutingExpression(ctx, value, kind, field) {
1348
+ if (typeof value !== "string" || value.trim() === "") return;
1349
+ const bare = value.trim();
1350
+ const subject = `${kind} "${ctx.node.id}" ${field} "${bare}"`;
1351
+ if (!value.includes("{{")) {
1352
+ ctx.abort(`${subject} is not an expression -- wrap it: {{ ${bare} }}`);
1353
+ }
1354
+ let open = 0;
1355
+ for (const [delimiter] of value.matchAll(/\{\{|\}\}/g)) {
1356
+ open = delimiter === "{{" ? open + 1 : Math.max(0, open - 1);
1357
+ }
1358
+ if (open > 0) {
1359
+ ctx.abort(`${subject} has an unclosed expression -- close every {{ with }}`);
1360
+ }
1361
+ }
1254
1362
 
1255
1363
  // src/registry/terminal-nodes.ts
1256
1364
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -1742,7 +1850,7 @@ var KINDS = [
1742
1850
  key: "condition",
1743
1851
  label: "Raw expression (advanced)",
1744
1852
  example: "{{ $json.active && $json.score > 10 }}",
1745
- description: "Escape hatch for logic the builder can't express. Overrides the conditions above when set."
1853
+ 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."
1746
1854
  }
1747
1855
  ]
1748
1856
  },
@@ -1762,7 +1870,7 @@ var KINDS = [
1762
1870
  // moves the ports on the canvas and the ports the runtime activates.
1763
1871
  outputs: (config) => casePorts(config?.cases),
1764
1872
  configSchema: [
1765
- { type: "expression", key: "value", label: "Switch on", example: "{{ $json.kind }}", required: true },
1873
+ { 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 },
1766
1874
  {
1767
1875
  type: "keyvalue",
1768
1876
  key: "cases",
@@ -1781,6 +1889,15 @@ var KINDS = [
1781
1889
  name: "@particle-academy/for_each",
1782
1890
  outputShape: [
1783
1891
  { path: "items", type: "array" },
1892
+ // `results` and `failures` are the WIRED shape -- what a lane produces
1893
+ // when the `item` port is connected. Declared unconditionally because a
1894
+ // declaration describes the kind, not one graph's use of it, and because
1895
+ // the shared table requires them: fancy-conformance 0.30.0 added
1896
+ // `results` and 0.31.0 added `failures`, both BREAKING, both so that a
1897
+ // runtime which has not implemented iteration fails here rather than
1898
+ // reporting surface parity it does not have.
1899
+ { path: "results", type: "array" },
1900
+ { path: "failures", type: "array" },
1784
1901
  { path: "count", type: "number" }
1785
1902
  ],
1786
1903
  aliases: ["for_each", "@fancy/for_each"],
@@ -1789,7 +1906,7 @@ var KINDS = [
1789
1906
  executor: forEachExecutor,
1790
1907
  category: "logic",
1791
1908
  label: "For Each",
1792
- description: "Publishes the resolved list and its size on BOTH `item` and `done`. Fan-out as DATA, not as jobs -- nothing runs per item.",
1909
+ 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.",
1793
1910
  icon: "\u21BB",
1794
1911
  inputs: [{ id: "in" }],
1795
1912
  outputs: [{ id: "item", label: "item" }, { id: "done", label: "done" }],