@techspokes/typescript-wsdl-client 0.6.3 → 0.7.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 (53) hide show
  1. package/README.md +242 -166
  2. package/dist/cli.js +223 -1
  3. package/dist/compiler/schemaCompiler.d.ts +54 -0
  4. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  5. package/dist/compiler/schemaCompiler.js +74 -7
  6. package/dist/config.d.ts +23 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/emit/catalogEmitter.d.ts +18 -0
  9. package/dist/emit/catalogEmitter.d.ts.map +1 -1
  10. package/dist/emit/catalogEmitter.js +31 -0
  11. package/dist/emit/clientEmitter.d.ts +17 -0
  12. package/dist/emit/clientEmitter.d.ts.map +1 -1
  13. package/dist/emit/clientEmitter.js +33 -3
  14. package/dist/emit/typesEmitter.d.ts +16 -5
  15. package/dist/emit/typesEmitter.d.ts.map +1 -1
  16. package/dist/emit/typesEmitter.js +30 -5
  17. package/dist/emit/utilsEmitter.d.ts +18 -0
  18. package/dist/emit/utilsEmitter.d.ts.map +1 -1
  19. package/dist/emit/utilsEmitter.js +30 -0
  20. package/dist/index.d.ts +22 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +36 -1
  23. package/dist/loader/fetch.d.ts +31 -0
  24. package/dist/loader/fetch.d.ts.map +1 -1
  25. package/dist/loader/fetch.js +31 -0
  26. package/dist/loader/wsdlLoader.d.ts +32 -0
  27. package/dist/loader/wsdlLoader.d.ts.map +1 -1
  28. package/dist/loader/wsdlLoader.js +80 -9
  29. package/dist/openapi/buildPaths.d.ts +74 -0
  30. package/dist/openapi/buildPaths.d.ts.map +1 -0
  31. package/dist/openapi/buildPaths.js +66 -0
  32. package/dist/openapi/buildSchemas.d.ts +44 -0
  33. package/dist/openapi/buildSchemas.d.ts.map +1 -0
  34. package/dist/openapi/buildSchemas.js +207 -0
  35. package/dist/openapi/casing.d.ts +38 -0
  36. package/dist/openapi/casing.d.ts.map +1 -0
  37. package/dist/openapi/casing.js +49 -0
  38. package/dist/openapi/generateOpenAPI.d.ts +57 -0
  39. package/dist/openapi/generateOpenAPI.d.ts.map +1 -0
  40. package/dist/openapi/generateOpenAPI.js +174 -0
  41. package/dist/openapi/security.d.ts +82 -0
  42. package/dist/openapi/security.d.ts.map +1 -0
  43. package/dist/openapi/security.js +145 -0
  44. package/dist/pipeline.d.ts +37 -0
  45. package/dist/pipeline.d.ts.map +1 -0
  46. package/dist/pipeline.js +72 -0
  47. package/dist/util/tools.d.ts +100 -7
  48. package/dist/util/tools.d.ts.map +1 -1
  49. package/dist/util/tools.js +85 -7
  50. package/dist/xsd/primitives.d.ts +33 -0
  51. package/dist/xsd/primitives.d.ts.map +1 -1
  52. package/dist/xsd/primitives.js +59 -7
  53. package/package.json +7 -2
