@techspokes/typescript-wsdl-client 0.3.0 → 0.4.2

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 (37) hide show
  1. package/README.md +82 -45
  2. package/dist/cli.js +34 -21
  3. package/dist/compiler/schemaCompiler.d.ts +3 -2
  4. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  5. package/dist/compiler/schemaCompiler.js +8 -7
  6. package/dist/config.d.ts +15 -26
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +15 -6
  9. package/dist/emit/catalogEmitter.d.ts +3 -0
  10. package/dist/emit/catalogEmitter.d.ts.map +1 -0
  11. package/dist/emit/catalogEmitter.js +10 -0
  12. package/dist/emit/clientEmitter.d.ts +1 -4
  13. package/dist/emit/clientEmitter.d.ts.map +1 -1
  14. package/dist/emit/clientEmitter.js +336 -51
  15. package/dist/emit/typesEmitter.d.ts.map +1 -1
  16. package/dist/emit/typesEmitter.js +7 -1
  17. package/dist/emit/utilsEmitter.d.ts +3 -0
  18. package/dist/emit/utilsEmitter.d.ts.map +1 -0
  19. package/dist/emit/utilsEmitter.js +33 -0
  20. package/dist/index.d.ts +0 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +47 -44
  23. package/dist/loader/wsdlLoader.js +1 -1
  24. package/dist/util/{xml.d.ts → tools.d.ts} +8 -1
  25. package/dist/util/tools.d.ts.map +1 -0
  26. package/dist/util/{xml.js → tools.js} +27 -0
  27. package/package.json +3 -2
  28. package/dist/emit/metaEmitter.d.ts +0 -4
  29. package/dist/emit/metaEmitter.d.ts.map +0 -1
  30. package/dist/emit/metaEmitter.js +0 -9
  31. package/dist/emit/opsEmitter.d.ts +0 -4
  32. package/dist/emit/opsEmitter.d.ts.map +0 -1
  33. package/dist/emit/opsEmitter.js +0 -13
  34. package/dist/emit/runtimeEmitter.d.ts +0 -2
  35. package/dist/emit/runtimeEmitter.d.ts.map +0 -1
  36. package/dist/emit/runtimeEmitter.js +0 -147
  37. package/dist/util/xml.d.ts.map +0 -1
@@ -1,24 +1,7 @@
1
+ // noinspection UnreachableCodeJS,JSUnusedLocalSymbols
1
2
  import fs from "node:fs";
