@particle-academy/fancy-flow 0.59.0 → 0.64.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 (66) hide show
  1. package/dist/{HumanPrompt-Bw09OyS3.d.ts → HumanPrompt-BKThiyjB.d.ts} +1 -1
  2. package/dist/{HumanPrompt-DV5-xnDa.d.cts → HumanPrompt-Bpp3JiVH.d.cts} +1 -1
  3. package/dist/{chunk-M7M6GLFV.js → chunk-5H54OTKT.js} +3 -3
  4. package/dist/{chunk-M7M6GLFV.js.map → chunk-5H54OTKT.js.map} +1 -1
  5. package/dist/chunk-77V4QC6Y.js +54 -0
  6. package/dist/chunk-77V4QC6Y.js.map +1 -0
  7. package/dist/{chunk-MX4SJDNI.js → chunk-EO6444T2.js} +4 -4
  8. package/dist/{chunk-MX4SJDNI.js.map → chunk-EO6444T2.js.map} +1 -1
  9. package/dist/{chunk-HBE4LVPX.js → chunk-JF6WCRBU.js} +9 -7
  10. package/dist/chunk-JF6WCRBU.js.map +1 -0
  11. package/dist/{chunk-LGLOBK6Q.js → chunk-MBVX4ZRB.js} +3 -3
  12. package/dist/{chunk-LGLOBK6Q.js.map → chunk-MBVX4ZRB.js.map} +1 -1
  13. package/dist/{chunk-A3TLZS5B.js → chunk-RIAFHQT5.js} +3 -3
  14. package/dist/{chunk-A3TLZS5B.js.map → chunk-RIAFHQT5.js.map} +1 -1
  15. package/dist/{chunk-BHM5HGXH.js → chunk-UM4C46AF.js} +38 -14
  16. package/dist/chunk-UM4C46AF.js.map +1 -0
  17. package/dist/{chunk-FGYG5JVW.js → chunk-W5DPKJY4.js} +5 -3
  18. package/dist/chunk-W5DPKJY4.js.map +1 -0
  19. package/dist/connectors.d.cts +1 -1
  20. package/dist/connectors.d.ts +1 -1
  21. package/dist/durable.cjs +7 -5
  22. package/dist/durable.cjs.map +1 -1
  23. package/dist/durable.js +2 -2
  24. package/dist/engine.cjs +189 -29
  25. package/dist/engine.cjs.map +1 -1
  26. package/dist/engine.d.cts +67 -5
  27. package/dist/engine.d.ts +67 -5
  28. package/dist/engine.js +7 -5
  29. package/dist/engine.js.map +1 -1
  30. package/dist/expr-BfXDrs4H.d.cts +149 -0
  31. package/dist/expr-BfXDrs4H.d.ts +149 -0
  32. package/dist/fields/react-fancy.d.cts +2 -2
  33. package/dist/fields/react-fancy.d.ts +2 -2
  34. package/dist/index.cjs +94 -16
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +7 -84
  37. package/dist/index.d.ts +7 -84
  38. package/dist/index.js +13 -12
  39. package/dist/index.js.map +1 -1
  40. package/dist/registry/index.d.cts +3 -3
  41. package/dist/registry/index.d.ts +3 -3
  42. package/dist/{registry-1qhmRtvC.d.cts → registry-CntAdUcY.d.cts} +1 -1
  43. package/dist/{registry-D2qocEsY.d.ts → registry-DYoV-MQi.d.ts} +1 -1
  44. package/dist/registry.cjs +7 -5
  45. package/dist/registry.cjs.map +1 -1
  46. package/dist/registry.js +2 -2
  47. package/dist/runtime.cjs +7 -5
  48. package/dist/runtime.cjs.map +1 -1
  49. package/dist/runtime.js +3 -3
  50. package/dist/schema.cjs +57 -5
  51. package/dist/schema.cjs.map +1 -1
  52. package/dist/schema.js +3 -2
  53. package/dist/screens.cjs +7 -5
  54. package/dist/screens.cjs.map +1 -1
  55. package/dist/screens.js +4 -4
  56. package/dist/{types-CLSUZW8c.d.ts → types-Ckhz-YwC.d.ts} +7 -2
  57. package/dist/{types-BLVpNNLN.d.cts → types-D71SKA5A.d.cts} +7 -2
  58. package/dist/ux.cjs +7 -5
  59. package/dist/ux.cjs.map +1 -1
  60. package/dist/ux.d.cts +1 -1
  61. package/dist/ux.d.ts +1 -1
  62. package/dist/ux.js +1 -1
  63. package/package.json +2 -2
  64. package/dist/chunk-BHM5HGXH.js.map +0 -1
  65. package/dist/chunk-FGYG5JVW.js.map +0 -1
  66. package/dist/chunk-HBE4LVPX.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { runFlow, getNodeKind, applyNodeChanges, applyEdgeChanges, addEdge } from './chunk-HBE4LVPX.js';
1
+ import { runFlow, getNodeKind, applyNodeChanges, applyEdgeChanges, addEdge } from './chunk-JF6WCRBU.js';
2
2
  import { useState, useRef, useCallback, useMemo } from 'react';
3
3
 
4
4
  function useFlowRun({ maxFeed = 200 } = {}) {
@@ -234,5 +234,5 @@ function useFlowHistory(flow) {
234
234
  }
235
235
 
236
236
  export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState };
