diff --git a/ROADMAP.md b/ROADMAP.md index 264e854..f034389 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -80,3 +80,419 @@ - [x] **Tool spec modularization**: Break `tools.json` into category files and load them as a directory. - [x] **Translation assets modularization**: Store messages per language in grouped JSON directories and update loaders/writers accordingly. - [x] **UI test decomposition**: Split Playwright UI tests into focused modules with shared helpers. + +```json +{ +"$schema": "https://json-schema.org/draft/2020-12/schema", +"$id": "https://example.com/schemas/n8n-workflow.schema.json", +"title": "N8N-Style Workflow", +"type": "object", +"additionalProperties": false, +"required": ["name", "nodes", "connections"], +"properties": { +"id": { +"description": "Optional external identifier (DB id, UUID, etc.).", +"type": ["string", "integer"] +}, +"name": { +"type": "string", +"minLength": 1 +}, +"active": { +"type": "boolean", +"default": false +}, +"versionId": { +"description": "Optional version identifier for optimistic concurrency.", +"type": "string" +}, +"createdAt": { +"type": "string", +"format": "date-time" +}, +"updatedAt": { +"type": "string", +"format": "date-time" +}, +"tags": { +"type": "array", +"items": { "$ref": "#/$defs/tag" }, +"default": [] +}, +"meta": { +"description": "Arbitrary metadata. Keep stable keys for tooling.", +"type": "object", +"additionalProperties": true, +"default": {} +}, +"settings": { +"$ref": "#/$defs/workflowSettings" +}, +"pinData": { +"description": "Optional pinned execution data (useful for dev).", +"type": "object", +"additionalProperties": { +"type": "array", +"items": { +"type": "object", +"additionalProperties": true +} +} +}, +"nodes": { +"type": "array", +"minItems": 1, +"items": { "$ref": "#/$defs/node" } +}, +"connections": { +"$ref": "#/$defs/connections" +}, +"staticData": { +"description": "Reserved for engine-managed workflow state.", +"type": "object", +"additionalProperties": true, +"default": {} +}, +"credentials": { +"description": "Optional top-level credential bindings (engine-specific).", +"type": "array", +"items": { "$ref": "#/$defs/credentialBinding" }, +"default": [] +} +}, +"$defs": { +"tag": { +"type": "object", +"additionalProperties": false, +"required": ["name"], +"properties": { +"id": { "type": ["string", "integer"] }, +"name": { "type": "string", "minLength": 1 } +} +}, +"workflowSettings": { +"type": "object", +"additionalProperties": false, +"properties": { +"timezone": { +"description": "IANA timezone name, e.g. Europe/London.", +"type": "string" +}, +"executionTimeout": { +"description": "Hard timeout in seconds for a workflow execution.", +"type": "integer", +"minimum": 0 +}, +"saveExecutionProgress": { +"type": "boolean", +"default": true +}, +"saveManualExecutions": { +"type": "boolean", +"default": true +}, +"saveDataErrorExecution": { +"description": "Persist execution data on error.", +"type": "string", +"enum": ["all", "none"], +"default": "all" +}, +"saveDataSuccessExecution": { +"description": "Persist execution data on success.", +"type": "string", +"enum": ["all", "none"], +"default": "all" +}, +"saveDataManualExecution": { +"description": "Persist execution data for manual runs.", +"type": "string", +"enum": ["all", "none"], +"default": "all" +}, +"errorWorkflowId": { +"description": "Optional workflow id to call on error.", +"type": ["string", "integer"] +}, +"callerPolicy": { +"description": "Optional policy controlling which workflows can call this workflow.", +"type": "string" +} +}, +"default": {} +}, +"node": { +"type": "object", +"additionalProperties": false, +"required": ["id", "name", "type", "typeVersion", "position"], +"properties": { +"id": { +"description": "Stable unique id within the workflow. Prefer UUID.", +"type": "string", +"minLength": 1 +}, +"name": { +"description": "Human-friendly name; should be unique in workflow.", +"type": "string", +"minLength": 1 +}, +"type": { +"description": "Node type identifier, e.g. n8n-nodes-base.httpRequest.", +"type": "string", +"minLength": 1 +}, +"typeVersion": { +"description": "Node implementation version.", +"type": ["integer", "number"], +"minimum": 1 +}, +"disabled": { +"type": "boolean", +"default": false +}, +"notes": { +"type": "string", +"default": "" +}, +"notesInFlow": { +"description": "When true, notes are displayed on canvas.", +"type": "boolean", +"default": false +}, +"retryOnFail": { +"type": "boolean", +"default": false +}, +"maxTries": { +"type": "integer", +"minimum": 1 +}, +"waitBetweenTries": { +"description": "Milliseconds.", +"type": "integer", +"minimum": 0 +}, +"continueOnFail": { +"type": "boolean", +"default": false +}, +"alwaysOutputData": { +"type": "boolean", +"default": false +}, +"executeOnce": { +"description": "If true, node executes only once per execution (engine-dependent).", +"type": "boolean", +"default": false +}, +"position": { +"$ref": "#/$defs/position" +}, +"parameters": { +"description": "Node-specific parameters. Typically JSON-serializable.", +"type": "object", +"additionalProperties": true, +"default": {} +}, +"credentials": { +"description": "Node-level credential references.", +"type": "object", +"additionalProperties": { +"$ref": "#/$defs/credentialRef" +}, +"default": {} +}, +"webhookId": { +"description": "Optional webhook id (for webhook-based trigger nodes).", +"type": "string" +}, +"onError": { +"description": "Node-level error routing policy (engine-dependent).", +"type": "string", +"enum": ["stopWorkflow", "continueRegularOutput", "continueErrorOutput"] +} +} +}, +"position": { +"type": "array", +"minItems": 2, +"maxItems": 2, +"items": { +"type": "number" +} +}, +"credentialRef": { +"type": "object", +"additionalProperties": false, +"required": ["id"], +"properties": { +"id": { +"description": "Credential id or stable key.", +"type": ["string", "integer"] +}, +"name": { +"description": "Optional human label.", +"type": "string" +} +} +}, +"credentialBinding": { +"type": "object", +"additionalProperties": false, +"required": ["nodeId", "credentialType", "credentialId"], +"properties": { +"nodeId": { "type": "string", "minLength": 1 }, +"credentialType": { "type": "string", "minLength": 1 }, +"credentialId": { "type": ["string", "integer"] } +} +}, +"connections": { +"description": "Adjacency map: fromNodeName -> outputType -> outputIndex -> array of targets.", +"type": "object", +"additionalProperties": { +"$ref": "#/$defs/nodeConnectionsByType" +}, +"default": {} +}, +"nodeConnectionsByType": { +"type": "object", +"additionalProperties": false, +"properties": { +"main": { +"$ref": "#/$defs/outputIndexMap" +}, +"error": { +"$ref": "#/$defs/outputIndexMap" +} +}, +"anyOf": [ +{ "required": ["main"] }, +{ "required": ["error"] } +] +}, +"outputIndexMap": { +"description": "Output index -> array of connection targets.", +"type": "object", +"additionalProperties": { +"type": "array", +"items": { "$ref": "#/$defs/connectionTarget" } +}, +"default": {} +}, +"connectionTarget": { +"type": "object", +"additionalProperties": false, +"required": ["node", "type", "index"], +"properties": { +"node": { +"description": "Target node name (n8n uses node 'name' in connections).", +"type": "string", +"minLength": 1 +}, +"type": { +"description": "Input type on target node (typically 'main' or 'error').", +"type": "string", +"minLength": 1 +}, +"index": { +"description": "Input index on target node.", +"type": "integer", +"minimum": 0 +} +} +} +} +} +``` + + +```json +example data: +{ +"id": "wf-001", +"name": "Example HTTP → Transform → Log", +"active": false, +"createdAt": "2026-01-10T10:15:00Z", +"updatedAt": "2026-01-10T10:20:00Z", +"tags": [ +{ "id": 1, "name": "example" }, +{ "id": 2, "name": "demo" } +], +"settings": { +"timezone": "Europe/London", +"executionTimeout": 300, +"saveExecutionProgress": true, +"saveDataErrorExecution": "all", +"saveDataSuccessExecution": "all" +}, +"nodes": [ +{ +"id": "node-1", +"name": "HTTP Request", +"type": "n8n-nodes-base.httpRequest", +"typeVersion": 4, +"position": [0, 0], +"parameters": { +"method": "GET", +"url": "https://api.example.com/users", +"responseFormat": "json" +}, +"credentials": { +"httpBasicAuth": { +"id": "cred-123", +"name": "Example API Auth" +} +} +}, +{ +"id": "node-2", +"name": "Transform Data", +"type": "n8n-nodes-base.function", +"typeVersion": 1, +"position": [300, 0], +"parameters": { +"code": "return items.map(i => ({ json: { id: i.json.id, email: i.json.email } }));" +} +}, +{ +"id": "node-3", +"name": "Log Output", +"type": "n8n-nodes-base.noOp", +"typeVersion": 1, +"position": [600, 0], +"parameters": {} +} +], +"connections": { +"HTTP Request": { +"main": { +"0": [ +{ +"node": "Transform Data", +"type": "main", +"index": 0 +} +] +} +}, +"Transform Data": { +"main": { +"0": [ +{ +"node": "Log Output", +"type": "main", +"index": 0 +} +] +} +} +}, +"staticData": {}, +"credentials": [ +{ +"nodeId": "node-1", +"credentialType": "httpBasicAuth", +"credentialId": "cred-123" +} +] +} +```