2
- import { pascal } from "../util/xml.js";
3
- export function emitClient(outFile, compiled, opts) {
4
- const ext = opts.importExt ?? ".js";
5
- const lines = [];
6
- lines.push(`import { createSoapClient, toSoapArgs, fromSoapResult } from "./runtime${ext}";`);
7
- lines.push(`import { ATTR_SPEC, CHILD_TYPE, PROP_META } from "./meta${ext}";`);
8
- lines.push(`import type * as T from "./types${ext}";`);
9
- lines.push("");
10
- // Derive class name: prefer explicit override, else WSDL service name, else base filename, else default
11
- const overrideName = (opts.clientName || "").trim();
12
- const svcName = compiled.serviceName && pascal(compiled.serviceName);
13
- const fileBase = (() => {
14
- const uri = compiled.wsdlUri || "";
15
- // extract last path segment and strip extension for both URL and file path
16
- const seg = uri.split(/[\\/]/).pop() || "";
17
- const noExt = seg.replace(/\.[^.]+$/, "");
18
- return noExt ? pascal(noExt) : "";
19
- })();
20
- const className = overrideName || ((svcName || fileBase) ? `${svcName || fileBase}SoapClient` : "GeneratedSoapClient");
21
- // Helpers for emitting safe method names
3
+ import { pascal, deriveClientName, pascalToSnakeCase } from "../util/tools.js";
4
+ export function emitClient(outFile, compiled) {
22
5
  const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
23
6
  const reserved = new Set([
24
7
  "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
@@ -27,42 +10,344 @@ export function emitClient(outFile, compiled, opts) {
27
10
  "true", "try", "typeof", "var", "void", "while", "with", "as", "implements", "interface",
28
11
  "let", "package", "private", "protected", "public", "static", "yield", "constructor"
29
12
  ]);
30
- lines.push(`export class ${className} {`);
31
- lines.push(` constructor(private source: string | any, private attributesKey: string = "${opts.attributesKey || "$attributes"}") {}`);
32
- lines.push(` async _client() {`);
33
- lines.push(` if (typeof this.source === 'string') return createSoapClient({ wsdlUrl: this.source });`);
34
- lines.push(` return this.source;`);
35
- lines.push(` }`);
13
+ const ext = compiled.options.imports ?? "bare";
14
+ const suffix = ("bare" === ext) ? "" : `.${ext}`;
15
+ const methods = [];
16
+ // get the class name for the client
17
+ const clientName = deriveClientName(compiled);
18
+ const clientConstant = pascalToSnakeCase(clientName).toUpperCase();
19
+ // Build the dynamic methods for the client class
36
20
  for (const op of compiled.operations) {
37
- const m = op.name;
21
+ const m = isValidIdent(op.name) && !reserved.has(op.name)
22
+ ? op.name
23
+ : `[${JSON.stringify(op.name)}]`;
38
24
  const inTypeName = op.inputElement ? pascal(op.inputElement.local) : undefined;
39
25
  const outTypeName = op.outputElement ? pascal(op.outputElement.local) : undefined;
40
26
  const inTs = inTypeName ? `T.${inTypeName}` : `any`;
41
27
  const outTs = outTypeName ? `T.${outTypeName}` : `any`;
42
- const inMetaKey = inTypeName ?? m;
43
- const outMetaKey = outTypeName ?? m;
44
28
  const secHints = Array.isArray(op.security) && op.security.length ? op.security : [];
45
- const methodHeader = (isValidIdent(m) && !reserved.has(m))
46
- ? ` async ${m}(args: ${inTs}): Promise<${outTs}> {`
47
- : ` async [${JSON.stringify(m)}](args: ${inTs}): Promise<${outTs}> {`;
48
- const clientCall = (isValidIdent(m) && !reserved.has(m))
49
- ? `c.${m}`
50
- : `c[${JSON.stringify(m)}]`;
51
- lines.push(` /** SOAPAction: ${op.soapAction}${secHints.length ? `\n * Security (WSDL policy hint): ${secHints.join(", ")}` : ""} */`);
52
- lines.push(methodHeader);
53
- lines.push(` const c: any = await this._client();`);
54
- if (secHints.length) {
55
- lines.push(` if (!c || !c.security) { console.warn("[wsdl-client] Operation '${m}' may require security: ${secHints.join(", ")}. Configure client.setSecurity(...) or pass { security } to createSoapClient()."); }`);
29
+ const secHintsStr = secHints.length ? `\n *\n * Security (WSDL policy hint): ${secHints.join(", ")}` : "";
30
+ const methodTemplate = `
31
+
32
+ /**
33
+ * Calls the ${m} operation of the ${clientName}.${secHintsStr}
34
+ *
35
+ * @param args - The request arguments for the ${m} operation.
36
+ * @returns A promise resolving to the operation response containing data, headers, response raw XML, and request raw XML.
37
+ */
38
+ async ${m}<HeadersType = Record<string, unknown>>(
39
+ args: ${inTs}
40
+ ): Promise<${clientName}Response<${outTs}, HeadersType>> {
41
+ return this.call<${inTs}, ${outTs}, HeadersType>(
42
+ args,
43
+ ${JSON.stringify(m)},
44
+ ${JSON.stringify(m)}
45
+ );
46
+ }`;
47
+ methods.push(methodTemplate);
48
+ }
49
+ const methodsBody = methods.join("\n");
50
+ // noinspection JSFileReferences,JSUnresolvedReference,CommaExpressionJS,JSDuplicatedDeclaration,ReservedWordAsName,JSCommentMatchesSignature,JSValidateTypes,JSIgnoredPromiseFromCall,BadExpressionStatementJS,ES6UnusedImports,JSUnnecessarySemicolon
51
+ const classTemplate = `// noinspection JSAnnotator
52
+
53
+ /**
54
+ * Generated ${clientName} client class.
55
+ * This class wraps the node-soap client and provides strongly-typed methods for each operation.
56
+ */
57
+ import * as soap from "soap";
58
+ import type * as T from "./types${suffix}";
59
+ import type {${clientName}DataTypes} from "./utils${suffix}";
60
+ import {${clientConstant}_DATA_TYPES} from "./utils${suffix}";
61
+
62
+ /**
63
+ * Represents the response structure for ${clientName} operations.
64
+ * Contains the response object, SOAP headers object, and raw response/request XML.
65
+ *
66
+ * @typeParam ResponseType - The type of the response data.
67
+ * @typeParam HeadersType - The type of the headers (default: Record<string, unknown>).
68
+ */
69
+ export type ${clientName}Response<ResponseType, HeadersType = Record<string, unknown>> = {
70
+ response: ResponseType;
71
+ headers: HeadersType;
72
+ responseRaw: string;
73
+ requestRaw: string;
74
+ }
75
+
76
+ /**
77
+ * @class ${clientName}
78
+ *
79
+ * Represents a SOAP client wrapper around the ${clientName} service.
80
+ * Provides async methods for each operation defined in the WSDL.
81
+ * Handles serialization/deserialization of request/response data
82
+ * according to the WSDL-defined data types.
83
+ * Supports security settings (hints) and custom attribute handling.
84
+ *
85
+ * @property source - The WSDL URL or local file path to use.
86
+ * @property options - Optional SOAP client configuration options.
87
+ * @property security - Optional SOAP security settings to apply.
88
+ * @property attributesKeyIn - Key name for input attribute bags (default: "$attributes").
89
+ * @property attributesKeyOut - Key name for output attribute bags (default: "attributes").
90
+ * @property client - The underlying node-soap client instance.
91
+ * @property dataTypes - Metadata for data types used in serialization/deserialization.
92
+ * @property dataTypes.Attributes - Maps type names to lists of property names that should be treated as XML attributes.
93
+ * @property dataTypes.ChildrenTypes - Maps type names to their child element types for recursive processing.
94
+ *
95
+ *
96
+ * @note Have fun with the generated client! If TechSpokes made your day (or a week),
97
+ * please consider supporting this project: https://github.com/TechSpokes/typescript-wsdl-client?tab=readme-ov-file#readme
98
+ * Thanks for using it!
99
+ *
100
+ * Cheers,
101
+ * Serge Liatko, TechSpokes, https://www.techspokes.com | https://www.techspokes.store
102
+ * https://www.linkedin.com/in/sergeliatko/ | https://www.linkedin.com/company/techspokes/
103
+ *
104
+ * @license MIT
105
+ */
106
+ export class ${clientName} {
107
+ protected source: string;
108
+ protected options?: soap.IOptions;
109
+ protected security?: soap.ISecurity;
110
+ protected attributesKeyIn: string;
111
+ protected attributesKeyOut: string;
112
+ protected client?: soap.Client;
113
+ protected dataTypes: ${clientName}DataTypes = ${clientConstant}_DATA_TYPES;
114
+
115
+ /**
116
+ * Creates a new ${clientName} instance.
117
+ *
118
+ * @param options - Configuration options for the service.
119
+ * @param options.source - The WSDL URL or local file path to use.
120
+ * @param [options.options] - Optional SOAP client configuration options.
121
+ * @param [options.security] - Optional SOAP security settings to apply.
122
+ * @param [options.attributesKeyIn] - Key name for input attribute bags (default: "$attributes").
123
+ * @param [options.attributesKeyOut] - Key name for output attribute bags (default: "attributes").
124
+ */
125
+ constructor(options: {
126
+ source: string,
127
+ options?: soap.IOptions,
128
+ security?: soap.ISecurity,
129
+ attributesKeyIn?: string,
130
+ attributesKeyOut?: string
131
+ }) {
132
+ this.source = options.source;
133
+ this.attributesKeyIn = options.attributesKeyIn ?? "$attributes";
134
+ this.attributesKeyOut = options.attributesKeyOut ?? "attributes";
135
+ if (options.options) {
136
+ this.options = options.options;
137
+ }
138
+ if (options.security) {
139
+ this.security = options.security;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Lazily initializes and returns the underlying SOAP client.
145
+ * If the client is not yet created or its WSDL is missing, a new client is instantiated.
146
+ *
147
+ * @returns The initialized SOAP client instance.
148
+ * @throws Error if the WSDL source is invalid or client creation fails.
149
+ */
150
+ async soapClient(): Promise<soap.Client> {
151
+ // If client is not initialized or has no WSDL source, create a new one
152
+ if (!this.client || !this.client.wsdl) {
153
+ // Note: source can be a URL or a local WSDL file path
154
+ if (!this.source) {
155
+ throw new Error("WSDL source must be a non-empty string URL or file path.");
156
+ }
157
+ try {
158
+ // Create the SOAP client using the provided source and options
159
+ this.client = await soap.createClientAsync(this.source, this.options || {});
160
+ if (this.security) {
161
+ this.client.setSecurity(this.security);
162
+ }
163
+ } catch (e) {
164
+ throw new Error("Error creating SOAP client: " + (e instanceof Error ? e.message : String(e)));
165
+ }
166
+ }
167
+ return this.client;
168
+ }
169
+ ${methodsBody}
170
+
171
+ /**
172
+ * Calls a specified SOAP operation with the provided arguments and response type.
173
+ *
174
+ * @param args - The request arguments/payload for the operation.
175
+ * @param operation - The name of the SOAP operation to invoke.
176
+ * @param responseType - The metadata type name for response deserialization.
177
+ * @returns A promise resolving to the operation response containing data, headers, and raw XML.
178
+ * @throws Error if the specified operation is not found on the SOAP client.
179
+ */
180
+ protected async call<RequestType, ResponseType, HeadersType>(
181
+ args: RequestType,
182
+ operation: string,
183
+ responseType: string
184
+ ): Promise<${clientName}Response<ResponseType, HeadersType>> {
185
+ const client = await this.soapClient();
186
+ if (!client[operation] || typeof client[operation] !== "function") {
187
+ throw new Error("Operation not found on SOAP client: " + operation);
188
+ }
189
+ // Convert TypeScript object to the format expected by node-soap
190
+ const soapArgs = this.toSoapArgs(args, responseType);
191
+ return new Promise((resolve, reject) => {
192
+ client[operation](soapArgs, (err: any, result: any, rawResponse: string, soapHeader: any, rawRequest: string) => {
193
+ if (err) {
194
+ reject(err);
195
+ } else {
196
+ // Convert the SOAP response back to TypeScript object
197
+ const response = this.fromSoapResult(result, responseType);
198
+ resolve({
199
+ response,
200
+ headers: soapHeader || {},
201
+ responseRaw: rawResponse,
202
+ requestRaw: rawRequest
203
+ });
56
204
  }
57
- lines.push(` const meta = { ATTR_SPEC, CHILD_TYPE, PROP_META } as const;`);
58
- lines.push(` const soapArgs = toSoapArgs(args as any, ${JSON.stringify(inMetaKey)}, meta, this.attributesKey);`);
59
- lines.push(` return new Promise((resolve, reject) => {`);
60
- lines.push(` ${clientCall}(soapArgs, (err: any, result: any) => {`);
61
- lines.push(` if (err) reject(err); else resolve(fromSoapResult(result, ${JSON.stringify(outMetaKey)}, meta, this.attributesKey));`);
62
- lines.push(` });`);
63
- lines.push(` });`);
64
- lines.push(` }`);
65
- }
66
- lines.push(`}`);
67
- fs.writeFileSync(outFile, lines.join("\n"), "utf8");
205
+ });
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Converts TypeScript objects to the format expected by node-soap for SOAP requests.
211
+ * Handles the conversion of object properties to XML attributes and elements based on metadata.
212
+ *
213
+ * @param value - The value to convert (object, array, or primitive)
214
+ * @param typeName - Optional type name for metadata lookup
215
+ * @returns Converted value ready for SOAP serialization
216
+ */
217
+ protected toSoapArgs(value: any, typeName?: string): any {
218
+ // Pass through null/undefined unchanged
219
+ if (value == null) {
220
+ return value;
221
+ }
222
+ // Pass through primitives (string, number, boolean) unchanged
223
+ if (typeof value !== "object") {
224
+ return value;
225
+ }
226
+ // Recursively process array elements with same type context
227
+ if (Array.isArray(value)) {
228
+ return value.map(v => this.toSoapArgs(v, typeName));
229
+ }
230
+
231
+ /**
232
+ * Normalizes values to strings for XML attribute serialization.
233
+ * Handles null, boolean, number, and other types appropriately.
234
+ */
235
+ const normalize = (v: unknown): string => {
236
+ if (v == null) return "";
237
+ if (typeof v === "boolean") return v ? "true" : "false";
238
+ if (typeof v === "number") return Number.isFinite(v) ? String(v) : "";
239
+ return String(v);
240
+ };
241
+
242
+ // Get metadata for this specific type to know which props are attributes
243
+ const attributesList = (typeName && this.dataTypes?.Attributes?.[typeName]) || [];
244
+ const childrenTypes = (typeName && this.dataTypes?.ChildrenTypes?.[typeName]) || {};
245
+
246
+ const out: any = {};
247
+ const attributesBag: Record<string, any> = {};
248
+
249
+ // Preserve text content for mixed XML elements (text + child elements)
250
+ if ("$value" in value) {
251
+ out.$value = (value as any).$value;
252
+ }
253
+
254
+ // Extract pre-existing attributes from input object's attribute bag
255
+ const inAttrNode = (value as any)[this.attributesKeyIn] ?? (value as any)["attributes"];
256
+ if (inAttrNode && typeof inAttrNode === "object") {
257
+ // Normalize all attribute values to strings for XML serialization
258
+ for (const [ak, av] of Object.entries(inAttrNode)) {
259
+ attributesBag[ak] = normalize(av);
260
+ }
261
+ }
262
+
263
+ // Categorize each property as either XML attribute or child element
264
+ for (const [k, v] of Object.entries<any>(value)) {
265
+ // Skip special properties that are handled separately above
266
+ if (k === "$value" || k === this.attributesKeyIn || k === "attributes") {
267
+ continue;
268
+ }
269
+
270
+ // Check metadata to see if this property should be an XML attribute
271
+ if (attributesList.includes(k)) {
272
+ attributesBag[k] = normalize(v);
273
+ continue;
274
+ }
275
+
276
+ // Everything else becomes a child element, recursively processed
277
+ const childType = (childrenTypes as any)[k] as string | undefined;
278
+ out[k] = Array.isArray(v)
279
+ ? v.map(node => this.toSoapArgs(node, childType))
280
+ : this.toSoapArgs(v, childType);
281
+ }
282
+
283
+ // Only add attributes bag if we actually have attributes to serialize
284
+ if (Object.keys(attributesBag).length) {
285
+ out[this.attributesKeyOut] = attributesBag; // renders as XML attributes
286
+ }
287
+
288
+ return out;
289
+ }
290
+
291
+ /**
292
+ * Converts SOAP response nodes into application DTOs based on metadata mapping.
293
+ *
294
+ * @param node - The raw SOAP response node.
295
+ * @param typeName - Optional metadata key to guide deserialization.
296
+ * @returns The deserialized object matching your DTO shape.
297
+ */
298
+ protected fromSoapResult(node: any, typeName?: string): any {
299
+ // Pass through null/undefined unchanged
300
+ if (node == null) {
301
+ return node;
302
+ }
303
+ // Pass through primitives unchanged
304
+ if (typeof node !== "object") {
305
+ return node;
306
+ }
307
+ // Recursively process array elements
308
+ if (Array.isArray(node)) {
309
+ return node.map(n => this.fromSoapResult(n, typeName));
310
+ }
311
+
312
+ // Get child type mapping for recursive processing with correct types
313
+ const childrenTypes = (typeName && this.dataTypes?.ChildrenTypes?.[typeName]) || {};
314
+ const result: any = {};
315
+
316
+ // Preserve text content for mixed XML elements
317
+ if ("$value" in node) {
318
+ result.$value = (node as any).$value;
319
+ }
320
+
321
+ // Extract attributes from various node-soap attribute containers
322
+ // Different SOAP parsers may use "attributes", "$", or custom keys
323
+ const inAttrNode = (node as any)[this.attributesKeyOut] || (node as any)["attributes"] || (node as any)["$"];
324
+ if (inAttrNode && typeof inAttrNode === "object") {
325
+ // Promote all attributes to top-level properties for easier TS access
326
+ Object.assign(result, inAttrNode);
327
+ }
328
+
329
+ // Process remaining properties as child elements
330
+ for (const [k, v] of Object.entries<any>(node)) {
331
+ // Skip all possible attribute containers and special properties
332
+ if (k === this.attributesKeyOut || k === "attributes" || k === "$" || k === "$value") {
333
+ continue;
334
+ }
335
+ // Recursively convert child elements with their specific type info
336
+ const childType = (childrenTypes as any)[k] as string | undefined;
337
+ result[k] = Array.isArray(v)
338
+ ? v.map(node => this.fromSoapResult(node, childType))
339
+ : this.fromSoapResult(v, childType);
340
+ }
341
+
342
+ return result;
343
+ }
344
+ }
345
+ `;
346
+ try {
347
+ fs.writeFileSync(outFile, classTemplate.replace(`// noinspection JSAnnotator\n\n`, ''), "utf8");
348
+ console.log(`Client class written to ${outFile}`);
349
+ }
350
+ catch (e) {
351
+ console.log(`Failed to write catalog to ${outFile}`);
352
+ }
68
353
  }
@@ -1 +1 @@
1
- {"version":3,"file":"typesEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/typesEmitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEjF;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QA2InE"}
1
+ {"version":3,"file":"typesEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/typesEmitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEjF;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QAgJnE"}
@@ -135,5 +135,11 @@ export function emitTypes(outFile, compiled) {
135
135
  lines.push("}");
136
136
  lines.push("");
137
137
  }
138
- fs.writeFileSync(outFile, lines.join("\n"), "utf8");
138
+ try {
139
+ fs.writeFileSync(outFile, lines.join("\n"), "utf8");
140
+ console.log(`Types written to ${outFile}`);
141
+ }
142
+ catch (e) {
143
+ console.error(`Failed to write types to ${outFile}:`, e);
144
+ }
139
145
  }
