#
tokens: 36422/50000 2/614 files (page 41/59)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 41 of 59. Use http://codebase.md/czlonkowski/n8n-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── _config.yml
├── .claude
│   └── agents
│       ├── code-reviewer.md
│       ├── context-manager.md
│       ├── debugger.md
│       ├── deployment-engineer.md
│       ├── mcp-backend-engineer.md
│       ├── n8n-mcp-tester.md
│       ├── technical-researcher.md
│       └── test-automator.md
├── .dockerignore
├── .env.docker
├── .env.example
├── .env.n8n.example
├── .env.test
├── .env.test.example
├── .github
│   ├── ABOUT.md
│   ├── BENCHMARK_THRESHOLDS.md
│   ├── FUNDING.yml
│   ├── gh-pages.yml
│   ├── secret_scanning.yml
│   └── workflows
│       ├── benchmark-pr.yml
│       ├── benchmark.yml
│       ├── docker-build-fast.yml
│       ├── docker-build-n8n.yml
│       ├── docker-build.yml
│       ├── release.yml
│       ├── test.yml
│       └── update-n8n-deps.yml
├── .gitignore
├── .npmignore
├── ATTRIBUTION.md
├── CHANGELOG.md
├── CLAUDE.md
├── codecov.yml
├── coverage.json
├── data
│   ├── .gitkeep
│   ├── nodes.db
│   ├── nodes.db-shm
│   ├── nodes.db-wal
│   └── templates.db
├── deploy
│   └── quick-deploy-n8n.sh
├── docker
│   ├── docker-entrypoint.sh
│   ├── n8n-mcp
│   ├── parse-config.js
│   └── README.md
├── docker-compose.buildkit.yml
├── docker-compose.extract.yml
├── docker-compose.n8n.yml
├── docker-compose.override.yml.example
├── docker-compose.test-n8n.yml
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.railway
├── Dockerfile.test
├── docs
│   ├── AUTOMATED_RELEASES.md
│   ├── BENCHMARKS.md
│   ├── CHANGELOG.md
│   ├── CLAUDE_CODE_SETUP.md
│   ├── CLAUDE_INTERVIEW.md
│   ├── CODECOV_SETUP.md
│   ├── CODEX_SETUP.md
│   ├── CURSOR_SETUP.md
│   ├── DEPENDENCY_UPDATES.md
│   ├── DOCKER_README.md
│   ├── DOCKER_TROUBLESHOOTING.md
│   ├── FINAL_AI_VALIDATION_SPEC.md
│   ├── FLEXIBLE_INSTANCE_CONFIGURATION.md
│   ├── HTTP_DEPLOYMENT.md
│   ├── img
│   │   ├── cc_command.png
│   │   ├── cc_connected.png
│   │   ├── codex_connected.png
│   │   ├── cursor_tut.png
│   │   ├── Railway_api.png
│   │   ├── Railway_server_address.png
│   │   ├── vsc_ghcp_chat_agent_mode.png
│   │   ├── vsc_ghcp_chat_instruction_files.png
│   │   ├── vsc_ghcp_chat_thinking_tool.png
│   │   └── windsurf_tut.png
│   ├── INSTALLATION.md
│   ├── LIBRARY_USAGE.md
│   ├── local
│   │   ├── DEEP_DIVE_ANALYSIS_2025-10-02.md
│   │   ├── DEEP_DIVE_ANALYSIS_README.md
│   │   ├── Deep_dive_p1_p2.md
│   │   ├── integration-testing-plan.md
│   │   ├── integration-tests-phase1-summary.md
│   │   ├── N8N_AI_WORKFLOW_BUILDER_ANALYSIS.md
│   │   ├── P0_IMPLEMENTATION_PLAN.md
│   │   └── TEMPLATE_MINING_ANALYSIS.md
│   ├── MCP_ESSENTIALS_README.md
│   ├── MCP_QUICK_START_GUIDE.md
│   ├── N8N_DEPLOYMENT.md
│   ├── RAILWAY_DEPLOYMENT.md
│   ├── README_CLAUDE_SETUP.md
│   ├── README.md
│   ├── tools-documentation-usage.md
│   ├── VS_CODE_PROJECT_SETUP.md
│   ├── WINDSURF_SETUP.md
│   └── workflow-diff-examples.md
├── examples
│   └── enhanced-documentation-demo.js
├── fetch_log.txt
├── LICENSE
├── MEMORY_N8N_UPDATE.md
├── MEMORY_TEMPLATE_UPDATE.md
├── monitor_fetch.sh
├── N8N_HTTP_STREAMABLE_SETUP.md
├── n8n-nodes.db
├── P0-R3-TEST-PLAN.md
├── package-lock.json
├── package.json
├── package.runtime.json
├── PRIVACY.md
├── railway.json
├── README.md
├── renovate.json
├── scripts
│   ├── analyze-optimization.sh
│   ├── audit-schema-coverage.ts
│   ├── build-optimized.sh
│   ├── compare-benchmarks.js
│   ├── demo-optimization.sh
│   ├── deploy-http.sh
│   ├── deploy-to-vm.sh
│   ├── export-webhook-workflows.ts
│   ├── extract-changelog.js
│   ├── extract-from-docker.js
│   ├── extract-nodes-docker.sh
│   ├── extract-nodes-simple.sh
│   ├── format-benchmark-results.js
│   ├── generate-benchmark-stub.js
│   ├── generate-detailed-reports.js
│   ├── generate-test-summary.js
│   ├── http-bridge.js
│   ├── mcp-http-client.js
│   ├── migrate-nodes-fts.ts
│   ├── migrate-tool-docs.ts
│   ├── n8n-docs-mcp.service
│   ├── nginx-n8n-mcp.conf
│   ├── prebuild-fts5.ts
│   ├── prepare-release.js
│   ├── publish-npm-quick.sh
│   ├── publish-npm.sh
│   ├── quick-test.ts
│   ├── run-benchmarks-ci.js
│   ├── sync-runtime-version.js
│   ├── test-ai-validation-debug.ts
│   ├── test-code-node-enhancements.ts
│   ├── test-code-node-fixes.ts
│   ├── test-docker-config.sh
│   ├── test-docker-fingerprint.ts
│   ├── test-docker-optimization.sh
│   ├── test-docker.sh
│   ├── test-empty-connection-validation.ts
│   ├── test-error-message-tracking.ts
│   ├── test-error-output-validation.ts
│   ├── test-error-validation.js
│   ├── test-essentials.ts
│   ├── test-expression-code-validation.ts
│   ├── test-expression-format-validation.js
│   ├── test-fts5-search.ts
│   ├── test-fuzzy-fix.ts
│   ├── test-fuzzy-simple.ts
│   ├── test-helpers-validation.ts
│   ├── test-http-search.ts
│   ├── test-http.sh
│   ├── test-jmespath-validation.ts
│   ├── test-multi-tenant-simple.ts
│   ├── test-multi-tenant.ts
│   ├── test-n8n-integration.sh
│   ├── test-node-info.js
│   ├── test-node-type-validation.ts
│   ├── test-nodes-base-prefix.ts
│   ├── test-operation-validation.ts
│   ├── test-optimized-docker.sh
│   ├── test-release-automation.js
│   ├── test-search-improvements.ts
│   ├── test-security.ts
│   ├── test-single-session.sh
│   ├── test-sqljs-triggers.ts
│   ├── test-telemetry-debug.ts
│   ├── test-telemetry-direct.ts
│   ├── test-telemetry-env.ts
│   ├── test-telemetry-integration.ts
│   ├── test-telemetry-no-select.ts
│   ├── test-telemetry-security.ts
│   ├── test-telemetry-simple.ts
│   ├── test-typeversion-validation.ts
│   ├── test-url-configuration.ts
│   ├── test-user-id-persistence.ts
│   ├── test-webhook-validation.ts
│   ├── test-workflow-insert.ts
│   ├── test-workflow-sanitizer.ts
│   ├── test-workflow-tracking-debug.ts
│   ├── update-and-publish-prep.sh
│   ├── update-n8n-deps.js
│   ├── update-readme-version.js
│   ├── vitest-benchmark-json-reporter.js
│   └── vitest-benchmark-reporter.ts
├── SECURITY.md
├── src
│   ├── config
│   │   └── n8n-api.ts
│   ├── data
│   │   └── canonical-ai-tool-examples.json
│   ├── database
│   │   ├── database-adapter.ts
│   │   ├── migrations
│   │   │   └── add-template-node-configs.sql
│   │   ├── node-repository.ts
│   │   ├── nodes.db
│   │   ├── schema-optimized.sql
│   │   └── schema.sql
│   ├── errors
│   │   └── validation-service-error.ts
│   ├── http-server-single-session.ts
│   ├── http-server.ts
│   ├── index.ts
│   ├── loaders
│   │   └── node-loader.ts
│   ├── mappers
│   │   └── docs-mapper.ts
│   ├── mcp
│   │   ├── handlers-n8n-manager.ts
│   │   ├── handlers-workflow-diff.ts
│   │   ├── index.ts
│   │   ├── server.ts
│   │   ├── stdio-wrapper.ts
│   │   ├── tool-docs
│   │   │   ├── configuration
│   │   │   │   ├── get-node-as-tool-info.ts
│   │   │   │   ├── get-node-documentation.ts
│   │   │   │   ├── get-node-essentials.ts
│   │   │   │   ├── get-node-info.ts
│   │   │   │   ├── get-property-dependencies.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── search-node-properties.ts
│   │   │   ├── discovery
│   │   │   │   ├── get-database-statistics.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-ai-tools.ts
│   │   │   │   ├── list-nodes.ts
│   │   │   │   └── search-nodes.ts
│   │   │   ├── guides
│   │   │   │   ├── ai-agents-guide.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── system
│   │   │   │   ├── index.ts
│   │   │   │   ├── n8n-diagnostic.ts
│   │   │   │   ├── n8n-health-check.ts
│   │   │   │   ├── n8n-list-available-tools.ts
│   │   │   │   └── tools-documentation.ts
│   │   │   ├── templates
│   │   │   │   ├── get-template.ts
│   │   │   │   ├── get-templates-for-task.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-node-templates.ts
│   │   │   │   ├── list-tasks.ts
│   │   │   │   ├── search-templates-by-metadata.ts
│   │   │   │   └── search-templates.ts
│   │   │   ├── types.ts
│   │   │   ├── validation
│   │   │   │   ├── index.ts
│   │   │   │   ├── validate-node-minimal.ts
│   │   │   │   ├── validate-node-operation.ts
│   │   │   │   ├── validate-workflow-connections.ts
│   │   │   │   ├── validate-workflow-expressions.ts
│   │   │   │   └── validate-workflow.ts
│   │   │   └── workflow_management
│   │   │       ├── index.ts
│   │   │       ├── n8n-autofix-workflow.ts
│   │   │       ├── n8n-create-workflow.ts
│   │   │       ├── n8n-delete-execution.ts
│   │   │       ├── n8n-delete-workflow.ts
│   │   │       ├── n8n-get-execution.ts
│   │   │       ├── n8n-get-workflow-details.ts
│   │   │       ├── n8n-get-workflow-minimal.ts
│   │   │       ├── n8n-get-workflow-structure.ts
│   │   │       ├── n8n-get-workflow.ts
│   │   │       ├── n8n-list-executions.ts
│   │   │       ├── n8n-list-workflows.ts
│   │   │       ├── n8n-trigger-webhook-workflow.ts
│   │   │       ├── n8n-update-full-workflow.ts
│   │   │       ├── n8n-update-partial-workflow.ts
│   │   │       └── n8n-validate-workflow.ts
│   │   ├── tools-documentation.ts
│   │   ├── tools-n8n-friendly.ts
│   │   ├── tools-n8n-manager.ts
│   │   ├── tools.ts
│   │   └── workflow-examples.ts
│   ├── mcp-engine.ts
│   ├── mcp-tools-engine.ts
│   ├── n8n
│   │   ├── MCPApi.credentials.ts
│   │   └── MCPNode.node.ts
│   ├── parsers
│   │   ├── node-parser.ts
│   │   ├── property-extractor.ts
│   │   └── simple-parser.ts
│   ├── scripts
│   │   ├── debug-http-search.ts
│   │   ├── extract-from-docker.ts
│   │   ├── fetch-templates-robust.ts
│   │   ├── fetch-templates.ts
│   │   ├── rebuild-database.ts
│   │   ├── rebuild-optimized.ts
│   │   ├── rebuild.ts
│   │   ├── sanitize-templates.ts
│   │   ├── seed-canonical-ai-examples.ts
│   │   ├── test-autofix-documentation.ts
│   │   ├── test-autofix-workflow.ts
│   │   ├── test-execution-filtering.ts
│   │   ├── test-node-suggestions.ts
│   │   ├── test-protocol-negotiation.ts
│   │   ├── test-summary.ts
│   │   ├── test-webhook-autofix.ts
│   │   ├── validate.ts
│   │   └── validation-summary.ts
│   ├── services
│   │   ├── ai-node-validator.ts
│   │   ├── ai-tool-validators.ts
│   │   ├── confidence-scorer.ts
│   │   ├── config-validator.ts
│   │   ├── enhanced-config-validator.ts
│   │   ├── example-generator.ts
│   │   ├── execution-processor.ts
│   │   ├── expression-format-validator.ts
│   │   ├── expression-validator.ts
│   │   ├── n8n-api-client.ts
│   │   ├── n8n-validation.ts
│   │   ├── node-documentation-service.ts
│   │   ├── node-similarity-service.ts
│   │   ├── node-specific-validators.ts
│   │   ├── operation-similarity-service.ts
│   │   ├── property-dependencies.ts
│   │   ├── property-filter.ts
│   │   ├── resource-similarity-service.ts
│   │   ├── sqlite-storage-service.ts
│   │   ├── task-templates.ts
│   │   ├── universal-expression-validator.ts
│   │   ├── workflow-auto-fixer.ts
│   │   ├── workflow-diff-engine.ts
│   │   └── workflow-validator.ts
│   ├── telemetry
│   │   ├── batch-processor.ts
│   │   ├── config-manager.ts
│   │   ├── early-error-logger.ts
│   │   ├── error-sanitization-utils.ts
│   │   ├── error-sanitizer.ts
│   │   ├── event-tracker.ts
│   │   ├── event-validator.ts
│   │   ├── index.ts
│   │   ├── performance-monitor.ts
│   │   ├── rate-limiter.ts
│   │   ├── startup-checkpoints.ts
│   │   ├── telemetry-error.ts
│   │   ├── telemetry-manager.ts
│   │   ├── telemetry-types.ts
│   │   └── workflow-sanitizer.ts
│   ├── templates
│   │   ├── batch-processor.ts
│   │   ├── metadata-generator.ts
│   │   ├── README.md
│   │   ├── template-fetcher.ts
│   │   ├── template-repository.ts
│   │   └── template-service.ts
│   ├── types
│   │   ├── index.ts
│   │   ├── instance-context.ts
│   │   ├── n8n-api.ts
│   │   ├── node-types.ts
│   │   └── workflow-diff.ts
│   └── utils
│       ├── auth.ts
│       ├── bridge.ts
│       ├── cache-utils.ts
│       ├── console-manager.ts
│       ├── documentation-fetcher.ts
│       ├── enhanced-documentation-fetcher.ts
│       ├── error-handler.ts
│       ├── example-generator.ts
│       ├── fixed-collection-validator.ts
│       ├── logger.ts
│       ├── mcp-client.ts
│       ├── n8n-errors.ts
│       ├── node-source-extractor.ts
│       ├── node-type-normalizer.ts
│       ├── node-type-utils.ts
│       ├── node-utils.ts
│       ├── npm-version-checker.ts
│       ├── protocol-version.ts
│       ├── simple-cache.ts
│       ├── ssrf-protection.ts
│       ├── template-node-resolver.ts
│       ├── template-sanitizer.ts
│       ├── url-detector.ts
│       ├── validation-schemas.ts
│       └── version.ts
├── test-output.txt
├── test-reinit-fix.sh
├── tests
│   ├── __snapshots__
│   │   └── .gitkeep
│   ├── auth.test.ts
│   ├── benchmarks
│   │   ├── database-queries.bench.ts
│   │   ├── index.ts
│   │   ├── mcp-tools.bench.ts
│   │   ├── mcp-tools.bench.ts.disabled
│   │   ├── mcp-tools.bench.ts.skip
│   │   ├── node-loading.bench.ts.disabled
│   │   ├── README.md
│   │   ├── search-operations.bench.ts.disabled
│   │   └── validation-performance.bench.ts.disabled
│   ├── bridge.test.ts
│   ├── comprehensive-extraction-test.js
│   ├── data
│   │   └── .gitkeep
│   ├── debug-slack-doc.js
│   ├── demo-enhanced-documentation.js
│   ├── docker-tests-README.md
│   ├── error-handler.test.ts
│   ├── examples
│   │   └── using-database-utils.test.ts
│   ├── extracted-nodes-db
│   │   ├── database-import.json
│   │   ├── extraction-report.json
│   │   ├── insert-nodes.sql
│   │   ├── n8n-nodes-base__Airtable.json
│   │   ├── n8n-nodes-base__Discord.json
│   │   ├── n8n-nodes-base__Function.json
│   │   ├── n8n-nodes-base__HttpRequest.json
│   │   ├── n8n-nodes-base__If.json
│   │   ├── n8n-nodes-base__Slack.json
│   │   ├── n8n-nodes-base__SplitInBatches.json
│   │   └── n8n-nodes-base__Webhook.json
│   ├── factories
│   │   ├── node-factory.ts
│   │   └── property-definition-factory.ts
│   ├── fixtures
│   │   ├── .gitkeep
│   │   ├── database
│   │   │   └── test-nodes.json
│   │   ├── factories
│   │   │   ├── node.factory.ts
│   │   │   └── parser-node.factory.ts
│   │   └── template-configs.ts
│   ├── helpers
│   │   └── env-helpers.ts
│   ├── http-server-auth.test.ts
│   ├── integration
│   │   ├── ai-validation
│   │   │   ├── ai-agent-validation.test.ts
│   │   │   ├── ai-tool-validation.test.ts
│   │   │   ├── chat-trigger-validation.test.ts
│   │   │   ├── e2e-validation.test.ts
│   │   │   ├── helpers.ts
│   │   │   ├── llm-chain-validation.test.ts
│   │   │   ├── README.md
│   │   │   └── TEST_REPORT.md
│   │   ├── ci
│   │   │   └── database-population.test.ts
│   │   ├── database
│   │   │   ├── connection-management.test.ts
│   │   │   ├── empty-database.test.ts
│   │   │   ├── fts5-search.test.ts
│   │   │   ├── node-fts5-search.test.ts
│   │   │   ├── node-repository.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── template-node-configs.test.ts
│   │   │   ├── template-repository.test.ts
│   │   │   ├── test-utils.ts
│   │   │   └── transactions.test.ts
│   │   ├── database-integration.test.ts
│   │   ├── docker
│   │   │   ├── docker-config.test.ts
│   │   │   ├── docker-entrypoint.test.ts
│   │   │   └── test-helpers.ts
│   │   ├── flexible-instance-config.test.ts
│   │   ├── mcp
│   │   │   └── template-examples-e2e.test.ts
│   │   ├── mcp-protocol
│   │   │   ├── basic-connection.test.ts
│   │   │   ├── error-handling.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── protocol-compliance.test.ts
│   │   │   ├── README.md
│   │   │   ├── session-management.test.ts
│   │   │   ├── test-helpers.ts
│   │   │   ├── tool-invocation.test.ts
│   │   │   └── workflow-error-validation.test.ts
│   │   ├── msw-setup.test.ts
│   │   ├── n8n-api
│   │   │   ├── executions
│   │   │   │   ├── delete-execution.test.ts
│   │   │   │   ├── get-execution.test.ts
│   │   │   │   ├── list-executions.test.ts
│   │   │   │   └── trigger-webhook.test.ts
│   │   │   ├── scripts
│   │   │   │   └── cleanup-orphans.ts
│   │   │   ├── system
│   │   │   │   ├── diagnostic.test.ts
│   │   │   │   ├── health-check.test.ts
│   │   │   │   └── list-tools.test.ts
│   │   │   ├── test-connection.ts
│   │   │   ├── types
│   │   │   │   └── mcp-responses.ts
│   │   │   ├── utils
│   │   │   │   ├── cleanup-helpers.ts
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── factories.ts
│   │   │   │   ├── fixtures.ts
│   │   │   │   ├── mcp-context.ts
│   │   │   │   ├── n8n-client.ts
│   │   │   │   ├── node-repository.ts
│   │   │   │   ├── response-types.ts
│   │   │   │   ├── test-context.ts
│   │   │   │   └── webhook-workflows.ts
│   │   │   └── workflows
│   │   │       ├── autofix-workflow.test.ts
│   │   │       ├── create-workflow.test.ts
│   │   │       ├── delete-workflow.test.ts
│   │   │       ├── get-workflow-details.test.ts
│   │   │       ├── get-workflow-minimal.test.ts
│   │   │       ├── get-workflow-structure.test.ts
│   │   │       ├── get-workflow.test.ts
│   │   │       ├── list-workflows.test.ts
│   │   │       ├── smart-parameters.test.ts
│   │   │       ├── update-partial-workflow.test.ts
│   │   │       ├── update-workflow.test.ts
│   │   │       └── validate-workflow.test.ts
│   │   ├── security
│   │   │   ├── command-injection-prevention.test.ts
│   │   │   └── rate-limiting.test.ts
│   │   ├── setup
│   │   │   ├── integration-setup.ts
│   │   │   └── msw-test-server.ts
│   │   ├── telemetry
│   │   │   ├── docker-user-id-stability.test.ts
│   │   │   └── mcp-telemetry.test.ts
│   │   ├── templates
│   │   │   └── metadata-operations.test.ts
│   │   └── workflow-creation-node-type-format.test.ts
│   ├── logger.test.ts
│   ├── MOCKING_STRATEGY.md
│   ├── mocks
│   │   ├── n8n-api
│   │   │   ├── data
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── executions.ts
│   │   │   │   └── workflows.ts
│   │   │   ├── handlers.ts
│   │   │   └── index.ts
│   │   └── README.md
│   ├── node-storage-export.json
│   ├── setup
│   │   ├── global-setup.ts
│   │   ├── msw-setup.ts
│   │   ├── TEST_ENV_DOCUMENTATION.md
│   │   └── test-env.ts
│   ├── test-database-extraction.js
│   ├── test-direct-extraction.js
│   ├── test-enhanced-documentation.js
│   ├── test-enhanced-integration.js
│   ├── test-mcp-extraction.js
│   ├── test-mcp-server-extraction.js
│   ├── test-mcp-tools-integration.js
│   ├── test-node-documentation-service.js
│   ├── test-node-list.js
│   ├── test-package-info.js
│   ├── test-parsing-operations.js
│   ├── test-slack-node-complete.js
│   ├── test-small-rebuild.js
│   ├── test-sqlite-search.js
│   ├── test-storage-system.js
│   ├── unit
│   │   ├── __mocks__
│   │   │   ├── n8n-nodes-base.test.ts
│   │   │   ├── n8n-nodes-base.ts
│   │   │   └── README.md
│   │   ├── database
│   │   │   ├── __mocks__
│   │   │   │   └── better-sqlite3.ts
│   │   │   ├── database-adapter-unit.test.ts
│   │   │   ├── node-repository-core.test.ts
│   │   │   ├── node-repository-operations.test.ts
│   │   │   ├── node-repository-outputs.test.ts
│   │   │   ├── README.md
│   │   │   └── template-repository-core.test.ts
│   │   ├── docker
│   │   │   ├── config-security.test.ts
│   │   │   ├── edge-cases.test.ts
│   │   │   ├── parse-config.test.ts
│   │   │   └── serve-command.test.ts
│   │   ├── errors
│   │   │   └── validation-service-error.test.ts
│   │   ├── examples
│   │   │   └── using-n8n-nodes-base-mock.test.ts
│   │   ├── flexible-instance-security-advanced.test.ts
│   │   ├── flexible-instance-security.test.ts
│   │   ├── http-server
│   │   │   └── multi-tenant-support.test.ts
│   │   ├── http-server-n8n-mode.test.ts
│   │   ├── http-server-n8n-reinit.test.ts
│   │   ├── http-server-session-management.test.ts
│   │   ├── loaders
│   │   │   └── node-loader.test.ts
│   │   ├── mappers
│   │   │   └── docs-mapper.test.ts
│   │   ├── mcp
│   │   │   ├── get-node-essentials-examples.test.ts
│   │   │   ├── handlers-n8n-manager-simple.test.ts
│   │   │   ├── handlers-n8n-manager.test.ts
│   │   │   ├── handlers-workflow-diff.test.ts
│   │   │   ├── lru-cache-behavior.test.ts
│   │   │   ├── multi-tenant-tool-listing.test.ts.disabled
│   │   │   ├── parameter-validation.test.ts
│   │   │   ├── search-nodes-examples.test.ts
│   │   │   ├── tools-documentation.test.ts
│   │   │   └── tools.test.ts
│   │   ├── monitoring
│   │   │   └── cache-metrics.test.ts
│   │   ├── MULTI_TENANT_TEST_COVERAGE.md
│   │   ├── multi-tenant-integration.test.ts
│   │   ├── parsers
│   │   │   ├── node-parser-outputs.test.ts
│   │   │   ├── node-parser.test.ts
│   │   │   ├── property-extractor.test.ts
│   │   │   └── simple-parser.test.ts
│   │   ├── scripts
│   │   │   └── fetch-templates-extraction.test.ts
│   │   ├── services
│   │   │   ├── ai-node-validator.test.ts
│   │   │   ├── ai-tool-validators.test.ts
│   │   │   ├── confidence-scorer.test.ts
│   │   │   ├── config-validator-basic.test.ts
│   │   │   ├── config-validator-edge-cases.test.ts
│   │   │   ├── config-validator-node-specific.test.ts
│   │   │   ├── config-validator-security.test.ts
│   │   │   ├── debug-validator.test.ts
│   │   │   ├── enhanced-config-validator-integration.test.ts
│   │   │   ├── enhanced-config-validator-operations.test.ts
│   │   │   ├── enhanced-config-validator.test.ts
│   │   │   ├── example-generator.test.ts
│   │   │   ├── execution-processor.test.ts
│   │   │   ├── expression-format-validator.test.ts
│   │   │   ├── expression-validator-edge-cases.test.ts
│   │   │   ├── expression-validator.test.ts
│   │   │   ├── fixed-collection-validation.test.ts
│   │   │   ├── loop-output-edge-cases.test.ts
│   │   │   ├── n8n-api-client.test.ts
│   │   │   ├── n8n-validation.test.ts
│   │   │   ├── node-similarity-service.test.ts
│   │   │   ├── node-specific-validators.test.ts
│   │   │   ├── operation-similarity-service-comprehensive.test.ts
│   │   │   ├── operation-similarity-service.test.ts
│   │   │   ├── property-dependencies.test.ts
│   │   │   ├── property-filter-edge-cases.test.ts
│   │   │   ├── property-filter.test.ts
│   │   │   ├── resource-similarity-service-comprehensive.test.ts
│   │   │   ├── resource-similarity-service.test.ts
│   │   │   ├── task-templates.test.ts
│   │   │   ├── template-service.test.ts
│   │   │   ├── universal-expression-validator.test.ts
│   │   │   ├── validation-fixes.test.ts
│   │   │   ├── workflow-auto-fixer.test.ts
│   │   │   ├── workflow-diff-engine.test.ts
│   │   │   ├── workflow-fixed-collection-validation.test.ts
│   │   │   ├── workflow-validator-comprehensive.test.ts
│   │   │   ├── workflow-validator-edge-cases.test.ts
│   │   │   ├── workflow-validator-error-outputs.test.ts
│   │   │   ├── workflow-validator-expression-format.test.ts
│   │   │   ├── workflow-validator-loops-simple.test.ts
│   │   │   ├── workflow-validator-loops.test.ts
│   │   │   ├── workflow-validator-mocks.test.ts
│   │   │   ├── workflow-validator-performance.test.ts
│   │   │   ├── workflow-validator-with-mocks.test.ts
│   │   │   └── workflow-validator.test.ts
│   │   ├── telemetry
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── config-manager.test.ts
│   │   │   ├── event-tracker.test.ts
│   │   │   ├── event-validator.test.ts
│   │   │   ├── rate-limiter.test.ts
│   │   │   ├── telemetry-error.test.ts
│   │   │   ├── telemetry-manager.test.ts
│   │   │   ├── v2.18.3-fixes-verification.test.ts
│   │   │   └── workflow-sanitizer.test.ts
│   │   ├── templates
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── metadata-generator.test.ts
│   │   │   ├── template-repository-metadata.test.ts
│   │   │   └── template-repository-security.test.ts
│   │   ├── test-env-example.test.ts
│   │   ├── test-infrastructure.test.ts
│   │   ├── types
│   │   │   ├── instance-context-coverage.test.ts
│   │   │   └── instance-context-multi-tenant.test.ts
│   │   ├── utils
│   │   │   ├── auth-timing-safe.test.ts
│   │   │   ├── cache-utils.test.ts
│   │   │   ├── console-manager.test.ts
│   │   │   ├── database-utils.test.ts
│   │   │   ├── fixed-collection-validator.test.ts
│   │   │   ├── n8n-errors.test.ts
│   │   │   ├── node-type-normalizer.test.ts
│   │   │   ├── node-type-utils.test.ts
│   │   │   ├── node-utils.test.ts
│   │   │   ├── simple-cache-memory-leak-fix.test.ts
│   │   │   ├── ssrf-protection.test.ts
│   │   │   └── template-node-resolver.test.ts
│   │   └── validation-fixes.test.ts
│   └── utils
│       ├── assertions.ts
│       ├── builders
│       │   └── workflow.builder.ts
│       ├── data-generators.ts
│       ├── database-utils.ts
│       ├── README.md
│       └── test-helpers.ts
├── thumbnail.png
├── tsconfig.build.json
├── tsconfig.json
├── types
│   ├── mcp.d.ts
│   └── test-env.d.ts
├── verify-telemetry-fix.js
├── versioned-nodes.md
├── vitest.config.benchmark.ts
├── vitest.config.integration.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/tests/unit/services/n8n-validation.test.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import { describe, it, expect, beforeEach, vi } from 'vitest';
   2 | import {
   3 |   workflowNodeSchema,
   4 |   workflowConnectionSchema,
   5 |   workflowSettingsSchema,
   6 |   defaultWorkflowSettings,
   7 |   validateWorkflowNode,
   8 |   validateWorkflowConnections,
   9 |   validateWorkflowSettings,
  10 |   cleanWorkflowForCreate,
  11 |   cleanWorkflowForUpdate,
  12 |   validateWorkflowStructure,
  13 |   hasWebhookTrigger,
  14 |   getWebhookUrl,
  15 |   getWorkflowStructureExample,
  16 |   getWorkflowFixSuggestions,
  17 | } from '../../../src/services/n8n-validation';
  18 | import { WorkflowBuilder } from '../../utils/builders/workflow.builder';
  19 | import { z } from 'zod';
  20 | import { WorkflowNode, WorkflowConnection, Workflow } from '../../../src/types/n8n-api';
  21 | 
  22 | describe('n8n-validation', () => {
  23 |   describe('Zod Schemas', () => {
  24 |     describe('workflowNodeSchema', () => {
  25 |       it('should validate a complete valid node', () => {
  26 |         const validNode = {
  27 |           id: 'node-1',
  28 |           name: 'Test Node',
  29 |           type: 'n8n-nodes-base.set',
  30 |           typeVersion: 3,
  31 |           position: [100, 200],
  32 |           parameters: { key: 'value' },
  33 |           credentials: { api: 'cred-id' },
  34 |           disabled: false,
  35 |           notes: 'Test notes',
  36 |           notesInFlow: true,
  37 |           continueOnFail: true,
  38 |           retryOnFail: true,
  39 |           maxTries: 3,
  40 |           waitBetweenTries: 1000,
  41 |           alwaysOutputData: true,
  42 |           executeOnce: false,
  43 |         };
  44 | 
  45 |         const result = workflowNodeSchema.parse(validNode);
  46 |         expect(result).toEqual(validNode);
  47 |       });
  48 | 
  49 |       it('should validate a minimal valid node', () => {
  50 |         const minimalNode = {
  51 |           id: 'node-1',
  52 |           name: 'Test Node',
  53 |           type: 'n8n-nodes-base.set',
  54 |           typeVersion: 3,
  55 |           position: [100, 200],
  56 |           parameters: {},
  57 |         };
  58 | 
  59 |         const result = workflowNodeSchema.parse(minimalNode);
  60 |         expect(result).toEqual(minimalNode);
  61 |       });
  62 | 
  63 |       it('should reject node with missing required fields', () => {
  64 |         const invalidNode = {
  65 |           name: 'Test Node',
  66 |           type: 'n8n-nodes-base.set',
  67 |         };
  68 | 
  69 |         expect(() => workflowNodeSchema.parse(invalidNode)).toThrow();
  70 |       });
  71 | 
  72 |       it('should reject node with invalid position format', () => {
  73 |         const invalidNode = {
  74 |           id: 'node-1',
  75 |           name: 'Test Node',
  76 |           type: 'n8n-nodes-base.set',
  77 |           typeVersion: 3,
  78 |           position: [100], // Should be tuple of 2 numbers
  79 |           parameters: {},
  80 |         };
  81 | 
  82 |         expect(() => workflowNodeSchema.parse(invalidNode)).toThrow();
  83 |       });
  84 | 
  85 |       it('should reject node with invalid type values', () => {
  86 |         const invalidNode = {
  87 |           id: 'node-1',
  88 |           name: 'Test Node',
  89 |           type: 'n8n-nodes-base.set',
  90 |           typeVersion: '3', // Should be number
  91 |           position: [100, 200],
  92 |           parameters: {},
  93 |         };
  94 | 
  95 |         expect(() => workflowNodeSchema.parse(invalidNode)).toThrow();
  96 |       });
  97 |     });
  98 | 
  99 |     describe('workflowConnectionSchema', () => {
 100 |       it('should validate valid connections', () => {
 101 |         const validConnections = {
 102 |           'node-1': {
 103 |             main: [[{ node: 'node-2', type: 'main', index: 0 }]],
 104 |           },
 105 |           'node-2': {
 106 |             main: [
 107 |               [
 108 |                 { node: 'node-3', type: 'main', index: 0 },
 109 |                 { node: 'node-4', type: 'main', index: 0 },
 110 |               ],
 111 |             ],
 112 |           },
 113 |         };
 114 | 
 115 |         const result = workflowConnectionSchema.parse(validConnections);
 116 |         expect(result).toEqual(validConnections);
 117 |       });
 118 | 
 119 |       it('should validate empty connections', () => {
 120 |         const emptyConnections = {};
 121 |         const result = workflowConnectionSchema.parse(emptyConnections);
 122 |         expect(result).toEqual(emptyConnections);
 123 |       });
 124 | 
 125 |       it('should reject invalid connection structure', () => {
 126 |         const invalidConnections = {
 127 |           'node-1': {
 128 |             main: [{ node: 'node-2', type: 'main', index: 0 }], // Should be array of arrays
 129 |           },
 130 |         };
 131 | 
 132 |         expect(() => workflowConnectionSchema.parse(invalidConnections)).toThrow();
 133 |       });
 134 | 
 135 |       it('should reject connections missing required fields', () => {
 136 |         const invalidConnections = {
 137 |           'node-1': {
 138 |             main: [[{ node: 'node-2' }]], // Missing type and index
 139 |           },
 140 |         };
 141 | 
 142 |         expect(() => workflowConnectionSchema.parse(invalidConnections)).toThrow();
 143 |       });
 144 |     });
 145 | 
 146 |     describe('workflowSettingsSchema', () => {
 147 |       it('should validate complete settings', () => {
 148 |         const completeSettings = {
 149 |           executionOrder: 'v1' as const,
 150 |           timezone: 'America/New_York',
 151 |           saveDataErrorExecution: 'all' as const,
 152 |           saveDataSuccessExecution: 'all' as const,
 153 |           saveManualExecutions: true,
 154 |           saveExecutionProgress: true,
 155 |           executionTimeout: 300,
 156 |           errorWorkflow: 'error-handler-workflow',
 157 |         };
 158 | 
 159 |         const result = workflowSettingsSchema.parse(completeSettings);
 160 |         expect(result).toEqual(completeSettings);
 161 |       });
 162 | 
 163 |       it('should apply defaults for missing fields', () => {
 164 |         const minimalSettings = {};
 165 |         const result = workflowSettingsSchema.parse(minimalSettings);
 166 |         
 167 |         expect(result).toEqual({
 168 |           executionOrder: 'v1',
 169 |           saveDataErrorExecution: 'all',
 170 |           saveDataSuccessExecution: 'all',
 171 |           saveManualExecutions: true,
 172 |           saveExecutionProgress: true,
 173 |         });
 174 |       });
 175 | 
 176 |       it('should reject invalid enum values', () => {
 177 |         const invalidSettings = {
 178 |           executionOrder: 'v2', // Invalid enum value
 179 |         };
 180 | 
 181 |         expect(() => workflowSettingsSchema.parse(invalidSettings)).toThrow();
 182 |       });
 183 |     });
 184 |   });
 185 | 
 186 |   describe('Validation Functions', () => {
 187 |     describe('validateWorkflowNode', () => {
 188 |       it('should validate and return a valid node', () => {
 189 |         const node = {
 190 |           id: 'test-1',
 191 |           name: 'Test',
 192 |           type: 'n8n-nodes-base.webhook',
 193 |           typeVersion: 2,
 194 |           position: [250, 300] as [number, number],
 195 |           parameters: {},
 196 |         };
 197 | 
 198 |         const result = validateWorkflowNode(node);
 199 |         expect(result).toEqual(node);
 200 |       });
 201 | 
 202 |       it('should throw for invalid node', () => {
 203 |         const invalidNode = { name: 'Test' };
 204 |         expect(() => validateWorkflowNode(invalidNode)).toThrow();
 205 |       });
 206 |     });
 207 | 
 208 |     describe('validateWorkflowConnections', () => {
 209 |       it('should validate and return valid connections', () => {
 210 |         const connections = {
 211 |           'Node1': {
 212 |             main: [[{ node: 'Node2', type: 'main', index: 0 }]],
 213 |           },
 214 |         };
 215 | 
 216 |         const result = validateWorkflowConnections(connections);
 217 |         expect(result).toEqual(connections);
 218 |       });
 219 | 
 220 |       it('should throw for invalid connections', () => {
 221 |         const invalidConnections = {
 222 |           'Node1': {
 223 |             main: 'invalid', // Should be array
 224 |           },
 225 |         };
 226 | 
 227 |         expect(() => validateWorkflowConnections(invalidConnections)).toThrow();
 228 |       });
 229 |     });
 230 | 
 231 |     describe('validateWorkflowSettings', () => {
 232 |       it('should validate and return valid settings', () => {
 233 |         const settings = {
 234 |           executionOrder: 'v1' as const,
 235 |           timezone: 'UTC',
 236 |         };
 237 | 
 238 |         const result = validateWorkflowSettings(settings);
 239 |         expect(result).toMatchObject(settings);
 240 |       });
 241 | 
 242 |       it('should apply defaults and validate', () => {
 243 |         const result = validateWorkflowSettings({});
 244 |         expect(result).toMatchObject(defaultWorkflowSettings);
 245 |       });
 246 |     });
 247 |   });
 248 | 
 249 |   describe('Workflow Cleaning Functions', () => {
 250 |     describe('cleanWorkflowForCreate', () => {
 251 |       it('should remove read-only fields', () => {
 252 |         const workflow = {
 253 |           id: 'should-be-removed',
 254 |           name: 'Test Workflow',
 255 |           nodes: [],
 256 |           connections: {},
 257 |           createdAt: '2023-01-01',
 258 |           updatedAt: '2023-01-01',
 259 |           versionId: 'v123',
 260 |           meta: { test: 'data' },
 261 |           active: true,
 262 |           tags: ['tag1'],
 263 |         };
 264 | 
 265 |         const cleaned = cleanWorkflowForCreate(workflow as any);
 266 |         
 267 |         expect(cleaned).not.toHaveProperty('id');
 268 |         expect(cleaned).not.toHaveProperty('createdAt');
 269 |         expect(cleaned).not.toHaveProperty('updatedAt');
 270 |         expect(cleaned).not.toHaveProperty('versionId');
 271 |         expect(cleaned).not.toHaveProperty('meta');
 272 |         expect(cleaned).not.toHaveProperty('active');
 273 |         expect(cleaned).not.toHaveProperty('tags');
 274 |         expect(cleaned.name).toBe('Test Workflow');
 275 |       });
 276 | 
 277 |       it('should add default settings if not present', () => {
 278 |         const workflow = {
 279 |           name: 'Test Workflow',
 280 |           nodes: [],
 281 |           connections: {},
 282 |         };
 283 | 
 284 |         const cleaned = cleanWorkflowForCreate(workflow as Workflow);
 285 |         expect(cleaned.settings).toEqual(defaultWorkflowSettings);
 286 |       });
 287 | 
 288 |       it('should preserve existing settings', () => {
 289 |         const customSettings = {
 290 |           executionOrder: 'v0' as const,
 291 |           timezone: 'America/New_York',
 292 |         };
 293 | 
 294 |         const workflow = {
 295 |           name: 'Test Workflow',
 296 |           nodes: [],
 297 |           connections: {},
 298 |           settings: customSettings,
 299 |         };
 300 | 
 301 |         const cleaned = cleanWorkflowForCreate(workflow as Workflow);
 302 |         expect(cleaned.settings).toEqual(customSettings);
 303 |       });
 304 |     });
 305 | 
 306 |     describe('cleanWorkflowForUpdate', () => {
 307 |       it('should remove all read-only and computed fields', () => {
 308 |         const workflow = {
 309 |           id: 'keep-id',
 310 |           name: 'Updated Workflow',
 311 |           nodes: [],
 312 |           connections: {},
 313 |           createdAt: '2023-01-01',
 314 |           updatedAt: '2023-01-01',
 315 |           versionId: 'v123',
 316 |           meta: { test: 'data' },
 317 |           staticData: { some: 'data' },
 318 |           pinData: { pin: 'data' },
 319 |           tags: ['tag1'],
 320 |           isArchived: false,
 321 |           usedCredentials: ['cred1'],
 322 |           sharedWithProjects: ['proj1'],
 323 |           triggerCount: 5,
 324 |           shared: true,
 325 |           active: true,
 326 |           settings: { executionOrder: 'v1' },
 327 |         } as any;
 328 | 
 329 |         const cleaned = cleanWorkflowForUpdate(workflow);
 330 |         
 331 |         // Should remove all these fields
 332 |         expect(cleaned).not.toHaveProperty('id');
 333 |         expect(cleaned).not.toHaveProperty('createdAt');
 334 |         expect(cleaned).not.toHaveProperty('updatedAt');
 335 |         expect(cleaned).not.toHaveProperty('versionId');
 336 |         expect(cleaned).not.toHaveProperty('meta');
 337 |         expect(cleaned).not.toHaveProperty('staticData');
 338 |         expect(cleaned).not.toHaveProperty('pinData');
 339 |         expect(cleaned).not.toHaveProperty('tags');
 340 |         expect(cleaned).not.toHaveProperty('isArchived');
 341 |         expect(cleaned).not.toHaveProperty('usedCredentials');
 342 |         expect(cleaned).not.toHaveProperty('sharedWithProjects');
 343 |         expect(cleaned).not.toHaveProperty('triggerCount');
 344 |         expect(cleaned).not.toHaveProperty('shared');
 345 |         expect(cleaned).not.toHaveProperty('active');
 346 |         
 347 |         // Should keep name and filter settings to safe properties
 348 |         expect(cleaned.name).toBe('Updated Workflow');
 349 |         expect(cleaned.settings).toEqual({ executionOrder: 'v1' });
 350 |       });
 351 | 
 352 |       it('should add empty settings object for cloud API compatibility', () => {
 353 |         const workflow = {
 354 |           name: 'Test Workflow',
 355 |           nodes: [],
 356 |           connections: {},
 357 |         } as any;
 358 | 
 359 |         const cleaned = cleanWorkflowForUpdate(workflow);
 360 |         expect(cleaned.settings).toEqual({});
 361 |       });
 362 | 
 363 |       it('should filter settings to safe properties to prevent API errors (Issue #248 - final fix)', () => {
 364 |         const workflow = {
 365 |           name: 'Test Workflow',
 366 |           nodes: [],
 367 |           connections: {},
 368 |           settings: {
 369 |             executionOrder: 'v1' as const,
 370 |             saveDataSuccessExecution: 'none' as const,
 371 |             callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out (not in OpenAPI spec)
 372 |             timeSavedPerExecution: 5, // Filtered out (UI-only property)
 373 |           },
 374 |         } as any;
 375 | 
 376 |         const cleaned = cleanWorkflowForUpdate(workflow);
 377 | 
 378 |         // Unsafe properties filtered out, safe properties kept
 379 |         expect(cleaned.settings).toEqual({
 380 |           executionOrder: 'v1',
 381 |           saveDataSuccessExecution: 'none'
 382 |         });
 383 |         expect(cleaned.settings).not.toHaveProperty('callerPolicy');
 384 |         expect(cleaned.settings).not.toHaveProperty('timeSavedPerExecution');
 385 |       });
 386 | 
 387 |       it('should filter out callerPolicy (Issue #248 - API limitation)', () => {
 388 |         const workflow = {
 389 |           name: 'Test Workflow',
 390 |           nodes: [],
 391 |           connections: {},
 392 |           settings: {
 393 |             executionOrder: 'v1' as const,
 394 |             callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
 395 |             errorWorkflow: 'N2O2nZy3aUiBRGFN',
 396 |           },
 397 |         } as any;
 398 | 
 399 |         const cleaned = cleanWorkflowForUpdate(workflow);
 400 | 
 401 |         // callerPolicy filtered out (causes API errors), safe properties kept
 402 |         expect(cleaned.settings).toEqual({
 403 |           executionOrder: 'v1',
 404 |           errorWorkflow: 'N2O2nZy3aUiBRGFN'
 405 |         });
 406 |         expect(cleaned.settings).not.toHaveProperty('callerPolicy');
 407 |       });
 408 | 
 409 |       it('should filter all settings properties correctly (Issue #248 - API design)', () => {
 410 |         const workflow = {
 411 |           name: 'Test Workflow',
 412 |           nodes: [],
 413 |           connections: {},
 414 |           settings: {
 415 |             executionOrder: 'v0' as const,
 416 |             timezone: 'UTC',
 417 |             saveDataErrorExecution: 'all' as const,
 418 |             saveDataSuccessExecution: 'none' as const,
 419 |             saveManualExecutions: false,
 420 |             saveExecutionProgress: false,
 421 |             executionTimeout: 300,
 422 |             errorWorkflow: 'error-workflow-id',
 423 |             callerPolicy: 'workflowsFromAList' as const, // Filtered out (not in OpenAPI spec)
 424 |           },
 425 |         } as any;
 426 | 
 427 |         const cleaned = cleanWorkflowForUpdate(workflow);
 428 | 
 429 |         // Safe properties kept, unsafe properties filtered out
 430 |         // See: https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916
 431 |         expect(cleaned.settings).toEqual({
 432 |           executionOrder: 'v0',
 433 |           timezone: 'UTC',
 434 |           saveDataErrorExecution: 'all',
 435 |           saveDataSuccessExecution: 'none',
 436 |           saveManualExecutions: false,
 437 |           saveExecutionProgress: false,
 438 |           executionTimeout: 300,
 439 |           errorWorkflow: 'error-workflow-id'
 440 |         });
 441 |         expect(cleaned.settings).not.toHaveProperty('callerPolicy');
 442 |       });
 443 | 
 444 |       it('should handle workflows without settings gracefully', () => {
 445 |         const workflow = {
 446 |           name: 'Test Workflow',
 447 |           nodes: [],
 448 |           connections: {},
 449 |         } as any;
 450 | 
 451 |         const cleaned = cleanWorkflowForUpdate(workflow);
 452 |         expect(cleaned.settings).toEqual({});
 453 |       });
 454 |     });
 455 |   });
 456 | 
 457 |   describe('validateWorkflowStructure', () => {
 458 |     it('should return no errors for valid workflow', () => {
 459 |       const workflow = new WorkflowBuilder('Valid Workflow')
 460 |         .addWebhookNode({ id: 'webhook-1', name: 'Webhook' })
 461 |         .addSlackNode({ id: 'slack-1', name: 'Send Slack' })
 462 |         .connect('Webhook', 'Send Slack')
 463 |         .build();
 464 | 
 465 |       const errors = validateWorkflowStructure(workflow as any);
 466 |       expect(errors).toEqual([]);
 467 |     });
 468 | 
 469 |     it('should detect missing workflow name', () => {
 470 |       const workflow = {
 471 |         nodes: [],
 472 |         connections: {},
 473 |       };
 474 | 
 475 |       const errors = validateWorkflowStructure(workflow as any);
 476 |       expect(errors).toContain('Workflow name is required');
 477 |     });
 478 | 
 479 |     it('should detect missing nodes', () => {
 480 |       const workflow = {
 481 |         name: 'Test',
 482 |         connections: {},
 483 |       };
 484 | 
 485 |       const errors = validateWorkflowStructure(workflow as any);
 486 |       expect(errors).toContain('Workflow must have at least one node');
 487 |     });
 488 | 
 489 |     it('should detect empty nodes array', () => {
 490 |       const workflow = {
 491 |         name: 'Test',
 492 |         nodes: [],
 493 |         connections: {},
 494 |       };
 495 | 
 496 |       const errors = validateWorkflowStructure(workflow as any);
 497 |       expect(errors).toContain('Workflow must have at least one node');
 498 |     });
 499 | 
 500 |     it('should detect missing connections', () => {
 501 |       const workflow = {
 502 |         name: 'Test',
 503 |         nodes: [{ id: 'node-1', name: 'Node 1', type: 'n8n-nodes-base.set', typeVersion: 1, position: [0, 0] as [number, number], parameters: {} }],
 504 |       };
 505 | 
 506 |       const errors = validateWorkflowStructure(workflow as any);
 507 |       expect(errors).toContain('Workflow connections are required');
 508 |     });
 509 | 
 510 |     it('should allow single webhook node workflow', () => {
 511 |       const workflow = {
 512 |         name: 'Webhook Only',
 513 |         nodes: [{
 514 |           id: 'webhook-1',
 515 |           name: 'Webhook',
 516 |           type: 'n8n-nodes-base.webhook',
 517 |           typeVersion: 2,
 518 |           position: [250, 300] as [number, number],
 519 |           parameters: {},
 520 |         }],
 521 |         connections: {},
 522 |       };
 523 | 
 524 |       const errors = validateWorkflowStructure(workflow as any);
 525 |       expect(errors).toEqual([]);
 526 |     });
 527 | 
 528 |     it('should reject single non-webhook node workflow', () => {
 529 |       const workflow = {
 530 |         name: 'Invalid Single Node',
 531 |         nodes: [{
 532 |           id: 'set-1',
 533 |           name: 'Set',
 534 |           type: 'n8n-nodes-base.set',
 535 |           typeVersion: 3,
 536 |           position: [250, 300] as [number, number],
 537 |           parameters: {},
 538 |         }],
 539 |         connections: {},
 540 |       };
 541 | 
 542 |       const errors = validateWorkflowStructure(workflow);
 543 |       expect(errors).toContain('Single-node workflows are only valid for webhooks. Add at least one more node and connect them. Example: Manual Trigger → Set node');
 544 |     });
 545 | 
 546 |     it('should detect empty connections in multi-node workflow', () => {
 547 |       const workflow = {
 548 |         name: 'Disconnected Nodes',
 549 |         nodes: [
 550 |           {
 551 |             id: 'node-1',
 552 |             name: 'Node 1',
 553 |             type: 'n8n-nodes-base.set',
 554 |             typeVersion: 3,
 555 |             position: [250, 300] as [number, number],
 556 |             parameters: {},
 557 |           },
 558 |           {
 559 |             id: 'node-2',
 560 |             name: 'Node 2',
 561 |             type: 'n8n-nodes-base.set',
 562 |             typeVersion: 3,
 563 |             position: [550, 300] as [number, number],
 564 |             parameters: {},
 565 |           },
 566 |         ],
 567 |         connections: {},
 568 |       };
 569 | 
 570 |       const errors = validateWorkflowStructure(workflow);
 571 |       expect(errors).toContain('Multi-node workflow has empty connections. Connect nodes like this: connections: { "Node1 Name": { "main": [[{ "node": "Node2 Name", "type": "main", "index": 0 }]] } }');
 572 |     });
 573 | 
 574 |     it('should validate node type format - missing package prefix', () => {
 575 |       const workflow = {
 576 |         name: 'Invalid Node Type',
 577 |         nodes: [{
 578 |           id: 'node-1',
 579 |           name: 'Node 1',
 580 |           type: 'webhook', // Missing package prefix
 581 |           typeVersion: 2,
 582 |           position: [250, 300] as [number, number],
 583 |           parameters: {},
 584 |         }],
 585 |         connections: {},
 586 |       };
 587 | 
 588 |       const errors = validateWorkflowStructure(workflow);
 589 |       expect(errors).toContain('Invalid node type "webhook" at index 0. Node types must include package prefix (e.g., "n8n-nodes-base.webhook").');
 590 |     });
 591 | 
 592 |     it('should validate node type format - wrong prefix format', () => {
 593 |       const workflow = {
 594 |         name: 'Invalid Node Type',
 595 |         nodes: [{
 596 |           id: 'node-1',
 597 |           name: 'Node 1',
 598 |           type: 'nodes-base.webhook', // Wrong prefix
 599 |           typeVersion: 2,
 600 |           position: [250, 300] as [number, number],
 601 |           parameters: {},
 602 |         }],
 603 |         connections: {},
 604 |       };
 605 | 
 606 |       const errors = validateWorkflowStructure(workflow);
 607 |       expect(errors).toContain('Invalid node type "nodes-base.webhook" at index 0. Use "n8n-nodes-base.webhook" instead.');
 608 |     });
 609 | 
 610 |     it('should detect invalid node structure', () => {
 611 |       const workflow = {
 612 |         name: 'Invalid Node',
 613 |         nodes: [{
 614 |           name: 'Missing Required Fields',
 615 |           // Missing id, type, typeVersion, position, parameters
 616 |         } as any],
 617 |         connections: {},
 618 |       };
 619 | 
 620 |       const errors = validateWorkflowStructure(workflow);
 621 |       // The validation will fail because the node is missing required fields
 622 |       expect(errors.some(e => e.includes('Invalid node at index 0'))).toBe(true);
 623 |     });
 624 | 
 625 |     it('should detect non-existent connection source by name', () => {
 626 |       const workflow = {
 627 |         name: 'Bad Connection',
 628 |         nodes: [{
 629 |           id: 'node-1',
 630 |           name: 'Node 1',
 631 |           type: 'n8n-nodes-base.set',
 632 |           typeVersion: 3,
 633 |           position: [250, 300] as [number, number],
 634 |           parameters: {},
 635 |         }],
 636 |         connections: {
 637 |           'Non-existent Node': {
 638 |             main: [[{ node: 'Node 1', type: 'main', index: 0 }]],
 639 |           },
 640 |         },
 641 |       };
 642 | 
 643 |       const errors = validateWorkflowStructure(workflow);
 644 |       expect(errors).toContain('Connection references non-existent node: Non-existent Node');
 645 |     });
 646 | 
 647 |     it('should detect non-existent connection target by name', () => {
 648 |       const workflow = {
 649 |         name: 'Bad Connection Target',
 650 |         nodes: [{
 651 |           id: 'node-1',
 652 |           name: 'Node 1',
 653 |           type: 'n8n-nodes-base.set',
 654 |           typeVersion: 3,
 655 |           position: [250, 300] as [number, number],
 656 |           parameters: {},
 657 |         }],
 658 |         connections: {
 659 |           'Node 1': {
 660 |             main: [[{ node: 'Non-existent Node', type: 'main', index: 0 }]],
 661 |           },
 662 |         },
 663 |       };
 664 | 
 665 |       const errors = validateWorkflowStructure(workflow);
 666 |       expect(errors).toContain('Connection references non-existent target node: Non-existent Node (from Node 1[0][0])');
 667 |     });
 668 | 
 669 |     it('should detect when node ID is used instead of name in connection source', () => {
 670 |       const workflow = {
 671 |         name: 'ID Instead of Name',
 672 |         nodes: [
 673 |           {
 674 |             id: 'node-1',
 675 |             name: 'First Node',
 676 |             type: 'n8n-nodes-base.set',
 677 |             typeVersion: 3,
 678 |             position: [250, 300] as [number, number],
 679 |             parameters: {},
 680 |           },
 681 |           {
 682 |             id: 'node-2',
 683 |             name: 'Second Node',
 684 |             type: 'n8n-nodes-base.set',
 685 |             typeVersion: 3,
 686 |             position: [550, 300] as [number, number],
 687 |             parameters: {},
 688 |           },
 689 |         ],
 690 |         connections: {
 691 |           'node-1': { // Using ID instead of name
 692 |             main: [[{ node: 'Second Node', type: 'main', index: 0 }]],
 693 |           },
 694 |         },
 695 |       };
 696 | 
 697 |       const errors = validateWorkflowStructure(workflow);
 698 |       expect(errors).toContain("Connection uses node ID 'node-1' but must use node name 'First Node'. Change connections.node-1 to connections['First Node']");
 699 |     });
 700 | 
 701 |     it('should detect when node ID is used instead of name in connection target', () => {
 702 |       const workflow = {
 703 |         name: 'ID Instead of Name in Target',
 704 |         nodes: [
 705 |           {
 706 |             id: 'node-1',
 707 |             name: 'First Node',
 708 |             type: 'n8n-nodes-base.set',
 709 |             typeVersion: 3,
 710 |             position: [250, 300] as [number, number],
 711 |             parameters: {},
 712 |           },
 713 |           {
 714 |             id: 'node-2',
 715 |             name: 'Second Node',
 716 |             type: 'n8n-nodes-base.set',
 717 |             typeVersion: 3,
 718 |             position: [550, 300] as [number, number],
 719 |             parameters: {},
 720 |           },
 721 |         ],
 722 |         connections: {
 723 |           'First Node': {
 724 |             main: [[{ node: 'node-2', type: 'main', index: 0 }]], // Using ID instead of name
 725 |           },
 726 |         },
 727 |       };
 728 | 
 729 |       const errors = validateWorkflowStructure(workflow);
 730 |       expect(errors).toContain("Connection target uses node ID 'node-2' but must use node name 'Second Node' (from First Node[0][0])");
 731 |     });
 732 | 
 733 |     it('should handle complex multi-output connections', () => {
 734 |       const workflow = {
 735 |         name: 'Complex Connections',
 736 |         nodes: [
 737 |           {
 738 |             id: 'if-1',
 739 |             name: 'IF Node',
 740 |             type: 'n8n-nodes-base.if',
 741 |             typeVersion: 2,
 742 |             position: [250, 300] as [number, number],
 743 |             parameters: {},
 744 |           },
 745 |           {
 746 |             id: 'true-1',
 747 |             name: 'True Branch',
 748 |             type: 'n8n-nodes-base.set',
 749 |             typeVersion: 3,
 750 |             position: [450, 200] as [number, number],
 751 |             parameters: {},
 752 |           },
 753 |           {
 754 |             id: 'false-1',
 755 |             name: 'False Branch',
 756 |             type: 'n8n-nodes-base.set',
 757 |             typeVersion: 3,
 758 |             position: [450, 400] as [number, number],
 759 |             parameters: {},
 760 |           },
 761 |         ],
 762 |         connections: {
 763 |           'IF Node': {
 764 |             main: [
 765 |               [{ node: 'True Branch', type: 'main', index: 0 }],
 766 |               [{ node: 'False Branch', type: 'main', index: 0 }],
 767 |             ],
 768 |           },
 769 |         },
 770 |       };
 771 | 
 772 |       const errors = validateWorkflowStructure(workflow);
 773 |       expect(errors).toEqual([]);
 774 |     });
 775 | 
 776 |     it('should validate invalid connections structure', () => {
 777 |       const workflow = {
 778 |         name: 'Invalid Connections',
 779 |         nodes: [
 780 |           {
 781 |             id: 'node-1',
 782 |             name: 'Node 1',
 783 |             type: 'n8n-nodes-base.set',
 784 |             typeVersion: 3,
 785 |             position: [250, 300] as [number, number],
 786 |             parameters: {},
 787 |           },
 788 |           {
 789 |             id: 'node-2',
 790 |             name: 'Node 2',
 791 |             type: 'n8n-nodes-base.set',
 792 |             typeVersion: 3,
 793 |             position: [550, 300] as [number, number],
 794 |             parameters: {},
 795 |           }
 796 |         ],
 797 |         connections: {
 798 |           'Node 1': 'invalid', // Should be an object
 799 |         } as any,
 800 |       };
 801 | 
 802 |       const errors = validateWorkflowStructure(workflow);
 803 |       expect(errors.some(e => e.includes('Invalid connections'))).toBe(true);
 804 |     });
 805 |   });
 806 | 
 807 |   describe('hasWebhookTrigger', () => {
 808 |     it('should return true for workflow with webhook node', () => {
 809 |       const workflow = new WorkflowBuilder()
 810 |         .addWebhookNode()
 811 |         .build() as Workflow;
 812 | 
 813 |       expect(hasWebhookTrigger(workflow)).toBe(true);
 814 |     });
 815 | 
 816 |     it('should return true for workflow with webhookTrigger node', () => {
 817 |       const workflow = {
 818 |         name: 'Test',
 819 |         nodes: [{
 820 |           id: 'webhook-1',
 821 |           name: 'Webhook Trigger',
 822 |           type: 'n8n-nodes-base.webhookTrigger',
 823 |           typeVersion: 1,
 824 |           position: [250, 300] as [number, number],
 825 |           parameters: {},
 826 |         }],
 827 |         connections: {},
 828 |       } as Workflow;
 829 | 
 830 |       expect(hasWebhookTrigger(workflow)).toBe(true);
 831 |     });
 832 | 
 833 |     it('should return false for workflow without webhook nodes', () => {
 834 |       const workflow = new WorkflowBuilder()
 835 |         .addSlackNode()
 836 |         .addHttpRequestNode()
 837 |         .build() as Workflow;
 838 | 
 839 |       expect(hasWebhookTrigger(workflow)).toBe(false);
 840 |     });
 841 | 
 842 |     it('should return true even if webhook is not the first node', () => {
 843 |       const workflow = new WorkflowBuilder()
 844 |         .addSlackNode()
 845 |         .addWebhookNode()
 846 |         .addHttpRequestNode()
 847 |         .build() as Workflow;
 848 | 
 849 |       expect(hasWebhookTrigger(workflow)).toBe(true);
 850 |     });
 851 |   });
 852 | 
 853 |   describe('getWebhookUrl', () => {
 854 |     it('should return webhook path from webhook node', () => {
 855 |       const workflow = {
 856 |         name: 'Test',
 857 |         nodes: [{
 858 |           id: 'webhook-1',
 859 |           name: 'Webhook',
 860 |           type: 'n8n-nodes-base.webhook',
 861 |           typeVersion: 2,
 862 |           position: [250, 300] as [number, number],
 863 |           parameters: {
 864 |             path: 'my-custom-webhook',
 865 |           },
 866 |         }],
 867 |         connections: {},
 868 |       } as Workflow;
 869 | 
 870 |       expect(getWebhookUrl(workflow)).toBe('my-custom-webhook');
 871 |     });
 872 | 
 873 |     it('should return webhook path from webhookTrigger node', () => {
 874 |       const workflow = {
 875 |         name: 'Test',
 876 |         nodes: [{
 877 |           id: 'webhook-1',
 878 |           name: 'Webhook Trigger',
 879 |           type: 'n8n-nodes-base.webhookTrigger',
 880 |           typeVersion: 1,
 881 |           position: [250, 300] as [number, number],
 882 |           parameters: {
 883 |             path: 'trigger-webhook-path',
 884 |           },
 885 |         }],
 886 |         connections: {},
 887 |       } as Workflow;
 888 | 
 889 |       expect(getWebhookUrl(workflow)).toBe('trigger-webhook-path');
 890 |     });
 891 | 
 892 |     it('should return null if no webhook node exists', () => {
 893 |       const workflow = new WorkflowBuilder()
 894 |         .addSlackNode()
 895 |         .build() as Workflow;
 896 | 
 897 |       expect(getWebhookUrl(workflow)).toBe(null);
 898 |     });
 899 | 
 900 |     it('should return null if webhook node has no parameters', () => {
 901 |       const workflow = {
 902 |         name: 'Test',
 903 |         nodes: [{
 904 |           id: 'webhook-1',
 905 |           name: 'Webhook',
 906 |           type: 'n8n-nodes-base.webhook',
 907 |           typeVersion: 2,
 908 |           position: [250, 300] as [number, number],
 909 |           parameters: undefined as any,
 910 |         }],
 911 |         connections: {},
 912 |       } as Workflow;
 913 | 
 914 |       expect(getWebhookUrl(workflow)).toBe(null);
 915 |     });
 916 | 
 917 |     it('should return null if webhook node has no path parameter', () => {
 918 |       const workflow = {
 919 |         name: 'Test',
 920 |         nodes: [{
 921 |           id: 'webhook-1',
 922 |           name: 'Webhook',
 923 |           type: 'n8n-nodes-base.webhook',
 924 |           typeVersion: 2,
 925 |           position: [250, 300] as [number, number],
 926 |           parameters: {
 927 |             method: 'POST',
 928 |             // No path parameter
 929 |           },
 930 |         }],
 931 |         connections: {},
 932 |       } as Workflow;
 933 | 
 934 |       expect(getWebhookUrl(workflow)).toBe(null);
 935 |     });
 936 | 
 937 |     it('should return first webhook path when multiple webhooks exist', () => {
 938 |       const workflow = {
 939 |         name: 'Test',
 940 |         nodes: [
 941 |           {
 942 |             id: 'webhook-1',
 943 |             name: 'Webhook 1',
 944 |             type: 'n8n-nodes-base.webhook',
 945 |             typeVersion: 2,
 946 |             position: [250, 300] as [number, number],
 947 |             parameters: {
 948 |               path: 'first-webhook',
 949 |             },
 950 |           },
 951 |           {
 952 |             id: 'webhook-2',
 953 |             name: 'Webhook 2',
 954 |             type: 'n8n-nodes-base.webhook',
 955 |             typeVersion: 2,
 956 |             position: [550, 300] as [number, number],
 957 |             parameters: {
 958 |               path: 'second-webhook',
 959 |             },
 960 |           },
 961 |         ],
 962 |         connections: {},
 963 |       } as Workflow;
 964 | 
 965 |       expect(getWebhookUrl(workflow)).toBe('first-webhook');
 966 |     });
 967 |   });
 968 | 
 969 |   describe('getWorkflowStructureExample', () => {
 970 |     it('should return a string containing example workflow structure', () => {
 971 |       const example = getWorkflowStructureExample();
 972 |       
 973 |       expect(example).toContain('Minimal Workflow Example');
 974 |       expect(example).toContain('Manual Trigger');
 975 |       expect(example).toContain('Set Data');
 976 |       expect(example).toContain('connections');
 977 |       expect(example).toContain('IMPORTANT: In connections, use the node NAME');
 978 |     });
 979 | 
 980 |     it('should contain valid JSON structure in example', () => {
 981 |       const example = getWorkflowStructureExample();
 982 |       // Extract the JSON part between the first { and last }
 983 |       const match = example.match(/\{[\s\S]*\}/);
 984 |       expect(match).toBeTruthy();
 985 |       
 986 |       if (match) {
 987 |         // Should not throw when parsing
 988 |         expect(() => JSON.parse(match[0])).not.toThrow();
 989 |       }
 990 |     });
 991 |   });
 992 | 
 993 |   describe('getWorkflowFixSuggestions', () => {
 994 |     it('should suggest fixes for empty connections', () => {
 995 |       const errors = ['Multi-node workflow has empty connections'];
 996 |       const suggestions = getWorkflowFixSuggestions(errors);
 997 |       
 998 |       expect(suggestions).toContain('Add connections between your nodes. Each node (except endpoints) should connect to another node.');
 999 |       expect(suggestions).toContain('Connection format: connections: { "Source Node Name": { "main": [[{ "node": "Target Node Name", "type": "main", "index": 0 }]] } }');
1000 |     });
1001 | 
1002 |     it('should suggest fixes for single-node workflows', () => {
1003 |       const errors = ['Single-node workflows are only valid for webhooks'];
1004 |       const suggestions = getWorkflowFixSuggestions(errors);
1005 |       
1006 |       expect(suggestions).toContain('Add at least one more node to process data. Common patterns: Trigger → Process → Output');
1007 |       expect(suggestions).toContain('Examples: Manual Trigger → Set, Webhook → HTTP Request, Schedule Trigger → Database Query');
1008 |     });
1009 | 
1010 |     it('should suggest fixes for node ID usage instead of names', () => {
1011 |       const errors = ["Connection uses node ID 'set-1' but must use node name 'Set Data' instead of node name"];
1012 |       const suggestions = getWorkflowFixSuggestions(errors);
1013 |       
1014 |       expect(suggestions.some(s => s.includes('Replace node IDs with node names'))).toBe(true);
1015 |       expect(suggestions.some(s => s.includes('connections: { "set-1": {...} }'))).toBe(true);
1016 |     });
1017 | 
1018 |     it('should return empty array for no errors', () => {
1019 |       const suggestions = getWorkflowFixSuggestions([]);
1020 |       expect(suggestions).toEqual([]);
1021 |     });
1022 | 
1023 |     it('should handle multiple error types', () => {
1024 |       const errors = [
1025 |         'Multi-node workflow has empty connections',
1026 |         'Single-node workflows are only valid for webhooks',
1027 |         "Connection uses node ID instead of node name",
1028 |       ];
1029 |       const suggestions = getWorkflowFixSuggestions(errors);
1030 |       
1031 |       expect(suggestions.length).toBeGreaterThan(3);
1032 |       expect(suggestions).toContain('Add connections between your nodes. Each node (except endpoints) should connect to another node.');
1033 |       expect(suggestions).toContain('Add at least one more node to process data. Common patterns: Trigger → Process → Output');
1034 |       expect(suggestions).toContain('Replace node IDs with node names in connections. The name is what appears in the node header.');
1035 |     });
1036 | 
1037 |     it('should not duplicate suggestions for similar errors', () => {
1038 |       const errors = [
1039 |         "Connection uses node ID 'id1' instead of node name",
1040 |         "Connection uses node ID 'id2' instead of node name",
1041 |       ];
1042 |       const suggestions = getWorkflowFixSuggestions(errors);
1043 |       
1044 |       // Should only have 2 suggestions for this error type
1045 |       const idSuggestions = suggestions.filter(s => s.includes('Replace node IDs'));
1046 |       expect(idSuggestions.length).toBe(1);
1047 |     });
1048 |   });
1049 | 
1050 |   describe('Edge Cases and Error Conditions', () => {
1051 |     it('should handle workflow with null values gracefully', () => {
1052 |       const workflow = {
1053 |         name: 'Test',
1054 |         nodes: null as any,
1055 |         connections: null as any,
1056 |       };
1057 | 
1058 |       const errors = validateWorkflowStructure(workflow);
1059 |       expect(errors).toContain('Workflow must have at least one node');
1060 |       expect(errors).toContain('Workflow connections are required');
1061 |     });
1062 | 
1063 |     it('should handle undefined parameters in cleaning functions', () => {
1064 |       const workflow = {
1065 |         name: undefined as any,
1066 |         nodes: undefined as any,
1067 |         connections: undefined as any,
1068 |       };
1069 | 
1070 |       expect(() => cleanWorkflowForCreate(workflow)).not.toThrow();
1071 |       expect(() => cleanWorkflowForUpdate(workflow as any)).not.toThrow();
1072 |     });
1073 | 
1074 |     it('should handle circular references in workflow structure', () => {
1075 |       const node1: any = {
1076 |         id: 'node-1',
1077 |         name: 'Node 1',
1078 |         type: 'n8n-nodes-base.set',
1079 |         typeVersion: 3,
1080 |         position: [250, 300],
1081 |         parameters: {},
1082 |       };
1083 |       
1084 |       // Create circular reference
1085 |       node1.parameters.circular = node1;
1086 | 
1087 |       const workflow = {
1088 |         name: 'Circular Ref',
1089 |         nodes: [node1],
1090 |         connections: {},
1091 |       };
1092 | 
1093 |       // Should handle circular references without crashing
1094 |       expect(() => validateWorkflowStructure(workflow)).not.toThrow();
1095 |     });
1096 | 
1097 |     it('should validate very large position values', () => {
1098 |       const node = {
1099 |         id: 'node-1',
1100 |         name: 'Test Node',
1101 |         type: 'n8n-nodes-base.set',
1102 |         typeVersion: 3,
1103 |         position: [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER] as [number, number],
1104 |         parameters: {},
1105 |       };
1106 | 
1107 |       expect(() => validateWorkflowNode(node)).not.toThrow();
1108 |     });
1109 | 
1110 |     it('should handle special characters in node names', () => {
1111 |       const workflow = {
1112 |         name: 'Special Chars',
1113 |         nodes: [
1114 |           {
1115 |             id: 'node-1',
1116 |             name: 'Node with "quotes" & special <chars>',
1117 |             type: 'n8n-nodes-base.set',
1118 |             typeVersion: 3,
1119 |             position: [250, 300] as [number, number],
1120 |             parameters: {},
1121 |           },
1122 |           {
1123 |             id: 'node-2',
1124 |             name: 'Normal Node',
1125 |             type: 'n8n-nodes-base.set',
1126 |             typeVersion: 3,
1127 |             position: [550, 300] as [number, number],
1128 |             parameters: {},
1129 |           },
1130 |         ],
1131 |         connections: {
1132 |           'Node with "quotes" & special <chars>': {
1133 |             main: [[{ node: 'Normal Node', type: 'main', index: 0 }]],
1134 |           },
1135 |         },
1136 |       };
1137 | 
1138 |       const errors = validateWorkflowStructure(workflow);
1139 |       expect(errors).toEqual([]);
1140 |     });
1141 | 
1142 |     it('should handle empty string values', () => {
1143 |       const workflow = {
1144 |         name: '',
1145 |         nodes: [{
1146 |           id: '',
1147 |           name: '',
1148 |           type: '',
1149 |           typeVersion: 1,
1150 |           position: [0, 0] as [number, number],
1151 |           parameters: {},
1152 |         }],
1153 |         connections: {},
1154 |       };
1155 | 
1156 |       const errors = validateWorkflowStructure(workflow);
1157 |       expect(errors).toContain('Workflow name is required');
1158 |       // Empty string for type will be caught as invalid
1159 |       expect(errors.some(e => e.includes('Invalid node at index 0') || e.includes('Node types must include package prefix'))).toBe(true);
1160 |     });
1161 | 
1162 |     it('should handle negative position values', () => {
1163 |       const node = {
1164 |         id: 'node-1',
1165 |         name: 'Test Node',
1166 |         type: 'n8n-nodes-base.set',
1167 |         typeVersion: 3,
1168 |         position: [-100, -200] as [number, number],
1169 |         parameters: {},
1170 |       };
1171 | 
1172 |       // Negative positions are valid
1173 |       expect(() => validateWorkflowNode(node)).not.toThrow();
1174 |     });
1175 | 
1176 |     it('should validate settings with additional unknown properties', () => {
1177 |       const settings = {
1178 |         executionOrder: 'v1' as const,
1179 |         timezone: 'UTC',
1180 |         unknownProperty: 'should be allowed',
1181 |         anotherUnknown: { nested: 'object' },
1182 |       };
1183 | 
1184 |       // Zod by default strips unknown properties
1185 |       const result = validateWorkflowSettings(settings);
1186 |       expect(result).toHaveProperty('executionOrder', 'v1');
1187 |       expect(result).toHaveProperty('timezone', 'UTC');
1188 |       expect(result).not.toHaveProperty('unknownProperty');
1189 |       expect(result).not.toHaveProperty('anotherUnknown');
1190 |     });
1191 |   });
1192 | 
1193 |   describe('Integration Tests', () => {
1194 |     it('should validate a complete real-world workflow', () => {
1195 |       const workflow = new WorkflowBuilder('Production Workflow')
1196 |         .addWebhookNode({ 
1197 |           id: 'webhook-1', 
1198 |           name: 'Order Webhook',
1199 |           parameters: {
1200 |             path: 'new-order',
1201 |             method: 'POST',
1202 |           },
1203 |         })
1204 |         .addIfNode({
1205 |           id: 'if-1',
1206 |           name: 'Check Order Value',
1207 |           parameters: {
1208 |             conditions: {
1209 |               options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' },
1210 |               conditions: [{
1211 |                 id: '1',
1212 |                 leftValue: '={{ $json.orderValue }}',
1213 |                 rightValue: '100',
1214 |                 operator: { type: 'number', operation: 'gte' },
1215 |               }],
1216 |               combinator: 'and',
1217 |             },
1218 |           },
1219 |         })
1220 |         .addSlackNode({
1221 |           id: 'slack-1',
1222 |           name: 'Notify High Value',
1223 |           parameters: {
1224 |             channel: '#high-value-orders',
1225 |             text: 'High value order received: ${{ $json.orderId }}',
1226 |           },
1227 |         })
1228 |         .addHttpRequestNode({
1229 |           id: 'http-1',
1230 |           name: 'Update Inventory',
1231 |           parameters: {
1232 |             method: 'POST',
1233 |             url: 'https://api.inventory.com/update',
1234 |             sendBody: true,
1235 |             bodyParametersJson: '={{ $json }}',
1236 |           },
1237 |         })
1238 |         .connect('Order Webhook', 'Check Order Value')
1239 |         .connect('Check Order Value', 'Notify High Value', 0) // True output
1240 |         .connect('Check Order Value', 'Update Inventory', 1) // False output
1241 |         .setSettings({
1242 |           executionOrder: 'v1',
1243 |           timezone: 'America/New_York',
1244 |           saveDataErrorExecution: 'all',
1245 |           saveDataSuccessExecution: 'none',
1246 |           executionTimeout: 300,
1247 |         })
1248 |         .build();
1249 | 
1250 |       const errors = validateWorkflowStructure(workflow as any);
1251 |       expect(errors).toEqual([]);
1252 | 
1253 |       // Validate individual components
1254 |       workflow.nodes.forEach(node => {
1255 |         expect(() => validateWorkflowNode(node)).not.toThrow();
1256 |       });
1257 |       expect(() => validateWorkflowConnections(workflow.connections)).not.toThrow();
1258 |       expect(() => validateWorkflowSettings(workflow.settings!)).not.toThrow();
1259 |     });
1260 | 
1261 |     it('should clean and validate workflow for API operations', () => {
1262 |       const originalWorkflow = {
1263 |         id: 'wf-123',
1264 |         name: 'API Test Workflow',
1265 |         nodes: [
1266 |           {
1267 |             id: 'manual-1',
1268 |             name: 'Manual Trigger',
1269 |             type: 'n8n-nodes-base.manualTrigger',
1270 |             typeVersion: 1,
1271 |             position: [250, 300] as [number, number],
1272 |             parameters: {},
1273 |           },
1274 |           {
1275 |             id: 'set-1',
1276 |             name: 'Set Data',
1277 |             type: 'n8n-nodes-base.set',
1278 |             typeVersion: 3.4,
1279 |             position: [450, 300] as [number, number],
1280 |             parameters: {
1281 |               mode: 'manual',
1282 |               assignments: {
1283 |                 assignments: [{
1284 |                   id: '1',
1285 |                   name: 'testKey',
1286 |                   value: 'testValue',
1287 |                   type: 'string',
1288 |                 }],
1289 |               },
1290 |             },
1291 |           }
1292 |         ],
1293 |         connections: {
1294 |           'Manual Trigger': {
1295 |             main: [[{
1296 |               node: 'Set Data',
1297 |               type: 'main',
1298 |               index: 0,
1299 |             }]],
1300 |           },
1301 |         },
1302 |         createdAt: '2023-01-01T00:00:00Z',
1303 |         updatedAt: '2023-01-02T00:00:00Z',
1304 |         versionId: 'v123',
1305 |         active: true,
1306 |         tags: ['test', 'api'],
1307 |         meta: { instanceId: 'instance-123' },
1308 |       };
1309 | 
1310 |       // Test create cleaning
1311 |       const forCreate = cleanWorkflowForCreate(originalWorkflow);
1312 |       expect(forCreate).not.toHaveProperty('id');
1313 |       expect(forCreate).not.toHaveProperty('createdAt');
1314 |       expect(forCreate).not.toHaveProperty('updatedAt');
1315 |       expect(forCreate).not.toHaveProperty('versionId');
1316 |       expect(forCreate).not.toHaveProperty('active');
1317 |       expect(forCreate).not.toHaveProperty('tags');
1318 |       expect(forCreate).not.toHaveProperty('meta');
1319 |       expect(forCreate).toHaveProperty('settings');
1320 |       expect(validateWorkflowStructure(forCreate)).toEqual([]);
1321 | 
1322 |       // Test update cleaning
1323 |       const forUpdate = cleanWorkflowForUpdate(originalWorkflow as any);
1324 |       expect(forUpdate).not.toHaveProperty('id');
1325 |       expect(forUpdate).not.toHaveProperty('createdAt');
1326 |       expect(forUpdate).not.toHaveProperty('updatedAt');
1327 |       expect(forUpdate).not.toHaveProperty('versionId');
1328 |       expect(forUpdate).not.toHaveProperty('active');
1329 |       expect(forUpdate).not.toHaveProperty('tags');
1330 |       expect(forUpdate).not.toHaveProperty('meta');
1331 |       expect(forUpdate.settings).toEqual({}); // Settings replaced with empty object for API compatibility
1332 |       expect(validateWorkflowStructure(forUpdate)).toEqual([]);
1333 |     });
1334 |   });
1335 | });
```

--------------------------------------------------------------------------------
/src/services/task-templates.ts:
--------------------------------------------------------------------------------

```typescript
   1 | /**
   2 |  * Task Templates Service
   3 |  *
   4 |  * @deprecated This module is deprecated as of v2.15.0 and will be removed in v2.16.0.
   5 |  * The get_node_for_task tool has been removed in favor of template-based configuration examples.
   6 |  *
   7 |  * Migration:
   8 |  * - Use `search_nodes({query: "webhook", includeExamples: true})` to find nodes with real template configs
   9 |  * - Use `get_node_essentials({nodeType: "nodes-base.webhook", includeExamples: true})` for top 3 examples
  10 |  * - New approach provides 2,646 real templates vs 31 hardcoded tasks
  11 |  *
  12 |  * Provides pre-configured node settings for common tasks.
  13 |  * This helps AI agents quickly configure nodes for specific use cases.
  14 |  */
  15 | 
  16 | export interface TaskTemplate {
  17 |   task: string;
  18 |   description: string;
  19 |   nodeType: string;
  20 |   configuration: Record<string, any>;
  21 |   userMustProvide: Array<{
  22 |     property: string;
  23 |     description: string;
  24 |     example?: any;
  25 |   }>;
  26 |   optionalEnhancements?: Array<{
  27 |     property: string;
  28 |     description: string;
  29 |     when?: string;
  30 |   }>;
  31 |   notes?: string[];
  32 | }
  33 | 
  34 | export class TaskTemplates {
  35 |   private static templates: Record<string, TaskTemplate> = {
  36 |     // HTTP Request Tasks
  37 |     'get_api_data': {
  38 |       task: 'get_api_data',
  39 |       description: 'Make a simple GET request to retrieve data from an API',
  40 |       nodeType: 'nodes-base.httpRequest',
  41 |       configuration: {
  42 |         method: 'GET',
  43 |         url: '',
  44 |         authentication: 'none',
  45 |         // Default error handling for API calls
  46 |         onError: 'continueRegularOutput',
  47 |         retryOnFail: true,
  48 |         maxTries: 3,
  49 |         waitBetweenTries: 1000,
  50 |         alwaysOutputData: true
  51 |       },
  52 |       userMustProvide: [
  53 |         {
  54 |           property: 'url',
  55 |           description: 'The API endpoint URL',
  56 |           example: 'https://api.example.com/users'
  57 |         }
  58 |       ],
  59 |       optionalEnhancements: [
  60 |         {
  61 |           property: 'authentication',
  62 |           description: 'Add authentication if the API requires it',
  63 |           when: 'API requires authentication'
  64 |         },
  65 |         {
  66 |           property: 'sendHeaders',
  67 |           description: 'Add custom headers if needed',
  68 |           when: 'API requires specific headers'
  69 |         },
  70 |         {
  71 |           property: 'alwaysOutputData',
  72 |           description: 'Set to true to capture error responses',
  73 |           when: 'Need to debug API errors'
  74 |         }
  75 |       ]
  76 |     },
  77 |     
  78 |     'post_json_request': {
  79 |       task: 'post_json_request',
  80 |       description: 'Send JSON data to an API endpoint',
  81 |       nodeType: 'nodes-base.httpRequest',
  82 |       configuration: {
  83 |         method: 'POST',
  84 |         url: '',
  85 |         sendBody: true,
  86 |         contentType: 'json',
  87 |         specifyBody: 'json',
  88 |         jsonBody: '',
  89 |         // POST requests might modify data, so be careful with retries
  90 |         onError: 'continueRegularOutput',
  91 |         retryOnFail: true,
  92 |         maxTries: 2,
  93 |         waitBetweenTries: 1000,
  94 |         alwaysOutputData: true
  95 |       },
  96 |       userMustProvide: [
  97 |         {
  98 |           property: 'url',
  99 |           description: 'The API endpoint URL',
 100 |           example: 'https://api.example.com/users'
 101 |         },
 102 |         {
 103 |           property: 'jsonBody',
 104 |           description: 'The JSON data to send',
 105 |           example: '{\n  "name": "John Doe",\n  "email": "[email protected]"\n}'
 106 |         }
 107 |       ],
 108 |       optionalEnhancements: [
 109 |         {
 110 |           property: 'authentication',
 111 |           description: 'Add authentication if required'
 112 |         },
 113 |         {
 114 |           property: 'onError',
 115 |           description: 'Set to "continueRegularOutput" for non-critical operations',
 116 |           when: 'Failure should not stop the workflow'
 117 |         }
 118 |       ],
 119 |       notes: [
 120 |         'Make sure jsonBody contains valid JSON',
 121 |         'Content-Type header is automatically set to application/json',
 122 |         'Be careful with retries on non-idempotent operations'
 123 |       ]
 124 |     },
 125 |     
 126 |     'call_api_with_auth': {
 127 |       task: 'call_api_with_auth',
 128 |       description: 'Make an authenticated API request',
 129 |       nodeType: 'nodes-base.httpRequest',
 130 |       configuration: {
 131 |         method: 'GET',
 132 |         url: '',
 133 |         authentication: 'genericCredentialType',
 134 |         genericAuthType: 'headerAuth',
 135 |         sendHeaders: true,
 136 |         // Authentication calls should handle auth failures gracefully
 137 |         onError: 'continueErrorOutput',
 138 |         retryOnFail: true,
 139 |         maxTries: 3,
 140 |         waitBetweenTries: 2000,
 141 |         alwaysOutputData: true,
 142 |         headerParameters: {
 143 |           parameters: [
 144 |             {
 145 |               name: '',
 146 |               value: ''
 147 |             }
 148 |           ]
 149 |         }
 150 |       },
 151 |       userMustProvide: [
 152 |         {
 153 |           property: 'url',
 154 |           description: 'The API endpoint URL'
 155 |         },
 156 |         {
 157 |           property: 'headerParameters.parameters[0].name',
 158 |           description: 'The header name for authentication',
 159 |           example: 'Authorization'
 160 |         },
 161 |         {
 162 |           property: 'headerParameters.parameters[0].value',
 163 |           description: 'The authentication value',
 164 |           example: 'Bearer YOUR_API_KEY'
 165 |         }
 166 |       ],
 167 |       optionalEnhancements: [
 168 |         {
 169 |           property: 'method',
 170 |           description: 'Change to POST/PUT/DELETE as needed'
 171 |         }
 172 |       ]
 173 |     },
 174 |     
 175 |     // Webhook Tasks
 176 |     'receive_webhook': {
 177 |       task: 'receive_webhook',
 178 |       description: 'Set up a webhook to receive data from external services',
 179 |       nodeType: 'nodes-base.webhook',
 180 |       configuration: {
 181 |         httpMethod: 'POST',
 182 |         path: 'webhook',
 183 |         responseMode: 'lastNode',
 184 |         responseData: 'allEntries',
 185 |         // Webhooks should always respond, even on error
 186 |         onError: 'continueRegularOutput',
 187 |         alwaysOutputData: true
 188 |       },
 189 |       userMustProvide: [
 190 |         {
 191 |           property: 'path',
 192 |           description: 'The webhook path (will be appended to your n8n URL)',
 193 |           example: 'github-webhook'
 194 |         }
 195 |       ],
 196 |       optionalEnhancements: [
 197 |         {
 198 |           property: 'httpMethod',
 199 |           description: 'Change if the service sends GET/PUT/etc'
 200 |         },
 201 |         {
 202 |           property: 'responseCode',
 203 |           description: 'Set custom response code (default 200)'
 204 |         }
 205 |       ],
 206 |       notes: [
 207 |         'The full webhook URL will be: https://your-n8n.com/webhook/[path]',
 208 |         'Test URL will be different from production URL'
 209 |       ]
 210 |     },
 211 |     
 212 |     'webhook_with_response': {
 213 |       task: 'webhook_with_response',
 214 |       description: 'Receive webhook and send custom response',
 215 |       nodeType: 'nodes-base.webhook',
 216 |       configuration: {
 217 |         httpMethod: 'POST',
 218 |         path: 'webhook',
 219 |         responseMode: 'responseNode',
 220 |         responseData: 'firstEntryJson',
 221 |         responseCode: 200,
 222 |         // Ensure webhook always sends response
 223 |         onError: 'continueRegularOutput',
 224 |         alwaysOutputData: true
 225 |       },
 226 |       userMustProvide: [
 227 |         {
 228 |           property: 'path',
 229 |           description: 'The webhook path'
 230 |         }
 231 |       ],
 232 |       notes: [
 233 |         'Use with a Respond to Webhook node to send custom response',
 234 |         'responseMode: responseNode requires a Respond to Webhook node'
 235 |       ]
 236 |     },
 237 |     
 238 |     'process_webhook_data': {
 239 |       task: 'process_webhook_data',
 240 |       description: 'Process incoming webhook data with Code node (shows correct data access)',
 241 |       nodeType: 'nodes-base.code',
 242 |       configuration: {
 243 |         language: 'javaScript',
 244 |         jsCode: `// ⚠️ CRITICAL: Webhook data is nested under 'body' property!
 245 | // Connect this Code node after a Webhook node
 246 | 
 247 | // Access webhook payload data - it's under .body, not directly under .json
 248 | const webhookData = items[0].json.body;  // ✅ CORRECT
 249 | const headers = items[0].json.headers;   // HTTP headers
 250 | const query = items[0].json.query;       // Query parameters
 251 | 
 252 | // Common mistake to avoid:
 253 | // const command = items[0].json.testCommand;  // ❌ WRONG - will be undefined!
 254 | // const command = items[0].json.body.testCommand;  // ✅ CORRECT
 255 | 
 256 | // Process the webhook data
 257 | try {
 258 |   // Validate required fields
 259 |   if (!webhookData.command) {
 260 |     throw new Error('Missing required field: command');
 261 |   }
 262 |   
 263 |   // Process based on command
 264 |   let result = {};
 265 |   switch (webhookData.command) {
 266 |     case 'process':
 267 |       result = {
 268 |         status: 'processed',
 269 |         data: webhookData.data,
 270 |         processedAt: DateTime.now().toISO()
 271 |       };
 272 |       break;
 273 |       
 274 |     case 'validate':
 275 |       result = {
 276 |         status: 'validated',
 277 |         isValid: true,
 278 |         validatedFields: Object.keys(webhookData.data || {})
 279 |       };
 280 |       break;
 281 |       
 282 |     default:
 283 |       result = {
 284 |         status: 'unknown_command',
 285 |         command: webhookData.command
 286 |       };
 287 |   }
 288 |   
 289 |   // Return processed data
 290 |   return [{
 291 |     json: {
 292 |       ...result,
 293 |       requestId: headers['x-request-id'] || crypto.randomUUID(),
 294 |       source: query.source || 'webhook',
 295 |       originalCommand: webhookData.command,
 296 |       metadata: {
 297 |         httpMethod: items[0].json.httpMethod,
 298 |         webhookPath: items[0].json.webhookPath,
 299 |         timestamp: DateTime.now().toISO()
 300 |       }
 301 |     }
 302 |   }];
 303 |   
 304 | } catch (error) {
 305 |   // Return error response
 306 |   return [{
 307 |     json: {
 308 |       status: 'error',
 309 |       error: error.message,
 310 |       timestamp: DateTime.now().toISO()
 311 |     }
 312 |   }];
 313 | }`,
 314 |         onError: 'continueRegularOutput'
 315 |       },
 316 |       userMustProvide: [],
 317 |       notes: [
 318 |         '⚠️ WEBHOOK DATA IS AT items[0].json.body, NOT items[0].json',
 319 |         'This is the most common webhook processing mistake',
 320 |         'Headers are at items[0].json.headers',
 321 |         'Query parameters are at items[0].json.query',
 322 |         'Connect this Code node directly after a Webhook node'
 323 |       ]
 324 |     },
 325 |     
 326 |     // Database Tasks
 327 |     'query_postgres': {
 328 |       task: 'query_postgres',
 329 |       description: 'Query data from PostgreSQL database',
 330 |       nodeType: 'nodes-base.postgres',
 331 |       configuration: {
 332 |         operation: 'executeQuery',
 333 |         query: '',
 334 |         // Database reads can continue on error
 335 |         onError: 'continueRegularOutput',
 336 |         retryOnFail: true,
 337 |         maxTries: 3,
 338 |         waitBetweenTries: 1000
 339 |       },
 340 |       userMustProvide: [
 341 |         {
 342 |           property: 'query',
 343 |           description: 'The SQL query to execute',
 344 |           example: 'SELECT * FROM users WHERE active = true LIMIT 10'
 345 |         }
 346 |       ],
 347 |       optionalEnhancements: [
 348 |         {
 349 |           property: 'additionalFields.queryParams',
 350 |           description: 'Use parameterized queries for security',
 351 |           when: 'Using dynamic values'
 352 |         }
 353 |       ],
 354 |       notes: [
 355 |         'Always use parameterized queries to prevent SQL injection',
 356 |         'Configure PostgreSQL credentials in n8n'
 357 |       ]
 358 |     },
 359 |     
 360 |     'insert_postgres_data': {
 361 |       task: 'insert_postgres_data',
 362 |       description: 'Insert data into PostgreSQL table',
 363 |       nodeType: 'nodes-base.postgres',
 364 |       configuration: {
 365 |         operation: 'insert',
 366 |         table: '',
 367 |         columns: '',
 368 |         returnFields: '*',
 369 |         // Database writes should stop on error by default
 370 |         onError: 'stopWorkflow',
 371 |         retryOnFail: true,
 372 |         maxTries: 2,
 373 |         waitBetweenTries: 1000
 374 |       },
 375 |       userMustProvide: [
 376 |         {
 377 |           property: 'table',
 378 |           description: 'The table name',
 379 |           example: 'users'
 380 |         },
 381 |         {
 382 |           property: 'columns',
 383 |           description: 'Comma-separated column names',
 384 |           example: 'name,email,created_at'
 385 |         }
 386 |       ],
 387 |       notes: [
 388 |         'Input data should match the column structure',
 389 |         'Use expressions like {{ $json.fieldName }} to map data'
 390 |       ]
 391 |     },
 392 |     
 393 |     // AI/LangChain Tasks
 394 |     'chat_with_ai': {
 395 |       task: 'chat_with_ai',
 396 |       description: 'Send a message to an AI model and get response',
 397 |       nodeType: 'nodes-base.openAi',
 398 |       configuration: {
 399 |         resource: 'chat',
 400 |         operation: 'message',
 401 |         modelId: 'gpt-3.5-turbo',
 402 |         messages: {
 403 |           values: [
 404 |             {
 405 |               role: 'user',
 406 |               content: ''
 407 |             }
 408 |           ]
 409 |         },
 410 |         // AI calls should handle rate limits and API errors
 411 |         onError: 'continueRegularOutput',
 412 |         retryOnFail: true,
 413 |         maxTries: 3,
 414 |         waitBetweenTries: 5000,
 415 |         alwaysOutputData: true
 416 |       },
 417 |       userMustProvide: [
 418 |         {
 419 |           property: 'messages.values[0].content',
 420 |           description: 'The message to send to the AI',
 421 |           example: '{{ $json.userMessage }}'
 422 |         }
 423 |       ],
 424 |       optionalEnhancements: [
 425 |         {
 426 |           property: 'modelId',
 427 |           description: 'Change to gpt-4 for better results'
 428 |         },
 429 |         {
 430 |           property: 'options.temperature',
 431 |           description: 'Adjust creativity (0-1)'
 432 |         },
 433 |         {
 434 |           property: 'options.maxTokens',
 435 |           description: 'Limit response length'
 436 |         }
 437 |       ]
 438 |     },
 439 |     
 440 |     'ai_agent_workflow': {
 441 |       task: 'ai_agent_workflow',
 442 |       description: 'Create an AI agent that can use tools',
 443 |       nodeType: 'nodes-langchain.agent',
 444 |       configuration: {
 445 |         text: '',
 446 |         outputType: 'output',
 447 |         systemMessage: 'You are a helpful assistant.'
 448 |       },
 449 |       userMustProvide: [
 450 |         {
 451 |           property: 'text',
 452 |           description: 'The input prompt for the agent',
 453 |           example: '{{ $json.query }}'
 454 |         }
 455 |       ],
 456 |       optionalEnhancements: [
 457 |         {
 458 |           property: 'systemMessage',
 459 |           description: 'Customize the agent\'s behavior'
 460 |         }
 461 |       ],
 462 |       notes: [
 463 |         'Connect tool nodes to give the agent capabilities',
 464 |         'Configure the AI model credentials'
 465 |       ]
 466 |     },
 467 |     
 468 |     // Data Processing Tasks
 469 |     'transform_data': {
 470 |       task: 'transform_data',
 471 |       description: 'Transform data structure using JavaScript',
 472 |       nodeType: 'nodes-base.code',
 473 |       configuration: {
 474 |         language: 'javaScript',
 475 |         jsCode: `// Transform each item
 476 | const results = [];
 477 | 
 478 | for (const item of items) {
 479 |   results.push({
 480 |     json: {
 481 |       // Transform your data here
 482 |       id: item.json.id,
 483 |       processedAt: new Date().toISOString()
 484 |     }
 485 |   });
 486 | }
 487 | 
 488 | return results;`
 489 |       },
 490 |       userMustProvide: [],
 491 |       notes: [
 492 |         'Access input data via items array',
 493 |         'Each item has a json property with the data',
 494 |         'Return array of objects with json property'
 495 |       ]
 496 |     },
 497 |     
 498 |     'filter_data': {
 499 |       task: 'filter_data',
 500 |       description: 'Filter items based on conditions',
 501 |       nodeType: 'nodes-base.if',
 502 |       configuration: {
 503 |         conditions: {
 504 |           conditions: [
 505 |             {
 506 |               leftValue: '',
 507 |               rightValue: '',
 508 |               operator: {
 509 |                 type: 'string',
 510 |                 operation: 'equals'
 511 |               }
 512 |             }
 513 |           ]
 514 |         }
 515 |       },
 516 |       userMustProvide: [
 517 |         {
 518 |           property: 'conditions.conditions[0].leftValue',
 519 |           description: 'The value to check',
 520 |           example: '{{ $json.status }}'
 521 |         },
 522 |         {
 523 |           property: 'conditions.conditions[0].rightValue',
 524 |           description: 'The value to compare against',
 525 |           example: 'active'
 526 |         }
 527 |       ],
 528 |       notes: [
 529 |         'True output contains matching items',
 530 |         'False output contains non-matching items'
 531 |       ]
 532 |     },
 533 |     
 534 |     // Communication Tasks
 535 |     'send_slack_message': {
 536 |       task: 'send_slack_message',
 537 |       description: 'Send a message to Slack channel',
 538 |       nodeType: 'nodes-base.slack',
 539 |       configuration: {
 540 |         resource: 'message',
 541 |         operation: 'post',
 542 |         channel: '',
 543 |         text: '',
 544 |         // Messaging can continue on error
 545 |         onError: 'continueRegularOutput',
 546 |         retryOnFail: true,
 547 |         maxTries: 2,
 548 |         waitBetweenTries: 2000
 549 |       },
 550 |       userMustProvide: [
 551 |         {
 552 |           property: 'channel',
 553 |           description: 'The Slack channel',
 554 |           example: '#general'
 555 |         },
 556 |         {
 557 |           property: 'text',
 558 |           description: 'The message text',
 559 |           example: 'New order received: {{ $json.orderId }}'
 560 |         }
 561 |       ],
 562 |       optionalEnhancements: [
 563 |         {
 564 |           property: 'attachments',
 565 |           description: 'Add rich message attachments'
 566 |         },
 567 |         {
 568 |           property: 'blocks',
 569 |           description: 'Use Block Kit for advanced formatting'
 570 |         }
 571 |       ]
 572 |     },
 573 |     
 574 |     'send_email': {
 575 |       task: 'send_email',
 576 |       description: 'Send an email notification',
 577 |       nodeType: 'nodes-base.emailSend',
 578 |       configuration: {
 579 |         fromEmail: '',
 580 |         toEmail: '',
 581 |         subject: '',
 582 |         text: '',
 583 |         // Email sending should retry on transient failures
 584 |         onError: 'continueRegularOutput',
 585 |         retryOnFail: true,
 586 |         maxTries: 3,
 587 |         waitBetweenTries: 3000,
 588 |         alwaysOutputData: true
 589 |       },
 590 |       userMustProvide: [
 591 |         {
 592 |           property: 'fromEmail',
 593 |           description: 'Sender email address',
 594 |           example: '[email protected]'
 595 |         },
 596 |         {
 597 |           property: 'toEmail',
 598 |           description: 'Recipient email address',
 599 |           example: '{{ $json.customerEmail }}'
 600 |         },
 601 |         {
 602 |           property: 'subject',
 603 |           description: 'Email subject',
 604 |           example: 'Order Confirmation #{{ $json.orderId }}'
 605 |         },
 606 |         {
 607 |           property: 'text',
 608 |           description: 'Email body (plain text)',
 609 |           example: 'Thank you for your order!'
 610 |         }
 611 |       ],
 612 |       optionalEnhancements: [
 613 |         {
 614 |           property: 'html',
 615 |           description: 'Use HTML for rich formatting'
 616 |         },
 617 |         {
 618 |           property: 'attachments',
 619 |           description: 'Attach files to the email'
 620 |         }
 621 |       ]
 622 |     },
 623 |     
 624 |     // AI Tool Usage Tasks
 625 |     'use_google_sheets_as_tool': {
 626 |       task: 'use_google_sheets_as_tool',
 627 |       description: 'Use Google Sheets as an AI tool for reading/writing data',
 628 |       nodeType: 'nodes-base.googleSheets',
 629 |       configuration: {
 630 |         operation: 'append',
 631 |         sheetId: '={{ $fromAI("sheetId", "The Google Sheets ID") }}',
 632 |         range: '={{ $fromAI("range", "The range to append to, e.g. A:Z") }}',
 633 |         dataMode: 'autoMap'
 634 |       },
 635 |       userMustProvide: [
 636 |         {
 637 |           property: 'Google Sheets credentials',
 638 |           description: 'Configure Google Sheets API credentials in n8n'
 639 |         },
 640 |         {
 641 |           property: 'Tool name in AI Agent',
 642 |           description: 'Give it a descriptive name like "Log Results to Sheet"'
 643 |         },
 644 |         {
 645 |           property: 'Tool description',
 646 |           description: 'Describe when and how the AI should use this tool'
 647 |         }
 648 |       ],
 649 |       notes: [
 650 |         'Connect this node to the ai_tool port of an AI Agent node',
 651 |         'The AI can dynamically determine sheetId and range using $fromAI',
 652 |         'Works great for logging AI analysis results or reading data for processing'
 653 |       ]
 654 |     },
 655 |     
 656 |     'use_slack_as_tool': {
 657 |       task: 'use_slack_as_tool',
 658 |       description: 'Use Slack as an AI tool for sending notifications',
 659 |       nodeType: 'nodes-base.slack',
 660 |       configuration: {
 661 |         resource: 'message',
 662 |         operation: 'post',
 663 |         channel: '={{ $fromAI("channel", "The Slack channel, e.g. #general") }}',
 664 |         text: '={{ $fromAI("message", "The message to send") }}',
 665 |         attachments: []
 666 |       },
 667 |       userMustProvide: [
 668 |         {
 669 |           property: 'Slack credentials',
 670 |           description: 'Configure Slack OAuth2 credentials in n8n'
 671 |         },
 672 |         {
 673 |           property: 'Tool configuration in AI Agent',
 674 |           description: 'Name it something like "Send Slack Notification"'
 675 |         }
 676 |       ],
 677 |       notes: [
 678 |         'Perfect for AI agents that need to notify teams',
 679 |         'The AI determines channel and message content dynamically',
 680 |         'Can be enhanced with blocks for rich formatting'
 681 |       ]
 682 |     },
 683 |     
 684 |     'multi_tool_ai_agent': {
 685 |       task: 'multi_tool_ai_agent',
 686 |       description: 'AI agent with multiple tools for complex automation',
 687 |       nodeType: 'nodes-langchain.agent',
 688 |       configuration: {
 689 |         text: '={{ $json.query }}',
 690 |         outputType: 'output',
 691 |         systemMessage: 'You are an intelligent assistant with access to multiple tools. Use them wisely to complete tasks.'
 692 |       },
 693 |       userMustProvide: [
 694 |         {
 695 |           property: 'AI model credentials',
 696 |           description: 'OpenAI, Anthropic, or other LLM credentials'
 697 |         },
 698 |         {
 699 |           property: 'Multiple tool nodes',
 700 |           description: 'Connect various nodes to the ai_tool port'
 701 |         },
 702 |         {
 703 |           property: 'Tool descriptions',
 704 |           description: 'Clear descriptions for each connected tool'
 705 |         }
 706 |       ],
 707 |       optionalEnhancements: [
 708 |         {
 709 |           property: 'Memory',
 710 |           description: 'Add memory nodes for conversation context'
 711 |         },
 712 |         {
 713 |           property: 'Custom tools',
 714 |           description: 'Create Code nodes as custom tools'
 715 |         }
 716 |       ],
 717 |       notes: [
 718 |         'Connect multiple nodes: HTTP Request, Slack, Google Sheets, etc.',
 719 |         'Each tool should have a clear, specific purpose',
 720 |         'Test each tool individually before combining',
 721 |         'Set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true for community nodes'
 722 |       ]
 723 |     },
 724 |     
 725 |     // Error Handling Templates
 726 |     'api_call_with_retry': {
 727 |       task: 'api_call_with_retry',
 728 |       description: 'Resilient API call with automatic retry on failure',
 729 |       nodeType: 'nodes-base.httpRequest',
 730 |       configuration: {
 731 |         method: 'GET',
 732 |         url: '',
 733 |         // Retry configuration for transient failures
 734 |         retryOnFail: true,
 735 |         maxTries: 5,
 736 |         waitBetweenTries: 2000,
 737 |         // Always capture response for debugging
 738 |         alwaysOutputData: true,
 739 |         // Add request tracking
 740 |         sendHeaders: true,
 741 |         headerParameters: {
 742 |           parameters: [
 743 |             {
 744 |               name: 'X-Request-ID',
 745 |               value: '={{ $workflow.id }}-{{ $itemIndex }}'
 746 |             }
 747 |           ]
 748 |         }
 749 |       },
 750 |       userMustProvide: [
 751 |         {
 752 |           property: 'url',
 753 |           description: 'The API endpoint to call',
 754 |           example: 'https://api.example.com/resource/{{ $json.id }}'
 755 |         }
 756 |       ],
 757 |       optionalEnhancements: [
 758 |         {
 759 |           property: 'authentication',
 760 |           description: 'Add API authentication'
 761 |         },
 762 |         {
 763 |           property: 'onError',
 764 |           description: 'Change to "stopWorkflow" for critical API calls',
 765 |           when: 'This is a critical API call that must succeed'
 766 |         }
 767 |       ],
 768 |       notes: [
 769 |         'Retries help with rate limits and transient network issues',
 770 |         'waitBetweenTries prevents hammering the API',
 771 |         'alwaysOutputData captures error responses for debugging',
 772 |         'Consider exponential backoff for production use'
 773 |       ]
 774 |     },
 775 |     
 776 |     'fault_tolerant_processing': {
 777 |       task: 'fault_tolerant_processing',
 778 |       description: 'Data processing that continues despite individual item failures',
 779 |       nodeType: 'nodes-base.code',
 780 |       configuration: {
 781 |         language: 'javaScript',
 782 |         jsCode: `// Process items with error handling
 783 | const results = [];
 784 | 
 785 | for (const item of items) {
 786 |   try {
 787 |     // Your processing logic here
 788 |     const processed = {
 789 |       ...item.json,
 790 |       processed: true,
 791 |       timestamp: new Date().toISOString()
 792 |     };
 793 |     
 794 |     results.push({ json: processed });
 795 |   } catch (error) {
 796 |     // Log error but continue processing
 797 |     console.error('Processing failed for item:', item.json.id, error);
 798 |     
 799 |     // Add error item to results
 800 |     results.push({
 801 |       json: {
 802 |         ...item.json,
 803 |         error: error.message,
 804 |         processed: false
 805 |       }
 806 |     });
 807 |   }
 808 | }
 809 | 
 810 | return results;`,
 811 |         // Continue workflow even if code fails entirely
 812 |         onError: 'continueRegularOutput',
 813 |         alwaysOutputData: true
 814 |       },
 815 |       userMustProvide: [
 816 |         {
 817 |           property: 'Processing logic',
 818 |           description: 'Replace the comment with your data transformation logic'
 819 |         }
 820 |       ],
 821 |       optionalEnhancements: [
 822 |         {
 823 |           property: 'Error notification',
 824 |           description: 'Add IF node after to handle error items separately'
 825 |         }
 826 |       ],
 827 |       notes: [
 828 |         'Individual item failures won\'t stop processing of other items',
 829 |         'Error items are marked and can be handled separately',
 830 |         'continueOnFail ensures workflow continues even on total failure'
 831 |       ]
 832 |     },
 833 |     
 834 |     'webhook_with_error_handling': {
 835 |       task: 'webhook_with_error_handling',
 836 |       description: 'Webhook that gracefully handles processing errors',
 837 |       nodeType: 'nodes-base.webhook',
 838 |       configuration: {
 839 |         httpMethod: 'POST',
 840 |         path: 'resilient-webhook',
 841 |         responseMode: 'responseNode',
 842 |         responseData: 'firstEntryJson',
 843 |         // Always continue to ensure response is sent
 844 |         onError: 'continueRegularOutput',
 845 |         alwaysOutputData: true
 846 |       },
 847 |       userMustProvide: [
 848 |         {
 849 |           property: 'path',
 850 |           description: 'Unique webhook path',
 851 |           example: 'order-processor'
 852 |         },
 853 |         {
 854 |           property: 'Respond to Webhook node',
 855 |           description: 'Add node to send appropriate success/error responses'
 856 |         }
 857 |       ],
 858 |       optionalEnhancements: [
 859 |         {
 860 |           property: 'Validation',
 861 |           description: 'Add IF node to validate webhook payload'
 862 |         },
 863 |         {
 864 |           property: 'Error logging',
 865 |           description: 'Add error handler node for failed requests'
 866 |         }
 867 |       ],
 868 |       notes: [
 869 |         'onError: continueRegularOutput ensures webhook always sends a response',
 870 |         'Use Respond to Webhook node to send appropriate status codes',
 871 |         'Log errors but don\'t expose internal errors to webhook callers',
 872 |         'Consider rate limiting for public webhooks'
 873 |       ]
 874 |     },
 875 |     
 876 |     // Modern Error Handling Patterns
 877 |     'modern_error_handling_patterns': {
 878 |       task: 'modern_error_handling_patterns',
 879 |       description: 'Examples of modern error handling using onError property',
 880 |       nodeType: 'nodes-base.httpRequest',
 881 |       configuration: {
 882 |         method: 'GET',
 883 |         url: '',
 884 |         // Modern error handling approach
 885 |         onError: 'continueRegularOutput', // Options: continueRegularOutput, continueErrorOutput, stopWorkflow
 886 |         retryOnFail: true,
 887 |         maxTries: 3,
 888 |         waitBetweenTries: 2000,
 889 |         alwaysOutputData: true
 890 |       },
 891 |       userMustProvide: [
 892 |         {
 893 |           property: 'url',
 894 |           description: 'The API endpoint'
 895 |         },
 896 |         {
 897 |           property: 'onError',
 898 |           description: 'Choose error handling strategy',
 899 |           example: 'continueRegularOutput'
 900 |         }
 901 |       ],
 902 |       notes: [
 903 |         'onError replaces the deprecated continueOnFail property',
 904 |         'continueRegularOutput: Continue with normal output on error',
 905 |         'continueErrorOutput: Route errors to error output for special handling', 
 906 |         'stopWorkflow: Stop the entire workflow on error',
 907 |         'Combine with retryOnFail for resilient workflows'
 908 |       ]
 909 |     },
 910 |     
 911 |     'database_transaction_safety': {
 912 |       task: 'database_transaction_safety',
 913 |       description: 'Database operations with proper error handling',
 914 |       nodeType: 'nodes-base.postgres',
 915 |       configuration: {
 916 |         operation: 'executeQuery',
 917 |         query: 'BEGIN; INSERT INTO orders ...; COMMIT;',
 918 |         // For transactions, don\'t retry automatically
 919 |         onError: 'continueErrorOutput',
 920 |         retryOnFail: false,
 921 |         alwaysOutputData: true
 922 |       },
 923 |       userMustProvide: [
 924 |         {
 925 |           property: 'query',
 926 |           description: 'Your SQL query or transaction'
 927 |         }
 928 |       ],
 929 |       notes: [
 930 |         'Transactions should not be retried automatically',
 931 |         'Use continueErrorOutput to handle errors separately',
 932 |         'Consider implementing compensating transactions',
 933 |         'Always log transaction failures for audit'
 934 |       ]
 935 |     },
 936 |     
 937 |     'ai_rate_limit_handling': {
 938 |       task: 'ai_rate_limit_handling',
 939 |       description: 'AI API calls with rate limit handling',
 940 |       nodeType: 'nodes-base.openAi',
 941 |       configuration: {
 942 |         resource: 'chat',
 943 |         operation: 'message',
 944 |         modelId: 'gpt-4',
 945 |         messages: {
 946 |           values: [
 947 |             {
 948 |               role: 'user',
 949 |               content: ''
 950 |             }
 951 |           ]
 952 |         },
 953 |         // Handle rate limits with exponential backoff
 954 |         onError: 'continueRegularOutput',
 955 |         retryOnFail: true,
 956 |         maxTries: 5,
 957 |         waitBetweenTries: 5000,
 958 |         alwaysOutputData: true
 959 |       },
 960 |       userMustProvide: [
 961 |         {
 962 |           property: 'messages.values[0].content',
 963 |           description: 'The prompt for the AI'
 964 |         }
 965 |       ],
 966 |       notes: [
 967 |         'AI APIs often have rate limits',
 968 |         'Longer wait times help avoid hitting limits',
 969 |         'Consider implementing exponential backoff in Code node',
 970 |         'Monitor usage to stay within quotas'
 971 |       ]
 972 |     },
 973 |     
 974 |     // Code Node Tasks
 975 |     'custom_ai_tool': {
 976 |       task: 'custom_ai_tool',
 977 |       description: 'Create a custom tool for AI agents using Code node',
 978 |       nodeType: 'nodes-base.code',
 979 |       configuration: {
 980 |         language: 'javaScript',
 981 |         mode: 'runOnceForEachItem',
 982 |         jsCode: `// Custom AI Tool - Example: Text Analysis
 983 | // This code will be called by AI agents with $json containing the input
 984 | 
 985 | // Access the input from the AI agent
 986 | const text = $json.text || '';
 987 | const operation = $json.operation || 'analyze';
 988 | 
 989 | // Perform the requested operation
 990 | let result = {};
 991 | 
 992 | switch (operation) {
 993 |   case 'wordCount':
 994 |     result = {
 995 |       wordCount: text.split(/\\s+/).filter(word => word.length > 0).length,
 996 |       characterCount: text.length,
 997 |       lineCount: text.split('\\n').length
 998 |     };
 999 |     break;
1000 |     
1001 |   case 'extract':
1002 |     // Extract specific patterns (emails, URLs, etc.)
1003 |     result = {
1004 |       emails: text.match(/[\\w.-]+@[\\w.-]+\\.\\w+/g) || [],
1005 |       urls: text.match(/https?:\\/\\/[^\\s]+/g) || [],
1006 |       numbers: text.match(/\\b\\d+\\b/g) || []
1007 |     };
1008 |     break;
1009 |     
1010 |   default:
1011 |     result = {
1012 |       error: 'Unknown operation',
1013 |       availableOperations: ['wordCount', 'extract']
1014 |     };
1015 | }
1016 | 
1017 | return [{
1018 |   json: {
1019 |     ...result,
1020 |     originalText: text,
1021 |     operation: operation,
1022 |     processedAt: DateTime.now().toISO()
1023 |   }
1024 | }];`,
1025 |         onError: 'continueRegularOutput'
1026 |       },
1027 |       userMustProvide: [],
1028 |       notes: [
1029 |         'Connect this to AI Agent node\'s tool input',
1030 |         'AI will pass data in $json',
1031 |         'Use "Run Once for Each Item" mode for AI tools',
1032 |         'Return structured data the AI can understand'
1033 |       ]
1034 |     },
1035 |     
1036 |     'aggregate_data': {
1037 |       task: 'aggregate_data',
1038 |       description: 'Aggregate data from multiple items into summary statistics',
1039 |       nodeType: 'nodes-base.code',
1040 |       configuration: {
1041 |         language: 'javaScript',
1042 |         jsCode: `// Aggregate data from all items
1043 | const stats = {
1044 |   count: 0,
1045 |   sum: 0,
1046 |   min: Infinity,
1047 |   max: -Infinity,
1048 |   values: [],
1049 |   categories: {},
1050 |   errors: []
1051 | };
1052 | 
1053 | // Process each item
1054 | for (const item of items) {
1055 |   try {
1056 |     const value = item.json.value || item.json.amount || 0;
1057 |     const category = item.json.category || 'uncategorized';
1058 |     
1059 |     stats.count++;
1060 |     stats.sum += value;
1061 |     stats.min = Math.min(stats.min, value);
1062 |     stats.max = Math.max(stats.max, value);
1063 |     stats.values.push(value);
1064 |     
1065 |     // Count by category
1066 |     stats.categories[category] = (stats.categories[category] || 0) + 1;
1067 |     
1068 |   } catch (error) {
1069 |     stats.errors.push({
1070 |       item: item.json,
1071 |       error: error.message
1072 |     });
1073 |   }
1074 | }
1075 | 
1076 | // Calculate additional statistics
1077 | const average = stats.count > 0 ? stats.sum / stats.count : 0;
1078 | const sorted = [...stats.values].sort((a, b) => a - b);
1079 | const median = sorted.length > 0 
1080 |   ? sorted[Math.floor(sorted.length / 2)] 
1081 |   : 0;
1082 | 
1083 | return [{
1084 |   json: {
1085 |     totalItems: stats.count,
1086 |     sum: stats.sum,
1087 |     average: average,
1088 |     median: median,
1089 |     min: stats.min === Infinity ? 0 : stats.min,
1090 |     max: stats.max === -Infinity ? 0 : stats.max,
1091 |     categoryCounts: stats.categories,
1092 |     errorCount: stats.errors.length,
1093 |     errors: stats.errors,
1094 |     processedAt: DateTime.now().toISO()
1095 |   }
1096 | }];`,
1097 |         onError: 'continueRegularOutput'
1098 |       },
1099 |       userMustProvide: [],
1100 |       notes: [
1101 |         'Assumes items have "value" or "amount" field',
1102 |         'Groups by "category" field if present',
1103 |         'Returns single item with all statistics',
1104 |         'Handles errors gracefully'
1105 |       ]
1106 |     },
1107 |     
1108 |     'batch_process_with_api': {
1109 |       task: 'batch_process_with_api',
1110 |       description: 'Process items in batches with API calls',
1111 |       nodeType: 'nodes-base.code',
1112 |       configuration: {
1113 |         language: 'javaScript',
1114 |         jsCode: `// Batch process items with API calls
1115 | const BATCH_SIZE = 10;
1116 | const API_URL = 'https://api.example.com/batch-process'; // USER MUST UPDATE
1117 | const results = [];
1118 | 
1119 | // Process items in batches
1120 | for (let i = 0; i < items.length; i += BATCH_SIZE) {
1121 |   const batch = items.slice(i, i + BATCH_SIZE);
1122 |   
1123 |   try {
1124 |     // Prepare batch data
1125 |     const batchData = batch.map(item => ({
1126 |       id: item.json.id,
1127 |       data: item.json
1128 |     }));
1129 |     
1130 |     // Make API request for batch
1131 |     const response = await $helpers.httpRequest({
1132 |       method: 'POST',
1133 |       url: API_URL,
1134 |       body: {
1135 |         items: batchData
1136 |       },
1137 |       headers: {
1138 |         'Content-Type': 'application/json'
1139 |       }
1140 |     });
1141 |     
1142 |     // Add results
1143 |     if (response.results && Array.isArray(response.results)) {
1144 |       response.results.forEach((result, index) => {
1145 |         results.push({
1146 |           json: {
1147 |             ...batch[index].json,
1148 |             ...result,
1149 |             batchNumber: Math.floor(i / BATCH_SIZE) + 1,
1150 |             processedAt: DateTime.now().toISO()
1151 |           }
1152 |         });
1153 |       });
1154 |     }
1155 |     
1156 |     // Add delay between batches to avoid rate limits
1157 |     if (i + BATCH_SIZE < items.length) {
1158 |       await new Promise(resolve => setTimeout(resolve, 1000));
1159 |     }
1160 |     
1161 |   } catch (error) {
1162 |     // Add failed batch items with error
1163 |     batch.forEach(item => {
1164 |       results.push({
1165 |         json: {
1166 |           ...item.json,
1167 |           error: error.message,
1168 |           status: 'failed',
1169 |           batchNumber: Math.floor(i / BATCH_SIZE) + 1
1170 |         }
1171 |       });
1172 |     });
1173 |   }
1174 | }
1175 | 
1176 | return results;`,
1177 |         onError: 'continueRegularOutput',
1178 |         retryOnFail: true,
1179 |         maxTries: 2
1180 |       },
1181 |       userMustProvide: [
1182 |         {
1183 |           property: 'jsCode',
1184 |           description: 'Update API_URL in the code',
1185 |           example: 'https://your-api.com/batch'
1186 |         }
1187 |       ],
1188 |       notes: [
1189 |         'Processes items in batches of 10',
1190 |         'Includes delay between batches',
1191 |         'Handles batch failures gracefully',
1192 |         'Update API_URL and adjust BATCH_SIZE as needed'
1193 |       ]
1194 |     },
1195 |     
1196 |     'error_safe_transform': {
1197 |       task: 'error_safe_transform',
1198 |       description: 'Transform data with comprehensive error handling',
1199 |       nodeType: 'nodes-base.code',
1200 |       configuration: {
1201 |         language: 'javaScript',
1202 |         jsCode: `// Safe data transformation with validation
1203 | const results = [];
1204 | const errors = [];
1205 | 
1206 | for (const item of items) {
1207 |   try {
1208 |     // Validate required fields
1209 |     const required = ['id', 'name']; // USER SHOULD UPDATE
1210 |     const missing = required.filter(field => !item.json[field]);
1211 |     
1212 |     if (missing.length > 0) {
1213 |       throw new Error(\`Missing required fields: \${missing.join(', ')}\`);
1214 |     }
1215 |     
1216 |     // Transform data with type checking
1217 |     const transformed = {
1218 |       // Ensure ID is string
1219 |       id: String(item.json.id),
1220 |       
1221 |       // Clean and validate name
1222 |       name: String(item.json.name).trim(),
1223 |       
1224 |       // Parse numbers safely
1225 |       amount: parseFloat(item.json.amount) || 0,
1226 |       
1227 |       // Parse dates safely
1228 |       date: item.json.date 
1229 |         ? DateTime.fromISO(item.json.date).isValid 
1230 |           ? DateTime.fromISO(item.json.date).toISO()
1231 |           : null
1232 |         : null,
1233 |       
1234 |       // Boolean conversion
1235 |       isActive: Boolean(item.json.active || item.json.isActive),
1236 |       
1237 |       // Array handling
1238 |       tags: Array.isArray(item.json.tags) 
1239 |         ? item.json.tags.filter(tag => typeof tag === 'string')
1240 |         : [],
1241 |       
1242 |       // Nested object handling
1243 |       metadata: typeof item.json.metadata === 'object' 
1244 |         ? item.json.metadata 
1245 |         : {},
1246 |       
1247 |       // Add processing info
1248 |       processedAt: DateTime.now().toISO(),
1249 |       originalIndex: items.indexOf(item)
1250 |     };
1251 |     
1252 |     results.push({
1253 |       json: transformed
1254 |     });
1255 |     
1256 |   } catch (error) {
1257 |     errors.push({
1258 |       json: {
1259 |         error: error.message,
1260 |         originalData: item.json,
1261 |         index: items.indexOf(item),
1262 |         status: 'failed'
1263 |       }
1264 |     });
1265 |   }
1266 | }
1267 | 
1268 | // Add summary at the end
1269 | results.push({
1270 |   json: {
1271 |     _summary: {
1272 |       totalProcessed: results.length - errors.length,
1273 |       totalErrors: errors.length,
1274 |       successRate: ((results.length - errors.length) / items.length * 100).toFixed(2) + '%',
1275 |       timestamp: DateTime.now().toISO()
1276 |     }
1277 |   }
1278 | });
1279 | 
1280 | // Include errors at the end
1281 | return [...results, ...errors];`,
1282 |         onError: 'continueRegularOutput'
1283 |       },
1284 |       userMustProvide: [
1285 |         {
1286 |           property: 'jsCode',
1287 |           description: 'Update required fields array',
1288 |           example: "const required = ['id', 'email', 'name'];"
1289 |         }
1290 |       ],
1291 |       notes: [
1292 |         'Validates all data types',
1293 |         'Handles missing/invalid data gracefully',
1294 |         'Returns both successful and failed items',
1295 |         'Includes processing summary'
1296 |       ]
1297 |     },
1298 |     
1299 |     'async_data_processing': {
1300 |       task: 'async_data_processing',
1301 |       description: 'Process data with async operations and proper error handling',
1302 |       nodeType: 'nodes-base.code',
1303 |       configuration: {
1304 |         language: 'javaScript',
1305 |         jsCode: `// Async processing with concurrent limits
1306 | const CONCURRENT_LIMIT = 5;
1307 | const results = [];
1308 | 
1309 | // Process items with concurrency control
1310 | async function processItem(item, index) {
1311 |   try {
1312 |     // Simulate async operation (replace with actual logic)
1313 |     // Example: API call, database query, file operation
1314 |     await new Promise(resolve => setTimeout(resolve, 100));
1315 |     
1316 |     // Actual processing logic here
1317 |     const processed = {
1318 |       ...item.json,
1319 |       processed: true,
1320 |       index: index,
1321 |       timestamp: DateTime.now().toISO()
1322 |     };
1323 |     
1324 |     // Example async operation - external API call
1325 |     if (item.json.needsEnrichment) {
1326 |       const enrichment = await $helpers.httpRequest({
1327 |         method: 'GET',
1328 |         url: \`https://api.example.com/enrich/\${item.json.id}\`
1329 |       });
1330 |       processed.enrichment = enrichment;
1331 |     }
1332 |     
1333 |     return { json: processed };
1334 |     
1335 |   } catch (error) {
1336 |     return {
1337 |       json: {
1338 |         ...item.json,
1339 |         error: error.message,
1340 |         status: 'failed',
1341 |         index: index
1342 |       }
1343 |     };
1344 |   }
1345 | }
1346 | 
1347 | // Process in batches with concurrency limit
1348 | for (let i = 0; i < items.length; i += CONCURRENT_LIMIT) {
1349 |   const batch = items.slice(i, i + CONCURRENT_LIMIT);
1350 |   const batchPromises = batch.map((item, batchIndex) => 
1351 |     processItem(item, i + batchIndex)
1352 |   );
1353 |   
1354 |   const batchResults = await Promise.all(batchPromises);
1355 |   results.push(...batchResults);
1356 | }
1357 | 
1358 | return results;`,
1359 |         onError: 'continueRegularOutput',
1360 |         retryOnFail: true,
1361 |         maxTries: 2
1362 |       },
1363 |       userMustProvide: [],
1364 |       notes: [
1365 |         'Processes 5 items concurrently',
1366 |         'Prevents overwhelming external services',
1367 |         'Each item processed independently',
1368 |         'Errors don\'t affect other items'
1369 |       ]
1370 |     },
1371 |     
1372 |     'python_data_analysis': {
1373 |       task: 'python_data_analysis',
1374 |       description: 'Analyze data using Python with statistics',
1375 |       nodeType: 'nodes-base.code',
1376 |       configuration: {
1377 |         language: 'python',
1378 |         pythonCode: `# Python data analysis - use underscore prefix for built-in variables
1379 | import json
1380 | from datetime import datetime
1381 | import statistics
1382 | 
1383 | # Collect data for analysis
1384 | values = []
1385 | categories = {}
1386 | dates = []
1387 | 
1388 | # Use _input.all() to get items in Python
1389 | for item in _input.all():
1390 |     # Convert JsProxy to Python dict for safe access
1391 |     item_data = item.json.to_py()
1392 |     
1393 |     # Extract numeric values
1394 |     if 'value' in item_data or 'amount' in item_data:
1395 |         value = item_data.get('value', item_data.get('amount', 0))
1396 |         if isinstance(value, (int, float)):
1397 |             values.append(value)
1398 |     
1399 |     # Count categories
1400 |     category = item_data.get('category', 'uncategorized')
1401 |     categories[category] = categories.get(category, 0) + 1
1402 |     
1403 |     # Collect dates
1404 |     if 'date' in item_data:
1405 |         dates.append(item_data['date'])
1406 | 
1407 | # Calculate statistics
1408 | result = {
1409 |     'itemCount': len(_input.all()),
1410 |     'values': {
1411 |         'count': len(values),
1412 |         'sum': sum(values) if values else 0,
1413 |         'mean': statistics.mean(values) if values else 0,
1414 |         'median': statistics.median(values) if values else 0,
1415 |         'min': min(values) if values else 0,
1416 |         'max': max(values) if values else 0,
1417 |         'stdev': statistics.stdev(values) if len(values) > 1 else 0
1418 |     },
1419 |     'categories': categories,
1420 |     'dateRange': {
1421 |         'earliest': min(dates) if dates else None,
1422 |         'latest': max(dates) if dates else None,
1423 |         'count': len(dates)
1424 |     },
1425 |     'analysis': {
1426 |         'hasNumericData': len(values) > 0,
1427 |         'hasCategoricalData': len(categories) > 0,
1428 |         'hasTemporalData': len(dates) > 0,
1429 |         'dataQuality': 'good' if len(values) > len(items) * 0.8 else 'partial'
1430 |     },
1431 |     'processedAt': datetime.now().isoformat()
1432 | }
1433 | 
1434 | # Return single summary item
1435 | return [{'json': result}]`,
1436 |         onError: 'continueRegularOutput'
1437 |       },
1438 |       userMustProvide: [],
1439 |       notes: [
1440 |         'Uses Python statistics module',
1441 |         'Analyzes numeric, categorical, and date data',
1442 |         'Returns comprehensive summary',
1443 |         'Handles missing data gracefully'
1444 |       ]
1445 |     }
1446 |   };
1447 |   
1448 |   /**
1449 |    * Get all available tasks
1450 |    */
1451 |   static getAllTasks(): string[] {
1452 |     return Object.keys(this.templates);
1453 |   }
1454 |   
1455 |   /**
1456 |    * Get tasks for a specific node type
1457 |    */
1458 |   static getTasksForNode(nodeType: string): string[] {
1459 |     return Object.entries(this.templates)
1460 |       .filter(([_, template]) => template.nodeType === nodeType)
1461 |       .map(([task, _]) => task);
1462 |   }
1463 |   
1464 |   /**
1465 |    * Get a specific task template
1466 |    */
1467 |   static getTaskTemplate(task: string): TaskTemplate | undefined {
1468 |     return this.templates[task];
1469 |   }
1470 |   
1471 |   /**
1472 |    * Get a specific task template (alias for getTaskTemplate)
1473 |    */
1474 |   static getTemplate(task: string): TaskTemplate | undefined {
1475 |     return this.getTaskTemplate(task);
1476 |   }
1477 |   
1478 |   /**
1479 |    * Search for tasks by keyword
1480 |    */
1481 |   static searchTasks(keyword: string): string[] {
1482 |     const lower = keyword.toLowerCase();
1483 |     return Object.entries(this.templates)
1484 |       .filter(([task, template]) => 
1485 |         task.toLowerCase().includes(lower) ||
1486 |         template.description.toLowerCase().includes(lower) ||
1487 |         template.nodeType.toLowerCase().includes(lower)
1488 |       )
1489 |       .map(([task, _]) => task);
1490 |   }
1491 |   
1492 |   /**
1493 |    * Get task categories
1494 |    */
1495 |   static getTaskCategories(): Record<string, string[]> {
1496 |     return {
1497 |       'HTTP/API': ['get_api_data', 'post_json_request', 'call_api_with_auth', 'api_call_with_retry'],
1498 |       'Webhooks': ['receive_webhook', 'webhook_with_response', 'webhook_with_error_handling', 'process_webhook_data'],
1499 |       'Database': ['query_postgres', 'insert_postgres_data', 'database_transaction_safety'],
1500 |       'AI/LangChain': ['chat_with_ai', 'ai_agent_workflow', 'multi_tool_ai_agent', 'ai_rate_limit_handling'],
1501 |       'Data Processing': ['transform_data', 'filter_data', 'fault_tolerant_processing', 'process_webhook_data'],
1502 |       'Communication': ['send_slack_message', 'send_email'],
1503 |       'AI Tool Usage': ['use_google_sheets_as_tool', 'use_slack_as_tool', 'multi_tool_ai_agent'],
1504 |       'Error Handling': ['modern_error_handling_patterns', 'api_call_with_retry', 'fault_tolerant_processing', 'webhook_with_error_handling', 'database_transaction_safety', 'ai_rate_limit_handling']
1505 |     };
1506 |   }
1507 | }
```
Page 41/59FirstPrevNextLast