@openapi-flow/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +27 -5
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/dist/arguments/list-api-request-media-types.d.ts +2 -0
  4. package/dist/arguments/list-api-request-media-types.js +8 -0
  5. package/dist/arguments/request-contract.d.ts +4 -0
  6. package/dist/arguments/request-contract.js +38 -0
  7. package/dist/arguments/request-media-type-priority.d.ts +2 -0
  8. package/dist/arguments/request-media-type-priority.js +8 -0
  9. package/dist/arguments/select-default-api-request-media-type.d.ts +3 -0
  10. package/dist/arguments/select-default-api-request-media-type.js +17 -0
  11. package/dist/arguments/validate-api-arguments.d.ts +2 -0
  12. package/dist/arguments/validate-api-arguments.js +140 -0
  13. package/dist/bindings/combine-binding-schemas.d.ts +4 -0
  14. package/dist/bindings/combine-binding-schemas.js +17 -0
  15. package/dist/bindings/create-native-array-item-schema.d.ts +4 -0
  16. package/dist/bindings/create-native-array-item-schema.js +16 -0
  17. package/dist/bindings/json-pointer.d.ts +3 -0
  18. package/dist/bindings/json-pointer.js +24 -0
  19. package/dist/bindings/materialize-api-arguments.d.ts +4 -0
  20. package/dist/bindings/materialize-api-arguments.js +54 -0
  21. package/dist/bindings/native-binding-schema.d.ts +3 -0
  22. package/dist/bindings/native-binding-schema.js +6 -0
  23. package/dist/bindings/project-array-item-schema.d.ts +5 -0
  24. package/dist/bindings/project-array-item-schema.js +67 -0
  25. package/dist/bindings/request-binding-schema.d.ts +2 -0
  26. package/dist/bindings/request-binding-schema.js +23 -0
  27. package/dist/bindings/request-container-kind.d.ts +3 -0
  28. package/dist/bindings/request-container-kind.js +17 -0
  29. package/dist/bindings/resources/clone-schema-resource.d.ts +3 -0
  30. package/dist/bindings/resources/clone-schema-resource.js +38 -0
  31. package/dist/bindings/resources/create-schema-resource-context.d.ts +4 -0
  32. package/dist/bindings/resources/create-schema-resource-context.js +130 -0
  33. package/dist/bindings/response-binding-schema.d.ts +3 -0
  34. package/dist/bindings/response-binding-schema.js +21 -0
  35. package/dist/bindings/schema-allows-container.d.ts +3 -0
  36. package/dist/bindings/schema-allows-container.js +20 -0
  37. package/dist/bindings/schema-at-pointer.d.ts +4 -0
  38. package/dist/bindings/schema-at-pointer.js +89 -0
  39. package/dist/bindings/validate-api-binding-assignments.d.ts +2 -0
  40. package/dist/bindings/validate-api-binding-assignments.js +81 -0
  41. package/dist/bindings/validate-api-binding-gaps.d.ts +2 -0
  42. package/dist/bindings/validate-api-binding-gaps.js +10 -0
  43. package/dist/bindings/validate-api-binding-plan.d.ts +3 -0
  44. package/dist/bindings/validate-api-binding-plan.js +11 -0
  45. package/dist/bindings/validate-native-output-contracts.d.ts +3 -0
  46. package/dist/bindings/validate-native-output-contracts.js +16 -0
  47. package/dist/internal-api.d.ts +30 -0
  48. package/dist/internal-api.js +25 -0
  49. package/dist/openapi/catalog/create-api-catalog.d.ts +2 -0
  50. package/dist/openapi/catalog/create-api-catalog.js +34 -0
  51. package/dist/openapi/catalog/list-api-operations.d.ts +2 -0
  52. package/dist/openapi/catalog/list-api-operations.js +3 -0
  53. package/dist/openapi/common/check-oas-value.d.ts +3 -0
  54. package/dist/openapi/common/check-oas-value.js +6 -0
  55. package/dist/openapi/common/check-schema-value.d.ts +2 -0
  56. package/dist/openapi/common/check-schema-value.js +12 -0
  57. package/dist/openapi/common/create-oas-value-validator.d.ts +3 -0
  58. package/dist/openapi/common/create-oas-value-validator.js +39 -0
  59. package/dist/openapi/common/normalize-oas-value-schema.d.ts +4 -0
  60. package/dist/openapi/common/normalize-oas-value-schema.js +50 -0
  61. package/dist/openapi/common/operation-metadata.d.ts +2 -0
  62. package/dist/openapi/common/operation-metadata.js +25 -0
  63. package/dist/openapi/common/operation-reference.d.ts +3 -0
  64. package/dist/openapi/{operation-reference.js → common/operation-reference.js} +1 -4
  65. package/dist/openapi/common/parse-spec-utils.d.ts +3 -0
  66. package/dist/openapi/common/parse-spec-utils.js +12 -0
  67. package/dist/openapi/common/resolve-openapi-security.d.ts +3 -0
  68. package/dist/openapi/common/resolve-openapi-security.js +28 -0
  69. package/dist/openapi/common/unsupported-operation-error.d.ts +4 -0
  70. package/dist/openapi/common/unsupported-operation-error.js +8 -0
  71. package/dist/openapi/common/validate-spec.d.ts +3 -0
  72. package/dist/openapi/{validate-spec.js → common/validate-spec.js} +2 -1
  73. package/dist/openapi/inbound/callback/parse-callback-operations.d.ts +3 -0
  74. package/dist/openapi/inbound/callback/parse-callback-operations.js +31 -0
  75. package/dist/openapi/inbound/inbound-path-item-operations.d.ts +4 -0
  76. package/dist/openapi/inbound/inbound-path-item-operations.js +31 -0
  77. package/dist/openapi/inbound/list-inbound-operations.d.ts +4 -0
  78. package/dist/openapi/inbound/list-inbound-operations.js +13 -0
  79. package/dist/openapi/inbound/webhook/parse-webhook-operations.d.ts +3 -0
  80. package/dist/openapi/inbound/webhook/parse-webhook-operations.js +13 -0
  81. package/dist/openapi/request/create-operation-catalog.d.ts +2 -0
  82. package/dist/openapi/request/create-operation-catalog.js +24 -0
  83. package/dist/openapi/request/encode-form-field.d.ts +2 -0
  84. package/dist/openapi/request/encode-form-field.js +6 -0
  85. package/dist/openapi/request/map-http-operation-contract.d.ts +4 -0
  86. package/dist/openapi/request/map-http-operation-contract.js +21 -0
  87. package/dist/openapi/request/operation-mapping.d.ts +3 -0
  88. package/dist/openapi/request/operation-mapping.js +10 -0
  89. package/dist/openapi/request/parse-operation-responses.d.ts +3 -0
  90. package/dist/openapi/request/parse-operation-responses.js +30 -0
  91. package/dist/openapi/request/parse-request-body.d.ts +3 -0
  92. package/dist/openapi/request/parse-request-body.js +29 -0
  93. package/dist/openapi/request/parse-request-operations.d.ts +7 -0
  94. package/dist/openapi/request/parse-request-operations.js +50 -0
  95. package/dist/openapi/request/parse-request-parameters.d.ts +3 -0
  96. package/dist/openapi/request/parse-request-parameters.js +93 -0
  97. package/dist/openapi/request/parse-security.d.ts +3 -0
  98. package/dist/openapi/request/parse-security.js +31 -0
  99. package/dist/openapi/request/request-operation-reference.d.ts +1 -0
  100. package/dist/openapi/request/request-operation-reference.js +4 -0
  101. package/dist/openapi/request/response-contract.d.ts +6 -0
  102. package/dist/openapi/request/response-contract.js +32 -0
  103. package/dist/openapi/request/scalar-response-schema.d.ts +2 -0
  104. package/dist/openapi/request/scalar-response-schema.js +20 -0
  105. package/dist/openapi/request/serialize-form-body.d.ts +3 -0
  106. package/dist/openapi/request/serialize-form-body.js +89 -0
  107. package/dist/openapi/request/serialize-parameter.d.ts +5 -0
  108. package/dist/openapi/request/serialize-parameter.js +145 -0
  109. package/dist/openapi/resolve-api-operations.d.ts +3 -0
  110. package/dist/openapi/resolve-api-operations.js +84 -0
  111. package/dist/openapi/response/api-response-validation-value.d.ts +3 -0
  112. package/dist/openapi/response/api-response-validation-value.js +21 -0
  113. package/dist/openapi/response/create-api-response-schema.d.ts +4 -0
  114. package/dist/openapi/response/create-api-response-schema.js +114 -0
  115. package/dist/openapi/response/create-response-array-item-schema.d.ts +4 -0
  116. package/dist/openapi/response/create-response-array-item-schema.js +12 -0
  117. package/dist/openapi/response/create-response-value-schema.d.ts +4 -0
  118. package/dist/openapi/response/create-response-value-schema.js +8 -0
  119. package/dist/openapi/response/list-api-response-pointers.d.ts +4 -0
  120. package/dist/openapi/response/list-api-response-pointers.js +30 -0
  121. package/dist/openapi/response/list-declared-schema-pointers.d.ts +4 -0
  122. package/dist/openapi/response/list-declared-schema-pointers.js +91 -0
  123. package/dist/public-api.d.ts +49 -7
  124. package/dist/public-api.js +35 -5
  125. package/dist/runtime-api.d.ts +6 -0
  126. package/dist/runtime-api.js +7 -0
  127. package/dist/schemas/create-api-argument-generation-contract.d.ts +4 -0
  128. package/dist/schemas/create-api-argument-generation-contract.js +72 -0
  129. package/dist/schemas/create-api-arguments-schema.d.ts +3 -0
  130. package/dist/schemas/create-api-arguments-schema.js +4 -0
  131. package/dist/schemas/create-api-request-schema.d.ts +4 -0
  132. package/dist/schemas/create-api-request-schema.js +50 -0
  133. package/dist/schemas/create-oas-value-schema.d.ts +6 -0
  134. package/dist/schemas/create-oas-value-schema.js +25 -0
  135. package/dist/schemas/operation-plan-schema.d.ts +8 -0
  136. package/dist/schemas/operation-plan-schema.js +99 -0
  137. package/dist/schemas/project-literal-array-schema.d.ts +4 -0
  138. package/dist/schemas/project-literal-array-schema.js +55 -0
  139. package/dist/schemas/project-literal-input-schema.d.ts +3 -0
  140. package/dist/schemas/project-literal-input-schema.js +129 -0
  141. package/dist/types/api-argument-generation.d.ts +13 -0
  142. package/dist/types/api-arguments.d.ts +42 -0
  143. package/dist/types/api-arguments.js +1 -0
  144. package/dist/types/api-bindings.d.ts +34 -0
  145. package/dist/types/api-bindings.js +1 -0
  146. package/dist/types/api-catalog.d.ts +34 -0
  147. package/dist/types/api-catalog.js +1 -0
  148. package/dist/types/api-operation.d.ts +26 -0
  149. package/dist/types/api-operation.js +1 -0
  150. package/dist/types/api-response-pointers.d.ts +20 -0
  151. package/dist/types/api-response-pointers.js +1 -0
  152. package/dist/types/api-response.d.ts +14 -0
  153. package/dist/types/api-response.js +1 -0
  154. package/dist/types/array-projection.d.ts +5 -0
  155. package/dist/types/array-projection.js +1 -0
  156. package/dist/types/catalog.d.ts +17 -0
  157. package/dist/types/catalog.js +1 -0
  158. package/dist/types/inbound.d.ts +18 -0
  159. package/dist/types/inbound.js +1 -0
  160. package/dist/types/legacy-plan.d.ts +47 -0
  161. package/dist/types/legacy-plan.js +1 -0
  162. package/dist/types/node-output.d.ts +11 -0
  163. package/dist/types/node-output.js +1 -0
  164. package/dist/types/oas-value-validation.d.ts +8 -0
  165. package/dist/types/oas-value-validation.js +1 -0
  166. package/dist/types/openapi.d.ts +7 -23
  167. package/dist/types/request.d.ts +41 -0
  168. package/dist/types/request.js +1 -0
  169. package/dist/types/reviewable-workflow.d.ts +53 -0
  170. package/dist/types/reviewable-workflow.js +1 -0
  171. package/dist/types/schema-dialect.d.ts +1 -0
  172. package/dist/types/schema-dialect.js +1 -0
  173. package/dist/types/schema-json.d.ts +4 -0
  174. package/dist/types/schema-json.js +1 -0
  175. package/dist/types/schema-resource.d.ts +12 -0
  176. package/dist/types/schema-resource.js +1 -0
  177. package/dist/types/security.d.ts +17 -0
  178. package/dist/types/security.js +1 -0
  179. package/dist/types/workflow-capability-planning.d.ts +10 -0
  180. package/dist/types/workflow-capability-planning.js +1 -0
  181. package/dist/types/workflow-plan.d.ts +104 -0
  182. package/dist/types/workflow-plan.js +1 -0
  183. package/dist/utils/is-object.d.ts +2 -0
  184. package/dist/utils/is-object.js +3 -0
  185. package/dist/utils/json-values-equal.d.ts +2 -0
  186. package/dist/utils/json-values-equal.js +16 -0
  187. package/dist/workflow/assert-api-call-identities.d.ts +1 -0
  188. package/dist/workflow/assert-api-call-identities.js +6 -0
  189. package/dist/workflow/create-native-output-contracts.d.ts +3 -0
  190. package/dist/workflow/create-native-output-contracts.js +23 -0
  191. package/dist/workflow/create-reviewable-node-contracts.d.ts +3 -0
  192. package/dist/workflow/create-reviewable-node-contracts.js +36 -0
  193. package/dist/workflow/create-reviewable-workflow-graph-plan.d.ts +2 -0
  194. package/dist/workflow/create-reviewable-workflow-graph-plan.js +29 -0
  195. package/dist/workflow/create-workflow-graph-plan.d.ts +2 -0
  196. package/dist/workflow/create-workflow-graph-plan.js +17 -0
  197. package/dist/workflow/create-workflow-node-contracts.d.ts +2 -0
  198. package/dist/workflow/create-workflow-node-contracts.js +50 -0
  199. package/dist/workflow/create-workflow-planning-capabilities.d.ts +3 -0
  200. package/dist/workflow/create-workflow-planning-capabilities.js +20 -0
  201. package/dist/workflow/parse-native-node-parameters.d.ts +3 -0
  202. package/dist/workflow/parse-native-node-parameters.js +7 -0
  203. package/dist/workflow/validate-exclusive-output-rejoin.d.ts +3 -0
  204. package/dist/workflow/validate-exclusive-output-rejoin.js +26 -0
  205. package/dist/workflow/validate-native-api-response-references.d.ts +2 -0
  206. package/dist/workflow/validate-native-api-response-references.js +26 -0
  207. package/dist/workflow/validate-reviewable-workflow-graph-plan.d.ts +2 -0
  208. package/dist/workflow/validate-reviewable-workflow-graph-plan.js +98 -0
  209. package/dist/workflow/validate-workflow-edges.d.ts +2 -0
  210. package/dist/workflow/validate-workflow-edges.js +19 -0
  211. package/dist/workflow/validate-workflow-graph-plan.d.ts +3 -0
  212. package/dist/workflow/validate-workflow-graph-plan.js +41 -0
  213. package/dist/workflow/validate-workflow-topology.d.ts +2 -0
  214. package/dist/workflow/validate-workflow-topology.js +105 -0
  215. package/package.json +19 -25
  216. package/dist/openapi/operation-reference.d.ts +0 -3
  217. package/dist/openapi/parse-spec-utils.d.ts +0 -5
  218. package/dist/openapi/parse-spec-utils.js +0 -52
  219. package/dist/openapi/parse-spec.d.ts +0 -5
  220. package/dist/openapi/parse-spec.js +0 -182
  221. package/dist/openapi/validate-spec.d.ts +0 -3
  222. package/dist/planning/generate-workflow.d.ts +0 -2
  223. package/dist/planning/generate-workflow.js +0 -126
  224. package/dist/schemas/planning-schemas.d.ts +0 -62
  225. package/dist/schemas/planning-schemas.js +0 -48
  226. package/dist/types/workflow.d.ts +0 -75
  227. package/dist/utils/validation.d.ts +0 -7
  228. package/dist/utils/validation.js +0 -44
  229. package/dist/utils/workflow-id.d.ts +0 -1
  230. package/dist/utils/workflow-id.js +0 -8
  231. package/dist/workflow/compile-sequence.d.ts +0 -2
  232. package/dist/workflow/compile-sequence.js +0 -167
  233. package/dist/workflow/compile-workflow.d.ts +0 -4
  234. package/dist/workflow/compile-workflow.js +0 -128
  235. package/dist/workflow/sequence-url.d.ts +0 -3
  236. package/dist/workflow/sequence-url.js +0 -81
  237. package/dist/workflow/workflow-helpers.d.ts +0 -6
  238. package/dist/workflow/workflow-helpers.js +0 -81
  239. /package/dist/types/{workflow.js → api-argument-generation.js} +0 -0