@@ -0,0 +1,3 @@
1
+ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
2
+ export declare function emitUtils(outFile: string, compiled: CompiledCatalog): void;
3
+ //# sourceMappingURL=utilsEmitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilsEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/utilsEmitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAGnE,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QAiCnE"}
@@ -0,0 +1,33 @@
1
+ import fs from "node:fs";
2
+ import { deriveClientName, pascalToSnakeCase } from "../util/tools.js";
3
+ export function emitUtils(outFile, compiled) {
4
+ const clientName = deriveClientName(compiled);
5
+ const clientConstant = pascalToSnakeCase(clientName).toUpperCase();
6
+ const { attrSpec, childType } = compiled.meta;
7
+ if (!attrSpec || !childType) {
8
+ throw new Error("Metadata not found in compiled catalog. Ensure schemaCompiler runs before metaEmitter.");
9
+ }
10
+ if (typeof attrSpec !== "object" || typeof childType !== "object") {
11
+ throw new Error("Invalid metadata structure. Expected objects for Attributes and ChildrenTypes.");
12
+ }
13
+ const metas = JSON.stringify({ Attributes: attrSpec, ChildrenTypes: childType }, null, 2);
14
+ const dataTypes = `/**
15
+ * ${clientName} WSDL data types for proper serialization/deserialization.
16
+ * Used to distinguish between XML attributes and elements during conversion.
17
+ */
18
+ export interface ${clientName}DataTypes {
19
+ /** Maps type names to lists of property names that should be treated as XML attributes */
20
+ Attributes: Record<string, readonly string[]>;
21
+ /** Maps type names to their child element types for recursive processing */
22
+ ChildrenTypes: Record<string, Readonly<Record<string, string>>>;
23
+ }
24
+
25
+ export const ${clientConstant}_DATA_TYPES: ${clientName}DataTypes = ${metas} as const;\n`;
26
+ try {
27
+ fs.writeFileSync(outFile, dataTypes, "utf8");
28
+ console.log(`Utils written to ${outFile}`);
29
+ }
30
+ catch (e) {
31
+ console.error(`Failed to write utils to ${outFile}:`, e);
32
+ }
33
+ }
package/dist/index.d.ts CHANGED
@@ -4,8 +4,4 @@ export declare function compileWsdlToProject(input: {
4
4
  outDir: string;
5
5
  options?: CompilerOptions;
6
6
  }): Promise<void>;
