@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
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CreateResponseCheckReaderInput, CompiledResponseCheckReader } from '../../../types/response-check-compilation.js';
|
|
2
|
+
/** Reuse full OAS response validation before native comparisons, without changing data. */
|
|
3
|
+
export declare function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiResponseContracts, linked, }: CreateResponseCheckReaderInput): CompiledResponseCheckReader;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { responseReferences } from '../common/response-references.js';
|
|
3
|
+
import { createOutputReaderCode } from '../../request/bindings/create-output-reader-code.js';
|
|
4
|
+
/** Reuse full OAS response validation before native comparisons, without changing data. */
|
|
5
|
+
export function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiResponseContracts, linked, }) {
|
|
6
|
+
const ids = [...new Set(responseReferences(checks).map((ref) => ref.nodeId))];
|
|
7
|
+
const bindingSources = ids.map((id) => {
|
|
8
|
+
const operation = apiResponseContracts?.[id];
|
|
9
|
+
if (!operation)
|
|
10
|
+
throw new Error(`native node ${nodeId}: apiResponseContracts is missing ${id}`);
|
|
11
|
+
const nodeName = apiNodeNames[id];
|
|
12
|
+
if (typeof nodeName !== 'string' || !nodeName.trim())
|
|
13
|
+
throw new Error(`native node ${nodeId}: apiNodeNames is missing ${id}`);
|
|
14
|
+
return { kind: 'api-response', nodeId: id, nodeName, operation };
|
|
15
|
+
});
|
|
16
|
+
if (!bindingSources.length)
|
|
17
|
+
return { code: 'const responses=Object.create(null);', bindingSources };
|
|
18
|
+
const runtime = readFileSync(new URL('../../request/runtime/request-runtime.bundle.js', import.meta.url), 'utf8');
|
|
19
|
+
return {
|
|
20
|
+
code: `${runtime}\n${createOutputReaderCode(bindingSources, linked)}`,
|
|
21
|
+
bindingSources,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { node } from '@n8n/workflow-sdk';
|
|
2
|
+
import { stopParametersSchema } from '../../schemas/native-capability-schemas.js';
|
|
3
|
+
import { createNativeFragment } from './common/create-native-fragment.js';
|
|
4
|
+
export function createStopAndErrorCapability() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'stop-and-error',
|
|
7
|
+
description: 'n8n StopAndError: main input, no outputs. Fail the workflow with an explicit message.',
|
|
8
|
+
parametersSchema: stopParametersSchema,
|
|
9
|
+
inputPorts: () => ['main'],
|
|
10
|
+
outputPorts: () => [],
|
|
11
|
+
responseReferences: () => [],
|
|
12
|
+
waitsForAllInputs: false,
|
|
13
|
+
exclusiveOutputPorts: false,
|
|
14
|
+
compile: ({ planned, position }) => createNativeFragment(node({
|
|
15
|
+
type: 'n8n-nodes-base.stopAndError',
|
|
16
|
+
version: 1,
|
|
17
|
+
config: {
|
|
18
|
+
id: planned.id,
|
|
19
|
+
name: planned.id,
|
|
20
|
+
position,
|
|
21
|
+
parameters: {
|
|
22
|
+
errorType: 'errorMessage',
|
|
23
|
+
errorMessage: stopParametersSchema.parse(planned.parameters)
|
|
24
|
+
.message,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}), ['main'], []),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function looksLikeCredential(name: string): boolean;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { HttpRequestAuthenticationMapping, ResolveHttpRequestAuthenticationInput } from '../../../types/authentication.js';
|
|
2
|
+
export declare function resolveHttpRequestAuthentication(input: ResolveHttpRequestAuthenticationInput): HttpRequestAuthenticationMapping | undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { resolveOpenApiSecurity } from '@openapi-flow/core';
|
|
2
|
+
import { UnsupportedOperationError } from '@openapi-flow/core/internal';
|
|
3
|
+
export function resolveHttpRequestAuthentication(input) {
|
|
4
|
+
const contract = input.operation;
|
|
5
|
+
const selected = resolveOpenApiSecurity({
|
|
6
|
+
operationRef: contract.key.operationRef,
|
|
7
|
+
security: contract.effective.security,
|
|
8
|
+
securitySchemes: contract.effective.securitySchemes,
|
|
9
|
+
requirementIndex: input.securityRequirementIndex,
|
|
10
|
+
});
|
|
11
|
+
if (!selected || !selected.schemes.length)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (selected.schemes.length !== 1)
|
|
14
|
+
throw new UnsupportedOperationError(contract.key.operationRef, `${contract.key.operationRef}: combined security schemes require a separate composite credential mapping`);
|
|
15
|
+
const { name, definition, scopes } = selected.schemes[0];
|
|
16
|
+
if (definition.type === 'http') {
|
|
17
|
+
const scheme = String(definition.scheme).toLowerCase();
|
|
18
|
+
if (scheme === 'bearer')
|
|
19
|
+
return { schemeName: name, credentialType: 'httpBearerAuth' };
|
|
20
|
+
if (scheme === 'basic')
|
|
21
|
+
return { schemeName: name, credentialType: 'httpBasicAuth' };
|
|
22
|
+
if (scheme === 'digest')
|
|
23
|
+
return { schemeName: name, credentialType: 'httpDigestAuth' };
|
|
24
|
+
}
|
|
25
|
+
if (definition.type === 'apiKey' &&
|
|
26
|
+
['header', 'query'].includes(String(definition.in))) {
|
|
27
|
+
if (typeof definition.name !== 'string' || !definition.name.trim())
|
|
28
|
+
throw new Error(`${contract.key.operationRef}.securitySchemes.${name}.name is required`);
|
|
29
|
+
return {
|
|
30
|
+
schemeName: name,
|
|
31
|
+
credentialType: definition.in === 'header' ? 'httpHeaderAuth' : 'httpQueryAuth',
|
|
32
|
+
credentialRequirements: `OAS ${name}: configure the credential ${definition.in} Name as ${JSON.stringify(definition.name)}. The workflow contains only a credential reference; the stored secret and Name are owned by n8n.`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (definition.type === 'oauth2')
|
|
36
|
+
return {
|
|
37
|
+
schemeName: name,
|
|
38
|
+
credentialType: 'oAuth2Api',
|
|
39
|
+
credentialRequirements: `OAS ${name}: use an existing OAuth2 credential configured for the declared flows and required scopes ${JSON.stringify(scopes)}. This compiler does not create grants, invent tokens or verify the stored credential configuration.`,
|
|
40
|
+
};
|
|
41
|
+
throw new UnsupportedOperationError(contract.key.operationRef, `${contract.key.operationRef}.securitySchemes.${name}: ${definition.type}/${definition.scheme ?? definition.in ?? ''} has no implemented HTTP Request credential mapping`);
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { compileStandaloneValidator } from '../../common/validation/compile-standalone-validator.js';
|
|
2
|
+
/** Every producer retains its own schema resource root and reference scope. */
|
|
3
|
+
export function compileNativeOutputValidators(sources) {
|
|
4
|
+
return sources
|
|
5
|
+
.map((source, index) => source.kind === 'native-json'
|
|
6
|
+
? compileStandaloneValidator({
|
|
7
|
+
schema: source.schema,
|
|
8
|
+
globalName: `OpenApiFlowNativeOutputValidator${index}`,
|
|
9
|
+
})
|
|
10
|
+
: '')
|
|
11
|
+
.join('\n');
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createApiRequestSchema } from '@openapi-flow/core';
|
|
2
|
+
import { compileStandaloneValidator } from '../../common/validation/compile-standalone-validator.js';
|
|
3
|
+
/** Compile OAS schemas on the host; n8n receives no schema compiler/eval. */
|
|
4
|
+
export function compileRequestValidator(config) {
|
|
5
|
+
const schema = createApiRequestSchema({
|
|
6
|
+
operation: config.contract,
|
|
7
|
+
requestMediaType: config.arguments.requestMediaType,
|
|
8
|
+
});
|
|
9
|
+
return compileStandaloneValidator({
|
|
10
|
+
schema,
|
|
11
|
+
globalName: 'OpenApiFlowRequestValidator',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createApiResponseSchema } from '@openapi-flow/core';
|
|
2
|
+
import { compileStandaloneValidator } from '../../common/validation/compile-standalone-validator.js';
|
|
3
|
+
export function compileSourceResponseValidators(sources) {
|
|
4
|
+
return sources
|
|
5
|
+
.map((source, index) => source.kind === 'api-response' && source.operation
|
|
6
|
+
? compileStandaloneValidator({
|
|
7
|
+
schema: createApiResponseSchema({ operation: source.operation }),
|
|
8
|
+
globalName: `OpenApiFlowResponseValidator${index}`,
|
|
9
|
+
})
|
|
10
|
+
: '')
|
|
11
|
+
.join('\n');
|
|
12
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CreateBoundHttpRequestNodeInput, N8nNodeFragment } from '../../../types/node-fragment.js';
|
|
2
|
+
import type { RequestMaterializationConfig } from '../../../types/request-materialization.js';
|
|
3
|
+
export declare function createBoundRequestFragment(input: CreateBoundHttpRequestNodeInput, config: RequestMaterializationConfig): N8nNodeFragment;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { node } from '@n8n/workflow-sdk';
|
|
3
|
+
import { javascriptJsonLiteral } from '../../../utils/javascript-json-literal.js';
|
|
4
|
+
import { createSerializedHttpRequestNode } from '../serialization/create-serialized-http-request-node.js';
|
|
5
|
+
import { resolveCredentialBinding } from '../../../legacy/workflow/request/credential-binding.js';
|
|
6
|
+
import { compileRequestValidator } from './compile-request-validator.js';
|
|
7
|
+
import { resolveOutputBindingSources } from './resolve-output-binding-sources.js';
|
|
8
|
+
import { createOutputReaderCode } from './create-output-reader-code.js';
|
|
9
|
+
export function createBoundRequestFragment(input, config) {
|
|
10
|
+
const sources = resolveOutputBindingSources(input);
|
|
11
|
+
const runtime = readFileSync(new URL('../runtime/request-runtime.bundle.js', import.meta.url), 'utf8');
|
|
12
|
+
const validator = compileRequestValidator(config);
|
|
13
|
+
const linked = input.itemMode === 'linked';
|
|
14
|
+
const materialize = `OpenApiFlowRequestRuntime.materializeHttpRequest(config,responses,OpenApiFlowRequestValidator)`;
|
|
15
|
+
const code = `${runtime}\n${validator}\nconst config=${javascriptJsonLiteral(config)};\n${linked ? `return $input.all().map((_,inputIndex)=>{${createOutputReaderCode(sources, true)}\nreturn {json:${materialize},pairedItem:{item:inputIndex}};});` : `${createOutputReaderCode(sources)}\nreturn [{json:${materialize}}];`}`;
|
|
16
|
+
const materializer = node({
|
|
17
|
+
type: 'n8n-nodes-base.code',
|
|
18
|
+
version: 2,
|
|
19
|
+
config: {
|
|
20
|
+
id: `${config.arguments.callId}-materialize`,
|
|
21
|
+
name: `Materialize ${config.arguments.callId}`,
|
|
22
|
+
position: input.position,
|
|
23
|
+
parameters: { mode: 'runOnceForAllItems', jsCode: code },
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const bodyPresent = Object.hasOwn(config.arguments.values, 'body') ||
|
|
27
|
+
config.arguments.bindings.some((binding) => binding.targetPointer === '/body' ||
|
|
28
|
+
binding.targetPointer.startsWith('/body/'));
|
|
29
|
+
const mediaTypes = config.operation.body
|
|
30
|
+
? Object.keys(config.operation.body.mediaTypes)
|
|
31
|
+
: [];
|
|
32
|
+
const mediaType = config.arguments.requestMediaType ??
|
|
33
|
+
(mediaTypes.length === 1 ? mediaTypes[0] : undefined);
|
|
34
|
+
if (bodyPresent && mediaType === undefined)
|
|
35
|
+
throw new Error('requestMediaType must select the bound body media type');
|
|
36
|
+
if (bodyPresent &&
|
|
37
|
+
mediaType !== 'application/json' &&
|
|
38
|
+
mediaType !== 'application/x-www-form-urlencoded')
|
|
39
|
+
throw new Error(`${config.operation.operationRef}: requestBody media type ${mediaType} has no n8n mapping`);
|
|
40
|
+
const request = createSerializedHttpRequestNode({
|
|
41
|
+
bodySource: 'materialized',
|
|
42
|
+
operation: config.operation,
|
|
43
|
+
id: `${config.arguments.callId}-transport`,
|
|
44
|
+
name: 'Receive ' + config.arguments.callId,
|
|
45
|
+
position: [input.position[0] + 220, input.position[1]],
|
|
46
|
+
url: '={{ $json.url }}',
|
|
47
|
+
headers: [],
|
|
48
|
+
body: bodyPresent
|
|
49
|
+
? {
|
|
50
|
+
contentType: mediaType === 'application/json' ? 'json' : 'form-urlencoded',
|
|
51
|
+
value: '={{ $json.body.value }}',
|
|
52
|
+
}
|
|
53
|
+
: undefined,
|
|
54
|
+
authentication: resolveCredentialBinding(input, input.credentialBindings),
|
|
55
|
+
shouldAssert: false,
|
|
56
|
+
});
|
|
57
|
+
// Headers may include bound values; HTTP Request supports a JSON expression.
|
|
58
|
+
request.config.parameters = {
|
|
59
|
+
...request.config.parameters,
|
|
60
|
+
sendHeaders: true,
|
|
61
|
+
specifyHeaders: 'json',
|
|
62
|
+
jsonHeaders: '={{ $json.headersJson }}',
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
nodeId: config.arguments.callId,
|
|
66
|
+
preservesInputItems: linked,
|
|
67
|
+
bindingSources: sources,
|
|
68
|
+
nodes: [materializer, request],
|
|
69
|
+
entry: materializer,
|
|
70
|
+
exit: request,
|
|
71
|
+
internalEdges: [
|
|
72
|
+
{ from: materializer.id, output: 0, to: request.id, input: 0 },
|
|
73
|
+
],
|
|
74
|
+
inputPorts: { main: 0 },
|
|
75
|
+
outputPorts: { main: 0 },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { N8nOutputBindingSource } from '../../../types/output-binding-source.js';
|
|
2
|
+
/** Read declared JSON roots without coercion or ambiguous item selection. */
|
|
3
|
+
export declare function createOutputReaderCode(sources: N8nOutputBindingSource[], linked?: boolean): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { javascriptJsonLiteral } from '../../../utils/javascript-json-literal.js';
|
|
2
|
+
import { compileNativeOutputValidators } from './compile-native-output-validators.js';
|
|
3
|
+
import { compileSourceResponseValidators } from './compile-source-response-validators.js';
|
|
4
|
+
/** Read declared JSON roots without coercion or ambiguous item selection. */
|
|
5
|
+
export function createOutputReaderCode(sources, linked = false) {
|
|
6
|
+
const declarations = sources.map((source, index) => ({
|
|
7
|
+
nodeId: source.nodeId,
|
|
8
|
+
nodeName: source.nodeName,
|
|
9
|
+
kind: source.kind,
|
|
10
|
+
validator: source.kind === 'native-json' || source.operation ? index : null,
|
|
11
|
+
}));
|
|
12
|
+
const responseValidators = sources
|
|
13
|
+
.map((source, index) => source.kind === 'api-response' && source.operation
|
|
14
|
+
? `OpenApiFlowResponseValidator${index}`
|
|
15
|
+
: 'null')
|
|
16
|
+
.join(',');
|
|
17
|
+
const nativeValidators = sources
|
|
18
|
+
.map((source, index) => source.kind === 'native-json'
|
|
19
|
+
? `OpenApiFlowNativeOutputValidator${index}`
|
|
20
|
+
: 'null')
|
|
21
|
+
.join(',');
|
|
22
|
+
const reader = linked
|
|
23
|
+
? 'const item=$(source.nodeName).itemMatching(inputIndex);'
|
|
24
|
+
: "const items=$(source.nodeName).all();if(items.length!==1)throw new Error('Response '+source.nodeId+' requires an unambiguous single JSON item; got '+items.length+' items');const item=items[0];";
|
|
25
|
+
return `${compileNativeOutputValidators(sources)}\n${compileSourceResponseValidators(sources)}\nconst sources=${javascriptJsonLiteral(declarations)};\nconst validators=[${responseValidators}];\nconst nativeValidators=[${nativeValidators}];\nconst responses=Object.create(null);\nfor(const source of sources){${reader}if(!item||!item.json||typeof item.json!=='object')throw new Error('Missing JSON output '+source.nodeId);if(source.kind==='api-response'){if(source.validator!==null){const validate=validators[source.validator];if(!validate(OpenApiFlowRequestRuntime.apiResponseValidationValue(item.json)))throw new Error('Response '+source.nodeId+' does not match its OAS contract: '+JSON.stringify(validate.errors));}if(!Object.hasOwn(item.json,'body'))throw new Error('Missing response body '+source.nodeId);responses[source.nodeId]=item.json.body;}else{const validate=nativeValidators[source.validator];if(!validate(item.json))throw new Error('Native output does not match declared schema: '+JSON.stringify(validate.errors)+' (source '+source.nodeId+')');responses[source.nodeId]=item.json;}}`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CreateHttpRequestNodeInput } from '../../../types/node-fragment.js';
|
|
2
|
+
import type { N8nOutputBindingSource } from '../../../types/output-binding-source.js';
|
|
3
|
+
export declare function resolveOutputBindingSources(input: CreateHttpRequestNodeInput): N8nOutputBindingSource[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { validateNativeOutputContracts } from '@openapi-flow/core';
|
|
2
|
+
export function resolveOutputBindingSources(input) {
|
|
3
|
+
validateNativeOutputContracts(input.nativeOutputSources ?? []);
|
|
4
|
+
const native = new Map((input.nativeOutputSources ?? []).map((item) => [item.nodeId, item]));
|
|
5
|
+
for (const item of native.values()) {
|
|
6
|
+
if (typeof item.nodeName !== 'string' || !item.nodeName.trim())
|
|
7
|
+
throw new Error(`nativeOutputSources[${item.nodeId}].nodeName must be a non-empty string`);
|
|
8
|
+
if (input.apiNodeNames && Object.hasOwn(input.apiNodeNames, item.nodeId))
|
|
9
|
+
throw new Error(`binding source ${item.nodeId} is declared as both API and native`);
|
|
10
|
+
}
|
|
11
|
+
return [
|
|
12
|
+
...new Set(input.arguments.bindings.map((binding) => binding.sourceNodeId)),
|
|
13
|
+
].map((nodeId) => {
|
|
14
|
+
const source = native.get(nodeId);
|
|
15
|
+
if (source)
|
|
16
|
+
return { ...source, kind: 'native-json' };
|
|
17
|
+
if (!input.apiNodeNames)
|
|
18
|
+
throw new Error(`arguments[${input.arguments.callId}] requires apiNodeNames for API response bindings`);
|
|
19
|
+
if (!Object.hasOwn(input.apiNodeNames, nodeId) ||
|
|
20
|
+
!input.apiNodeNames[nodeId].trim())
|
|
21
|
+
throw new Error(`apiNodeNames is missing ${nodeId}`);
|
|
22
|
+
if ((input.apiResponseContracts || input.itemMode === 'linked') &&
|
|
23
|
+
(!input.apiResponseContracts ||
|
|
24
|
+
!Object.hasOwn(input.apiResponseContracts, nodeId) ||
|
|
25
|
+
!input.apiResponseContracts[nodeId]))
|
|
26
|
+
throw new Error(`apiResponseContracts is missing ${nodeId}`);
|
|
27
|
+
return {
|
|
28
|
+
kind: 'api-response',
|
|
29
|
+
nodeId,
|
|
30
|
+
nodeName: input.apiNodeNames[nodeId],
|
|
31
|
+
...(input.apiResponseContracts
|
|
32
|
+
? { operation: input.apiResponseContracts[nodeId] }
|
|
33
|
+
: {}),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { validateApiArguments } from '@openapi-flow/core';
|
|
2
|
+
import { mapHttpOperationContract } from '@openapi-flow/core/internal';
|
|
3
|
+
import { originFrom } from '../../legacy/workflow/request/base-url.js';
|
|
4
|
+
import { resolveCredentialBinding } from '../../legacy/workflow/request/credential-binding.js';
|
|
5
|
+
import { absoluteOperationPath } from '../../legacy/workflow/request/request-url.js';
|
|
6
|
+
import { createBoundRequestFragment } from './bindings/create-bound-request-fragment.js';
|
|
7
|
+
import { serializeHttpRequest } from './serialization/serialize-http-request.js';
|
|
8
|
+
import { createSerializedHttpRequestNode } from './serialization/create-serialized-http-request-node.js';
|
|
9
|
+
import { resolveHttpRequestDeployment } from './deployment/resolve-http-request-deployment.js';
|
|
10
|
+
import { createRequestDeploymentNotes } from './deployment/create-request-deployment-notes.js';
|
|
11
|
+
import { resolveHttpRequestAuthentication } from './authentication/resolve-http-request-authentication.js';
|
|
12
|
+
import { createHttpResponseDecoder } from './response/create-http-response-decoder.js';
|
|
13
|
+
export function createHttpRequestNode(input) {
|
|
14
|
+
const { operation: contract, arguments: args } = input;
|
|
15
|
+
if (input.itemMode !== undefined && input.itemMode !== 'linked')
|
|
16
|
+
throw new Error('itemMode must be linked or omitted');
|
|
17
|
+
if (!args || typeof args.callId !== 'string' || !args.callId.trim())
|
|
18
|
+
throw new Error('arguments.callId must be non-empty');
|
|
19
|
+
if (!Array.isArray(args.unresolvedInputs))
|
|
20
|
+
throw new Error(`arguments[${args.callId}].unresolvedInputs must be an array`);
|
|
21
|
+
if (args.unresolvedInputs.length)
|
|
22
|
+
throw new Error(`arguments[${args.callId}] has unresolved inputs: ${args.unresolvedInputs.join(', ')}`);
|
|
23
|
+
const validation = validateApiArguments({
|
|
24
|
+
operation: contract,
|
|
25
|
+
values: args.values,
|
|
26
|
+
bindings: args.bindings,
|
|
27
|
+
requestMediaType: args.requestMediaType,
|
|
28
|
+
});
|
|
29
|
+
if (!validation.valid)
|
|
30
|
+
throw new Error(`arguments[${args.callId}] is missing ${validation.missingInputs.join(', ')}`);
|
|
31
|
+
const operation = mapHttpOperationContract(contract.path, contract.method, contract.key.operationRef, contract.pathItem, contract.operation);
|
|
32
|
+
const authentication = resolveHttpRequestAuthentication({
|
|
33
|
+
operation: contract,
|
|
34
|
+
securityRequirementIndex: input.securityRequirementIndex,
|
|
35
|
+
});
|
|
36
|
+
const deployment = resolveHttpRequestDeployment({
|
|
37
|
+
documentId: contract.key.documentId,
|
|
38
|
+
authentication,
|
|
39
|
+
baseUrl: input.baseUrl,
|
|
40
|
+
credentialBindings: input.credentialBindings,
|
|
41
|
+
securityRequirementIndex: input.securityRequirementIndex,
|
|
42
|
+
});
|
|
43
|
+
const resolvedInput = { ...input, ...deployment };
|
|
44
|
+
let fragment;
|
|
45
|
+
if (args.bindings.length) {
|
|
46
|
+
fragment = createBoundRequestFragment(resolvedInput, {
|
|
47
|
+
contract,
|
|
48
|
+
operation,
|
|
49
|
+
arguments: args,
|
|
50
|
+
templateUrl: new URL(absoluteOperationPath(originFrom(deployment.baseUrl), operation.path)).href,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const { url, body, headers } = serializeHttpRequest(operation, new URL(absoluteOperationPath(originFrom(deployment.baseUrl), operation.path)).href, args.values, args.requestMediaType);
|
|
55
|
+
const request = createSerializedHttpRequestNode({
|
|
56
|
+
bodySource: 'literal',
|
|
57
|
+
operation,
|
|
58
|
+
id: `${args.callId}-transport`,
|
|
59
|
+
name: 'Receive ' + args.callId,
|
|
60
|
+
position: input.position,
|
|
61
|
+
url,
|
|
62
|
+
body,
|
|
63
|
+
headers,
|
|
64
|
+
authentication: resolveCredentialBinding(deployment, deployment.credentialBindings),
|
|
65
|
+
shouldAssert: false,
|
|
66
|
+
});
|
|
67
|
+
fragment = {
|
|
68
|
+
nodeId: args.callId,
|
|
69
|
+
preservesInputItems: true,
|
|
70
|
+
nodes: [request],
|
|
71
|
+
entry: request,
|
|
72
|
+
exit: request,
|
|
73
|
+
inputPorts: { main: 0 },
|
|
74
|
+
outputPorts: { main: 0 },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
fragment = createHttpResponseDecoder({ fragment, method: contract.method });
|
|
78
|
+
if (deployment.pendingFields.length ||
|
|
79
|
+
authentication?.credentialRequirements) {
|
|
80
|
+
const notes = [
|
|
81
|
+
...(deployment.pendingFields.length
|
|
82
|
+
? [createRequestDeploymentNotes(contract.key.documentId, deployment)]
|
|
83
|
+
: []),
|
|
84
|
+
...(authentication?.credentialRequirements
|
|
85
|
+
? [authentication.credentialRequirements]
|
|
86
|
+
: []),
|
|
87
|
+
].join('\n');
|
|
88
|
+
for (const requestNode of fragment.nodes) {
|
|
89
|
+
requestNode.config.notes = notes;
|
|
90
|
+
requestNode.config.notesInFlow = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return fragment;
|
|
94
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export function createRequestDeploymentNotes(documentId, deployment) {
|
|
2
|
+
return `REPLACE_ME: OAS ${documentId} requires ${deployment.pendingFields.join(', ')} before execution. Supply deployment options and regenerate, or replace the placeholders in the exported JSON and select the real n8n credential. Bound request URLs are configured in their Materialize node. No credential secret is stored in this workflow.`;
|
|
3
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { httpRequestDeploymentOptionsSchema } from '../../../schemas/request-deployment-schema.js';
|
|
2
|
+
export function resolveHttpRequestDeployment(input) {
|
|
3
|
+
const options = httpRequestDeploymentOptionsSchema.parse({
|
|
4
|
+
baseUrl: input.baseUrl,
|
|
5
|
+
credentialBindings: input.credentialBindings,
|
|
6
|
+
securityRequirementIndex: input.securityRequirementIndex,
|
|
7
|
+
});
|
|
8
|
+
const pendingFields = [];
|
|
9
|
+
let baseUrl;
|
|
10
|
+
if (options.baseUrl === undefined) {
|
|
11
|
+
baseUrl = 'https://REPLACE_ME.invalid';
|
|
12
|
+
pendingFields.push('baseUrl');
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
baseUrl = options.baseUrl;
|
|
16
|
+
}
|
|
17
|
+
const credentialBindings = options.credentialBindings === undefined ? {} : options.credentialBindings;
|
|
18
|
+
const schemeName = input.authentication?.schemeName;
|
|
19
|
+
if (schemeName !== undefined &&
|
|
20
|
+
!Object.hasOwn(credentialBindings, schemeName)) {
|
|
21
|
+
credentialBindings[schemeName] = {
|
|
22
|
+
id: `REPLACE_ME:${encodeURIComponent(input.documentId)}:${encodeURIComponent(schemeName)}`,
|
|
23
|
+
name: `REPLACE_ME (${input.documentId}: ${schemeName})`,
|
|
24
|
+
};
|
|
25
|
+
pendingFields.push(`credentialBindings.${schemeName}`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
baseUrl,
|
|
29
|
+
credentialBindings,
|
|
30
|
+
pendingFields,
|
|
31
|
+
authentication: input.authentication,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { N8nNodeFragment } from '../../../types/node-fragment.js';
|
|
2
|
+
import type { CreateHttpResponseDecoderInput } from '../../../types/http-response-decoding.js';
|
|
3
|
+
/** Keep transport metadata and item ancestry while exposing the decoded API exit. */
|
|
4
|
+
export declare function createHttpResponseDecoder({ fragment, method, }: CreateHttpResponseDecoderInput): N8nNodeFragment;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { node } from '@n8n/workflow-sdk';
|
|
3
|
+
import { isObject } from '@openapi-flow/core/internal';
|
|
4
|
+
import { javascriptJsonLiteral } from '../../../utils/javascript-json-literal.js';
|
|
5
|
+
/** Keep transport metadata and item ancestry while exposing the decoded API exit. */
|
|
6
|
+
export function createHttpResponseDecoder({ fragment, method, }) {
|
|
7
|
+
const transport = fragment.exit;
|
|
8
|
+
const position = transport.config.position;
|
|
9
|
+
if (!position || position.length !== 2)
|
|
10
|
+
throw new Error(`API ${fragment.nodeId} response decoder requires transport position`);
|
|
11
|
+
const parameters = transport.config.parameters;
|
|
12
|
+
if (!parameters ||
|
|
13
|
+
!isObject(parameters.options) ||
|
|
14
|
+
!isObject(parameters.options.response) ||
|
|
15
|
+
!isObject(parameters.options.response.response))
|
|
16
|
+
throw new Error(`API ${fragment.nodeId} transport requires options.response.response`);
|
|
17
|
+
transport.config.parameters = {
|
|
18
|
+
...parameters,
|
|
19
|
+
options: {
|
|
20
|
+
...parameters.options,
|
|
21
|
+
response: {
|
|
22
|
+
...parameters.options.response,
|
|
23
|
+
response: {
|
|
24
|
+
...parameters.options.response.response,
|
|
25
|
+
fullResponse: true,
|
|
26
|
+
responseFormat: 'file',
|
|
27
|
+
outputPropertyName: 'openApiFlowRawResponse',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const runtime = readFileSync(new URL('./response-runtime.bundle.js', import.meta.url), 'utf8');
|
|
33
|
+
const decoder = node({
|
|
34
|
+
type: 'n8n-nodes-base.code',
|
|
35
|
+
version: 2,
|
|
36
|
+
config: {
|
|
37
|
+
id: fragment.nodeId,
|
|
38
|
+
name: `Request ${fragment.nodeId}`,
|
|
39
|
+
position: [position[0] + 220, position[1]],
|
|
40
|
+
notes: 'Decode the raw HTTP response once. Preserve status, headers, body types and item ancestry. Invalid JSON fails without repair.',
|
|
41
|
+
parameters: {
|
|
42
|
+
mode: 'runOnceForAllItems',
|
|
43
|
+
jsCode: `${runtime}\nconst sourceNodeId=${javascriptJsonLiteral(fragment.nodeId)};\nconst method=${javascriptJsonLiteral(method)};\nconst result=[];\nfor(const [inputIndex,item] of $input.all().entries()){\nconst bytes=await this.helpers.getBinaryDataBuffer(inputIndex,'openApiFlowRawResponse');\nconst decoded=OpenApiFlowResponseRuntime.decodeHttpResponse({response:item.json,bytes,method,sourceNodeId});\nconst binary={...item.binary};\nconst raw=binary.openApiFlowRawResponse;\ndelete binary.openApiFlowRawResponse;\nif(decoded.kind==='binary')binary.data=raw;\nresult.push({json:decoded.response,...(Object.keys(binary).length?{binary}:{}),pairedItem:{item:inputIndex}});\n}\nreturn result;`,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
...fragment,
|
|
49
|
+
nodes: [...fragment.nodes, decoder],
|
|
50
|
+
exit: decoder,
|
|
51
|
+
internalEdges: [
|
|
52
|
+
...(fragment.internalEdges ?? []),
|
|
53
|
+
{ from: transport.id, output: 0, to: decoder.id, input: 0 },
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DecodedHttpResponse, HttpResponseDecodeInput } from '../../../types/http-response-decoding.js';
|
|
2
|
+
/** Decode raw transport bytes once; never guess or repair an already parsed value. */
|
|
3
|
+
export declare function decodeHttpResponse({ response, bytes, method, sourceNodeId, }: HttpResponseDecodeInput): DecodedHttpResponse;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { apiResponseValidationValue } from '@openapi-flow/core/runtime';
|
|
2
|
+
import { isBinaryResponseMedia, isJsonResponseMedia, } from './http-response-media.js';
|
|
3
|
+
/** Decode raw transport bytes once; never guess or repair an already parsed value. */
|
|
4
|
+
export function decodeHttpResponse({ response, bytes, method, sourceNodeId, }) {
|
|
5
|
+
const observed = apiResponseValidationValue(response);
|
|
6
|
+
if (Object.hasOwn(response, 'body'))
|
|
7
|
+
throw new Error(`API ${sourceNodeId} raw File transport must not contain body`);
|
|
8
|
+
if (bytes.byteLength === 0 &&
|
|
9
|
+
(method === 'HEAD' ||
|
|
10
|
+
observed.statusCode === 204 ||
|
|
11
|
+
observed.statusCode === 304))
|
|
12
|
+
return { response: { ...response }, kind: 'empty' };
|
|
13
|
+
if (isBinaryResponseMedia(observed.mediaType))
|
|
14
|
+
return { response: { ...response }, kind: 'binary' };
|
|
15
|
+
const json = isJsonResponseMedia(observed.mediaType);
|
|
16
|
+
const contentType = Object.entries(response.headers).find(([name]) => name.toLowerCase() === 'content-type')?.[1];
|
|
17
|
+
const charset = typeof contentType === 'string'
|
|
18
|
+
? /;\s*charset\s*=\s*(?:"([^"]*)"|([^;\s]*))/i.exec(contentType)
|
|
19
|
+
: null;
|
|
20
|
+
// JSON uses UTF-8 (RFC 8259 §8.1). Text retains the native UTF-8 codec default
|
|
21
|
+
// unless the actual HTTP header explicitly declares another charset.
|
|
22
|
+
const encoding = json || charset === null ? 'utf-8' : (charset[1] ?? charset[2]);
|
|
23
|
+
let text;
|
|
24
|
+
try {
|
|
25
|
+
text = new TextDecoder(encoding, { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
26
|
+
}
|
|
27
|
+
catch (cause) {
|
|
28
|
+
throw new Error(`API ${sourceNodeId} has invalid HTTP response encoding`, {
|
|
29
|
+
cause,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (!json)
|
|
33
|
+
return { response: { ...response, body: text }, kind: 'text' };
|
|
34
|
+
try {
|
|
35
|
+
return { response: { ...response, body: JSON.parse(text) }, kind: 'json' };
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
throw new Error(`API ${sourceNodeId} has an invalid JSON HTTP response`, {
|
|
39
|
+
cause,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Preserve the HTTP Request v4.3 binary routing policy (n8n 2.37.10).
|
|
2
|
+
// https://github.com/n8n-io/n8n/blob/n8n%402.37.10/packages/nodes-base/nodes/HttpRequest/GenericFunctions.ts
|
|
3
|
+
const binaryMediaTypes = new Set([
|
|
4
|
+
'application/octet-stream',
|
|
5
|
+
'application/gzip',
|
|
6
|
+
'application/zip',
|
|
7
|
+
'application/vnd.rar',
|
|
8
|
+
'application/epub+zip',
|
|
9
|
+
'application/x-bzip',
|
|
10
|
+
'application/x-bzip2',
|
|
11
|
+
'application/x-cdf',
|
|
12
|
+
'application/vnd.amazon.ebook',
|
|
13
|
+
'application/msword',
|
|
14
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
15
|
+
'application/vnd.ms-fontobject',
|
|
16
|
+
'application/vnd.oasis.opendocument.presentation',
|
|
17
|
+
'application/pdf',
|
|
18
|
+
'application/x-tar',
|
|
19
|
+
'application/vnd.visio',
|
|
20
|
+
'application/vnd.ms-excel',
|
|
21
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
22
|
+
'application/x-7z-compressed',
|
|
23
|
+
]);
|
|
24
|
+
export function isJsonResponseMedia(mediaType) {
|
|
25
|
+
return (mediaType === 'application/json' ||
|
|
26
|
+
(mediaType !== undefined && /^[^/]+\/[^/]+\+json$/.test(mediaType)));
|
|
27
|
+
}
|
|
28
|
+
export function isBinaryResponseMedia(mediaType) {
|
|
29
|
+
return (mediaType !== undefined &&
|
|
30
|
+
(/^(image|audio|video)\//.test(mediaType) ||
|
|
31
|
+
binaryMediaTypes.has(mediaType)));
|
|
32
|
+
}
|