@openapi-flow/n8n 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -2
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/legacy/workflow/common/plan-validation.d.ts +1 -0
- package/dist/legacy/workflow/common/plan-validation.js +11 -0
- package/dist/legacy/workflow/common/workflow-id.d.ts +1 -0
- package/dist/legacy/workflow/common/workflow-id.js +8 -0
- package/dist/legacy/workflow/inbound/build-inbound-workflow.d.ts +2 -0
- package/dist/legacy/workflow/inbound/build-inbound-workflow.js +60 -0
- package/dist/legacy/workflow/inbound/compile-inbound-workflow.d.ts +2 -0
- package/dist/legacy/workflow/inbound/compile-inbound-workflow.js +25 -0
- package/dist/legacy/workflow/inbound/inbound-response.d.ts +2 -0
- package/dist/legacy/workflow/inbound/inbound-response.js +51 -0
- package/dist/legacy/workflow/request/base-url.d.ts +1 -0
- package/dist/legacy/workflow/request/base-url.js +14 -0
- package/dist/legacy/workflow/request/build-request-workflow.d.ts +3 -0
- package/dist/legacy/workflow/request/build-request-workflow.js +47 -0
- package/dist/legacy/workflow/request/build-sequence-workflow.d.ts +3 -0
- package/dist/legacy/workflow/request/build-sequence-workflow.js +53 -0
- package/dist/legacy/workflow/request/compile-catalog-sequence.d.ts +2 -0
- package/dist/legacy/workflow/request/compile-catalog-sequence.js +122 -0
- package/dist/legacy/workflow/request/compile-sequence.d.ts +2 -0
- package/dist/legacy/workflow/request/compile-sequence.js +41 -0
- package/dist/legacy/workflow/request/compile-workflow.d.ts +4 -0
- package/dist/legacy/workflow/request/compile-workflow.js +93 -0
- package/dist/legacy/workflow/request/credential-binding.d.ts +5 -0
- package/dist/legacy/workflow/request/credential-binding.js +31 -0
- package/dist/legacy/workflow/request/effect-policy.d.ts +4 -0
- package/dist/legacy/workflow/request/effect-policy.js +16 -0
- package/dist/legacy/workflow/request/http-request-node.d.ts +2 -0
- package/dist/legacy/workflow/request/http-request-node.js +69 -0
- package/dist/legacy/workflow/request/prepare-sequence.d.ts +4 -0
- package/dist/legacy/workflow/request/prepare-sequence.js +103 -0
- package/dist/legacy/workflow/request/request-body.d.ts +4 -0
- package/dist/legacy/workflow/request/request-body.js +64 -0
- package/dist/legacy/workflow/request/request-headers.d.ts +4 -0
- package/dist/legacy/workflow/request/request-headers.js +30 -0
- package/dist/legacy/workflow/request/request-url.d.ts +4 -0
- package/dist/legacy/workflow/request/request-url.js +34 -0
- package/dist/legacy/workflow/request/response-assertion-code.d.ts +3 -0
- package/dist/legacy/workflow/request/response-assertion-code.js +28 -0
- package/dist/legacy/workflow/request/sequence-url.d.ts +4 -0
- package/dist/legacy/workflow/request/sequence-url.js +104 -0
- package/dist/legacy-api.d.ts +25 -0
- package/dist/legacy-api.js +20 -0
- package/dist/nodes/common/validation/compile-standalone-validator.d.ts +3 -0
- package/dist/nodes/common/validation/compile-standalone-validator.js +44 -0
- package/dist/nodes/gap/create-gap-node.d.ts +3 -0
- package/dist/nodes/gap/create-gap-node.js +35 -0
- package/dist/nodes/native/arrays/create-array-node-fragment.d.ts +4 -0
- package/dist/nodes/native/arrays/create-array-node-fragment.js +42 -0
- package/dist/nodes/native/arrays/create-array-reader-code.d.ts +3 -0
- package/dist/nodes/native/arrays/create-array-reader-code.js +12 -0
- package/dist/nodes/native/arrays/create-native-array-capability.d.ts +3 -0
- package/dist/nodes/native/arrays/create-native-array-capability.js +64 -0
- package/dist/nodes/native/common/create-native-fragment.d.ts +2 -0
- package/dist/nodes/native/common/create-native-fragment.js +10 -0
- package/dist/nodes/native/common/response-references.d.ts +3 -0
- package/dist/nodes/native/common/response-references.js +5 -0
- package/dist/nodes/native/create-exclusive-branch-merge-capability.d.ts +3 -0
- package/dist/nodes/native/create-exclusive-branch-merge-capability.js +33 -0
- package/dist/nodes/native/create-json-output-capability.d.ts +3 -0
- package/dist/nodes/native/create-json-output-capability.js +38 -0
- package/dist/nodes/native/create-native-capabilities.d.ts +2 -0
- package/dist/nodes/native/create-native-capabilities.js +14 -0
- package/dist/nodes/native/create-pass-through-capability.d.ts +3 -0
- package/dist/nodes/native/create-pass-through-capability.js +30 -0
- package/dist/nodes/native/create-response-array-capability.d.ts +3 -0
- package/dist/nodes/native/create-response-array-capability.js +73 -0
- package/dist/nodes/native/create-response-collection-capability.d.ts +3 -0
- package/dist/nodes/native/create-response-collection-capability.js +132 -0
- package/dist/nodes/native/if-capability.d.ts +2 -0
- package/dist/nodes/native/if-capability.js +76 -0
- package/dist/nodes/native/item-join/create-item-join-capability.d.ts +3 -0
- package/dist/nodes/native/item-join/create-item-join-capability.js +153 -0
- package/dist/nodes/native/item-join/create-item-join-reader-code.d.ts +2 -0
- package/dist/nodes/native/item-join/create-item-join-reader-code.js +18 -0
- package/dist/nodes/native/item-join/runtime/item-join-runtime.bundle.js +24 -0
- package/dist/nodes/native/item-join/runtime/join-api-items.d.ts +3 -0
- package/dist/nodes/native/item-join/runtime/join-api-items.js +37 -0
- package/dist/nodes/native/merge-append-capability.d.ts +2 -0
- package/dist/nodes/native/merge-append-capability.js +31 -0
- package/dist/nodes/native/response-assertion-capability.d.ts +2 -0
- package/dist/nodes/native/response-assertion-capability.js +53 -0
- package/dist/nodes/native/response-check-code.d.ts +3 -0
- package/dist/nodes/native/response-check-code.js +59 -0
- package/dist/nodes/native/response-checks/create-response-check-guard.d.ts +4 -0
- package/dist/nodes/native/response-checks/create-response-check-guard.js +22 -0
- package/dist/nodes/native/response-checks/create-response-check-reader.d.ts +3 -0
- package/dist/nodes/native/response-checks/create-response-check-reader.js +23 -0
- package/dist/nodes/native/stop-and-error-capability.d.ts +2 -0
- package/dist/nodes/native/stop-and-error-capability.js +29 -0
- package/dist/nodes/request/authentication/credential-field-name.d.ts +1 -0
- package/dist/nodes/request/authentication/credential-field-name.js +3 -0
- package/dist/nodes/request/authentication/resolve-http-request-authentication.d.ts +2 -0
- package/dist/nodes/request/authentication/resolve-http-request-authentication.js +42 -0
- package/dist/nodes/request/bindings/compile-native-output-validators.d.ts +3 -0
- package/dist/nodes/request/bindings/compile-native-output-validators.js +12 -0
- package/dist/nodes/request/bindings/compile-request-validator.d.ts +3 -0
- package/dist/nodes/request/bindings/compile-request-validator.js +13 -0
- package/dist/nodes/request/bindings/compile-source-response-validators.d.ts +2 -0
- package/dist/nodes/request/bindings/compile-source-response-validators.js +12 -0
- package/dist/nodes/request/bindings/create-bound-request-fragment.d.ts +3 -0
- package/dist/nodes/request/bindings/create-bound-request-fragment.js +77 -0
- package/dist/nodes/request/bindings/create-output-reader-code.d.ts +3 -0
- package/dist/nodes/request/bindings/create-output-reader-code.js +26 -0
- package/dist/nodes/request/bindings/resolve-output-binding-sources.d.ts +3 -0
- package/dist/nodes/request/bindings/resolve-output-binding-sources.js +36 -0
- package/dist/nodes/request/create-http-request-node.d.ts +2 -0
- package/dist/nodes/request/create-http-request-node.js +94 -0
- package/dist/nodes/request/deployment/create-request-deployment-notes.d.ts +2 -0
- package/dist/nodes/request/deployment/create-request-deployment-notes.js +3 -0
- package/dist/nodes/request/deployment/resolve-http-request-deployment.d.ts +2 -0
- package/dist/nodes/request/deployment/resolve-http-request-deployment.js +33 -0
- package/dist/nodes/request/response/create-http-response-decoder.d.ts +4 -0
- package/dist/nodes/request/response/create-http-response-decoder.js +56 -0
- package/dist/nodes/request/response/decode-http-response.d.ts +3 -0
- package/dist/nodes/request/response/decode-http-response.js +42 -0
- package/dist/nodes/request/response/http-response-media.d.ts +2 -0
- package/dist/nodes/request/response/http-response-media.js +32 -0
- package/dist/nodes/request/response/response-runtime.bundle.js +24 -0
- package/dist/nodes/request/runtime/materialize-http-request.d.ts +3 -0
- package/dist/nodes/request/runtime/materialize-http-request.js +17 -0
- package/dist/nodes/request/runtime/request-runtime.bundle.js +24 -0
- package/dist/nodes/request/serialization/create-serialized-http-request-node.d.ts +3 -0
- package/dist/nodes/request/serialization/create-serialized-http-request-node.js +17 -0
- package/dist/nodes/request/serialization/serialize-http-request.d.ts +5 -0
- package/dist/nodes/request/serialization/serialize-http-request.js +70 -0
- package/dist/public-api.d.ts +30 -0
- package/dist/public-api.js +17 -0
- package/dist/schemas/batch-execution-schema.d.ts +15 -0
- package/dist/schemas/batch-execution-schema.js +14 -0
- package/dist/schemas/create-native-comparison-planning-schema.d.ts +81 -0
- package/dist/schemas/create-native-comparison-planning-schema.js +36 -0
- package/dist/schemas/exclusive-branch-merge-schema.d.ts +4 -0
- package/dist/schemas/exclusive-branch-merge-schema.js +7 -0
- package/dist/schemas/item-join-schema.d.ts +13 -0
- package/dist/schemas/item-join-schema.js +26 -0
- package/dist/schemas/native-array-schema.d.ts +11 -0
- package/dist/schemas/native-array-schema.js +19 -0
- package/dist/schemas/native-capability-schemas.d.ts +100 -0
- package/dist/schemas/native-capability-schemas.js +62 -0
- package/dist/schemas/request-deployment-schema.d.ts +9 -0
- package/dist/schemas/request-deployment-schema.js +27 -0
- package/dist/schemas/response-array-schema.d.ts +11 -0
- package/dist/schemas/response-array-schema.js +19 -0
- package/dist/schemas/response-collection-schema.d.ts +11 -0
- package/dist/schemas/response-collection-schema.js +19 -0
- package/dist/types/array-iteration.d.ts +8 -0
- package/dist/types/array-iteration.js +1 -0
- package/dist/types/array-split.d.ts +13 -0
- package/dist/types/array-split.js +1 -0
- package/dist/types/authentication.d.ts +14 -0
- package/dist/types/authentication.js +1 -0
- package/dist/types/batch-execution.d.ts +23 -0
- package/dist/types/batch-execution.js +1 -0
- package/dist/types/http-response-decoding.d.ts +16 -0
- package/dist/types/http-response-decoding.js +1 -0
- package/dist/types/item-join.d.ts +40 -0
- package/dist/types/item-join.js +1 -0
- package/dist/types/legacy/catalog-workflow.d.ts +37 -0
- package/dist/types/legacy/catalog-workflow.js +1 -0
- package/dist/types/legacy/inbound-workflow.d.ts +27 -0
- package/dist/types/legacy/inbound-workflow.js +1 -0
- package/dist/types/legacy/request-node.d.ts +23 -0
- package/dist/types/legacy/request-node.js +1 -0
- package/dist/types/legacy/request-workflow.d.ts +43 -0
- package/dist/types/legacy/request-workflow.js +1 -0
- package/dist/types/legacy/sequence-workflow.d.ts +57 -0
- package/dist/types/legacy/sequence-workflow.js +1 -0
- package/dist/types/native-array.d.ts +8 -0
- package/dist/types/native-array.js +1 -0
- package/dist/types/native-capability.d.ts +37 -0
- package/dist/types/native-capability.js +1 -0
- package/dist/types/node-fragment.d.ts +51 -0
- package/dist/types/node-fragment.js +1 -0
- package/dist/types/output-binding-source.d.ts +22 -0
- package/dist/types/output-binding-source.js +1 -0
- package/dist/types/request-deployment.d.ts +17 -0
- package/dist/types/request-deployment.js +1 -0
- package/dist/types/request-materialization.d.ts +22 -0
- package/dist/types/request-materialization.js +1 -0
- package/dist/types/response-check-compilation.d.ts +11 -0
- package/dist/types/response-check-compilation.js +1 -0
- package/dist/types/response-check-guard.d.ts +6 -0
- package/dist/types/response-check-guard.js +1 -0
- package/dist/types/response-collection.d.ts +7 -0
- package/dist/types/response-collection.js +1 -0
- package/dist/types/reviewable-workflow.d.ts +27 -0
- package/dist/types/reviewable-workflow.js +1 -0
- package/dist/types/schema-compilation.d.ts +5 -0
- package/dist/types/schema-compilation.js +1 -0
- package/dist/types/serialized-http-request.d.ts +4 -0
- package/dist/types/serialized-http-request.js +1 -0
- package/dist/types/workflow-compilation.d.ts +36 -0
- package/dist/types/workflow-compilation.js +1 -0
- package/dist/utils/bundled-license-banner.d.ts +3 -0
- package/dist/utils/bundled-license-banner.js +32 -0
- package/dist/utils/javascript-json-literal.d.ts +2 -0
- package/dist/utils/javascript-json-literal.js +10 -0
- package/dist/utils/json-values-equal.cjs +24 -0
- package/dist/utils/json-values-equal.d.cts +1 -0
- package/dist/workflow/assemble-n8n-workflow.d.ts +2 -0
- package/dist/workflow/assemble-n8n-workflow.js +11 -0
- package/dist/workflow/batch/compile-batched-n8n-workflow.d.ts +3 -0
- package/dist/workflow/batch/compile-batched-n8n-workflow.js +18 -0
- package/dist/workflow/batch/create-batch-execution-scopes.d.ts +3 -0
- package/dist/workflow/batch/create-batch-execution-scopes.js +59 -0
- package/dist/workflow/batch/validate-batch-exclusive-rejoin.d.ts +3 -0
- package/dist/workflow/batch/validate-batch-exclusive-rejoin.js +49 -0
- package/dist/workflow/batch/validate-batch-item-flow.d.ts +3 -0
- package/dist/workflow/batch/validate-batch-item-flow.js +98 -0
- package/dist/workflow/build-n8n-workflow.d.ts +2 -0
- package/dist/workflow/build-n8n-workflow.js +143 -0
- package/dist/workflow/compile-native-workflow-nodes.d.ts +3 -0
- package/dist/workflow/compile-native-workflow-nodes.js +26 -0
- package/dist/workflow/compile-planned-n8n-workflow.d.ts +3 -0
- package/dist/workflow/compile-planned-n8n-workflow.js +17 -0
- package/dist/workflow/compile-reviewable-n8n-workflow.d.ts +3 -0
- package/dist/workflow/compile-reviewable-n8n-workflow.js +70 -0
- package/dist/workflow/compile-validated-workflow-fragments.d.ts +3 -0
- package/dist/workflow/compile-validated-workflow-fragments.js +20 -0
- package/dist/workflow/create-n8n-native-output-sources.d.ts +3 -0
- package/dist/workflow/create-n8n-native-output-sources.js +17 -0
- package/dist/workflow/review/create-gap-review-content.d.ts +2 -0
- package/dist/workflow/review/create-gap-review-content.js +28 -0
- package/dist/workflow/review/create-gap-review-region.d.ts +2 -0
- package/dist/workflow/review/create-gap-review-region.js +23 -0
- package/dist/workflow/review/escape-review-markdown.d.ts +1 -0
- package/dist/workflow/review/escape-review-markdown.js +7 -0
- package/dist/workflow/review/gap-review-region-layout.d.ts +4 -0
- package/dist/workflow/review/gap-review-region-layout.js +21 -0
- package/dist/workflow/validate-compiled-binding-sources.d.ts +4 -0
- package/dist/workflow/validate-compiled-binding-sources.js +40 -0
- package/dist/workflow/validate-node-fragments.d.ts +3 -0
- package/dist/workflow/validate-node-fragments.js +58 -0
- package/package.json +54 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 carefreelife98
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,102 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @openapi-flow/n8n
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deterministic OpenAPI request-node creation and explicit n8n DAG assembly with the official workflow SDK. Unreleased `0.2.0` source; this package is not yet published.
|
|
4
|
+
|
|
5
|
+
## Installed declaration dependencies
|
|
6
|
+
|
|
7
|
+
This adapter's public types reference `@n8n/workflow-sdk@0.36.4`, which depends on `n8n-workflow@2.43.4`. Declarations in that SDK/workflow chain, `@n8n/errors@0.15.0` and `@n8n/utils@1.50.1` import modules/types listed only as development dependencies. A consumer does not install another package's development dependencies. This adapter therefore declares the required packages in its own `dependencies`: `@n8n/config@2.41.2`, `@sentry/node@10.70.0`, `nock@14.0.14`, `@types/estree@1.0.8`, `@types/express@5.0.1`, `@types/json-schema@7.0.15`, `@types/luxon@3.2.0`, `@types/semver@7.7.0` and `@types/ssh2@1.15.0`. These versions follow the corresponding upstream manifests; a fresh strict consumer verifies compatibility. Installing Sentry does not initialize telemetry in this adapter.
|
|
8
|
+
|
|
9
|
+
Normal installation brings these packages automatically, including production installations that omit development dependencies. Consumers do not need to install them manually, enable `skipLibCheck`, add declaration shims or run a custom installation script. These dependencies do not start an HTTP server, execute nock or configure an n8n instance. Core and LangChain packages do not own this SDK-specific contract. Keep these declarations until a verified SDK/workflow upgrade supplies its complete public declaration dependency graph; remove them only after the independent strict consumer still passes. See the [TypeScript publishing guidance](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#dependencies).
|
|
10
|
+
|
|
11
|
+
## Public APIs
|
|
12
|
+
|
|
13
|
+
- `createHttpRequestNode`: full selected OAS contract + literal values/response bindings + optional trusted base URL/credential references → SDK node fragment. Bound requests additionally require `apiNodeNames` and compile into materialization Code → HTTP Request.
|
|
14
|
+
- `httpRequestDeploymentOptionsSchema`: shared validation for optional execution settings. Missing URLs become `https://replace_me.invalid`; required missing Bearer references become `REPLACE_ME:<documentId>:<schemeName>`. Supplied invalid settings fail rather than becoming placeholders.
|
|
15
|
+
- `assembleN8nWorkflow`: explicit fragments, named ports, edges and starts → SDK-validated importable JSON. Fan-out is supported; node-array order does not define execution order.
|
|
16
|
+
- `createN8nNativeCapabilities`: described Zod configuration schemas and deterministic compilers for IF, Merge Append, response-assertion Code and StopAndError. Custom capability registration is supported.
|
|
17
|
+
- `compilePlannedN8nWorkflow`: validates model/host graph plans against materials/capabilities, checks response availability and joins, and builds importable JSON using the SDK.
|
|
18
|
+
- `compileBatchedN8nWorkflow`: the same complete-plan checks plus explicit finite batch scopes → SDK Loop Over Items wiring, unchanged item ancestry and completion-time downstream execution.
|
|
19
|
+
- `compileReviewableN8nWorkflow`: validated review plan + ready/blocked API materials + ready fragments + native capabilities → common `workflow` JSON. Complete plans use the strict compiler. Gap plans return `{ status: 'needs-review', workflow, diagnostics }` with connected internal nodes and a detached Start.
|
|
20
|
+
- `compileInboundWorkflow`: preserved Webhook/Callback response compilation.
|
|
21
|
+
|
|
22
|
+
Built-in IF and response assertions provide capability-owned planning-schema hooks. Their response `nodeId` is an enum of supplied API call instances during generation, while their runtime parameter schemas remain unchanged. Without API materials, only literal operands are offered; other native capabilities remain usable. The specialized schema does not change OAS acceptance, data types, response values or SDK compilation. Both generation-time and final graph/reference checks are retained.
|
|
23
|
+
|
|
24
|
+
No LLM dependency, HTTP server, workflow import/publication or API execution. Compilation does not require per-path `effectPolicy`; the host owns execution approval. Bound requests preserve actual JSON types, validate the complete request against its OAS schema and reuse literal serialization. Ordinary reads require one unambiguous item; explicit linked mode uses n8n ancestry for HTTP bindings, IF and assertions. Native JSON output binding, Split Out array iteration including linked nested API arrays, Aggregate collection, shared-scope API item joins and explicit linear/complete-fan-out batch paths are implemented; conditional/filtering and nested batch scopes, cross-invocation joins and exhaustive response-schema proof remain pending. The validator is compiled on the host with [Ajv standalone code generation](https://ajv.js.org/standalone.html); n8n receives no schema compiler or eval requirement. Its bundled equality helper compares JSON structurally rather than inspecting constructors across isolated realms, without changing schemas or values. Missing required API values fail without defaults. Code is library-owned, not model-written. Multi-node fragments require explicit internal edges, validated and wired by the assembler. Earlier policy-aware single/sequence compilers live under `/legacy`.
|
|
25
|
+
|
|
26
|
+
Template nodes visibly list missing execution settings in their notes. No OAS server is selected automatically, no credential secret is embedded, and anonymous APIs receive no credential placeholder. Provide settings and regenerate before execution, or replace placeholders and select existing n8n credentials. Bound URLs also live in the Materialize node configuration; editing only the HTTP Request URL is insufficient. Fully configured output and strict `/legacy` contracts remain unchanged.
|
|
27
|
+
|
|
28
|
+
Review workflows preserve confirmed HTTP Request/native nodes, materialization nodes and internal DAG edges. Each gap and blocked API call becomes a main-input/main-output Code placeholder that always throws `OPENAPI_FLOW_UNRESOLVED_STEP`; a Sticky Note explains the reported requirement. Placeholder code is library-owned and encodes descriptions as data, never JavaScript instructions. No unavailable API, argument or response is fabricated. Every Start-to-root connection is omitted, and `active: false` is supplementary. Review status does not certify scenario coverage or execution safety. Reconnecting Start can execute real preceding API calls before the placeholder fails; host approval and credential controls remain necessary.
|
|
29
|
+
|
|
30
|
+
Each placeholder is enclosed in a red Sticky Note region with the Korean heading `확인 필요`, localized stage labels, replacement guidance and execution warnings. The node is named `확인 필요 · <nodeId>` and displays a short Korean note in the canvas. The region reserves space for its complete diagnostic text above the node; multiple regions are placed separately below existing positioned nodes. Supplied descriptions, identifiers and OAS contracts are preserved, not automatically translated or truncated. This uses built-in nodes only and needs no community-node installation.
|
|
31
|
+
|
|
32
|
+
Compilation types live in `src/types/reviewable-workflow.ts` and `src/types/workflow-compilation.ts`; fixed failure nodes in `src/nodes/gap/`; annotations/escaping in `src/workflow/review/`; assembly in `src/workflow/build-n8n-workflow.ts`. The assembler creates the sole Manual Trigger and preserves all internal edges. Core validates topology even with gaps. Unblocked calls cannot reference blocked API responses; confirmed response-dependent calls must themselves be explicitly blocked. Invalid OAS, literal inputs, ports, references or unsupported unblocked compilers are errors, not automatic gaps. Existing strict compilers retain their fail-fast behavior. The old unpublished notes-only preview API was replaced, not aliased.
|
|
33
|
+
|
|
34
|
+
`integration/workflow-review-local-n8n.mjs` imports/exports three public scripted-model gap cases and an independent-root case, executes only Start with zero API calls, then deliberately reconnects one case against a local contract server and verifies failure at the placeholder without downstream calls. Output is in `.local-artifacts/workflow-review/`. This tests CLI runtime, not browser partial execution, real LLM selection quality or business APIs. [n8n distinguishes manual, partial and production execution](https://docs.n8n.io/build/understand-workflows/understand-executions/types-of-executions/); `active: false` alone does not prevent manual execution. Redact scenario/request/diagnostic content before sharing workflow JSON.
|
|
35
|
+
|
|
36
|
+
Our code is MIT-licensed. The separately licensed `@n8n/workflow-sdk` dependency is subject to n8n's [Sustainable Use License](https://docs.n8n.io/n8n-community-license/). Our license does not change those terms. See [third-party notices](./THIRD_PARTY_NOTICES.md) and the [workspace guide](https://github.com/carefreelife98/openapi-flow#implemented-checkpoint-and-remaining-work).
|
|
37
|
+
|
|
38
|
+
## Native response checks
|
|
39
|
+
|
|
40
|
+
`compilePlannedN8nWorkflow`, `compileBatchedN8nWorkflow` and `compileReviewableN8nWorkflow` provide original API response contracts from their materials to native compilers. IF/assertions require the original OAS contract and actual source name for each referenced API. If invoking a capability's `compile` directly, supply `apiResponseContracts` alongside `apiNodeNames`; literal-only checks need no response context. Assembly rejects a different declared contract or producer.
|
|
41
|
+
|
|
42
|
+
Shared compilation lives in `nodes/native/response-checks/`, with types in `types/response-check-compilation.ts` and `types/response-check-guard.ts`. IF uses Code guard → official IF, preserving input values, binary fields and paired-item ancestry. Its expressions contain only source reads and comparisons. Assertions use the same full-response reader before checking values. Runtime status/media select the OAS response contract; matching one field never exempts invalid sibling fields. No schema or data conversion is introduced.
|
|
43
|
+
|
|
44
|
+
`test:array-iteration-local-n8n` covers 14 cases, including five new IF/assertion failures with no forbidden downstream calls. Its report explicitly retains the original error and the n8n display message. n8n 2.37.10's [task-runner parser](https://github.com/n8n-io/n8n/blob/n8n%402.37.10/packages/%40n8n/task-runner/src/js-task-runner/errors/execution-error.ts) truncates colon-containing errors despite preserving the original stack. This display defect remains unresolved; the adapter does not rewrite errors to accommodate it.
|
|
45
|
+
|
|
46
|
+
## Declared native output bindings
|
|
47
|
+
|
|
48
|
+
`createJsonOutputCapability({ name, description, parametersSchema })` creates an opt-in typed literal producer using the official SDK's Edit Fields (`n8n-nodes-base.set`, version 3.4) JSON mode. Parameters are validated unchanged: schema coercion, defaults or transformations cannot silently rewrite them. Expression-like strings and dotted keys remain data. This is not a general calculation engine or an automatic conversion step.
|
|
49
|
+
|
|
50
|
+
Native producers declare `outputSchema(parameters)` in their capability. `createN8nNativeOutputSources({ nativeNodes, capabilities, apiNodeNames })` derives the schema and actual SDK exit name. Pass these as `nativeOutputSources` to `createHttpRequestNode`; retain `apiNodeNames` for API response producers. API values are read from `json.body`; native values from the JSON item root. Both use the same pointer materializer and OAS request validation/serialization. Native source schemas are also validated before HTTP. A missing field, incompatible type, ambiguous multiple item output, wrong runtime name/schema or unavailable graph producer is an error, not a fallback.
|
|
51
|
+
|
|
52
|
+
`integration/native-output-bindings-local-n8n.mjs` uses a supplied private ICL OAS file and a local contract endpoint. Its CLI is an explicitly scripted baseline; `runNativeOutputValidation` also accepts a real LangChain model. It validates unchanged mixed JSON types, dotted keys, expression-like literal text and three producer failures. It does not call ICL, prove response business semantics, add iteration or broaden authentication. Artifacts live under `.local-artifacts/native-output-bindings/<plannerLabel>/` and are not published. The [Edit Fields documentation](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/) and [upstream implementation](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/Set/v2/SetV2.node.ts) describe JSON mode; the adapter disables inclusion of other input fields and dot notation explicitly.
|
|
53
|
+
|
|
54
|
+
Each validation attempt now retains an independent `runs/<UUID>/` directory: `planning-context.json`, successful `workflow.json`/`report.json`, or a `failure.json` containing the phase and application-owned planning stage/cause/raw output. It never copies unclassified transport messages, headers or request objects. Top-level workflow/report files still represent the latest successful result, not an aggregate success rate. No failure is repaired or retried by the runner.
|
|
55
|
+
|
|
56
|
+
Observed upstream issue: on n8n `2.37.10`, a valid top-level JSON string response with `autodetect` + `fullResponse: true` failed before the next node. The installed HTTP Request implementation parses the JSON at `HttpRequestV3.node.js:688`, then parses the resulting string again at line `799`. Current `createHttpRequestNode` fragments now use an explicit OSS response adapter: official File-mode HTTP transport `Receive <callId>` → deterministic Code decoder `Request <callId>`. The latter is the public fragment exit; all response names must refer to that decoded exit. The adapter reads original bytes using the official `this.helpers.getBinaryDataBuffer` API and parses JSON once, without changing OAS contracts or repairing values. This is an OSS mitigation, not an upstream n8n fix. Legacy generation entrypoints are unchanged.
|
|
57
|
+
|
|
58
|
+
`createResponseCollectionCapability({ materials })` registers `collect-api-responses`. Only source call ID and pointer are planned; code validates every original response and compiles Code reader → Aggregate. Its `{items: values}` output preserves nesting, nulls, duplicates and input item order. Missing pointers fail before Aggregate, never becoming null substitutes. Collection applies to one incoming stream in one invocation, not a join or standalone accumulator. A batch scope's done stream can supply all processed items in one invocation; the reader uses each item's ancestry to resolve source responses across node runs. Empty streams do not fabricate output. Source name/contract declarations are checked against compiled workflow materials for both API and native fragments.
|
|
59
|
+
|
|
60
|
+
File mode disables n8n's automatic JSON request encoding too. The OSS serializer encodes the OAS value once; the node adapter encodes that wire text into the JSON Body setting, which n8n parses back to the unchanged wire text before sending. The API does not receive the setting envelope. Both literal and materialized JSON bodies use this contract, including strings, nulls, arrays and DELETE bodies.
|
|
61
|
+
|
|
62
|
+
`npm run test:json-response-local-n8n` verifies nine unchanged JSON values through source → OAS validation → response-bound echo POST → literal echo DELETE → response collection. Both actual received bodies and responses must equal the original values. Ordinary, numeric-looking, JSON-shaped and empty strings retain their types. `node integration/json-response-local-n8n.mjs --native` retains a separately failing unmodified-n8n baseline. Artifacts: `.local-artifacts/json-response/` and its `native/` subdirectory. `npm run test:http-response-decoding-local-n8n` checks twelve additional transport cases, including malformed JSON, invalid UTF-8, structured `+json`, text charsets, binary bytes and no-body responses. Raw metadata/status and item ancestry remain intact; invalid content is never replaced by `{}`, `null` or an empty item. JSON is UTF-8; text uses its explicit header charset or the native UTF-8 codec default. Binary routing retains the verified HTTP Request v4.3 policy, with bytes exposed as `binary.data`. The temporary `openApiFlowRawResponse` binary field is reserved for the transport adapter and removed from its output. See [official binary access](https://docs.n8n.io/code/cookbook/code-node/get-binary-data-buffer/).
|
|
63
|
+
|
|
64
|
+
`createItemJoinCapability({ materials, scopes })` registers `join-api-items` with existing API contracts and actual compiled native scope sources. Only `scopeNodeId` and unique `sourceCallIds` are planned. Input ports correspond to that call order; each port requires its own API and the shared native ancestor. Per-branch readers validate full OAS responses and the native scope, official Merge Append waits for the branches, and library-owned grouping combines only responses whose n8n ancestry identifies the same scope object. It never matches business IDs, equal JSON or independent stream positions. Missing/duplicate branch results and ambiguous ancestry are errors.
|
|
65
|
+
|
|
66
|
+
Output `{ responses: { callId: unchangedBody } }` preserves values and every contributing item link. Results follow original scope order. Both branches excluding an item yields no result for that item; an asymmetric exclusion is an error, not a silently dropped join. Empty scopes emit nothing. Scope names/schemas and API contracts are checked against the compiled producers. The implementation is invocation-scoped and can join complete branches in each explicit batch invocation, as described below. Cross-invocation joins and nested batch scopes remain pending. SDK fragments may declare validated `inputEndpoints` for separate input readers; the workflow assembler routes the ports to those actual entry nodes.
|
|
67
|
+
|
|
68
|
+
Derived response pointer/item candidates preserve conjunctive constraints and normalize OAS 3.0 syntax before native validation. They do not replace full source validation: projected oneOf branches can overlap, and item candidates do not prove tuple positions, sibling conditions or complete schema implication. `npm run test:schema-projection-local-n8n` checks composite item binding and nullable scalar collection through the installed n8n runtime. Neither original OAS contracts nor data are modified.
|
|
69
|
+
|
|
70
|
+
## Nested API response arrays
|
|
71
|
+
|
|
72
|
+
Register `createResponseArrayCapability({ materials, itemMode: 'linked' })` to split each input's linked API response array. The host selects execution mode; the planner still supplies only `sourceNodeId` and `pointer`. The reader validates every full OAS response before Split Out and pairs each array wrapper with its own input. Split Out preserves that parent link for every unchanged child. Dependent linked API calls can read both split levels and intermediate API responses. Empty arrays produce no child requests; missing/ambiguous ancestry and invalid OAS values fail without substitution. The ordinary array registry retains its singleton guard.
|
|
73
|
+
|
|
74
|
+
`npm run test:nested-array-iteration-local-n8n` verifies two split levels, five APIs, repeated parent/child IDs, IF-filtered parents, an empty middle array, full empty streams and invalid response/media cases on n8n 2.37.10. Local import JSON/report: `.local-artifacts/nested-array-iteration/`. This is a deterministic regression, not an LLM quality score. Cross-invocation accumulation and automatic scope selection remain pending. Explicit batch execution is a separate compiler below.
|
|
75
|
+
|
|
76
|
+
### Native JSON output arrays
|
|
77
|
+
|
|
78
|
+
`createNativeArrayCapability({ sources, itemMode: 'linked' })` accepts actual native output contracts and compiled names from `createN8nNativeOutputSources`. Its structured-output parameters contain only the supplied `sourceNodeId` and array `pointer`. Code derives the item contract and validates the whole original native output before official Split Out, sharing mechanics with API array splitting in `src/nodes/native/arrays/`. Native pointers start at the JSON root rather than an HTTP response body. No conversion, flattening or default values are added. Empty arrays emit no children; linked mode retains parent ancestry; ordinary mode rejects ambiguous multiple inputs/sources.
|
|
79
|
+
|
|
80
|
+
The host derives parent contracts before registering a child planning stage; automatic recursive scope discovery remains pending. Source names/schemas and dependencies are checked by workflow assembly. `npm run test:native-array-iteration-local-n8n` covers twenty cases: ten each with inline and recursive-reference producer contracts. Both variants include two splits, five distinct REST APIs, fan-out/join/collection, empty streams and original native/API contract failures. Local artifacts: `.local-artifacts/native-array-iteration/`, including `five-api-references-workflow.json`. These are deterministic fixtures, not model quality results.
|
|
81
|
+
|
|
82
|
+
Native output validation compiles each original schema independently instead of embedding producers under a shared root. Core's item/pointer projections bundle the original resource and reference its locations using Ajv's public registry and fast-uri; local and recursive `$ref`, anchors and nested IDs retain scope. The host assigns an offline retrieval URI to anonymous resources without changing original input contracts or values. Runtime nodes receive standalone validators, not a schema resolver. `npm run test:native-schema-references` now verifies the original compiler and reusable item contract as a passing regression. Missing references fail; external registry injection, dynamic-scope projection coverage and exhaustive schema implication remain pending.
|
|
83
|
+
|
|
84
|
+
## Finite batch paths
|
|
85
|
+
|
|
86
|
+
Call `compileBatchedN8nWorkflow({ ...completePlanInput, batchScopes })`. Required scopes contain `id`, `batchSize`, `nodeIds`, `entryNodeId` and `exitNodeId`. No scope, size or member is inferred. The logical DAG and all existing provenance/response/port checks remain intact; only the adapter adds official SDK Loop Over Items v3 controllers and their return edges. Scope size is a positive safe integer, reset is false and controller IDs/names must not collide with any existing SDK node.
|
|
87
|
+
|
|
88
|
+
Scopes must not overlap and must have a single entry/exit. Fragments declare either `preservesInputItems` for one-to-one linked steps or `joinsInputItemsByAncestry: { scopeNodeId }` for explicit joins of complete branches. `validateBatchItemFlow` traces the declared flow, checks one branch per join input and requires a shared scope ancestor that identifies every entry item. Fan-out is allowed without cloning logical/API nodes. Item-preserving relays before the scope retain ancestry. Earlier batch completion is not assumed to be the original exit node's latest run. Custom compilers must honor their declarations; this is not arbitrary JavaScript proof.
|
|
89
|
+
|
|
90
|
+
`createPassThroughCapability()` is opt-in and compiles the official [No Operation node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.noop/), version 1, with a strict empty configuration. It preserves input items/links unchanged and can supply the common entry before fan-out. It neither emits synthetic items nor declares a guessed native output schema.
|
|
91
|
+
|
|
92
|
+
IF with a discarded alternative, item-dropping Filter, Split Out, Aggregate and ordinary Merge Append lack a complete batch return contract: an empty branch may never execute its return and stop the remaining batches. Place Split Out before the scope and Aggregate after completion. This does not narrow OAS intake. Item-dropping Filter completion, nested batch scopes, cross-invocation joins and automatic model scope planning remain pending.
|
|
93
|
+
|
|
94
|
+
Register `createExclusiveBranchMergeCapability()` explicitly for `rejoin-exclusive-branches`. Its only parameter is an existing IF's `sourceNodeId`; code owns the official Merge 3.2 append parameters and two input ports. Core verifies distinct complete alternatives and retains route-specific response availability. Batch validation requires every IF output to return through a one-to-one linked path and rejoin inside the same scope. IF declares `partitionsInputItems`; rejoin declares `rejoinsExclusiveOutputs`, rather than claiming a single-input contract. Normal Merge Append is unchanged. Custom compilers must preserve these declarations.
|
|
95
|
+
|
|
96
|
+
An explicit `createPassThroughCapability()` skip branch returns the actual item unchanged, not an empty completion item. In n8n 2.37.10 with `executionOrder: 'v1'`, waiting Merge handles an alternative that never ran after runnable work is exhausted. No `alwaysOutputData`, forced execution order changes or missing-response defaults are added. A response from only the executed business branch is still unavailable unconditionally after rejoin.
|
|
97
|
+
|
|
98
|
+
`npm run test:conditional-batch-local-n8n` checks 14 deterministic cases with five distinct API nodes, all/mixed/no eligible items at batch sizes 1/2/10, empty input and contract/assertion failures. It collects all original detail items once after completion; skipped items are not successful business API responses. Import JSONs/report: `.local-artifacts/conditional-batch/`. These tests do not measure real-model planning or call business services. Source contracts and validators remain separated under `src/schemas/`, `src/types/`, core `src/workflow/` and n8n `src/workflow/batch/`.
|
|
99
|
+
|
|
100
|
+
`npm run test:batch-iteration-local-n8n` verifies ten cases on n8n 2.37.10, including five API instances, multiple batch sizes, linked cross-run references and one final array-body request. Import JSONs/report live under `.local-artifacts/batch-iteration/`; regenerate with a running endpoint before execution. Types and schema are separated in `src/types/batch-execution.ts` and `src/schemas/batch-execution-schema.ts`; validation/lowering live in `src/workflow/batch/` and reuse `compile-validated-workflow-fragments.ts`.
|
|
101
|
+
|
|
102
|
+
`npm run test:batch-item-join-local-n8n` verifies eight complete-branch cases on the same pinned runtime. Reordered branches and identical original values retain distinct ancestry; source/branch contract errors and wrong media type stop later batches and final collection. The three-branch/five-API case makes 13 requests for three items and collects once. Local import JSONs/report: `.local-artifacts/batch-item-join/`. These deterministic fixtures test runtime behavior, not Chomsky selection quality or business APIs.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
`@openapi-flow/n8n` depends on `@n8n/workflow-sdk`, separately licensed under n8n's [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md). Its terms restrict use and redistribution; check them for your intended use. This package does not bundle the SDK's source code.
|
|
4
|
+
|
|
5
|
+
Other dependencies retain their own licenses. The MIT license in this package applies to openapi-flow's code only.
|
|
6
|
+
|
|
7
|
+
The adapter also installs `@n8n/config` and its dependencies to resolve the pinned SDK's public declaration imports. The installed `@n8n/config`, `@n8n/di` and `@n8n/constants` packages ship their own n8n license notices; this adapter does not replace those notices or bundle those packages' source code. `nock` and the Express, JSON Schema, SSH2 and Luxon declaration packages likewise retain their own license notices.
|
|
8
|
+
|
|
9
|
+
Runtime-bound HTTP requests include library-owned materialization code and an Ajv-generated standalone request validator. Any bundled validator dependency retains its complete license notice in the generated Code node. Ajv and ajv-formats are MIT-licensed; their transitive runtime helpers retain their own MIT or BSD notices when bundled. The schema compiler and the n8n SDK are not embedded in these Code nodes. esbuild is used on the host to bundle standalone runtime code and is separately MIT-licensed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assertSerializablePlan(plan: unknown): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function workflowId(baseUrl: string, plan: unknown): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { node, trigger, validateWorkflow, workflow } from '@n8n/workflow-sdk';
|
|
2
|
+
import { UnsupportedOperationError } from '@openapi-flow/core/internal';
|
|
3
|
+
import { workflowId } from '../common/workflow-id.js';
|
|
4
|
+
import { inboundResponseBody } from './inbound-response.js';
|
|
5
|
+
export function buildInboundWorkflow(context) {
|
|
6
|
+
const { document, selected, plan } = context;
|
|
7
|
+
const { candidate, operation } = selected;
|
|
8
|
+
if (!['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'].includes(candidate.method))
|
|
9
|
+
throw new UnsupportedOperationError(candidate.operationRef, `operationRef ${candidate.operationRef} uses HTTP method ${candidate.method}, which n8n Webhook node v2.1 does not expose`);
|
|
10
|
+
const effectiveSecurity = operation.security === undefined
|
|
11
|
+
? document.spec.security
|
|
12
|
+
: operation.security;
|
|
13
|
+
if (Array.isArray(effectiveSecurity) && effectiveSecurity.length > 0)
|
|
14
|
+
throw new UnsupportedOperationError(candidate.operationRef, `operationRef ${candidate.operationRef} requires inbound authentication that has no n8n Webhook credential mapping`);
|
|
15
|
+
const responseBody = inboundResponseBody(context);
|
|
16
|
+
const id = workflowId(plan.webhookPath, plan);
|
|
17
|
+
const start = trigger({
|
|
18
|
+
type: 'n8n-nodes-base.webhook',
|
|
19
|
+
version: 2.1,
|
|
20
|
+
config: {
|
|
21
|
+
id: 'webhook',
|
|
22
|
+
name: 'Webhook',
|
|
23
|
+
webhookId: id,
|
|
24
|
+
position: [240, 300],
|
|
25
|
+
parameters: {
|
|
26
|
+
httpMethod: candidate.method,
|
|
27
|
+
path: plan.webhookPath,
|
|
28
|
+
authentication: 'none',
|
|
29
|
+
responseMode: 'responseNode',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
const respond = node({
|
|
34
|
+
type: 'n8n-nodes-base.respondToWebhook',
|
|
35
|
+
version: 1.5,
|
|
36
|
+
config: {
|
|
37
|
+
id: 'respond',
|
|
38
|
+
name: 'Respond to Webhook',
|
|
39
|
+
position: [480, 300],
|
|
40
|
+
parameters: {
|
|
41
|
+
respondWith: responseBody === undefined ? 'noData' : 'json',
|
|
42
|
+
...(responseBody === undefined ? {} : { responseBody }),
|
|
43
|
+
options: { responseCode: plan.responseStatus },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const built = workflow(id, 'OpenAPI ' + candidate.source + ' ' + candidate.method)
|
|
48
|
+
.add(start)
|
|
49
|
+
.to(respond);
|
|
50
|
+
const validation = validateWorkflow(built);
|
|
51
|
+
if (!validation.valid)
|
|
52
|
+
throw new Error('n8n SDK validation failed: ' +
|
|
53
|
+
validation.errors.map((error) => error.message).join('; '));
|
|
54
|
+
return {
|
|
55
|
+
status: 'complete',
|
|
56
|
+
plan,
|
|
57
|
+
evidence: candidate,
|
|
58
|
+
workflow: built.toJSON(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { validateAndResolveOpenApiDocument } from '@openapi-flow/core/internal';
|
|
2
|
+
import { inboundOperationSourcesFromDocument } from '@openapi-flow/core/internal';
|
|
3
|
+
import { isObject } from '@openapi-flow/core/internal';
|
|
4
|
+
import { assertSerializablePlan } from '../common/plan-validation.js';
|
|
5
|
+
import { buildInboundWorkflow } from './build-inbound-workflow.js';
|
|
6
|
+
export async function compileInboundWorkflow({ spec, plan, }) {
|
|
7
|
+
if (!isObject(plan) ||
|
|
8
|
+
plan.version !== '1' ||
|
|
9
|
+
typeof plan.goal !== 'string' ||
|
|
10
|
+
!plan.goal.trim() ||
|
|
11
|
+
typeof plan.operationRef !== 'string' ||
|
|
12
|
+
typeof plan.webhookPath !== 'string' ||
|
|
13
|
+
!plan.webhookPath.trim() ||
|
|
14
|
+
!Number.isInteger(plan.responseStatus) ||
|
|
15
|
+
plan.responseStatus < 100 ||
|
|
16
|
+
plan.responseStatus > 599)
|
|
17
|
+
throw new Error('plan must contain version 1, goal, operationRef, webhookPath, and responseStatus');
|
|
18
|
+
assertSerializablePlan(plan);
|
|
19
|
+
const document = await validateAndResolveOpenApiDocument(spec);
|
|
20
|
+
const selected = inboundOperationSourcesFromDocument(document).find(({ candidate }) => candidate.operationRef === plan.operationRef);
|
|
21
|
+
if (!selected)
|
|
22
|
+
throw new Error('plan.operationRef is not in spec.webhooks or callbacks: ' +
|
|
23
|
+
plan.operationRef);
|
|
24
|
+
return buildInboundWorkflow({ document, selected, plan });
|
|
25
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { checkSchemaValue } from '@openapi-flow/core/internal';
|
|
2
|
+
import { dereferencedObject, object } from '@openapi-flow/core/internal';
|
|
3
|
+
import { UnsupportedOperationError } from '@openapi-flow/core/internal';
|
|
4
|
+
export function inboundResponseBody({ selected, plan, }) {
|
|
5
|
+
const { candidate, operation } = selected;
|
|
6
|
+
const responses = object(operation.responses, `operationRef ${candidate.operationRef}.responses`);
|
|
7
|
+
const status = String(plan.responseStatus);
|
|
8
|
+
const responseKey = Object.hasOwn(responses, status)
|
|
9
|
+
? status
|
|
10
|
+
: Object.hasOwn(responses, status[0] + 'XX')
|
|
11
|
+
? status[0] + 'XX'
|
|
12
|
+
: Object.hasOwn(responses, 'default')
|
|
13
|
+
? 'default'
|
|
14
|
+
: undefined;
|
|
15
|
+
if (responseKey === undefined)
|
|
16
|
+
throw new Error(`plan.responseStatus ${status} is not declared by operationRef ${candidate.operationRef}`);
|
|
17
|
+
const response = dereferencedObject(responses[responseKey], `operationRef ${candidate.operationRef}.responses.${responseKey}`);
|
|
18
|
+
if (response.headers !== undefined) {
|
|
19
|
+
const headers = object(response.headers, `operationRef ${candidate.operationRef}.responses.${responseKey}.headers`);
|
|
20
|
+
for (const [name, raw] of Object.entries(headers)) {
|
|
21
|
+
const header = dereferencedObject(raw, `operationRef ${candidate.operationRef}.responses.${responseKey}.headers.${name}`);
|
|
22
|
+
if (header.required === true)
|
|
23
|
+
throw new UnsupportedOperationError(candidate.operationRef, `operationRef ${candidate.operationRef} response header ${name} needs a host value`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
let responseBody;
|
|
27
|
+
if (plan.responseBody !== undefined) {
|
|
28
|
+
const content = object(response.content, `operationRef ${candidate.operationRef}.responses.${responseKey}.content`);
|
|
29
|
+
const declared = Object.keys(content);
|
|
30
|
+
const mediaType = plan.responseMediaType ??
|
|
31
|
+
(declared.length === 1 ? declared[0] : undefined);
|
|
32
|
+
if (mediaType === undefined)
|
|
33
|
+
throw new Error('plan.responseMediaType is required for this response body');
|
|
34
|
+
if (!Object.hasOwn(content, mediaType))
|
|
35
|
+
throw new Error(`plan.responseMediaType ${mediaType} is not declared by operationRef ${candidate.operationRef}`);
|
|
36
|
+
if (mediaType !== 'application/json')
|
|
37
|
+
throw new UnsupportedOperationError(candidate.operationRef, `operationRef ${candidate.operationRef} response media type ${mediaType} has no n8n mapping`);
|
|
38
|
+
const media = dereferencedObject(content[mediaType], `operationRef ${candidate.operationRef}.responses.${responseKey}.content.${mediaType}`);
|
|
39
|
+
if (media.schema !== undefined) {
|
|
40
|
+
const schema = typeof media.schema === 'boolean'
|
|
41
|
+
? media.schema
|
|
42
|
+
: dereferencedObject(media.schema, `operationRef ${candidate.operationRef}.responses.${responseKey}.content.${mediaType}.schema`);
|
|
43
|
+
checkSchemaValue(plan.responseBody, schema, 'plan.responseBody');
|
|
44
|
+
}
|
|
45
|
+
responseBody = JSON.stringify(plan.responseBody);
|
|
46
|
+
}
|
|
47
|
+
else if (plan.responseMediaType !== undefined) {
|
|
48
|
+
throw new Error('plan.responseMediaType requires plan.responseBody');
|
|
49
|
+
}
|
|
50
|
+
return responseBody;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function originFrom(baseUrl: string): URL;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function originFrom(baseUrl) {
|
|
2
|
+
if (typeof baseUrl !== 'string' || !URL.canParse(baseUrl)) {
|
|
3
|
+
throw new Error('baseUrl must be an absolute HTTP(S) URL');
|
|
4
|
+
}
|
|
5
|
+
const url = new URL(baseUrl);
|
|
6
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
7
|
+
url.username ||
|
|
8
|
+
url.password ||
|
|
9
|
+
url.search ||
|
|
10
|
+
url.hash) {
|
|
11
|
+
throw new Error('baseUrl must be an HTTP(S) URL without credentials, query, or fragment');
|
|
12
|
+
}
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkflowJSON } from '@n8n/workflow-sdk';
|
|
2
|
+
import type { BuildRequestWorkflowInput } from '../../../types/legacy/request-workflow.js';
|
|
3
|
+
export declare function buildRequestWorkflow({ baseUrl, plan, operation, url, body, headers, authentication, expectedBody, }: BuildRequestWorkflowInput): WorkflowJSON;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { node, trigger, validateWorkflow, workflow } from '@n8n/workflow-sdk';
|
|
2
|
+
import { workflowId } from '../common/workflow-id.js';
|
|
3
|
+
import { httpRequestNode } from './http-request-node.js';
|
|
4
|
+
import { assertionCode } from './response-assertion-code.js';
|
|
5
|
+
export function buildRequestWorkflow({ baseUrl, plan, operation, url, body, headers, authentication, expectedBody, }) {
|
|
6
|
+
const shouldAssert = Object.keys(expectedBody).length > 0;
|
|
7
|
+
const start = trigger({
|
|
8
|
+
type: 'n8n-nodes-base.manualTrigger',
|
|
9
|
+
version: 1,
|
|
10
|
+
config: { id: 'start', name: 'Start', position: [240, 300] },
|
|
11
|
+
});
|
|
12
|
+
const request = httpRequestNode({
|
|
13
|
+
operation,
|
|
14
|
+
id: 'request',
|
|
15
|
+
name: operation.method + ' ' + operation.path,
|
|
16
|
+
position: [480, 300],
|
|
17
|
+
url,
|
|
18
|
+
body,
|
|
19
|
+
headers,
|
|
20
|
+
authentication,
|
|
21
|
+
shouldAssert,
|
|
22
|
+
});
|
|
23
|
+
const built = workflow(workflowId(baseUrl, plan), 'OpenAPI ' + operation.method + ' ' + operation.path)
|
|
24
|
+
.add(start)
|
|
25
|
+
.to(request);
|
|
26
|
+
const result = shouldAssert
|
|
27
|
+
? built.to(node({
|
|
28
|
+
type: 'n8n-nodes-base.code',
|
|
29
|
+
version: 2,
|
|
30
|
+
config: {
|
|
31
|
+
id: 'assert',
|
|
32
|
+
name: 'Assert response',
|
|
33
|
+
position: [720, 300],
|
|
34
|
+
parameters: {
|
|
35
|
+
mode: 'runOnceForAllItems',
|
|
36
|
+
jsCode: assertionCode(expectedBody),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}))
|
|
40
|
+
: built;
|
|
41
|
+
const validation = validateWorkflow(result);
|
|
42
|
+
if (!validation.valid) {
|
|
43
|
+
throw new Error('n8n SDK validation failed: ' +
|
|
44
|
+
validation.errors.map((error) => error.message).join('; '));
|
|
45
|
+
}
|
|
46
|
+
return result.toJSON();
|
|
47
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkflowJSON } from '@n8n/workflow-sdk';
|
|
2
|
+
import type { BuildSequenceWorkflowInput } from '../../../types/legacy/sequence-workflow.js';
|
|
3
|
+
export declare function buildSequenceWorkflow({ baseUrl, plan, nodes, requiredOutputs, }: BuildSequenceWorkflowInput): WorkflowJSON;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { node, trigger, validateWorkflow, workflow } from '@n8n/workflow-sdk';
|
|
2
|
+
import { responseFieldType } from '@openapi-flow/core/internal';
|
|
3
|
+
import { workflowId } from '../common/workflow-id.js';
|
|
4
|
+
import { httpRequestNode } from './http-request-node.js';
|
|
5
|
+
import { assertionCode } from './response-assertion-code.js';
|
|
6
|
+
export function buildSequenceWorkflow({ baseUrl, plan, nodes, requiredOutputs, }) {
|
|
7
|
+
let built = workflow(workflowId(baseUrl, plan), 'OpenAPI sequence').add(trigger({
|
|
8
|
+
type: 'n8n-nodes-base.manualTrigger',
|
|
9
|
+
version: 1,
|
|
10
|
+
config: { id: 'start', name: 'Start', position: [240, 300] },
|
|
11
|
+
}));
|
|
12
|
+
nodes.forEach(({ operation, id, url, body, headers, expectedBody, authentication }, index) => {
|
|
13
|
+
const requiredFields = {};
|
|
14
|
+
for (const field of requiredOutputs.get(id) ?? []) {
|
|
15
|
+
requiredFields[field] = responseFieldType(operation, field);
|
|
16
|
+
}
|
|
17
|
+
const shouldAssert = Object.keys(expectedBody).length > 0 ||
|
|
18
|
+
Object.keys(requiredFields).length > 0;
|
|
19
|
+
const request = httpRequestNode({
|
|
20
|
+
operation,
|
|
21
|
+
id: 'request-' + id,
|
|
22
|
+
name: 'Request ' + id,
|
|
23
|
+
position: [480 + index * 480, 300],
|
|
24
|
+
url,
|
|
25
|
+
body,
|
|
26
|
+
headers,
|
|
27
|
+
authentication,
|
|
28
|
+
shouldAssert,
|
|
29
|
+
});
|
|
30
|
+
built = built.to(request);
|
|
31
|
+
if (!shouldAssert)
|
|
32
|
+
return;
|
|
33
|
+
const assert = node({
|
|
34
|
+
type: 'n8n-nodes-base.code',
|
|
35
|
+
version: 2,
|
|
36
|
+
config: {
|
|
37
|
+
id: 'assert-' + id,
|
|
38
|
+
name: 'Assert ' + id,
|
|
39
|
+
position: [720 + index * 480, 300],
|
|
40
|
+
parameters: {
|
|
41
|
+
mode: 'runOnceForAllItems',
|
|
42
|
+
jsCode: assertionCode(expectedBody, requiredFields),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
built = built.to(assert);
|
|
47
|
+
});
|
|
48
|
+
const validation = validateWorkflow(built);
|
|
49
|
+
if (!validation.valid)
|
|
50
|
+
throw new Error('n8n SDK validation failed: ' +
|
|
51
|
+
validation.errors.map((error) => error.message).join('; '));
|
|
52
|
+
return built.toJSON();
|
|
53
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createOperationCatalog } from '@openapi-flow/core/internal';
|
|
2
|
+
import { operationFromDocument } from '@openapi-flow/core/internal';
|
|
3
|
+
import { isObject } from '@openapi-flow/core/internal';
|
|
4
|
+
import { assertSerializablePlan } from '../common/plan-validation.js';
|
|
5
|
+
import { originFrom } from './base-url.js';
|
|
6
|
+
import { isSafeMethod } from './effect-policy.js';
|
|
7
|
+
import { buildSequenceWorkflow } from './build-sequence-workflow.js';
|
|
8
|
+
import { prepareSteps } from './prepare-sequence.js';
|
|
9
|
+
export async function compileCatalogSequence({ sources, profile, plan, }) {
|
|
10
|
+
if (profile !== 'read-only' && profile !== 'test')
|
|
11
|
+
throw new Error('profile must be read-only or test');
|
|
12
|
+
if (!isObject(plan) ||
|
|
13
|
+
plan.version !== '1' ||
|
|
14
|
+
typeof plan.goal !== 'string' ||
|
|
15
|
+
!plan.goal.trim() ||
|
|
16
|
+
!Array.isArray(plan.steps) ||
|
|
17
|
+
(plan.gaps !== undefined && !Array.isArray(plan.gaps)) ||
|
|
18
|
+
(plan.steps.length === 0 && (!plan.gaps || plan.gaps.length === 0)))
|
|
19
|
+
throw new Error('plan must contain version 1, goal, steps or gaps');
|
|
20
|
+
assertSerializablePlan(plan);
|
|
21
|
+
const catalog = await createOperationCatalog(sources);
|
|
22
|
+
const gaps = plan.gaps ?? [];
|
|
23
|
+
for (const gap of gaps) {
|
|
24
|
+
if (!isObject(gap) ||
|
|
25
|
+
(gap.kind !== 'missing_operation' &&
|
|
26
|
+
gap.kind !== 'insufficient_contract') ||
|
|
27
|
+
typeof gap.description !== 'string' ||
|
|
28
|
+
!gap.description.trim())
|
|
29
|
+
throw new Error('plan.gaps[] must contain kind and non-empty description');
|
|
30
|
+
if (gap.kind === 'missing_operation' &&
|
|
31
|
+
(gap.documentId !== undefined || gap.operationRef !== undefined))
|
|
32
|
+
throw new Error('plan.gaps[] missing_operation must not identify an operation');
|
|
33
|
+
if (gap.kind === 'insufficient_contract') {
|
|
34
|
+
if (typeof gap.documentId !== 'string' ||
|
|
35
|
+
typeof gap.operationRef !== 'string')
|
|
36
|
+
throw new Error('plan.gaps[] insufficient_contract needs documentId and operationRef');
|
|
37
|
+
const source = catalog.sources.get(gap.documentId);
|
|
38
|
+
if (!source ||
|
|
39
|
+
!source.operations.some((operation) => operation.operationRef === gap.operationRef))
|
|
40
|
+
throw new Error('plan.gaps[] insufficient_contract operation is not in sources');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const stepById = new Map();
|
|
44
|
+
for (const step of plan.steps) {
|
|
45
|
+
if (!isObject(step) ||
|
|
46
|
+
typeof step.documentId !== 'string' ||
|
|
47
|
+
!step.documentId.trim())
|
|
48
|
+
throw new Error('plan.steps[].documentId must be non-empty');
|
|
49
|
+
if (typeof step.id !== 'string' || !step.id.trim() || stepById.has(step.id))
|
|
50
|
+
throw new Error('plan.steps must have unique non-empty id values');
|
|
51
|
+
stepById.set(step.id, step.documentId);
|
|
52
|
+
}
|
|
53
|
+
const prepared = prepareSteps({
|
|
54
|
+
profile,
|
|
55
|
+
plan,
|
|
56
|
+
resolveStep: (step) => {
|
|
57
|
+
const documentId = stepById.get(step.id);
|
|
58
|
+
const source = documentId === undefined ? undefined : catalog.sources.get(documentId);
|
|
59
|
+
if (!source)
|
|
60
|
+
throw new Error(`plan.steps[${step.id}].documentId is not in sources: ${documentId}`);
|
|
61
|
+
const target = sources.find((item) => item.id === source.id);
|
|
62
|
+
if (!target)
|
|
63
|
+
throw new Error(`sources[${source.id}] execution configuration is missing`);
|
|
64
|
+
return {
|
|
65
|
+
operation: operationFromDocument(source.document, step.operationRef),
|
|
66
|
+
origin: originFrom(target.baseUrl),
|
|
67
|
+
effectPolicy: target.effectPolicy,
|
|
68
|
+
credentialBindings: target.credentialBindings,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
const evidence = prepared.evidence.map((entry) => ({
|
|
73
|
+
...entry,
|
|
74
|
+
documentId: stepById.get(entry.stepId),
|
|
75
|
+
}));
|
|
76
|
+
const diagnostics = [
|
|
77
|
+
...gaps.map((gap) => ({
|
|
78
|
+
code: gap.kind,
|
|
79
|
+
message: gap.description,
|
|
80
|
+
...(gap.documentId === undefined ? {} : { documentId: gap.documentId }),
|
|
81
|
+
...(gap.operationRef === undefined
|
|
82
|
+
? {}
|
|
83
|
+
: { operationRef: gap.operationRef }),
|
|
84
|
+
})),
|
|
85
|
+
...prepared.missingInputs.map((missing) => ({
|
|
86
|
+
code: 'missing_input',
|
|
87
|
+
message: missing,
|
|
88
|
+
stepId: missing.split('.')[0],
|
|
89
|
+
})),
|
|
90
|
+
...evidence
|
|
91
|
+
.filter((entry) => entry.effect === 'unknown' ||
|
|
92
|
+
(!isSafeMethod(entry.method) && entry.effect !== 'write') ||
|
|
93
|
+
(profile === 'read-only' &&
|
|
94
|
+
(!isSafeMethod(entry.method) || entry.effect !== 'read')))
|
|
95
|
+
.map((entry) => ({
|
|
96
|
+
code: 'effect_not_approved',
|
|
97
|
+
message: `step ${entry.stepId} requires an approved effect`,
|
|
98
|
+
stepId: entry.stepId,
|
|
99
|
+
})),
|
|
100
|
+
];
|
|
101
|
+
const status = gaps.length
|
|
102
|
+
? 'needs_capability'
|
|
103
|
+
: prepared.blocked
|
|
104
|
+
? 'blocked'
|
|
105
|
+
: prepared.missingInputs.length
|
|
106
|
+
? 'needs_input'
|
|
107
|
+
: 'complete';
|
|
108
|
+
if (status !== 'complete')
|
|
109
|
+
return { status, plan, evidence, diagnostics };
|
|
110
|
+
return {
|
|
111
|
+
status,
|
|
112
|
+
plan,
|
|
113
|
+
evidence,
|
|
114
|
+
diagnostics,
|
|
115
|
+
workflow: buildSequenceWorkflow({
|
|
116
|
+
baseUrl: JSON.stringify(sources.map(({ id, baseUrl }) => ({ id, baseUrl }))),
|
|
117
|
+
plan,
|
|
118
|
+
nodes: prepared.nodes,
|
|
119
|
+
requiredOutputs: prepared.requiredOutputs,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}
|