7
- export { compileCatalog } from "./compiler/schemaCompiler.js";
8
- export type { PrimitiveOptions } from "./xsd/primitives.js";
9
- export { xsdToTsPrimitive } from "./xsd/primitives.js";
10
- export type { CompilerOptions } from "./config.js";
11
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAcnD,wBAAsB,oBAAoB,CACtC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,iBA6CrE;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAUjD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,iBAsDnE"}
package/dist/index.js CHANGED
@@ -1,57 +1,60 @@
1
- import { defaultOptions } from "./config.js";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
2
3
  import { loadWsdl } from "./loader/wsdlLoader.js";
3
4
  import { compileCatalog } from "./compiler/schemaCompiler.js";
4
5
  import { emitTypes } from "./emit/typesEmitter.js";
5
- import { emitMeta } from "./emit/metaEmitter.js";
6
- import { emitOperations } from "./emit/opsEmitter.js";
6
+ import { emitUtils } from "./emit/utilsEmitter.js";
7
+ import { emitCatalog } from "./emit/catalogEmitter.js";
7
8
  import { emitClient } from "./emit/clientEmitter.js";
8
- import { emitRuntime } from "./emit/runtimeEmitter.js";
9
- import fs from "node:fs";
10
- import path from "node:path";
11
- // Entry point for programmatic API: compile a WSDL into a set of TypeScript files in a project layout
9
+ import { TYPESCRIPT_WSDL_CLIENT_DEFAULT_COMPLIER_OPTIONS } from "./config.js";
12
10
  // noinspection JSUnusedGlobalSymbols
