@openapi-flow/core 0.1.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.
@@ -0,0 +1,49 @@
1
+ export type JsonObject = Record<string, unknown>;
2
+ export type Scalar = string | number | boolean;
3
+ export type OperationMethod = string;
4
+ export interface OperationEntry {
5
+ method: string;
6
+ key: string;
7
+ value: unknown;
8
+ }
9
+ export interface ScalarSchema {
10
+ type: string;
11
+ enum?: Scalar[];
12
+ }
13
+ export type ScalarProperties = Record<string, ScalarSchema>;
14
+ export interface OperationParameter {
15
+ name: string;
16
+ in: 'path' | 'query';
17
+ required: boolean;
18
+ schema: ScalarSchema;
19
+ }
20
+ export interface OperationBody {
21
+ required: boolean;
22
+ requiredProperties: string[];
23
+ properties: ScalarProperties;
24
+ }
25
+ export interface OperationMetadata {
26
+ operationId?: string;
27
+ summary: string;
28
+ description: string;
29
+ tags: string[];
30
+ }
31
+ export interface Operation extends OperationMetadata {
32
+ operationRef: string;
33
+ method: OperationMethod;
34
+ path: string;
35
+ effect: 'read' | 'write' | 'unknown';
36
+ status: number;
37
+ parameters: OperationParameter[];
38
+ body?: OperationBody;
39
+ responseProperties: ScalarProperties;
40
+ }
41
+ export type OperationCandidate = Pick<Operation, 'operationRef' | 'operationId' | 'method' | 'path' | 'summary' | 'description' | 'tags'>;
42
+ export interface ObjectSchemaProperties {
43
+ properties: JsonObject;
44
+ required: string[];
45
+ }
46
+ export interface ParsedDocument {
47
+ spec: JsonObject;
48
+ paths: JsonObject;
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import type { WorkflowJSON } from '@n8n/workflow-sdk';
2
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
+ import type { Operation, Scalar } from './openapi.js';
4
+ export type Primitive = Scalar;
5
+ export type ExpectedBody = Record<string, Primitive>;
6
+ export type WorkflowInputs = Record<string, Primitive | ExpectedBody>;
7
+ export type InputValues = Record<string, unknown>;
8
+ export type RequiredFields = Record<string, string>;
9
+ export type PreviousOperations = Map<string, Operation>;
10
+ export type RequiredOutputs = Map<string, Set<string>>;
11
+ export type CompileStatus = 'complete' | 'needs_input' | 'blocked';
12
+ export type OperationEvidence = Pick<Operation, 'operationRef' | 'operationId' | 'method' | 'path' | 'status' | 'effect'>;
13
+ export interface WorkflowPlan {
14
+ version: '1';
15
+ goal: string;
16
+ operationRef: string;
17
+ inputs?: WorkflowInputs;
18
+ expectedBody?: ExpectedBody;
19
+ }
20
+ export interface CompileRequest {
21
+ spec: unknown;
22
+ baseUrl: string;
23
+ profile: 'read-only' | 'test';
24
+ plan: WorkflowPlan;
25
+ }
26
+ export type CompileOperationRequest = Pick<CompileRequest, 'baseUrl' | 'profile' | 'plan'>;
27
+ export interface CompileResult {
28
+ status: CompileStatus;
29
+ plan: WorkflowPlan;
30
+ evidence: OperationEvidence;
31
+ missingInputs: string[];
32
+ workflow?: WorkflowJSON;
33
+ }
34
+ export interface ResponseReference {
35
+ fromStep: string;
36
+ field: string;
37
+ }
38
+ export interface SequenceStep {
39
+ id: string;
40
+ operationRef: string;
41
+ inputs?: Record<string, Primitive | ResponseReference | ExpectedBody>;
42
+ expectedBody?: ExpectedBody;
43
+ }
44
+ export interface SequencePlan {
45
+ version: '1';
46
+ goal: string;
47
+ steps: SequenceStep[];
48
+ }
49
+ export interface SequenceRequest {
50
+ spec: unknown;
51
+ baseUrl: string;
52
+ profile: CompileRequest['profile'];
53
+ plan: SequencePlan;
54
+ }
55
+ export interface SequenceEvidence extends OperationEvidence {
56
+ stepId: string;
57
+ }
58
+ export interface SequenceResult {
59
+ status: CompileStatus;
60
+ plan: SequencePlan;
61
+ evidence: SequenceEvidence[];
62
+ missingInputs: string[];
63
+ workflow?: WorkflowJSON;
64
+ }
65
+ export interface PreparedSequenceNode {
66
+ operation: Operation;
67
+ id: string;
68
+ url: string;
69
+ body?: string;
70
+ expectedBody: ExpectedBody;
71
+ }
72
+ export interface GenerateRequest extends Omit<CompileRequest, 'plan'> {
73
+ scenario: string;
74
+ model: BaseChatModel;
75
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { JsonObject, ScalarSchema } from '../types/openapi.js';
2
+ import type { Primitive } from '../types/workflow.js';
3
+ export declare function isObject(value: unknown): value is JsonObject;
4
+ export declare function looksLikeCredential(name: string): boolean;
5
+ export declare function checkPrimitive(value: unknown, schema: ScalarSchema, source: string): asserts value is Primitive;
6
+ export declare function originFrom(baseUrl: string): URL;
7
+ export declare function checkPlanSize(plan: unknown): void;
@@ -0,0 +1,44 @@
1
+ export function isObject(value) {
2
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
3
+ }
4
+ export function looksLikeCredential(name) {
5
+ return /(token|secret|password|authorization|api.?key)/i.test(name);
6
+ }
7
+ export function checkPrimitive(value, schema, source) {
8
+ if (schema.type === 'integer'
9
+ ? !Number.isSafeInteger(value)
10
+ : schema.type === 'number'
11
+ ? typeof value !== 'number' || !Number.isFinite(value)
12
+ : typeof value !== schema.type) {
13
+ throw new Error(source + ' must be ' + schema.type);
14
+ }
15
+ if (schema.enum && !schema.enum.includes(value))
16
+ throw new Error(source + ' is outside the OAS enum');
17
+ }
18
+ export function originFrom(baseUrl) {
19
+ if (typeof baseUrl !== 'string' || !URL.canParse(baseUrl)) {
20
+ throw new Error('baseUrl must be an absolute HTTP(S) origin');
21
+ }
22
+ const url = new URL(baseUrl);
23
+ if (!['http:', 'https:'].includes(url.protocol) ||
24
+ url.username ||
25
+ url.password ||
26
+ url.pathname !== '/' ||
27
+ url.search ||
28
+ url.hash) {
29
+ throw new Error('baseUrl must be an HTTP(S) origin without credentials, path, query, or fragment');
30
+ }
31
+ return url;
32
+ }
33
+ export function checkPlanSize(plan) {
34
+ let serialized;
35
+ try {
36
+ serialized = JSON.stringify(plan);
37
+ }
38
+ catch {
39
+ throw new Error('plan must be JSON-serializable');
40
+ }
41
+ if (!serialized || Buffer.byteLength(serialized) > 100_000) {
42
+ throw new Error('plan must be a JSON document under 100 KB');
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ export declare function workflowId(baseUrl: string, plan: unknown): string;
@@ -0,0 +1,8 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function workflowId(baseUrl, plan) {
3
+ return ('openapi-flow-' +
4
+ createHash('sha256')
5
+ .update(JSON.stringify({ baseUrl, plan }))
6
+ .digest('hex')
7
+ .slice(0, 16));
8
+ }
@@ -0,0 +1,2 @@
1
+ import type { SequenceRequest, SequenceResult } from '../types/workflow.js';
2
+ export declare function compileSequence({ spec, baseUrl, profile, plan, }: SequenceRequest): Promise<SequenceResult>;
@@ -0,0 +1,167 @@
1
+ import { node, trigger, validateWorkflow, workflow } from '@n8n/workflow-sdk';
2
+ import { operationFromDocument } from '../openapi/parse-spec.js';
3
+ import { validatedDocument } from '../openapi/validate-spec.js';
4
+ import { checkPlanSize, checkPrimitive, isObject, looksLikeCredential, originFrom, } from '../utils/validation.js';
5
+ import { workflowId } from '../utils/workflow-id.js';
6
+ import { assertionCode, isSafeMethod, makeBody } from './workflow-helpers.js';
7
+ import { sequenceUrl } from './sequence-url.js';
8
+ export async function compileSequence({ spec, baseUrl, profile, plan, }) {
9
+ const origin = originFrom(baseUrl);
10
+ if (profile !== 'read-only' && profile !== 'test')
11
+ throw new Error('profile must be read-only or test');
12
+ if (!isObject(plan) ||
13
+ plan.version !== '1' ||
14
+ typeof plan.goal !== 'string' ||
15
+ !plan.goal.trim() ||
16
+ plan.goal.length > 2_000 ||
17
+ !Array.isArray(plan.steps) ||
18
+ plan.steps.length < 1 ||
19
+ plan.steps.length > 5) {
20
+ throw new Error('plan must contain version 1, goal, and 1–5 steps');
21
+ }
22
+ checkPlanSize(plan);
23
+ const document = await validatedDocument(spec);
24
+ const previous = new Map();
25
+ const requiredOutputs = new Map();
26
+ const evidence = [];
27
+ const missingInputs = [];
28
+ const nodes = [];
29
+ let blocked = false;
30
+ for (const step of plan.steps) {
31
+ if (!isObject(step) ||
32
+ typeof step.id !== 'string' ||
33
+ !/^[A-Za-z][A-Za-z0-9_-]*$/.test(step.id) ||
34
+ previous.has(step.id) ||
35
+ typeof step.operationRef !== 'string') {
36
+ throw new Error('plan.steps must have unique id and operationRef values');
37
+ }
38
+ const operation = operationFromDocument(document, step.operationRef);
39
+ const inputs = step.inputs === undefined ? {} : step.inputs;
40
+ const expectedBody = step.expectedBody === undefined ? {} : step.expectedBody;
41
+ if (!isObject(inputs) || !isObject(expectedBody)) {
42
+ throw new Error('plan.steps[' + step.id + '] inputs and expectedBody must be objects');
43
+ }
44
+ for (const [key, value] of Object.entries(expectedBody)) {
45
+ if (looksLikeCredential(key)) {
46
+ throw new Error('plan.steps[' +
47
+ step.id +
48
+ '].expectedBody.' +
49
+ key +
50
+ ' looks like a credential');
51
+ }
52
+ if (!Object.hasOwn(operation.responseProperties, key)) {
53
+ throw new Error('plan.steps[' +
54
+ step.id +
55
+ '].expectedBody.' +
56
+ key +
57
+ ' is not in the OAS response schema');
58
+ }
59
+ checkPrimitive(value, operation.responseProperties[key], 'plan.steps[' + step.id + '].expectedBody.' + key);
60
+ }
61
+ const allowed = new Set(operation.parameters.map((parameter) => parameter.in + '.' + parameter.name));
62
+ if (operation.body)
63
+ allowed.add('body');
64
+ for (const key of Object.keys(inputs)) {
65
+ if (!allowed.has(key))
66
+ throw new Error('plan.steps[' +
67
+ step.id +
68
+ '].inputs.' +
69
+ key +
70
+ ' is not declared in OAS');
71
+ if (looksLikeCredential(key)) {
72
+ throw new Error('plan.steps[' +
73
+ step.id +
74
+ '].inputs.' +
75
+ key +
76
+ ' looks like a credential');
77
+ }
78
+ }
79
+ const stepMissing = [];
80
+ const url = sequenceUrl(operation, origin, inputs, previous, requiredOutputs, stepMissing);
81
+ const body = makeBody(operation, inputs, stepMissing);
82
+ missingInputs.push(...stepMissing.map((key) => step.id + '.' + key));
83
+ if (operation.effect === 'unknown' ||
84
+ (!isSafeMethod(operation.method) && operation.effect !== 'write') ||
85
+ (profile === 'read-only' &&
86
+ (!isSafeMethod(operation.method) || operation.effect !== 'read')))
87
+ blocked = true;
88
+ evidence.push({
89
+ stepId: step.id,
90
+ operationRef: operation.operationRef,
91
+ operationId: operation.operationId,
92
+ method: operation.method,
93
+ path: operation.path,
94
+ status: operation.status,
95
+ effect: operation.effect,
96
+ });
97
+ nodes.push({
98
+ operation,
99
+ id: step.id,
100
+ url,
101
+ body,
102
+ expectedBody: expectedBody,
103
+ });
104
+ previous.set(step.id, operation);
105
+ }
106
+ if (blocked)
107
+ return { status: 'blocked', plan, evidence, missingInputs };
108
+ if (missingInputs.length)
109
+ return { status: 'needs_input', plan, evidence, missingInputs };
110
+ let built = workflow(workflowId(baseUrl, plan), 'OpenAPI sequence').add(trigger({
111
+ type: 'n8n-nodes-base.manualTrigger',
112
+ version: 1,
113
+ config: { id: 'start', name: 'Start', position: [240, 300] },
114
+ }));
115
+ nodes.forEach(({ operation, id, url, body, expectedBody }, index) => {
116
+ const requiredFields = {};
117
+ for (const field of requiredOutputs.get(id) ?? []) {
118
+ requiredFields[field] = operation.responseProperties[field].type;
119
+ }
120
+ const request = node({
121
+ type: 'n8n-nodes-base.httpRequest',
122
+ version: 4.3,
123
+ config: {
124
+ id: 'request-' + id,
125
+ name: 'Request ' + id,
126
+ position: [480 + index * 480, 300],
127
+ parameters: {
128
+ method: operation.method,
129
+ url,
130
+ ...(body === undefined
131
+ ? {}
132
+ : { sendBody: true, specifyBody: 'json', jsonBody: body }),
133
+ options: {
134
+ response: {
135
+ response: { fullResponse: true, responseFormat: 'json' },
136
+ },
137
+ },
138
+ },
139
+ },
140
+ });
141
+ const assert = node({
142
+ type: 'n8n-nodes-base.code',
143
+ version: 2,
144
+ config: {
145
+ id: 'assert-' + id,
146
+ name: 'Assert ' + id,
147
+ position: [720 + index * 480, 300],
148
+ parameters: {
149
+ mode: 'runOnceForAllItems',
150
+ jsCode: assertionCode(operation.status, expectedBody, requiredFields),
151
+ },
152
+ },
153
+ });
154
+ built = built.to(request).to(assert);
155
+ });
156
+ const validation = validateWorkflow(built);
157
+ if (!validation.valid)
158
+ throw new Error('n8n SDK validation failed: ' +
159
+ validation.errors.map((error) => error.message).join('; '));
160
+ return {
161
+ status: 'complete',
162
+ plan,
163
+ evidence,
164
+ missingInputs,
165
+ workflow: built.toJSON(),
166
+ };
167
+ }
@@ -0,0 +1,4 @@
1
+ import type { Operation } from '../types/openapi.js';
2
+ import type { CompileOperationRequest, CompileRequest, CompileResult } from '../types/workflow.js';
3
+ export declare function compileWorkflow(request: CompileRequest): Promise<CompileResult>;
4
+ export declare function compileWorkflowFromOperation({ baseUrl, profile, plan }: CompileOperationRequest, operation: Operation): CompileResult;
@@ -0,0 +1,128 @@
1
+ import { node, trigger, validateWorkflow, workflow } from '@n8n/workflow-sdk';
2
+ import { operationFromSpec } from '../openapi/parse-spec.js';
3
+ import { checkPlanSize, isObject, looksLikeCredential, originFrom, checkPrimitive, } from '../utils/validation.js';
4
+ import { workflowId } from '../utils/workflow-id.js';
5
+ import { assertionCode, isSafeMethod, makeBody, makeUrl, } from './workflow-helpers.js';
6
+ export async function compileWorkflow(request) {
7
+ if (!isObject(request.plan) ||
8
+ typeof request.plan.operationRef !== 'string') {
9
+ throw new Error('plan must contain version 1, goal, and operationRef');
10
+ }
11
+ const operation = await operationFromSpec(request.spec, request.plan.operationRef);
12
+ return compileWorkflowFromOperation(request, operation);
13
+ }
14
+ export function compileWorkflowFromOperation({ baseUrl, profile, plan }, operation) {
15
+ const origin = originFrom(baseUrl);
16
+ if (profile !== 'read-only' && profile !== 'test')
17
+ throw new Error('profile must be read-only or test');
18
+ if (!isObject(plan) ||
19
+ plan.version !== '1' ||
20
+ typeof plan.goal !== 'string' ||
21
+ !plan.goal.trim() ||
22
+ plan.goal.length > 2_000 ||
23
+ typeof plan.operationRef !== 'string') {
24
+ throw new Error('plan must contain version 1, goal, and operationRef');
25
+ }
26
+ checkPlanSize(plan);
27
+ const evidence = {
28
+ operationRef: operation.operationRef,
29
+ operationId: operation.operationId,
30
+ method: operation.method,
31
+ path: operation.path,
32
+ status: operation.status,
33
+ effect: operation.effect,
34
+ };
35
+ const inputs = plan.inputs === undefined ? {} : plan.inputs;
36
+ if (!isObject(inputs))
37
+ throw new Error('plan.inputs must be an object');
38
+ const expectedBody = plan.expectedBody === undefined ? {} : plan.expectedBody;
39
+ if (!isObject(expectedBody))
40
+ throw new Error('plan.expectedBody must be an object');
41
+ for (const [key, value] of Object.entries(expectedBody)) {
42
+ if (looksLikeCredential(key)) {
43
+ throw new Error('plan.expectedBody.' + key + ' looks like a credential');
44
+ }
45
+ if (!Object.hasOwn(operation.responseProperties, key)) {
46
+ throw new Error('plan.expectedBody.' + key + ' is not in the OAS response schema');
47
+ }
48
+ checkPrimitive(value, operation.responseProperties[key], 'plan.expectedBody.' + key);
49
+ }
50
+ const allowed = new Set(operation.parameters.map((parameter) => parameter.in + '.' + parameter.name));
51
+ if (operation.body)
52
+ allowed.add('body');
53
+ for (const key of Object.keys(inputs)) {
54
+ if (!allowed.has(key))
55
+ throw new Error('plan.inputs.' + key + ' is not declared in the OAS operation');
56
+ if (looksLikeCredential(key)) {
57
+ throw new Error('plan.inputs.' +
58
+ key +
59
+ ' looks like a credential; v1 has no credential binding');
60
+ }
61
+ }
62
+ const missingInputs = [];
63
+ const url = makeUrl(operation, origin, inputs, missingInputs);
64
+ const body = makeBody(operation, inputs, missingInputs);
65
+ if (operation.effect === 'unknown' ||
66
+ (!isSafeMethod(operation.method) && operation.effect !== 'write') ||
67
+ (profile === 'read-only' &&
68
+ (!isSafeMethod(operation.method) || operation.effect !== 'read'))) {
69
+ return { status: 'blocked', plan, evidence, missingInputs };
70
+ }
71
+ if (missingInputs.length)
72
+ return { status: 'needs_input', plan, evidence, missingInputs };
73
+ const start = trigger({
74
+ type: 'n8n-nodes-base.manualTrigger',
75
+ version: 1,
76
+ config: { id: 'start', name: 'Start', position: [240, 300] },
77
+ });
78
+ const request = node({
79
+ type: 'n8n-nodes-base.httpRequest',
80
+ version: 4.3,
81
+ config: {
82
+ id: 'request',
83
+ name: operation.method + ' ' + operation.path,
84
+ position: [480, 300],
85
+ parameters: {
86
+ method: operation.method,
87
+ url,
88
+ ...(body === undefined
89
+ ? {}
90
+ : { sendBody: true, specifyBody: 'json', jsonBody: body }),
91
+ options: {
92
+ response: {
93
+ response: { fullResponse: true, responseFormat: 'json' },
94
+ },
95
+ },
96
+ },
97
+ },
98
+ });
99
+ const assert = node({
100
+ type: 'n8n-nodes-base.code',
101
+ version: 2,
102
+ config: {
103
+ id: 'assert',
104
+ name: 'Assert response',
105
+ position: [720, 300],
106
+ parameters: {
107
+ mode: 'runOnceForAllItems',
108
+ jsCode: assertionCode(operation.status, expectedBody),
109
+ },
110
+ },
111
+ });
112
+ const built = workflow(workflowId(baseUrl, plan), 'OpenAPI ' + operation.method + ' ' + operation.path)
113
+ .add(start)
114
+ .to(request)
115
+ .to(assert);
116
+ const validation = validateWorkflow(built);
117
+ if (!validation.valid) {
118
+ throw new Error('n8n SDK validation failed: ' +
119
+ validation.errors.map((error) => error.message).join('; '));
120
+ }
121
+ return {
122
+ status: 'complete',
123
+ plan,
124
+ evidence,
125
+ missingInputs,
126
+ workflow: built.toJSON(),
127
+ };
128
+ }
@@ -0,0 +1,3 @@
1
+ import type { Operation } from '../types/openapi.js';
2
+ import type { InputValues, PreviousOperations, RequiredOutputs } from '../types/workflow.js';
3
+ export declare function sequenceUrl(operation: Operation, origin: URL, inputs: InputValues, previous: PreviousOperations, requiredOutputs: RequiredOutputs, missing: string[]): string;
@@ -0,0 +1,81 @@
1
+ import { checkPrimitive, isObject, looksLikeCredential, } from '../utils/validation.js';
2
+ import { makeUrl } from './workflow-helpers.js';
3
+ export function sequenceUrl(operation, origin, inputs, previous, requiredOutputs, missing) {
4
+ let path = operation.path;
5
+ const references = new Map();
6
+ for (const parameter of operation.parameters) {
7
+ const key = parameter.in + '.' + parameter.name;
8
+ const value = inputs[key];
9
+ if (value === undefined) {
10
+ if (parameter.required)
11
+ missing.push(key);
12
+ continue;
13
+ }
14
+ if (isObject(value)) {
15
+ if (typeof value.fromStep !== 'string' ||
16
+ typeof value.field !== 'string' ||
17
+ Object.keys(value).length !== 2) {
18
+ throw new Error('plan.inputs.' + key + ' must have fromStep and field');
19
+ }
20
+ if (looksLikeCredential(value.field)) {
21
+ throw new Error('plan.inputs.' + key + ' references a credential-like response field');
22
+ }
23
+ const source = previous.get(value.fromStep);
24
+ if (!source || !Object.hasOwn(source.responseProperties, value.field)) {
25
+ throw new Error('plan.inputs.' + key + ' must reference a prior response field');
26
+ }
27
+ const sourceType = source.responseProperties[value.field].type;
28
+ if (sourceType !== parameter.schema.type &&
29
+ !(sourceType === 'integer' && parameter.schema.type === 'number')) {
30
+ throw new Error('plan.inputs.' + key + ' has a response type mismatch');
31
+ }
32
+ const required = requiredOutputs.get(value.fromStep) ?? new Set();
33
+ required.add(value.field);
34
+ requiredOutputs.set(value.fromStep, required);
35
+ references.set(key, '$node[' +
36
+ JSON.stringify('Request ' + value.fromStep) +
37
+ '].json.body[' +
38
+ JSON.stringify(value.field) +
39
+ ']');
40
+ }
41
+ else {
42
+ checkPrimitive(value, parameter.schema, 'plan.inputs.' + key);
43
+ if (parameter.in === 'path')
44
+ path = path.replace('{' + parameter.name + '}', encodeURIComponent(String(value)));
45
+ }
46
+ }
47
+ if (missing.length)
48
+ return '';
49
+ if (references.size === 0)
50
+ return makeUrl(operation, origin, inputs, []);
51
+ const absolute = origin.href.slice(0, -1) + path;
52
+ const parts = [];
53
+ let offset = 0;
54
+ for (const match of absolute.matchAll(/\{([A-Za-z][A-Za-z0-9_]*)\}/g)) {
55
+ const key = 'path.' + match[1];
56
+ const reference = references.get(key);
57
+ if (!reference)
58
+ throw new Error('plan.inputs.' + key + ' is required');
59
+ parts.push(JSON.stringify(absolute.slice(offset, match.index)), 'encodeURIComponent(' + reference + ')');
60
+ offset = match.index + match[0].length;
61
+ }
62
+ parts.push(JSON.stringify(absolute.slice(offset)));
63
+ let expression = parts.join(' + ');
64
+ let firstQuery = true;
65
+ for (const parameter of operation.parameters) {
66
+ if (parameter.in !== 'query' ||
67
+ inputs['query.' + parameter.name] === undefined)
68
+ continue;
69
+ const key = 'query.' + parameter.name;
70
+ const reference = references.get(key);
71
+ expression +=
72
+ ' + ' +
73
+ JSON.stringify((firstQuery ? '?' : '&') + encodeURIComponent(parameter.name) + '=') +
74
+ ' + ' +
75
+ (reference
76
+ ? 'encodeURIComponent(' + reference + ')'
77
+ : JSON.stringify(encodeURIComponent(String(inputs[key]))));
78
+ firstQuery = false;
79
+ }
80
+ return '={{ ' + expression + ' }}';
81
+ }
@@ -0,0 +1,6 @@
1
+ import type { Operation } from '../types/openapi.js';
2
+ import type { ExpectedBody, InputValues, RequiredFields } from '../types/workflow.js';
3
+ export declare function isSafeMethod(method: string): boolean;
4
+ export declare function makeUrl(operation: Operation, origin: URL, inputs: InputValues, missing: string[]): string;
5
+ export declare function makeBody(operation: Operation, inputs: InputValues, missing: string[]): string | undefined;
6
+ export declare function assertionCode(status: number, expectedBody: ExpectedBody, requiredFields?: RequiredFields): string;