@techspokes/typescript-wsdl-client 0.7.15 → 0.8.15

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 (60) hide show
  1. package/README.md +1504 -263
  2. package/dist/cli.js +423 -270
  3. package/dist/{emit/clientEmitter.d.ts → client/generateClient.d.ts} +2 -2
  4. package/dist/client/generateClient.d.ts.map +1 -0
  5. package/dist/{emit/clientEmitter.js → client/generateClient.js} +5 -5
  6. package/dist/{emit/typesEmitter.d.ts → client/generateTypes.d.ts} +4 -4
  7. package/dist/client/generateTypes.d.ts.map +1 -0
  8. package/dist/{emit/typesEmitter.js → client/generateTypes.js} +6 -6
  9. package/dist/{emit/utilsEmitter.d.ts → client/generateUtils.d.ts} +4 -4
  10. package/dist/client/generateUtils.d.ts.map +1 -0
  11. package/dist/{emit/utilsEmitter.js → client/generateUtils.js} +7 -7
  12. package/dist/{emit/catalogEmitter.d.ts → compiler/generateCatalog.d.ts} +4 -4
  13. package/dist/compiler/generateCatalog.d.ts.map +1 -0
  14. package/dist/{emit/catalogEmitter.js → compiler/generateCatalog.js} +5 -5
  15. package/dist/compiler/schemaCompiler.d.ts +1 -1
  16. package/dist/compiler/schemaCompiler.js +1 -1
  17. package/dist/config.d.ts +13 -0
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +17 -0
  20. package/dist/gateway/generateGateway.d.ts +73 -0
  21. package/dist/gateway/generateGateway.d.ts.map +1 -0
  22. package/dist/gateway/generateGateway.js +135 -0
  23. package/dist/gateway/generators.d.ts +90 -0
  24. package/dist/gateway/generators.d.ts.map +1 -0
  25. package/dist/gateway/generators.js +270 -0
  26. package/dist/gateway/helpers.d.ts +115 -0
  27. package/dist/gateway/helpers.d.ts.map +1 -0
  28. package/dist/gateway/helpers.js +224 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +18 -18
  32. package/dist/loader/wsdlLoader.d.ts.map +1 -1
  33. package/dist/loader/wsdlLoader.js +1 -3
  34. package/dist/openapi/generateOpenAPI.d.ts +25 -1
  35. package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
  36. package/dist/openapi/generateOpenAPI.js +28 -27
  37. package/dist/openapi/{buildPaths.d.ts → generatePaths.d.ts} +6 -6
  38. package/dist/openapi/generatePaths.d.ts.map +1 -0
  39. package/dist/openapi/{buildPaths.js → generatePaths.js} +1 -1
  40. package/dist/openapi/{buildSchemas.d.ts → generateSchemas.d.ts} +10 -10
  41. package/dist/openapi/generateSchemas.d.ts.map +1 -0
  42. package/dist/openapi/{buildSchemas.js → generateSchemas.js} +5 -5
  43. package/dist/openapi/security.d.ts.map +1 -1
  44. package/dist/openapi/security.js +2 -1
  45. package/dist/pipeline.d.ts +21 -7
  46. package/dist/pipeline.d.ts.map +1 -1
  47. package/dist/pipeline.js +66 -32
  48. package/dist/util/builder.d.ts +25 -0
  49. package/dist/util/builder.d.ts.map +1 -0
  50. package/dist/util/builder.js +52 -0
  51. package/dist/util/cli.d.ts +106 -0
  52. package/dist/util/cli.d.ts.map +1 -0
  53. package/dist/util/cli.js +164 -0
  54. package/package.json +12 -9
  55. package/dist/emit/catalogEmitter.d.ts.map +0 -1
  56. package/dist/emit/clientEmitter.d.ts.map +0 -1
  57. package/dist/emit/typesEmitter.d.ts.map +0 -1
  58. package/dist/emit/utilsEmitter.d.ts.map +0 -1
  59. package/dist/openapi/buildPaths.d.ts.map +0 -1
  60. package/dist/openapi/buildSchemas.d.ts.map +0 -1
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Gateway Code Generators
3
+ *
4
+ * This module contains functions for generating various gateway code artifacts:
5
+ * - Model JSON Schema files with URN IDs
6
+ * - Operation schema files with Fastify-compatible structure
7
+ * - schemas.ts module for registering all model schemas
8
+ * - Individual route files with handler stubs
9
+ * - routes.ts module for registering all routes
10
+ *
11
+ * All emitters follow deterministic generation rules for diff-friendly output.
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { rewriteSchemaRefs, slugName, } from "./helpers.js";
16
+ /**
17
+ * Emits individual JSON Schema files for each OpenAPI component schema
18
+ *
19
+ * Process:
20
+ * 1. Creates schemas/models/ directory
21
+ * 2. For each component schema:
22
+ * - Generates URN $id: urn:services:{service}:{version}:schemas:models:{slug}
23
+ * - Rewrites all internal $refs to URN format
24
+ * - Writes to {slug}.json
25
+ *
26
+ * @param {Record<string, any>} schemas - OpenAPI components.schemas object
27
+ * @param {string} modelsDir - Output directory for model schema files
28
+ * @param {string} versionSlug - Version slug for URN generation
29
+ * @param {string} serviceSlug - Service slug for URN generation
30
+ * @returns {Record<string, string>} - Map from original schema name to URN ID
31
+ */
32
+ export function emitModelSchemas(schemas, modelsDir, versionSlug, serviceSlug) {
33
+ fs.mkdirSync(modelsDir, { recursive: true });
34
+ const schemaIdByName = {};
35
+ const modelSlugByName = {};
36
+ // First pass: generate all URN IDs
37
+ for (const name of Object.keys(schemas)) {
38
+ const slug = slugName(name);
39
+ if (modelSlugByName[name]) {
40
+ throw new Error(`Duplicate schema name '${name}'`);
41
+ }
42
+ modelSlugByName[name] = slug;
43
+ schemaIdByName[name] = `urn:services:${serviceSlug}:${versionSlug}:schemas:models:${slug}`;
44
+ }
45
+ // Second pass: rewrite refs and emit files
46
+ for (const [name, schema] of Object.entries(schemas)) {
47
+ const slug = modelSlugByName[name];
48
+ const cloned = rewriteSchemaRefs(schema, schemaIdByName);
49
+ cloned.$id = schemaIdByName[name];
50
+ const outPath = path.join(modelsDir, `${slug}.json`);
51
+ fs.writeFileSync(outPath, JSON.stringify(cloned, null, 2), "utf8");
52
+ }
53
+ return schemaIdByName;
54
+ }
55
+ /**
56
+ * Emits Fastify-compatible operation schema files
57
+ *
58
+ * Schema structure:
59
+ * {
60
+ * "$id": "urn:services:{service}:{version}:schemas:operations:{slug}",
61
+ * "body": { $ref: "..." }, // optional
62
+ * "params": { type: object, ... }, // optional
63
+ * "querystring": { type: object, ... }, // optional
64
+ * "headers": { type: object, ... }, // optional
65
+ * "response": {
66
+ * "200": { $ref: "..." },
67
+ * "400": { $ref: "..." },
68
+ * ...
69
+ * }
70
+ * }
71
+ *
72
+ * @param {OpenAPIDocument} doc - OpenAPI document
73
+ * @param {string} opsDir - Output directory for operation schema files
74
+ * @param {string} versionSlug - Version slug for URN generation
75
+ * @param {string} serviceSlug - Service slug for URN generation
76
+ * @param {Record<string, string>} schemaIdByName - Schema name to URN ID mapping
77
+ * @param {number[]} defaultResponseStatusCodes - Status codes to backfill with default response
78
+ * @param {Function} buildParamSchemas - Function to build param schemas
79
+ * @param {Function} getRefName - Function to extract $ref name
80
+ * @param {Function} isNumeric - Function to check if status code is numeric
81
+ * @returns {OperationMetadata[]} - Array of operation metadata for route generation
82
+ */
83
+ export function emitOperationSchemas(doc, opsDir, versionSlug, serviceSlug, schemaIdByName, defaultResponseStatusCodes, buildParamSchemas, getRefName, isNumeric) {
84
+ fs.mkdirSync(opsDir, { recursive: true });
85
+ const operations = [];
86
+ const seenOperationSlugs = new Set();
87
+ for (const [p, pathItem] of Object.entries(doc.paths)) {
88
+ if (!pathItem || typeof pathItem !== "object")
89
+ continue;
90
+ const pathLevel = pathItem;
91
+ for (const [method, opVal] of Object.entries(pathItem)) {
92
+ const lowerMethod = method.toLowerCase();
93
+ if (!["get", "post", "put", "patch", "delete", "options", "head"].includes(lowerMethod)) {
94
+ continue;
95
+ }
96
+ const operation = opVal;
97
+ if (!operation || typeof operation !== "object")
98
+ continue;
99
+ let operationId = operation.operationId;
100
+ if (!operationId) {
101
+ operationId = `${lowerMethod}_${p}`;
102
+ }
103
+ const operationSlug = slugName(operationId);
104
+ if (seenOperationSlugs.has(operationSlug)) {
105
+ throw new Error(`Duplicate operation slug '${operationSlug}' derived from operationId '${operationId}'`);
106
+ }
107
+ seenOperationSlugs.add(operationSlug);
108
+ // Build request body schema
109
+ let bodySchema;
110
+ const rbSchema = operation.requestBody?.content?.["application/json"]?.schema;
111
+ if (rbSchema) {
112
+ const name = getRefName(rbSchema);
113
+ const id = schemaIdByName[name];
114
+ if (!id) {
115
+ throw new Error(`Unknown request body schema component '${name}'`);
116
+ }
117
+ bodySchema = { $ref: id + "#" };
118
+ }
119
+ // Build parameter schemas
120
+ const { paramsSchema, querySchema, headersSchema } = buildParamSchemas(pathLevel, operation, doc, schemaIdByName);
121
+ // Build response schemas
122
+ const responses = operation.responses || {};
123
+ const responseObj = {};
124
+ const explicitCodes = Object.keys(responses).filter(isNumeric);
125
+ for (const code of explicitCodes) {
126
+ const r = responses[code];
127
+ if (!r || typeof r !== "object")
128
+ continue;
129
+ const rSchema = r.content?.["application/json"]?.schema;
130
+ if (!rSchema) {
131
+ throw new Error(`Response ${code} for operation '${operationId}' is missing application/json schema`);
132
+ }
133
+ const name = getRefName(rSchema);
134
+ const id = schemaIdByName[name];
135
+ if (!id) {
136
+ throw new Error(`Unknown response schema component '${name}' for code ${code}`);
137
+ }
138
+ responseObj[code] = { $ref: id + "#" };
139
+ }
140
+ // Validate and get default response
141
+ const defaultResp = responses.default;
142
+ if (!defaultResp || typeof defaultResp !== "object") {
143
+ throw new Error(`Operation '${operationId}' is missing default response; required by gateway codegen`);
144
+ }
145
+ const defaultSchema = defaultResp.content?.["application/json"]?.schema;
146
+ if (!defaultSchema) {
147
+ throw new Error(`Operation '${operationId}' default response is missing application/json schema`);
148
+ }
149
+ const defaultName = getRefName(defaultSchema);
150
+ const defaultId = schemaIdByName[defaultName];
151
+ if (!defaultId) {
152
+ throw new Error(`Unknown default response schema component '${defaultName}' for operation '${operationId}'`);
153
+ }
154
+ // Backfill default response codes
155
+ for (const code of defaultResponseStatusCodes) {
156
+ const codeStr = String(code);
157
+ if (!responseObj[codeStr]) {
158
+ responseObj[codeStr] = { $ref: defaultId + "#" };
159
+ }
160
+ }
161
+ // Build operation schema object
162
+ const opSchema = {
163
+ $id: `urn:services:${serviceSlug}:${versionSlug}:schemas:operations:${operationSlug}`,
164
+ response: responseObj,
165
+ };
166
+ if (bodySchema)
167
+ opSchema.body = bodySchema;
168
+ if (paramsSchema)
169
+ opSchema.params = paramsSchema;
170
+ if (querySchema)
171
+ opSchema.querystring = querySchema;
172
+ if (headersSchema)
173
+ opSchema.headers = headersSchema;
174
+ const opOutPath = path.join(opsDir, `${operationSlug}.json`);
175
+ fs.writeFileSync(opOutPath, JSON.stringify(opSchema, null, 2), "utf8");
176
+ operations.push({
177
+ operationSlug,
178
+ method: lowerMethod,
179
+ path: p,
180
+ });
181
+ }
182
+ }
183
+ return operations;
184
+ }
185
+ /**
186
+ * Emits schemas.ts module that registers all model schemas with Fastify
187
+ *
188
+ * Generated code:
189
+ * - Imports all JSON files from schemas/models/
190
+ * - Exports registerSchemas_{version}_{service}(fastify) function
191
+ * - Calls fastify.addSchema for each model schema
192
+ *
193
+ * @param {string} outDir - Root output directory
194
+ * @param {string} modelsDir - Directory containing model schema files
195
+ * @param {string} versionSlug - Version slug for function naming
196
+ * @param {string} serviceSlug - Service slug for function naming
197
+ */
198
+ export function emitSchemasModule(outDir, modelsDir, versionSlug, serviceSlug) {
199
+ const modelFiles = fs.readdirSync(modelsDir).filter((f) => f.endsWith(".json"));
200
+ modelFiles.sort();
201
+ let schemasTs = "";
202
+ schemasTs += `import type { FastifyInstance } from "fastify";\n`;
203
+ modelFiles.forEach((file, idx) => {
204
+ const varName = `m${idx}`;
205
+ schemasTs += `import ${varName} from "./schemas/models/${file}" with { type: "json" };\n`;
206
+ });
207
+ schemasTs += `\nexport async function registerSchemas_${slugName(versionSlug)}_${slugName(serviceSlug)}(fastify: FastifyInstance) {\n`;
208
+ schemasTs += ` const schemas = [${modelFiles
209
+ .map((_, idx) => `m${idx}`)
210
+ .join(", ")}];\n`;
211
+ schemasTs += ` for (const s of schemas) {\n`;
212
+ schemasTs += ` fastify.addSchema(s as any);\n`;
213
+ schemasTs += ` }\n`;
214
+ schemasTs += `}\n`;
215
+ fs.writeFileSync(path.join(outDir, "schemas.ts"), schemasTs, "utf8");
216
+ }
217
+ /**
218
+ * Emits individual route files and routes.ts aggregator module
219
+ *
220
+ * For each operation:
221
+ * - Creates routes/{slug}.ts with registerRoute_{slug} function
222
+ * - Imports operation schema from schemas/operations/{slug}.json
223
+ * - Defines fastify.route() with method, url, schema, and stub handler
224
+ *
225
+ * Then creates routes.ts:
226
+ * - Imports all route registration functions
227
+ * - Exports registerRoutes_{version}_{service}(fastify) function
228
+ *
229
+ * @param {string} outDir - Root output directory
230
+ * @param {string} routesDir - Directory for individual route files
231
+ * @param {string} versionSlug - Version slug for function naming
232
+ * @param {string} serviceSlug - Service slug for function naming
233
+ * @param {OperationMetadata[]} operations - Array of operation metadata
234
+ * @param {"js"|"ts"|"bare"} importsMode - Import-extension mode for generated TypeScript modules
235
+ */
236
+ export function emitRouteFiles(outDir, routesDir, versionSlug, serviceSlug, operations, importsMode) {
237
+ fs.mkdirSync(routesDir, { recursive: true });
238
+ // Sort operations for deterministic output
239
+ operations.sort((a, b) => a.operationSlug.localeCompare(b.operationSlug));
240
+ const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
241
+ let routesTs = `import type { FastifyInstance } from "fastify";\n`;
242
+ operations.forEach((op) => {
243
+ const fnName = `registerRoute_${slugName(versionSlug)}_${slugName(serviceSlug)}_${op.operationSlug.replace(/[^a-zA-Z0-9_]/g, "_")}`;
244
+ const routeFileBase = op.operationSlug;
245
+ routesTs += `import { ${fnName} } from "./routes/${routeFileBase}${suffix}";\n`;
246
+ // Generate individual route file
247
+ let routeTs = "";
248
+ routeTs += `import type { FastifyInstance } from "fastify";\n`;
249
+ routeTs += `import schema from "../schemas/operations/${op.operationSlug}.json" with { type: "json" };\n\n`;
250
+ routeTs += `export async function ${fnName}(fastify: FastifyInstance) {\n`;
251
+ routeTs += ` fastify.route({\n`;
252
+ routeTs += ` method: "${op.method.toUpperCase()}",\n`;
253
+ routeTs += ` url: "${op.path}",\n`;
254
+ routeTs += ` schema: schema as any,\n`;
255
+ routeTs += ` handler: async (request, reply) => {\n`;
256
+ routeTs += ` throw new Error("Not implemented");\n`;
257
+ routeTs += ` }\n`;
258
+ routeTs += ` });\n`;
259
+ routeTs += `}\n`;
260
+ fs.writeFileSync(path.join(routesDir, `${routeFileBase}.ts`), routeTs, "utf8");
261
+ });
262
+ const routeFnName = `registerRoutes_${slugName(versionSlug)}_${slugName(serviceSlug)}`;
263
+ routesTs += `\nexport async function ${routeFnName}(fastify: FastifyInstance) {\n`;
264
+ operations.forEach((op) => {
265
+ const fnName = `registerRoute_${slugName(versionSlug)}_${slugName(serviceSlug)}_${op.operationSlug.replace(/[^a-zA-Z0-9_]/g, "_")}`;
266
+ routesTs += ` await ${fnName}(fastify);\n`;
267
+ });
268
+ routesTs += `}\n`;
269
+ fs.writeFileSync(path.join(outDir, "routes.ts"), routesTs, "utf8");
270
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Gateway Generation Helpers
3
+ *
4
+ * This module provides utility functions for generating Fastify gateway code from OpenAPI 3.1 specifications.
5
+ * It handles slug generation, schema reference rewriting, parameter resolution, and version/service detection.
6
+ *
7
+ * Key capabilities:
8
+ * - Deterministic slug generation from OpenAPI component names
9
+ * - Schema $ref rewriting from OpenAPI references to URN-style IDs
10
+ * - Parameter resolution with path-level and operation-level override support
11
+ * - Automatic version/service detection from OpenAPI paths
12
+ */
13
+ /**
14
+ * OpenAPI document structure (minimal type for internal use)
15
+ */
16
+ export interface OpenAPIDocument {
17
+ openapi: string;
18
+ info?: any;
19
+ paths: Record<string, any>;
20
+ components?: {
21
+ schemas?: Record<string, any>;
22
+ parameters?: Record<string, any>;
23
+ };
24
+ }
25
+ /**
26
+ * Converts a name to a deterministic slug for use in file names and URN IDs
27
+ *
28
+ * Rules:
29
+ * - Lowercase all characters
30
+ * - Replace non-alphanumeric sequences with single underscores
31
+ * - Strip leading/trailing underscores
32
+ * - Collapse multiple underscores to single underscore
33
+ *
34
+ * @param {string} name - Original name to convert
35
+ * @returns {string} - Slugified name
36
+ *
37
+ * @example
38
+ * slugName("GetUserDetails") // "getuserdetails"
39
+ * slugName("User-Profile_Data") // "user_profile_data"
40
+ */
41
+ export declare function slugName(name: string): string;
42
+ /**
43
+ * Checks if a status code string is numeric
44
+ *
45
+ * @param {string} code - Status code to check
46
+ * @returns {boolean} - True if the code is numeric
47
+ */
48
+ export declare function isNumericStatus(code: string): boolean;
49
+ /**
50
+ * Recursively rewrites all $ref properties in a schema object from OpenAPI component references
51
+ * to URN-style schema IDs
52
+ *
53
+ * Contract enforcement:
54
+ * - All $ref values must start with "#/components/schemas/"
55
+ * - The referenced schema name must exist in schemaIdByName map
56
+ * - Throws if unknown schema is referenced
57
+ *
58
+ * @param {any} node - Schema object or primitive value to process
59
+ * @param {Record<string, string>} schemaIdByName - Map from OpenAPI component name to URN ID
60
+ * @returns {any} - Deep clone with rewritten $ref properties
61
+ * @throws {Error} If unknown schema reference is encountered
62
+ */
63
+ export declare function rewriteSchemaRefs(node: any, schemaIdByName: Record<string, string>): any;
64
+ /**
65
+ * Extracts the component schema name from a $ref object
66
+ *
67
+ * Contract enforcement (strict validation):
68
+ * - Schema must be an object with a $ref property
69
+ * - $ref must start with "#/components/schemas/"
70
+ * - Throws if inline schema or invalid $ref format
71
+ *
72
+ * @param {any} schema - Schema object expected to contain a $ref
73
+ * @returns {string} - Extracted schema component name
74
+ * @throws {Error} If schema is not a proper $ref or uses unsupported format
75
+ */
76
+ export declare function getJsonSchemaRefName(schema: any): string;
77
+ /**
78
+ * Resolves a parameter object or $ref to a component parameter
79
+ *
80
+ * Supports:
81
+ * - Inline parameter objects (returned as-is)
82
+ * - $ref to "#/components/parameters/Name" (resolved from document)
83
+ *
84
+ * @param {any} paramOrRef - Parameter object or reference
85
+ * @param {OpenAPIDocument} doc - OpenAPI document containing components
86
+ * @returns {any} - Resolved parameter object
87
+ * @throws {Error} If $ref format is invalid or referenced parameter not found
88
+ */
89
+ export declare function resolveParameter(paramOrRef: any, doc: OpenAPIDocument): any;
90
+ /**
91
+ * Result of building Fastify-compatible param schemas from OpenAPI parameters
92
+ */
93
+ export interface ParamSchemaBuildResult {
94
+ paramsSchema?: any;
95
+ querySchema?: any;
96
+ headersSchema?: any;
97
+ }
98
+ /**
99
+ * Builds Fastify-compatible params/querystring/headers schemas from OpenAPI parameters
100
+ *
101
+ * Logic:
102
+ * 1. Merges path-level and operation-level parameters (operation overrides path)
103
+ * 2. Partitions by "in" field: path -> params, query -> querystring, header -> headers
104
+ * 3. Builds inline object schemas with properties/required arrays
105
+ * 4. Lowercases header names (Fastify convention)
106
+ * 5. Rewrites any schema $refs to URN format
107
+ *
108
+ * @param {any} pathItem - Path-level object (may have parameters array)
109
+ * @param {any} operation - Operation object (may have parameters array)
110
+ * @param {OpenAPIDocument} doc - OpenAPI document for resolving parameter refs
111
+ * @param {Record<string, string>} schemaIdByName - Schema name to URN ID mapping
112
+ * @returns {ParamSchemaBuildResult} - Object with optional params/querystring/headers schemas
113
+ */
114
+ export declare function buildParamSchemasForOperation(pathItem: any, operation: any, doc: OpenAPIDocument, schemaIdByName: Record<string, string>): ParamSchemaBuildResult;
115
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/gateway/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,UAAU,CAAC,EAAE;QACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAClC,CAAC;CACH;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAqBxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,CAcxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,GAAG,GAAG,CAiB3E;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,aAAa,CAAC,EAAE,GAAG,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,GAAG,EACb,SAAS,EAAE,GAAG,EACd,GAAG,EAAE,eAAe,EACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,sBAAsB,CAmFxB"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Gateway Generation Helpers
3
+ *
4
+ * This module provides utility functions for generating Fastify gateway code from OpenAPI 3.1 specifications.
5
+ * It handles slug generation, schema reference rewriting, parameter resolution, and version/service detection.
6
+ *
7
+ * Key capabilities:
8
+ * - Deterministic slug generation from OpenAPI component names
9
+ * - Schema $ref rewriting from OpenAPI references to URN-style IDs
10
+ * - Parameter resolution with path-level and operation-level override support
11
+ * - Automatic version/service detection from OpenAPI paths
12
+ */
13
+ /**
14
+ * Converts a name to a deterministic slug for use in file names and URN IDs
15
+ *
16
+ * Rules:
17
+ * - Lowercase all characters
18
+ * - Replace non-alphanumeric sequences with single underscores
19
+ * - Strip leading/trailing underscores
20
+ * - Collapse multiple underscores to single underscore
21
+ *
22
+ * @param {string} name - Original name to convert
23
+ * @returns {string} - Slugified name
24
+ *
25
+ * @example
26
+ * slugName("GetUserDetails") // "getuserdetails"
27
+ * slugName("User-Profile_Data") // "user_profile_data"
28
+ */
29
+ export function slugName(name) {
30
+ const lower = String(name ?? "").toLowerCase();
31
+ const collapsed = lower.replace(/[^a-z0-9]+/g, "_");
32
+ return collapsed.replace(/^_+|_+$/g, "");
33
+ }
34
+ /**
35
+ * Checks if a status code string is numeric
36
+ *
37
+ * @param {string} code - Status code to check
38
+ * @returns {boolean} - True if the code is numeric
39
+ */
40
+ export function isNumericStatus(code) {
41
+ return /^\d+$/.test(code);
42
+ }
43
+ /**
44
+ * Recursively rewrites all $ref properties in a schema object from OpenAPI component references
45
+ * to URN-style schema IDs
46
+ *
47
+ * Contract enforcement:
48
+ * - All $ref values must start with "#/components/schemas/"
49
+ * - The referenced schema name must exist in schemaIdByName map
50
+ * - Throws if unknown schema is referenced
51
+ *
52
+ * @param {any} node - Schema object or primitive value to process
53
+ * @param {Record<string, string>} schemaIdByName - Map from OpenAPI component name to URN ID
54
+ * @returns {any} - Deep clone with rewritten $ref properties
55
+ * @throws {Error} If unknown schema reference is encountered
56
+ */
57
+ export function rewriteSchemaRefs(node, schemaIdByName) {
58
+ if (Array.isArray(node)) {
59
+ return node.map((v) => rewriteSchemaRefs(v, schemaIdByName));
60
+ }
61
+ if (node && typeof node === "object") {
62
+ const out = {};
63
+ for (const [k, v] of Object.entries(node)) {
64
+ if (k === "$ref" && typeof v === "string" && v.startsWith("#/components/schemas/")) {
65
+ const name = v.slice("#/components/schemas/".length);
66
+ const id = schemaIdByName[name];
67
+ if (!id) {
68
+ throw new Error(`Unknown schema reference '${name}' in $ref '${v}'`);
69
+ }
70
+ out["$ref"] = id + "#";
71
+ }
72
+ else {
73
+ out[k] = rewriteSchemaRefs(v, schemaIdByName);
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ return node;
79
+ }
80
+ /**
81
+ * Extracts the component schema name from a $ref object
82
+ *
83
+ * Contract enforcement (strict validation):
84
+ * - Schema must be an object with a $ref property
85
+ * - $ref must start with "#/components/schemas/"
86
+ * - Throws if inline schema or invalid $ref format
87
+ *
88
+ * @param {any} schema - Schema object expected to contain a $ref
89
+ * @returns {string} - Extracted schema component name
90
+ * @throws {Error} If schema is not a proper $ref or uses unsupported format
91
+ */
92
+ export function getJsonSchemaRefName(schema) {
93
+ if (!schema || typeof schema !== "object" || typeof schema.$ref !== "string") {
94
+ throw new Error("Expected schema with $ref to '#/components/schemas/Name', but got inline or missing $ref");
95
+ }
96
+ const ref = schema.$ref;
97
+ const prefix = "#/components/schemas/";
98
+ if (!ref.startsWith(prefix)) {
99
+ throw new Error(`Unsupported $ref '${ref}'. Expected to start with '${prefix}'.`);
100
+ }
101
+ return ref.slice(prefix.length);
102
+ }
103
+ /**
104
+ * Resolves a parameter object or $ref to a component parameter
105
+ *
106
+ * Supports:
107
+ * - Inline parameter objects (returned as-is)
108
+ * - $ref to "#/components/parameters/Name" (resolved from document)
109
+ *
110
+ * @param {any} paramOrRef - Parameter object or reference
111
+ * @param {OpenAPIDocument} doc - OpenAPI document containing components
112
+ * @returns {any} - Resolved parameter object
113
+ * @throws {Error} If $ref format is invalid or referenced parameter not found
114
+ */
115
+ export function resolveParameter(paramOrRef, doc) {
116
+ if (paramOrRef && typeof paramOrRef === "object" && typeof paramOrRef.$ref === "string") {
117
+ const ref = paramOrRef.$ref;
118
+ const prefix = "#/components/parameters/";
119
+ if (!ref.startsWith(prefix)) {
120
+ throw new Error(`Unsupported parameter $ref '${ref}'. Expected to start with '${prefix}'.`);
121
+ }
122
+ const name = ref.slice(prefix.length);
123
+ const p = doc.components?.parameters?.[name];
124
+ if (!p) {
125
+ throw new Error(`Parameter '${name}' not found in components.parameters`);
126
+ }
127
+ return p;
128
+ }
129
+ return paramOrRef;
130
+ }
131
+ /**
132
+ * Builds Fastify-compatible params/querystring/headers schemas from OpenAPI parameters
133
+ *
134
+ * Logic:
135
+ * 1. Merges path-level and operation-level parameters (operation overrides path)
136
+ * 2. Partitions by "in" field: path -> params, query -> querystring, header -> headers
137
+ * 3. Builds inline object schemas with properties/required arrays
138
+ * 4. Lowercases header names (Fastify convention)
139
+ * 5. Rewrites any schema $refs to URN format
140
+ *
141
+ * @param {any} pathItem - Path-level object (may have parameters array)
142
+ * @param {any} operation - Operation object (may have parameters array)
143
+ * @param {OpenAPIDocument} doc - OpenAPI document for resolving parameter refs
144
+ * @param {Record<string, string>} schemaIdByName - Schema name to URN ID mapping
145
+ * @returns {ParamSchemaBuildResult} - Object with optional params/querystring/headers schemas
146
+ */
147
+ export function buildParamSchemasForOperation(pathItem, operation, doc, schemaIdByName) {
148
+ const all = [];
149
+ if (Array.isArray(pathItem?.parameters)) {
150
+ all.push(...pathItem.parameters);
151
+ }
152
+ if (Array.isArray(operation?.parameters)) {
153
+ all.push(...operation.parameters);
154
+ }
155
+ // Map by (in + ":" + name), last one wins (operation-level override)
156
+ const byKey = {};
157
+ for (const p of all) {
158
+ const resolved = resolveParameter(p, doc);
159
+ if (!resolved || typeof resolved !== "object")
160
+ continue;
161
+ const loc = resolved.in;
162
+ const name = resolved.name;
163
+ if (!loc || !name)
164
+ continue;
165
+ const key = `${loc}:${name}`;
166
+ byKey[key] = resolved;
167
+ }
168
+ const pathParams = [];
169
+ const queryParams = [];
170
+ const headerParams = [];
171
+ for (const param of Object.values(byKey)) {
172
+ switch (param.in) {
173
+ case "path":
174
+ pathParams.push(param);
175
+ break;
176
+ case "query":
177
+ queryParams.push(param);
178
+ break;
179
+ case "header":
180
+ headerParams.push(param);
181
+ break;
182
+ default:
183
+ // ignore cookie or other locations for now
184
+ break;
185
+ }
186
+ }
187
+ function buildObjectFromParams(params, header) {
188
+ if (params.length === 0)
189
+ return undefined;
190
+ const properties = {};
191
+ const required = [];
192
+ for (const p of params) {
193
+ let name = p.name;
194
+ if (header) {
195
+ name = name.toLowerCase();
196
+ }
197
+ let propSchema = {};
198
+ if (p.schema) {
199
+ propSchema = rewriteSchemaRefs(p.schema, schemaIdByName);
200
+ }
201
+ properties[name] = propSchema;
202
+ if (p.required) {
203
+ required.push(name);
204
+ }
205
+ }
206
+ const obj = {
207
+ type: "object",
208
+ properties,
209
+ additionalProperties: false,
210
+ };
211
+ if (required.length > 0) {
212
+ obj.required = required;
213
+ }
214
+ return obj;
215
+ }
216
+ const paramsSchema = buildObjectFromParams(pathParams, false);
217
+ const querySchema = buildObjectFromParams(queryParams, false);
218
+ const headersSchema = buildObjectFromParams(headerParams, true);
219
+ return {
220
+ paramsSchema,
221
+ querySchema,
222
+ headersSchema,
223
+ };
224
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { CompilerOptions } from "./config.js";
2
2
  export { generateOpenAPI } from "./openapi/generateOpenAPI.js";
3
+ export { generateGateway } from "./gateway/generateGateway.js";
3
4
  export { runGenerationPipeline } from "./pipeline.js";
4
5
  /**
5
6
  * Compiles a WSDL file to TypeScript client code
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AASjD,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,qBAAqB,EAAC,MAAM,eAAe,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,GACjE,OAAO,CAAC,IAAI,CAAC,CAqDf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAUjD,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,qBAAqB,EAAC,MAAM,eAAe,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,GACjE,OAAO,CAAC,IAAI,CAAC,CAsDf"}