13
11
  export async function compileWsdlToProject(input) {
14
- // Merge defaults with user overrides
15
- const baseOptions = { ...defaultOptions, ...(input.options || {}) };
16
- // Backward compatibility: legacy flags map to the new primitive preferences if not set
17
- const normalizedPrimitive = { ...(baseOptions.primitive || {}) };
18
- if (baseOptions.dateAs && normalizedPrimitive.dateAs == null) {
19
- // dateAs legacy: choose Date or string based on flag
20
- normalizedPrimitive.dateAs = baseOptions.dateAs === "date" ? "Date" : "string";
21
- }
22
- if (baseOptions.intAs && (normalizedPrimitive.int64As == null || normalizedPrimitive.bigIntegerAs == null)) {
23
- // intAs legacy: apply to both 64-bit and big integer types
24
- const as = baseOptions.intAs;
25
- if (normalizedPrimitive.int64As == null)
26
- normalizedPrimitive.int64As = as;
27
- if (normalizedPrimitive.bigIntegerAs == null)
28
- normalizedPrimitive.bigIntegerAs = as;
29
- }
30
- // Final merged options including computed primitive mappings
31
- const finalOptions = { ...baseOptions, primitive: normalizedPrimitive };
32
- // Load WSDL definitions and schema catalog (remote or local file)
12
+ // Merge defaults with overrides, always set wsdl+out
13
+ const finalOptions = {
14
+ ...TYPESCRIPT_WSDL_CLIENT_DEFAULT_COMPLIER_OPTIONS,
15
+ ...(input.options || {}),
16
+ wsdl: input.wsdl,
17
+ out: input.outDir,
18
+ };
19
+ // Load & compile
33
20
  const wsdlCatalog = await loadWsdl(input.wsdl);
34
21
  console.log(`Loaded WSDL: ${wsdlCatalog.wsdlUri}`);
35
- // Compile schemas and operations into intermediate data structures
36
- const compiledCatalog = compileCatalog(wsdlCatalog, finalOptions);
22
+ if (wsdlCatalog.schemas.length === 0) {
23
+ throw new Error(`No schemas found in WSDL: ${input.wsdl}`);
24
+ }
37
25
  console.log(`Schemas discovered: ${wsdlCatalog.schemas.length}`);
38
- console.log(`Compiled types: ${compiledCatalog.types.length}`);
39
- console.log(`Operations: ${compiledCatalog.operations.length}`);
40
- // Prepare output directory for generated files
41
- fs.mkdirSync(input.outDir, { recursive: true });
42
- // Define target paths
26
+ const compiled = compileCatalog(wsdlCatalog, finalOptions);
27
+ console.log(`Compiled WSDL: ${wsdlCatalog.wsdlUri}`);
28
+ // check if we have any types and operations
29
+ if (compiled.types.length === 0) {
30
+ throw new Error(`No types compiled from WSDL: ${input.wsdl}`);
31
+ }
32
+ else {
33
+ console.log(`Types discovered: ${compiled.types.length}`);
34
+ }
35
+ if (compiled.operations.length === 0) {
36
+ throw new Error(`No operations compiled from WSDL: ${input.wsdl}`);
37
+ }
38
+ else {
39
+ console.log(`Operations discovered: ${compiled.operations.length}`);
40
+ }
41
+ // Emit artifacts
43
42
  const typesFile = path.join(input.outDir, "types.ts");
44
43
  const metaFile = path.join(input.outDir, "meta.ts");
45
- const opsFile = path.join(input.outDir, "operations.json");
44
+ const catalogFile = path.join(input.outDir, "catalog.json");
46
45
  const clientFile = path.join(input.outDir, "client.ts");
47
- const runtimeFile = path.join(input.outDir, "runtime.ts");
48
- // Emit code artifacts: types, metadata, operations listing, client harness, runtime helpers
49
- emitTypes(typesFile, compiledCatalog);
50
- emitMeta(metaFile, compiledCatalog, finalOptions);
51
- emitOperations(opsFile, compiledCatalog);
52
- emitClient(clientFile, compiledCatalog, finalOptions);
53
- emitRuntime(runtimeFile);
46
+ // Prepare output dir
47
+ try {
48
+ fs.mkdirSync(input.outDir, { recursive: true });
49
+ }
50
+ catch (e) {
51
+ throw new Error(`Failed to create output directory '${input.outDir}': ${e instanceof Error ? e.message : String(e)}`);
52
+ }
53
+ // Emit files
54
+ emitClient(clientFile, compiled);
55
+ emitTypes(typesFile, compiled);
56
+ emitUtils(metaFile, compiled);
57
+ if (compiled.options.catalog) {
58
+ emitCatalog(catalogFile, compiled);
59
+ }
54
60
  }
