@openapi-flow/n8n 0.2.0 → 0.3.1

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.
Files changed (141) hide show
  1. package/README.md +29 -1
  2. package/dist/nodes/inputs/compile-template-input-validators.d.ts +3 -0
  3. package/dist/nodes/inputs/compile-template-input-validators.js +26 -0
  4. package/dist/nodes/inputs/create-template-input-nodes.d.ts +4 -0
  5. package/dist/nodes/inputs/create-template-input-nodes.js +61 -0
  6. package/dist/nodes/native/arrays/create-bound-native-array-capability.d.ts +4 -0
  7. package/dist/nodes/native/arrays/create-bound-native-array-capability.js +81 -0
  8. package/dist/nodes/native/arrays/create-native-array-capability.d.ts +1 -0
  9. package/dist/nodes/native/arrays/create-native-array-capability.js +7 -59
  10. package/dist/nodes/native/arrays/native-array-definition.d.ts +2 -0
  11. package/dist/nodes/native/arrays/native-array-definition.js +27 -0
  12. package/dist/nodes/native/common/create-material-planning-capability.d.ts +4 -0
  13. package/dist/nodes/native/common/create-material-planning-capability.js +14 -0
  14. package/dist/nodes/native/common/define-material-bound-capability.d.ts +4 -0
  15. package/dist/nodes/native/common/define-material-bound-capability.js +33 -0
  16. package/dist/nodes/native/common/resolve-native-runtime-source.d.ts +3 -0
  17. package/dist/nodes/native/common/resolve-native-runtime-source.js +15 -0
  18. package/dist/nodes/native/common/response-reference-bindings.d.ts +4 -0
  19. package/dist/nodes/native/common/response-reference-bindings.js +15 -0
  20. package/dist/nodes/native/create-exclusive-branch-merge-capability.js +10 -0
  21. package/dist/nodes/native/create-json-output-capability.js +14 -4
  22. package/dist/nodes/native/create-native-capabilities.js +13 -12
  23. package/dist/nodes/native/create-pass-through-capability.js +2 -0
  24. package/dist/nodes/native/create-response-array-capability.js +13 -8
  25. package/dist/nodes/native/create-response-collection-capability.js +10 -8
  26. package/dist/nodes/native/if-capability.js +6 -2
  27. package/dist/nodes/native/inputs/native-input-runtime.bundle.js +24 -0
  28. package/dist/nodes/native/inputs/prepend-native-parameter-guard.d.ts +4 -0
  29. package/dist/nodes/native/inputs/prepend-native-parameter-guard.js +52 -0
  30. package/dist/nodes/native/item-join/create-bound-item-join-capability.d.ts +3 -0
  31. package/dist/nodes/native/item-join/create-bound-item-join-capability.js +169 -0
  32. package/dist/nodes/native/item-join/create-item-join-capability.d.ts +2 -1
  33. package/dist/nodes/native/item-join/create-item-join-capability.js +8 -151
  34. package/dist/nodes/native/item-join/item-join-definition.d.ts +2 -0
  35. package/dist/nodes/native/item-join/item-join-definition.js +33 -0
  36. package/dist/nodes/native/merge-append-capability.js +2 -0
  37. package/dist/nodes/native/registry/native-feature-definitions.d.ts +4 -0
  38. package/dist/nodes/native/registry/native-feature-definitions.js +93 -0
  39. package/dist/nodes/native/registry/native-feature-material-requirements.d.ts +3 -0
  40. package/dist/nodes/native/registry/native-feature-material-requirements.js +10 -0
  41. package/dist/nodes/native/registry/resolve-n8n-native-features.d.ts +3 -0
  42. package/dist/nodes/native/registry/resolve-n8n-native-features.js +6 -0
  43. package/dist/nodes/native/registry/resolve-n8n-native-planning-features.d.ts +3 -0
  44. package/dist/nodes/native/registry/resolve-n8n-native-planning-features.js +5 -0
  45. package/dist/nodes/native/registry/resolve-native-feature-registry.d.ts +3 -0
  46. package/dist/nodes/native/registry/resolve-native-feature-registry.js +99 -0
  47. package/dist/nodes/native/registry/validate-native-feature-context.d.ts +2 -0
  48. package/dist/nodes/native/registry/validate-native-feature-context.js +46 -0
  49. package/dist/nodes/native/response-array-definition.d.ts +2 -0
  50. package/dist/nodes/native/response-array-definition.js +27 -0
  51. package/dist/nodes/native/response-assertion-capability.js +5 -1
  52. package/dist/nodes/native/response-check-code.d.ts +1 -1
  53. package/dist/nodes/native/response-check-code.js +13 -7
  54. package/dist/nodes/native/response-checks/create-response-check-reader.d.ts +1 -1
  55. package/dist/nodes/native/response-checks/create-response-check-reader.js +2 -2
  56. package/dist/nodes/native/response-collection-definition.d.ts +2 -0
  57. package/dist/nodes/native/response-collection-definition.js +27 -0
  58. package/dist/nodes/native/stop-and-error-capability.js +28 -14
  59. package/dist/nodes/request/bindings/compile-request-validator.d.ts +1 -1
  60. package/dist/nodes/request/bindings/compile-request-validator.js +2 -2
  61. package/dist/nodes/request/bindings/create-bound-request-fragment.js +8 -3
  62. package/dist/nodes/request/bindings/create-output-item-reader-code.d.ts +2 -0
  63. package/dist/nodes/request/bindings/create-output-item-reader-code.js +7 -0
  64. package/dist/nodes/request/bindings/create-output-reader-code.d.ts +1 -1
  65. package/dist/nodes/request/bindings/create-output-reader-code.js +4 -4
  66. package/dist/nodes/request/create-http-request-node.js +23 -44
  67. package/dist/nodes/request/finalize-http-request-fragment.d.ts +3 -0
  68. package/dist/nodes/request/finalize-http-request-fragment.js +22 -0
  69. package/dist/nodes/request/resolve-http-request-node-configuration.d.ts +4 -0
  70. package/dist/nodes/request/resolve-http-request-node-configuration.js +41 -0
  71. package/dist/nodes/request/response/create-http-response-decoder.js +2 -1
  72. package/dist/nodes/request/response/http-response-node-name.d.ts +2 -0
  73. package/dist/nodes/request/response/http-response-node-name.js +4 -0
  74. package/dist/nodes/request/response/response-runtime.bundle.js +1 -1
  75. package/dist/nodes/request/runtime/materialize-http-request.d.ts +2 -1
  76. package/dist/nodes/request/runtime/materialize-http-request.js +21 -3
  77. package/dist/nodes/request/runtime/request-runtime.bundle.js +1 -1
  78. package/dist/public-api.d.ts +13 -0
  79. package/dist/public-api.js +9 -0
  80. package/dist/schemas/native-feature-registry-schema.d.ts +17 -0
  81. package/dist/schemas/native-feature-registry-schema.js +8 -0
  82. package/dist/schemas/native-runtime-input-schema.d.ts +5 -0
  83. package/dist/schemas/native-runtime-input-schema.js +5 -0
  84. package/dist/schemas/template-deployment-selection-schema.d.ts +3 -0
  85. package/dist/schemas/template-deployment-selection-schema.js +10 -0
  86. package/dist/schemas/workflow-input-value-map-schema.d.ts +5 -0
  87. package/dist/schemas/workflow-input-value-map-schema.js +5 -0
  88. package/dist/schemas/workflow-template-schema.d.ts +11 -0
  89. package/dist/schemas/workflow-template-schema.js +10 -0
  90. package/dist/types/native-capability.d.ts +10 -1
  91. package/dist/types/native-feature-registry.d.ts +70 -0
  92. package/dist/types/native-feature-registry.js +14 -0
  93. package/dist/types/native-material-binding.d.ts +56 -0
  94. package/dist/types/native-material-binding.js +1 -0
  95. package/dist/types/native-runtime-parameters.d.ts +55 -0
  96. package/dist/types/native-runtime-parameters.js +1 -0
  97. package/dist/types/output-binding-source.d.ts +5 -0
  98. package/dist/types/request-materialization.d.ts +9 -1
  99. package/dist/types/request-node-configuration.d.ts +13 -0
  100. package/dist/types/request-node-configuration.js +1 -0
  101. package/dist/types/response-check-compilation.d.ts +1 -1
  102. package/dist/types/reviewable-workflow.d.ts +2 -0
  103. package/dist/types/workflow-compilation.d.ts +3 -0
  104. package/dist/types/workflow-template.d.ts +121 -0
  105. package/dist/types/workflow-template.js +1 -0
  106. package/dist/workflow/batch/compile-batched-n8n-workflow.js +22 -4
  107. package/dist/workflow/build-n8n-workflow.js +3 -6
  108. package/dist/workflow/compile-native-material-fragments.d.ts +3 -0
  109. package/dist/workflow/compile-native-material-fragments.js +76 -0
  110. package/dist/workflow/compile-native-workflow-nodes.js +22 -23
  111. package/dist/workflow/compile-planned-n8n-workflow.js +28 -3
  112. package/dist/workflow/compile-reviewable-n8n-workflow.js +31 -3
  113. package/dist/workflow/compile-validated-workflow-fragments.js +14 -14
  114. package/dist/workflow/compile-workflow-material-fragments.d.ts +4 -0
  115. package/dist/workflow/compile-workflow-material-fragments.js +18 -0
  116. package/dist/workflow/create-n8n-native-output-sources.js +53 -12
  117. package/dist/workflow/entrypoint/manual-trigger.d.ts +4 -0
  118. package/dist/workflow/entrypoint/manual-trigger.js +16 -0
  119. package/dist/workflow/find-singleton-api-responses.d.ts +3 -0
  120. package/dist/workflow/find-singleton-api-responses.js +22 -0
  121. package/dist/workflow/inputs/compile-native-runtime-inputs.d.ts +5 -0
  122. package/dist/workflow/inputs/compile-native-runtime-inputs.js +129 -0
  123. package/dist/workflow/inputs/create-runtime-input-native-capabilities.d.ts +4 -0
  124. package/dist/workflow/inputs/create-runtime-input-native-capabilities.js +36 -0
  125. package/dist/workflow/resolve-n8n-native-materials.d.ts +2 -0
  126. package/dist/workflow/resolve-n8n-native-materials.js +16 -0
  127. package/dist/workflow/templates/compile-n8n-workflow-template.d.ts +3 -0
  128. package/dist/workflow/templates/compile-n8n-workflow-template.js +211 -0
  129. package/dist/workflow/templates/create-template-artifact-mapping.d.ts +2 -0
  130. package/dist/workflow/templates/create-template-artifact-mapping.js +58 -0
  131. package/dist/workflow/templates/create-template-gap-regions.d.ts +5 -0
  132. package/dist/workflow/templates/create-template-gap-regions.js +22 -0
  133. package/dist/workflow/templates/create-workflow-template-guide.d.ts +3 -0
  134. package/dist/workflow/templates/create-workflow-template-guide.js +44 -0
  135. package/dist/workflow/templates/prepare-template-api-fragments.d.ts +3 -0
  136. package/dist/workflow/templates/prepare-template-api-fragments.js +74 -0
  137. package/dist/workflow/templates/select-n8n-template-deployments.d.ts +3 -0
  138. package/dist/workflow/templates/select-n8n-template-deployments.js +12 -0
  139. package/dist/workflow/templates/workflow-template-identities.d.ts +5 -0
  140. package/dist/workflow/templates/workflow-template-identities.js +5 -0
  141. package/package.json +9 -4
