@particle-academy/fancy-flow 0.77.1 → 0.78.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-G5RAGPVD.js → chunk-3EDB6ELL.js} +3 -3
  6. package/dist/{chunk-G5RAGPVD.js.map → chunk-3EDB6ELL.js.map} +1 -1
  7. package/dist/{chunk-CQAMEPPI.js → chunk-4GNDRRJ3.js} +3 -3
  8. package/dist/{chunk-CQAMEPPI.js.map → chunk-4GNDRRJ3.js.map} +1 -1
  9. package/dist/{chunk-EGXA4SYJ.js → chunk-BBFHGAEN.js} +4 -4
  10. package/dist/{chunk-EGXA4SYJ.js.map → chunk-BBFHGAEN.js.map} +1 -1
  11. package/dist/{chunk-ARSOSEJV.js → chunk-GPFIJR2K.js} +4 -4
  12. package/dist/{chunk-ARSOSEJV.js.map → chunk-GPFIJR2K.js.map} +1 -1
  13. package/dist/{chunk-EEIWAIXW.js → chunk-J5YUYDMK.js} +106 -6
  14. package/dist/chunk-J5YUYDMK.js.map +1 -0
  15. package/dist/{chunk-2VI24UPB.js → chunk-S4MOUOG4.js} +3 -3
  16. package/dist/{chunk-2VI24UPB.js.map → chunk-S4MOUOG4.js.map} +1 -1
  17. package/dist/{chunk-T6WIIGSK.js → chunk-XO4ODEY2.js} +3 -3
  18. package/dist/{chunk-T6WIIGSK.js.map → chunk-XO4ODEY2.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 +104 -4
  24. package/dist/durable.cjs.map +1 -1
  25. package/dist/durable.js +1 -1
  26. package/dist/engine.cjs +104 -4
  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 +104 -4
  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 +104 -4
  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 +104 -4
  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 +104 -4
  63. package/dist/schema.cjs.map +1 -1
  64. package/dist/schema.js +3 -3
  65. package/dist/screens.cjs +104 -4
  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 +104 -4
  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-BBFHGAEN.js';
2
+ export { FlowViewer } from './chunk-BBFHGAEN.js';
3
+ import './chunk-XO4ODEY2.js';
4
4
  import './chunk-F5RPRB7A.js';
5
5
  import './chunk-OWENS2H5.js';
6
- import './chunk-EEIWAIXW.js';
6
+ import './chunk-J5YUYDMK.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
  );
@@ -1217,14 +1220,102 @@ var mergeExecutor = (ctx) => {
1217
1220
  }
1218
1221
  return merged;
1219
1222
  };
