@particle-academy/fancy-flow 0.2.2
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.
- package/README.md +106 -0
- package/dist/chunk-BCXECQUC.js +110 -0
- package/dist/chunk-BCXECQUC.js.map +1 -0
- package/dist/chunk-JNYYJHNJ.js +247 -0
- package/dist/chunk-JNYYJHNJ.js.map +1 -0
- package/dist/chunk-WNVBXXOL.js +101 -0
- package/dist/chunk-WNVBXXOL.js.map +1 -0
- package/dist/chunk-XZEUFVJ2.js +507 -0
- package/dist/chunk-XZEUFVJ2.js.map +1 -0
- package/dist/index.cjs +1677 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +226 -0
- package/dist/index.d.ts +226 -0
- package/dist/index.js +705 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/index.d.cts +170 -0
- package/dist/registry/index.d.ts +170 -0
- package/dist/registry.cjs +615 -0
- package/dist/registry.cjs.map +1 -0
- package/dist/registry.js +4 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime/index.d.cts +93 -0
- package/dist/runtime/index.d.ts +93 -0
- package/dist/runtime.cjs +252 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.js +3 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema/index.d.cts +81 -0
- package/dist/schema/index.d.ts +81 -0
- package/dist/schema.cjs +170 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.js +4 -0
- package/dist/schema.js.map +1 -0
- package/dist/styles.css +690 -0
- package/dist/styles.css.map +1 -0
- package/dist/types-TemTtb04.d.cts +103 -0
- package/dist/types-TemTtb04.d.ts +103 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @particle-academy/fancy-flow
|
|
2
|
+
|
|
3
|
+
Workflow editor + runner built on [React Flow](https://reactflow.dev/). Six built-in node kits, tokenized theme, topological execution with per-node status events.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @particle-academy/fancy-flow @xyflow/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import "@xyflow/react/dist/style.css";
|
|
13
|
+
import "@particle-academy/fancy-flow/styles.css";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { FlowCanvas, useFlowState, useFlowRun, applyStatusesToNodes, FlowRunControls, FlowRunFeed } from "@particle-academy/fancy-flow";
|
|
20
|
+
import type { ExecutorRegistry, FlowGraph } from "@particle-academy/fancy-flow";
|
|
21
|
+
|
|
22
|
+
const initial: FlowGraph = {
|
|
23
|
+
nodes: [
|
|
24
|
+
{ id: "t", type: "trigger", position: { x: 0, y: 0 }, data: { kind: "trigger", label: "Manual" } },
|
|
25
|
+
{ id: "a", type: "action", position: { x: 240, y: 0 }, data: { kind: "action", label: "Fetch user" } },
|
|
26
|
+
{ id: "d", type: "decision", position: { x: 480, y: 0 }, data: { kind: "decision", label: "Active?" } },
|
|
27
|
+
{ id: "ok", type: "output", position: { x: 720, y: -60 }, data: { kind: "output", label: "Allow" } },
|
|
28
|
+
{ id: "no", type: "output", position: { x: 720, y: 80 }, data: { kind: "output", label: "Deny" } },
|
|
29
|
+
],
|
|
30
|
+
edges: [
|
|
31
|
+
{ id: "e1", source: "t", target: "a" },
|
|
32
|
+
{ id: "e2", source: "a", target: "d" },
|
|
33
|
+
{ id: "e3", source: "d", sourceHandle: "true", target: "ok" },
|
|
34
|
+
{ id: "e4", source: "d", sourceHandle: "false", target: "no" },
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const executors: ExecutorRegistry = {
|
|
39
|
+
trigger: () => ({ now: Date.now() }),
|
|
40
|
+
action: async () => ({ id: 1, active: true }),
|
|
41
|
+
decision: ({ inputs }) => ({ branch: (inputs.in as any)?.active ? "true" : "false" }),
|
|
42
|
+
output: ({ inputs }) => inputs.in,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function MyEditor() {
|
|
46
|
+
const flow = useFlowState(initial);
|
|
47
|
+
const runner = useFlowRun();
|
|
48
|
+
const renderedNodes = applyStatusesToNodes(flow.nodes, runner.statuses, runner.statusText);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 360px", gap: 16 }}>
|
|
52
|
+
<FlowCanvas
|
|
53
|
+
nodes={renderedNodes}
|
|
54
|
+
edges={flow.edges}
|
|
55
|
+
onNodesChange={flow.onNodesChange}
|
|
56
|
+
onEdgesChange={flow.onEdgesChange}
|
|
57
|
+
onConnect={flow.onConnect}
|
|
58
|
+
toolbar={<FlowRunControls running={runner.running} onRun={() => runner.run(flow.toGraph(), executors)} onCancel={runner.cancel} onReset={runner.reset} />}
|
|
59
|
+
/>
|
|
60
|
+
<FlowRunFeed entries={runner.feed} />
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Node kit (v0.1)
|
|
67
|
+
|
|
68
|
+
| Kind | Purpose | Default ports |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `trigger` | Entry point | outputs only (`out`) |
|
|
71
|
+
| `action` | Work-doing node | `in` → `out` |
|
|
72
|
+
| `decision` | Branching | `in` → `true` / `false` (configurable) |
|
|
73
|
+
| `output` | Terminal | `in` only |
|
|
74
|
+
| `note` | Annotation | none |
|
|
75
|
+
| `subgraph` | Collapse a group | facade ports |
|
|
76
|
+
|
|
77
|
+
Custom nodes plug in via xyflow's standard `nodeTypes` prop:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<FlowCanvas nodeTypes={{ ...defaultNodeTypes, myNode: MyCustomNode }} ... />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Runtime
|
|
84
|
+
|
|
85
|
+
`runFlow(graph, executors, onEvent?, options?)` does a topological walk:
|
|
86
|
+
|
|
87
|
+
- Each node fires once when all upstream connected ports have produced values.
|
|
88
|
+
- Decision-style nodes can return `{ branch: "true" }` or `{ __port: "out", value }` to activate specific output ports — only edges leaving an active port propagate.
|
|
89
|
+
- Cycles abort the run.
|
|
90
|
+
- `onEvent` receives `RunEvent`s for status, output, log, run-start/end.
|
|
91
|
+
|
|
92
|
+
`useFlowRun` wraps `runFlow` with React state for statuses, status text, and a feed log.
|
|
93
|
+
|
|
94
|
+
## Status
|
|
95
|
+
|
|
96
|
+
`v0.1` — editor + runner + node kit. Roadmap:
|
|
97
|
+
|
|
98
|
+
- Subgraph expand/collapse interactions
|
|
99
|
+
- Edge labels (config metadata)
|
|
100
|
+
- Auto-layout (`dagre` integration)
|
|
101
|
+
- Persistence helpers (zod schema)
|
|
102
|
+
- Agent bridge (in `@particle-academy/agent-integrations` — coming next)
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getNodeKind, defaultConfigFor, validateConfig } from './chunk-WNVBXXOL.js';
|
|
2
|
+
|
|
3
|
+
// src/schema/workflow-schema.ts
|
|
4
|
+
var WORKFLOW_SCHEMA_VERSION = 1;
|
|
5
|
+
var WORKFLOW_SCHEMA_URL = "https://particle.academy/schemas/workflow/v1.json";
|
|
6
|
+
function exportWorkflow(graph, metadata, view) {
|
|
7
|
+
return {
|
|
8
|
+
$schema: WORKFLOW_SCHEMA_URL,
|
|
9
|
+
version: WORKFLOW_SCHEMA_VERSION,
|
|
10
|
+
metadata: metadata ? { ...metadata, updatedAt: Date.now() } : void 0,
|
|
11
|
+
graph: {
|
|
12
|
+
nodes: graph.nodes.map(toSchemaNode),
|
|
13
|
+
edges: graph.edges.map(toSchemaEdge)
|
|
14
|
+
},
|
|
15
|
+
view
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function toSchemaNode(n) {
|
|
19
|
+
const data = n.data ?? {};
|
|
20
|
+
return {
|
|
21
|
+
id: n.id,
|
|
22
|
+
kind: data.kind ?? n.type ?? "custom",
|
|
23
|
+
position: { x: n.position.x, y: n.position.y },
|
|
24
|
+
label: data.label,
|
|
25
|
+
description: data.description,
|
|
26
|
+
config: data.config
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function toSchemaEdge(e) {
|
|
30
|
+
return {
|
|
31
|
+
id: e.id,
|
|
32
|
+
source: e.source,
|
|
33
|
+
target: e.target,
|
|
34
|
+
sourceHandle: e.sourceHandle ?? void 0,
|
|
35
|
+
targetHandle: e.targetHandle ?? void 0,
|
|
36
|
+
label: typeof e.label === "string" ? e.label : void 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function importWorkflow(schema, options = {}) {
|
|
40
|
+
const issues = [];
|
|
41
|
+
const lenient = options.lenient === true;
|
|
42
|
+
if (!schema || typeof schema !== "object") {
|
|
43
|
+
return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: "error", message: "Schema is not an object." }] };
|
|
44
|
+
}
|
|
45
|
+
const s = schema;
|
|
46
|
+
if (s.version !== WORKFLOW_SCHEMA_VERSION) {
|
|
47
|
+
issues.push({
|
|
48
|
+
level: lenient ? "warning" : "error",
|
|
49
|
+
message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`
|
|
50
|
+
});
|
|
51
|
+
if (!lenient) return { ok: false, graph: { nodes: [], edges: [] }, issues };
|
|
52
|
+
}
|
|
53
|
+
const rawNodes = s.graph?.nodes ?? [];
|
|
54
|
+
const rawEdges = s.graph?.edges ?? [];
|
|
55
|
+
const nodes = rawNodes.map((n) => {
|
|
56
|
+
const kind = getNodeKind(n.kind);
|
|
57
|
+
if (!kind) {
|
|
58
|
+
issues.push({
|
|
59
|
+
level: lenient ? "warning" : "error",
|
|
60
|
+
nodeId: n.id,
|
|
61
|
+
message: `Unknown kind "${n.kind}" \u2014 register it before importing.`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const config = n.config ?? (kind ? defaultConfigFor(kind) : {});
|
|
65
|
+
if (kind) {
|
|
66
|
+
for (const iss of validateConfig(kind, config)) {
|
|
67
|
+
issues.push({ level: "warning", nodeId: n.id, message: `${iss.key}: ${iss.message}` });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
id: n.id,
|
|
72
|
+
type: n.kind,
|
|
73
|
+
position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },
|
|
74
|
+
data: {
|
|
75
|
+
kind: n.kind,
|
|
76
|
+
label: n.label ?? kind?.label ?? n.kind,
|
|
77
|
+
description: n.description,
|
|
78
|
+
config
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
83
|
+
const edges = rawEdges.map((e) => {
|
|
84
|
+
if (!nodeIds.has(e.source)) {
|
|
85
|
+
issues.push({ level: "warning", edgeId: e.id, message: `Edge source "${e.source}" not found.` });
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (!nodeIds.has(e.target)) {
|
|
89
|
+
issues.push({ level: "warning", edgeId: e.id, message: `Edge target "${e.target}" not found.` });
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
id: e.id,
|
|
94
|
+
source: e.source,
|
|
95
|
+
target: e.target,
|
|
96
|
+
sourceHandle: e.sourceHandle,
|
|
97
|
+
targetHandle: e.targetHandle,
|
|
98
|
+
label: e.label
|
|
99
|
+
};
|
|
100
|
+
}).filter((e) => e !== null);
|
|
101
|
+
const ok = issues.every((i) => i.level !== "error");
|
|
102
|
+
return { ok, graph: { nodes, edges }, issues };
|
|
103
|
+
}
|
|
104
|
+
function workflowToBlob(schema) {
|
|
105
|
+
return new Blob([JSON.stringify(schema, null, 2)], { type: "application/json" });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, workflowToBlob };
|
|
109
|
+
//# sourceMappingURL=chunk-BCXECQUC.js.map
|
|
110
|
+
//# sourceMappingURL=chunk-BCXECQUC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"chunk-BCXECQUC.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\nimport { defaultConfigFor, getNodeKind, validateConfig } from \"../registry/registry\";\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 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 config?: 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 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 return {\n id: n.id,\n kind: data.kind ?? n.type ?? \"custom\",\n position: { x: n.position.x, y: n.position.y },\n label: data.label,\n description: data.description,\n config: data.config,\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/**\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\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 return {\n id: n.id,\n type: n.kind,\n position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },\n data: {\n kind: n.kind,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\n config,\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 const ok = issues.every((i) => i.level !== \"error\");\n return { ok, graph: { nodes, edges }, issues };\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"]}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { useState, useRef, useCallback } from 'react';
|
|
2
|
+
import { applyNodeChanges, applyEdgeChanges, addEdge } from '@xyflow/react';
|
|
3
|
+
|
|
4
|
+
// src/runtime/run-flow.ts
|
|
5
|
+
async function runFlow(graph, executors, onEvent = () => {
|
|
6
|
+
}, options = {}) {
|
|
7
|
+
const { signal, initialInputs = {}, timeoutMs } = options;
|
|
8
|
+
const outputs = {};
|
|
9
|
+
const portValues = /* @__PURE__ */ new Map();
|
|
10
|
+
const completed = /* @__PURE__ */ new Set();
|
|
11
|
+
const errors = [];
|
|
12
|
+
const order = topoSort(graph);
|
|
13
|
+
if (order === null) {
|
|
14
|
+
const msg = "Cycle detected in flow graph \u2014 aborting.";
|
|
15
|
+
onEvent({ type: "run-error", error: msg });
|
|
16
|
+
return { ok: false, outputs, error: msg };
|
|
17
|
+
}
|
|
18
|
+
const incomingByNode = indexIncoming(graph.edges);
|
|
19
|
+
const timer = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;
|
|
20
|
+
onEvent({ type: "run-start" });
|
|
21
|
+
try {
|
|
22
|
+
for (const node of order) {
|
|
23
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
24
|
+
if (errors.length) break;
|
|
25
|
+
const incoming = incomingByNode.get(node.id) ?? [];
|
|
26
|
+
if (incoming.length > 0) {
|
|
27
|
+
const allActive = incoming.every((e) => portValues.has(`${e.source}:${e.sourceHandle ?? "out"}`));
|
|
28
|
+
if (!allActive) {
|
|
29
|
+
onEvent({ type: "node-status", nodeId: node.id, status: "idle", text: "skipped" });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (node.type === "note") {
|
|
34
|
+
onEvent({ type: "node-status", nodeId: node.id, status: "idle", text: "annotation" });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
onEvent({ type: "node-status", nodeId: node.id, status: "running" });
|
|
38
|
+
const inputs = collectInputs(node, incoming, portValues, initialInputs);
|
|
39
|
+
const exec = pickExecutor(executors, node);
|
|
40
|
+
if (!exec) {
|
|
41
|
+
const msg = `No executor registered for kind=${node.type}`;
|
|
42
|
+
errors.push(msg);
|
|
43
|
+
onEvent({ type: "node-status", nodeId: node.id, status: "error", text: msg });
|
|
44
|
+
onEvent({ type: "log", nodeId: node.id, level: "error", message: msg });
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const result = await Promise.resolve(
|
|
49
|
+
exec({
|
|
50
|
+
node,
|
|
51
|
+
inputs,
|
|
52
|
+
abort: (reason) => {
|
|
53
|
+
throw new Error(reason ?? "aborted");
|
|
54
|
+
},
|
|
55
|
+
emit: onEvent
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
outputs[node.id] = result;
|
|
59
|
+
const activated = activatedPorts(node, result);
|
|
60
|
+
for (const portId of activated.ports) {
|
|
61
|
+
portValues.set(`${node.id}:${portId}`, activated.value);
|
|
62
|
+
onEvent({ type: "node-output", nodeId: node.id, portId, value: activated.value });
|
|
63
|
+
}
|
|
64
|
+
completed.add(node.id);
|
|
65
|
+
onEvent({ type: "node-status", nodeId: node.id, status: "done" });
|
|
66
|
+
} catch (e) {
|
|
67
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
68
|
+
errors.push(msg);
|
|
69
|
+
onEvent({ type: "node-status", nodeId: node.id, status: "error", text: msg });
|
|
70
|
+
onEvent({ type: "log", nodeId: node.id, level: "error", message: msg });
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} finally {
|
|
75
|
+
if (timer) clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
const ok = errors.length === 0;
|
|
78
|
+
onEvent({ type: "run-end", ok });
|
|
79
|
+
return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };
|
|
80
|
+
}
|
|
81
|
+
function indexIncoming(edges) {
|
|
82
|
+
const map = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const e of edges) {
|
|
84
|
+
const list = map.get(e.target) ?? [];
|
|
85
|
+
list.push(e);
|
|
86
|
+
map.set(e.target, list);
|
|
87
|
+
}
|
|
88
|
+
return map;
|
|
89
|
+
}
|
|
90
|
+
function topoSort(graph) {
|
|
91
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const n of graph.nodes) inDegree.set(n.id, 0);
|
|
93
|
+
for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);
|
|
94
|
+
const queue = [];
|
|
95
|
+
for (const [id, d] of inDegree) if (d === 0) queue.push(id);
|
|
96
|
+
const ordered = [];
|
|
97
|
+
while (queue.length) {
|
|
98
|
+
const id = queue.shift();
|
|
99
|
+
ordered.push(id);
|
|
100
|
+
for (const e of graph.edges) {
|
|
101
|
+
if (e.source !== id) continue;
|
|
102
|
+
const next = (inDegree.get(e.target) ?? 0) - 1;
|
|
103
|
+
inDegree.set(e.target, next);
|
|
104
|
+
if (next === 0) queue.push(e.target);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (ordered.length !== graph.nodes.length) return null;
|
|
108
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
109
|
+
return ordered.map((id) => byId.get(id)).filter(Boolean);
|
|
110
|
+
}
|
|
111
|
+
function collectInputs(node, incoming, portValues, initial) {
|
|
112
|
+
const inputs = { ...initial[node.id] ?? {} };
|
|
113
|
+
for (const e of incoming) {
|
|
114
|
+
const portId = e.targetHandle ?? "in";
|
|
115
|
+
const val = portValues.get(`${e.source}:${e.sourceHandle ?? "out"}`);
|
|
116
|
+
inputs[portId] = val;
|
|
117
|
+
}
|
|
118
|
+
return inputs;
|
|
119
|
+
}
|
|
120
|
+
function pickExecutor(executors, node) {
|
|
121
|
+
if (executors[node.id]) return executors[node.id];
|
|
122
|
+
if (node.type && executors[node.type]) return executors[node.type];
|
|
123
|
+
return executors["*"];
|
|
124
|
+
}
|
|
125
|
+
function activatedPorts(node, result) {
|
|
126
|
+
if (result && typeof result === "object") {
|
|
127
|
+
const r = result;
|
|
128
|
+
if (typeof r.__port === "string") {
|
|
129
|
+
return { ports: [r.__port], value: r.value };
|
|
130
|
+
}
|
|
131
|
+
if (typeof r.branch === "string") {
|
|
132
|
+
return { ports: [r.branch], value: r.value ?? r };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const declared = node.data.outputs?.map((p) => p.id) ?? ["out"];
|
|
136
|
+
return { ports: declared, value: result };
|
|
137
|
+
}
|
|
138
|
+
function useFlowRun({ maxFeed = 200 } = {}) {
|
|
139
|
+
const [statuses, setStatuses] = useState({});
|
|
140
|
+
const [statusText, setStatusText] = useState({});
|
|
141
|
+
const [feed, setFeed] = useState([]);
|
|
142
|
+
const [running, setRunning] = useState(false);
|
|
143
|
+
const [lastResult, setLastResult] = useState(null);
|
|
144
|
+
const abortRef = useRef(null);
|
|
145
|
+
const handleEvent = useCallback(
|
|
146
|
+
(e) => {
|
|
147
|
+
switch (e.type) {
|
|
148
|
+
case "node-status":
|
|
149
|
+
setStatuses((s) => ({ ...s, [e.nodeId]: e.status }));
|
|
150
|
+
setStatusText((t) => ({ ...t, [e.nodeId]: e.text }));
|
|
151
|
+
appendFeed({ level: "status", text: `${e.nodeId} \u2192 ${e.status}${e.text ? ` (${e.text})` : ""}`, nodeId: e.nodeId });
|
|
152
|
+
break;
|
|
153
|
+
case "node-output":
|
|
154
|
+
appendFeed({ level: "info", text: `${e.nodeId}.${e.portId} = ${preview(e.value)}`, nodeId: e.nodeId, detail: e.value });
|
|
155
|
+
break;
|
|
156
|
+
case "log":
|
|
157
|
+
appendFeed({ level: e.level, text: e.message, nodeId: e.nodeId, detail: e.detail });
|
|
158
|
+
break;
|
|
159
|
+
case "run-start":
|
|
160
|
+
appendFeed({ level: "info", text: "\u25B6 run started" });
|
|
161
|
+
break;
|
|
162
|
+
case "run-end":
|
|
163
|
+
appendFeed({ level: e.ok ? "info" : "error", text: e.ok ? "\u2713 run complete" : "\u2717 run failed" });
|
|
164
|
+
break;
|
|
165
|
+
case "run-error":
|
|
166
|
+
appendFeed({ level: "error", text: e.error });
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
function appendFeed(partial) {
|
|
170
|
+
setFeed((f) => {
|
|
171
|
+
const entry = { id: `${Date.now()}_${f.length}`, at: Date.now(), ...partial };
|
|
172
|
+
const next = [...f, entry];
|
|
173
|
+
return next.length > maxFeed ? next.slice(next.length - maxFeed) : next;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
[maxFeed]
|
|
178
|
+
);
|
|
179
|
+
const run = useCallback(
|
|
180
|
+
async (graph, executors, options = {}) => {
|
|
181
|
+
if (running) {
|
|
182
|
+
return { ok: false, outputs: {}, error: "another run is already in progress" };
|
|
183
|
+
}
|
|
184
|
+
const controller = new AbortController();
|
|
185
|
+
abortRef.current = controller;
|
|
186
|
+
const idleStatuses = {};
|
|
187
|
+
for (const n of graph.nodes) idleStatuses[n.id] = "idle";
|
|
188
|
+
setStatuses(idleStatuses);
|
|
189
|
+
setStatusText({});
|
|
190
|
+
setRunning(true);
|
|
191
|
+
try {
|
|
192
|
+
const result = await runFlow(graph, executors, handleEvent, { ...options, signal: controller.signal });
|
|
193
|
+
setLastResult(result);
|
|
194
|
+
return result;
|
|
195
|
+
} finally {
|
|
196
|
+
setRunning(false);
|
|
197
|
+
abortRef.current = null;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
[handleEvent, running]
|
|
201
|
+
);
|
|
202
|
+
const cancel = useCallback(() => abortRef.current?.abort(), []);
|
|
203
|
+
const reset = useCallback(() => {
|
|
204
|
+
setStatuses({});
|
|
205
|
+
setStatusText({});
|
|
206
|
+
setFeed([]);
|
|
207
|
+
setLastResult(null);
|
|
208
|
+
}, []);
|
|
209
|
+
return { statuses, statusText, feed, running, lastResult, run, cancel, reset };
|
|
210
|
+
}
|
|
211
|
+
function applyStatusesToNodes(nodes, statuses, statusText) {
|
|
212
|
+
return nodes.map((n) => ({
|
|
213
|
+
...n,
|
|
214
|
+
data: {
|
|
215
|
+
...n.data,
|
|
216
|
+
status: statuses[n.id] ?? n.data?.status ?? "idle",
|
|
217
|
+
statusText: statusText[n.id] ?? n.data?.statusText
|
|
218
|
+
}
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
function preview(v) {
|
|
222
|
+
try {
|
|
223
|
+
const s = JSON.stringify(v);
|
|
224
|
+
return s && s.length > 60 ? s.slice(0, 57) + "\u2026" : s ?? String(v);
|
|
225
|
+
} catch {
|
|
226
|
+
return String(v);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function useFlowState(initial) {
|
|
230
|
+
const [nodes, setNodes] = useState(initial.nodes);
|
|
231
|
+
const [edges, setEdges] = useState(initial.edges);
|
|
232
|
+
const onNodesChange = useCallback((changes) => {
|
|
233
|
+
setNodes((ns) => applyNodeChanges(changes, ns));
|
|
234
|
+
}, []);
|
|
235
|
+
const onEdgesChange = useCallback((changes) => {
|
|
236
|
+
setEdges((es) => applyEdgeChanges(changes, es));
|
|
237
|
+
}, []);
|
|
238
|
+
const onConnect = useCallback((connection) => {
|
|
239
|
+
setEdges((es) => addEdge(connection, es));
|
|
240
|
+
}, []);
|
|
241
|
+
const toGraph = useCallback(() => ({ nodes, edges }), [nodes, edges]);
|
|
242
|
+
return { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, onConnect, toGraph };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export { applyStatusesToNodes, runFlow, useFlowRun, useFlowState };
|
|
246
|
+
//# sourceMappingURL=chunk-JNYYJHNJ.js.map
|
|
247
|
+
//# sourceMappingURL=chunk-JNYYJHNJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/run-flow.ts","../src/runtime/use-flow-run.ts","../src/runtime/use-flow-state.ts"],"names":["useState","useCallback"],"mappings":"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","file":"chunk-JNYYJHNJ.js","sourcesContent":["import type {\n ExecutorRegistry,\n FlowEdge,\n FlowGraph,\n FlowNode,\n NodeExecutor,\n RunEvent,\n} from \"../types\";\n\nexport type RunOptions = {\n /** Stop the run after this many ms. Default: no timeout. */\n timeoutMs?: number;\n /** Abort signal — host can cancel the run. */\n signal?: AbortSignal;\n /** Initial inputs supplied to entry-point nodes (no incoming edges). */\n initialInputs?: Record<string, Record<string, unknown>>;\n};\n\nexport type RunResult = {\n ok: boolean;\n /** Outputs collected per node, keyed by node id. */\n outputs: Record<string, unknown>;\n /** Error captured if any node threw. */\n error?: string;\n};\n\n/**\n * runFlow — topological execution of a FlowGraph against an ExecutorRegistry.\n *\n * Each node runs once, when all upstream nodes have produced outputs on the\n * connected ports. Decision nodes (or any executor that returns `{ branch:\n * 'true' }`) can short-circuit specific output ports — only edges leaving\n * an \"active\" port propagate to downstream nodes.\n *\n * Cycles are detected and abort the run with an error.\n *\n * The `onEvent` callback receives a stream of `RunEvent`s — wire it to a\n * status feed, log panel, or store.\n */\nexport async function runFlow(\n graph: FlowGraph,\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent) => void = () => {},\n options: RunOptions = {},\n): Promise<RunResult> {\n const { signal, initialInputs = {}, timeoutMs } = options;\n const outputs: Record<string, unknown> = {};\n const portValues = new Map<string, unknown>(); // key: `${nodeId}:${portId}`\n const completed = new Set<string>();\n const errors: string[] = [];\n\n // Topological order via Kahn's algorithm. We allow nodes to run as soon\n // as their incoming edges' source ports have produced values, so the\n // order here is just a deterministic baseline used for cycle detection.\n const order = topoSort(graph);\n if (order === null) {\n const msg = \"Cycle detected in flow graph — aborting.\";\n onEvent({ type: \"run-error\", error: msg });\n return { ok: false, outputs, error: msg };\n }\n\n const incomingByNode = indexIncoming(graph.edges);\n const timer = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;\n\n onEvent({ type: \"run-start\" });\n\n try {\n for (const node of order) {\n if (signal?.aborted) throw new Error(\"aborted\");\n if (errors.length) break;\n\n const incoming = incomingByNode.get(node.id) ?? [];\n\n // Skip nodes whose upstream wasn't activated (e.g. a Decision routed\n // to a different branch).\n if (incoming.length > 0) {\n const allActive = incoming.every((e) => portValues.has(`${e.source}:${e.sourceHandle ?? \"out\"}`));\n if (!allActive) {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"skipped\" });\n continue;\n }\n }\n\n // Note nodes are annotations — never executed.\n if (node.type === \"note\") {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"annotation\" });\n continue;\n }\n\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"running\" });\n\n const inputs = collectInputs(node, incoming, portValues, initialInputs);\n const exec = pickExecutor(executors, node);\n if (!exec) {\n const msg = `No executor registered for kind=${node.type}`;\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n\n try {\n const result = await Promise.resolve(\n exec({\n node,\n inputs,\n abort: (reason) => { throw new Error(reason ?? \"aborted\"); },\n emit: onEvent,\n }),\n );\n outputs[node.id] = result;\n\n // Decide which output ports were activated. Three conventions:\n // 1) If result is `{ __port: \"out\", value: x }`, only that port emits.\n // 2) If result has `branch: <portId>`, only that port emits (decision sugar).\n // 3) Otherwise, the value is published on every declared output port.\n const activated = activatedPorts(node, result);\n for (const portId of activated.ports) {\n portValues.set(`${node.id}:${portId}`, activated.value);\n onEvent({ type: \"node-output\", nodeId: node.id, portId, value: activated.value });\n }\n completed.add(node.id);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"done\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n }\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const ok = errors.length === 0;\n onEvent({ type: \"run-end\", ok });\n return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };\n}\n\nfunction indexIncoming(edges: FlowEdge[]): Map<string, FlowEdge[]> {\n const map = new Map<string, FlowEdge[]>();\n for (const e of edges) {\n const list = map.get(e.target) ?? [];\n list.push(e);\n map.set(e.target, list);\n }\n return map;\n}\n\nfunction topoSort(graph: FlowGraph): FlowNode[] | null {\n const inDegree = new Map<string, number>();\n for (const n of graph.nodes) inDegree.set(n.id, 0);\n for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);\n const queue: string[] = [];\n for (const [id, d] of inDegree) if (d === 0) queue.push(id);\n const ordered: string[] = [];\n while (queue.length) {\n const id = queue.shift()!;\n ordered.push(id);\n for (const e of graph.edges) {\n if (e.source !== id) continue;\n const next = (inDegree.get(e.target) ?? 0) - 1;\n inDegree.set(e.target, next);\n if (next === 0) queue.push(e.target);\n }\n }\n if (ordered.length !== graph.nodes.length) return null;\n const byId = new Map(graph.nodes.map((n) => [n.id, n]));\n return ordered.map((id) => byId.get(id)!).filter(Boolean);\n}\n\nfunction collectInputs(\n node: FlowNode,\n incoming: FlowEdge[],\n portValues: Map<string, unknown>,\n initial: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n const inputs: Record<string, unknown> = { ...(initial[node.id] ?? {}) };\n for (const e of incoming) {\n const portId = e.targetHandle ?? \"in\";\n const val = portValues.get(`${e.source}:${e.sourceHandle ?? \"out\"}`);\n inputs[portId] = val;\n }\n return inputs;\n}\n\nfunction pickExecutor(\n executors: ExecutorRegistry,\n node: FlowNode,\n): NodeExecutor | undefined {\n if (executors[node.id]) return executors[node.id];\n if (node.type && executors[node.type]) return executors[node.type];\n return executors[\"*\"];\n}\n\nfunction activatedPorts(node: FlowNode, result: unknown): { ports: string[]; value: unknown } {\n if (result && typeof result === \"object\") {\n const r = result as Record<string, unknown>;\n if (typeof r.__port === \"string\") {\n return { ports: [r.__port], value: r.value };\n }\n if (typeof r.branch === \"string\") {\n return { ports: [r.branch], value: r.value ?? r };\n }\n }\n const declared = node.data.outputs?.map((p) => p.id) ?? [\"out\"];\n return { ports: declared, value: result };\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { runFlow, type RunOptions, type RunResult } from \"./run-flow\";\nimport type {\n ExecutorRegistry,\n FlowGraph,\n NodeRunStatus,\n RunEvent,\n} from \"../types\";\n\nexport type FlowRunFeedEntry = {\n id: string;\n at: number;\n level: \"info\" | \"warn\" | \"error\" | \"status\";\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 /** 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 [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-output\":\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 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 setFeed([]);\n setLastResult(null);\n }, []);\n\n return { statuses, statusText, feed, running, lastResult, run, cancel, reset };\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 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 toGraph = useCallback(() => ({ nodes, edges }), [nodes, edges]);\n\n return { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, onConnect, toGraph };\n}\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// src/registry/registry.ts
|
|
2
|
+
var kinds = /* @__PURE__ */ new Map();
|
|
3
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
function registerNodeKind(definition) {
|
|
5
|
+
kinds.set(definition.name, definition);
|
|
6
|
+
notify();
|
|
7
|
+
return () => {
|
|
8
|
+
if (kinds.get(definition.name) === definition) {
|
|
9
|
+
kinds.delete(definition.name);
|
|
10
|
+
notify();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function getNodeKind(name) {
|
|
15
|
+
return kinds.get(name) ?? null;
|
|
16
|
+
}
|
|
17
|
+
function listNodeKinds(category) {
|
|
18
|
+
const all = Array.from(kinds.values());
|
|
19
|
+
return category ? all.filter((k) => k.category === category) : all;
|
|
20
|
+
}
|
|
21
|
+
function onNodeKindsChanged(listener) {
|
|
22
|
+
listeners.add(listener);
|
|
23
|
+
return () => listeners.delete(listener);
|
|
24
|
+
}
|
|
25
|
+
function notify() {
|
|
26
|
+
for (const l of listeners) l();
|
|
27
|
+
}
|
|
28
|
+
function defaultConfigFor(kind) {
|
|
29
|
+
const fromKind = kind.defaultConfig ? { ...kind.defaultConfig } : {};
|
|
30
|
+
for (const field of kind.configSchema ?? []) {
|
|
31
|
+
if (fromKind[field.key] !== void 0) continue;
|
|
32
|
+
if ("default" in field && field.default !== void 0) {
|
|
33
|
+
fromKind[field.key] = field.default;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return fromKind;
|
|
37
|
+
}
|
|
38
|
+
function validateConfig(kind, config) {
|
|
39
|
+
const issues = [];
|
|
40
|
+
for (const field of kind.configSchema ?? []) {
|
|
41
|
+
const value = config[field.key];
|
|
42
|
+
if (field.required && (value === void 0 || value === null || value === "")) {
|
|
43
|
+
issues.push({ key: field.key, message: `${field.label} is required` });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (value === void 0 || value === null) continue;
|
|
47
|
+
const issue = validateField(field, value);
|
|
48
|
+
if (issue) issues.push({ key: field.key, message: issue });
|
|
49
|
+
}
|
|
50
|
+
return issues;
|
|
51
|
+
}
|
|
52
|
+
function validateField(field, value) {
|
|
53
|
+
switch (field.type) {
|
|
54
|
+
case "text":
|
|
55
|
+
case "textarea":
|
|
56
|
+
case "expression":
|
|
57
|
+
case "credential":
|
|
58
|
+
return typeof value === "string" ? null : `${field.label} must be a string`;
|
|
59
|
+
case "number": {
|
|
60
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return `${field.label} must be a number`;
|
|
61
|
+
if (field.min !== void 0 && value < field.min) return `${field.label} must be >= ${field.min}`;
|
|
62
|
+
if (field.max !== void 0 && value > field.max) return `${field.label} must be <= ${field.max}`;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
case "switch":
|
|
66
|
+
return typeof value === "boolean" ? null : `${field.label} must be a boolean`;
|
|
67
|
+
case "select": {
|
|
68
|
+
const allowed = field.options.map((o) => o.value);
|
|
69
|
+
return allowed.includes(String(value)) ? null : `${field.label} must be one of ${allowed.join(", ")}`;
|
|
70
|
+
}
|
|
71
|
+
case "json":
|
|
72
|
+
return null;
|
|
73
|
+
// permissive — just JSON-shaped
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function categoryAccent(category) {
|
|
79
|
+
switch (category) {
|
|
80
|
+
case "trigger":
|
|
81
|
+
return "#10b981";
|
|
82
|
+
case "logic":
|
|
83
|
+
return "#f59e0b";
|
|
84
|
+
case "data":
|
|
85
|
+
return "#0ea5e9";
|
|
86
|
+
case "ai":
|
|
87
|
+
return "#8b5cf6";
|
|
88
|
+
case "io":
|
|
89
|
+
return "#3b82f6";
|
|
90
|
+
case "human":
|
|
91
|
+
return "#ec4899";
|
|
92
|
+
case "output":
|
|
93
|
+
return "#a855f7";
|
|
94
|
+
default:
|
|
95
|
+
return "#71717a";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig };
|
|
100
|
+
//# sourceMappingURL=chunk-WNVBXXOL.js.map
|
|
101
|
+
//# sourceMappingURL=chunk-WNVBXXOL.js.map
|