@@ -0,0 +1,93 @@
1
+ import { createIfCapability } from '../if-capability.js';
2
+ import { createMergeAppendCapability } from '../merge-append-capability.js';
3
+ import { createResponseAssertionCapability } from '../response-assertion-capability.js';
4
+ import { createStopAndErrorCapability } from '../stop-and-error-capability.js';
5
+ import { createPassThroughCapability } from '../create-pass-through-capability.js';
6
+ import { createExclusiveBranchMergeCapability } from '../create-exclusive-branch-merge-capability.js';
7
+ import { createJsonOutputCapability } from '../create-json-output-capability.js';
8
+ import { createResponseArrayCapability } from '../create-response-array-capability.js';
9
+ import { createResponseCollectionCapability } from '../create-response-collection-capability.js';
10
+ import { createNativeArrayCapability } from '../arrays/create-native-array-capability.js';
11
+ import { createItemJoinCapability } from '../item-join/create-item-join-capability.js';
12
+ import { createBoundNativeArrayCapability } from '../arrays/create-bound-native-array-capability.js';
13
+ import { createBoundItemJoinCapability } from '../item-join/create-bound-item-join-capability.js';
14
+ import { createMaterialPlanningCapability } from '../common/create-material-planning-capability.js';
15
+ import { responseArrayDefinition } from '../response-array-definition.js';
16
+ import { responseCollectionDefinition } from '../response-collection-definition.js';
17
+ import { nativeArrayDefinition } from '../arrays/native-array-definition.js';
18
+ import { itemJoinDefinition } from '../item-join/item-join-definition.js';
19
+ import { nativeFeatureMaterialRequirements } from './native-feature-material-requirements.js';
20
+ import { N8nNativeFeature } from '../../../types/native-feature-registry.js';
21
+ /** Material prerequisites belong to their actual factories, not model prompts. */
22
+ export const nativeFeatureDefinitions = {
23
+ [N8nNativeFeature.If]: {
24
+ requirements: [],
25
+ create: createIfCapability,
26
+ },
27
+ [N8nNativeFeature.MergeAppend]: {
28
+ requirements: [],
29
+ create: createMergeAppendCapability,
30
+ },
31
+ [N8nNativeFeature.AssertResponses]: {
32
+ requirements: [],
33
+ create: createResponseAssertionCapability,
34
+ },
35
+ [N8nNativeFeature.StopAndError]: {
36
+ requirements: [],
37
+ create: createStopAndErrorCapability,
38
+ },
39
+ [N8nNativeFeature.PassThrough]: {
40
+ requirements: [],
41
+ create: createPassThroughCapability,
42
+ },
43
+ [N8nNativeFeature.RejoinExclusiveBranches]: {
44
+ requirements: [],
45
+ create: createExclusiveBranchMergeCapability,
46
+ },
47
+ [N8nNativeFeature.JsonOutput]: {
48
+ requirements: [{ field: 'jsonOutput' }],
49
+ create: (context) => createJsonOutputCapability(context.jsonOutput),
50
+ },
51
+ [N8nNativeFeature.SplitApiResponseArray]: {
52
+ requirements: nativeFeatureMaterialRequirements(responseArrayDefinition),
53
+ createPlanning: (context) => createMaterialPlanningCapability(responseArrayDefinition, (sources) => createResponseArrayCapability({
54
+ materials: sources.apiMaterials,
55
+ itemMode: context.itemMode,
56
+ })),
57
+ create: (context) => createResponseArrayCapability({
58
+ materials: context.apiMaterials,
59
+ itemMode: context.itemMode,
60
+ }),
61
+ },
62
+ [N8nNativeFeature.CollectApiResponses]: {
63
+ requirements: nativeFeatureMaterialRequirements(responseCollectionDefinition),
64
+ createPlanning: () => createMaterialPlanningCapability(responseCollectionDefinition, (sources) => createResponseCollectionCapability({
65
+ materials: sources.apiMaterials,
66
+ })),
67
+ create: (context) => createResponseCollectionCapability({ materials: context.apiMaterials }),
68
+ },
69
+ [N8nNativeFeature.SplitNativeOutputArray]: {
70
+ requirements: nativeFeatureMaterialRequirements(nativeArrayDefinition),
71
+ createPlanning: (context) => createMaterialPlanningCapability(nativeArrayDefinition, (sources) => createBoundNativeArrayCapability({
72
+ sources: sources.nativeOutputs,
73
+ itemMode: context.itemMode,
74
+ runtime: { kind: 'compiled' },
75
+ })),
76
+ create: (context) => createNativeArrayCapability({
77
+ sources: context.nativeOutputSources,
78
+ itemMode: context.itemMode,
79
+ }),
80
+ },
81
+ [N8nNativeFeature.JoinApiItems]: {
82
+ requirements: nativeFeatureMaterialRequirements(itemJoinDefinition),
83
+ createPlanning: () => createMaterialPlanningCapability(itemJoinDefinition, (sources) => createBoundItemJoinCapability({
84
+ materials: sources.apiMaterials,
85
+ scopes: sources.nativeOutputs,
86
+ runtime: { kind: 'compiled' },
87
+ })),
88
+ create: (context) => createItemJoinCapability({
89
+ materials: context.apiMaterials,
90
+ scopes: context.nativeOutputSources,
91
+ }),
92
+ },
93
+ };
@@ -0,0 +1,3 @@
1
+ import type { NativeMaterialFeatureDefinition } from '../../../types/native-material-binding.js';
2
+ import type { N8nNativeFeatureContextRequirement } from '../../../types/native-feature-registry.js';
3
+ export declare function nativeFeatureMaterialRequirements(definition: NativeMaterialFeatureDefinition): N8nNativeFeatureContextRequirement[];
@@ -0,0 +1,10 @@
1
+ export function nativeFeatureMaterialRequirements(definition) {
2
+ if (!definition.materialRequirements)
3
+ throw new Error(`Native feature ${definition.name} has no declared material requirements`);
4
+ return definition.materialRequirements.map((requirement) => ({
5
+ field: requirement.field === 'nativeOutputs'
6
+ ? 'nativeOutputSources'
7
+ : requirement.field,
8
+ minimumItems: requirement.minimumItems,
9
+ }));
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { N8nNativeFeatureRegistry, ResolveN8nNativeFeaturesInput } from '../../../types/native-feature-registry.js';
2
+ /** Construct capabilities only from already supplied source contracts. */
3
+ export declare function resolveN8nNativeFeatures(input?: ResolveN8nNativeFeaturesInput): N8nNativeFeatureRegistry;
@@ -0,0 +1,6 @@
1
+ import { resolveNativeFeatureRegistry } from './resolve-native-feature-registry.js';
2
+ /** Construct capabilities only from already supplied source contracts. */
3
+ export function resolveN8nNativeFeatures(input = {}) {
4
+ const { registrations, capabilities } = resolveNativeFeatureRegistry(input, 'materials');
5
+ return { registrations, capabilities };
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { N8nNativePlanningFeatureRegistry, ResolveN8nNativeFeaturesInput } from '../../../types/native-feature-registry.js';
2
+ /** Expose stable definitions before API/native producer instances have been planned. */
3
+ export declare function resolveN8nNativePlanningFeatures(input?: ResolveN8nNativeFeaturesInput): N8nNativePlanningFeatureRegistry;
@@ -0,0 +1,5 @@
1
+ import { resolveNativeFeatureRegistry } from './resolve-native-feature-registry.js';
2
+ /** Expose stable definitions before API/native producer instances have been planned. */
3
+ export function resolveN8nNativePlanningFeatures(input = {}) {
4
+ return resolveNativeFeatureRegistry(input, 'planning');
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { N8nNativePlanningFeatureRegistry, N8nNativeFeatureResolutionMode, ResolveN8nNativeFeaturesInput } from '../../../types/native-feature-registry.js';
2
+ /** No model calls, fabricated contracts, source discovery or automatic registration retries. */
3
+ export declare function resolveNativeFeatureRegistry(input: ResolveN8nNativeFeaturesInput, mode: N8nNativeFeatureResolutionMode): N8nNativePlanningFeatureRegistry;
@@ -0,0 +1,99 @@
1
+ import { N8nNativeFeature } from '../../../types/native-feature-registry.js';
2
+ import { n8nNativeFeatureSelectionSchema } from '../../../schemas/native-feature-registry-schema.js';
3
+ import { nativeFeatureDefinitions } from './native-feature-definitions.js';
4
+ import { validateNativeFeatureContext } from './validate-native-feature-context.js';
5
+ /** No model calls, fabricated contracts, source discovery or automatic registration retries. */
6
+ export function resolveNativeFeatureRegistry(input, mode) {
7
+ validateNativeFeatureContext(input);
8
+ const selection = n8nNativeFeatureSelectionSchema.safeParse(input.features === undefined
9
+ ? Object.values(N8nNativeFeature)
10
+ : input.features);
11
+ if (!selection.success)
12
+ throw new Error('native features.features is invalid', {
13
+ cause: selection.error,
14
+ });
15
+ const registrations = selection.data.map((feature) => {
16
+ const definition = nativeFeatureDefinitions[feature];
17
+ if (!definition)
18
+ throw new Error(`native feature ${feature} has no registered factory`);
19
+ const missingContext = definition.requirements
20
+ .filter((requirement) => {
21
+ if (requirement.field === 'jsonOutput')
22
+ return input.jsonOutput === undefined;
23
+ const materials = input[requirement.field];
24
+ return (materials === undefined ||
25
+ materials.length < requirement.minimumItems);
26
+ })
27
+ .map((requirement) => ({ ...requirement }));
28
+ if (missingContext.length) {
29
+ if (mode === 'materials' && input.features !== undefined) {
30
+ const requiredFields = missingContext
31
+ .map((requirement) => requirement.field === 'jsonOutput'
32
+ ? requirement.field
33
+ : `${requirement.field} (at least ${requirement.minimumItems})`)
34
+ .join(', ');
35
+ throw new Error(`native feature ${feature} requires ${requiredFields}`);
36
+ }
37
+ return { feature, status: 'requires-context', missingContext };
38
+ }
39
+ const capability = definition.create(input);
40
+ if (feature !== N8nNativeFeature.JsonOutput &&
41
+ capability.name !== feature)
42
+ throw new Error(`native feature ${feature} factory changed its identity`);
43
+ return { feature, status: 'ready', capability };
44
+ });
45
+ const capabilities = [
46
+ ...registrations.flatMap((registration) => registration.status === 'ready' ? [registration.capability] : []),
47
+ ...(input.extensions === undefined ? [] : input.extensions),
48
+ ];
49
+ const planningRegistrations = registrations.flatMap((registration) => {
50
+ if (registration.status === 'ready')
51
+ return [
52
+ {
53
+ feature: registration.feature,
54
+ capability: registration.capability,
55
+ },
56
+ ];
57
+ if (mode === 'materials')
58
+ return [];
59
+ const factory = nativeFeatureDefinitions[registration.feature].createPlanning;
60
+ return factory === undefined
61
+ ? []
62
+ : [
63
+ {
64
+ feature: registration.feature,
65
+ capability: factory({ itemMode: input.itemMode }),
66
+ },
67
+ ];
68
+ });
69
+ const planningCapabilities = [
70
+ ...planningRegistrations.map((entry) => entry.capability),
71
+ ...(input.extensions === undefined ? [] : input.extensions),
72
+ ];
73
+ const names = new Set();
74
+ for (const capability of planningCapabilities) {
75
+ if (!capability ||
76
+ typeof capability.name !== 'string' ||
77
+ !capability.name.trim() ||
78
+ typeof capability.description !== 'string' ||
79
+ !capability.description.trim() ||
80
+ !capability.parametersSchema ||
81
+ typeof capability.parametersSchema.safeParse !== 'function' ||
82
+ typeof capability.compile !== 'function' ||
83
+ typeof capability.inputPorts !== 'function' ||
84
+ typeof capability.outputPorts !== 'function' ||
85
+ typeof capability.responseReferences !== 'function' ||
86
+ typeof capability.waitsForAllInputs !== 'boolean' ||
87
+ typeof capability.exclusiveOutputPorts !== 'boolean')
88
+ throw new Error('native features requires complete named capability implementations');
89
+ if (names.has(capability.name))
90
+ throw new Error(`native features contains duplicate capability ${capability.name}`);
91
+ names.add(capability.name);
92
+ }
93
+ return {
94
+ registrations,
95
+ capabilities,
96
+ planningCapabilities,
97
+ planningRegistrations,
98
+ };
99
+ }
@@ -0,0 +1,2 @@
1
+ import type { ResolveN8nNativeFeaturesInput } from '../../../types/native-feature-registry.js';
2
+ export declare function validateNativeFeatureContext(input: ResolveN8nNativeFeaturesInput): void;
@@ -0,0 +1,46 @@
1
+ import { validateNativeOutputContracts } from '@openapi-flow/core';
2
+ import { nativeItemExecutionOptionsSchema } from '../../../schemas/native-feature-registry-schema.js';
3
+ export function validateNativeFeatureContext(input) {
4
+ if (!input || typeof input !== 'object' || Array.isArray(input))
5
+ throw new Error('native features input must be an object');
6
+ const execution = nativeItemExecutionOptionsSchema.safeParse({
7
+ itemMode: input.itemMode,
8
+ });
9
+ if (!execution.success)
10
+ throw new Error('native features.itemMode must be linked or omitted', {
11
+ cause: execution.error,
12
+ });
13
+ if (input.apiMaterials !== undefined) {
14
+ if (!Array.isArray(input.apiMaterials))
15
+ throw new Error('native features.apiMaterials must be an array');
16
+ const ids = new Set();
17
+ for (const material of input.apiMaterials) {
18
+ if (!material ||
19
+ typeof material.callId !== 'string' ||
20
+ !material.callId.trim() ||
21
+ ids.has(material.callId) ||
22
+ !material.operation ||
23
+ material.operation.source !== 'paths')
24
+ throw new Error('native features.apiMaterials requires unique callIds and resolved OAS operation contracts');
25
+ ids.add(material.callId);
26
+ }
27
+ }
28
+ if (input.nativeOutputSources !== undefined) {
29
+ validateNativeOutputContracts(input.nativeOutputSources);
30
+ if (input.nativeOutputSources.some((source) => typeof source.nodeName !== 'string' || !source.nodeName.trim()))
31
+ throw new Error('native features.nativeOutputSources requires actual compiled nodeName values');
32
+ }
33
+ if (input.jsonOutput !== undefined) {
34
+ const output = input.jsonOutput;
35
+ if (!output ||
36
+ typeof output.name !== 'string' ||
37
+ !output.name.trim() ||
38
+ typeof output.description !== 'string' ||
39
+ !output.description.trim() ||
40
+ !output.parametersSchema ||
41
+ typeof output.parametersSchema.safeParse !== 'function')
42
+ throw new Error('native features.jsonOutput requires name, description and a Zod parametersSchema');
43
+ }
44
+ if (input.extensions !== undefined && !Array.isArray(input.extensions))
45
+ throw new Error('native features.extensions must be an array');
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { NativeMaterialFeatureDefinition } from '../../types/native-material-binding.js';
2
+ export declare const responseArrayDefinition: NativeMaterialFeatureDefinition;
@@ -0,0 +1,27 @@
1
+ import { responseArrayParametersSchema } from '../../schemas/response-array-schema.js';
2
+ export const responseArrayDefinition = {
3
+ name: 'split-api-response-array',
4
+ materialRequirements: [{ field: 'apiMaterials', minimumItems: 1 }],
5
+ description: 'Split a declared OAS response array into one unchanged JSON item per element using n8n Split Out. Output is {item: element}. Downstream bound requests must use linked item mode. An empty array creates no downstream items.',
6
+ parametersSchema: responseArrayParametersSchema,
7
+ parameterReferenceSources: ['response'],
8
+ inputPorts: () => ['main'],
9
+ outputPorts: () => ['main'],
10
+ referenceStructureSchema: responseArrayParametersSchema,
11
+ parameterReferenceBindings: (values) => {
12
+ const parameters = responseArrayParametersSchema.parse(values);
13
+ return [
14
+ {
15
+ nodeIdPointer: '/sourceNodeId',
16
+ valuePointer: '/pointer',
17
+ reference: {
18
+ source: 'response',
19
+ nodeId: parameters.sourceNodeId,
20
+ pointer: parameters.pointer,
21
+ },
22
+ },
23
+ ];
24
+ },
25
+ waitsForAllInputs: false,
26
+ exclusiveOutputPorts: false,
27
+ };
@@ -3,6 +3,7 @@ import { assertionParametersSchema } from '../../schemas/native-capability-schem
3
3
  import { createAssertionPlanningParametersSchema } from '../../schemas/create-native-comparison-planning-schema.js';
4
4
  import { createNativeFragment } from './common/create-native-fragment.js';
5
5
  import { responseReferences } from './common/response-references.js';
6
+ import { responseReferenceBindings } from './common/response-reference-bindings.js';
6
7
  import { responseCheckCode } from './response-check-code.js';
7
8
  import { createResponseCheckReader } from './response-checks/create-response-check-reader.js';
8
9
  export function createResponseAssertionCapability(input = {}) {
@@ -10,19 +11,22 @@ export function createResponseAssertionCapability(input = {}) {
10
11
  name: 'assert-responses',
11
12
  description: 'n8n Code: library-generated JSON comparisons. main input/output. Returns pass/assertionCount on success and can be the final node; no separate success/end node is needed. Failed checks throw their message and fail the workflow, without a failure output port. Join branches before comparing their responses.',
12
13
  parametersSchema: assertionParametersSchema,
14
+ parameterReferenceSources: ['response'],
13
15
  createPlanningParametersSchema: createAssertionPlanningParametersSchema,
14
16
  inputPorts: () => ['main'],
15
17
  outputPorts: () => ['main'],
16
18
  responseReferences: (parameters) => responseReferences(assertionParametersSchema.parse(parameters).checks),
19
+ parameterReferenceBindings: (parameters) => responseReferenceBindings(assertionParametersSchema.parse(parameters).checks, '/checks'),
17
20
  waitsForAllInputs: false,
18
21
  exclusiveOutputPorts: false,
19
- compile: ({ planned, apiNodeNames, apiResponseContracts, position }) => {
22
+ compile: ({ planned, apiNodeNames, apiResponseContracts, singletonApiResponseIds, position, }) => {
20
23
  const parameters = assertionParametersSchema.parse(planned.parameters);
21
24
  const reader = createResponseCheckReader({
22
25
  nodeId: planned.id,
23
26
  checks: parameters.checks,
24
27
  apiNodeNames,
25
28
  apiResponseContracts,
29
+ singletonApiResponseIds,
26
30
  linked: input.itemMode === 'linked',
27
31
  });
28
32
  const checkCode = `${responseCheckCode(reader.code)}\nconst checks = ${JSON.stringify(parameters.checks)};\nfor (const check of checks) if (!compare(check)) throw new Error(check.message);`;
@@ -1,3 +1,3 @@
1
1
  import type { NativeCheck } from '../../types/native-capability.js';
2
2
  export declare function responseCheckCode(responseReaderCode: string): string;
3
- export declare function conditionExpression(check: NativeCheck, names: Record<string, string>, linked?: boolean): string;
3
+ export declare function conditionExpression(check: NativeCheck, names: Record<string, string>, linked?: boolean, singletonApiResponseIds?: string[]): string;
@@ -1,5 +1,6 @@
1
1
  import { javascriptJsonLiteral } from '../../utils/javascript-json-literal.js';
2
2
  import { responseReferences } from './common/response-references.js';
3
+ import { createOutputItemReaderCode } from '../request/bindings/create-output-item-reader-code.js';
3
4
  /** Library-owned runtime, shared by IF expressions and assertion Code nodes. */
4
5
  function responseCheckRuntime() {
5
6
  return `
@@ -43,16 +44,21 @@ function compare(check) {
43
44
  export function responseCheckCode(responseReaderCode) {
44
45
  return `${responseReaderCode}\n${responseCheckRuntime()}`;
45
46
  }
46
- export function conditionExpression(check, names, linked = false) {
47
- const declaredNames = Object.fromEntries(responseReferences([check]).map((ref) => [ref.nodeId, names[ref.nodeId]]));
47
+ export function conditionExpression(check, names, linked = false, singletonApiResponseIds = []) {
48
+ const sources = responseReferences([check]).map((ref) => ({
49
+ nodeId: ref.nodeId,
50
+ nodeName: names[ref.nodeId],
51
+ singleton: singletonApiResponseIds.includes(ref.nodeId),
52
+ }));
48
53
  // The preceding Code guard validates full responses. Keep IF expressions small.
49
- const reader = `const names=${javascriptJsonLiteral(declaredNames)};
54
+ const reader = `const sources=${javascriptJsonLiteral(sources)};
50
55
  const responses=Object.create(null);
51
- for (const id of Object.keys(names)) {
52
- ${linked ? 'const envelope=$(names[id]).itemMatching(inputIndex);' : "const items=$(names[id]).all();if(items.length!==1)throw new Error('Response '+id+' requires an unambiguous single JSON item');const envelope=items[0];"}
56
+ for (const source of sources) {
57
+ ${createOutputItemReaderCode(linked)}
58
+ const envelope=item;
53
59
  if (!envelope || !envelope.json || !Object.hasOwn(envelope.json, 'body'))
54
- throw new Error('Missing response body: '+id);
55
- responses[id]=envelope.json.body;
60
+ throw new Error('Missing response body: '+source.nodeId);
61
+ responses[source.nodeId]=envelope.json.body;
56
62
  }
57
63
  `;
58
64
  return `={{ (() => { ${linked ? 'const inputIndex=$itemIndex;' : ''}${responseCheckCode(reader)}; return compare(${javascriptJsonLiteral(check)}); })() }}`;
@@ -1,3 +1,3 @@
1
1
  import type { CreateResponseCheckReaderInput, CompiledResponseCheckReader } from '../../../types/response-check-compilation.js';
2
2
  /** Reuse full OAS response validation before native comparisons, without changing data. */
3
- export declare function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiResponseContracts, linked, }: CreateResponseCheckReaderInput): CompiledResponseCheckReader;
3
+ export declare function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiResponseContracts, singletonApiResponseIds, linked, }: CreateResponseCheckReaderInput): CompiledResponseCheckReader;
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
2
2
  import { responseReferences } from '../common/response-references.js';
3
3
  import { createOutputReaderCode } from '../../request/bindings/create-output-reader-code.js';
4
4
  /** Reuse full OAS response validation before native comparisons, without changing data. */
5
- export function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiResponseContracts, linked, }) {
5
+ export function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiResponseContracts, singletonApiResponseIds, linked, }) {
6
6
  const ids = [...new Set(responseReferences(checks).map((ref) => ref.nodeId))];
7
7
  const bindingSources = ids.map((id) => {
8
8
  const operation = apiResponseContracts?.[id];
@@ -17,7 +17,7 @@ export function createResponseCheckReader({ nodeId, checks, apiNodeNames, apiRes
17
17
  return { code: 'const responses=Object.create(null);', bindingSources };
18
18
  const runtime = readFileSync(new URL('../../request/runtime/request-runtime.bundle.js', import.meta.url), 'utf8');
19
19
  return {
20
- code: `${runtime}\n${createOutputReaderCode(bindingSources, linked)}`,
20
+ code: `${runtime}\n${createOutputReaderCode(bindingSources, linked, singletonApiResponseIds)}`,
21
21
  bindingSources,
22
22
  };
23
23
  }
@@ -0,0 +1,2 @@
1
+ import type { NativeMaterialFeatureDefinition } from '../../types/native-material-binding.js';
2
+ export declare const responseCollectionDefinition: NativeMaterialFeatureDefinition;
@@ -0,0 +1,27 @@
1
+ import { responseCollectionParametersSchema } from '../../schemas/response-collection-schema.js';
2
+ export const responseCollectionDefinition = {
3
+ name: 'collect-api-responses',
4
+ materialRequirements: [{ field: 'apiMaterials', minimumItems: 1 }],
5
+ description: 'Collect unchanged item-linked API response values from one incoming stream into {items: values} using n8n Aggregate. Validates each full OAS response first. Preserves null, nested arrays, duplicates and received order. Not a branch join. Empty streams do not trigger this node or create an artificial result.',
6
+ parametersSchema: responseCollectionParametersSchema,
7
+ parameterReferenceSources: ['response'],
8
+ inputPorts: () => ['main'],
9
+ outputPorts: () => ['main'],
10
+ referenceStructureSchema: responseCollectionParametersSchema,
11
+ parameterReferenceBindings: (values) => {
12
+ const parameters = responseCollectionParametersSchema.parse(values);
13
+ return [
14
+ {
15
+ nodeIdPointer: '/sourceNodeId',
16
+ valuePointer: '/pointer',
17
+ reference: {
18
+ source: 'response',
19
+ nodeId: parameters.sourceNodeId,
20
+ pointer: parameters.pointer,
21
+ },
22
+ },
23
+ ];
24
+ },
25
+ waitsForAllInputs: false,
26
+ exclusiveOutputPorts: false,
27
+ };
@@ -1,29 +1,43 @@
1
1
  import { node } from '@n8n/workflow-sdk';
2
+ import { resolveNativeStructureParameters } from '@openapi-flow/core';
2
3
  import { stopParametersSchema } from '../../schemas/native-capability-schemas.js';
3
4
  import { createNativeFragment } from './common/create-native-fragment.js';
5
+ import { prependNativeParameterGuard } from './inputs/prepend-native-parameter-guard.js';
4
6
  export function createStopAndErrorCapability() {
5
- return {
7
+ const capability = {
6
8
  name: 'stop-and-error',
7
9
  description: 'n8n StopAndError: main input, no outputs. Fail the workflow with an explicit message.',
8
10
  parametersSchema: stopParametersSchema,
11
+ parameterReferenceSources: [],
12
+ supportsRuntimeParameters: true,
9
13
  inputPorts: () => ['main'],
10
14
  outputPorts: () => [],
11
15
  responseReferences: () => [],
16
+ parameterReferenceBindings: () => [],
12
17
  waitsForAllInputs: false,
13
18
  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,
19
+ compile: (source) => {
20
+ const { planned, position } = source;
21
+ resolveNativeStructureParameters(planned, capability);
22
+ const fragment = createNativeFragment(node({
23
+ type: 'n8n-nodes-base.stopAndError',
24
+ version: 1,
25
+ config: {
26
+ id: planned.id,
27
+ name: planned.id,
28
+ position,
29
+ parameters: {
30
+ errorType: 'errorMessage',
31
+ errorMessage: planned.runtimeParameters !== undefined
32
+ ? '={{ $json.message }}'
33
+ : stopParametersSchema.parse(planned.parameters).message,
34
+ },
25
35
  },
26
- },
27
- }), ['main'], []),
36
+ }), ['main'], []);
37
+ return planned.runtimeParameters !== undefined
38
+ ? prependNativeParameterGuard({ input: source, capability, fragment })
39
+ : fragment;
40
+ },
28
41
  };
42
+ return capability;
29
43
  }
@@ -1,3 +1,3 @@
1
1
  import type { RequestMaterializationConfig } from '../../../types/request-materialization.js';
2
2
  /** Compile OAS schemas on the host; n8n receives no schema compiler/eval. */
3
- export declare function compileRequestValidator(config: RequestMaterializationConfig): string;
3
+ export declare function compileRequestValidator(config: RequestMaterializationConfig, globalName?: string): string;
@@ -1,13 +1,13 @@
1
1
  import { createApiRequestSchema } from '@openapi-flow/core';
2
2
  import { compileStandaloneValidator } from '../../common/validation/compile-standalone-validator.js';
3
3
  /** Compile OAS schemas on the host; n8n receives no schema compiler/eval. */
4
- export function compileRequestValidator(config) {
4
+ export function compileRequestValidator(config, globalName = 'OpenApiFlowRequestValidator') {
5
5
  const schema = createApiRequestSchema({
6
6
  operation: config.contract,
7
7
  requestMediaType: config.arguments.requestMediaType,
8
8
  });
9
9
  return compileStandaloneValidator({
10
10
  schema,
11
- globalName: 'OpenApiFlowRequestValidator',
11
+ globalName,
12
12
  });
13
13
  }
@@ -11,8 +11,11 @@ export function createBoundRequestFragment(input, config) {
11
11
  const runtime = readFileSync(new URL('../runtime/request-runtime.bundle.js', import.meta.url), 'utf8');
12
12
  const validator = compileRequestValidator(config);
13
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}}];`}`;
14
+ const materialize = `OpenApiFlowRequestRuntime.materializeHttpRequest(config,responses,OpenApiFlowRequestValidator${config.deferredInputs ? ',inputValues' : ''})`;
15
+ const inputReader = config.deferredInputs
16
+ ? `const inputItems=$(${javascriptJsonLiteral(config.deferredInputs.inputNodeName)}).all(0,0);if(inputItems.length!==1||inputItems[0].json.revision!==${config.deferredInputs.revision})throw new Error('Missing or stale validated workflow inputs: expected revision ${config.deferredInputs.revision}');const inputValues=inputItems[0].json.values;`
17
+ : '';
18
+ const code = `${runtime}\n${validator}\nconst config=${javascriptJsonLiteral(config)};\n${inputReader}\n${linked ? `return $input.all().map((_,inputIndex)=>{${createOutputReaderCode(sources, true)}\nreturn {json:${materialize},pairedItem:{item:inputIndex}};});` : `${createOutputReaderCode(sources)}\nreturn [{json:${materialize}}];`}`;
16
19
  const materializer = node({
17
20
  type: 'n8n-nodes-base.code',
18
21
  version: 2,
@@ -25,7 +28,9 @@ export function createBoundRequestFragment(input, config) {
25
28
  });
26
29
  const bodyPresent = Object.hasOwn(config.arguments.values, 'body') ||
27
30
  config.arguments.bindings.some((binding) => binding.targetPointer === '/body' ||
28
- binding.targetPointer.startsWith('/body/'));
31
+ binding.targetPointer.startsWith('/body/')) ||
32
+ config.deferredInputs?.slots.some((slot) => slot.targetPointer === '/body' ||
33
+ slot.targetPointer.startsWith('/body/')) === true;
29
34
  const mediaTypes = config.operation.body
30
35
  ? Object.keys(config.operation.body.mediaTypes)
31
36
  : [];
@@ -0,0 +1,2 @@
1
+ /** A graph-proven singleton is read as exactly one item, never as an ancestry fallback. */
2
+ export declare function createOutputItemReaderCode(linked: boolean): string;
@@ -0,0 +1,7 @@
1
+ /** A graph-proven singleton is read as exactly one item, never as an ancestry fallback. */
2
+ export function createOutputItemReaderCode(linked) {
3
+ const single = "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');return items[0];";
4
+ return linked
5
+ ? `const item=source.singleton===true?(()=>{${single}})():$(source.nodeName).itemMatching(inputIndex);`
6
+ : `const item=(()=>{${single}})();`;
7
+ }
@@ -1,3 +1,3 @@
1
1
  import type { N8nOutputBindingSource } from '../../../types/output-binding-source.js';
2
2
  /** Read declared JSON roots without coercion or ambiguous item selection. */
3
- export declare function createOutputReaderCode(sources: N8nOutputBindingSource[], linked?: boolean): string;
3
+ export declare function createOutputReaderCode(sources: N8nOutputBindingSource[], linked?: boolean, singletonNodeIds?: string[]): string;
@@ -1,12 +1,14 @@
1
1
  import { javascriptJsonLiteral } from '../../../utils/javascript-json-literal.js';
2
2
  import { compileNativeOutputValidators } from './compile-native-output-validators.js';
3
3
  import { compileSourceResponseValidators } from './compile-source-response-validators.js';
4
+ import { createOutputItemReaderCode } from './create-output-item-reader-code.js';
4
5
  /** Read declared JSON roots without coercion or ambiguous item selection. */
5
- export function createOutputReaderCode(sources, linked = false) {
6
+ export function createOutputReaderCode(sources, linked = false, singletonNodeIds = []) {
6
7
  const declarations = sources.map((source, index) => ({
7
8
  nodeId: source.nodeId,
8
9
  nodeName: source.nodeName,
9
10
  kind: source.kind,
11
+ singleton: singletonNodeIds.includes(source.nodeId),
10
12
  validator: source.kind === 'native-json' || source.operation ? index : null,
11
13
  }));
12
14
  const responseValidators = sources
@@ -19,8 +21,6 @@ export function createOutputReaderCode(sources, linked = false) {
19
21
  ? `OpenApiFlowNativeOutputValidator${index}`
20
22
  : 'null')
21
23
  .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];";
24
+ const reader = createOutputItemReaderCode(linked);
25
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
26
  }