@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.
- package/README.md +29 -1
- package/dist/nodes/inputs/compile-template-input-validators.d.ts +3 -0
- package/dist/nodes/inputs/compile-template-input-validators.js +26 -0
- package/dist/nodes/inputs/create-template-input-nodes.d.ts +4 -0
- package/dist/nodes/inputs/create-template-input-nodes.js +61 -0
- package/dist/nodes/native/arrays/create-bound-native-array-capability.d.ts +4 -0
- package/dist/nodes/native/arrays/create-bound-native-array-capability.js +81 -0
- package/dist/nodes/native/arrays/create-native-array-capability.d.ts +1 -0
- package/dist/nodes/native/arrays/create-native-array-capability.js +7 -59
- package/dist/nodes/native/arrays/native-array-definition.d.ts +2 -0
- package/dist/nodes/native/arrays/native-array-definition.js +27 -0
- package/dist/nodes/native/common/create-material-planning-capability.d.ts +4 -0
- package/dist/nodes/native/common/create-material-planning-capability.js +14 -0
- package/dist/nodes/native/common/define-material-bound-capability.d.ts +4 -0
- package/dist/nodes/native/common/define-material-bound-capability.js +33 -0
- package/dist/nodes/native/common/resolve-native-runtime-source.d.ts +3 -0
- package/dist/nodes/native/common/resolve-native-runtime-source.js +15 -0
- package/dist/nodes/native/common/response-reference-bindings.d.ts +4 -0
- package/dist/nodes/native/common/response-reference-bindings.js +15 -0
- package/dist/nodes/native/create-exclusive-branch-merge-capability.js +10 -0
- package/dist/nodes/native/create-json-output-capability.js +14 -4
- package/dist/nodes/native/create-native-capabilities.js +13 -12
- package/dist/nodes/native/create-pass-through-capability.js +2 -0
- package/dist/nodes/native/create-response-array-capability.js +13 -8
- package/dist/nodes/native/create-response-collection-capability.js +10 -8
- package/dist/nodes/native/if-capability.js +6 -2
- package/dist/nodes/native/inputs/native-input-runtime.bundle.js +24 -0
- package/dist/nodes/native/inputs/prepend-native-parameter-guard.d.ts +4 -0
- package/dist/nodes/native/inputs/prepend-native-parameter-guard.js +52 -0
- package/dist/nodes/native/item-join/create-bound-item-join-capability.d.ts +3 -0
- package/dist/nodes/native/item-join/create-bound-item-join-capability.js +169 -0
- package/dist/nodes/native/item-join/create-item-join-capability.d.ts +2 -1
- package/dist/nodes/native/item-join/create-item-join-capability.js +8 -151
- package/dist/nodes/native/item-join/item-join-definition.d.ts +2 -0
- package/dist/nodes/native/item-join/item-join-definition.js +33 -0
- package/dist/nodes/native/merge-append-capability.js +2 -0
- package/dist/nodes/native/registry/native-feature-definitions.d.ts +4 -0
- package/dist/nodes/native/registry/native-feature-definitions.js +93 -0
- package/dist/nodes/native/registry/native-feature-material-requirements.d.ts +3 -0
- package/dist/nodes/native/registry/native-feature-material-requirements.js +10 -0
- package/dist/nodes/native/registry/resolve-n8n-native-features.d.ts +3 -0
- package/dist/nodes/native/registry/resolve-n8n-native-features.js +6 -0
- package/dist/nodes/native/registry/resolve-n8n-native-planning-features.d.ts +3 -0
- package/dist/nodes/native/registry/resolve-n8n-native-planning-features.js +5 -0
- package/dist/nodes/native/registry/resolve-native-feature-registry.d.ts +3 -0
- package/dist/nodes/native/registry/resolve-native-feature-registry.js +99 -0
- package/dist/nodes/native/registry/validate-native-feature-context.d.ts +2 -0
- package/dist/nodes/native/registry/validate-native-feature-context.js +46 -0
- package/dist/nodes/native/response-array-definition.d.ts +2 -0
- package/dist/nodes/native/response-array-definition.js +27 -0
- package/dist/nodes/native/response-assertion-capability.js +5 -1
- package/dist/nodes/native/response-check-code.d.ts +1 -1
- package/dist/nodes/native/response-check-code.js +13 -7
- package/dist/nodes/native/response-checks/create-response-check-reader.d.ts +1 -1
- package/dist/nodes/native/response-checks/create-response-check-reader.js +2 -2
- package/dist/nodes/native/response-collection-definition.d.ts +2 -0
- package/dist/nodes/native/response-collection-definition.js +27 -0
- package/dist/nodes/native/stop-and-error-capability.js +28 -14
- package/dist/nodes/request/bindings/compile-request-validator.d.ts +1 -1
- package/dist/nodes/request/bindings/compile-request-validator.js +2 -2
- package/dist/nodes/request/bindings/create-bound-request-fragment.js +8 -3
- package/dist/nodes/request/bindings/create-output-item-reader-code.d.ts +2 -0
- package/dist/nodes/request/bindings/create-output-item-reader-code.js +7 -0
- package/dist/nodes/request/bindings/create-output-reader-code.d.ts +1 -1
- package/dist/nodes/request/bindings/create-output-reader-code.js +4 -4
- package/dist/nodes/request/create-http-request-node.js +23 -44
- package/dist/nodes/request/finalize-http-request-fragment.d.ts +3 -0
- package/dist/nodes/request/finalize-http-request-fragment.js +22 -0
- package/dist/nodes/request/resolve-http-request-node-configuration.d.ts +4 -0
- package/dist/nodes/request/resolve-http-request-node-configuration.js +41 -0
- package/dist/nodes/request/response/create-http-response-decoder.js +2 -1
- package/dist/nodes/request/response/http-response-node-name.d.ts +2 -0
- package/dist/nodes/request/response/http-response-node-name.js +4 -0
- package/dist/nodes/request/response/response-runtime.bundle.js +1 -1
- package/dist/nodes/request/runtime/materialize-http-request.d.ts +2 -1
- package/dist/nodes/request/runtime/materialize-http-request.js +21 -3
- package/dist/nodes/request/runtime/request-runtime.bundle.js +1 -1
- package/dist/public-api.d.ts +13 -0
- package/dist/public-api.js +9 -0
- package/dist/schemas/native-feature-registry-schema.d.ts +17 -0
- package/dist/schemas/native-feature-registry-schema.js +8 -0
- package/dist/schemas/native-runtime-input-schema.d.ts +5 -0
- package/dist/schemas/native-runtime-input-schema.js +5 -0
- package/dist/schemas/template-deployment-selection-schema.d.ts +3 -0
- package/dist/schemas/template-deployment-selection-schema.js +10 -0
- package/dist/schemas/workflow-input-value-map-schema.d.ts +5 -0
- package/dist/schemas/workflow-input-value-map-schema.js +5 -0
- package/dist/schemas/workflow-template-schema.d.ts +11 -0
- package/dist/schemas/workflow-template-schema.js +10 -0
- package/dist/types/native-capability.d.ts +10 -1
- package/dist/types/native-feature-registry.d.ts +70 -0
- package/dist/types/native-feature-registry.js +14 -0
- package/dist/types/native-material-binding.d.ts +56 -0
- package/dist/types/native-material-binding.js +1 -0
- package/dist/types/native-runtime-parameters.d.ts +55 -0
- package/dist/types/native-runtime-parameters.js +1 -0
- package/dist/types/output-binding-source.d.ts +5 -0
- package/dist/types/request-materialization.d.ts +9 -1
- package/dist/types/request-node-configuration.d.ts +13 -0
- package/dist/types/request-node-configuration.js +1 -0
- package/dist/types/response-check-compilation.d.ts +1 -1
- package/dist/types/reviewable-workflow.d.ts +2 -0
- package/dist/types/workflow-compilation.d.ts +3 -0
- package/dist/types/workflow-template.d.ts +121 -0
- package/dist/types/workflow-template.js +1 -0
- package/dist/workflow/batch/compile-batched-n8n-workflow.js +22 -4
- package/dist/workflow/build-n8n-workflow.js +3 -6
- package/dist/workflow/compile-native-material-fragments.d.ts +3 -0
- package/dist/workflow/compile-native-material-fragments.js +76 -0
- package/dist/workflow/compile-native-workflow-nodes.js +22 -23
- package/dist/workflow/compile-planned-n8n-workflow.js +28 -3
- package/dist/workflow/compile-reviewable-n8n-workflow.js +31 -3
- package/dist/workflow/compile-validated-workflow-fragments.js +14 -14
- package/dist/workflow/compile-workflow-material-fragments.d.ts +4 -0
- package/dist/workflow/compile-workflow-material-fragments.js +18 -0
- package/dist/workflow/create-n8n-native-output-sources.js +53 -12
- package/dist/workflow/entrypoint/manual-trigger.d.ts +4 -0
- package/dist/workflow/entrypoint/manual-trigger.js +16 -0
- package/dist/workflow/find-singleton-api-responses.d.ts +3 -0
- package/dist/workflow/find-singleton-api-responses.js +22 -0
- package/dist/workflow/inputs/compile-native-runtime-inputs.d.ts +5 -0
- package/dist/workflow/inputs/compile-native-runtime-inputs.js +129 -0
- package/dist/workflow/inputs/create-runtime-input-native-capabilities.d.ts +4 -0
- package/dist/workflow/inputs/create-runtime-input-native-capabilities.js +36 -0
- package/dist/workflow/resolve-n8n-native-materials.d.ts +2 -0
- package/dist/workflow/resolve-n8n-native-materials.js +16 -0
- package/dist/workflow/templates/compile-n8n-workflow-template.d.ts +3 -0
- package/dist/workflow/templates/compile-n8n-workflow-template.js +211 -0
- package/dist/workflow/templates/create-template-artifact-mapping.d.ts +2 -0
- package/dist/workflow/templates/create-template-artifact-mapping.js +58 -0
- package/dist/workflow/templates/create-template-gap-regions.d.ts +5 -0
- package/dist/workflow/templates/create-template-gap-regions.js +22 -0
- package/dist/workflow/templates/create-workflow-template-guide.d.ts +3 -0
- package/dist/workflow/templates/create-workflow-template-guide.js +44 -0
- package/dist/workflow/templates/prepare-template-api-fragments.d.ts +3 -0
- package/dist/workflow/templates/prepare-template-api-fragments.js +74 -0
- package/dist/workflow/templates/select-n8n-template-deployments.d.ts +3 -0
- package/dist/workflow/templates/select-n8n-template-deployments.js +12 -0
- package/dist/workflow/templates/workflow-template-identities.d.ts +5 -0
- package/dist/workflow/templates/workflow-template-identities.js +5 -0
- package/package.json +9 -4
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { sticky } from '@n8n/workflow-sdk';
|
|
2
|
+
import { validateWorkflowDraft, inspectDraftReadiness, resolveWorkflowDraftMaterials, inspectDeferredApiInputs, inspectNativeRuntimeInputs, createNativeRuntimeParameterMaterializations, } from '@openapi-flow/core';
|
|
3
|
+
import { createTemplateInputNodes } from '../../nodes/inputs/create-template-input-nodes.js';
|
|
4
|
+
import { compileValidatedWorkflowFragments } from '../compile-validated-workflow-fragments.js';
|
|
5
|
+
import { buildN8nWorkflow } from '../build-n8n-workflow.js';
|
|
6
|
+
import { createWorkflowTemplateGuide } from './create-workflow-template-guide.js';
|
|
7
|
+
import { prepareTemplateApiFragments } from './prepare-template-api-fragments.js';
|
|
8
|
+
import { createTemplateArtifactMapping } from './create-template-artifact-mapping.js';
|
|
9
|
+
import { TEMPLATE_INPUT_ID, TEMPLATE_PREFLIGHT_ID, TEMPLATE_PREFLIGHT_NAME, TEMPLATE_GUIDE_ID, } from './workflow-template-identities.js';
|
|
10
|
+
import { n8nTemplateInputValuesSchema } from '../../schemas/workflow-template-schema.js';
|
|
11
|
+
import { createRuntimeInputNativeCapabilities } from '../inputs/create-runtime-input-native-capabilities.js';
|
|
12
|
+
import { resolveN8nNativeMaterials } from '../resolve-n8n-native-materials.js';
|
|
13
|
+
import { compileWorkflowMaterialFragments } from '../compile-workflow-material-fragments.js';
|
|
14
|
+
import { createTemplateGapRegions } from './create-template-gap-regions.js';
|
|
15
|
+
/** An explicit template boundary, never a false fully-resolved strict request. */
|
|
16
|
+
export function compileN8nWorkflowTemplate(source) {
|
|
17
|
+
if (source.itemMode !== undefined && source.itemMode !== 'linked')
|
|
18
|
+
throw new Error('template.itemMode must be linked or omitted');
|
|
19
|
+
const initial = resolveWorkflowDraftMaterials(source);
|
|
20
|
+
const materialSnapshot = resolveN8nNativeMaterials({
|
|
21
|
+
apiMaterials: initial.materials,
|
|
22
|
+
nativeOutputs: [],
|
|
23
|
+
nativeNodes: source.draft.nativeNodes,
|
|
24
|
+
capabilities: source.context.capabilities,
|
|
25
|
+
});
|
|
26
|
+
// Planning receipts belong to the original declarations. Runtime-bound
|
|
27
|
+
// capabilities must not replace that source context during draft validation.
|
|
28
|
+
const input = source;
|
|
29
|
+
validateWorkflowDraft(input);
|
|
30
|
+
if (typeof input.name !== 'string' || !input.name.trim())
|
|
31
|
+
throw new Error('template.name must be non-empty');
|
|
32
|
+
const readiness = inspectDraftReadiness(input);
|
|
33
|
+
const { materials, slots, nativeSlots } = resolveWorkflowDraftMaterials(input);
|
|
34
|
+
const allSlots = [...slots, ...nativeSlots];
|
|
35
|
+
const checkedValues = n8nTemplateInputValuesSchema.safeParse(input.inputValues === undefined ? {} : input.inputValues);
|
|
36
|
+
if (!checkedValues.success)
|
|
37
|
+
throw new Error('template.inputValues must be a JSON value map', {
|
|
38
|
+
cause: checkedValues.error,
|
|
39
|
+
});
|
|
40
|
+
const inputValues = checkedValues.data;
|
|
41
|
+
const known = new Set(allSlots.map((slot) => slot.id));
|
|
42
|
+
const unknown = Object.keys(inputValues).filter((id) => !known.has(id));
|
|
43
|
+
if (unknown.length)
|
|
44
|
+
throw new Error(`template.inputValues contains undeclared or stale slots ${unknown.join(', ')}`);
|
|
45
|
+
const apiValues = Object.fromEntries(slots
|
|
46
|
+
.filter((slot) => Object.hasOwn(inputValues, slot.id))
|
|
47
|
+
.map((slot) => [slot.id, inputValues[slot.id]]));
|
|
48
|
+
const nativeValues = Object.fromEntries(nativeSlots
|
|
49
|
+
.filter((slot) => Object.hasOwn(inputValues, slot.id))
|
|
50
|
+
.map((slot) => [slot.id, inputValues[slot.id]]));
|
|
51
|
+
const inputs = inspectDeferredApiInputs({
|
|
52
|
+
materials,
|
|
53
|
+
inputValues: apiValues,
|
|
54
|
+
});
|
|
55
|
+
if (inputs.status === 'invalid-input')
|
|
56
|
+
throw new Error(inputs.issues.map((issue) => issue.message).join('; '));
|
|
57
|
+
const nativeInputs = inspectNativeRuntimeInputs({
|
|
58
|
+
nativeNodes: input.draft.nativeNodes,
|
|
59
|
+
capabilities: input.context.capabilities,
|
|
60
|
+
inputValues: nativeValues,
|
|
61
|
+
});
|
|
62
|
+
if (nativeInputs.status === 'invalid-input')
|
|
63
|
+
throw new Error(nativeInputs.issues.map((issue) => issue.message).join('; '));
|
|
64
|
+
const capabilities = createRuntimeInputNativeCapabilities({
|
|
65
|
+
capabilities: materialSnapshot.capabilities,
|
|
66
|
+
slots: nativeSlots,
|
|
67
|
+
revision: input.draft.revision,
|
|
68
|
+
inputNodeName: TEMPLATE_PREFLIGHT_NAME,
|
|
69
|
+
});
|
|
70
|
+
const workflowMaterials = materials.map((material) => ({
|
|
71
|
+
operation: material.operation,
|
|
72
|
+
arguments: {
|
|
73
|
+
callId: material.callId,
|
|
74
|
+
values: material.values,
|
|
75
|
+
bindings: material.bindings,
|
|
76
|
+
...(material.requestedInputs === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { requestedInputs: material.requestedInputs }),
|
|
79
|
+
unresolvedInputs: slots
|
|
80
|
+
.filter((slot) => slot.callId === material.callId)
|
|
81
|
+
.map((slot) => slot.targetPointer),
|
|
82
|
+
...(material.requestMediaType !== undefined
|
|
83
|
+
? { requestMediaType: material.requestMediaType }
|
|
84
|
+
: {}),
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
const nodeIds = [
|
|
88
|
+
...input.draft.apiCalls.map((call) => call.id),
|
|
89
|
+
...input.draft.nativeNodes.map((node) => node.id),
|
|
90
|
+
...(input.draft.gapNodes === undefined
|
|
91
|
+
? []
|
|
92
|
+
: input.draft.gapNodes.map((node) => node.id)),
|
|
93
|
+
];
|
|
94
|
+
if (!nodeIds.length)
|
|
95
|
+
throw new Error('A workflow template requires at least one API, native or unresolved instance');
|
|
96
|
+
if (nodeIds.some((id) => [TEMPLATE_INPUT_ID, TEMPLATE_PREFLIGHT_ID, TEMPLATE_GUIDE_ID].includes(id)))
|
|
97
|
+
throw new Error('Draft node ID conflicts with a template system node');
|
|
98
|
+
const plan = {
|
|
99
|
+
nativeNodes: input.draft.nativeNodes,
|
|
100
|
+
edges: input.draft.edges,
|
|
101
|
+
starts: nodeIds.filter((id) => !input.draft.edges.some((edge) => edge.to === id)),
|
|
102
|
+
gaps: [],
|
|
103
|
+
};
|
|
104
|
+
const prepared = prepareTemplateApiFragments({
|
|
105
|
+
template: input,
|
|
106
|
+
capabilities,
|
|
107
|
+
materials: workflowMaterials,
|
|
108
|
+
slots,
|
|
109
|
+
plan,
|
|
110
|
+
});
|
|
111
|
+
const compilation = {
|
|
112
|
+
id: input.draft.id,
|
|
113
|
+
name: input.name,
|
|
114
|
+
plan,
|
|
115
|
+
materials: workflowMaterials,
|
|
116
|
+
apiNodes: prepared.apiNodes,
|
|
117
|
+
capabilities,
|
|
118
|
+
};
|
|
119
|
+
const gapNodes = input.draft.gapNodes === undefined ? [] : input.draft.gapNodes;
|
|
120
|
+
// validateWorkflowDraft above owns the complete graph, including unresolved
|
|
121
|
+
// structural nodes. The strict compiler continues to reject unresolved graphs.
|
|
122
|
+
const materialFragments = gapNodes.length
|
|
123
|
+
? compileWorkflowMaterialFragments(compilation)
|
|
124
|
+
: compileValidatedWorkflowFragments(compilation);
|
|
125
|
+
const gapRegions = createTemplateGapRegions(gapNodes, materialFragments);
|
|
126
|
+
const fragments = [
|
|
127
|
+
...materialFragments,
|
|
128
|
+
...gapRegions.map((region) => region.fragment),
|
|
129
|
+
];
|
|
130
|
+
const { mappings, inputSlots } = createTemplateArtifactMapping({
|
|
131
|
+
context: input.context,
|
|
132
|
+
draft: input.draft,
|
|
133
|
+
materials: workflowMaterials,
|
|
134
|
+
slots: allSlots,
|
|
135
|
+
fragments,
|
|
136
|
+
});
|
|
137
|
+
const { deploymentRequirements } = prepared;
|
|
138
|
+
const triggerConnected = readiness.designReady && deploymentRequirements.length === 0;
|
|
139
|
+
const guide = createWorkflowTemplateGuide({
|
|
140
|
+
draft: input.draft,
|
|
141
|
+
readiness,
|
|
142
|
+
slots: inputSlots,
|
|
143
|
+
mappings,
|
|
144
|
+
deploymentRequirements,
|
|
145
|
+
requestInputsReady: inputs.status === 'ready',
|
|
146
|
+
nativeInputsReady: nativeInputs.status === 'ready',
|
|
147
|
+
triggerConnected,
|
|
148
|
+
});
|
|
149
|
+
const systemNodes = createTemplateInputNodes({
|
|
150
|
+
revision: input.draft.revision,
|
|
151
|
+
slots: allSlots,
|
|
152
|
+
inputValues,
|
|
153
|
+
requests: prepared.requests,
|
|
154
|
+
nativeParameters: createNativeRuntimeParameterMaterializations({
|
|
155
|
+
nativeNodes: input.draft.nativeNodes,
|
|
156
|
+
capabilities: materialSnapshot.capabilities,
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
const height = 100 +
|
|
160
|
+
guide
|
|
161
|
+
.split('\n')
|
|
162
|
+
.reduce((total, line) => total + Math.max(1, Math.ceil(line.length / 90)) * 24, 0);
|
|
163
|
+
const workflow = buildN8nWorkflow({
|
|
164
|
+
id: input.draft.id,
|
|
165
|
+
name: input.name,
|
|
166
|
+
nodes: [...systemNodes, ...fragments],
|
|
167
|
+
edges: [
|
|
168
|
+
{
|
|
169
|
+
from: TEMPLATE_INPUT_ID,
|
|
170
|
+
output: 'main',
|
|
171
|
+
to: TEMPLATE_PREFLIGHT_ID,
|
|
172
|
+
input: 'main',
|
|
173
|
+
},
|
|
174
|
+
...plan.starts.map((to) => ({
|
|
175
|
+
from: TEMPLATE_PREFLIGHT_ID,
|
|
176
|
+
output: 'main',
|
|
177
|
+
to,
|
|
178
|
+
input: 'main',
|
|
179
|
+
})),
|
|
180
|
+
...plan.edges,
|
|
181
|
+
],
|
|
182
|
+
starts: [TEMPLATE_INPUT_ID],
|
|
183
|
+
triggerConnections: triggerConnected ? 'connected' : 'detached',
|
|
184
|
+
annotations: [
|
|
185
|
+
sticky(guide, [], {
|
|
186
|
+
id: TEMPLATE_GUIDE_ID,
|
|
187
|
+
name: '워크플로우 사용 안내',
|
|
188
|
+
position: [0, -height - 100],
|
|
189
|
+
width: 1000,
|
|
190
|
+
height,
|
|
191
|
+
color: triggerConnected ? 5 : 3,
|
|
192
|
+
}),
|
|
193
|
+
...gapRegions.map((region) => region.annotation),
|
|
194
|
+
],
|
|
195
|
+
});
|
|
196
|
+
workflow.settings = { ...workflow.settings, executionOrder: 'v1' };
|
|
197
|
+
return {
|
|
198
|
+
revision: input.draft.revision,
|
|
199
|
+
workflow,
|
|
200
|
+
draftReadiness: readiness,
|
|
201
|
+
requestInputsReady: inputs.status === 'ready',
|
|
202
|
+
nativeInputsReady: nativeInputs.status === 'ready',
|
|
203
|
+
runtimeInputsReady: inputs.status === 'ready' && nativeInputs.status === 'ready',
|
|
204
|
+
deploymentConfigured: deploymentRequirements.length === 0,
|
|
205
|
+
triggerConnected,
|
|
206
|
+
inputSlots,
|
|
207
|
+
deploymentRequirements,
|
|
208
|
+
nodeMappings: mappings,
|
|
209
|
+
guide,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { inspectWorkflowDraftCapabilityGap } from '@openapi-flow/core';
|
|
2
|
+
import { TEMPLATE_INPUT_ID, TEMPLATE_INPUT_NAME, } from './workflow-template-identities.js';
|
|
3
|
+
export function createTemplateArtifactMapping(input) {
|
|
4
|
+
const mappings = input.fragments.map((fragment) => {
|
|
5
|
+
const common = {
|
|
6
|
+
nodeId: fragment.nodeId,
|
|
7
|
+
sdkNodeIds: fragment.nodes.map((node) => node.id),
|
|
8
|
+
entryNodeName: fragment.entry.name,
|
|
9
|
+
exitNodeName: fragment.exit.name,
|
|
10
|
+
};
|
|
11
|
+
const material = input.materials.find((item) => item.arguments.callId === fragment.nodeId);
|
|
12
|
+
if (material) {
|
|
13
|
+
const { key, method, path } = material.operation;
|
|
14
|
+
const { summary } = material.operation.operation;
|
|
15
|
+
return {
|
|
16
|
+
...common,
|
|
17
|
+
kind: 'api',
|
|
18
|
+
operation: {
|
|
19
|
+
key,
|
|
20
|
+
method,
|
|
21
|
+
path,
|
|
22
|
+
...(summary !== undefined ? { summary } : {}),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const gap = input.draft.gapNodes?.find((node) => node.id === fragment.nodeId);
|
|
27
|
+
if (gap)
|
|
28
|
+
return {
|
|
29
|
+
...common,
|
|
30
|
+
kind: 'capability-gap',
|
|
31
|
+
gap: structuredClone(gap),
|
|
32
|
+
inventoryCurrent: inspectWorkflowDraftCapabilityGap({
|
|
33
|
+
gap,
|
|
34
|
+
context: input.context,
|
|
35
|
+
}).inventoryCurrent,
|
|
36
|
+
};
|
|
37
|
+
const planned = input.draft.nativeNodes.find((node) => node.id === fragment.nodeId);
|
|
38
|
+
if (!planned)
|
|
39
|
+
throw new Error(`Template fragment ${fragment.nodeId} has no original API or native instance`);
|
|
40
|
+
return { ...common, kind: 'native', capability: planned.capability };
|
|
41
|
+
});
|
|
42
|
+
const inputSlots = input.slots.map((slot) => {
|
|
43
|
+
const mapping = mappings.find((mapping) => mapping.kind ===
|
|
44
|
+
(slot.kind === 'api-request-input' ? 'api' : 'native') &&
|
|
45
|
+
mapping.nodeId ===
|
|
46
|
+
(slot.kind === 'api-request-input' ? slot.callId : slot.nodeId));
|
|
47
|
+
if (!mapping)
|
|
48
|
+
throw new Error(`Template slot ${slot.id} has no compiled source node mapping`);
|
|
49
|
+
return {
|
|
50
|
+
...slot,
|
|
51
|
+
editNodeId: TEMPLATE_INPUT_ID,
|
|
52
|
+
editNodeName: TEMPLATE_INPUT_NAME,
|
|
53
|
+
editParameter: 'jsCode',
|
|
54
|
+
materializerNodeName: mapping.entryNodeName,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return { mappings, inputSlots };
|
|
58
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WorkflowDraftCapabilityGap } from '@openapi-flow/core';
|
|
2
|
+
import type { GapReviewRegion } from '../../types/reviewable-workflow.js';
|
|
3
|
+
import type { N8nNodeFragment } from '../../types/node-fragment.js';
|
|
4
|
+
/** Existing warning region and stop-on-execution surrogate; no invented API/response. */
|
|
5
|
+
export declare function createTemplateGapRegions(gaps: WorkflowDraftCapabilityGap[], materialFragments: N8nNodeFragment[]): GapReviewRegion[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createGapReviewRegion } from '../review/create-gap-review-region.js';
|
|
2
|
+
import { GAP_REVIEW_REGION_WIDTH, GAP_REVIEW_REGION_SPACING, } from '../review/gap-review-region-layout.js';
|
|
3
|
+
/** Existing warning region and stop-on-execution surrogate; no invented API/response. */
|
|
4
|
+
export function createTemplateGapRegions(gaps, materialFragments) {
|
|
5
|
+
const top = Math.max(0, ...materialFragments.flatMap((fragment) => fragment.nodes.map((node) => node.config.position?.[1] === undefined ? 0 : node.config.position[1]))) + 240;
|
|
6
|
+
return gaps.map((gap, index) => createGapReviewRegion({
|
|
7
|
+
nodeId: gap.id,
|
|
8
|
+
position: [
|
|
9
|
+
700 + index * (GAP_REVIEW_REGION_WIDTH + GAP_REVIEW_REGION_SPACING),
|
|
10
|
+
top,
|
|
11
|
+
],
|
|
12
|
+
gaps: [
|
|
13
|
+
{
|
|
14
|
+
id: gap.id,
|
|
15
|
+
stage: gap.finding.kind === 'unsupported-native'
|
|
16
|
+
? 'workflow-graph'
|
|
17
|
+
: 'api-selection',
|
|
18
|
+
description: `요청한 기능: ${gap.requestedBehavior}\n판단 근거: ${gap.finding.reason}${gap.finding.kind === 'api-contract-gap' ? '\n대상 OAS 작업: ' + gap.finding.operation.documentId + ' ' + gap.finding.operation.operationRef : ''}\n제공된 OAS·native 계약에서 충족하지 못한 기능입니다. 전체 서비스에 API가 없다는 확정이나 실행 가능한 구현이 아닙니다.`,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { escapeReviewMarkdown } from '../review/escape-review-markdown.js';
|
|
2
|
+
/** Status and edit locations derive from the same revision and compiled mapping. */
|
|
3
|
+
export function createWorkflowTemplateGuide(input) {
|
|
4
|
+
const text = escapeReviewMarkdown;
|
|
5
|
+
const lines = [
|
|
6
|
+
'# 워크플로우 사용 안내',
|
|
7
|
+
`초안 버전: ${input.draft.revision}`,
|
|
8
|
+
`설계 결정: ${input.readiness.designReady ? '확정' : '확인 필요'} / 사람 검토: ${input.readiness.reviewed ? '완료' : '미완료'} / 실제 요청 입력: ${input.requestInputsReady ? '준비됨' : '미입력 있음'} / native 입력: ${input.nativeInputsReady ? '준비됨' : '미입력 있음'}`,
|
|
9
|
+
input.triggerConnected
|
|
10
|
+
? 'Start → 입력 설정 → 입력 사전 검사 → 업무 흐름 순서로 실행됩니다. 미입력·잘못된 값은 외부 호출 전에 오류로 중단됩니다.'
|
|
11
|
+
: '미확정 설계 또는 배포 설정이 있어 Start 연결을 끊었습니다. 아래 항목을 해결한 뒤 다시 생성하거나 설정을 확인하고 Start를 입력 설정에 연결하세요.',
|
|
12
|
+
'## 입력 슬롯',
|
|
13
|
+
'입력 설정 노드의 jsCode에 있는 inputValues 객체에 슬롯 ID를 키로 실제 JSON 값을 추가하세요. API 값은 원래 OAS, native 값은 등록된 파라미터 스키마를 따릅니다. native 슬롯에는 표시된 필드의 값 또는 전체 파라미터 객체를 입력합니다. 이미 정한 리터럴은 보존하며, 포트·참조·연결을 바꾸는 설정은 실행 입력으로 미루지 않습니다. 비밀값은 여기에 넣지 말고 n8n credential을 사용하세요.',
|
|
14
|
+
...input.slots.map((slot) => `- ${text(slot.id)}: ${text(slot.kind === 'api-request-input' ? slot.callId + slot.targetPointer : 'native ' + slot.nodeId + (slot.targetPointer === '' ? ' 전체 파라미터' : slot.targetPointer))} → ${text(slot.materializerNodeName)} / 원래 스키마: ${text(JSON.stringify(slot.schema))}${slot.kind === 'api-request-input' && slot.requestedInputs !== undefined ? ' / 사용자 요청으로 포함할 입력: ' + text(slot.requestedInputs.join(', ')) + ' (OAS 필수 여부와 별도)' : ''}`),
|
|
15
|
+
...(input.slots.length ? [] : ['추가로 입력할 필수 실행 값이 없습니다.']),
|
|
16
|
+
'## 노드 구성',
|
|
17
|
+
...input.mappings.map((mapping) => `- ${text(mapping.nodeId)} (${mapping.kind === 'api' ? text(mapping.operation.method + ' ' + mapping.operation.path) : mapping.kind === 'native' ? text('등록 native ' + mapping.capability) : '확인 필요 · 미해결 기능'}): ${text(mapping.entryNodeName)} → ${text(mapping.exitNodeName)}`),
|
|
18
|
+
'## 연결과 데이터 출처',
|
|
19
|
+
...input.draft.edges.map((edge) => `- ${text(edge.from)} (${text(edge.output)}) → ${text(edge.to)} (${text(edge.input)})`),
|
|
20
|
+
...input.draft.apiCalls.flatMap((call) => call.bindings.map((binding) => `- ${text(binding.sourceNodeId + binding.sourcePointer)} 값을 ${text(call.id + binding.targetPointer)}에 타입 변환 없이 전달합니다.`)),
|
|
21
|
+
'## 미확정 결정',
|
|
22
|
+
...(input.draft.gapNodes === undefined
|
|
23
|
+
? []
|
|
24
|
+
: input.draft.gapNodes.flatMap((gap) => [
|
|
25
|
+
`- ${text(gap.id)}: ${text(gap.requestedBehavior)} / ${text(gap.finding.kind)} / 판단 근거: ${text(gap.finding.reason)}`,
|
|
26
|
+
...(gap.finding.kind === 'api-contract-gap'
|
|
27
|
+
? [
|
|
28
|
+
` 대상 OAS 작업: ${text(gap.finding.operation.documentId + ' ' + gap.finding.operation.operationRef)}`,
|
|
29
|
+
]
|
|
30
|
+
: []),
|
|
31
|
+
` 제공된 계약에서 충족하지 못한 기능이며 전체 서비스의 API 부재를 확정하지 않습니다. 원래 검토 범위: OAS ${gap.inventory.operations.length}개 작업·native ${gap.inventory.capabilities.length}개 계약. 대역을 실제 구현으로 교체하고 검증하기 전에는 Start를 연결하지 마세요.`,
|
|
32
|
+
` 검토 범위 일치: ${input.mappings.some((mapping) => mapping.kind === 'capability-gap' && mapping.nodeId === gap.id && mapping.inventoryCurrent) ? '현재 계약과 일치' : '계약 변경됨. 새 계약으로 다시 검토해야 하며 결손을 자동 해소하지 않았습니다.'}`,
|
|
33
|
+
])),
|
|
34
|
+
...input.draft.requirements
|
|
35
|
+
.filter((item) => item.state !== 'confirmed')
|
|
36
|
+
.map((item) => `- ${text(item.id)}: ${text(item.text)} / 영향 범위: ${text(item.scope.kind === 'workflow' ? '전체' : item.scope.nodeIds.join(', '))}`),
|
|
37
|
+
'## 배포 설정',
|
|
38
|
+
...input.deploymentRequirements.map((item) => `- ${text(item.documentId)} / ${text(item.callId)}: ${text(item.fields.join(', '))}. 주소는 ${text(item.materializerNodeName)}의 config.templateUrl, credential은 ${text(item.requestNodeName)}에서 설정하세요. 호스트에 배포 옵션을 제공하고 다시 생성하는 방법을 권장합니다.`),
|
|
39
|
+
'credential 참조를 제공해도 실제 n8n에서 해당 credential을 사용할 권한이 있는지는 여기서 확인하지 않습니다. 실행 전 n8n에서 확인하세요.',
|
|
40
|
+
'## 실행 전 확인',
|
|
41
|
+
'입력 사전 검사와 요청 전 OAS 검사는 정상 실행 경로의 오류 방지 장치입니다. 부분 실행·pinning·JSON 직접 편집으로 건너뛸 수 있으므로 보안 경계가 아닙니다. 이 산출물은 사용자 n8n에 저장·활성화·실행하지 않습니다.',
|
|
42
|
+
];
|
|
43
|
+
return lines.join('\n\n');
|
|
44
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PrepareTemplateApiFragmentsInput, PreparedTemplateApiFragments } from '../../types/workflow-template.js';
|
|
2
|
+
/** Resolve deployment and request runtime configuration only from supplied contracts. */
|
|
3
|
+
export declare function prepareTemplateApiFragments(input: PrepareTemplateApiFragmentsInput): PreparedTemplateApiFragments;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { resolveHttpRequestNodeConfiguration } from '../../nodes/request/resolve-http-request-node-configuration.js';
|
|
2
|
+
import { createBoundRequestFragment } from '../../nodes/request/bindings/create-bound-request-fragment.js';
|
|
3
|
+
import { finalizeHttpRequestFragment } from '../../nodes/request/finalize-http-request-fragment.js';
|
|
4
|
+
import { httpResponseNodeName } from '../../nodes/request/response/http-response-node-name.js';
|
|
5
|
+
import { createN8nNativeOutputSources } from '../create-n8n-native-output-sources.js';
|
|
6
|
+
import { TEMPLATE_PREFLIGHT_NAME } from './workflow-template-identities.js';
|
|
7
|
+
import { n8nTemplateDeploymentsSchema } from '../../schemas/workflow-template-schema.js';
|
|
8
|
+
/** Resolve deployment and request runtime configuration only from supplied contracts. */
|
|
9
|
+
export function prepareTemplateApiFragments(input) {
|
|
10
|
+
const deployments = n8nTemplateDeploymentsSchema.parse(input.template.deployments === undefined ? [] : input.template.deployments);
|
|
11
|
+
const usedDocuments = new Set(input.materials.map((material) => material.operation.key.documentId));
|
|
12
|
+
if (deployments.some((item) => !usedDocuments.has(item.documentId)))
|
|
13
|
+
throw new Error('template.deployments contains an unused documentId');
|
|
14
|
+
const apiNodeNames = Object.fromEntries(input.materials.map((material) => [
|
|
15
|
+
material.arguments.callId,
|
|
16
|
+
httpResponseNodeName(material.arguments.callId),
|
|
17
|
+
]));
|
|
18
|
+
const apiResponseContracts = Object.fromEntries(input.materials.map((material) => [
|
|
19
|
+
material.arguments.callId,
|
|
20
|
+
material.operation,
|
|
21
|
+
]));
|
|
22
|
+
const nativeOutputSources = createN8nNativeOutputSources({
|
|
23
|
+
nativeNodes: input.plan.nativeNodes,
|
|
24
|
+
capabilities: input.capabilities,
|
|
25
|
+
apiNodeNames,
|
|
26
|
+
apiResponseContracts,
|
|
27
|
+
});
|
|
28
|
+
const prepared = input.materials.map((material, index) => {
|
|
29
|
+
const deployment = deployments.find((item) => item.documentId === material.operation.key.documentId);
|
|
30
|
+
const result = resolveHttpRequestNodeConfiguration({
|
|
31
|
+
...material,
|
|
32
|
+
baseUrl: deployment?.baseUrl,
|
|
33
|
+
credentialBindings: deployment?.credentialBindings,
|
|
34
|
+
securityRequirementIndex: deployment?.securityRequirementIndex,
|
|
35
|
+
position: [700, index * 260],
|
|
36
|
+
apiNodeNames,
|
|
37
|
+
nativeOutputSources,
|
|
38
|
+
apiResponseContracts,
|
|
39
|
+
itemMode: input.template.itemMode,
|
|
40
|
+
});
|
|
41
|
+
result.config.deferredInputs = {
|
|
42
|
+
revision: input.template.draft.revision,
|
|
43
|
+
inputNodeName: TEMPLATE_PREFLIGHT_NAME,
|
|
44
|
+
slots: input.slots.filter((slot) => slot.callId === material.arguments.callId),
|
|
45
|
+
};
|
|
46
|
+
return result;
|
|
47
|
+
});
|
|
48
|
+
const apiNodes = prepared.map((item) => finalizeHttpRequestFragment({
|
|
49
|
+
fragment: createBoundRequestFragment(item.input, item.config),
|
|
50
|
+
method: item.config.contract.method,
|
|
51
|
+
documentId: item.config.contract.key.documentId,
|
|
52
|
+
deployment: item.input,
|
|
53
|
+
}));
|
|
54
|
+
const deploymentRequirements = prepared
|
|
55
|
+
.filter((item) => item.input.pendingFields.length > 0)
|
|
56
|
+
.map((item) => {
|
|
57
|
+
const fragment = apiNodes.find((fragment) => fragment.nodeId === item.config.arguments.callId);
|
|
58
|
+
const request = fragment?.nodes.find((node) => node.type === 'n8n-nodes-base.httpRequest');
|
|
59
|
+
if (!fragment || !request)
|
|
60
|
+
throw new Error(`Template ${item.config.arguments.callId} requires its compiled HTTP request mapping`);
|
|
61
|
+
return {
|
|
62
|
+
callId: item.config.arguments.callId,
|
|
63
|
+
documentId: item.config.contract.key.documentId,
|
|
64
|
+
fields: item.input.pendingFields,
|
|
65
|
+
materializerNodeName: fragment.entry.name,
|
|
66
|
+
requestNodeName: request.name,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
apiNodes,
|
|
71
|
+
requests: prepared.map((item) => item.config),
|
|
72
|
+
deploymentRequirements,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SelectN8nTemplateDeploymentsInput, N8nTemplateDeployment } from '../../types/workflow-template.js';
|
|
2
|
+
/** Validate the whole supplied registry first, then project declared settings onto the selected OAS documents. */
|
|
3
|
+
export declare function selectN8nTemplateDeployments(input: SelectN8nTemplateDeploymentsInput): N8nTemplateDeployment[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { templateDeploymentSelectionSchema } from '../../schemas/template-deployment-selection-schema.js';
|
|
2
|
+
/** Validate the whole supplied registry first, then project declared settings onto the selected OAS documents. */
|
|
3
|
+
export function selectN8nTemplateDeployments(input) {
|
|
4
|
+
const parsed = templateDeploymentSelectionSchema.parse(input);
|
|
5
|
+
for (const deployment of parsed.deployments)
|
|
6
|
+
if (!parsed.availableDocumentIds.includes(deployment.documentId))
|
|
7
|
+
throw new Error(`deployments.${deployment.documentId} does not belong to an available original OAS document`);
|
|
8
|
+
for (const documentId of parsed.selectedDocumentIds)
|
|
9
|
+
if (!parsed.availableDocumentIds.includes(documentId))
|
|
10
|
+
throw new Error(`selectedDocumentIds.${documentId} is not an available original OAS document`);
|
|
11
|
+
return structuredClone(parsed.deployments.filter((deployment) => parsed.selectedDocumentIds.includes(deployment.documentId)));
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const TEMPLATE_INPUT_ID = "openapi-flow-inputs";
|
|
2
|
+
export declare const TEMPLATE_INPUT_NAME = "\uC785\uB825 \uC124\uC815";
|
|
3
|
+
export declare const TEMPLATE_PREFLIGHT_ID = "openapi-flow-preflight";
|
|
4
|
+
export declare const TEMPLATE_PREFLIGHT_NAME = "\uC785\uB825 \uC0AC\uC804 \uAC80\uC0AC";
|
|
5
|
+
export declare const TEMPLATE_GUIDE_ID = "openapi-flow-template-guide";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const TEMPLATE_INPUT_ID = 'openapi-flow-inputs';
|
|
2
|
+
export const TEMPLATE_INPUT_NAME = '입력 설정';
|
|
3
|
+
export const TEMPLATE_PREFLIGHT_ID = 'openapi-flow-preflight';
|
|
4
|
+
export const TEMPLATE_PREFLIGHT_NAME = '입력 사전 검사';
|
|
5
|
+
export const TEMPLATE_GUIDE_ID = 'openapi-flow-template-guide';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-flow/n8n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Deterministic OpenAPI request nodes and n8n workflow assembly",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"THIRD_PARTY_NOTICES.md"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "tsc -p tsconfig.json && node ../../scripts/build-request-runtime.mjs && node ../../scripts/build-item-join-runtime.mjs && node ../../scripts/build-response-runtime.mjs",
|
|
27
|
+
"build": "tsc -p tsconfig.json && node ../../scripts/build-request-runtime.mjs && node ../../scripts/build-native-input-runtime.mjs && node ../../scripts/build-item-join-runtime.mjs && node ../../scripts/build-response-runtime.mjs",
|
|
28
28
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@n8n/config": "2.41.2",
|
|
32
32
|
"@n8n/workflow-sdk": "0.36.4",
|
|
33
|
-
"@openapi-flow/core": "0.
|
|
33
|
+
"@openapi-flow/core": "0.3.1",
|
|
34
34
|
"@sentry/node": "10.70.0",
|
|
35
35
|
"@types/estree": "1.0.8",
|
|
36
36
|
"@types/express": "5.0.1",
|
|
@@ -41,7 +41,12 @@
|
|
|
41
41
|
"ajv": "8.20.0",
|
|
42
42
|
"ajv-formats": "3.0.1",
|
|
43
43
|
"esbuild": "0.28.2",
|
|
44
|
-
"nock": "14.0.14"
|
|
44
|
+
"nock": "14.0.14"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"zod": "^4.6.5"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
45
50
|
"zod": "4.6.5"
|
|
46
51
|
},
|
|
47
52
|
"publishConfig": {
|