55
- // Re-export public API for library consumers
56
- export { compileCatalog } from "./compiler/schemaCompiler.js";
57
- export { xsdToTsPrimitive } from "./xsd/primitives.js";
@@ -2,7 +2,7 @@ import { XMLParser } from "fast-xml-parser";
2
2
  import { fetchText } from "./fetch.js";
3
3
  import path from "node:path";
4
4
  // noinspection ES6UnusedImports
5
- import { getChildrenWithLocalName, normalizeArray } from "../util/xml.js";
5
+ import { getChildrenWithLocalName, normalizeArray } from "../util/tools.js";
6
6
  const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
7
7
  export async function loadWsdl(wsdlUrlOrPath) {
8
8
  const { uri: wsdlUri, text } = await fetchText(wsdlUrlOrPath);
@@ -1,3 +1,4 @@
1
+ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
1
2
  /** Normalize a possibly-single value into an array. */
2
3
  export declare function normalizeArray<T>(x: T | T[] | undefined | null): T[];
3
4
  /** Collect direct children whose LOCAL name matches (prefix-agnostic). */
@@ -10,4 +11,10 @@ export declare function resolveQName(qname: string, defaultNS: string, prefixes:
10
11
  ns: string;
11
12
  local: string;
12
13
  };
13
- //# sourceMappingURL=xml.d.ts.map
14
+ /** Derive a SOAP client class name from CompilerOptions and compiled catalog. */
15
+ export declare function deriveClientName(compiled: CompiledCatalog): string;
16
+ /** Explode a PascalCase string into its constituent segments. */
17
+ export declare function explodePascal(s: string): string[];
18
+ /** Convert a PascalCase string to snake_case (lowercase + underscores). */
19
+ export declare function pascalToSnakeCase(s: string): string;
20
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/util/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,EAAE,CAGpE;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,CASxE;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAK/E;AAED,6DAA6D;AAC7D,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CA6BxC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAS/B;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,GACxB,MAAM,CAWR;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAMjD;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAInD"}
@@ -66,3 +66,30 @@ export function resolveQName(qname, defaultNS, prefixes) {
66
66
  }
67
67
  return { ns: defaultNS, local: qname };
68
68
  }
