@techspokes/typescript-wsdl-client 0.1.9 → 0.2.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 +59 -9
- package/dist/cli.js +9 -0
- package/dist/compiler/schemaCompiler.d.ts +13 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +264 -80
- package/dist/config.d.ts +29 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/emit/clientEmitter.d.ts.map +1 -1
- package/dist/emit/clientEmitter.js +27 -5
- package/dist/emit/runtimeEmitter.d.ts.map +1 -1
- package/dist/emit/runtimeEmitter.js +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Reads WSDL/XSD (with imports) and emits a small, typed client you can compile in
|
|
|
23
23
|
* Deterministic metadata (`ATTR_SPEC`, `PROP_META`) for clean JSON ⇄ SOAP mapping
|
|
24
24
|
* ESM **and** CommonJS friendly output
|
|
25
25
|
|
|
26
|
-
Vendor: **[TechSpokes](https://www.techspokes.com)** · Contact: **[contact
|
|
26
|
+
Vendor: **[TechSpokes](https://www.techspokes.com)** · Contact: **[contact page](https://www.techspokes.com/contact/)**
|
|
27
27
|
Maintainer: **Serge Liatko** ([@sergeliatko](https://github.com/sergeliatko)) · GitHub org: [@techspokes](https://github.com/techspokes)
|
|
28
28
|
|
|
29
29
|
---
|
|
@@ -33,7 +33,7 @@ Maintainer: **Serge Liatko** ([@sergeliatko](https://github.com/sergeliatko)) ·
|
|
|
33
33
|
Install the generator (dev-time) in your project:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npm i -D typescript-wsdl-client
|
|
36
|
+
npm i -D @techspokes/typescript-wsdl-client
|
|
37
37
|
# your app will use node-soap at runtime:
|
|
38
38
|
npm i soap
|
|
39
39
|
```
|
|
@@ -49,17 +49,28 @@ Use the generated client:
|
|
|
49
49
|
```ts
|
|
50
50
|
// ESM / NodeNext app
|
|
51
51
|
import { createSoapClient } from "./generated/my/runtime.js";
|
|
52
|
-
|
|
52
|
+
// class name defaults to <ServiceName>SoapClient (from WSDL), e.g., MyServiceSoapClient
|
|
53
|
+
import { MyServiceSoapClient } from "./generated/my/client.js";
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
// optional: pass security straight into createSoapClient (node-soap security instance)
|
|
56
|
+
import soap from "soap";
|
|
57
|
+
const security = new soap.WSSecurity("user", "pass");
|
|
56
58
|
|
|
59
|
+
const soapClient = await createSoapClient({
|
|
60
|
+
wsdlUrl: "https://example.com/MyService?wsdl",
|
|
61
|
+
security, // or configure after creation: (await createSoapClient(...)).setSecurity(security)
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const client = new MyServiceSoapClient(soapClient, "$attributes"); // attributes key optional
|
|
65
|
+
|
|
66
|
+
// Example: To set literal text content for an XML element, use the reserved `$value` key.
|
|
67
|
+
// Other keys in the object represent XML attributes (e.g. `MyAttribute`) and child elements (e.g. `MyElement`).
|
|
68
|
+
// This lets you build mixed-content XML: `$value` for text, attribute keys for XML attributes, and element keys for nested elements.
|
|
57
69
|
const rs = await client.MyOperation({
|
|
58
70
|
MyOperationRQ: {
|
|
59
71
|
MyAttribute: "passed as attribute",
|
|
60
72
|
MyElement: "passed as child element",
|
|
61
|
-
|
|
62
|
-
// attributes appear as top-level props; child elements as nested objects
|
|
73
|
+
$value: "passed text content here",
|
|
63
74
|
}
|
|
64
75
|
});
|
|
65
76
|
|
|
@@ -70,12 +81,42 @@ console.log(rs);
|
|
|
70
81
|
|
|
71
82
|
---
|
|
72
83
|
|
|
84
|
+
## Security and WS-Policy hints
|
|
85
|
+
|
|
86
|
+
- node-soap leaves security up to you. Create a security instance (e.g., `new soap.BasicAuthSecurity(...)`, `new soap.WSSecurity(...)`, `new soap.ClientSSLSecurity(...)`) and set it on the client via `client.setSecurity(...)`.
|
|
87
|
+
- The runtime factory `createSoapClient({ wsdlUrl, endpoint?, wsdlOptions?, security? })` accepts an optional `security` and will call `client.setSecurity(security)` for you.
|
|
88
|
+
- The generated client includes minimal WS-Policy hints if your WSDL embeds inline `wsp:Policy` under `wsdl:binding` or `wsdl:binding/wsdl:operation`.
|
|
89
|
+
- Hints are surfaced in method JSDoc as “Security (WSDL policy hint): …” and in `operations.json`/`operations.ts` as a `security: string[]` field (e.g., `usernameToken`, `https`, `x509`, `messageSecurity`).
|
|
90
|
+
- These hints are best-effort and not authoritative (no `wsp:PolicyReference` dereferencing yet). They’re intended to nudge you to configure the right security.
|
|
91
|
+
- At runtime, if an operation has hints and your client has no `security` configured, a console warning is emitted.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
73
95
|
## CLI
|
|
74
96
|
|
|
75
97
|
```
|
|
76
98
|
wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
77
99
|
```
|
|
78
100
|
|
|
101
|
+
### Local development
|
|
102
|
+
|
|
103
|
+
By default, `npx wsdl-tsc` invokes the published npm version. To run the CLI from your local source (with your latest changes), use one of these approaches:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Directly via tsx (requires tsx in devDependencies)
|
|
107
|
+
npx tsx src/cli.ts --wsdl <path-or-url> --out <dir> [options]
|
|
108
|
+
|
|
109
|
+
# Via npm script
|
|
110
|
+
git clone ... then:
|
|
111
|
+
npm install
|
|
112
|
+
git checkout <branch>
|
|
113
|
+
npm run dev -- --wsdl <path-or-url> --out <dir> [options]
|
|
114
|
+
|
|
115
|
+
# Using npm link to symlink your working copy
|
|
116
|
+
npm link
|
|
117
|
+
wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
118
|
+
```
|
|
119
|
+
|
|
79
120
|
**Required**
|
|
80
121
|
|
|
81
122
|
* `--wsdl` — WSDL path or URL
|
|
@@ -88,11 +129,19 @@ wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
|
88
129
|
| `--imports` | string | js, ts, bare | js | Intra-generated import specifiers: '.js', '.ts', or bare |
|
|
89
130
|
| `--ops-ts` | boolean | true, false | true | Emit `operations.ts` instead of JSON |
|
|
90
131
|
| `--attributes-key` | string | any | $attributes | Key used by runtime marshaller for XML attributes |
|
|
132
|
+
| `--client-name` | string | any | — | Override the exported client class name (exact) |
|
|
91
133
|
| `--int64-as` | string | string, number, bigint | string | How to map xs:long/xs:unsignedLong |
|
|
92
134
|
| `--bigint-as` | string | string, number | string | How to map xs:integer family (positive/nonNegative/etc.) |
|
|
93
135
|
| `--decimal-as` | string | string, number | string | How to map xs:decimal (money/precision) |
|
|
94
136
|
| `--date-as` | string | string, Date | string | How to map date/time/duration types |
|
|
95
137
|
|
|
138
|
+
### Client naming
|
|
139
|
+
|
|
140
|
+
- By default, the generated client class is named after the WSDL service: `<ServiceName>SoapClient`.
|
|
141
|
+
- If the service name can’t be determined, we fall back to the WSDL filename: `<WsdlFileBaseName>SoapClient`.
|
|
142
|
+
- If neither is available, we fall back to `GeneratedSoapClient`.
|
|
143
|
+
- You can override the name entirely with `--client-name MyCustomClient` (the exact export name will be used).
|
|
144
|
+
|
|
96
145
|
**Primitive mapping (safe defaults)**
|
|
97
146
|
|
|
98
147
|
Defaults are **string-first** to avoid precision & timezone surprises:
|
|
@@ -176,8 +225,8 @@ TypeScript will compile to `require("./runtime")` cleanly.
|
|
|
176
225
|
## Programmatic API (optional)
|
|
177
226
|
|
|
178
227
|
```ts
|
|
179
|
-
import { compileCatalog, xsdToTsPrimitive, type CompilerOptions } from "typescript-wsdl-client";
|
|
180
|
-
import { loadWsdlCatalog } from "typescript-wsdl-client/internal-or-your-loader"; // if you expose it
|
|
228
|
+
import { compileCatalog, xsdToTsPrimitive, type CompilerOptions } from "@techspokes/typescript-wsdl-client";
|
|
229
|
+
import { loadWsdlCatalog } from "@techspokes/typescript-wsdl-client/internal-or-your-loader"; // if you expose it
|
|
181
230
|
|
|
182
231
|
const catalog = await loadWsdlCatalog("./spec/wsdl/MyService.wsdl");
|
|
183
232
|
const compiled = compileCatalog(catalog, {
|
|
@@ -218,6 +267,7 @@ npx tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 .
|
|
|
218
267
|
* **“Cannot find './runtime.js'”** — Your app must be `module: "NodeNext"` when using `--imports js`.
|
|
219
268
|
Use `--imports bare` for CommonJS apps.
|
|
220
269
|
* **“node-soap not found”** — Install it in your **app**: `npm i soap`.
|
|
270
|
+
* **“Security required?”** — If your generated client warns or JSDoc shows a security hint, configure node-soap security: `client.setSecurity(new soap.WSSecurity(...))` or pass `security` to `createSoapClient`.
|
|
221
271
|
|
|
222
272
|
---
|
|
223
273
|
|
package/dist/cli.js
CHANGED
|
@@ -37,6 +37,10 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
37
37
|
type: "string",
|
|
38
38
|
default: "$attributes",
|
|
39
39
|
desc: "Key used by runtime marshaller for XML attributes",
|
|
40
|
+
})
|
|
41
|
+
.option("client-name", {
|
|
42
|
+
type: "string",
|
|
43
|
+
desc: "Override the generated client class name (exact export name)",
|
|
40
44
|
})
|
|
41
45
|
// Primitive mapping knobs (safe defaults)
|
|
42
46
|
.option("int64-as", {
|
|
@@ -78,6 +82,10 @@ const compiled = compileCatalog(catalog, {
|
|
|
78
82
|
dateAs: argv["date-as"],
|
|
79
83
|
},
|
|
80
84
|
});
|
|
85
|
+
// Report counts of types and operations for user visibility
|
|
86
|
+
console.log(`Schemas discovered: ${catalog.schemas.length}`);
|
|
87
|
+
console.log(`Compiled types: ${compiled.types.length}`);
|
|
88
|
+
console.log(`Operations: ${compiled.operations.length}`);
|
|
81
89
|
// Emit files
|
|
82
90
|
const importExt = argv.imports === "js" ? ".js" : argv.imports === "ts" ? ".ts" : "";
|
|
83
91
|
emitTypes(path.join(outDir, "types.ts"), compiled);
|
|
@@ -90,5 +98,6 @@ emitClient(path.join(outDir, "client.ts"), compiled, {
|
|
|
90
98
|
// @ts-ignore runtime-only options for emitter
|
|
91
99
|
importExt,
|
|
92
100
|
attributesKey: String(argv["attributes-key"]),
|
|
101
|
+
clientName: argv["client-name"],
|
|
93
102
|
});
|
|
94
103
|
console.log(`✅ Generated TypeScript client in ${outDir}`);
|
|
@@ -43,8 +43,21 @@ export type CompiledCatalog = {
|
|
|
43
43
|
soapAction: string;
|
|
44
44
|
inputElement?: QName;
|
|
45
45
|
outputElement?: QName;
|
|
46
|
+
security?: string[];
|
|
46
47
|
}>;
|
|
47
48
|
wsdlTargetNS: string;
|
|
49
|
+
serviceName?: string;
|
|
50
|
+
wsdlUri: string;
|
|
48
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Compile a WSDL catalog into an internal representation (CompiledCatalog).
|
|
54
|
+
* Steps:
|
|
55
|
+
* 1. Collect and index complexType, simpleType, and element definitions across all schemas.
|
|
56
|
+
* 2. Recursively compile each named type to a TS interface or alias, handling inheritance,
|
|
57
|
+
* inline definitions, and XSD primitives.
|
|
58
|
+
* 3. Build metadata maps for runtime marshalling (attributes, children, nillable, occurrence).
|
|
59
|
+
* 4. Extract WSDL operations: pick the appropriate SOAP binding (v1.1 or v1.2), resolve its
|
|
60
|
+
* portType reference, then enumerate operations and their soapAction URIs.
|
|
61
|
+
*/
|
|
49
62
|
export declare function compileCatalog(cat: WsdlCatalog, _opts: CompilerOptions): CompiledCatalog;
|
|
50
63
|
//# sourceMappingURL=schemaCompiler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemaCompiler.d.ts","sourceRoot":"","sources":["../../src/compiler/schemaCompiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAU3D,MAAM,MAAM,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;QAC9B,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;QAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;KAC/C,CAAC;IACF,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,KAAK,CAAC;QACrB,aAAa,CAAC,EAAE,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"schemaCompiler.d.ts","sourceRoot":"","sources":["../../src/compiler/schemaCompiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAU3D,MAAM,MAAM,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;QAC9B,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;QAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;KAC/C,CAAC;IACF,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,KAAK,CAAC;QACrB,aAAa,CAAC,EAAE,KAAK,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC,CAAC;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA6DF;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,GAAG,eAAe,CA2gBxF"}
|
|
@@ -8,10 +8,71 @@ function qkey(q) {
|
|
|
8
8
|
function makeInlineTypeName(parentTypeName, propName, _max) {
|
|
9
9
|
const base = pascal(parentTypeName || "AnonParent");
|
|
10
10
|
const prop = pascal(propName || "");
|
|
11
|
-
if (prop)
|
|
11
|
+
if (prop) {
|
|
12
12
|
return prop;
|
|
13
|
+
}
|
|
13
14
|
return `${base}Anon`;
|
|
14
15
|
}
|
|
16
|
+
// --- Minimal WS-Policy scanning (inline policies only; PolicyReference not resolved) ---
|
|
17
|
+
function collectSecurityFromPolicyNodes(policyNodes) {
|
|
18
|
+
const found = new Set();
|
|
19
|
+
const targets = new Set([
|
|
20
|
+
"UsernameToken",
|
|
21
|
+
"TransportBinding",
|
|
22
|
+
"HttpsToken",
|
|
23
|
+
"TransportToken",
|
|
24
|
+
"AsymmetricBinding",
|
|
25
|
+
"SymmetricBinding",
|
|
26
|
+
"SignedSupportingTokens",
|
|
27
|
+
"X509Token",
|
|
28
|
+
]);
|
|
29
|
+
const visit = (n) => {
|
|
30
|
+
if (n == null || typeof n !== "object")
|
|
31
|
+
return;
|
|
32
|
+
for (const [k, v] of Object.entries(n)) {
|
|
33
|
+
if (k.startsWith("@_"))
|
|
34
|
+
continue; // attribute
|
|
35
|
+
// localName match (prefix-agnostic) by checking tail after ':' or whole key if no ':'
|
|
36
|
+
const ln = k.includes(":") ? k.split(":").pop() : k;
|
|
37
|
+
if (targets.has(ln)) {
|
|
38
|
+
switch (ln) {
|
|
39
|
+
case "UsernameToken":
|
|
40
|
+
found.add("usernameToken");
|
|
41
|
+
break;
|
|
42
|
+
case "HttpsToken":
|
|
43
|
+
case "TransportBinding":
|
|
44
|
+
case "TransportToken":
|
|
45
|
+
found.add("https");
|
|
46
|
+
break;
|
|
47
|
+
case "X509Token":
|
|
48
|
+
found.add("x509");
|
|
49
|
+
break;
|
|
50
|
+
case "AsymmetricBinding":
|
|
51
|
+
case "SymmetricBinding":
|
|
52
|
+
case "SignedSupportingTokens":
|
|
53
|
+
found.add("messageSecurity");
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// recurse
|
|
58
|
+
if (v && typeof v === "object")
|
|
59
|
+
visit(v);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
for (const p of policyNodes)
|
|
63
|
+
visit(p);
|
|
64
|
+
return Array.from(found);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compile a WSDL catalog into an internal representation (CompiledCatalog).
|
|
68
|
+
* Steps:
|
|
69
|
+
* 1. Collect and index complexType, simpleType, and element definitions across all schemas.
|
|
70
|
+
* 2. Recursively compile each named type to a TS interface or alias, handling inheritance,
|
|
71
|
+
* inline definitions, and XSD primitives.
|
|
72
|
+
* 3. Build metadata maps for runtime marshalling (attributes, children, nillable, occurrence).
|
|
73
|
+
* 4. Extract WSDL operations: pick the appropriate SOAP binding (v1.1 or v1.2), resolve its
|
|
74
|
+
* portType reference, then enumerate operations and their soapAction URIs.
|
|
75
|
+
*/
|
|
15
76
|
export function compileCatalog(cat, _opts) {
|
|
16
77
|
// symbol tables discovered across all schemas
|
|
17
78
|
const complexTypes = new Map();
|
|
@@ -19,20 +80,25 @@ export function compileCatalog(cat, _opts) {
|
|
|
19
80
|
const elements = new Map();
|
|
20
81
|
for (const s of cat.schemas) {
|
|
21
82
|
const tns = s.targetNS;
|
|
83
|
+
// merge WSDL-level prefixes with schema-level (schema wins)
|
|
84
|
+
const mergedPrefixes = { ...cat.prefixMap, ...s.prefixes };
|
|
22
85
|
for (const n of getChildrenWithLocalName(s.xml, "complexType")) {
|
|
23
86
|
const name = n["@_name"];
|
|
24
|
-
if (name)
|
|
25
|
-
complexTypes.set(qkey({ ns: tns, local: name }), { node: n, tns, prefixes:
|
|
87
|
+
if (name) {
|
|
88
|
+
complexTypes.set(qkey({ ns: tns, local: name }), { node: n, tns, prefixes: mergedPrefixes });
|
|
89
|
+
}
|
|
26
90
|
}
|
|
27
91
|
for (const n of getChildrenWithLocalName(s.xml, "simpleType")) {
|
|
28
92
|
const name = n["@_name"];
|
|
29
|
-
if (name)
|
|
30
|
-
simpleTypes.set(qkey({ ns: tns, local: name }), { node: n, tns, prefixes:
|
|
93
|
+
if (name) {
|
|
94
|
+
simpleTypes.set(qkey({ ns: tns, local: name }), { node: n, tns, prefixes: mergedPrefixes });
|
|
95
|
+
}
|
|
31
96
|
}
|
|
32
97
|
for (const n of getChildrenWithLocalName(s.xml, "element")) {
|
|
33
98
|
const name = n["@_name"];
|
|
34
|
-
if (name)
|
|
35
|
-
elements.set(qkey({ ns: tns, local: name }), { node: n, tns, prefixes:
|
|
99
|
+
if (name) {
|
|
100
|
+
elements.set(qkey({ ns: tns, local: name }), { node: n, tns, prefixes: mergedPrefixes });
|
|
101
|
+
}
|
|
36
102
|
}
|
|
37
103
|
}
|
|
38
104
|
// outputs & state
|
|
@@ -72,8 +138,9 @@ export function compileCatalog(cat, _opts) {
|
|
|
72
138
|
function getOrCompileAlias(name, sNode, schemaNS, prefixes) {
|
|
73
139
|
const key = `${schemaNS}|${name}`;
|
|
74
140
|
const present = aliasMap.get(key);
|
|
75
|
-
if (present)
|
|
141
|
+
if (present) {
|
|
76
142
|
return present;
|
|
143
|
+
}
|
|
77
144
|
const { tsType, declared, jsdoc } = compileSimpleTypeNode(sNode, schemaNS, prefixes);
|
|
78
145
|
const alias = { name: pascal(name), ns: schemaNS, tsType, declared, jsdoc };
|
|
79
146
|
aliasMap.set(key, alias);
|
|
@@ -81,8 +148,9 @@ export function compileCatalog(cat, _opts) {
|
|
|
81
148
|
}
|
|
82
149
|
/** Resolve a QName reference to a TS type; compile targets if needed. */
|
|
83
150
|
function resolveTypeRef(q, schemaNS, prefixes) {
|
|
84
|
-
if (!q.ns)
|
|
151
|
+
if (!q.ns) {
|
|
85
152
|
q = resolveQName(q.local, schemaNS, prefixes);
|
|
153
|
+
}
|
|
86
154
|
if (q.ns === XS) {
|
|
87
155
|
const label = `xs:${q.local}`;
|
|
88
156
|
return { tsType: xsdToTsPrimitive(label, _opts?.primitive), declared: label };
|
|
@@ -109,16 +177,20 @@ export function compileCatalog(cat, _opts) {
|
|
|
109
177
|
const outName = pascal(name);
|
|
110
178
|
const key = `${schemaNS}|${outName}`;
|
|
111
179
|
const present = compiledMap.get(key);
|
|
112
|
-
if (present)
|
|
180
|
+
if (present) {
|
|
113
181
|
return present;
|
|
182
|
+
}
|
|
114
183
|
if (inProgress.has(key)) {
|
|
115
184
|
// minimal cycle break
|
|
116
185
|
return { name: outName, ns: schemaNS, attrs: [], elems: [] };
|
|
117
186
|
}
|
|
118
187
|
inProgress.add(key);
|
|
188
|
+
// mergeAttrs: combine base attributes with local ones, overriding duplicates by name
|
|
119
189
|
const mergeAttrs = (into, list) => {
|
|
190
|
+
// build index of existing attribute names
|
|
120
191
|
const idx = new Map();
|
|
121
192
|
into.forEach((a, i) => idx.set(a.name, i));
|
|
193
|
+
// for each new attr, add or override existing
|
|
122
194
|
for (const a of list) {
|
|
123
195
|
const pos = idx.get(a.name);
|
|
124
196
|
if (pos == null) {
|
|
@@ -126,10 +198,11 @@ export function compileCatalog(cat, _opts) {
|
|
|
126
198
|
into.push(a);
|
|
127
199
|
}
|
|
128
200
|
else {
|
|
129
|
-
into[pos] = a; // override
|
|
201
|
+
into[pos] = a; // override existing attribute details
|
|
130
202
|
}
|
|
131
203
|
}
|
|
132
204
|
};
|
|
205
|
+
// mergeElems: combine base elements (particles) with local ones, preserving unique names and overriding duplicates
|
|
133
206
|
const mergeElems = (into, list) => {
|
|
134
207
|
const idx = new Map();
|
|
135
208
|
into.forEach((e, i) => idx.set(e.name, i));
|
|
@@ -140,41 +213,39 @@ export function compileCatalog(cat, _opts) {
|
|
|
140
213
|
into.push(e);
|
|
141
214
|
}
|
|
142
215
|
else {
|
|
143
|
-
into[pos] = e; // override
|
|
216
|
+
into[pos] = e; // override existing element details
|
|
144
217
|
}
|
|
145
218
|
}
|
|
146
219
|
};
|
|
220
|
+
// collectAttributes: read all <attribute> children, handle inline simpleType vs named type references
|
|
221
|
+
// maps each attribute to a TS type, tracks required vs optional via @use, and records the original declared XSD type
|
|
147
222
|
const collectAttributes = (node) => {
|
|
148
223
|
const out = [];
|
|
149
224
|
const attrs = getChildrenWithLocalName(node, "attribute");
|
|
150
225
|
for (const a of attrs) {
|
|
151
226
|
const an = a["@_name"];
|
|
152
|
-
if (!an)
|
|
227
|
+
if (!an) {
|
|
153
228
|
continue;
|
|
229
|
+
}
|
|
154
230
|
const inlineSimple = getFirstWithLocalName(a, "simpleType");
|
|
155
231
|
if (inlineSimple) {
|
|
232
|
+
// inline enumeration or restriction inside attribute
|
|
156
233
|
const r = compileSimpleTypeNode(inlineSimple, schemaNS, prefixes);
|
|
157
|
-
out.push({
|
|
158
|
-
name: an,
|
|
159
|
-
tsType: r.tsType,
|
|
160
|
-
use: a["@_use"] === "required" ? "required" : "optional",
|
|
161
|
-
declaredType: r.declared,
|
|
162
|
-
});
|
|
234
|
+
out.push({ name: an, tsType: r.tsType, use: a["@_use"] === "required" ? "required" : "optional", declaredType: r.declared });
|
|
163
235
|
}
|
|
164
236
|
else {
|
|
237
|
+
// named type or default xs:string
|
|
165
238
|
const t = a["@_type"];
|
|
166
239
|
const q = t ? resolveQName(t, schemaNS, prefixes) : { ns: XS, local: "string" };
|
|
167
240
|
const r = resolveTypeRef(q, schemaNS, prefixes);
|
|
168
|
-
out.push({
|
|
169
|
-
name: an,
|
|
170
|
-
tsType: r.tsType,
|
|
171
|
-
use: a["@_use"] === "required" ? "required" : "optional",
|
|
172
|
-
declaredType: r.declared,
|
|
173
|
-
});
|
|
241
|
+
out.push({ name: an, tsType: r.tsType, use: a["@_use"] === "required" ? "required" : "optional", declaredType: r.declared });
|
|
174
242
|
}
|
|
175
243
|
}
|
|
176
244
|
return out;
|
|
177
245
|
};
|
|
246
|
+
// collectParticles: parse compositor elements (sequence, all, choice), extract <element> definitions
|
|
247
|
+
// handles inline complex/simple definitions by generating a unique inline type name
|
|
248
|
+
// resolves type refs or @ref, applies min/max occurrence and nillable flags
|
|
178
249
|
const collectParticles = (ownerTypeName, node) => {
|
|
179
250
|
const out = [];
|
|
180
251
|
const groups = [
|
|
@@ -185,52 +256,33 @@ export function compileCatalog(cat, _opts) {
|
|
|
185
256
|
for (const grp of groups) {
|
|
186
257
|
for (const e of getChildrenWithLocalName(grp, "element")) {
|
|
187
258
|
const nameOrRef = e["@_name"] || e["@_ref"];
|
|
188
|
-
if (!nameOrRef)
|
|
259
|
+
if (!nameOrRef) {
|
|
189
260
|
continue;
|
|
190
|
-
|
|
261
|
+
}
|
|
262
|
+
const propName = e["@_name"];
|
|
191
263
|
const min = e["@_minOccurs"] ? Number(e["@_minOccurs"]) : 1;
|
|
192
264
|
const maxAttr = e["@_maxOccurs"];
|
|
193
265
|
const max = maxAttr === "unbounded" ? "unbounded" : maxAttr ? Number(maxAttr) : 1;
|
|
194
266
|
const nillable = e["@_nillable"] === "true";
|
|
195
|
-
// inline
|
|
267
|
+
// inline complexType: create a nested interface with a generated name
|
|
196
268
|
const inlineComplex = getFirstWithLocalName(e, "complexType");
|
|
197
269
|
const inlineSimple = getFirstWithLocalName(e, "simpleType");
|
|
198
270
|
if (inlineComplex) {
|
|
199
271
|
const inlineName = makeInlineTypeName(ownerTypeName, propName || nameOrRef, max);
|
|
200
272
|
const rec = getOrCompileComplex(inlineName, inlineComplex, schemaNS, prefixes);
|
|
201
|
-
out.push({
|
|
202
|
-
name: propName || nameOrRef,
|
|
203
|
-
tsType: rec.name,
|
|
204
|
-
min,
|
|
205
|
-
max,
|
|
206
|
-
nillable,
|
|
207
|
-
declaredType: `{${schemaNS}}${rec.name}`,
|
|
208
|
-
});
|
|
273
|
+
out.push({ name: propName || nameOrRef, tsType: rec.name, min, max, nillable, declaredType: `{${schemaNS}}${rec.name}` });
|
|
209
274
|
}
|
|
210
275
|
else if (inlineSimple) {
|
|
276
|
+
// inline simpleType (e.g., list or enumeration)
|
|
211
277
|
const r = compileSimpleTypeNode(inlineSimple, schemaNS, prefixes);
|
|
212
|
-
out.push({
|
|
213
|
-
name: propName || nameOrRef,
|
|
214
|
-
tsType: r.tsType,
|
|
215
|
-
min,
|
|
216
|
-
max,
|
|
217
|
-
nillable,
|
|
218
|
-
declaredType: r.declared,
|
|
219
|
-
});
|
|
278
|
+
out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
|
|
220
279
|
}
|
|
221
280
|
else {
|
|
222
|
-
//
|
|
281
|
+
// named type or ref: resolve via QName
|
|
223
282
|
const t = e["@_type"] || e["@_ref"];
|
|
224
283
|
const q = t ? resolveQName(t, schemaNS, prefixes) : { ns: XS, local: "string" };
|
|
225
284
|
const r = resolveTypeRef(q, schemaNS, prefixes);
|
|
226
|
-
out.push({
|
|
227
|
-
name: propName || nameOrRef,
|
|
228
|
-
tsType: r.tsType,
|
|
229
|
-
min,
|
|
230
|
-
max,
|
|
231
|
-
nillable,
|
|
232
|
-
declaredType: r.declared,
|
|
233
|
-
});
|
|
285
|
+
out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
|
|
234
286
|
}
|
|
235
287
|
}
|
|
236
288
|
}
|
|
@@ -277,7 +329,7 @@ export function compileCatalog(cat, _opts) {
|
|
|
277
329
|
if (baseAttr) {
|
|
278
330
|
r = resolveTypeRef(resolveQName(baseAttr, schemaNS, prefixes), schemaNS, prefixes);
|
|
279
331
|
}
|
|
280
|
-
//
|
|
332
|
+
// text node is modeled as "$value" (not "value")
|
|
281
333
|
mergeElems(elems, [{
|
|
282
334
|
name: "$value",
|
|
283
335
|
tsType: r.tsType,
|
|
@@ -292,10 +344,12 @@ export function compileCatalog(cat, _opts) {
|
|
|
292
344
|
inProgress.delete(key);
|
|
293
345
|
return result;
|
|
294
346
|
};
|
|
295
|
-
if (ext)
|
|
347
|
+
if (ext) {
|
|
296
348
|
return model(ext);
|
|
297
|
-
|
|
349
|
+
}
|
|
350
|
+
if (res) {
|
|
298
351
|
return model(res);
|
|
352
|
+
}
|
|
299
353
|
}
|
|
300
354
|
// Attributes + particles
|
|
301
355
|
mergeAttrs(attrs, collectAttributes(cnode));
|
|
@@ -305,13 +359,84 @@ export function compileCatalog(cat, _opts) {
|
|
|
305
359
|
inProgress.delete(key);
|
|
306
360
|
return result;
|
|
307
361
|
}
|
|
362
|
+
// Helper: compile a global element into a surface type (wrapper)
|
|
363
|
+
function compileElementAsType(name, enode, schemaNS, prefixes) {
|
|
364
|
+
const outName = pascal(name);
|
|
365
|
+
const key = `${schemaNS}|${outName}`;
|
|
366
|
+
const present = compiledMap.get(key);
|
|
367
|
+
if (present)
|
|
368
|
+
return present;
|
|
369
|
+
const inlineComplex = getFirstWithLocalName(enode, "complexType");
|
|
370
|
+
if (inlineComplex) {
|
|
371
|
+
return getOrCompileComplex(name, inlineComplex, schemaNS, prefixes);
|
|
372
|
+
}
|
|
373
|
+
const inlineSimple = getFirstWithLocalName(enode, "simpleType");
|
|
374
|
+
if (inlineSimple) {
|
|
375
|
+
const r = compileSimpleTypeNode(inlineSimple, schemaNS, prefixes);
|
|
376
|
+
const t = {
|
|
377
|
+
name: outName,
|
|
378
|
+
ns: schemaNS,
|
|
379
|
+
attrs: [],
|
|
380
|
+
elems: [{ name: "$value", tsType: r.tsType, min: 0, max: 1, nillable: false, declaredType: r.declared }],
|
|
381
|
+
};
|
|
382
|
+
compiledMap.set(key, t);
|
|
383
|
+
return t;
|
|
384
|
+
}
|
|
385
|
+
const tAttr = enode["@_type"];
|
|
386
|
+
if (tAttr) {
|
|
387
|
+
const q = resolveQName(tAttr, schemaNS, prefixes);
|
|
388
|
+
// If references a simple type → $value; if complex type → copy members
|
|
389
|
+
if (q.ns === XS) {
|
|
390
|
+
const label = `xs:${q.local}`;
|
|
391
|
+
const t = {
|
|
392
|
+
name: outName,
|
|
393
|
+
ns: schemaNS,
|
|
394
|
+
attrs: [],
|
|
395
|
+
elems: [{ name: "$value", tsType: xsdToTsPrimitive(label, _opts?.primitive), min: 0, max: 1, nillable: false, declaredType: label }],
|
|
396
|
+
};
|
|
397
|
+
compiledMap.set(key, t);
|
|
398
|
+
return t;
|
|
399
|
+
}
|
|
400
|
+
const baseRec = complexTypes.get(qkey(q));
|
|
401
|
+
if (baseRec) {
|
|
402
|
+
const base = getOrCompileComplex(baseRec.node["@_name"], baseRec.node, baseRec.tns, baseRec.prefixes);
|
|
403
|
+
const t = { name: outName, ns: schemaNS, attrs: [...(base.attrs || [])], elems: [...(base.elems || [])] };
|
|
404
|
+
compiledMap.set(key, t);
|
|
405
|
+
return t;
|
|
406
|
+
}
|
|
407
|
+
const srec = simpleTypes.get(qkey(q));
|
|
408
|
+
if (srec) {
|
|
409
|
+
const a = getOrCompileAlias(q.local, srec.node, srec.tns, srec.prefixes);
|
|
410
|
+
const t = {
|
|
411
|
+
name: outName,
|
|
412
|
+
ns: schemaNS,
|
|
413
|
+
attrs: [],
|
|
414
|
+
elems: [{ name: "$value", tsType: a.name, min: 0, max: 1, nillable: false, declaredType: `{${a.ns}}${q.local}` }],
|
|
415
|
+
};
|
|
416
|
+
compiledMap.set(key, t);
|
|
417
|
+
return t;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// default empty wrapper
|
|
421
|
+
const t = { name: outName, ns: schemaNS, attrs: [], elems: [] };
|
|
422
|
+
compiledMap.set(key, t);
|
|
423
|
+
return t;
|
|
424
|
+
}
|
|
308
425
|
// compile every discovered complex type
|
|
309
426
|
for (const rec of complexTypes.values()) {
|
|
310
427
|
const name = rec.node["@_name"];
|
|
311
|
-
if (!name)
|
|
428
|
+
if (!name) {
|
|
312
429
|
continue;
|
|
430
|
+
}
|
|
313
431
|
getOrCompileComplex(name, rec.node, rec.tns, rec.prefixes);
|
|
314
432
|
}
|
|
433
|
+
// compile global element wrappers, so metadata contains operation wrappers
|
|
434
|
+
for (const rec of elements.values()) {
|
|
435
|
+
const name = rec.node["@_name"];
|
|
436
|
+
if (!name)
|
|
437
|
+
continue;
|
|
438
|
+
compileElementAsType(name, rec.node, rec.tns, rec.prefixes);
|
|
439
|
+
}
|
|
315
440
|
// emit lists
|
|
316
441
|
const typesList = Array.from(compiledMap.values());
|
|
317
442
|
const aliasList = Array.from(aliasMap.values());
|
|
@@ -332,41 +457,100 @@ export function compileCatalog(cat, _opts) {
|
|
|
332
457
|
childType[t.name] = child;
|
|
333
458
|
propMeta[t.name] = meta;
|
|
334
459
|
}
|
|
335
|
-
// operations / soapAction (
|
|
460
|
+
// operations / soapAction (enriched)
|
|
461
|
+
// 1) Gather all binding definitions and select the SOAP binding (soap:binding or soap12:binding child).
|
|
462
|
+
// 2) Use the binding's @type to locate the matching portType in definitions.
|
|
463
|
+
// 3) Enumerate operations on that portType (wsdl:operation) for input/output WSDL messages.
|
|
464
|
+
// 4) In the chosen binding, extract the <soap:operation> child to read SOAP action URIs.
|
|
336
465
|
const defs = cat.wsdlXml["wsdl:definitions"] || cat.wsdlXml["definitions"];
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
const
|
|
466
|
+
const tns = defs?.["@_targetNamespace"] || "";
|
|
467
|
+
const bindingDefs = normalizeArray(defs?.["wsdl:binding"] || defs?.["binding"]);
|
|
468
|
+
const soapBinding = bindingDefs.find(b => Object.keys(b).some(k => k === "soap:binding" || k === "soap12:binding")) || bindingDefs[0];
|
|
469
|
+
// binding @type typically looks like "tns:MyPortType", so resolve via prefixes map
|
|
470
|
+
const portTypeAttr = soapBinding?.["@_type"];
|
|
471
|
+
const portTypeQName = portTypeAttr ? resolveQName(portTypeAttr, tns, cat.prefixMap) : undefined;
|
|
472
|
+
const portTypeDefs = normalizeArray(defs?.["wsdl:portType"] || defs?.["portType"]);
|
|
473
|
+
const targetPortType = portTypeQName
|
|
474
|
+
? portTypeDefs.find(pt => pt?.["@_name"] === portTypeQName.local)
|
|
475
|
+
: portTypeDefs[0];
|
|
476
|
+
// operations listed under <wsdl:portType>
|
|
477
|
+
const pOps = targetPortType ? getChildrenWithLocalName(targetPortType, "operation") : [];
|
|
340
478
|
const bOps = new Map();
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
for (const bo of
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const node = Array.isArray(v) ? v[0] : v;
|
|
351
|
-
action = node?.["@_soapAction"] || node?.["@_soapActionURI"] || node?.["@_soapActionUrl"] || "";
|
|
352
|
-
if (action)
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
479
|
+
// in the <wsdl:binding>, each operation has a nested <soap:operation> child
|
|
480
|
+
const opsNodes = getChildrenWithLocalName(soapBinding, "operation").filter(o => o["@_name"]);
|
|
481
|
+
for (const bo of opsNodes) {
|
|
482
|
+
// operation name must match portType operation name
|
|
483
|
+
const name = bo["@_name"];
|
|
484
|
+
// find nested soap:operation node for the soapAction attribute
|
|
485
|
+
const soapOps = getChildrenWithLocalName(bo, "operation");
|
|
486
|
+
const soapOp = soapOps[0];
|
|
487
|
+
const action = soapOp?.["@_soapAction"] || soapOp?.["@_soapActionURI"] || soapOp?.["@_soapActionUrl"] || "";
|
|
356
488
|
bOps.set(name, action);
|
|
357
489
|
}
|
|
358
|
-
const
|
|
359
|
-
|
|
490
|
+
const msgDefs = normalizeArray(defs?.["wsdl:message"] || defs?.["message"]);
|
|
491
|
+
const findMessage = (qstr) => {
|
|
492
|
+
if (!qstr)
|
|
493
|
+
return undefined;
|
|
494
|
+
const q = resolveQName(qstr, tns, cat.prefixMap);
|
|
495
|
+
return msgDefs.find(m => m?.["@_name"] === q.local);
|
|
496
|
+
};
|
|
497
|
+
const elementOfMessage = (msg) => {
|
|
498
|
+
if (!msg)
|
|
499
|
+
return undefined;
|
|
500
|
+
const parts = getChildrenWithLocalName(msg, "part");
|
|
501
|
+
// Prefer element-based part (doc/literal)
|
|
502
|
+
const withElem = parts.find(p => p?.["@_element"]);
|
|
503
|
+
const el = withElem?.["@_element"];
|
|
504
|
+
if (!el)
|
|
505
|
+
return undefined;
|
|
506
|
+
const q = resolveQName(el, tns, cat.prefixMap);
|
|
507
|
+
return { ns: q.ns, local: q.local };
|
|
508
|
+
};
|
|
509
|
+
// build operations list
|
|
510
|
+
const ops = (pOps
|
|
511
|
+
.map(po => {
|
|
360
512
|
const name = po?.["@_name"];
|
|
361
513
|
if (!name)
|
|
362
|
-
|
|
363
|
-
|
|
514
|
+
return undefined;
|
|
515
|
+
const inMsg = findMessage(getFirstWithLocalName(po, "input")?.["@_message"]);
|
|
516
|
+
const outMsg = findMessage(getFirstWithLocalName(po, "output")?.["@_message"]);
|
|
517
|
+
const inputElement = elementOfMessage(inMsg);
|
|
518
|
+
const outputElement = elementOfMessage(outMsg);
|
|
519
|
+
return { name, soapAction: bOps.get(name) || "", inputElement, outputElement };
|
|
520
|
+
})
|
|
521
|
+
.filter((x) => x != null));
|
|
522
|
+
// --- WS-Policy: scan for security requirements (inline policies only) ---
|
|
523
|
+
const bindingPolicies = getChildrenWithLocalName(soapBinding || {}, "Policy");
|
|
524
|
+
const bindingSec = collectSecurityFromPolicyNodes(bindingPolicies);
|
|
525
|
+
for (const op of ops) {
|
|
526
|
+
const bo = opsNodes.find(o => o?.["@_name"] === op.name) || {};
|
|
527
|
+
const opPolicies = getChildrenWithLocalName(bo, "Policy");
|
|
528
|
+
const opSec = collectSecurityFromPolicyNodes(opPolicies);
|
|
529
|
+
const secSet = new Set([...bindingSec, ...opSec]);
|
|
530
|
+
op.security = Array.from(secSet);
|
|
364
531
|
}
|
|
532
|
+
// --- Service discovery (for client naming) ---
|
|
533
|
+
let serviceName;
|
|
534
|
+
const soapBindingName = soapBinding?.["@_name"];
|
|
535
|
+
const serviceDefs = normalizeArray(defs?.["wsdl:service"] || defs?.["service"]);
|
|
536
|
+
const serviceUsingBinding = serviceDefs.find(s => {
|
|
537
|
+
const ports = getChildrenWithLocalName(s, "port");
|
|
538
|
+
return ports.some(p => {
|
|
539
|
+
const bq = p?.["@_binding"];
|
|
540
|
+
if (!bq || !soapBindingName)
|
|
541
|
+
return false;
|
|
542
|
+
const q = resolveQName(bq, tns, cat.prefixMap);
|
|
543
|
+
return q.local === soapBindingName; // match by local name
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
serviceName = serviceUsingBinding?.["@_name"] || serviceDefs[0]?.["@_name"];
|
|
365
547
|
return {
|
|
366
548
|
types: typesList,
|
|
367
549
|
aliases: aliasList,
|
|
368
550
|
meta: { attrSpec, childType, propMeta },
|
|
369
551
|
operations: ops,
|
|
370
552
|
wsdlTargetNS: defs?.["@_targetNamespace"] || "",
|
|
553
|
+
serviceName,
|
|
554
|
+
wsdlUri: cat.wsdlUri,
|
|
371
555
|
};
|
|
372
556
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import type { PrimitiveOptions } from "./xsd/primitives.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options to control WSDL-to-TypeScript compilation behavior.
|
|
4
|
+
*/
|
|
2
5
|
export type CompilerOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* How to represent XML <choice> elements: as all-optional properties or a discriminated union.
|
|
8
|
+
*/
|
|
3
9
|
choice?: "all-optional" | "union";
|
|
10
|
+
/**
|
|
11
|
+
* Legacy flag: map all date/time/duration types to string or Date. Superceded by primitive.dateAs if set.
|
|
12
|
+
*/
|
|
4
13
|
dateAs?: "string" | "date";
|
|
14
|
+
/**
|
|
15
|
+
* Legacy flag: shorthand for int64As and bigIntegerAs. Maps integer types to number or string.
|
|
16
|
+
*/
|
|
5
17
|
intAs?: "number" | "string";
|
|
18
|
+
/**
|
|
19
|
+
* Emit errors if any type references cannot be resolved in the WSDL schema.
|
|
20
|
+
*/
|
|
6
21
|
failOnUnresolved?: boolean;
|
|
7
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Attribute bag key for the runtime mapper (node-soap).
|
|
24
|
+
*/
|
|
8
25
|
attributesKey?: string;
|
|
9
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Optional override for the generated client class name.
|
|
28
|
+
* If provided, the emitter will export this exact class name.
|
|
29
|
+
*/
|
|
30
|
+
clientName?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Controls low-level mapping of XSD primitives to TypeScript types. Safe defaults are provided.
|
|
33
|
+
*/
|
|
10
34
|
primitive?: PrimitiveOptions;
|
|
11
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Default compiler options. Users may override selectively.
|
|
38
|
+
*/
|
|
12
39
|
export declare const defaultOptions: CompilerOptions;
|
|
13
40
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,MAAM,eAAe,GAAG;IAC1B,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;IAClC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;IAClC;;OAEG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B;;OAEG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B;;OAEG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAChC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,eAM5B,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/clientEmitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"clientEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/clientEmitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,QAkDpH"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { pascal } from "../util/xml.js";
|
|
2
3
|
export function emitClient(outFile, compiled, opts) {
|
|
3
4
|
const ext = opts.importExt ?? ".js";
|
|
4
5
|
const lines = [];
|
|
@@ -6,7 +7,18 @@ export function emitClient(outFile, compiled, opts) {
|
|
|
6
7
|
lines.push(`import { ATTR_SPEC, CHILD_TYPE, PROP_META } from "./meta${ext}";`);
|
|
7
8
|
lines.push(`import type * as T from "./types${ext}";`);
|
|
8
9
|
lines.push("");
|
|
9
|
-
|
|
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
|
+
lines.push(`export class ${className} {`);
|
|
10
22
|
lines.push(` constructor(private source: string | any, private attributesKey: string = "${opts.attributesKey || "$attributes"}") {}`);
|
|
11
23
|
lines.push(` async _client() {`);
|
|
12
24
|
lines.push(` if (typeof this.source === 'string') return createSoapClient({ wsdlUrl: this.source });`);
|
|
@@ -14,14 +26,24 @@ export function emitClient(outFile, compiled, opts) {
|
|
|
14
26
|
lines.push(` }`);
|
|
15
27
|
for (const op of compiled.operations) {
|
|
16
28
|
const m = op.name;
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
const inTypeName = op.inputElement ? pascal(op.inputElement.local) : undefined;
|
|
30
|
+
const outTypeName = op.outputElement ? pascal(op.outputElement.local) : undefined;
|
|
31
|
+
const inTs = inTypeName ? `T.${inTypeName}` : `any`;
|
|
32
|
+
const outTs = outTypeName ? `T.${outTypeName}` : `any`;
|
|
33
|
+
const inMetaKey = inTypeName ?? m;
|
|
34
|
+
const outMetaKey = outTypeName ?? m;
|
|
35
|
+
const secHints = Array.isArray(op.security) && op.security.length ? op.security : [];
|
|
36
|
+
lines.push(` /** SOAPAction: ${op.soapAction}${secHints.length ? `\n * Security (WSDL policy hint): ${secHints.join(", ")}` : ""} */`);
|
|
37
|
+
lines.push(` async ${m}(args: ${inTs}): Promise<${outTs}> {`);
|
|
19
38
|
lines.push(` const c: any = await this._client();`);
|
|
39
|
+
if (secHints.length) {
|
|
40
|
+
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()."); }`);
|
|
41
|
+
}
|
|
20
42
|
lines.push(` const meta = { ATTR_SPEC, CHILD_TYPE, PROP_META } as const;`);
|
|
21
|
-
lines.push(` const soapArgs = toSoapArgs(args, "${
|
|
43
|
+
lines.push(` const soapArgs = toSoapArgs(args as any, "${inMetaKey}", meta, this.attributesKey);`);
|
|
22
44
|
lines.push(` return new Promise((resolve, reject) => {`);
|
|
23
45
|
lines.push(` c['${m}'](soapArgs, (err: any, result: any) => {`);
|
|
24
|
-
lines.push(` if (err) reject(err); else resolve(fromSoapResult(result, "${
|
|
46
|
+
lines.push(` if (err) reject(err); else resolve(fromSoapResult(result, "${outMetaKey}", meta, this.attributesKey));`);
|
|
25
47
|
lines.push(` });`);
|
|
26
48
|
lines.push(` });`);
|
|
27
49
|
lines.push(` }`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtimeEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/runtimeEmitter.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"runtimeEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/runtimeEmitter.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,QAoF1C"}
|
|
@@ -9,6 +9,7 @@ export type CreateSoapClientOptions = {
|
|
|
9
9
|
endpoint?: string;
|
|
10
10
|
wsdlOptions?: Record<string, any>;
|
|
11
11
|
requestOptions?: Record<string, any>;
|
|
12
|
+
security?: any; // pass an instance of a node-soap security class, e.g., new soap.WSSecurity("user","pass")
|
|
12
13
|
}
|
|
13
14
|
export type AttrSpec = Record<string, readonly string[]>;
|
|
14
15
|
export type ChildType = Record<string, Readonly<Record<string, string>>>;
|
|
@@ -17,12 +18,8 @@ export type PropMeta = Record<string, Readonly<Record<string, any>>>;
|
|
|
17
18
|
export async function createSoapClient(opts: CreateSoapClientOptions): Promise<any> {
|
|
18
19
|
const client = await soap.createClientAsync(opts.wsdlUrl, opts.wsdlOptions || {});
|
|
19
20
|
if (opts.endpoint) client.setEndpoint(opts.endpoint);
|
|
20
|
-
if (opts.
|
|
21
|
-
|
|
22
|
-
if (client.setSecurity && req) {
|
|
23
|
-
// security is caller's responsibility (if needed)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
21
|
+
if (opts.security) client.setSecurity(opts.security);
|
|
22
|
+
// security and any request-specific configuration are the caller's responsibility
|
|
26
23
|
return client;
|
|
27
24
|
}
|
|
28
25
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -8,39 +8,50 @@ import { emitClient } from "./emit/clientEmitter.js";
|
|
|
8
8
|
import { emitRuntime } from "./emit/runtimeEmitter.js";
|
|
9
9
|
import fs from "node:fs";
|
|
10
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
|
|
11
12
|
// noinspection JSUnusedGlobalSymbols
|
|
12
13
|
export async function compileWsdlToProject(input) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (primitive.int64As == null)
|
|
21
|
-
primitive.int64As = as;
|
|
22
|
-
if (primitive.bigIntegerAs == null)
|
|
23
|
-
primitive.bigIntegerAs = as;
|
|
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";
|
|
24
21
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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)
|
|
33
|
+
const wsdlCatalog = await loadWsdl(input.wsdl);
|
|
34
|
+
console.log(`Loaded WSDL: ${wsdlCatalog.wsdlUri}`);
|
|
35
|
+
// Compile schemas and operations into intermediate data structures
|
|
36
|
+
const compiledCatalog = compileCatalog(wsdlCatalog, finalOptions);
|
|
37
|
+
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
|
|
32
41
|
fs.mkdirSync(input.outDir, { recursive: true });
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
// Define target paths
|
|
43
|
+
const typesFile = path.join(input.outDir, "types.ts");
|
|
44
|
+
const metaFile = path.join(input.outDir, "meta.ts");
|
|
45
|
+
const opsFile = path.join(input.outDir, "operations.json");
|
|
46
|
+
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);
|
|
43
54
|
}
|
|
44
|
-
//
|
|
55
|
+
// Re-export public API for library consumers
|
|
45
56
|
export { compileCatalog } from "./compiler/schemaCompiler.js";
|
|
46
57
|
export { xsdToTsPrimitive } from "./xsd/primitives.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techspokes/typescript-wsdl-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "TypeScript WSDL → SOAP client generator with full xs:attribute support, complex types, sequences, inheritance, and namespace-collision merging.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wsdl",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"prepublishOnly": "npm run clean && npm run build",
|
|
54
54
|
"test": "exit 0",
|
|
55
55
|
"smoke": "tsx src/cli.ts --help",
|
|
56
|
-
"smoke:gen": "rimraf tmp && tsx src/cli.ts --wsdl examples/minimal/
|
|
56
|
+
"smoke:gen": "rimraf tmp && tsx src/cli.ts --wsdl examples/minimal/weather.wsdl --out tmp && tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 tmp/types.ts tmp/meta.ts tmp/operations.ts tmp/runtime.ts tmp/client.ts src/types/soap.d.ts",
|
|
57
57
|
"ci": "npm run build && npm run typecheck && npm run smoke && npm run smoke:gen"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|