237
- //# sourceMappingURL=chunk-LGLOBK6Q.js.map
238
- //# sourceMappingURL=chunk-LGLOBK6Q.js.map
237
+ //# sourceMappingURL=chunk-MBVX4ZRB.js.map
238
+ //# sourceMappingURL=chunk-MBVX4ZRB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime/use-flow-run.ts","../src/runtime/use-flow-state.ts","../src/runtime/history.ts","../src/runtime/use-flow-history.ts"],"names":["useState","useCallback","useRef"],"mappings":"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","file":"chunk-LGLOBK6Q.js","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\nimport { getNodeKind } from \"../registry/registry\";\nimport type {\n ExecutorRegistry,\n FlowGraph,\n NodeRunStatus,\n RunEvent,\n} from \"../types\";\n\nexport type FlowRunFeedEntry = {\n id: string;\n at: number;\n /**\n * `message` is a node's own human-facing announcement (`startingMsg` /\n * `stoppingMsg`), kept distinct from `status` and `info` so a progress UI can\n * show ONLY the narration. Folded in with diagnostics it would be unfindable,\n * which is the same as not having it.\n */\n level: \"info\" | \"warn\" | \"error\" | \"status\" | \"message\";\n text: string;\n nodeId?: string;\n detail?: unknown;\n};\n\nexport type UseFlowRunReturn = {\n /** Status keyed by nodeId — drive the UI overlay from this. */\n statuses: Record<string, NodeRunStatus>;\n /** Per-node status text (e.g. error message). */\n statusText: Record<string, string | undefined>;\n /** Latest computed output value per node (for reactive kinds). */\n outputs: Record<string, unknown>;\n /**\n * Latest status message per nodeId, from the node's own `startingMsg` /\n * `stoppingMsg`. Undefined for the nodes that declared none, which is most\n * of them by design.\n */\n nodeMessages: Record<string, string | undefined>;\n /** Live event log (capped to last N). */\n feed: FlowRunFeedEntry[];\n /** Whether a run is currently in progress. */\n running: boolean;\n /** Last run result, or null. */\n lastResult: RunResult | null;\n /** Kick off a run with the provided graph + executors. */\n run: (graph: FlowGraph, executors: ExecutorRegistry, options?: RunOptions) => Promise<RunResult>;\n /** Cancel the current run (if any). */\n cancel: () => void;\n /** Reset all runtime state (statuses, feed, lastResult). */\n reset: () => void;\n};\n\nexport type UseFlowRunOptions = {\n /** Cap the in-memory feed to this many entries. Default 200. */\n maxFeed?: number;\n};\n\n/**\n * useFlowRun — drives `runFlow` + maintains observability state. Pair with\n * `applyStatusesToNodes` (below) before passing nodes to `<FlowCanvas>` so\n * the per-node status badge renders.\n */\nexport function useFlowRun({ maxFeed = 200 }: UseFlowRunOptions = {}): UseFlowRunReturn {\n const [statuses, setStatuses] = useState<Record<string, NodeRunStatus>>({});\n const [statusText, setStatusText] = useState<Record<string, string | undefined>>({});\n const [nodeMessages, setNodeMessages] = useState<Record<string, string | undefined>>({});\n const [outputs, setOutputs] = useState<Record<string, unknown>>({});\n const [feed, setFeed] = useState<FlowRunFeedEntry[]>([]);\n const [running, setRunning] = useState(false);\n const [lastResult, setLastResult] = useState<RunResult | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const handleEvent = useCallback(\n (e: RunEvent) => {\n switch (e.type) {\n case \"node-status\":\n setStatuses((s) => ({ ...s, [e.nodeId]: e.status }));\n setStatusText((t) => ({ ...t, [e.nodeId]: e.text }));\n appendFeed({ level: \"status\", text: `${e.nodeId} → ${e.status}${e.text ? ` (${e.text})` : \"\"}`, nodeId: e.nodeId });\n break;\n case \"node-message\":\n setNodeMessages((m) => ({ ...m, [e.nodeId]: e.message }));\n appendFeed({ level: \"message\", text: e.message, nodeId: e.nodeId });\n break;\n case \"node-output\":\n setOutputs((o) => ({ ...o, [e.nodeId]: e.value }));\n appendFeed({ level: \"info\", text: `${e.nodeId}.${e.portId} = ${preview(e.value)}`, nodeId: e.nodeId, detail: e.value });\n break;\n case \"log\":\n appendFeed({ level: e.level, text: e.message, nodeId: e.nodeId, detail: e.detail });\n break;\n case \"run-start\":\n appendFeed({ level: \"info\", text: \"▶ run started\" });\n break;\n case \"run-end\":\n appendFeed({ level: e.ok ? \"info\" : \"error\", text: e.ok ? \"✓ run complete\" : \"✗ run failed\" });\n break;\n case \"run-error\":\n appendFeed({ level: \"error\", text: e.error });\n break;\n }\n function appendFeed(partial: Omit<FlowRunFeedEntry, \"id\" | \"at\">) {\n setFeed((f) => {\n const entry: FlowRunFeedEntry = { id: `${Date.now()}_${f.length}`, at: Date.now(), ...partial };\n const next = [...f, entry];\n return next.length > maxFeed ? next.slice(next.length - maxFeed) : next;\n });\n }\n },\n [maxFeed],\n );\n\n const run = useCallback(\n async (graph: FlowGraph, executors: ExecutorRegistry, options: RunOptions = {}) => {\n if (running) {\n return { ok: false, outputs: {}, error: \"another run is already in progress\" } satisfies RunResult;\n }\n const controller = new AbortController();\n abortRef.current = controller;\n // Reset previous statuses for the nodes we're about to run.\n const idleStatuses: Record<string, NodeRunStatus> = {};\n for (const n of graph.nodes) idleStatuses[n.id] = \"idle\";\n setStatuses(idleStatuses);\n setStatusText({});\n setOutputs({});\n setRunning(true);\n try {\n const result = await runFlow(graph, executors, handleEvent, { ...options, signal: controller.signal });\n setLastResult(result);\n return result;\n } finally {\n setRunning(false);\n abortRef.current = null;\n }\n },\n [handleEvent, running],\n );\n\n const cancel = useCallback(() => abortRef.current?.abort(), []);\n\n const reset = useCallback(() => {\n setStatuses({});\n setStatusText({});\n setOutputs({});\n setFeed([]);\n setLastResult(null);\n }, []);\n\n return { statuses, statusText, nodeMessages, outputs, feed, running, lastResult, run, cancel, reset };\n}\n\n/**\n * Write each reactive kind's latest run output into its `data.output`, so its\n * card can render the computed value live. Non-reactive kinds are returned\n * untouched. Pair with `applyStatusesToNodes` before rendering.\n */\nexport function applyOutputsToNodes<TNode extends { id: string; type?: string; data: any }>(\n nodes: TNode[],\n outputs: Record<string, unknown>,\n): TNode[] {\n return nodes.map((n) => {\n const kind = getNodeKind((n.data as any)?.kind ?? n.type ?? \"\");\n if (!kind?.reactive || !(n.id in outputs)) return n;\n return { ...n, data: { ...n.data, output: outputs[n.id] } };\n });\n}\n\n/** Merge runtime statuses into nodes for rendering. */\nexport function applyStatusesToNodes<TNode extends { id: string; data: any }>(\n nodes: TNode[],\n statuses: Record<string, NodeRunStatus>,\n statusText: Record<string, string | undefined>,\n): TNode[] {\n return nodes.map((n) => ({\n ...n,\n data: {\n ...n.data,\n status: statuses[n.id] ?? n.data?.status ?? \"idle\",\n statusText: statusText[n.id] ?? n.data?.statusText,\n },\n }));\n}\n\nfunction preview(v: unknown): string {\n try {\n const s = JSON.stringify(v);\n return s && s.length > 60 ? s.slice(0, 57) + \"…\" : (s ?? String(v));\n } catch {\n return String(v);\n }\n}\n","import { useCallback, useState } from \"react\";\nimport {\n addEdge,\n applyEdgeChanges,\n applyNodeChanges,\n type Connection,\n type Edge,\n type EdgeChange,\n type NodeChange,\n} from \"@xyflow/react\";\nimport type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\n\nexport type UseFlowStateReturn = {\n nodes: FlowNode[];\n edges: FlowEdge[];\n setNodes: React.Dispatch<React.SetStateAction<FlowNode[]>>;\n setEdges: React.Dispatch<React.SetStateAction<FlowEdge[]>>;\n /**\n * Replace nodes AND edges atomically in a single commit. Use this for any op\n * that touches both (delete-with-edge-prune, undo/redo restore, setGraph):\n * calling `setNodes` then `setEdges` is NOT atomic in controlled mode (each\n * closes over a stale `value`), so the second write clobbers the first.\n */\n setGraph: (graph: FlowGraph) => void;\n onNodesChange: (changes: NodeChange[]) => void;\n onEdgesChange: (changes: EdgeChange[]) => void;\n onConnect: (connection: Connection) => void;\n /** Snapshot the current graph (suitable for serialization). */\n toGraph: () => FlowGraph;\n};\n\n/**\n * useFlowState — React Flow's standard controlled-state plumbing in one hook.\n * Spread into <FlowCanvas>.\n */\nexport function useFlowState(initial: FlowGraph): UseFlowStateReturn {\n const [nodes, setNodes] = useState<FlowNode[]>(initial.nodes);\n const [edges, setEdges] = useState<FlowEdge[]>(initial.edges);\n\n const onNodesChange = useCallback((changes: NodeChange[]) => {\n setNodes((ns) => applyNodeChanges(changes, ns) as FlowNode[]);\n }, []);\n const onEdgesChange = useCallback((changes: EdgeChange[]) => {\n setEdges((es) => applyEdgeChanges(changes, es));\n }, []);\n const onConnect = useCallback((connection: Connection) => {\n setEdges((es) => addEdge(connection, es) as Edge[]);\n }, []);\n\n const setGraph = useCallback((graph: FlowGraph) => {\n // Two useState writes in one event are batched, so this IS atomic here.\n setNodes(graph.nodes);\n setEdges(graph.edges);\n }, []);\n\n const toGraph = useCallback(() => ({ nodes, edges }), [nodes, edges]);\n\n return { nodes, edges, setNodes, setEdges, setGraph, onNodesChange, onEdgesChange, onConnect, toGraph };\n}\n","import type { FlowGraph } from \"../types\";\n\n/**\n * Undo/redo history — a pure, React-free snapshot controller. The editor pushes\n * a graph snapshot BEFORE each committing mutation; `undo`/`redo` swap snapshots\n * between the past/future stacks. Kept free of React so it can be unit-tested\n * and reused by any host (or a headless driver).\n *\n * Coalescing (collapsing a burst of setState calls from one logical op into a\n * single undo step) is the caller's job — see `useFlowHistory`.\n */\nexport type HistoryController = {\n /** Record `graph` as an undo point. Clears the redo stack (new branch). */\n push: (graph: FlowGraph) => void;\n /** Pop the last undo point; `current` is pushed onto the redo stack. */\n undo: (current: FlowGraph) => FlowGraph | null;\n /** Pop the last redo point; `current` is pushed back onto the undo stack. */\n redo: (current: FlowGraph) => FlowGraph | null;\n canUndo: () => boolean;\n canRedo: () => boolean;\n clear: () => void;\n /** Test/inspection helper — sizes of the two stacks. */\n size: () => { past: number; future: number };\n};\n\nexport function createHistory(limit = 100): HistoryController {\n let past: FlowGraph[] = [];\n let future: FlowGraph[] = [];\n\n return {\n push(graph) {\n past.push(graph);\n if (past.length > limit) past.shift();\n future = [];\n },\n undo(current) {\n const prev = past.pop();\n if (prev === undefined) return null;\n future.push(current);\n return prev;\n },\n redo(current) {\n const next = future.pop();\n if (next === undefined) return null;\n past.push(current);\n return next;\n },\n canUndo: () => past.length > 0,\n canRedo: () => future.length > 0,\n clear() {\n past = [];\n future = [];\n },\n size: () => ({ past: past.length, future: future.length }),\n };\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { EdgeChange, NodeChange } from \"@xyflow/react\";\nimport type { FlowGraph } from \"../types\";\nimport { createHistory } from \"./history\";\nimport type { UseFlowStateReturn } from \"./use-flow-state\";\n\nexport type UseFlowHistoryReturn = {\n /** The flow sink wrapped so committing mutations snapshot for undo first. */\n flow: UseFlowStateReturn;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n /** Snapshot the current graph as an undo point (before a programmatic edit). */\n capture: () => void;\n /** Wire to `<FlowCanvas onNodeDragStart>` — snapshots the pre-drag graph. */\n onNodeDragStart: () => void;\n};\n\n/**\n * useFlowHistory — the commit/undo pipeline. Wraps a flow sink (the uncontrolled\n * `useFlowState` hook OR the controlled adapter) so every *committing* mutation\n * records a snapshot first, giving undo/redo without the editor threading\n * history through each call site. This is the single interception point the\n * two-sink architecture otherwise lacks.\n *\n * Granularity: a burst of setState calls from one logical op (e.g. a\n * delete = setNodes+setEdges) is coalesced into ONE undo step; transient\n * interactive changes (drag-move, dimension-measure, selection) are NOT captured\n * — a drag is captured once at `onNodeDragStart` instead.\n */\nexport function useFlowHistory(flow: UseFlowStateReturn): UseFlowHistoryReturn {\n const history = useRef(createHistory()).current;\n const restoring = useRef(false);\n const coalesced = useRef(false);\n const [, bump] = useState(0);\n const rerender = useCallback(() => bump((n) => n + 1), []);\n\n // Latest graph via a ref, so capture never reads a stale snapshot.\n const graphRef = useRef<FlowGraph>({ nodes: flow.nodes, edges: flow.edges });\n graphRef.current = { nodes: flow.nodes, edges: flow.edges };\n\n const capture = useCallback(() => {\n if (restoring.current || coalesced.current) return;\n coalesced.current = true;\n queueMicrotask(() => {\n coalesced.current = false;\n });\n history.push(graphRef.current);\n rerender();\n }, [history, rerender]);\n\n const restore = useCallback(\n (g: FlowGraph | null) => {\n if (!g) return;\n restoring.current = true;\n flow.setGraph(g);\n queueMicrotask(() => {\n restoring.current = false;\n });\n rerender();\n },\n [flow, rerender],\n );\n\n const undo = useCallback(() => restore(history.undo(graphRef.current)), [history, restore]);\n const redo = useCallback(() => restore(history.redo(graphRef.current)), [history, restore]);\n\n const wrapped = useMemo<UseFlowStateReturn>(\n () => ({\n ...flow,\n setNodes: (next) => {\n capture();\n flow.setNodes(next);\n },\n setEdges: (next) => {\n capture();\n flow.setEdges(next);\n },\n setGraph: (g) => {\n capture();\n flow.setGraph(g);\n },\n onNodesChange: (changes: NodeChange[]) => {\n // Only structural removes commit; position/dimensions/select are transient.\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onNodesChange(changes);\n },\n onEdgesChange: (changes: EdgeChange[]) => {\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onEdgesChange(changes);\n },\n onConnect: (conn) => {\n capture();\n flow.onConnect(conn);\n },\n }),\n [flow, capture],\n );\n\n return {\n flow: wrapped,\n undo,\n redo,\n canUndo: history.canUndo(),\n canRedo: history.canRedo(),\n capture,\n onNodeDragStart: capture,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/runtime/use-flow-run.ts","../src/runtime/use-flow-state.ts","../src/runtime/history.ts","../src/runtime/use-flow-history.ts"],"names":["useState","useCallback","useRef"],"mappings":"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","file":"chunk-MBVX4ZRB.js","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\nimport { getNodeKind } from \"../registry/registry\";\nimport type {\n ExecutorRegistry,\n FlowGraph,\n NodeRunStatus,\n RunEvent,\n} from \"../types\";\n\nexport type FlowRunFeedEntry = {\n id: string;\n at: number;\n /**\n * `message` is a node's own human-facing announcement (`startingMsg` /\n * `stoppingMsg`), kept distinct from `status` and `info` so a progress UI can\n * show ONLY the narration. Folded in with diagnostics it would be unfindable,\n * which is the same as not having it.\n */\n level: \"info\" | \"warn\" | \"error\" | \"status\" | \"message\";\n text: string;\n nodeId?: string;\n detail?: unknown;\n};\n\nexport type UseFlowRunReturn = {\n /** Status keyed by nodeId — drive the UI overlay from this. */\n statuses: Record<string, NodeRunStatus>;\n /** Per-node status text (e.g. error message). */\n statusText: Record<string, string | undefined>;\n /** Latest computed output value per node (for reactive kinds). */\n outputs: Record<string, unknown>;\n /**\n * Latest status message per nodeId, from the node's own `startingMsg` /\n * `stoppingMsg`. Undefined for the nodes that declared none, which is most\n * of them by design.\n */\n nodeMessages: Record<string, string | undefined>;\n /** Live event log (capped to last N). */\n feed: FlowRunFeedEntry[];\n /** Whether a run is currently in progress. */\n running: boolean;\n /** Last run result, or null. */\n lastResult: RunResult | null;\n /** Kick off a run with the provided graph + executors. */\n run: (graph: FlowGraph, executors: ExecutorRegistry, options?: RunOptions) => Promise<RunResult>;\n /** Cancel the current run (if any). */\n cancel: () => void;\n /** Reset all runtime state (statuses, feed, lastResult). */\n reset: () => void;\n};\n\nexport type UseFlowRunOptions = {\n /** Cap the in-memory feed to this many entries. Default 200. */\n maxFeed?: number;\n};\n\n/**\n * useFlowRun — drives `runFlow` + maintains observability state. Pair with\n * `applyStatusesToNodes` (below) before passing nodes to `<FlowCanvas>` so\n * the per-node status badge renders.\n */\nexport function useFlowRun({ maxFeed = 200 }: UseFlowRunOptions = {}): UseFlowRunReturn {\n const [statuses, setStatuses] = useState<Record<string, NodeRunStatus>>({});\n const [statusText, setStatusText] = useState<Record<string, string | undefined>>({});\n const [nodeMessages, setNodeMessages] = useState<Record<string, string | undefined>>({});\n const [outputs, setOutputs] = useState<Record<string, unknown>>({});\n const [feed, setFeed] = useState<FlowRunFeedEntry[]>([]);\n const [running, setRunning] = useState(false);\n const [lastResult, setLastResult] = useState<RunResult | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const handleEvent = useCallback(\n (e: RunEvent) => {\n switch (e.type) {\n case \"node-status\":\n setStatuses((s) => ({ ...s, [e.nodeId]: e.status }));\n setStatusText((t) => ({ ...t, [e.nodeId]: e.text }));\n appendFeed({ level: \"status\", text: `${e.nodeId} → ${e.status}${e.text ? ` (${e.text})` : \"\"}`, nodeId: e.nodeId });\n break;\n case \"node-message\":\n setNodeMessages((m) => ({ ...m, [e.nodeId]: e.message }));\n appendFeed({ level: \"message\", text: e.message, nodeId: e.nodeId });\n break;\n case \"node-output\":\n setOutputs((o) => ({ ...o, [e.nodeId]: e.value }));\n appendFeed({ level: \"info\", text: `${e.nodeId}.${e.portId} = ${preview(e.value)}`, nodeId: e.nodeId, detail: e.value });\n break;\n case \"log\":\n appendFeed({ level: e.level, text: e.message, nodeId: e.nodeId, detail: e.detail });\n break;\n case \"run-start\":\n appendFeed({ level: \"info\", text: \"▶ run started\" });\n break;\n case \"run-end\":\n appendFeed({ level: e.ok ? \"info\" : \"error\", text: e.ok ? \"✓ run complete\" : \"✗ run failed\" });\n break;\n case \"run-error\":\n appendFeed({ level: \"error\", text: e.error });\n break;\n }\n function appendFeed(partial: Omit<FlowRunFeedEntry, \"id\" | \"at\">) {\n setFeed((f) => {\n const entry: FlowRunFeedEntry = { id: `${Date.now()}_${f.length}`, at: Date.now(), ...partial };\n const next = [...f, entry];\n return next.length > maxFeed ? next.slice(next.length - maxFeed) : next;\n });\n }\n },\n [maxFeed],\n );\n\n const run = useCallback(\n async (graph: FlowGraph, executors: ExecutorRegistry, options: RunOptions = {}) => {\n if (running) {\n return { ok: false, outputs: {}, error: \"another run is already in progress\" } satisfies RunResult;\n }\n const controller = new AbortController();\n abortRef.current = controller;\n // Reset previous statuses for the nodes we're about to run.\n const idleStatuses: Record<string, NodeRunStatus> = {};\n for (const n of graph.nodes) idleStatuses[n.id] = \"idle\";\n setStatuses(idleStatuses);\n setStatusText({});\n setOutputs({});\n setRunning(true);\n try {\n const result = await runFlow(graph, executors, handleEvent, { ...options, signal: controller.signal });\n setLastResult(result);\n return result;\n } finally {\n setRunning(false);\n abortRef.current = null;\n }\n },\n [handleEvent, running],\n );\n\n const cancel = useCallback(() => abortRef.current?.abort(), []);\n\n const reset = useCallback(() => {\n setStatuses({});\n setStatusText({});\n setOutputs({});\n setFeed([]);\n setLastResult(null);\n }, []);\n\n return { statuses, statusText, nodeMessages, outputs, feed, running, lastResult, run, cancel, reset };\n}\n\n/**\n * Write each reactive kind's latest run output into its `data.output`, so its\n * card can render the computed value live. Non-reactive kinds are returned\n * untouched. Pair with `applyStatusesToNodes` before rendering.\n */\nexport function applyOutputsToNodes<TNode extends { id: string; type?: string; data: any }>(\n nodes: TNode[],\n outputs: Record<string, unknown>,\n): TNode[] {\n return nodes.map((n) => {\n const kind = getNodeKind((n.data as any)?.kind ?? n.type ?? \"\");\n if (!kind?.reactive || !(n.id in outputs)) return n;\n return { ...n, data: { ...n.data, output: outputs[n.id] } };\n });\n}\n\n/** Merge runtime statuses into nodes for rendering. */\nexport function applyStatusesToNodes<TNode extends { id: string; data: any }>(\n nodes: TNode[],\n statuses: Record<string, NodeRunStatus>,\n statusText: Record<string, string | undefined>,\n): TNode[] {\n return nodes.map((n) => ({\n ...n,\n data: {\n ...n.data,\n status: statuses[n.id] ?? n.data?.status ?? \"idle\",\n statusText: statusText[n.id] ?? n.data?.statusText,\n },\n }));\n}\n\nfunction preview(v: unknown): string {\n try {\n const s = JSON.stringify(v);\n return s && s.length > 60 ? s.slice(0, 57) + \"…\" : (s ?? String(v));\n } catch {\n return String(v);\n }\n}\n","import { useCallback, useState } from \"react\";\nimport {\n addEdge,\n applyEdgeChanges,\n applyNodeChanges,\n type Connection,\n type Edge,\n type EdgeChange,\n type NodeChange,\n} from \"@xyflow/react\";\nimport type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\n\nexport type UseFlowStateReturn = {\n nodes: FlowNode[];\n edges: FlowEdge[];\n setNodes: React.Dispatch<React.SetStateAction<FlowNode[]>>;\n setEdges: React.Dispatch<React.SetStateAction<FlowEdge[]>>;\n /**\n * Replace nodes AND edges atomically in a single commit. Use this for any op\n * that touches both (delete-with-edge-prune, undo/redo restore, setGraph):\n * calling `setNodes` then `setEdges` is NOT atomic in controlled mode (each\n * closes over a stale `value`), so the second write clobbers the first.\n */\n setGraph: (graph: FlowGraph) => void;\n onNodesChange: (changes: NodeChange[]) => void;\n onEdgesChange: (changes: EdgeChange[]) => void;\n onConnect: (connection: Connection) => void;\n /** Snapshot the current graph (suitable for serialization). */\n toGraph: () => FlowGraph;\n};\n\n/**\n * useFlowState — React Flow's standard controlled-state plumbing in one hook.\n * Spread into <FlowCanvas>.\n */\nexport function useFlowState(initial: FlowGraph): UseFlowStateReturn {\n const [nodes, setNodes] = useState<FlowNode[]>(initial.nodes);\n const [edges, setEdges] = useState<FlowEdge[]>(initial.edges);\n\n const onNodesChange = useCallback((changes: NodeChange[]) => {\n setNodes((ns) => applyNodeChanges(changes, ns) as FlowNode[]);\n }, []);\n const onEdgesChange = useCallback((changes: EdgeChange[]) => {\n setEdges((es) => applyEdgeChanges(changes, es));\n }, []);\n const onConnect = useCallback((connection: Connection) => {\n setEdges((es) => addEdge(connection, es) as Edge[]);\n }, []);\n\n const setGraph = useCallback((graph: FlowGraph) => {\n // Two useState writes in one event are batched, so this IS atomic here.\n setNodes(graph.nodes);\n setEdges(graph.edges);\n }, []);\n\n const toGraph = useCallback(() => ({ nodes, edges }), [nodes, edges]);\n\n return { nodes, edges, setNodes, setEdges, setGraph, onNodesChange, onEdgesChange, onConnect, toGraph };\n}\n","import type { FlowGraph } from \"../types\";\n\n/**\n * Undo/redo history — a pure, React-free snapshot controller. The editor pushes\n * a graph snapshot BEFORE each committing mutation; `undo`/`redo` swap snapshots\n * between the past/future stacks. Kept free of React so it can be unit-tested\n * and reused by any host (or a headless driver).\n *\n * Coalescing (collapsing a burst of setState calls from one logical op into a\n * single undo step) is the caller's job — see `useFlowHistory`.\n */\nexport type HistoryController = {\n /** Record `graph` as an undo point. Clears the redo stack (new branch). */\n push: (graph: FlowGraph) => void;\n /** Pop the last undo point; `current` is pushed onto the redo stack. */\n undo: (current: FlowGraph) => FlowGraph | null;\n /** Pop the last redo point; `current` is pushed back onto the undo stack. */\n redo: (current: FlowGraph) => FlowGraph | null;\n canUndo: () => boolean;\n canRedo: () => boolean;\n clear: () => void;\n /** Test/inspection helper — sizes of the two stacks. */\n size: () => { past: number; future: number };\n};\n\nexport function createHistory(limit = 100): HistoryController {\n let past: FlowGraph[] = [];\n let future: FlowGraph[] = [];\n\n return {\n push(graph) {\n past.push(graph);\n if (past.length > limit) past.shift();\n future = [];\n },\n undo(current) {\n const prev = past.pop();\n if (prev === undefined) return null;\n future.push(current);\n return prev;\n },\n redo(current) {\n const next = future.pop();\n if (next === undefined) return null;\n past.push(current);\n return next;\n },\n canUndo: () => past.length > 0,\n canRedo: () => future.length > 0,\n clear() {\n past = [];\n future = [];\n },\n size: () => ({ past: past.length, future: future.length }),\n };\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { EdgeChange, NodeChange } from \"@xyflow/react\";\nimport type { FlowGraph } from \"../types\";\nimport { createHistory } from \"./history\";\nimport type { UseFlowStateReturn } from \"./use-flow-state\";\n\nexport type UseFlowHistoryReturn = {\n /** The flow sink wrapped so committing mutations snapshot for undo first. */\n flow: UseFlowStateReturn;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n /** Snapshot the current graph as an undo point (before a programmatic edit). */\n capture: () => void;\n /** Wire to `<FlowCanvas onNodeDragStart>` — snapshots the pre-drag graph. */\n onNodeDragStart: () => void;\n};\n\n/**\n * useFlowHistory — the commit/undo pipeline. Wraps a flow sink (the uncontrolled\n * `useFlowState` hook OR the controlled adapter) so every *committing* mutation\n * records a snapshot first, giving undo/redo without the editor threading\n * history through each call site. This is the single interception point the\n * two-sink architecture otherwise lacks.\n *\n * Granularity: a burst of setState calls from one logical op (e.g. a\n * delete = setNodes+setEdges) is coalesced into ONE undo step; transient\n * interactive changes (drag-move, dimension-measure, selection) are NOT captured\n * — a drag is captured once at `onNodeDragStart` instead.\n */\nexport function useFlowHistory(flow: UseFlowStateReturn): UseFlowHistoryReturn {\n const history = useRef(createHistory()).current;\n const restoring = useRef(false);\n const coalesced = useRef(false);\n const [, bump] = useState(0);\n const rerender = useCallback(() => bump((n) => n + 1), []);\n\n // Latest graph via a ref, so capture never reads a stale snapshot.\n const graphRef = useRef<FlowGraph>({ nodes: flow.nodes, edges: flow.edges });\n graphRef.current = { nodes: flow.nodes, edges: flow.edges };\n\n const capture = useCallback(() => {\n if (restoring.current || coalesced.current) return;\n coalesced.current = true;\n queueMicrotask(() => {\n coalesced.current = false;\n });\n history.push(graphRef.current);\n rerender();\n }, [history, rerender]);\n\n const restore = useCallback(\n (g: FlowGraph | null) => {\n if (!g) return;\n restoring.current = true;\n flow.setGraph(g);\n queueMicrotask(() => {\n restoring.current = false;\n });\n rerender();\n },\n [flow, rerender],\n );\n\n const undo = useCallback(() => restore(history.undo(graphRef.current)), [history, restore]);\n const redo = useCallback(() => restore(history.redo(graphRef.current)), [history, restore]);\n\n const wrapped = useMemo<UseFlowStateReturn>(\n () => ({\n ...flow,\n setNodes: (next) => {\n capture();\n flow.setNodes(next);\n },\n setEdges: (next) => {\n capture();\n flow.setEdges(next);\n },\n setGraph: (g) => {\n capture();\n flow.setGraph(g);\n },\n onNodesChange: (changes: NodeChange[]) => {\n // Only structural removes commit; position/dimensions/select are transient.\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onNodesChange(changes);\n },\n onEdgesChange: (changes: EdgeChange[]) => {\n if (!restoring.current && changes.some((c) => c.type === \"remove\")) capture();\n flow.onEdgesChange(changes);\n },\n onConnect: (conn) => {\n capture();\n flow.onConnect(conn);\n },\n }),\n [flow, capture],\n );\n\n return {\n flow: wrapped,\n undo,\n redo,\n canUndo: history.canUndo(),\n canRedo: history.canRedo(),\n capture,\n onNodeDragStart: capture,\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { runFlow } from './chunk-HBE4LVPX.js';
1
+ import { runFlow } from './chunk-JF6WCRBU.js';
2
2
 
