@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.
- package/README.md +82 -45
- package/dist/cli.js +34 -21
- package/dist/compiler/schemaCompiler.d.ts +3 -2
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +8 -7
- package/dist/config.d.ts +15 -26
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -6
- package/dist/emit/catalogEmitter.d.ts +3 -0
- package/dist/emit/catalogEmitter.d.ts.map +1 -0
- package/dist/emit/catalogEmitter.js +10 -0
- package/dist/emit/clientEmitter.d.ts +1 -4
- package/dist/emit/clientEmitter.d.ts.map +1 -1
- package/dist/emit/clientEmitter.js +336 -51
- package/dist/emit/typesEmitter.d.ts.map +1 -1
- package/dist/emit/typesEmitter.js +7 -1
- package/dist/emit/utilsEmitter.d.ts +3 -0
- package/dist/emit/utilsEmitter.d.ts.map +1 -0
- package/dist/emit/utilsEmitter.js +33 -0
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -44
- package/dist/loader/wsdlLoader.js +1 -1
- package/dist/util/{xml.d.ts → tools.d.ts} +8 -1
- package/dist/util/tools.d.ts.map +1 -0
- package/dist/util/{xml.js → tools.js} +27 -0
- package/package.json +3 -2
- package/dist/emit/metaEmitter.d.ts +0 -4
- package/dist/emit/metaEmitter.d.ts.map +0 -1
- package/dist/emit/metaEmitter.js +0 -9
- package/dist/emit/opsEmitter.d.ts +0 -4
- package/dist/emit/opsEmitter.d.ts.map +0 -1
- package/dist/emit/opsEmitter.js +0 -13
- package/dist/emit/runtimeEmitter.d.ts +0 -2
- package/dist/emit/runtimeEmitter.d.ts.map +0 -1
- package/dist/emit/runtimeEmitter.js +0 -147
- 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/
|
|
3
|
-
export function emitClient(outFile, compiled
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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,
|
|
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
|
-
|
|
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 @@
|
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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
|
|
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 {
|
|
6
|
-
import {
|
|
6
|
+
import { emitUtils } from "./emit/utilsEmitter.js";
|
|
7
|
+
import { emitCatalog } from "./emit/catalogEmitter.js";
|
|
7
8
|
import { emitClient } from "./emit/clientEmitter.js";
|
|
8
|
-
import {
|
|
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
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
console.log(`
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
44
|
+
const catalogFile = path.join(input.outDir, "catalog.json");
|
|
46
45
|
const clientFile = path.join(input.outDir, "client.ts");
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
+
}
|