@particle-academy/fancy-flow 0.20.0 → 0.22.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.
- package/dist/{capabilities-DUhAc-EJ.d.ts → capabilities-85Mjn4uW.d.ts} +1 -1
- package/dist/{capabilities-ChE3Xi8R.d.cts → capabilities-exVeADXz.d.cts} +1 -1
- package/dist/{chunk-L4AX73Q6.js → chunk-KDHLKBL2.js} +11 -5
- package/dist/chunk-KDHLKBL2.js.map +1 -0
- package/dist/{chunk-KI4RI2N3.js → chunk-O7F6SGV4.js} +4 -4
- package/dist/{chunk-KI4RI2N3.js.map → chunk-O7F6SGV4.js.map} +1 -1
- package/dist/{chunk-D6W5FMCT.js → chunk-OHVSEHDC.js} +23 -5
- package/dist/chunk-OHVSEHDC.js.map +1 -0
- package/dist/{chunk-U5F22BHV.js → chunk-YXRLIV7A.js} +4 -2
- package/dist/chunk-YXRLIV7A.js.map +1 -0
- package/dist/{chunk-AW2QUEL2.js → chunk-YZTBRXBZ.js} +66 -8
- package/dist/chunk-YZTBRXBZ.js.map +1 -0
- package/dist/{chunk-XETAGLH6.js → chunk-ZB4HHQMR.js} +130 -32
- package/dist/chunk-ZB4HHQMR.js.map +1 -0
- package/dist/engine.cjs +8 -2
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +3 -3
- package/dist/engine.d.ts +3 -3
- package/dist/engine.js +3 -3
- package/dist/index.cjs +3553 -3278
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -8
- package/dist/index.d.ts +25 -8
- package/dist/index.js +200 -107
- package/dist/index.js.map +1 -1
- package/dist/llm/vercel-ai.d.cts +2 -2
- package/dist/llm/vercel-ai.d.ts +2 -2
- package/dist/registry/index.d.cts +5 -5
- package/dist/registry/index.d.ts +5 -5
- package/dist/registry.cjs +197 -33
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +4 -4
- package/dist/{run-flow-ChBi6q-W.d.ts → run-flow-9U-gaYVF.d.ts} +1 -1
- package/dist/{run-flow-VKm57Hcj.d.cts → run-flow-AXzOR-Wq.d.cts} +1 -1
- package/dist/runtime/index.d.cts +3 -3
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime.cjs +8 -2
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +4 -4
- package/dist/schema/index.d.cts +21 -2
- package/dist/schema/index.d.ts +21 -2
- package/dist/schema.cjs +20 -1
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/styles.css +61 -0
- package/dist/styles.css.map +1 -1
- package/dist/{types-NerkPtHS.d.ts → types-CFEtuRWJ.d.ts} +33 -3
- package/dist/{types-sOmpCitB.d.cts → types-CMSrWVYM.d.cts} +1 -1
- package/dist/{types-sOmpCitB.d.ts → types-CMSrWVYM.d.ts} +1 -1
- package/dist/{types-D_jOR3Z2.d.cts → types-DneUoivl.d.cts} +33 -3
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.d.cts +3 -3
- package/dist/ux.d.ts +3 -3
- package/dist/ux.js +1 -1
- package/package.json +23 -3
- package/dist/chunk-AW2QUEL2.js.map +0 -1
- package/dist/chunk-D6W5FMCT.js.map +0 -1
- package/dist/chunk-L4AX73Q6.js.map +0 -1
- package/dist/chunk-U5F22BHV.js.map +0 -1
- package/dist/chunk-XETAGLH6.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-
|
|
2
|
-
import './chunk-
|
|
3
|
-
export { runFlow } from './chunk-
|
|
1
|
+
export { applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-O7F6SGV4.js';
|
|
2
|
+
import './chunk-ZB4HHQMR.js';
|
|
3
|
+
export { runFlow } from './chunk-KDHLKBL2.js';
|
|
4
4
|
import './chunk-TITD5W4Y.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-YXRLIV7A.js';
|
|
6
6
|
//# sourceMappingURL=runtime.js.map
|
|
7
7
|
//# sourceMappingURL=runtime.js.map
|
package/dist/schema/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PortDescriptor, F as FlowGraph } from '../types-
|
|
1
|
+
import { P as PortDescriptor, F as FlowGraph } from '../types-CMSrWVYM.cjs';
|
|
2
2
|
import '@xyflow/react';
|
|
3
3
|
|
|
4
4
|
/** Schema version. Bump on breaking shape changes; add migrations as needed. */
|
|
@@ -56,6 +56,18 @@ type WorkflowSchemaNode = {
|
|
|
56
56
|
*/
|
|
57
57
|
inputs?: PortDescriptor[];
|
|
58
58
|
outputs?: PortDescriptor[];
|
|
59
|
+
/**
|
|
60
|
+
* Visual layout — additive + optional. `parentId`/`extent` carry node grouping
|
|
61
|
+
* (swimlanes / containers); `width`/`height` carry an explicit (resized) size;
|
|
62
|
+
* `style` carries inline presentation. A runtime that only walks edges/ports
|
|
63
|
+
* (e.g. the PHP twin) ignores all of these — they exist purely for the canvas,
|
|
64
|
+
* so an older reader that doesn't know them simply drops them.
|
|
65
|
+
*/
|
|
66
|
+
parentId?: string;
|
|
67
|
+
extent?: "parent" | [[number, number], [number, number]];
|
|
68
|
+
width?: number;
|
|
69
|
+
height?: number;
|
|
70
|
+
style?: Record<string, unknown>;
|
|
59
71
|
};
|
|
60
72
|
type WorkflowSchemaEdge = {
|
|
61
73
|
id: string;
|
|
@@ -85,6 +97,13 @@ type ImportOptions = {
|
|
|
85
97
|
* instead of errors. Default false. */
|
|
86
98
|
lenient?: boolean;
|
|
87
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Migrate a raw schema object up to the current version. Additive changes need
|
|
102
|
+
* no migration (an older document simply lacks the newer optional fields); this
|
|
103
|
+
* is the seam for a future BREAKING bump — add `case N → N+1` steps here and
|
|
104
|
+
* `importWorkflow` runs them before validating the version.
|
|
105
|
+
*/
|
|
106
|
+
declare function migrateSchema(schema: unknown): unknown;
|
|
88
107
|
/**
|
|
89
108
|
* Hydrate a schema into runtime FlowGraph + validate kinds/configs against
|
|
90
109
|
* the registry. Reports issues for unknown kinds, missing required config,
|
|
@@ -94,4 +113,4 @@ declare function importWorkflow(schema: unknown, options?: ImportOptions): Impor
|
|
|
94
113
|
/** Convenience: serialize a schema as a downloadable JSON Blob. */
|
|
95
114
|
declare function workflowToBlob(schema: WorkflowSchema): Blob;
|
|
96
115
|
|
|
97
|
-
export { type ImportIssue, type ImportOptions, type ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, type WorkflowMetadata, type WorkflowSchema, type WorkflowSchemaEdge, type WorkflowSchemaNode, exportWorkflow, importWorkflow, workflowToBlob };
|
|
116
|
+
export { type ImportIssue, type ImportOptions, type ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, type WorkflowMetadata, type WorkflowSchema, type WorkflowSchemaEdge, type WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob };
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PortDescriptor, F as FlowGraph } from '../types-
|
|
1
|
+
import { P as PortDescriptor, F as FlowGraph } from '../types-CMSrWVYM.js';
|
|
2
2
|
import '@xyflow/react';
|
|
3
3
|
|
|
4
4
|
/** Schema version. Bump on breaking shape changes; add migrations as needed. */
|
|
@@ -56,6 +56,18 @@ type WorkflowSchemaNode = {
|
|
|
56
56
|
*/
|
|
57
57
|
inputs?: PortDescriptor[];
|
|
58
58
|
outputs?: PortDescriptor[];
|
|
59
|
+
/**
|
|
60
|
+
* Visual layout — additive + optional. `parentId`/`extent` carry node grouping
|
|
61
|
+
* (swimlanes / containers); `width`/`height` carry an explicit (resized) size;
|
|
62
|
+
* `style` carries inline presentation. A runtime that only walks edges/ports
|
|
63
|
+
* (e.g. the PHP twin) ignores all of these — they exist purely for the canvas,
|
|
64
|
+
* so an older reader that doesn't know them simply drops them.
|
|
65
|
+
*/
|
|
66
|
+
parentId?: string;
|
|
67
|
+
extent?: "parent" | [[number, number], [number, number]];
|
|
68
|
+
width?: number;
|
|
69
|
+
height?: number;
|
|
70
|
+
style?: Record<string, unknown>;
|
|
59
71
|
};
|
|
60
72
|
type WorkflowSchemaEdge = {
|
|
61
73
|
id: string;
|
|
@@ -85,6 +97,13 @@ type ImportOptions = {
|
|
|
85
97
|
* instead of errors. Default false. */
|
|
86
98
|
lenient?: boolean;
|
|
87
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Migrate a raw schema object up to the current version. Additive changes need
|
|
102
|
+
* no migration (an older document simply lacks the newer optional fields); this
|
|
103
|
+
* is the seam for a future BREAKING bump — add `case N → N+1` steps here and
|
|
104
|
+
* `importWorkflow` runs them before validating the version.
|
|
105
|
+
*/
|
|
106
|
+
declare function migrateSchema(schema: unknown): unknown;
|
|
88
107
|
/**
|
|
89
108
|
* Hydrate a schema into runtime FlowGraph + validate kinds/configs against
|
|
90
109
|
* the registry. Reports issues for unknown kinds, missing required config,
|
|
@@ -94,4 +113,4 @@ declare function importWorkflow(schema: unknown, options?: ImportOptions): Impor
|
|
|
94
113
|
/** Convenience: serialize a schema as a downloadable JSON Blob. */
|
|
95
114
|
declare function workflowToBlob(schema: WorkflowSchema): Blob;
|
|
96
115
|
|
|
97
|
-
export { type ImportIssue, type ImportOptions, type ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, type WorkflowMetadata, type WorkflowSchema, type WorkflowSchemaEdge, type WorkflowSchemaNode, exportWorkflow, importWorkflow, workflowToBlob };
|
|
116
|
+
export { type ImportIssue, type ImportOptions, type ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, type WorkflowMetadata, type WorkflowSchema, type WorkflowSchemaEdge, type WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob };
|
package/dist/schema.cjs
CHANGED
|
@@ -146,6 +146,7 @@ function toSchemaNode(n) {
|
|
|
146
146
|
const data = n.data ?? {};
|
|
147
147
|
const kindName = data.kind ?? n.type ?? "custom";
|
|
148
148
|
const ports = resolveNodePorts(n, getNodeKind(kindName) ?? void 0);
|
|
149
|
+
const node = n;
|
|
149
150
|
return {
|
|
150
151
|
id: n.id,
|
|
151
152
|
kind: kindName,
|
|
@@ -154,7 +155,14 @@ function toSchemaNode(n) {
|
|
|
154
155
|
description: data.description,
|
|
155
156
|
config: data.config,
|
|
156
157
|
inputs: ports.inputs,
|
|
157
|
-
outputs: ports.outputs
|
|
158
|
+
outputs: ports.outputs,
|
|
159
|
+
// Visual layout — only when explicitly set (never persist auto-`measured`
|
|
160
|
+
// dimensions, which are derived, not authored).
|
|
161
|
+
...node.parentId ? { parentId: node.parentId } : {},
|
|
162
|
+
...node.extent ? { extent: node.extent } : {},
|
|
163
|
+
...typeof node.width === "number" ? { width: node.width } : {},
|
|
164
|
+
...typeof node.height === "number" ? { height: node.height } : {},
|
|
165
|
+
...node.style ? { style: node.style } : {}
|
|
158
166
|
};
|
|
159
167
|
}
|
|
160
168
|
function toSchemaEdge(e) {
|
|
@@ -167,9 +175,13 @@ function toSchemaEdge(e) {
|
|
|
167
175
|
label: typeof e.label === "string" ? e.label : void 0
|
|
168
176
|
};
|
|
169
177
|
}
|
|
178
|
+
function migrateSchema(schema) {
|
|
179
|
+
return schema;
|
|
180
|
+
}
|
|
170
181
|
function importWorkflow(schema, options = {}) {
|
|
171
182
|
const issues = [];
|
|
172
183
|
const lenient = options.lenient === true;
|
|
184
|
+
schema = migrateSchema(schema);
|
|
173
185
|
if (!schema || typeof schema !== "object") {
|
|
174
186
|
return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: "error", message: "Schema is not an object." }] };
|
|
175
187
|
}
|
|
@@ -203,6 +215,12 @@ function importWorkflow(schema, options = {}) {
|
|
|
203
215
|
id: n.id,
|
|
204
216
|
type: kindId,
|
|
205
217
|
position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },
|
|
218
|
+
// Rehydrate visual layout onto the node's top level (where xyflow reads it).
|
|
219
|
+
...n.parentId ? { parentId: n.parentId } : {},
|
|
220
|
+
...n.extent ? { extent: n.extent } : {},
|
|
221
|
+
...typeof n.width === "number" ? { width: n.width } : {},
|
|
222
|
+
...typeof n.height === "number" ? { height: n.height } : {},
|
|
223
|
+
...n.style ? { style: n.style } : {},
|
|
206
224
|
data: {
|
|
207
225
|
kind: kindId,
|
|
208
226
|
label: n.label ?? kind?.label ?? n.kind,
|
|
@@ -245,6 +263,7 @@ exports.WORKFLOW_SCHEMA_URL = WORKFLOW_SCHEMA_URL;
|
|
|
245
263
|
exports.WORKFLOW_SCHEMA_VERSION = WORKFLOW_SCHEMA_VERSION;
|
|
246
264
|
exports.exportWorkflow = exportWorkflow;
|
|
247
265
|
exports.importWorkflow = importWorkflow;
|
|
266
|
+
exports.migrateSchema = migrateSchema;
|
|
248
267
|
exports.workflowToBlob = workflowToBlob;
|
|
249
268
|
//# sourceMappingURL=schema.cjs.map
|
|
250
269
|
//# sourceMappingURL=schema.cjs.map
|
package/dist/schema.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/registry/registry.ts","../src/registry/ports.ts","../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"schema.cjs","sourcesContent":["import type { ConfigField, NodeKindDefinition } from \"./types\";\n\nconst kinds = new Map<string, NodeKindDefinition<any, any, any>>();\n/** alias → canonical name. See `resolveKindId`. */\nconst aliases = new Map<string, string>();\nconst listeners = new Set<() => void>();\n\n/**\n * registerNodeKind — install a node kind in the global registry. Returns\n * an `unregister` function. Calling with the same name replaces the prior\n * registration (handy for HMR).\n *\n * A kind's `name` is its CANONICAL id and is what gets written into saved\n * documents. Publish namespaced (`@fancy/llm_branch`, `@acme/salesforce_upsert`)\n * and list any previous bare names in `aliases`, so graphs saved before the\n * rename keep resolving.\n */\nexport function registerNodeKind<TC = any, TI = any, TO = any>(\n definition: NodeKindDefinition<TC, TI, TO>,\n): () => void {\n kinds.set(definition.name, definition as NodeKindDefinition<any, any, any>);\n for (const alias of definition.aliases ?? []) aliases.set(alias, definition.name);\n notify();\n return () => {\n if (kinds.get(definition.name) === (definition as any)) {\n kinds.delete(definition.name);\n for (const alias of definition.aliases ?? []) {\n if (aliases.get(alias) === definition.name) aliases.delete(alias);\n }\n notify();\n }\n };\n}\n\n/**\n * Resolve any id — canonical or alias — to the canonical one, or null.\n *\n * `kind` is persisted inside every saved graph, so a bare name that two\n * packages could both claim is unfixable after the fact: the ambiguous string\n * is already in the document. Canonical ids are namespaced; aliases exist so\n * documents written before namespacing keep opening.\n */\nexport function resolveKindId(id: string): string | null {\n if (kinds.has(id)) return id;\n const canonical = aliases.get(id);\n return canonical && kinds.has(canonical) ? canonical : null;\n}\n\n/** Get a single kind by canonical id or alias, or null. */\nexport function getNodeKind(name: string): NodeKindDefinition | null {\n const canonical = resolveKindId(name);\n return canonical ? ((kinds.get(canonical) as NodeKindDefinition) ?? null) : null;\n}\n\n/** Every id a kind answers to — canonical first. Used to key node-type maps. */\nexport function kindIds(kind: NodeKindDefinition): string[] {\n return [kind.name, ...(kind.aliases ?? [])];\n}\n\n/** List every registered kind, optionally filtered by category. */\nexport function listNodeKinds(category?: string): NodeKindDefinition[] {\n const all = Array.from(kinds.values()) as NodeKindDefinition[];\n return category ? all.filter((k) => k.category === category) : all;\n}\n\n/** Subscribe to registry changes. Returns an unsubscribe function. */\nexport function onNodeKindsChanged(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nfunction notify(): void {\n for (const l of listeners) l();\n}\n\n/** Fill in defaults from a kind's configSchema for newly-created nodes. */\nexport function defaultConfigFor(kind: NodeKindDefinition): Record<string, unknown> {\n const fromKind = kind.defaultConfig ? { ...(kind.defaultConfig as Record<string, unknown>) } : {};\n for (const field of kind.configSchema ?? []) {\n if (fromKind[field.key] !== undefined) continue;\n if (\"default\" in field && (field as any).default !== undefined) {\n fromKind[field.key] = (field as any).default;\n }\n }\n return fromKind;\n}\n\n/**\n * Validate a config object against a kind's schema. Returns an array of\n * issues (empty = valid). Validation is intentionally light — type\n * coercion + required-field checks. Hosts can layer Zod / Ajv on top.\n */\nexport function validateConfig(\n kind: NodeKindDefinition,\n config: Record<string, unknown>,\n): Array<{ key: string; message: string }> {\n const issues: Array<{ key: string; message: string }> = [];\n for (const field of kind.configSchema ?? []) {\n const value = config[field.key];\n if (field.required && (value === undefined || value === null || value === \"\")) {\n issues.push({ key: field.key, message: `${field.label} is required` });\n continue;\n }\n if (value === undefined || value === null) continue;\n const issue = validateField(field, value);\n if (issue) issues.push({ key: field.key, message: issue });\n }\n return issues;\n}\n\nfunction validateField(field: ConfigField, value: unknown): string | null {\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"expression\":\n case \"credential\":\n return typeof value === \"string\" ? null : `${field.label} must be a string`;\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return `${field.label} must be a number`;\n if (field.min !== undefined && value < field.min) return `${field.label} must be >= ${field.min}`;\n if (field.max !== undefined && value > field.max) return `${field.label} must be <= ${field.max}`;\n return null;\n }\n case \"switch\":\n return typeof value === \"boolean\" ? null : `${field.label} must be a boolean`;\n case \"select\": {\n const allowed = field.options.map((o) => o.value);\n return allowed.includes(String(value)) ? null : `${field.label} must be one of ${allowed.join(\", \")}`;\n }\n case \"json\":\n return null; // permissive — just JSON-shaped\n case \"repeater\": {\n if (!Array.isArray(value)) return `${field.label} must be a list`;\n if (field.minItems !== undefined && value.length < field.minItems) {\n return `${field.label} needs at least ${field.minItems}`;\n }\n if (field.maxItems !== undefined && value.length > field.maxItems) {\n return `${field.label} allows at most ${field.maxItems}`;\n }\n // Surface the first offending row so the author knows WHICH one.\n for (let i = 0; i < value.length; i++) {\n const row = value[i];\n if (!row || typeof row !== \"object\" || Array.isArray(row)) {\n return `${field.label} item ${i + 1} must be an object`;\n }\n for (const sub of field.fields) {\n const cell = (row as Record<string, unknown>)[sub.key];\n if (sub.required && (cell === undefined || cell === null || cell === \"\")) {\n return `${field.label} item ${i + 1}: ${sub.label} is required`;\n }\n if (cell === undefined || cell === null) continue;\n const issue = validateField(sub, cell);\n if (issue) return `${field.label} item ${i + 1}: ${issue}`;\n }\n }\n return null;\n }\n case \"keyvalue\": {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return `${field.label} must be a key/value map`;\n }\n const allowed = field.valueOptions?.map((o) => o.value);\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v !== \"string\") return `${field.label}: \"${k}\" must be a string`;\n if (allowed && !allowed.includes(v)) {\n return `${field.label}: \"${k}\" must be one of ${allowed.join(\", \")}`;\n }\n }\n return null;\n }\n case \"document\":\n return null; // opaque to fancy-flow — the host's editor owns its shape\n default:\n return null;\n }\n}\n\n/** Default accents per category. */\nexport function categoryAccent(category: string): string {\n switch (category) {\n case \"trigger\": return \"#10b981\";\n case \"logic\": return \"#f59e0b\";\n case \"data\": return \"#0ea5e9\";\n case \"ai\": return \"#8b5cf6\";\n case \"io\": return \"#3b82f6\";\n case \"human\": return \"#ec4899\";\n case \"output\": return \"#a855f7\";\n default: return \"#71717a\";\n }\n}\n","import type { FlowNode, PortDescriptor } from \"../types\";\nimport type { NodeKindDefinition, PortSpec } from \"./types\";\n\n/**\n * Port resolution — the single place a node's ports are derived, shared by\n * the canvas renderer and the headless runtime.\n *\n * This module is deliberately React-free: `runFlow` (the `/engine` entry)\n * imports it, and the engine bundle must stay free of React.\n *\n * ## Why this is centralized\n *\n * Ports used to be read in two places that disagreed — the canvas consulted\n * `data.outputs ?? kind.outputs`, while the runtime consulted `data.outputs`\n * ONLY and fell back to a single `out` port. A kind that declared branch ports\n * therefore drew correctly and then routed as if it had one output, unless the\n * host remembered to mirror the ports onto every node's `data`. Both callers\n * now go through `resolveNodePorts`, so declared ports and executed ports\n * cannot drift apart.\n */\n\n/**\n * Resolve a `PortSpec` against a config object.\n *\n * A config-driven spec is author-supplied and runs on every render, so a throw\n * is contained: it degrades to \"undeclared\" (letting the caller fall back)\n * rather than taking out the canvas or aborting a run mid-flight.\n */\nexport function resolvePortSpec<TConfig>(\n spec: PortSpec<TConfig> | undefined,\n config: TConfig,\n): PortDescriptor[] | undefined {\n if (spec === undefined) return undefined;\n if (typeof spec !== \"function\") return spec;\n try {\n const resolved = (spec as (c: TConfig) => PortDescriptor[])(config);\n return Array.isArray(resolved) ? resolved : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Read the config bag off a node, tolerating the FlowNodeData union. */\nexport function nodeConfig(node: Pick<FlowNode, \"data\">): Record<string, unknown> {\n return ((node.data as any)?.config ?? {}) as Record<string, unknown>;\n}\n\n/**\n * Resolve a node's effective ports.\n *\n * Precedence: explicit `data.inputs`/`data.outputs` (a per-node host override)\n * beats the kind's declaration. `undefined` means \"nothing declared\" — the\n * caller applies its own category default.\n */\nexport function resolveNodePorts(\n node: Pick<FlowNode, \"data\">,\n kind?: Pick<NodeKindDefinition<any>, \"inputs\" | \"outputs\">,\n): { inputs?: PortDescriptor[]; outputs?: PortDescriptor[] } {\n const config = nodeConfig(node);\n const data = node.data as any;\n return {\n inputs: data?.inputs ?? resolvePortSpec(kind?.inputs, config),\n outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config),\n };\n}\n","import type { FlowEdge, FlowGraph, FlowNode, PortDescriptor } from \"../types\";\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 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 * 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\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 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 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 config: data.config,\n inputs: ports.inputs,\n outputs: ports.outputs,\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 // 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 data: {\n kind: kindId,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\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 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/registry/registry.ts","../src/registry/ports.ts","../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"schema.cjs","sourcesContent":["import type { ConfigField, NodeKindDefinition } from \"./types\";\n\nconst kinds = new Map<string, NodeKindDefinition<any, any, any>>();\n/** alias → canonical name. See `resolveKindId`. */\nconst aliases = new Map<string, string>();\nconst listeners = new Set<() => void>();\n\n/**\n * registerNodeKind — install a node kind in the global registry. Returns\n * an `unregister` function. Calling with the same name replaces the prior\n * registration (handy for HMR).\n *\n * A kind's `name` is its CANONICAL id and is what gets written into saved\n * documents. Publish namespaced (`@fancy/llm_branch`, `@acme/salesforce_upsert`)\n * and list any previous bare names in `aliases`, so graphs saved before the\n * rename keep resolving.\n */\nexport function registerNodeKind<TC = any, TI = any, TO = any>(\n definition: NodeKindDefinition<TC, TI, TO>,\n): () => void {\n kinds.set(definition.name, definition as NodeKindDefinition<any, any, any>);\n for (const alias of definition.aliases ?? []) aliases.set(alias, definition.name);\n notify();\n return () => {\n if (kinds.get(definition.name) === (definition as any)) {\n kinds.delete(definition.name);\n for (const alias of definition.aliases ?? []) {\n if (aliases.get(alias) === definition.name) aliases.delete(alias);\n }\n notify();\n }\n };\n}\n\n/**\n * Resolve any id — canonical or alias — to the canonical one, or null.\n *\n * `kind` is persisted inside every saved graph, so a bare name that two\n * packages could both claim is unfixable after the fact: the ambiguous string\n * is already in the document. Canonical ids are namespaced; aliases exist so\n * documents written before namespacing keep opening.\n */\nexport function resolveKindId(id: string): string | null {\n if (kinds.has(id)) return id;\n const canonical = aliases.get(id);\n return canonical && kinds.has(canonical) ? canonical : null;\n}\n\n/** Get a single kind by canonical id or alias, or null. */\nexport function getNodeKind(name: string): NodeKindDefinition | null {\n const canonical = resolveKindId(name);\n return canonical ? ((kinds.get(canonical) as NodeKindDefinition) ?? null) : null;\n}\n\n/** Every id a kind answers to — canonical first. Used to key node-type maps. */\nexport function kindIds(kind: NodeKindDefinition): string[] {\n return [kind.name, ...(kind.aliases ?? [])];\n}\n\n/** List every registered kind, optionally filtered by category. */\nexport function listNodeKinds(category?: string): NodeKindDefinition[] {\n const all = Array.from(kinds.values()) as NodeKindDefinition[];\n return category ? all.filter((k) => k.category === category) : all;\n}\n\n/** Subscribe to registry changes. Returns an unsubscribe function. */\nexport function onNodeKindsChanged(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nfunction notify(): void {\n for (const l of listeners) l();\n}\n\n/** Fill in defaults from a kind's configSchema for newly-created nodes. */\nexport function defaultConfigFor(kind: NodeKindDefinition): Record<string, unknown> {\n const fromKind = kind.defaultConfig ? { ...(kind.defaultConfig as Record<string, unknown>) } : {};\n for (const field of kind.configSchema ?? []) {\n if (fromKind[field.key] !== undefined) continue;\n if (\"default\" in field && (field as any).default !== undefined) {\n fromKind[field.key] = (field as any).default;\n }\n }\n return fromKind;\n}\n\n/**\n * Validate a config object against a kind's schema. Returns an array of\n * issues (empty = valid). Validation is intentionally light — type\n * coercion + required-field checks. Hosts can layer Zod / Ajv on top.\n */\nexport function validateConfig(\n kind: NodeKindDefinition,\n config: Record<string, unknown>,\n): Array<{ key: string; message: string }> {\n const issues: Array<{ key: string; message: string }> = [];\n for (const field of kind.configSchema ?? []) {\n const value = config[field.key];\n if (field.required && (value === undefined || value === null || value === \"\")) {\n issues.push({ key: field.key, message: `${field.label} is required` });\n continue;\n }\n if (value === undefined || value === null) continue;\n const issue = validateField(field, value);\n if (issue) issues.push({ key: field.key, message: issue });\n }\n return issues;\n}\n\nfunction validateField(field: ConfigField, value: unknown): string | null {\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"expression\":\n case \"credential\":\n return typeof value === \"string\" ? null : `${field.label} must be a string`;\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return `${field.label} must be a number`;\n if (field.min !== undefined && value < field.min) return `${field.label} must be >= ${field.min}`;\n if (field.max !== undefined && value > field.max) return `${field.label} must be <= ${field.max}`;\n return null;\n }\n case \"switch\":\n return typeof value === \"boolean\" ? null : `${field.label} must be a boolean`;\n case \"select\": {\n const allowed = field.options.map((o) => o.value);\n return allowed.includes(String(value)) ? null : `${field.label} must be one of ${allowed.join(\", \")}`;\n }\n case \"json\":\n return null; // permissive — just JSON-shaped\n case \"repeater\": {\n if (!Array.isArray(value)) return `${field.label} must be a list`;\n if (field.minItems !== undefined && value.length < field.minItems) {\n return `${field.label} needs at least ${field.minItems}`;\n }\n if (field.maxItems !== undefined && value.length > field.maxItems) {\n return `${field.label} allows at most ${field.maxItems}`;\n }\n // Surface the first offending row so the author knows WHICH one.\n for (let i = 0; i < value.length; i++) {\n const row = value[i];\n if (!row || typeof row !== \"object\" || Array.isArray(row)) {\n return `${field.label} item ${i + 1} must be an object`;\n }\n for (const sub of field.fields) {\n const cell = (row as Record<string, unknown>)[sub.key];\n if (sub.required && (cell === undefined || cell === null || cell === \"\")) {\n return `${field.label} item ${i + 1}: ${sub.label} is required`;\n }\n if (cell === undefined || cell === null) continue;\n const issue = validateField(sub, cell);\n if (issue) return `${field.label} item ${i + 1}: ${issue}`;\n }\n }\n return null;\n }\n case \"keyvalue\": {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return `${field.label} must be a key/value map`;\n }\n const allowed = field.valueOptions?.map((o) => o.value);\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v !== \"string\") return `${field.label}: \"${k}\" must be a string`;\n if (allowed && !allowed.includes(v)) {\n return `${field.label}: \"${k}\" must be one of ${allowed.join(\", \")}`;\n }\n }\n return null;\n }\n case \"document\":\n return null; // opaque to fancy-flow — the host's editor owns its shape\n default:\n return null;\n }\n}\n\n/** Default accents per category. */\nexport function categoryAccent(category: string): string {\n switch (category) {\n case \"trigger\": return \"#10b981\";\n case \"logic\": return \"#f59e0b\";\n case \"data\": return \"#0ea5e9\";\n case \"ai\": return \"#8b5cf6\";\n case \"io\": return \"#3b82f6\";\n case \"human\": return \"#ec4899\";\n case \"output\": return \"#a855f7\";\n case \"layout\": return \"#64748b\";\n default: return \"#71717a\";\n }\n}\n","import type { FlowNode, PortDescriptor } from \"../types\";\nimport type { NodeKindDefinition, PortSpec } from \"./types\";\n\n/**\n * Port resolution — the single place a node's ports are derived, shared by\n * the canvas renderer and the headless runtime.\n *\n * This module is deliberately React-free: `runFlow` (the `/engine` entry)\n * imports it, and the engine bundle must stay free of React.\n *\n * ## Why this is centralized\n *\n * Ports used to be read in two places that disagreed — the canvas consulted\n * `data.outputs ?? kind.outputs`, while the runtime consulted `data.outputs`\n * ONLY and fell back to a single `out` port. A kind that declared branch ports\n * therefore drew correctly and then routed as if it had one output, unless the\n * host remembered to mirror the ports onto every node's `data`. Both callers\n * now go through `resolveNodePorts`, so declared ports and executed ports\n * cannot drift apart.\n */\n\n/**\n * Resolve a `PortSpec` against a config object.\n *\n * A config-driven spec is author-supplied and runs on every render, so a throw\n * is contained: it degrades to \"undeclared\" (letting the caller fall back)\n * rather than taking out the canvas or aborting a run mid-flight.\n */\nexport function resolvePortSpec<TConfig>(\n spec: PortSpec<TConfig> | undefined,\n config: TConfig,\n): PortDescriptor[] | undefined {\n if (spec === undefined) return undefined;\n if (typeof spec !== \"function\") return spec;\n try {\n const resolved = (spec as (c: TConfig) => PortDescriptor[])(config);\n return Array.isArray(resolved) ? resolved : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Read the config bag off a node, tolerating the FlowNodeData union. */\nexport function nodeConfig(node: Pick<FlowNode, \"data\">): Record<string, unknown> {\n return ((node.data as any)?.config ?? {}) as Record<string, unknown>;\n}\n\n/**\n * Resolve a node's effective ports.\n *\n * Precedence: explicit `data.inputs`/`data.outputs` (a per-node host override)\n * beats the kind's declaration. `undefined` means \"nothing declared\" — the\n * caller applies its own category default.\n */\nexport function resolveNodePorts(\n node: Pick<FlowNode, \"data\">,\n kind?: Pick<NodeKindDefinition<any>, \"inputs\" | \"outputs\">,\n): { inputs?: PortDescriptor[]; outputs?: PortDescriptor[] } {\n const config = nodeConfig(node);\n const data = node.data as any;\n return {\n inputs: data?.inputs ?? resolvePortSpec(kind?.inputs, config),\n outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config),\n };\n}\n","import type { FlowEdge, FlowGraph, FlowNode, PortDescriptor } from \"../types\";\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 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 * 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 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 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/**\n * Migrate a raw schema object up to the current version. Additive changes need\n * no migration (an older document simply lacks the newer optional fields); this\n * is the seam for a future BREAKING bump — add `case N → N+1` steps here and\n * `importWorkflow` runs them before validating the version.\n */\nexport function migrateSchema(schema: unknown): unknown {\n // v1 is current — nothing to migrate yet.\n return schema;\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 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 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"]}
|
package/dist/schema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, workflowToBlob } from './chunk-
|
|
1
|
+
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './chunk-OHVSEHDC.js';
|
|
2
2
|
import './chunk-TITD5W4Y.js';
|
|
3
|
-
import './chunk-
|
|
3
|
+
import './chunk-YXRLIV7A.js';
|
|
4
4
|
//# sourceMappingURL=schema.js.map
|
|
5
5
|
//# sourceMappingURL=schema.js.map
|
package/dist/styles.css
CHANGED
|
@@ -1535,4 +1535,65 @@ svg.react-flow__connectionline {
|
|
|
1535
1535
|
.dark .ff-rich-preview__title {
|
|
1536
1536
|
color: #d4d4d8;
|
|
1537
1537
|
}
|
|
1538
|
+
.ff-node--resizable {
|
|
1539
|
+
max-width: none;
|
|
1540
|
+
width: 100%;
|
|
1541
|
+
height: 100%;
|
|
1542
|
+
}
|
|
1543
|
+
.react-flow__resize-control.line {
|
|
1544
|
+
border-color: rgba(0, 0, 0, 0.15);
|
|
1545
|
+
}
|
|
1546
|
+
.react-flow__resize-control.handle {
|
|
1547
|
+
width: 8px;
|
|
1548
|
+
height: 8px;
|
|
1549
|
+
border-radius: 2px;
|
|
1550
|
+
border: 1px solid #fff;
|
|
1551
|
+
}
|
|
1552
|
+
.dark .react-flow__resize-control.line {
|
|
1553
|
+
border-color: rgba(255, 255, 255, 0.18);
|
|
1554
|
+
}
|
|
1555
|
+
.dark .react-flow__resize-control.handle {
|
|
1556
|
+
border-color: #1c1917;
|
|
1557
|
+
}
|
|
1558
|
+
.ff-lane {
|
|
1559
|
+
width: 100%;
|
|
1560
|
+
height: 100%;
|
|
1561
|
+
box-sizing: border-box;
|
|
1562
|
+
background: rgba(100, 116, 139, 0.06);
|
|
1563
|
+
border: 1px dashed #cbd5e1;
|
|
1564
|
+
border-radius: 8px;
|
|
1565
|
+
overflow: hidden;
|
|
1566
|
+
}
|
|
1567
|
+
.ff-lane--selected {
|
|
1568
|
+
border-style: solid;
|
|
1569
|
+
}
|
|
1570
|
+
.ff-lane__header {
|
|
1571
|
+
display: flex;
|
|
1572
|
+
align-items: center;
|
|
1573
|
+
height: 22px;
|
|
1574
|
+
padding: 0 8px;
|
|
1575
|
+
color: #fff;
|
|
1576
|
+
font-size: 11px;
|
|
1577
|
+
font-weight: 600;
|
|
1578
|
+
font-family:
|
|
1579
|
+
ui-sans-serif,
|
|
1580
|
+
system-ui,
|
|
1581
|
+
sans-serif;
|
|
1582
|
+
border-radius: 7px 7px 0 0;
|
|
1583
|
+
}
|
|
1584
|
+
.ff-lane__title {
|
|
1585
|
+
overflow: hidden;
|
|
1586
|
+
text-overflow: ellipsis;
|
|
1587
|
+
white-space: nowrap;
|
|
1588
|
+
}
|
|
1589
|
+
.dark .ff-lane {
|
|
1590
|
+
background: rgba(148, 163, 184, 0.08);
|
|
1591
|
+
border-color: #3f3f46;
|
|
1592
|
+
}
|
|
1593
|
+
@media (prefers-color-scheme: dark) {
|
|
1594
|
+
.ff-lane:not(.ff-canvas--light *) {
|
|
1595
|
+
background: rgba(148, 163, 184, 0.08);
|
|
1596
|
+
border-color: #3f3f46;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1538
1599
|
/*# sourceMappingURL=styles.css.map */
|