3
3
  // src/runtime/run-cohort.ts
4
4
  async function runCohort(flows, executors, onEvent = () => {
@@ -46,5 +46,5 @@ function errorText(error) {
46
46
  }
47
47
 
48
48
  export { runCohort };
49
- //# sourceMappingURL=chunk-A3TLZS5B.js.map
50
- //# sourceMappingURL=chunk-A3TLZS5B.js.map
49
+ //# sourceMappingURL=chunk-RIAFHQT5.js.map
50
+ //# sourceMappingURL=chunk-RIAFHQT5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime/run-cohort.ts"],"names":[],"mappings":";;;AAoFA,eAAsB,SAAA,CACpB,KAAA,EACA,SAAA,EACA,OAAA,GAAoD,MAAM;AAAC,CAAA,EAC3D,OAAA,GAAyB,EAAC,EACD;AACzB,EAAA,MAAM,EAAE,MAAA,GAAS,gBAAA,EAAkB,OAAO,MAAA,EAAQ,GAAG,YAAW,GAAI,OAAA;AAEpE,EAAA,IAAI,WAAW,UAAA,EAAY;AACzB,IAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,MACb,KAAA,CAAM,GAAA,CAAI,OAAO,IAAA,EAAM,KAAA,MAAW;AAAA,QAChC,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,QACvE;AAAA,OACF,CAAE;AAAA,KACJ;AAAA,EACF;AAEA,EAAA,MAAM,UAA0B,EAAC;AAEjC,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AAC3C,IAAA,IAAI,MAAA,KAAW,oBAAoB,KAAA,EAAO;AACxC,MAAA,MAAM,GAAA,GAAM,MAAM,MAAA,GAAS,IAAA,EAAM,KAAK,CAAA,IAAK,sCAAA;AAC3C,MAAA,IAAI,MAAA;AAEJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,MAClC,SAAS,KAAA,EAAO;AAGd,QAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,UACX,EAAA,EAAI,KAAA;AAAA,UACJ,SAAS,EAAC;AAAA,UACV,KAAA;AAAA,UACA,OAAA,EAAS,IAAA;AAAA,UACT,aAAA,EAAe,CAAA,8BAAA,EAAiC,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,SACjE,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,EAAA,EAAI,KAAA,EAAO,OAAA,EAAS,EAAC,EAAG,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,aAAA,EAAe,GAAA,IAAO,CAAA;AACnF,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,MACvE;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,OAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC9D","file":"chunk-A3TLZS5B.js","sourcesContent":["import type { ExecutorRegistry, FlowGraph, RunEvent } from \"../types\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\n\n/**\n * How the flows one trigger fired should treat each other.\n *\n * - `serial-guarded` (default) — run in the declared order, one at a time,\n * re-checking `guard` immediately before each. This is the safe default\n * because it is the only one that notices when an earlier flow invalidated\n * the state a later one was fired for.\n * - `serial` — ordered, one at a time, unguarded.\n * - `parallel` — all at once. Ordering is incidental and collisions are yours to\n * handle; correct for fan-outs that share no state, and only those.\n */\nexport type CohortPolicy = \"serial-guarded\" | \"serial\" | \"parallel\";\n\n/**\n * The precondition re-checked just before each flow starts.\n *\n * Return `false` (or throw) to skip that flow. The engine cannot know what\n * \"still valid\" means for your data, so it asks you — right before the run,\n * not at dispatch, because the whole hazard is what changed in between.\n */\nexport type CohortGuard = (\n flow: FlowGraph,\n index: number,\n) => boolean | Promise<boolean>;\n\nexport type CohortOptions = RunOptions & {\n policy?: CohortPolicy;\n guard?: CohortGuard;\n /** Why the guard said no — recorded on the skipped result. */\n reason?: (flow: FlowGraph, index: number) => string;\n};\n\nexport type CohortResult = RunResult & {\n index: number;\n /** True when the flow never ran because its guard did not pass. */\n skipped?: boolean;\n skippedReason?: string;\n};\n\n/**\n * runCohort — run every flow that one trigger event fired, as a group.\n *\n * ## Why this exists\n *\n * `runFlow` runs one graph. A host that fans a single webhook, schedule, or\n * record change out to several flows usually loops it — and a loop, or worse a\n * `Promise.all`, has no answer for the case that actually bites: one of those\n * flows deletes or mutates the record they were ALL fired for. The others then\n * run against state that is no longer there and resolve `ok: true`, having done\n * nothing. Nothing throws. Nothing is logged. That silent success is the failure\n * mode this function exists to remove.\n *\n * ## What it does\n *\n * Runs the flows in the order you declared, one at a time, and calls `guard`\n * immediately before each. A flow whose guard does not pass is returned with\n * `skipped: true` and a reason instead of being run — and the cohort carries on\n * to the next one.\n *\n * ```ts\n * const results = await runCohort([enrich, archive, notify], executors, undefined, {\n * initialInputs: { t: { deal } }, // snapshotted once, shared by all\n * guard: async () => Boolean(await findDeal(deal.id)),\n * reason: () => `deal ${deal.id} no longer exists`,\n * });\n * ```\n *\n * If `archive` deletes the deal, `notify` comes back skipped with that reason\n * rather than notifying about nothing.\n *\n * ## Failure does not cancel the cohort\n *\n * A flow that fails is reported and the next one still runs. \"The flow before me\n * threw\" is not an answer to \"is my input still there\" — the guard is, and it\n * gets asked either way.\n *\n * The Laravel twin (`FancyFlow::dispatchCohort()` in `fancy-flow-php`) is the\n * same contract across a queue, where each run is durable and hands on to its\n * successor when it settles. Same policies, same guard semantics, same\n * fail-closed rule.\n */\nexport async function runCohort(\n flows: FlowGraph[],\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent, index: number) => void = () => {},\n options: CohortOptions = {},\n): Promise<CohortResult[]> {\n const { policy = \"serial-guarded\", guard, reason, ...runOptions } = options;\n\n if (policy === \"parallel\") {\n return Promise.all(\n flows.map(async (flow, index) => ({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n })),\n );\n }\n\n const results: CohortResult[] = [];\n\n for (const [index, flow] of flows.entries()) {\n if (policy === \"serial-guarded\" && guard) {\n const why = () => reason?.(flow, index) ?? \"trigger precondition no longer holds\";\n let passes: boolean;\n\n try {\n passes = await guard(flow, index);\n } catch (error) {\n // Fail CLOSED. A guard that cannot answer is not permission to proceed:\n // a skip is visible and re-runnable, a run over missing state is neither.\n results.push({\n ok: false,\n outputs: {},\n index,\n skipped: true,\n skippedReason: `guard could not be evaluated: ${errorText(error)}`,\n });\n continue;\n }\n\n if (!passes) {\n results.push({ ok: false, outputs: {}, index, skipped: true, skippedReason: why() });\n continue;\n }\n }\n\n results.push({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n });\n }\n\n return results;\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
1
+ {"version":3,"sources":["../src/runtime/run-cohort.ts"],"names":[],"mappings":";;;AAoFA,eAAsB,SAAA,CACpB,KAAA,EACA,SAAA,EACA,OAAA,GAAoD,MAAM;AAAC,CAAA,EAC3D,OAAA,GAAyB,EAAC,EACD;AACzB,EAAA,MAAM,EAAE,MAAA,GAAS,gBAAA,EAAkB,OAAO,MAAA,EAAQ,GAAG,YAAW,GAAI,OAAA;AAEpE,EAAA,IAAI,WAAW,UAAA,EAAY;AACzB,IAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,MACb,KAAA,CAAM,GAAA,CAAI,OAAO,IAAA,EAAM,KAAA,MAAW;AAAA,QAChC,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,QACvE;AAAA,OACF,CAAE;AAAA,KACJ;AAAA,EACF;AAEA,EAAA,MAAM,UAA0B,EAAC;AAEjC,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AAC3C,IAAA,IAAI,MAAA,KAAW,oBAAoB,KAAA,EAAO;AACxC,MAAA,MAAM,GAAA,GAAM,MAAM,MAAA,GAAS,IAAA,EAAM,KAAK,CAAA,IAAK,sCAAA;AAC3C,MAAA,IAAI,MAAA;AAEJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,MAClC,SAAS,KAAA,EAAO;AAGd,QAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,UACX,EAAA,EAAI,KAAA;AAAA,UACJ,SAAS,EAAC;AAAA,UACV,KAAA;AAAA,UACA,OAAA,EAAS,IAAA;AAAA,UACT,aAAA,EAAe,CAAA,8BAAA,EAAiC,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,SACjE,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,EAAA,EAAI,KAAA,EAAO,OAAA,EAAS,EAAC,EAAG,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,aAAA,EAAe,GAAA,IAAO,CAAA;AACnF,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,GAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,EAAG,KAAK,CAAA,EAAG,UAAU,CAAA;AAAA,MACvE;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,OAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC9D","file":"chunk-RIAFHQT5.js","sourcesContent":["import type { ExecutorRegistry, FlowGraph, RunEvent } from \"../types\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\n\n/**\n * How the flows one trigger fired should treat each other.\n *\n * - `serial-guarded` (default) — run in the declared order, one at a time,\n * re-checking `guard` immediately before each. This is the safe default\n * because it is the only one that notices when an earlier flow invalidated\n * the state a later one was fired for.\n * - `serial` — ordered, one at a time, unguarded.\n * - `parallel` — all at once. Ordering is incidental and collisions are yours to\n * handle; correct for fan-outs that share no state, and only those.\n */\nexport type CohortPolicy = \"serial-guarded\" | \"serial\" | \"parallel\";\n\n/**\n * The precondition re-checked just before each flow starts.\n *\n * Return `false` (or throw) to skip that flow. The engine cannot know what\n * \"still valid\" means for your data, so it asks you — right before the run,\n * not at dispatch, because the whole hazard is what changed in between.\n */\nexport type CohortGuard = (\n flow: FlowGraph,\n index: number,\n) => boolean | Promise<boolean>;\n\nexport type CohortOptions = RunOptions & {\n policy?: CohortPolicy;\n guard?: CohortGuard;\n /** Why the guard said no — recorded on the skipped result. */\n reason?: (flow: FlowGraph, index: number) => string;\n};\n\nexport type CohortResult = RunResult & {\n index: number;\n /** True when the flow never ran because its guard did not pass. */\n skipped?: boolean;\n skippedReason?: string;\n};\n\n/**\n * runCohort — run every flow that one trigger event fired, as a group.\n *\n * ## Why this exists\n *\n * `runFlow` runs one graph. A host that fans a single webhook, schedule, or\n * record change out to several flows usually loops it — and a loop, or worse a\n * `Promise.all`, has no answer for the case that actually bites: one of those\n * flows deletes or mutates the record they were ALL fired for. The others then\n * run against state that is no longer there and resolve `ok: true`, having done\n * nothing. Nothing throws. Nothing is logged. That silent success is the failure\n * mode this function exists to remove.\n *\n * ## What it does\n *\n * Runs the flows in the order you declared, one at a time, and calls `guard`\n * immediately before each. A flow whose guard does not pass is returned with\n * `skipped: true` and a reason instead of being run — and the cohort carries on\n * to the next one.\n *\n * ```ts\n * const results = await runCohort([enrich, archive, notify], executors, undefined, {\n * initialInputs: { t: { deal } }, // snapshotted once, shared by all\n * guard: async () => Boolean(await findDeal(deal.id)),\n * reason: () => `deal ${deal.id} no longer exists`,\n * });\n * ```\n *\n * If `archive` deletes the deal, `notify` comes back skipped with that reason\n * rather than notifying about nothing.\n *\n * ## Failure does not cancel the cohort\n *\n * A flow that fails is reported and the next one still runs. \"The flow before me\n * threw\" is not an answer to \"is my input still there\" — the guard is, and it\n * gets asked either way.\n *\n * The Laravel twin (`FancyFlow::dispatchCohort()` in `fancy-flow-php`) is the\n * same contract across a queue, where each run is durable and hands on to its\n * successor when it settles. Same policies, same guard semantics, same\n * fail-closed rule.\n */\nexport async function runCohort(\n flows: FlowGraph[],\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent, index: number) => void = () => {},\n options: CohortOptions = {},\n): Promise<CohortResult[]> {\n const { policy = \"serial-guarded\", guard, reason, ...runOptions } = options;\n\n if (policy === \"parallel\") {\n return Promise.all(\n flows.map(async (flow, index) => ({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n })),\n );\n }\n\n const results: CohortResult[] = [];\n\n for (const [index, flow] of flows.entries()) {\n if (policy === \"serial-guarded\" && guard) {\n const why = () => reason?.(flow, index) ?? \"trigger precondition no longer holds\";\n let passes: boolean;\n\n try {\n passes = await guard(flow, index);\n } catch (error) {\n // Fail CLOSED. A guard that cannot answer is not permission to proceed:\n // a skip is visible and re-runnable, a run over missing state is neither.\n results.push({\n ok: false,\n outputs: {},\n index,\n skipped: true,\n skippedReason: `guard could not be evaluated: ${errorText(error)}`,\n });\n continue;\n }\n\n if (!passes) {\n results.push({ ok: false, outputs: {}, index, skipped: true, skippedReason: why() });\n continue;\n }\n }\n\n results.push({\n ...(await runFlow(flow, executors, (e) => onEvent(e, index), runOptions)),\n index,\n });\n }\n\n return results;\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
@@ -17,9 +17,19 @@ function interpolate(template, resolve) {
17
17
  }
18
18
  }
19
19
  var FALSY_STRINGS = /* @__PURE__ */ new Set(["", "0", "false", "no", "off", "null"]);
20
- function resolvePath(path, context) {
20
+ var UnresolvedPathError = class extends Error {
21
+ constructor(path) {
22
+ super(
23
+ `Expression path "${path}" did not resolve. Under the "throw" policy an unresolvable path is an error rather than an empty string.`
24
+ );
25
+ this.path = path;
26
+ this.name = "UnresolvedPathError";
27
+ }
28
+ };
29
+ function tryResolvePath(path, context) {
30
+ const unresolved = { resolved: false, value: null };
21
31
  const trimmed = path.trim();
22
- if (trimmed === "") return null;
32
+ if (trimmed === "") return unresolved;
23
33
  const segments = trimmed.split(".");
24
34
  let cursor;
25
35
  const head = segments[0];
@@ -30,19 +40,33 @@ function resolvePath(path, context) {
30
40
  cursor = context;
31
41
  }
32
42
  for (const segment of segments) {
33
- if (cursor === null || cursor === void 0) return null;
34
- if (typeof cursor !== "object") return null;
43
+ if (cursor === null || cursor === void 0) return unresolved;
44
+ if (typeof cursor !== "object") return unresolved;
35
45
  const next = cursor[segment];
36
- if (next === void 0) return null;
46
+ if (next === void 0) return unresolved;
37
47
  cursor = next;
38
48
  }
39
- return cursor === void 0 ? null : cursor;
49
+ return { resolved: true, value: cursor === void 0 ? null : cursor };
40
50
  }
41
- function evaluateExpression(template, context) {
51
+ function resolvePath(path, context) {
52
+ return tryResolvePath(path, context).value;
53
+ }
54
+ function evaluateExpression(template, context, options = {}) {
42
55
  if (typeof template !== "string") return template;
56
+ const policy = options.onUnresolved ?? "empty";
43
57
  const whole = wholeExpression(template.trim());
44
- if (whole !== null) return resolvePath(whole, context);
45
- return interpolate(template, (path) => stringify(resolvePath(path, context)));
58
+ if (whole !== null) {
59
+ const r = tryResolvePath(whole, context);
60
+ if (r.resolved) return r.value;
61
+ if (policy === "throw") throw new UnresolvedPathError(whole);
62
+ return policy === "keep" ? template : null;
63
+ }
64
+ return interpolate(template, (path) => {
65
+ const r = tryResolvePath(path, context);
66
+ if (r.resolved) return stringify(r.value);
67
+ if (policy === "throw") throw new UnresolvedPathError(path);
68
+ return policy === "keep" ? `{{${path}}}` : "";
69
+ });
46
70
  }
47
71
  function truthy(value) {
48
72
  if (typeof value === "boolean") return value;
@@ -66,14 +90,14 @@ function stringify(value) {
66
90
  return "";
67
91
  }
68
92
  }
69
- function evaluateConfig(config, context) {
93
+ function evaluateConfig(config, context, options = {}) {
70
94
  const out = {};
71
95
  for (const [key, value] of Object.entries(config)) {
72
- out[key] = Array.isArray(value) ? value.map((v) => evaluateExpression(v, context)) : value !== null && typeof value === "object" ? evaluateConfig(value, context) : evaluateExpression(value, context);
96
+ out[key] = Array.isArray(value) ? value.map((v) => evaluateExpression(v, context, options)) : value !== null && typeof value === "object" ? evaluateConfig(value, context, options) : evaluateExpression(value, context, options);
73
97
  }
74
98
  return out;
75
99
  }
76
100
 
77
- export { evaluateConfig, evaluateExpression, resolvePath, text, truthy };
78
- //# sourceMappingURL=chunk-BHM5HGXH.js.map
79
- //# sourceMappingURL=chunk-BHM5HGXH.js.map
101
+ export { UnresolvedPathError, evaluateConfig, evaluateExpression, resolvePath, text, truthy, tryResolvePath };
102
+ //# sourceMappingURL=chunk-UM4C46AF.js.map
103
+ //# sourceMappingURL=chunk-UM4C46AF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/expressions/expr.ts"],"names":[],"mappings":"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","file":"chunk-UM4C46AF.js","sourcesContent":["/**\n * `{{ }}` resolution — the TypeScript twin of `FancyFlow\\Nodes\\Support\\Expr`.\n *\n * ## Why this is opt-in and not wired into `runFlow`\n *\n * The PHP runtime resolves expressions inside its batteries-included executors.\n * The JS runtime never has: `runFlow` hands `node.data.config` to your executor\n * verbatim, so every host that uses `{{ }}` today already resolves it itself.\n * Turning resolution on inside `runFlow` would interpolate a second time over\n * values those hosts had already substituted — silently, and only for graphs\n * that happen to contain `{{` in their DATA.\n *\n * So this exports the semantics and changes no behaviour. Call it yourself:\n *\n * ```ts\n * const url = evaluateExpression(node.data.config.url, inputs);\n * ```\n *\n * ## The semantics are the PHP file's, deliberately\n *\n * This is not a general expression language and must not grow into one. It\n * resolves a dot-path against a context and nothing else — no arithmetic, no\n * comparisons, no calls. Hosts that want real expressions override the executor\n * (the PHP docblock points at symfony/expression-language for the same reason).\n *\n * Divergence here is a correctness bug rather than a style difference: the same\n * graph is authored once and may run on either runtime. `suites/shared/expr`\n * in `@particle-academy/fancy-conformance` is the fixture table both sides run,\n * so parity is a test result instead of a claim.\n */\n\n/** Anything a context or a resolved value can be. */\nexport type ExprValue = unknown;\n\n/** The context an expression resolves against — the executor's `inputs`. */\nexport type ExprContext = Record<string, ExprValue>;\n\n/**\n * `{{ … }}` is parsed by SCANNING, not by a regular expression.\n *\n * Two CodeQL `js/polynomial-redos` alerts (#2, #3) came out of the regex\n * version, and the second survived the obvious fix — which is the useful part.\n * Dropping the ambiguous `\\s*` around the capture killed one witness\n * (`'{{{{' + ' '.repeat(n)`) and CodeQL immediately produced another\n * (`'{{{{a'` repeated). That is not a pattern bug: a GLOBAL lazy scan for a\n * delimiter that never arrives is quadratic by construction — O(n) starts, each\n * scanning O(n) forward — and no amount of tuning the pattern removes it.\n *\n * `indexOf` has no backtracking at all. Each character is visited a bounded\n * number of times, so this is linear by construction rather than by careful\n * pattern-writing, and there is no next witness to find.\n *\n * The behaviour is deliberately identical to the regexes it replaces, including\n * the odd corner: `{{a}}{{b}}` is a WHOLE expression whose path is `a}}{{b`\n * (which resolves to null), because the old pattern was `$`-anchored and its\n * lazy capture had to grow to reach the end. The PHP twin does the same, and\n * `shared/expr` is what holds the two together.\n */\n\n/** The inner text of a template that is exactly one expression, else `null`. */\nfunction wholeExpression(trimmed: string): string | null {\n if (trimmed.length < 4) return null;\n if (!trimmed.startsWith(\"{{\") || !trimmed.endsWith(\"}}\")) return null;\n return trimmed.slice(2, -2);\n}\n\n/**\n * Replace every `{{ … }}` run, left to right, in a single pass.\n *\n * An unterminated `{{` is literal text — the same thing the regex did by simply\n * not matching, and the case an author hits constantly while typing.\n */\nfunction interpolate(template: string, resolve: (path: string) => string): string {\n let out = \"\";\n let i = 0;\n\n for (;;) {\n const open = template.indexOf(\"{{\", i);\n if (open === -1) return out + template.slice(i);\n\n const close = template.indexOf(\"}}\", open + 2);\n if (close === -1) return out + template.slice(i);\n\n out += template.slice(i, open) + resolve(template.slice(open + 2, close));\n i = close + 2;\n }\n}\n\n/**\n * Strings PHP's `Expr::truthy` treats as false.\n *\n * `\"0\"` and `\"false\"` are the ones that matter: a branch condition arriving as\n * text from a form or a JSON body would otherwise be truthy for every non-empty\n * string, and `\"false\"` taking the true branch is the kind of bug that looks\n * like the engine is broken.\n */\nconst FALSY_STRINGS = new Set([\"\", \"0\", \"false\", \"no\", \"off\", \"null\"]);\n\n/**\n * What a resolution attempt ANSWERS — did the path resolve, and to what.\n *\n * `resolvePath` cannot express this, and that is the defect it exists to fix:\n * it returns `null` both for \"this path does not exist\" and for \"this path\n * exists and holds null\". One value standing for two states.\n *\n * At the interpolation layer the collapse is worse, because `null` stringifies\n * to `\"\"`. A consumer put it exactly:\n *\n * > \"An unresolvable path yields `''`, so a wrong field is indistinguishable\n * > from an empty one at runtime.\"\n *\n * A misspelled field renders as an empty string, which looks precisely like a\n * field that is legitimately empty. The graph runs, the node succeeds, and the\n * output is quietly missing a value nobody is told about — worst on\n * LLM-authored graphs, where the field name was guessed in the first place.\n *\n * Same shape as the four `??` collapses fixed across all four runtimes on\n * 2026-08-26 (absent vs null), one layer up: **presence is the only correct\n * test, and a return value that cannot express presence cannot be tested for\n * it.** Hence a second return channel rather than a cleverer sentinel — every\n * sentinel is a legal value for somebody.\n */\nexport interface Resolution {\n /** Whether the path resolved at all. `false` means it does not exist. */\n resolved: boolean;\n /** The value, when `resolved`. `null` when not — do not read it blind. */\n value: ExprValue;\n}\n\n/** Thrown by the `\"throw\"` policy when a path does not resolve. */\nexport class UnresolvedPathError extends Error {\n constructor(readonly path: string) {\n super(\n `Expression path \"${path}\" did not resolve. ` +\n `Under the \"throw\" policy an unresolvable path is an error rather than an empty string.`,\n );\n this.name = \"UnresolvedPathError\";\n }\n}\n\n/**\n * What evaluation does with a path that does not resolve.\n *\n * - `\"empty\"` — today's behaviour, and the DEFAULT. Interpolates to `\"\"`; a\n * whole expression yields `null`. Unchanged so that widening this API breaks\n * nobody: 40 call sites across the runtimes assume it.\n * - `\"keep\"` — leave the `{{ … }}` text in place. The failure becomes VISIBLE\n * in the output without stopping the run, which is what you want for\n * human-reviewed content: a rendered `{{ in.recipient_naem }}` is self-\n * diagnosing in a way an absence never is.\n * - `\"throw\"` — refuse. For hosts that would rather fail a run than deliver a\n * silently incomplete result.\n *\n * Opt-in before default at the request of the consumer who reported it — the\n * host with the most LLM-authored graphs, and so both the biggest beneficiary\n * and the right place for it to break first if it is going to.\n */\nexport type UnresolvedPolicy = \"empty\" | \"keep\" | \"throw\";\n\n/** Options for {@link evaluateExpression} / {@link evaluateConfig}. */\nexport interface EvaluateOptions {\n /** What to do with a path that does not resolve. Default `\"empty\"`. */\n onUnresolved?: UnresolvedPolicy;\n}\n\n/**\n * Resolve a dot-path, reporting WHETHER it resolved.\n *\n * The same walk as `resolvePath` — deliberately, so the two can never disagree\n * about what resolves; `resolvePath` is defined in terms of this one below.\n *\n * A note on JS having two absent values: a key present with the value\n * `undefined` reports `resolved: false`, matching `resolvePath`'s long-standing\n * behaviour. It cannot arise from graph data (JSON has no `undefined`), and\n * changing it would make the two functions disagree for no reachable gain.\n */\nexport function tryResolvePath(path: string, context: ExprContext): Resolution {\n const unresolved: Resolution = { resolved: false, value: null };\n\n const trimmed = path.trim();\n if (trimmed === \"\") return unresolved;\n\n const segments = trimmed.split(\".\");\n let cursor: ExprValue;\n\n const head = segments[0];\n if (head === \"$json\" || head === \"$input\") {\n cursor = context !== null && typeof context === \"object\" && \"in\" in context ? context.in : context;\n segments.shift();\n } else {\n cursor = context;\n }\n\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return unresolved;\n if (typeof cursor !== \"object\") return unresolved;\n // Arrays are indexed by their numeric keys, matching PHP's list access.\n const next = (cursor as Record<string, ExprValue>)[segment];\n if (next === undefined) return unresolved;\n cursor = next;\n }\n\n return { resolved: true, value: cursor === undefined ? null : cursor };\n}\n\n/**\n * Resolve a dot-path against the context, honouring the `$json` / `$input`\n * alias.\n *\n * Both aliases point at the `in` port value when the context has one, and at\n * the whole context otherwise — the same fallback the PHP does, which is what\n * makes `{{ $json.x }}` work on a trigger node that has no upstream input.\n *\n * A path that does not resolve returns `null`, never `undefined`: PHP has one\n * absent value and JS has two, and letting the difference leak would make the\n * two runtimes disagree about `{{ missing }}` for no useful reason.\n */\nexport function resolvePath(path: string, context: ExprContext): ExprValue {\n // Defined in terms of `tryResolvePath` rather than repeating the walk. Two\n // copies of a traversal agree right up until someone edits one of them, and\n // nothing anywhere reports that -- the same reason the shared conformance\n // tables exist instead of hand-copied fixture rows.\n return tryResolvePath(path, context).value;\n}\n\n/**\n * Evaluate a template against a context.\n *\n * A string that is EXACTLY one expression returns the resolved value with its\n * type intact — `{{ $json.count }}` gives you a number, not `\"3\"`. Anything\n * else interpolates each run as text. That distinction is load-bearing: it is\n * what lets a config field carry either a value or a sentence.\n *\n * Non-string templates pass through untouched, so this is safe to map over a\n * whole config object.\n */\nexport function evaluateExpression(\n template: ExprValue,\n context: ExprContext,\n options: EvaluateOptions = {},\n): ExprValue {\n if (typeof template !== \"string\") return template;\n\n const policy = options.onUnresolved ?? \"empty\";\n\n const whole = wholeExpression(template.trim());\n if (whole !== null) {\n const r = tryResolvePath(whole, context);\n if (r.resolved) return r.value;\n\n // The whole-expression branch returns `null` under `\"empty\"`, not `\"\"` --\n // that is what it has always done, and the asymmetry is deliberate: this\n // branch preserves TYPE, so its absent value is the typed one.\n if (policy === \"throw\") throw new UnresolvedPathError(whole);\n return policy === \"keep\" ? template : null;\n }\n\n return interpolate(template, (path) => {\n const r = tryResolvePath(path, context);\n if (r.resolved) return stringify(r.value);\n if (policy === \"throw\") throw new UnresolvedPathError(path);\n return policy === \"keep\" ? `{{${path}}}` : \"\";\n });\n}\n\n/**\n * Truthiness for branch / switch decisions.\n *\n * Mirrors PHP's rules rather than JavaScript's, because the graph is authored\n * once and may run on either side. The two disagree in exactly the places a\n * workflow hits: `\"0\"` and `\"false\"` are truthy in JS and falsy here, and an\n * empty array is truthy in JS and falsy here.\n */\nexport function truthy(value: ExprValue): boolean {\n if (typeof value === \"boolean\") return value;\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return !FALSY_STRINGS.has(value.trim().toLowerCase());\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === \"number\") return value !== 0;\n return Boolean(value);\n}\n\n/** Coerce a value to text the way interpolation does. */\nexport function text(value: ExprValue): string {\n return stringify(value);\n}\n\nfunction stringify(value: ExprValue): string {\n if (typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"number\" || typeof value === \"bigint\") return String(value);\n try {\n return JSON.stringify(value) ?? \"\";\n } catch {\n return \"\";\n }\n}\n\n/**\n * Resolve every string in a config object, one level of nesting at a time.\n *\n * The convenience most hosts actually want, and the shape their hand-rolled\n * version usually takes. Opt-in like the rest of this module.\n */\nexport function evaluateConfig<T extends Record<string, ExprValue>>(\n config: T,\n context: ExprContext,\n options: EvaluateOptions = {},\n): T {\n const out: Record<string, ExprValue> = {};\n for (const [key, value] of Object.entries(config)) {\n out[key] = Array.isArray(value)\n ? value.map((v) => evaluateExpression(v, context, options))\n : value !== null && typeof value === \"object\"\n ? evaluateConfig(value as Record<string, ExprValue>, context, options)\n : evaluateExpression(value, context, options);\n }\n return out as T;\n}\n"]}
@@ -1,4 +1,5 @@
1
- import { resolveNodePorts, getNodeKind, defaultConfigFor, validateConfig } from './chunk-HBE4LVPX.js';
1
+ import { checkGraphConnectivity } from './chunk-77V4QC6Y.js';
2
+ import { resolveNodePorts, getNodeKind, defaultConfigFor, validateConfig } from './chunk-JF6WCRBU.js';
2
3
 
3
4
  // src/schema/workflow-schema.ts
4
5
  var WORKFLOW_SCHEMA_VERSION = 1;
@@ -147,6 +148,7 @@ function importWorkflow(schema, options = {}) {
147
148
  label: e.label
148
149
  };
149
150
  }).filter((e) => e !== null);
151
+ issues.push(...checkGraphConnectivity({ nodes, edges }));
150
152
  const ok = issues.every((i) => i.level !== "error");
151
153
  const declaredInputs = Array.isArray(s.inputs) ? s.inputs.filter(
152
154
  (input) => input !== null && typeof input === "object" && typeof input.name === "string"
@@ -166,5 +168,5 @@ function workflowToBlob(schema) {
166
168
  }
167
169
 
168
170
  export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob };
169
- //# sourceMappingURL=chunk-FGYG5JVW.js.map
170
- //# sourceMappingURL=chunk-FGYG5JVW.js.map
171
+ //# sourceMappingURL=chunk-W5DPKJY4.js.map
172
+ //# sourceMappingURL=chunk-W5DPKJY4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"chunk-W5DPKJY4.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode, PortDescriptor, WorkflowInput } from \"../types\";\nimport { checkGraphConnectivity } from \"../analysis/graph-connectivity\";\nimport { defaultConfigFor, getNodeKind, validateConfig } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\n\n/** Schema version. Bump on breaking shape changes; add migrations as needed. */\nexport const WORKFLOW_SCHEMA_VERSION = 1 as const;\nexport const WORKFLOW_SCHEMA_URL = \"https://particle.academy/schemas/workflow/v1.json\";\n\nexport type WorkflowSchema = {\n $schema: typeof WORKFLOW_SCHEMA_URL;\n version: typeof WORKFLOW_SCHEMA_VERSION;\n metadata?: WorkflowMetadata;\n /**\n * What this workflow accepts at run start, passed BY NAME.\n *\n * Lives beside `graph` rather than inside it because it describes the\n * workflow's CONTRACT, not its topology: a caller reads this to know what to\n * pass, and reading it should not mean walking nodes looking for a trigger.\n *\n * Omitted entirely when a workflow takes none, so every existing saved\n * document is unchanged byte for byte.\n */\n inputs?: WorkflowInput[];\n graph: {\n nodes: WorkflowSchemaNode[];\n edges: WorkflowSchemaEdge[];\n };\n view?: {\n viewport?: { x: number; y: number; zoom: number };\n };\n};\n\nexport type WorkflowMetadata = {\n id?: string;\n name?: string;\n description?: string;\n createdAt?: number;\n updatedAt?: number;\n author?: string;\n tags?: string[];\n};\n\nexport type WorkflowSchemaNode = {\n id: string;\n /** Registry kind name (e.g. \"memory_store\"). */\n kind: string;\n position: { x: number; y: number };\n label?: string;\n description?: string;\n /**\n * Announced to a person just before / just after this node runs. Omitted\n * entirely when unset, so a graph of ordinary plumbing nodes does not carry\n * a pair of empty keys per node and every diff of a saved graph stays\n * readable.\n */\n startingMsg?: string;\n stoppingMsg?: string;\n config?: Record<string, unknown>;\n /**\n * Resolved ports, written on export.\n *\n * A kind may derive its ports from config (`switch_case` cases,\n * `llm_branch` routes), and that derivation is a JavaScript function — a\n * runtime in another language cannot execute it. Without the resolved ports\n * in the document, the PHP twin sees no declared outputs and falls back to a\n * single `out`, so every branch edge in an exported flow silently stops\n * firing. Serializing them keeps the schema self-describing and preserves\n * the cross-runtime guarantee: same JSON in, same routing out.\n *\n * Optional and additive — a hand-written schema may omit them, and each\n * runtime then falls back to its own kind registry.\n */\n inputs?: PortDescriptor[];\n outputs?: PortDescriptor[];\n /**\n * Visual layout — additive + optional. `parentId`/`extent` carry node grouping\n * (swimlanes / containers); `width`/`height` carry an explicit (resized) size;\n * `style` carries inline presentation. A runtime that only walks edges/ports\n * (e.g. the PHP twin) ignores all of these — they exist purely for the canvas,\n * so an older reader that doesn't know them simply drops them.\n */\n parentId?: string;\n extent?: \"parent\" | [[number, number], [number, number]];\n width?: number;\n height?: number;\n style?: Record<string, unknown>;\n};\n\nexport type WorkflowSchemaEdge = {\n id: string;\n source: string;\n target: string;\n sourceHandle?: string;\n targetHandle?: string;\n label?: string;\n};\n\nexport type ImportIssue = {\n level: \"error\" | \"warning\";\n nodeId?: string;\n edgeId?: string;\n message: string;\n};\n\nexport type ImportResult = {\n graph: FlowGraph;\n issues: ImportIssue[];\n /** True when the import produced a usable graph (errors may have been\n * rewritten to warnings via `lenient: true`). */\n ok: boolean;\n};\n\n/** Snapshot the in-memory graph as a portable WorkflowSchema. */\nexport function exportWorkflow(\n graph: FlowGraph,\n metadata?: WorkflowMetadata,\n view?: WorkflowSchema[\"view\"],\n): WorkflowSchema {\n return {\n $schema: WORKFLOW_SCHEMA_URL,\n version: WORKFLOW_SCHEMA_VERSION,\n metadata: metadata ? { ...metadata, updatedAt: Date.now() } : undefined,\n // Round-trips, or a saved workflow silently forgets its own contract and\n // every caller's props become \"unknown input\" on the next load.\n ...(graph.inputs && graph.inputs.length > 0 ? { inputs: graph.inputs } : {}),\n graph: {\n nodes: graph.nodes.map(toSchemaNode),\n edges: graph.edges.map(toSchemaEdge),\n },\n view,\n };\n}\n\nfunction toSchemaNode(n: FlowNode): WorkflowSchemaNode {\n const data: any = n.data ?? {};\n const kindName = data.kind ?? n.type ?? \"custom\";\n // Resolve through the same helper the canvas and runtime use, so a\n // config-driven kind writes its ACTUAL ports into the document instead of\n // leaving another language's runtime to guess at them.\n const ports = resolveNodePorts(n, getNodeKind(kindName) ?? undefined);\n const node = n as any;\n return {\n id: n.id,\n kind: kindName,\n position: { x: n.position.x, y: n.position.y },\n label: data.label,\n description: data.description,\n ...(typeof data.startingMsg === \"string\" && data.startingMsg !== \"\" ? { startingMsg: data.startingMsg } : {}),\n ...(typeof data.stoppingMsg === \"string\" && data.stoppingMsg !== \"\" ? { stoppingMsg: data.stoppingMsg } : {}),\n config: data.config,\n inputs: ports.inputs,\n outputs: ports.outputs,\n // Visual layout — only when explicitly set (never persist auto-`measured`\n // dimensions, which are derived, not authored).\n ...(node.parentId ? { parentId: node.parentId } : {}),\n ...(node.extent ? { extent: node.extent } : {}),\n ...(typeof node.width === \"number\" ? { width: node.width } : {}),\n ...(typeof node.height === \"number\" ? { height: node.height } : {}),\n ...(node.style ? { style: node.style } : {}),\n };\n}\n\nfunction toSchemaEdge(e: FlowEdge): WorkflowSchemaEdge {\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle ?? undefined,\n targetHandle: e.targetHandle ?? undefined,\n label: typeof e.label === \"string\" ? e.label : undefined,\n };\n}\n\nexport type ImportOptions = {\n /** When true, unknown kinds become warnings + a \"custom\" placeholder\n * instead of errors. Default false. */\n lenient?: boolean;\n};\n\n/** A migration step: takes a version-N document to version N+1. */\nexport type MigrationStep = (schema: Record<string, unknown>) => Record<string, unknown>;\n\n/**\n * Every migration step, keyed by the version it upgrades FROM.\n *\n * Empty today because v1 is current — when a BREAKING bump lands, add the step\n * here and every stored document upgrades on read, in this runtime and its\n * PHP and Python twins.\n */\nexport const MIGRATIONS: Record<number, MigrationStep> = {};\n\n/**\n * Migrate a raw schema object up to the current version, as far as it can go.\n *\n * Additive changes need no migration — an older document simply lacks the newer\n * optional fields. This is the seam for a future BREAKING bump, and\n * `importWorkflow` runs it before validating the version.\n *\n * ## The three rules, each with a reason\n *\n * - **A PAST version migrates forward**, step by step, to the current one.\n * - **A FUTURE version is left ALONE.** We cannot know what a later schema\n * means, and migrating downward would be guessing. Untouched hands it to the\n * version check, which reports it honestly.\n * - **A GAP in the table is left alone too.** A missing step is not a licence\n * to guess.\n *\n * ## Why `steps` is a parameter\n *\n * With only v1 in existence there is no old document to migrate, so a seam\n * tested against the built-in (empty) table is a check that CANNOT fail — it\n * would pass identically against a function that returned its input untouched,\n * which is what this was until the twins needed the same seam.\n *\n * The PHP and Python runtimes carry the identical shape. That mattered more\n * than it sounds: they had no migration at all and errored on any version\n * mismatch, so a v2 bump would have made every stored Op unreadable on exactly\n * the runtimes where durable runs resume.\n */\nexport function migrateSchema(\n schema: unknown,\n steps: Record<number, MigrationStep> = MIGRATIONS,\n): unknown {\n if (!schema || typeof schema !== \"object\") return schema;\n\n const doc = schema as Record<string, unknown>;\n let version = doc.version;\n\n if (typeof version !== \"number\" || !Number.isInteger(version) || version >= WORKFLOW_SCHEMA_VERSION) {\n return schema;\n }\n\n let out = doc;\n while (version < WORKFLOW_SCHEMA_VERSION) {\n const step = steps[version];\n if (!step) return out;\n\n out = step(out);\n version += 1;\n out.version = version;\n }\n\n return out;\n}\n\n/**\n * Hydrate a schema into runtime FlowGraph + validate kinds/configs against\n * the registry. Reports issues for unknown kinds, missing required config,\n * and dangling edges.\n */\nexport function importWorkflow(schema: unknown, options: ImportOptions = {}): ImportResult {\n const issues: ImportIssue[] = [];\n const lenient = options.lenient === true;\n schema = migrateSchema(schema);\n\n if (!schema || typeof schema !== \"object\") {\n return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: \"error\", message: \"Schema is not an object.\" }] };\n }\n const s = schema as Partial<WorkflowSchema>;\n if (s.version !== WORKFLOW_SCHEMA_VERSION) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`,\n });\n if (!lenient) return { ok: false, graph: { nodes: [], edges: [] }, issues };\n }\n\n const rawNodes = s.graph?.nodes ?? [];\n const rawEdges = s.graph?.edges ?? [];\n\n const nodes: FlowNode[] = rawNodes.map((n) => {\n const kind = getNodeKind(n.kind);\n if (!kind) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n nodeId: n.id,\n message: `Unknown kind \"${n.kind}\" — register it before importing.`,\n });\n }\n const config = n.config ?? (kind ? defaultConfigFor(kind) : {});\n if (kind) {\n for (const iss of validateConfig(kind, config)) {\n issues.push({ level: \"warning\", nodeId: n.id, message: `${iss.key}: ${iss.message}` });\n }\n }\n // Canonicalise on the way in: a document may carry a pre-namespace id, and\n // rewriting it here means the graph converges on the canonical id the next\n // time it is saved, rather than carrying the ambiguous name forever.\n const kindId = kind?.name ?? n.kind;\n return {\n id: n.id,\n type: kindId,\n position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },\n // Rehydrate visual layout onto the node's top level (where xyflow reads it).\n ...(n.parentId ? { parentId: n.parentId } : {}),\n ...(n.extent ? { extent: n.extent } : {}),\n ...(typeof n.width === \"number\" ? { width: n.width } : {}),\n ...(typeof n.height === \"number\" ? { height: n.height } : {}),\n ...(n.style ? { style: n.style } : {}),\n data: {\n kind: kindId,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\n ...(n.startingMsg ? { startingMsg: n.startingMsg } : {}),\n ...(n.stoppingMsg ? { stoppingMsg: n.stoppingMsg } : {}),\n config,\n // Carry serialized ports back onto the node so a round-trip is stable\n // and an unknown kind still routes the way the document described.\n ...(n.inputs ? { inputs: n.inputs } : {}),\n ...(n.outputs ? { outputs: n.outputs } : {}),\n } as any,\n };\n });\n\n const nodeIds = new Set(nodes.map((n) => n.id));\n const edges: FlowEdge[] = rawEdges\n .map((e) => {\n if (!nodeIds.has(e.source)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge source \"${e.source}\" not found.` });\n return null;\n }\n if (!nodeIds.has(e.target)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge target \"${e.target}\" not found.` });\n return null;\n }\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle,\n targetHandle: e.targetHandle,\n label: e.label,\n } as FlowEdge;\n })\n .filter((e): e is FlowEdge => e !== null);\n\n // WIRING, not merely dataflow: a node no edge reaches and that reaches no\n // edge, and an edge reading from a node that publishes nothing.\n //\n // Deliberately AFTER the edge loop, so it sees the same edges the engine will\n // -- a dangling edge is dropped with a warning above, and running this first\n // would let a dropped edge count as a connection.\n //\n // Deliberately NOT gated on `lenient`. That flag is about unknown VOCABULARY\n // (a kind this host has not registered), never about wiring.\n issues.push(...checkGraphConnectivity({ nodes, edges }));\n\n const ok = issues.every((i) => i.level !== \"error\");\n // The declaration comes back with the graph. Dropping it on import would be\n // the worse half of losing it on export: the document still says what it\n // accepts, the loaded graph does not, and every prop a caller passes becomes\n // \"unknown input\" — an error pointing at the caller for a bug in the loader.\n const declaredInputs = Array.isArray(s.inputs)\n ? (s.inputs as WorkflowInput[]).filter(\n (input): input is WorkflowInput =>\n input !== null && typeof input === \"object\" && typeof (input as WorkflowInput).name === \"string\",\n )\n : undefined;\n\n return {\n ok,\n graph: {\n nodes,\n edges,\n ...(declaredInputs && declaredInputs.length > 0 ? { inputs: declaredInputs } : {}),\n },\n issues,\n };\n}\n\n/** Convenience: serialize a schema as a downloadable JSON Blob. */\nexport function workflowToBlob(schema: WorkflowSchema): Blob {\n return new Blob([JSON.stringify(schema, null, 2)], { type: \"application/json\" });\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { C as ConfigField, N as NodeKindDefinition, O as OutputField } from './types-BLVpNNLN.cjs';
1
+ import { C as ConfigField, N as NodeKindDefinition, O as OutputField } from './types-D71SKA5A.cjs';
2
2
  import 'react';
3
3
  import './types-JFYjPJAG.cjs';
4
4
  import '@xyflow/react';
@@ -1,4 +1,4 @@
1
- import { C as ConfigField, N as NodeKindDefinition, O as OutputField } from './types-CLSUZW8c.js';
1
+ import { C as ConfigField, N as NodeKindDefinition, O as OutputField } from './types-Ckhz-YwC.js';
2
2
  import 'react';
3
3
  import './types-JFYjPJAG.js';
4
4
  import '@xyflow/react';
package/dist/durable.cjs CHANGED
@@ -9634,7 +9634,7 @@ function activatedPorts(node, result) {
9634
9634
  return { ports: [r.__port], value: r.value };
9635
9635
  }
9636
9636
  if (typeof r.branch === "string") {
9637
- return { ports: [r.branch], value: r.value ?? r };
9637
+ return { ports: [r.branch], value: Object.prototype.hasOwnProperty.call(r, "value") ? r.value : r };
9638
9638
  }
9639
9639
  }
9640
9640
  const kind = getNodeKind(node.data?.kind ?? node.type ?? "") ?? void 0;
@@ -9796,8 +9796,9 @@ var KINDS = [
9796
9796
  // ───────────── Triggers ─────────────
9797
9797
  {
9798
9798
  name: "@particle-academy/manual_trigger",
9799
- // returns the inputs it was started with
9800
- emits: "input",
9799
+ // Returns the raw inputs MAP, not the `in` port -- flat at an entry
9800
+ // point, port-keyed the moment the node has an inbound edge.
9801
+ emits: "input-map-merged",
9801
9802
  aliases: ["manual_trigger", "@fancy/manual_trigger"],
9802
9803
  category: "trigger",
9803
9804
  label: "Manual",
@@ -9830,8 +9831,9 @@ var KINDS = [
9830
9831
  { path: "cron", type: "string", description: "The cron expression that fired." },
9831
9832
  { path: "timezone", type: "string", description: "The timezone it was evaluated in." }
9832
9833
  ],
9833
- // merges its inputs into the TOP level beside cron/timezone
9834
- emits: "inputs-merged",
9834
+ // Merges $ctx->inputs ITSELF beside cron/timezone -- the MAP, whose
9835
+ // shape depends on position, not each port's payload the way `merge` does.
9836
+ emits: "input-map-merged",
9835
9837
  aliases: ["schedule_trigger", "@fancy/schedule_trigger"],
9836
9838
  category: "trigger",
9837
9839
  label: "Schedule",