69
+ /** Derive a SOAP client class name from CompilerOptions and compiled catalog. */
70
+ export function deriveClientName(compiled) {
71
+ const overrideName = (compiled.options.clientName || "").trim();
72
+ const svcName = compiled.serviceName ? pascal(compiled.serviceName) : "";
73
+ const fileBase = (() => {
74
+ const uri = compiled.wsdlUri || "";
75
+ const seg = uri.split(/[\\/]/).pop() || "";
76
+ const noExt = seg.replace(/\.[^.]+$/, "");
77
+ return noExt ? pascal(noExt) : "";
78
+ })();
79
+ return overrideName
80
+ || ((svcName || fileBase) ? `${svcName || fileBase}` : "GeneratedSOAPClient");
81
+ }
82
+ /** Explode a PascalCase string into its constituent segments. */
83
+ export function explodePascal(s) {
84
+ // insert underscores between camel‐transitions
85
+ const withUnderscores = String(s)
86
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
87
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2');
88
+ return withUnderscores.split('_').filter(Boolean);
89
+ }
90
+ /** Convert a PascalCase string to snake_case (lowercase + underscores). */
91
+ export function pascalToSnakeCase(s) {
92
+ return explodePascal(s)
93
+ .map(seg => seg.toLowerCase())
94
+ .join('_');
95
+ }