@@ -0,0 +1,31 @@
1
+ import { dereferencedObject, object } from '../common/parse-spec-utils.js';
2
+ import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
3
+ export function parseOperationSecurity(spec, operation, operationRef) {
4
+ const security = operation.security === undefined ? spec.security : operation.security;
5
+ if (security === undefined)
6
+ return undefined;
7
+ if (!Array.isArray(security)) {
8
+ throw new Error(`operationRef ${operationRef}.security must be an array`);
9
+ }
10
+ if (security.length === 0)
11
+ return undefined;
12
+ if (security.length !== 1) {
13
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.security has unsupported alternatives`);
14
+ }
15
+ const requirement = object(security[0], `operationRef ${operationRef}.security[0]`);
16
+ const names = Object.keys(requirement);
17
+ if (names.length === 0)
18
+ return undefined;
19
+ if (names.length !== 1) {
20
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.security has unsupported combined schemes`);
21
+ }
22
+ const schemeName = names[0];
23
+ const schemes = object(object(spec.components, `operationRef ${operationRef}.components`)
24
+ .securitySchemes, `operationRef ${operationRef}.components.securitySchemes`);
25
+ const scheme = dereferencedObject(schemes[schemeName], `operationRef ${operationRef}.components.securitySchemes.${schemeName}`);
26
+ if (scheme.type !== 'http' ||
27
+ String(scheme.scheme).toLowerCase() !== 'bearer') {
28
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.security.${schemeName} cannot be mapped to a supported n8n credential`);
29
+ }
30
+ return { schemeName, credentialType: 'httpBearerAuth' };
31
+ }
@@ -0,0 +1 @@
1
+ export declare function requestOperationReference(path: string, key: string): string;
@@ -0,0 +1,4 @@
1
+ import { pointerSegment } from '../common/operation-reference.js';
2
+ export function requestOperationReference(path, key) {
3
+ return '#/paths/' + pointerSegment(path) + '/' + key;
4
+ }
@@ -0,0 +1,6 @@
1
+ import type { JsonObject } from '../../types/openapi.js';
2
+ import type { Operation } from '../../types/request.js';
3
+ export declare function responseFieldNames(operation: Operation): string[];
4
+ export declare function responseFieldSchemas(operation: Operation, field: string): (JsonObject | boolean)[];
5
+ export declare function validateResponseField(operation: Operation, field: string, value: unknown, source: string): void;
6
+ export declare function responseFieldType(operation: Operation, field: string): string;
@@ -0,0 +1,32 @@
1
+ import { checkSchemaValue } from '../common/check-schema-value.js';
2
+ import { scalarResponseSchema } from './scalar-response-schema.js';
3
+ export function responseFieldNames(operation) {
4
+ return [...new Set(Object.values(operation.responses).flatMap(Object.keys))];
5
+ }
6
+ export function responseFieldSchemas(operation, field) {
7
+ return Object.values(operation.responses).flatMap((properties) => Object.hasOwn(properties, field) ? [properties[field]] : []);
8
+ }
9
+ export function validateResponseField(operation, field, value, source) {
10
+ const schemas = responseFieldSchemas(operation, field);
11
+ if (schemas.length === 0)
12
+ throw new Error(source + ' is not in the OAS response schema');
13
+ for (const schema of schemas) {
14
+ try {
15
+ checkSchemaValue(value, schema, source);
16
+ return;
17
+ }
18
+ catch {
19
+ // Another declared response may define this field differently.
20
+ }
21
+ }
22
+ throw new Error(source + ' does not match the OAS schema');
23
+ }
24
+ export function responseFieldType(operation, field) {
25
+ const schemas = responseFieldSchemas(operation, field);
26
+ if (schemas.length === 0)
27
+ throw new Error('prior response field ' + field + ' is not in the OAS');
28
+ const types = new Set(schemas.map((schema) => scalarResponseSchema(schema, 'operationRef ' + operation.operationRef + ' response field ' + field, operation.operationRef).type));
29
+ if (types.size !== 1)
30
+ throw new Error('prior response field ' + field + ' has ambiguous OAS types');
31
+ return [...types][0];
32
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScalarSchema } from '../../types/openapi.js';
2
+ export declare function scalarResponseSchema(value: unknown, source: string, operationRef: string): ScalarSchema;
@@ -0,0 +1,20 @@
1
+ import { dereferencedObject } from '../common/parse-spec-utils.js';
2
+ import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
3
+ export function scalarResponseSchema(value, source, operationRef) {
4
+ const schema = dereferencedObject(value, source);
5
+ if (!['string', 'integer', 'number', 'boolean'].includes(String(schema.type))) {
6
+ throw new UnsupportedOperationError(operationRef, `${source} must be a primitive schema`);
7
+ }
8
+ if (schema.enum !== undefined &&
9
+ (!Array.isArray(schema.enum) ||
10
+ schema.enum.length === 0 ||
11
+ schema.enum.some((entry) => typeof entry !== 'string' &&
12
+ typeof entry !== 'number' &&
13
+ typeof entry !== 'boolean'))) {
14
+ throw new UnsupportedOperationError(operationRef, `${source}.enum must contain primitive values`);
15
+ }
16
+ return {
17
+ type: schema.type,
18
+ enum: schema.enum,
19
+ };
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { JsonObject } from '../../types/openapi.js';
2
+ import type { OperationBodyMedia } from '../../types/request.js';
3
+ export declare function serializeFormBody(operationRef: string, media: OperationBodyMedia, value: JsonObject): string;
@@ -0,0 +1,89 @@
1
+ import { serializeQueryParameter } from './serialize-parameter.js';
2
+ import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
3
+ import { dereferencedObject } from '../common/parse-spec-utils.js';
4
+ import { isObject } from '../../utils/is-object.js';
5
+ import { encodeFormField } from './encode-form-field.js';
6
+ function defaultFieldMediaType(schema) {
7
+ const type = schema.type === 'array' && isObject(schema.items)
8
+ ? schema.items.type
9
+ : schema.type;
10
+ if (type === 'object' || type === 'array')
11
+ return 'application/json';
12
+ if (['string', 'number', 'integer', 'boolean'].includes(String(type)))
13
+ return 'text/plain';
14
+ return undefined;
15
+ }
16
+ function encodedField(name, field, schema, contentType, operationRef) {
17
+ if (contentType !== 'application/json' && contentType !== 'text/plain')
18
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} contentType ${contentType} has no form mapping`);
19
+ const values = Array.isArray(field) ? field : [field];
20
+ if (values.length === 0)
21
+ return [];
22
+ const form = [];
23
+ for (const value of values) {
24
+ if (contentType === 'text/plain' && typeof value === 'object')
25
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} cannot use text/plain for a complex value`);
26
+ if (contentType === 'application/json') {
27
+ form.push(encodeFormField(name) + '=' + encodeFormField(JSON.stringify(value)));
28
+ }
29
+ else {
30
+ form.push(encodeFormField(name) + '=' + encodeFormField(String(value)));
31
+ }
32
+ }
33
+ if (schema.contentEncoding !== undefined)
34
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} contentEncoding requires explicit binary serialization`);
35
+ return form;
36
+ }
37
+ function formFieldSchema(operationRef, media, name) {
38
+ const source = `operationRef ${operationRef}.requestBody.schema.properties.${name}`;
39
+ const declared = media.properties[name];
40
+ if (declared !== undefined) {
41
+ if (typeof declared === 'boolean')
42
+ throw new UnsupportedOperationError(operationRef, `${source} does not define a form encoding type`);
43
+ return dereferencedObject(declared, source);
44
+ }
45
+ if (isObject(media.schema) && media.schema.additionalProperties !== undefined)
46
+ if (typeof media.schema.additionalProperties !== 'boolean')
47
+ return dereferencedObject(media.schema.additionalProperties, `operationRef ${operationRef}.requestBody.schema.additionalProperties`);
48
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} has no schema to determine form encoding`);
49
+ }
50
+ export function serializeFormBody(operationRef, media, value) {
51
+ const parts = [];
52
+ for (const [name, field] of Object.entries(value)) {
53
+ const schema = formFieldSchema(operationRef, media, name);
54
+ const rawEncoding = media.encoding?.[name];
55
+ const encoding = rawEncoding === undefined
56
+ ? undefined
57
+ : dereferencedObject(rawEncoding, `operationRef ${operationRef}.requestBody.encoding.${name}`);
58
+ if (encoding?.encoding !== undefined ||
59
+ encoding?.prefixEncoding !== undefined ||
60
+ encoding?.itemEncoding !== undefined)
61
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} needs nested encoding`);
62
+ const usesStyle = encoding?.style !== undefined ||
63
+ encoding?.explode !== undefined ||
64
+ encoding?.allowReserved !== undefined;
65
+ if (usesStyle) {
66
+ const style = typeof encoding?.style === 'string' ? encoding.style : 'form';
67
+ const parameter = {
68
+ name,
69
+ in: 'query',
70
+ required: false,
71
+ schema,
72
+ style,
73
+ explode: typeof encoding?.explode === 'boolean'
74
+ ? encoding.explode
75
+ : style === 'form',
76
+ allowReserved: encoding?.allowReserved === true,
77
+ };
78
+ parts.push(...serializeQueryParameter(parameter, operationRef, field));
79
+ continue;
80
+ }
81
+ const contentType = typeof encoding?.contentType === 'string'
82
+ ? encoding.contentType
83
+ : defaultFieldMediaType(schema);
84
+ if (contentType === undefined)
85
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} has no form content mapping`);
86
+ parts.push(...encodedField(name, field, schema, contentType, operationRef));
87
+ }
88
+ return parts.join('&');
89
+ }
@@ -0,0 +1,5 @@
1
+ import type { OperationParameter } from '../../types/request.js';
2
+ export declare function serializePathParameter(parameter: OperationParameter, operationRef: string, value: unknown): string;
3
+ export declare function serializeQueryParameter(parameter: OperationParameter, operationRef: string, value: unknown): string[];
4
+ export declare function serializeHeaderParameter(parameter: OperationParameter, operationRef: string, value: unknown): string;
5
+ export declare function serializeCookieParameter(parameter: OperationParameter, operationRef: string, value: unknown): string[];
@@ -0,0 +1,145 @@
1
+ import { isObject } from '../../utils/is-object.js';
2
+ import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
3
+ function unsupported(parameter, operationRef, reason) {
4
+ throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef} parameter ${parameter.in}.${parameter.name} ${reason}`);
5
+ }
6
+ function flatValue(parameter, operationRef, value) {
7
+ if (typeof value === 'string' ||
8
+ typeof value === 'boolean' ||
9
+ (typeof value === 'number' && Number.isFinite(value)))
10
+ return value;
11
+ return unsupported(parameter, operationRef, 'requires a scalar value');
12
+ }
13
+ function encoded(value, allowReserved) {
14
+ const escaped = encodeURIComponent(String(value)).replace(/[!'()*]/g, (character) => '%' + character.charCodeAt(0).toString(16).toUpperCase());
15
+ if (!allowReserved)
16
+ return escaped;
17
+ return escaped.replace(/%(3A|2F|3F|23|5B|5D|40|21|24|26|27|28|29|2A|2B|2C|3B|3D)/gi, (match) => String.fromCharCode(Number.parseInt(match.slice(1), 16)));
18
+ }
19
+ function entries(parameter, operationRef, value, encode) {
20
+ if (Array.isArray(value))
21
+ return value.map((item) => encode(flatValue(parameter, operationRef, item)));
22
+ if (isObject(value))
23
+ return Object.entries(value).flatMap(([key, item]) => [
24
+ encode(key),
25
+ encode(flatValue(parameter, operationRef, item)),
26
+ ]);
27
+ return [encode(flatValue(parameter, operationRef, value))];
28
+ }
29
+ function contentValue(parameter, operationRef, value) {
30
+ if (parameter.contentMediaType !== 'application/json')
31
+ return unsupported(parameter, operationRef, 'has no content mapping');
32
+ const result = JSON.stringify(value);
33
+ if (result === undefined)
34
+ return unsupported(parameter, operationRef, 'cannot be JSON serialized');
35
+ return result;
36
+ }
37
+ export function serializePathParameter(parameter, operationRef, value) {
38
+ if (parameter.contentMediaType) {
39
+ return encoded(contentValue(parameter, operationRef, value), false);
40
+ }
41
+ const encode = (scalar) => encoded(scalar, parameter.allowReserved);
42
+ const parts = entries(parameter, operationRef, value, encode);
43
+ const object = isObject(value);
44
+ if (parameter.style === 'simple') {
45
+ if (!object || !parameter.explode)
46
+ return parts.join(',');
47
+ return parts
48
+ .flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
49
+ .join(',');
50
+ }
51
+ if (parameter.style === 'label') {
52
+ if (!object || !parameter.explode)
53
+ return '.' + parts.join(parameter.explode ? '.' : ',');
54
+ return ('.' +
55
+ parts
56
+ .flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
57
+ .join('.'));
58
+ }
59
+ if (parameter.style === 'matrix') {
60
+ const name = encoded(parameter.name, false);
61
+ if (!parameter.explode)
62
+ return ';' + name + '=' + parts.join(',');
63
+ if (object)
64
+ return parts
65
+ .flatMap((item, index) => index % 2 === 0 ? [';' + item + '=' + parts[index + 1]] : [])
66
+ .join('');
67
+ return parts.map((item) => ';' + name + '=' + item).join('');
68
+ }
69
+ return unsupported(parameter, operationRef, `style ${parameter.style} is not a path style`);
70
+ }
71
+ export function serializeQueryParameter(parameter, operationRef, value) {
72
+ const name = encoded(parameter.name, false);
73
+ if (parameter.contentMediaType)
74
+ return [
75
+ name + '=' + encoded(contentValue(parameter, operationRef, value), false),
76
+ ];
77
+ const encode = (scalar) => encoded(scalar, parameter.allowReserved);
78
+ const parts = entries(parameter, operationRef, value, encode);
79
+ const object = isObject(value);
80
+ if (parameter.style === 'deepObject') {
81
+ if (!object)
82
+ return unsupported(parameter, operationRef, 'deepObject requires an object');
83
+ return parts.flatMap((item, index) => index % 2 === 0 ? [name + '%5B' + item + '%5D=' + parts[index + 1]] : []);
84
+ }
85
+ if (parameter.style === 'form') {
86
+ if (!parameter.explode)
87
+ return [name + '=' + parts.join(',')];
88
+ if (object)
89
+ return parts.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : []);
90
+ return parts.map((item) => name + '=' + item);
91
+ }
92
+ if ((parameter.style === 'spaceDelimited' ||
93
+ parameter.style === 'pipeDelimited') &&
94
+ !parameter.explode &&
95
+ (object || Array.isArray(value))) {
96
+ return [
97
+ name +
98
+ '=' +
99
+ parts.join(parameter.style === 'spaceDelimited' ? '%20' : '%7C'),
100
+ ];
101
+ }
102
+ return unsupported(parameter, operationRef, `style ${parameter.style} is not mapped for query`);
103
+ }
104
+ export function serializeHeaderParameter(parameter, operationRef, value) {
105
+ if (parameter.contentMediaType)
106
+ return contentValue(parameter, operationRef, value);
107
+ if (parameter.style !== 'simple')
108
+ return unsupported(parameter, operationRef, `style ${parameter.style} is not a header style`);
109
+ const parts = entries(parameter, operationRef, value, String);
110
+ const result = isObject(value) && parameter.explode
111
+ ? parts
112
+ .flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
113
+ .join(',')
114
+ : parts.join(',');
115
+ if (/[\r\n]/.test(result))
116
+ throw new Error(`plan.inputs.header.${parameter.name} must not contain a newline`);
117
+ return result;
118
+ }
119
+ export function serializeCookieParameter(parameter, operationRef, value) {
120
+ if (parameter.contentMediaType)
121
+ return unsupported(parameter, operationRef, 'cookie content needs a declared escaping strategy');
122
+ if (parameter.style !== 'form' && parameter.style !== 'cookie')
123
+ return unsupported(parameter, operationRef, `style ${parameter.style} is not a cookie style`);
124
+ const encode = parameter.style === 'cookie'
125
+ ? (scalar) => String(scalar)
126
+ : (scalar) => encoded(scalar, parameter.allowReserved);
127
+ const parts = entries(parameter, operationRef, value, encode);
128
+ const name = encode(parameter.name);
129
+ if (!parameter.explode && (isObject(value) || Array.isArray(value)))
130
+ return unsupported(parameter, operationRef, 'explode:false produces invalid Cookie syntax');
131
+ const result = isObject(value)
132
+ ? parts.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
133
+ : parts.map((item) => name + '=' + item);
134
+ if (result.some((part) => [...part].some((character) => {
135
+ const code = character.charCodeAt(0);
136
+ return (code <= 32 ||
137
+ code === 34 ||
138
+ code === 44 ||
139
+ code === 59 ||
140
+ code === 92 ||
141
+ code === 127);
142
+ })))
143
+ return unsupported(parameter, operationRef, 'value is not valid Cookie syntax');
144
+ return result;
145
+ }
@@ -0,0 +1,3 @@
1
+ import type { ApiCatalog, ApiOperationKey } from '../types/api-catalog.js';
2
+ import type { ApiOperationContract } from '../types/api-operation.js';
3
+ export declare function resolveApiOperations(catalog: ApiCatalog, keys: ApiOperationKey[]): Promise<ApiOperationContract[]>;
@@ -0,0 +1,84 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { object } from './common/parse-spec-utils.js';
3
+ import { validateAndResolveOpenApiDocument } from './common/validate-spec.js';
4
+ import { selectedOperationSource } from './request/parse-request-operations.js';
5
+ export async function resolveApiOperations(catalog, keys) {
6
+ if (!Array.isArray(keys))
7
+ throw new Error('operation keys must be an array');
8
+ const documents = new Map();
9
+ const contracts = [];
10
+ for (const key of keys) {
11
+ if (!key ||
12
+ typeof key.documentId !== 'string' ||
13
+ !key.documentId.trim() ||
14
+ typeof key.snapshotId !== 'string' ||
15
+ !key.snapshotId ||
16
+ typeof key.operationRef !== 'string' ||
17
+ !key.operationRef)
18
+ throw new Error('operation keys require documentId, snapshotId and operationRef');
19
+ const source = catalog.documents.find((item) => item.id === key.documentId);
20
+ if (!source)
21
+ throw new Error(`operation.documentId ${key.documentId} is not in catalog`);
22
+ if (source.snapshotId !== key.snapshotId)
23
+ throw new Error(`operation.snapshotId does not match sources[${source.id}]`);
24
+ if (createHash('sha256').update(JSON.stringify(source.spec)).digest('hex') !==
25
+ source.snapshotId)
26
+ throw new Error(`sources[${source.id}].spec changed after catalog creation`);
27
+ if (!catalog.operations.some((item) => item.key.documentId === key.documentId &&
28
+ item.operationRef === key.operationRef))
29
+ throw new Error(`sources[${source.id}] has no operationRef ${key.operationRef}`);
30
+ let document = documents.get(source.id);
31
+ if (!document) {
32
+ document = await validateAndResolveOpenApiDocument(source.spec);
33
+ documents.set(source.id, document);
34
+ }
35
+ const { candidate, pathItem, entry } = selectedOperationSource(document, key.operationRef);
36
+ const operation = object(entry.value, `sources[${source.id}].${key.operationRef}`);
37
+ const parameters = new Map();
38
+ for (const level of [pathItem, operation]) {
39
+ if (level.parameters === undefined)
40
+ continue;
41
+ if (!Array.isArray(level.parameters))
42
+ throw new Error(`sources[${source.id}].${key.operationRef}.parameters must be an array`);
43
+ for (const raw of level.parameters) {
44
+ const parameter = object(raw, `${key.operationRef}.parameters`);
45
+ parameters.set(JSON.stringify([parameter.in, parameter.name]), parameter);
46
+ }
47
+ }
48
+ const security = operation.security === undefined
49
+ ? document.spec.security
50
+ : operation.security;
51
+ const servers = operation.servers === undefined
52
+ ? pathItem.servers === undefined
53
+ ? document.spec.servers
54
+ : pathItem.servers
55
+ : operation.servers;
56
+ const components = document.spec.components === undefined
57
+ ? undefined
58
+ : object(document.spec.components, 'spec.components');
59
+ if (typeof document.spec.openapi !== 'string')
60
+ throw new Error(`sources[${source.id}].spec.openapi is missing`);
61
+ contracts.push({
62
+ key: { ...key },
63
+ openapiVersion: document.spec.openapi,
64
+ source: 'paths',
65
+ method: candidate.method,
66
+ path: candidate.path,
67
+ operation: structuredClone(operation),
68
+ pathItem: structuredClone(pathItem),
69
+ effective: {
70
+ parameters: [...parameters.values()],
71
+ security: security === undefined
72
+ ? []
73
+ : structuredClone(security),
74
+ securitySchemes: components?.securitySchemes === undefined
75
+ ? {}
76
+ : object(components.securitySchemes, 'spec.components.securitySchemes'),
77
+ ...(servers === undefined
78
+ ? {}
79
+ : { servers: structuredClone(servers) }),
80
+ },
81
+ });
82
+ }
83
+ return contracts;
84
+ }
@@ -0,0 +1,3 @@
1
+ import type { ApiResponseEnvelope, ApiResponseValidationValue } from '../../types/api-response.js';
2
+ /** Project transport metadata for validation without rewriting response data. */
3
+ export declare function apiResponseValidationValue(response: ApiResponseEnvelope): ApiResponseValidationValue;
@@ -0,0 +1,21 @@
1
+ /** Project transport metadata for validation without rewriting response data. */
2
+ export function apiResponseValidationValue(response) {
3
+ if (!response || !Number.isInteger(response.statusCode))
4
+ throw new Error('API response.statusCode is required');
5
+ if (!response.headers ||
6
+ typeof response.headers !== 'object' ||
7
+ Array.isArray(response.headers))
8
+ throw new Error('API response.headers is required');
9
+ const headers = Object.entries(response.headers).filter(([name]) => name.toLowerCase() === 'content-type');
10
+ if (headers.length > 1)
11
+ throw new Error('API response.headers has ambiguous Content-Type');
12
+ const value = { statusCode: response.statusCode };
13
+ if (headers.length) {
14
+ if (typeof headers[0][1] !== 'string')
15
+ throw new Error('API response Content-Type must be a string');
16
+ value.mediaType = headers[0][1].split(';')[0].trim().toLowerCase();
17
+ }
18
+ if (Object.hasOwn(response, 'body'))
19
+ value.body = response.body;
20
+ return value;
21
+ }
@@ -0,0 +1,4 @@
1
+ import type { ApiResponseValidationInput } from '../../types/api-response.js';
2
+ import type { JsonObject } from '../../types/openapi.js';
3
+ /** Validate the observed response, not an LLM-planned expected status. */
4
+ export declare function createApiResponseSchema({ operation, }: ApiResponseValidationInput): JsonObject;
@@ -0,0 +1,114 @@
1
+ import { isObject } from '../../utils/is-object.js';
2
+ import { createOasValueValidator } from '../common/create-oas-value-validator.js';
3
+ function statusSchema(key, keys) {
4
+ if (/^[1-5][0-9]{2}$/.test(key))
5
+ return { const: Number(key) };
6
+ if (/^[1-5]XX$/.test(key))
7
+ return {
8
+ minimum: Number(key[0]) * 100,
9
+ maximum: Number(key[0]) * 100 + 99,
10
+ ...(keys.some((item) => /^[1-5][0-9]{2}$/.test(item))
11
+ ? {
12
+ not: {
13
+ enum: keys
14
+ .filter((item) => /^[1-5][0-9]{2}$/.test(item))
15
+ .map(Number),
16
+ },
17
+ }
18
+ : {}),
19
+ };
20
+ if (key === 'default')
21
+ return keys.some((item) => item !== 'default')
22
+ ? {
23
+ not: {
24
+ anyOf: keys
25
+ .filter((item) => item !== 'default')
26
+ .map((item) => statusSchema(item, keys)),
27
+ },
28
+ }
29
+ : {};
30
+ throw new Error(`responses.${key} is not an OAS response status`);
31
+ }
32
+ function mediaPattern(media) {
33
+ return ('^' +
34
+ media
35
+ .toLowerCase()
36
+ .split('*')
37
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
38
+ .join('[^/;]+') +
39
+ '$');
40
+ }
41
+ /** Validate the observed response, not an LLM-planned expected status. */
42
+ export function createApiResponseSchema({ operation, }) {
43
+ const responses = operation.operation.responses;
44
+ if (!isObject(responses))
45
+ throw new Error(`${operation.key.operationRef}.responses is required`);
46
+ const keys = Object.keys(responses).filter((key) => !key.startsWith('x-'));
47
+ const dialect = operation.openapiVersion.startsWith('3.0.')
48
+ ? 'openapi-3.0'
49
+ : 'draft-2020-12';
50
+ const branches = keys.flatMap((key) => {
51
+ const response = responses[key];
52
+ if (!isObject(response))
53
+ throw new Error(`${operation.key.operationRef}.responses.${key} must be resolved`);
54
+ if ('$ref' in response)
55
+ throw new Error(`${operation.key.operationRef}.responses.${key} must be resolved`);
56
+ const status = statusSchema(key, keys);
57
+ const content = response.content;
58
+ if (!isObject(content) || !Object.keys(content).length)
59
+ return [{ properties: { statusCode: status } }];
60
+ const mediaTypes = Object.keys(content);
61
+ return mediaTypes.map((mediaType) => {
62
+ const media = content[mediaType];
63
+ if (!isObject(media))
64
+ throw new Error(`${operation.key.operationRef}.responses.${key}.content.${mediaType} must be resolved`);
65
+ if ('$ref' in media)
66
+ throw new Error(`${operation.key.operationRef}.responses.${key}.content.${mediaType} must be resolved`);
67
+ const schema = media.schema;
68
+ if (schema !== undefined &&
69
+ typeof schema !== 'boolean' &&
70
+ !isObject(schema))
71
+ throw new Error(`${operation.key.operationRef}.responses.${key}.content.${mediaType}.schema must be an OAS schema`);
72
+ const body = schema === undefined
73
+ ? true
74
+ : createOasValueValidator(schema, dialect, `${operation.key.documentId}${operation.key.operationRef}/responses/${key}/${mediaType}`).schema;
75
+ const moreSpecific = mediaTypes.filter((other) => other !== mediaType &&
76
+ (mediaType === '*/*'
77
+ ? other !== '*/*'
78
+ : mediaType.endsWith('/*') &&
79
+ !other.includes('*') &&
80
+ other.split('/')[0].toLowerCase() ===
81
+ mediaType.split('/')[0].toLowerCase()));
82
+ return {
83
+ properties: {
84
+ statusCode: status,
85
+ mediaType: {
86
+ pattern: mediaPattern(mediaType),
87
+ ...(moreSpecific.length
88
+ ? {
89
+ not: {
90
+ anyOf: moreSpecific.map((other) => ({
91
+ pattern: mediaPattern(other),
92
+ })),
93
+ },
94
+ }
95
+ : {}),
96
+ },
97
+ body,
98
+ },
99
+ required: ['mediaType'],
100
+ };
101
+ });
102
+ });
103
+ return {
104
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
105
+ type: 'object',
106
+ properties: {
107
+ statusCode: { type: 'integer', minimum: 100, maximum: 599 },
108
+ mediaType: { type: 'string' },
109
+ body: true,
110
+ },
111
+ required: ['statusCode'],
112
+ ...(branches.length ? { anyOf: branches } : { not: {} }),
113
+ };
114
+ }
@@ -0,0 +1,4 @@
1
+ import type { ApiOperationContract } from '../../types/api-operation.js';
2
+ import type { BindingSchema } from '../../types/api-bindings.js';
3
+ /** Item candidates come from OAS; the original full response is validated at runtime. */
4
+ export declare function createResponseArrayItemSchema(operation: ApiOperationContract, pointer: string): BindingSchema;
@@ -0,0 +1,12 @@
1
+ import { responseBindingSchemas } from '../../bindings/response-binding-schema.js';
2
+ import { projectArrayItemSchema } from '../../bindings/project-array-item-schema.js';
3
+ /** Item candidates come from OAS; the original full response is validated at runtime. */
4
+ export function createResponseArrayItemSchema(operation, pointer) {
5
+ const items = responseBindingSchemas(operation, pointer).flatMap((schema) => {
6
+ const projection = projectArrayItemSchema(schema);
7
+ return projection.allowsArray ? [projection.schema] : [];
8
+ });
9
+ if (!items.length)
10
+ throw new Error(`${operation.key.operationRef}${pointer} has no declared response array`);
11
+ return { anyOf: items };
12
+ }
@@ -0,0 +1,4 @@
1
+ import type { ApiOperationContract } from '../../types/api-operation.js';
2
+ import type { BindingSchema } from '../../types/api-bindings.js';
3
+ /** Candidate schemas for a pointer; validate the selected full response at runtime. */
4
+ export declare function createResponseValueSchema(operation: ApiOperationContract, pointer: string): BindingSchema;
@@ -0,0 +1,8 @@
1
+ import { responseBindingSchemas } from '../../bindings/response-binding-schema.js';
2
+ /** Candidate schemas for a pointer; validate the selected full response at runtime. */
3
+ export function createResponseValueSchema(operation, pointer) {
4
+ const schemas = responseBindingSchemas(operation, pointer);
5
+ if (!schemas.length)
6
+ throw new Error(`${operation.key.operationRef}${pointer} has no declared response value`);
7
+ return { anyOf: schemas };
8
+ }
@@ -0,0 +1,4 @@
1
+ import type { ApiOperationContract } from '../../types/api-operation.js';
2
+ import type { ApiResponsePointerCatalog } from '../../types/api-response-pointers.js';
3
+ /** Describe finite schema declarations, not guaranteed runtime fields or a whitelist. */
4
+ export declare function listApiResponsePointers(operation: ApiOperationContract): ApiResponsePointerCatalog[];