1220
- var forEachExecutor = (ctx) => {
1221
- const source = resolve(configOf(ctx.node).source, ctx.inputs);
1223
+ var FOR_EACH_DEFAULT_MAX_ITEMS = 1e3;
1224
+ var FOR_EACH_HARD_MAX_ITEMS = 1e4;
1225
+ function reachable(adjacency, starts) {
1226
+ const seen = /* @__PURE__ */ new Set();
1227
+ const queue = [...starts];
1228
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
1229
+ const id = queue[cursor];
1230
+ if (seen.has(id)) continue;
1231
+ seen.add(id);
1232
+ for (const target of adjacency.get(id) ?? []) queue.push(target);
1233
+ }
1234
+ return seen;
1235
+ }
1236
+ function forEachLane(graph, nodeId) {
1237
+ const handleOf = (edge) => edge.sourceHandle ?? "out";
1238
+ const itemEdges = graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "item");
1239
+ if (itemEdges.length === 0) return null;
1240
+ const adjacency = /* @__PURE__ */ new Map();
1241
+ for (const edge of graph.edges) {
1242
+ const list = adjacency.get(edge.source) ?? [];
1243
+ list.push(edge.target);
1244
+ adjacency.set(edge.source, list);
1245
+ }
1246
+ const done = reachable(
1247
+ adjacency,
1248
+ graph.edges.filter((e) => e.source === nodeId && handleOf(e) === "done").map((e) => e.target)
1249
+ );
1250
+ const body = new Set(
1251
+ [...reachable(adjacency, itemEdges.map((e) => e.target))].filter(
1252
+ (id) => !done.has(id) && id !== nodeId
1253
+ )
1254
+ );
1255
+ return {
1256
+ graph: {
1257
+ nodes: graph.nodes.filter((n) => body.has(n.id)),
1258
+ edges: graph.edges.filter((e) => body.has(e.source) && body.has(e.target)),
1259
+ ...graph.inputs === void 0 ? {} : { inputs: graph.inputs }
1260
+ },
1261
+ entries: itemEdges.filter((e) => body.has(e.target))
1262
+ };
1263
+ }
1264
+ var forEachExecutor = async (ctx) => {
1265
+ const config = configOf(ctx.node);
1266
+ const source = resolve(config.source, ctx.inputs);
1222
1267
  let items;
1223
1268
  if (Array.isArray(source)) items = source;
1224
1269
  else if (source === null || source === void 0) items = [];
1225
1270
  else if (typeof source === "object") items = Object.values(source);
1226
1271
  else items = [source];
1227
- return { items, count: items.length };
1272
+ const lane = ctx.graph ? forEachLane(ctx.graph, ctx.node.id) : null;
1273
+ if (lane === null || config.mode === "collect") {
1274
+ return { items, count: items.length };
1275
+ }
1276
+ if (lane.graph.nodes.length === 0) {
1277
+ ctx.abort(`for_each "${ctx.node.id}" has an item edge but its derived lane is empty`);
1278
+ }
1279
+ const maxItems = Number(config.maxItems ?? FOR_EACH_DEFAULT_MAX_ITEMS);
1280
+ if (!Number.isFinite(maxItems) || maxItems < 1 || maxItems > FOR_EACH_HARD_MAX_ITEMS) {
1281
+ ctx.abort(`for_each "${ctx.node.id}" maxItems must be between 1 and ${FOR_EACH_HARD_MAX_ITEMS}`);
1282
+ }
1283
+ if (items.length > maxItems) {
1284
+ ctx.abort(
1285
+ `for_each "${ctx.node.id}" resolved ${items.length} items exceeds its maxItems cap of ${maxItems}`
1286
+ );
1287
+ }
1288
+ const results = [];
1289
+ const failures = [];
1290
+ for (const [index, item] of items.entries()) {
1291
+ const initialInputs = {};
1292
+ for (const edge of lane.entries) {
1293
+ initialInputs[edge.target] = {
1294
+ ...initialInputs[edge.target] ?? {},
1295
+ [edge.targetHandle ?? "in"]: item
1296
+ };
1297
+ }
1298
+ const nested = await runFlow(lane.graph, { ...ctx.executors ?? {} }, () => {
1299
+ }, {
1300
+ initialInputs,
1301
+ depth: (ctx.depth ?? 0) + 1,
1302
+ // The index rides on the identity, so a node in iteration 3 cannot share
1303
+ // an idempotency key with the same node in iteration 4.
1304
+ run: ctx.run?.descend(ctx.node.id, index)
1305
+ });
1306
+ if (!nested.ok) {
1307
+ const reason = nested.error ?? "unknown error";
1308
+ if (decodePause(reason)) ctx.abort(reason);
1309
+ results.push(null);
1310
+ failures.push({ index, item, error: reason });
1311
+ continue;
1312
+ }
1313
+ results.push(nested.outputs);
1314
+ }
1315
+ return {
1316
+ __port: "done",
1317
+ value: { items, results, failures, count: items.length }
1318
+ };
1228
1319
  };
1229
1320
  var manualTriggerExecutor = (ctx) => ctx.inputs;
1230
1321
  var outputExecutor = (ctx) => {
@@ -1781,6 +1872,15 @@ var KINDS = [
1781
1872
  name: "@particle-academy/for_each",
1782
1873
  outputShape: [
1783
1874
  { path: "items", type: "array" },
1875
+ // `results` and `failures` are the WIRED shape -- what a lane produces
1876
+ // when the `item` port is connected. Declared unconditionally because a
1877
+ // declaration describes the kind, not one graph's use of it, and because
1878
+ // the shared table requires them: fancy-conformance 0.30.0 added
1879
+ // `results` and 0.31.0 added `failures`, both BREAKING, both so that a
1880
+ // runtime which has not implemented iteration fails here rather than
1881
+ // reporting surface parity it does not have.
1882
+ { path: "results", type: "array" },
1883
+ { path: "failures", type: "array" },
1784
1884
  { path: "count", type: "number" }
1785
1885
  ],
1786
1886
  aliases: ["for_each", "@fancy/for_each"],
@@ -1789,7 +1889,7 @@ var KINDS = [
1789
1889
  executor: forEachExecutor,
1790
1890
  category: "logic",
1791
1891
  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.",
1892
+ 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
1893
  icon: "\u21BB",
1794
1894
  inputs: [{ id: "in" }],
1795
1895
  outputs: [{ id: "item", label: "item" }, { id: "done", label: "done" }],