@@ -0,0 +1,66 @@
1
+ import { toPathSegment } from "./casing.js";
2
+ export function buildPaths(compiled, opts) {
3
+ const paths = {};
4
+ const base = normalizeBase(opts.basePath || "/");
5
+ for (const op of compiled.operations) {
6
+ const seg = toPathSegment(op.name, opts.pathStyle);
7
+ const fullPath = (base.endsWith("/") ? base.slice(0, -1) : base) + "/" + seg;
8
+ const override = opts.overrides?.[op.name] || {};
9
+ const method = (override.method || opts.defaultMethod || "post").toLowerCase();
10
+ const tag = opts.tagsMap?.[op.name] || opts.defaultTag;
11
+ const inputRef = op.inputElement?.local ? { $ref: `#/components/schemas/${op.inputElement.local}` } : { type: "object" };
12
+ const outputRef = op.outputElement?.local ? { $ref: `#/components/schemas/${op.outputElement.local}` } : { type: "object" };
13
+ const parameters = [];
14
+ // Header parameters from security builder
15
+ const headerParamNames = opts.opHeaderParameters[op.name] || [];
16
+ for (const pName of headerParamNames) {
17
+ parameters.push({ $ref: `#/components/parameters/${pName}` });
18
+ }
19
+ const operationObject = {
20
+ operationId: op.name,
21
+ tags: [tag],
22
+ requestBody: {
23
+ required: true,
24
+ content: {
25
+ "application/json": { schema: inputRef }
26
+ }
27
+ },
28
+ responses: {
29
+ "200": {
30
+ description: "Successful SOAP operation response",
31
+ content: {
32
+ "application/json": { schema: outputRef }
33
+ }
34
+ },
35
+ default: {
36
+ description: "Error response",
37
+ content: {
38
+ "application/json": { schema: { $ref: "#/components/schemas/ErrorEnvelope" } }
39
+ }
40
+ }
41
+ },
42
+ };
43
+ if (override.summary)
44
+ operationObject.summary = override.summary;
45
+ if (override.description)
46
+ operationObject.description = override.description;
47
+ if (override.deprecated)
48
+ operationObject.deprecated = true;
49
+ if (parameters.length)
50
+ operationObject.parameters = parameters;
51
+ const opSec = opts.opSecurity[op.name];
52
+ if (opSec)
53
+ operationObject.security = opSec;
54
+ if (!paths[fullPath])
55
+ paths[fullPath] = {};
56
+ paths[fullPath][method] = operationObject;
57
+ }
58
+ return paths;
59
+ }
60
+ function normalizeBase(base) {
61
+ if (!base.startsWith("/"))
62
+ base = "/" + base;
63
+ if (base.endsWith("/"))
64
+ return base;
65
+ return base;
66
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * OpenAPI Schema Component Builder
3
+ *
4
+ * This module transforms the compiled TypeScript types from the WSDL catalog
5
+ * into OpenAPI 3.1 schema components. It handles the conversion of complex types,
6
+ * type aliases, arrays, and primitive types to their JSON Schema equivalents.
7
+ *
8
+ * Key features:
9
+ * - Converts TypeScript interfaces to JSON Schema objects
10
+ * - Handles type aliases and enumerations
11
+ * - Supports array types with appropriate item definitions
12
+ * - Manages property requirements based on XML schema constraints
13
+ * - Optionally enforces closed schemas with additionalProperties:false
14
+ * - Can prune schemas that aren't referenced by any operation
15
+ */
16
+ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
17
+ /**
18
+ * Options for building OpenAPI schema components
19
+ *
20
+ * @interface BuildSchemasOptions
21
+ * @property {boolean} [closedSchemas] - Whether to add additionalProperties:false to object schemas
22
+ * @property {boolean} [pruneUnusedSchemas] - Whether to exclude schemas not referenced by operations
23
+ */
24
+ export interface BuildSchemasOptions {
25
+ closedSchemas?: boolean;
26
+ pruneUnusedSchemas?: boolean;
27
+ }
28
+ export type ComponentsSchemas = Record<string, any>;
29
+ /**
30
+ * Transforms the compiled WSDL catalog into OpenAPI schema components
31
+ *
32
+ * @function buildSchemas
33
+ * @param {CompiledCatalog} compiled - The compiled WSDL catalog containing types, aliases, and operations
34
+ * @param {BuildSchemasOptions} opts - Options for schema generation
35
+ * @returns {ComponentsSchemas} - A record of schema component names to their JSON Schema definitions
36
+ *
37
+ * @throws Will throw an error if there are unknown referenced types or bases while building schemas
38
+ *
39
+ * @example
40
+ * // Example usage: building schemas with closed schemas enforcement and unused schema pruning
41
+ * const schemas = buildSchemas(compiledCatalog, { closedSchemas: true, pruneUnusedSchemas: true });
42
+ */
43
+ export declare function buildSchemas(compiled: CompiledCatalog, opts: BuildSchemasOptions): ComponentsSchemas;
44
+ //# sourceMappingURL=buildSchemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildSchemas.d.ts","sourceRoot":"","sources":["../../src/openapi/buildSchemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAgB,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEhG;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAmIpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,CA0DpG"}
@@ -0,0 +1,207 @@
1
+ function isLiteralUnion(ts) {
2
+ // very naive: split by | and ensure each trimmed starts and ends with quotes
3
+ const parts = ts.split("|").map(p => p.trim()).filter(Boolean);
4
+ if (!parts.length)
5
+ return null;
6
+ if (parts.every(p => /^".*"$/.test(p))) {
7
+ return parts.map(p => p.slice(1, -1));
8
+ }
9
+ return null;
10
+ }
11
+ function primitiveSchema(ts) {
12
+ switch (ts) {
13
+ case "string":
14
+ return { type: "string" };
15
+ case "number":
16
+ return { type: "number" };
17
+ case "boolean":
18
+ return { type: "boolean" };
19
+ case "any":
20
+ return {};
21
+ default:
22
+ if (ts.endsWith("[]")) {
23
+ return { type: "array", items: primitiveSchema(ts.slice(0, -2)) };
24
+ }
25
+ return { $ref: `#/components/schemas/${ts}` };
26
+ }
27
+ }
28
+ function buildAliasSchema(a) {
29
+ const lit = isLiteralUnion(a.tsType);
30
+ if (lit) {
31
+ return { type: "string", enum: lit };
32
+ }
33
+ // If alias wraps primitive
34
+ if (["string", "number", "boolean", "any"].includes(a.tsType) || a.tsType.endsWith("[]")) {
35
+ return primitiveSchema(a.tsType);
36
+ }
37
+ // alias of another complex/alias type -> allOf wrapper preserves name
38
+ return { allOf: [{ $ref: `#/components/schemas/${a.tsType}` }] };
39
+ }
40
+ function isArrayWrapper(t) {
41
+ if (t.attrs.length !== 0)
42
+ return null;
43
+ if (t.elems.length !== 1)
44
+ return null;
45
+ const e = t.elems[0];
46
+ if (e.max !== "unbounded" && !(e.max > 1))
47
+ return null;
48
+ return { itemType: e.tsType };
49
+ }
50
+ function buildComplexSchema(t, closed, knownTypeNames, aliasNames) {
51
+ // Use knownTypeNames/aliasNames to validate $ref targets so we surface
52
+ // compiler issues early instead of emitting dangling references in OpenAPI output.
53
+ function refOrPrimitive(ts) {
54
+ switch (ts) {
55
+ case "string":
56
+ return { type: "string" };
57
+ case "number":
58
+ return { type: "number" };
59
+ case "boolean":
60
+ return { type: "boolean" };
61
+ case "any":
62
+ return {}; // intentionally permissive
63
+ default:
64
+ if (ts.endsWith("[]")) {
65
+ const inner = ts.slice(0, -2);
66
+ return { type: "array", items: refOrPrimitive(inner) };
67
+ }
68
+ if (!knownTypeNames.has(ts) && !aliasNames.has(ts)) {
69
+ // Fail fast: this indicates a mismatch between schemaCompiler output and OpenAPI builder expectations.
70
+ throw new Error(`[openapi] unknown referenced type '${ts}' while building schema '${t.name}'`);
71
+ }
72
+ return { $ref: `#/components/schemas/${ts}` };
73
+ }
74
+ }
75
+ const arrayWrap = isArrayWrapper(t);
76
+ if (arrayWrap) {
77
+ const item = refOrPrimitive(String(arrayWrap.itemType));
78
+ return { type: "array", items: item };
79
+ }
80
+ const properties = {};
81
+ const required = [];
82
+ // attributes
83
+ for (const a of t.attrs) {
84
+ properties[a.name] = refOrPrimitive(a.tsType);
85
+ if (a.use === "required")
86
+ required.push(a.name);
87
+ }
88
+ // elements
89
+ for (const e of t.elems) {
90
+ const baseSchema = refOrPrimitive(e.tsType);
91
+ const isArray = e.max === "unbounded" || (e.max > 1);
92
+ let schema = baseSchema;
93
+ if (isArray)
94
+ schema = { type: "array", items: baseSchema };
95
+ if (e.nillable) {
96
+ schema = { anyOf: [schema, { type: "null" }] };
97
+ }
98
+ properties[e.name] = schema;
99
+ if (e.name === "$value") {
100
+ // never required
101
+ }
102
+ else if (e.min >= 1) {
103
+ required.push(e.name);
104
+ }
105
+ }
106
+ const obj = {
107
+ type: "object",
108
+ properties,
109
+ };
110
+ if (required.length)
111
+ obj.required = Array.from(new Set(required));
112
+ if (closed)
113
+ obj.additionalProperties = false;
114
+ // inheritance via base => allOf
115
+ if (t.base) {
116
+ const baseName = t.base;
117
+ // Validate base reference explicitly (using helper ensures error if unknown)
118
+ if (!knownTypeNames.has(baseName) && !aliasNames.has(baseName)) {
119
+ throw new Error(`[openapi] unknown base type '${baseName}' while building schema '${t.name}'`);
120
+ }
121
+ obj.allOf = [{ $ref: `#/components/schemas/${baseName}` }, { ...obj }];
122
+ delete obj.type; // inner object part handled in allOf
123
+ delete obj.properties;
124
+ if (!required.length)
125
+ delete obj.required;
126
+ if (!closed)
127
+ delete obj.additionalProperties; // put closed only on leaf part
128
+ }
129
+ return obj;
130
+ }
131
+ /**
132
+ * Transforms the compiled WSDL catalog into OpenAPI schema components
133
+ *
134
+ * @function buildSchemas
135
+ * @param {CompiledCatalog} compiled - The compiled WSDL catalog containing types, aliases, and operations
136
+ * @param {BuildSchemasOptions} opts - Options for schema generation
137
+ * @returns {ComponentsSchemas} - A record of schema component names to their JSON Schema definitions
138
+ *
139
+ * @throws Will throw an error if there are unknown referenced types or bases while building schemas
140
+ *
141
+ * @example
142
+ * // Example usage: building schemas with closed schemas enforcement and unused schema pruning
143
+ * const schemas = buildSchemas(compiledCatalog, { closedSchemas: true, pruneUnusedSchemas: true });
144
+ */
145
+ export function buildSchemas(compiled, opts) {
146
+ const closed = !!opts.closedSchemas;
147
+ const schemas = {};
148
+ const typeNames = new Set(compiled.types.map(t => t.name));
149
+ const aliasNames = new Set(compiled.aliases.map(a => a.name));
150
+ // Build alias schemas first so complex types can reference them
151
+ for (const a of compiled.aliases) {
152
+ schemas[a.name] = buildAliasSchema(a);
153
+ }
154
+ for (const t of compiled.types) {
155
+ schemas[t.name] = buildComplexSchema(t, closed, typeNames, aliasNames);
156
+ }
157
+ if (opts.pruneUnusedSchemas) {
158
+ // Root references: each operation's inputElement.local, outputElement.local
159
+ const roots = new Set();
160
+ for (const op of compiled.operations) {
161
+ if (op.inputElement?.local)
162
+ roots.add(op.inputElement.local);
163
+ if (op.outputElement?.local)
164
+ roots.add(op.outputElement.local);
165
+ }
166
+ // BFS through $ref graph
167
+ const reachable = new Set();
168
+ const queue = Array.from(roots);
169
+ while (queue.length) {
170
+ const cur = queue.shift();
171
+ if (reachable.has(cur))
172
+ continue;
173
+ if (!schemas[cur])
174
+ continue; // unknown
175
+ reachable.add(cur);
176
+ const scan = (node) => {
177
+ if (!node || typeof node !== "object")
178
+ return;
179
+ if (node.$ref) {
180
+ const name = node.$ref.split("/").pop();
181
+ if (name && !reachable.has(name))
182
+ queue.push(name);
183
+ }
184
+ for (const v of Object.values(node))
185
+ scan(v);
186
+ };
187
+ scan(schemas[cur]);
188
+ }
189
+ // prune
190
+ for (const k of Object.keys(schemas)) {
191
+ if (!reachable.has(k))
192
+ delete schemas[k];
193
+ }
194
+ }
195
+ // Standard error envelope (always include)
196
+ if (!schemas.ErrorEnvelope) {
197
+ schemas.ErrorEnvelope = {
198
+ type: "object",
199
+ properties: {
200
+ message: { type: "string" },
201
+ faultCode: { type: "string" },
202
+ detail: { type: "object", additionalProperties: true },
203
+ },
204
+ };
205
+ }
206
+ return schemas;
207
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Path Segment Styling for OpenAPI Generation
3
+ *
4
+ * This module provides utilities for converting operation names to appropriately styled
5
+ * path segments in the OpenAPI specification. It supports multiple styling options to
6
+ * accommodate different API design preferences:
7
+ *
8
+ * - kebab-case: Transforms "GetUserDetails" to "get-user-details"
9
+ * - as-is: Preserves the original operation name without modification
10
+ * - lowercase: Converts to lowercase and removes all non-alphanumeric characters
11
+ *
12
+ * These transformations ensure that the generated API paths follow consistent conventions
13
+ * and are properly formatted for RESTful API design.
14
+ */
15
+ /**
16
+ * Path segment styling options for OpenAPI generation
17
+ *
18
+ * @property {"kebab"} string Convert to kebab-case (e.g., "get-user-details")
19
+ * @property {"asis"} string Keep the original operation name unchanged
20
+ * @property {"lower"} string Convert to lowercase and remove non-alphanumeric characters
21
+ */
22
+ export type PathStyle = "kebab" | "asis" | "lower";
23
+ /**
24
+ * Converts an operation name to a path segment according to the specified style
25
+ *
26
+ * This function transforms operation names like "GetUserDetails" or "createNewOrder"
27
+ * into appropriately styled path segments based on the selected style:
28
+ *
29
+ * - kebab: "GetUserDetails" → "get-user-details"
30
+ * - asis: "GetUserDetails" → "GetUserDetails"
31
+ * - lower: "GetUserDetails" → "getuserdetails"
32
+ *
33
+ * @param {string} name - Operation name to convert
34
+ * @param {PathStyle} style - Path segment style to apply
35
+ * @returns {string} - Formatted path segment
36
+ */
37
+ export declare function toPathSegment(name: string, style: PathStyle): string;
38
+ //# sourceMappingURL=casing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"casing.d.ts","sourceRoot":"","sources":["../../src/openapi/casing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAQnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAepE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Path Segment Styling for OpenAPI Generation
3
+ *
4
+ * This module provides utilities for converting operation names to appropriately styled
5
+ * path segments in the OpenAPI specification. It supports multiple styling options to
6
+ * accommodate different API design preferences:
7
+ *
8
+ * - kebab-case: Transforms "GetUserDetails" to "get-user-details"
9
+ * - as-is: Preserves the original operation name without modification
10
+ * - lowercase: Converts to lowercase and removes all non-alphanumeric characters
11
+ *
12
+ * These transformations ensure that the generated API paths follow consistent conventions
13
+ * and are properly formatted for RESTful API design.
14
+ */
15
+ /**
16
+ * Regular expression for detecting boundaries between words in camelCase
17
+ * Matches transitions from lowercase/digit to uppercase letter
18
+ */
19
+ const CAMEL_BOUNDARY = /([a-z0-9])([A-Z])/g;
20
+ /**
21
+ * Converts an operation name to a path segment according to the specified style
22
+ *
23
+ * This function transforms operation names like "GetUserDetails" or "createNewOrder"
24
+ * into appropriately styled path segments based on the selected style:
25
+ *
26
+ * - kebab: "GetUserDetails" → "get-user-details"
27
+ * - asis: "GetUserDetails" → "GetUserDetails"
28
+ * - lower: "GetUserDetails" → "getuserdetails"
29
+ *
30
+ * @param {string} name - Operation name to convert
31
+ * @param {PathStyle} style - Path segment style to apply
32
+ * @returns {string} - Formatted path segment
33
+ */
34
+ export function toPathSegment(name, style) {
35
+ switch (style) {
36
+ case "asis":
37
+ return name;
38
+ case "lower":
39
+ return name.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
40
+ case "kebab":
41
+ default:
42
+ // insert hyphen between camelCase boundaries then sanitize
43
+ return name
44
+ .replace(CAMEL_BOUNDARY, "$1-$2")
45
+ .replace(/[^A-Za-z0-9]+/g, "-")
46
+ .replace(/^-+|-+$/g, "")
47
+ .toLowerCase();
48
+ }
49
+ }
@@ -0,0 +1,57 @@
1
+ import { type CompiledCatalog } from "../compiler/schemaCompiler.js";
2
+ import type { PathStyle } from "./casing.js";
3
+ /**
4
+ * Options for OpenAPI generation from WSDL
5
+ *
6
+ * @interface GenerateOpenAPIOptions
7
+ * @property {string} [wsdl] - Path or URL to WSDL file (exclusive with catalogFile and compiledCatalog)
8
+ * @property {string} [catalogFile] - Path to existing compiled catalog.json (exclusive with wsdl and compiledCatalog)
9
+ * @property {CompiledCatalog} [compiledCatalog] - Pre-compiled catalog in memory (exclusive with wsdl and catalogFile)
10
+ * @property {string} [outFile] - Output path for generated OpenAPI specification
11
+ * @property {string} [title] - API title (defaults to derived service name)
12
+ * @property {string} [version] - API version for info.version (default 0.0.0)
13
+ * @property {string} [description] - API description
14
+ * @property {string[]} [servers] - List of server URLs
15
+ * @property {string} [basePath] - Base path prefix (e.g., /v1/soap)
16
+ * @property {PathStyle} [pathStyle] - Path segment style: kebab, asis, or lower
17
+ * @property {string} [defaultMethod] - Default HTTP method: post, get, put, patch, delete
18
+ * @property {string} [securityConfigFile] - Path to security.json configuration
19
+ * @property {string} [tagsFile] - Path to tags.json mapping operation names to tags
20
+ * @property {string} [opsFile] - Path to ops.json with per-operation overrides
21
+ * @property {boolean} [closedSchemas] - Whether to emit additionalProperties:false
22
+ * @property {boolean} [pruneUnusedSchemas] - Whether to exclude schemas not referenced by operations
23
+ * @property {boolean} [asYaml] - Force YAML output regardless of extension (deprecated)
24
+ * @property {boolean} [validate] - Whether to validate using swagger-parser
25
+ * @property {"default"|"first"|"service"} [tagStyle] - Heuristic for deriving tags
26
+ * @property {"json"|"yaml"|"both"} [format] - Output format (default: json)
27
+ * @property {boolean} [skipValidate] - Skip validation (default: false)
28
+ */
29
+ export interface GenerateOpenAPIOptions {
30
+ wsdl?: string;
31
+ catalogFile?: string;
32
+ outFile?: string;
33
+ title?: string;
34
+ version?: string;
35
+ description?: string;
36
+ servers?: string[];
37
+ basePath?: string;
38
+ pathStyle?: PathStyle;
39
+ defaultMethod?: string;
40
+ securityConfigFile?: string;
41
+ tagsFile?: string;
42
+ opsFile?: string;
43
+ closedSchemas?: boolean;
44
+ pruneUnusedSchemas?: boolean;
45
+ asYaml?: boolean;
46
+ validate?: boolean;
47
+ tagStyle?: "default" | "first" | "service";
48
+ compiledCatalog?: CompiledCatalog;
49
+ format?: "json" | "yaml" | "both";
50
+ skipValidate?: boolean;
51
+ }
52
+ export declare function generateOpenAPI(opts: GenerateOpenAPIOptions): Promise<{
53
+ doc: any;
54
+ jsonPath?: string;
55
+ yamlPath?: string;
56
+ }>;
57
+ //# sourceMappingURL=generateOpenAPI.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateOpenAPI.d.ts","sourceRoot":"","sources":["../../src/openapi/generateOpenAPI.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAiB,KAAK,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAInF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC;IAC3E,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAgJD"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * OpenAPI 3.1 Generator from WSDL
3
+ *
4
+ * This module generates OpenAPI 3.1 specifications from WSDL documents or compiled catalogs.
5
+ * It bridges the gap between SOAP web services and REST APIs by creating an OpenAPI
6
+ * representation that mirrors the TypeScript model generated by the WSDL client generator.
7
+ *
8
+ * The generator supports:
9
+ * - Multiple input sources (WSDL URL/path, pre-compiled catalog, or in-memory catalog)
10
+ * - Customizable paths, operations, and schemas
11
+ * - Security scheme configuration
12
+ * - Custom operation tagging and metadata
13
+ * - Multiple output formats (JSON, YAML, or both)
14
+ * - OpenAPI validation
15
+ */
16
+ import * as fs from "fs";
17
+ import * as path from "path";
18
+ import * as yaml from "js-yaml";
19
+ import { loadWsdl } from "../loader/wsdlLoader.js";
20
+ import { compileCatalog } from "../compiler/schemaCompiler.js";
21
+ import { buildSchemas } from "./buildSchemas.js";
22
+ import { buildPaths } from "./buildPaths.js";
23
+ import { buildSecurity, loadSecurityConfig } from "./security.js";
24
+ export async function generateOpenAPI(opts) {
25
+ // Normalize format (back-compat: asYaml overrides if provided and format not set)
26
+ let format = opts.format || (opts.asYaml ? "yaml" : "json");
27
+ if (format === "yaml" && opts.asYaml && opts.outFile && /\.json$/i.test(opts.outFile)) {
28
+ // user asked for yaml but provided .json path → we'll still switch extension
29
+ }
30
+ if (!opts.compiledCatalog && !opts.wsdl && !opts.catalogFile) {
31
+ throw new Error("Provide one of: compiledCatalog, wsdl, or catalogFile");
32
+ }
33
+ if ((opts.wsdl && opts.catalogFile) || (opts.compiledCatalog && (opts.wsdl || opts.catalogFile))) {
34
+ // Not strictly an error, but disallow ambiguous multi-source inputs to keep deterministic
35
+ // Users should supply only ONE source of truth.
36
+ throw new Error("Provide only one source: compiledCatalog OR wsdl OR catalogFile");
37
+ }
38
+ let compiled;
39
+ if (opts.compiledCatalog) {
40
+ compiled = opts.compiledCatalog;
41
+ }
42
+ else if (opts.catalogFile) {
43
+ const raw = fs.readFileSync(opts.catalogFile, "utf8");
44
+ compiled = JSON.parse(raw);
45
+ }
46
+ else {
47
+ const wsdlCatalog = await loadWsdl(String(opts.wsdl));
48
+ compiled = compileCatalog(wsdlCatalog, {
49
+ // minimal compiler options (no generation side effects needed here)
50
+ wsdl: String(opts.wsdl),
51
+ out: "",
52
+ imports: "js",
53
+ catalog: false,
54
+ primitive: { int64As: "string", bigIntegerAs: "string", decimalAs: "string", dateAs: "string" },
55
+ choice: "all-optional",
56
+ failOnUnresolved: false,
57
+ attributesKey: "$attributes",
58
+ nillableAsOptional: false,
59
+ clientName: undefined,
60
+ });
61
+ }
62
+ const title = opts.title || (compiled.serviceName ? `${compiled.serviceName} SOAP API` : "Generated SOAP API");
63
+ const infoVersion = opts.version || "0.0.0";
64
+ // Load external config files (optional)
65
+ const tagsMap = opts.tagsFile ? safeJson(opts.tagsFile) : undefined;
66
+ const opsOverrides = opts.opsFile ? safeJson(opts.opsFile) : undefined;
67
+ const securityCfg = loadSecurityConfig(opts.securityConfigFile);
68
+ const securityBuilt = buildSecurity(securityCfg);
69
+ // Build components.schemas
70
+ const schemas = buildSchemas(compiled, {
71
+ closedSchemas: opts.closedSchemas,
72
+ pruneUnusedSchemas: opts.pruneUnusedSchemas
73
+ });
74
+ // Build paths
75
+ const tagStyle = opts.tagStyle || "default";
76
+ const defaultTag = (() => {
77
+ if (tagStyle === "service")
78
+ return compiled.serviceName || "SOAP";
79
+ if (tagStyle === "first")
80
+ return "General"; // fallback; per-op derivation below
81
+ return compiled.serviceName || "SOAP";
82
+ })();
83
+ const paths = buildPaths(compiled, {
84
+ basePath: opts.basePath || "/",
85
+ pathStyle: opts.pathStyle || "kebab",
86
+ defaultMethod: opts.defaultMethod || "post",
87
+ tagsMap,
88
+ overrides: opsOverrides,
89
+ defaultTag,
90
+ opSecurity: securityBuilt.opSecurity,
91
+ opHeaderParameters: securityBuilt.opHeaderParameters,
92
+ });
93
+ // Apply tag heuristics for operations missing explicit tag if style=first
94
+ if (tagStyle === "first") {
95
+ for (const p of Object.values(paths)) {
96
+ for (const methodObj of Object.values(p)) {
97
+ if (Array.isArray(methodObj.tags) && methodObj.tags[0] === "General") {
98
+ const opId = methodObj.operationId || "Op";
99
+ const seg = opId.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean)[0] || "General";
100
+ methodObj.tags = [seg];
101
+ }
102
+ }
103
+ }
104
+ }
105
+ const doc = {
106
+ openapi: "3.1.0",
107
+ jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema",
108
+ info: { title, version: infoVersion },
109
+ paths,
110
+ components: {
111
+ schemas,
112
+ ...(securityBuilt.securitySchemes ? { securitySchemes: securityBuilt.securitySchemes } : {}),
113
+ ...(Object.keys(securityBuilt.headerParameters).length ? { parameters: securityBuilt.headerParameters } : {}),
114
+ },
115
+ };
116
+ if (opts.description)
117
+ doc.info.description = opts.description;
118
+ if (opts.servers && opts.servers.length) {
119
+ doc.servers = opts.servers.map(u => ({ url: u }));
120
+ }
121
+ if (opts.skipValidate !== true) {
122
+ try {
123
+ const parser = await import("@apidevtools/swagger-parser");
124
+ await parser.default.validate(JSON.parse(JSON.stringify(doc)));
125
+ console.log("OpenAPI validation: OK");
126
+ }
127
+ catch (e) {
128
+ console.error("OpenAPI validation failed:", e instanceof Error ? e.message : e);
129
+ throw e;
130
+ }
131
+ }
132
+ else {
133
+ console.log("OpenAPI validation skipped by flag");
134
+ }
135
+ // Determine base path for writing
136
+ let base = opts.outFile;
137
+ if (base) {
138
+ const extMatch = base.match(/\.(json|ya?ml)$/i);
139
+ if (extMatch) {
140
+ base = base.slice(0, -extMatch[0].length); // strip extension
141
+ }
142
+ }
143
+ let jsonPath;
144
+ let yamlPath;
145
+ if (opts.outFile) {
146
+ const dir = path.dirname(opts.outFile);
147
+ if (!fs.existsSync(dir))
148
+ fs.mkdirSync(dir, { recursive: true });
149
+ if (format === "json") {
150
+ jsonPath = base ? `${base}.json` : opts.outFile;
151
+ fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2), "utf8");
152
+ }
153
+ else if (format === "yaml") {
154
+ yamlPath = base ? `${base}.yaml` : opts.outFile.replace(/\.(json)$/i, ".yaml");
155
+ fs.writeFileSync(yamlPath, yaml.dump(doc), "utf8");
156
+ }
157
+ else { // both
158
+ jsonPath = `${base}.json`;
159
+ yamlPath = `${base}.yaml`;
160
+ fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2), "utf8");
161
+ fs.writeFileSync(yamlPath, yaml.dump(doc), "utf8");
162
+ }
163
+ }
164
+ return { doc, jsonPath, yamlPath };
165
+ }
166
+ function safeJson(file) {
167
+ try {
168
+ return JSON.parse(fs.readFileSync(file, "utf8"));
169
+ }
170
+ catch (e) {
171
+ console.warn(`⚠️ Failed to parse JSON file '${file}': ${e instanceof Error ? e.message : String(e)}`);
172
+ return undefined;
173
+ }
174
+ }