@techspokes/typescript-wsdl-client 0.2.0 → 0.2.5
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 +77 -170
- package/dist/cli.js +5 -0
- package/dist/compiler/schemaCompiler.d.ts +18 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +103 -21
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/emit/clientEmitter.d.ts.map +1 -1
- package/dist/emit/clientEmitter.js +36 -6
- package/dist/emit/runtimeEmitter.d.ts.map +1 -1
- package/dist/emit/runtimeEmitter.js +3 -6
- package/dist/emit/typesEmitter.d.ts.map +1 -1
- package/dist/emit/typesEmitter.js +74 -26
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/util/xml.js +29 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,96 +11,93 @@
|
|
|
11
11
|
[](https://github.com/techspokes)
|
|
12
12
|
[](https://github.com/sponsors/TechSpokes)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
Reads WSDL/XSD (with imports) and emits a small, typed client you can compile into your app.
|
|
14
|
+
## Introduction
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
* `complexType` (`sequence` / `all` / `choice`)
|
|
19
|
-
* `simpleContent` / `complexContent` (`extension` + `restriction`)
|
|
20
|
-
* Inline **anonymous** types (auto-named, stable)
|
|
21
|
-
* Global `element @ref` resolution
|
|
22
|
-
* Duplicate/local-name **merge** across schemas/namespaces
|
|
23
|
-
* Deterministic metadata (`ATTR_SPEC`, `PROP_META`) for clean JSON ⇄ SOAP mapping
|
|
24
|
-
* ESM **and** CommonJS friendly output
|
|
16
|
+
**TypeScript WSDL Client** is a generator that converts WSDL/XSD files into a fully-typed SOAP client for TypeScript. It simplifies SOAP integration by generating deterministic, type-safe code that works seamlessly with modern TypeScript and Node.js environments.
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
Key features:
|
|
19
|
+
- **Typed SOAP client**: Generates TypeScript interfaces and runtime code.
|
|
20
|
+
- **Deterministic metadata**: Ensures clean JSON ⇄ SOAP mapping.
|
|
21
|
+
- **ESM and CommonJS support**: Compatible with modern and legacy module systems.
|
|
22
|
+
- **Customizable mappings**: Control how XML primitives (e.g., `xs:decimal`, `xs:date`) are mapped.
|
|
23
|
+
|
|
24
|
+
Vendor: **[TechSpokes](https://www.techspokes.com)**
|
|
25
|
+
Maintainer: **Serge Liatko** ([@sergeliatko](https://github.com/sergeliatko))
|
|
28
26
|
|
|
29
27
|
---
|
|
30
28
|
|
|
31
|
-
##
|
|
29
|
+
## Installation
|
|
32
30
|
|
|
33
|
-
Install the generator
|
|
31
|
+
Install the generator as a development dependency:
|
|
34
32
|
|
|
35
33
|
```bash
|
|
36
|
-
npm i -D typescript-wsdl-client
|
|
37
|
-
#
|
|
34
|
+
npm i -D @techspokes/typescript-wsdl-client
|
|
35
|
+
# Your app will use node-soap at runtime:
|
|
38
36
|
npm i soap
|
|
39
37
|
```
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Generate a SOAP Client
|
|
44
|
+
|
|
45
|
+
Run the following command to generate a client from your WSDL file:
|
|
42
46
|
|
|
43
47
|
```bash
|
|
44
48
|
npx wsdl-tsc --wsdl ./spec/wsdl/MyService.wsdl --out ./src/generated/my --imports js --ops-ts
|
|
45
49
|
```
|
|
46
50
|
|
|
47
|
-
Use the
|
|
51
|
+
### Use the Generated Client
|
|
48
52
|
|
|
49
53
|
```ts
|
|
50
|
-
// ESM / NodeNext app
|
|
51
54
|
import { createSoapClient } from "./generated/my/runtime.js";
|
|
52
|
-
import {
|
|
55
|
+
import { MyServiceSoapClient } from "./generated/my/client.js";
|
|
56
|
+
import soap from "soap";
|
|
53
57
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
58
|
+
const security = new soap.WSSecurity("user", "pass");
|
|
59
|
+
const soapClient = await createSoapClient({
|
|
60
|
+
wsdlUrl: "https://example.com/MyService?wsdl",
|
|
61
|
+
security,
|
|
62
|
+
});
|
|
56
63
|
|
|
57
|
-
const
|
|
64
|
+
const client = new MyServiceSoapClient(soapClient, "$attributes");
|
|
65
|
+
const response = await client.MyOperation({
|
|
58
66
|
MyOperationRQ: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
67
|
+
MyElement: {
|
|
68
|
+
MyAttribute: "value",
|
|
69
|
+
ChildElementA: "valueA",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
64
72
|
});
|
|
65
73
|
|
|
66
|
-
console.log(
|
|
74
|
+
console.log(response);
|
|
67
75
|
```
|
|
68
76
|
|
|
69
|
-
> The generator always emits TypeScript sources (`*.ts`). You compile them with your app.
|
|
70
|
-
|
|
71
77
|
---
|
|
72
78
|
|
|
73
|
-
##
|
|
79
|
+
## Features
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
- **Attributes and Child Elements**: Supports both attributes and nested elements in SOAP requests.
|
|
82
|
+
- **Literal Text Values**: Handles mixed content (attributes + text).
|
|
83
|
+
- **Security Integration**: Works with `node-soap` security instances (e.g., `WSSecurity`, `BasicAuthSecurity`).
|
|
84
|
+
- **WS-Policy Hints**: Provides inline hints for security configuration based on WSDL policies.
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
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:
|
|
86
|
+
---
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
# Directly via tsx (requires tsx in devDependencies)
|
|
85
|
-
npx tsx src/cli.ts --wsdl <path-or-url> --out <dir> [options]
|
|
88
|
+
## CLI Usage
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
git clone ... then:
|
|
89
|
-
npm install
|
|
90
|
-
git checkout <branch>
|
|
91
|
-
npm run dev -- --wsdl <path-or-url> --out <dir> [options]
|
|
90
|
+
The CLI is the primary way to generate SOAP clients.
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
npm link
|
|
92
|
+
```bash
|
|
95
93
|
wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
96
94
|
```
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* `--out` — output directory (created if missing)
|
|
96
|
+
### Required Flags
|
|
97
|
+
- `--wsdl`: Path or URL to the WSDL file.
|
|
98
|
+
- `--out`: Output directory for the generated files.
|
|
102
99
|
|
|
103
|
-
|
|
100
|
+
### Options
|
|
104
101
|
|
|
105
102
|
| Flag | Type | Choices | Default | Description |
|
|
106
103
|
|---------------------|-----------|--------------------------------|--------------|------------------------------------------------------------------|
|
|
@@ -112,153 +109,62 @@ wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
|
112
109
|
| `--decimal-as` | string | string, number | string | How to map xs:decimal (money/precision) |
|
|
113
110
|
| `--date-as` | string | string, Date | string | How to map date/time/duration types |
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Defaults are **string-first** to avoid precision & timezone surprises:
|
|
118
|
-
|
|
119
|
-
* `xs:decimal` → `string` (money/precision safe)
|
|
120
|
-
* 64-bit integers → `string` (you can opt into `bigint` or `number`)
|
|
121
|
-
* dates/times → `string` (transport-friendly, no implicit tz conversion)
|
|
112
|
+
---
|
|
122
113
|
|
|
123
|
-
|
|
114
|
+
## Generated Files
|
|
124
115
|
|
|
125
|
-
|
|
116
|
+
The generator produces the following files in the output directory:
|
|
126
117
|
|
|
127
118
|
```
|
|
128
119
|
<out>/
|
|
129
|
-
types.ts # interfaces
|
|
130
|
-
client.ts #
|
|
131
|
-
runtime.ts #
|
|
132
|
-
meta.ts #
|
|
133
|
-
operations.ts #
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Example: if `User` has `@Id` and `@CreatedAt`, you’ll see:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
interface User {
|
|
140
|
-
Id?: string;
|
|
141
|
-
CreatedAt?: string; // or Date if you chose --date-as Date
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Using in different app module systems
|
|
148
|
-
|
|
149
|
-
### ESM / NodeNext (recommended)
|
|
150
|
-
|
|
151
|
-
Service `tsconfig.json`:
|
|
152
|
-
|
|
153
|
-
```json
|
|
154
|
-
{
|
|
155
|
-
"compilerOptions": {
|
|
156
|
-
"target": "ES2022",
|
|
157
|
-
"module": "NodeNext",
|
|
158
|
-
"moduleResolution": "NodeNext",
|
|
159
|
-
"strict": true,
|
|
160
|
-
"esModuleInterop": true,
|
|
161
|
-
"skipLibCheck": true
|
|
162
|
-
}
|
|
163
|
-
}
|
|
120
|
+
types.ts # TypeScript interfaces and type aliases
|
|
121
|
+
client.ts # Thin wrapper for SOAP operations
|
|
122
|
+
runtime.ts # SOAP runtime utilities
|
|
123
|
+
meta.ts # Metadata for JSON ⇄ SOAP mapping
|
|
124
|
+
operations.ts # Operation metadata (optional, based on --ops-ts)
|
|
164
125
|
```
|
|
165
126
|
|
|
166
|
-
Generate with `--imports js` and import `./client.js`, `./runtime.js`.
|
|
167
|
-
|
|
168
|
-
### CommonJS
|
|
169
|
-
|
|
170
|
-
Keep your app `module: "CommonJS"` and generate with `--imports bare` (imports like `./runtime`).
|
|
171
|
-
TypeScript will compile to `require("./runtime")` cleanly.
|
|
172
|
-
|
|
173
127
|
---
|
|
174
128
|
|
|
175
|
-
##
|
|
129
|
+
## Advanced Usage
|
|
176
130
|
|
|
177
|
-
|
|
178
|
-
* Generate into `src/generated/<service>/` and **commit the generated files** (deterministic CI/Docker).
|
|
179
|
-
* Build your app (the generated code compiles with it).
|
|
131
|
+
### Programmatic API
|
|
180
132
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```json
|
|
184
|
-
{
|
|
185
|
-
"scripts": {
|
|
186
|
-
"codegen:my": "wsdl-tsc --wsdl ./spec/wsdl/MyService.wsdl --out ./src/generated/my --imports js --ops-ts",
|
|
187
|
-
"prebuild": "npm run codegen:my",
|
|
188
|
-
"build": "tsc -p tsconfig.json"
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## Programmatic API (optional)
|
|
133
|
+
You can use the generator programmatically for custom workflows:
|
|
196
134
|
|
|
197
135
|
```ts
|
|
198
|
-
import { compileCatalog
|
|
199
|
-
import { loadWsdlCatalog } from "typescript-wsdl-client/internal-or-your-loader"; // if you expose it
|
|
136
|
+
import { compileCatalog } from "@techspokes/typescript-wsdl-client";
|
|
200
137
|
|
|
201
138
|
const catalog = await loadWsdlCatalog("./spec/wsdl/MyService.wsdl");
|
|
202
139
|
const compiled = compileCatalog(catalog, {
|
|
203
|
-
primitive: { decimalAs: "string", dateAs: "string" }
|
|
140
|
+
primitive: { decimalAs: "string", dateAs: "string" },
|
|
204
141
|
});
|
|
205
|
-
// …write your own emitters or use the built-ins in the CLI.
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Primitive mapping rationale
|
|
211
|
-
|
|
212
|
-
Defaults are **string-first** to avoid precision & timezone surprises:
|
|
213
|
-
|
|
214
|
-
* `xs:decimal` → `string` (money/precision safe)
|
|
215
|
-
* 64-bit integers → `string` (you can opt into `bigint` or `number`)
|
|
216
|
-
* dates/times → `string` (transport-friendly, no implicit tz conversion)
|
|
217
|
-
|
|
218
|
-
Change per your needs with the CLI flags above.
|
|
219
|
-
|
|
220
|
-
---
|
|
221
142
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
# generate from a local WSDL
|
|
226
|
-
npx wsdl-tsc --wsdl ./spec/wsdl/MyService.wsdl --out ./tmp --imports js --ops-ts
|
|
227
|
-
|
|
228
|
-
# quick typecheck of generated output (NodeNext)
|
|
229
|
-
npx tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 ./tmp/*.ts
|
|
143
|
+
// Use the compiled output as needed.
|
|
230
144
|
```
|
|
231
145
|
|
|
232
146
|
---
|
|
233
147
|
|
|
234
148
|
## Troubleshooting
|
|
235
149
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
* **“node-soap not found”** — Install it in your **app**: `npm i soap`.
|
|
150
|
+
- **Missing `runtime.ts`**: Ensure the output directory is writable and you're using the latest version.
|
|
151
|
+
- **Module system issues**: Use `--imports js` for ESM/NodeNext or `--imports bare` for CommonJS.
|
|
152
|
+
- **Security warnings**: Configure `node-soap` security (e.g., `WSSecurity`) as needed.
|
|
240
153
|
|
|
241
154
|
---
|
|
242
155
|
|
|
243
|
-
##
|
|
244
|
-
|
|
245
|
-
Issues and PRs welcome. Please include a **minimal WSDL/XSD** fixture that reproduces the case.
|
|
246
|
-
Node 20+ supported.
|
|
156
|
+
## Roadmap
|
|
247
157
|
|
|
248
|
-
|
|
249
|
-
- See CODE_OF_CONDUCT.md for community expectations.
|
|
250
|
-
- Security reports: see SECURITY.md.
|
|
158
|
+
Please see the [ROADMAP.md](ROADMAP.md) for planned features and improvements.
|
|
251
159
|
|
|
252
160
|
---
|
|
253
161
|
|
|
254
|
-
##
|
|
162
|
+
## Contributing
|
|
255
163
|
|
|
256
|
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
- Roadmap: ROADMAP.md
|
|
261
|
-
- Changelog: CHANGELOG.md
|
|
164
|
+
We welcome contributions! Please see the following resources:
|
|
165
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md): Development workflow and guidelines.
|
|
166
|
+
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md): Community expectations.
|
|
167
|
+
- [SECURITY.md](SECURITY.md): Reporting vulnerabilities.
|
|
262
168
|
|
|
263
169
|
---
|
|
264
170
|
|
|
@@ -275,9 +181,10 @@ MIT © TechSpokes.
|
|
|
275
181
|
- Your Name Here!
|
|
276
182
|
|
|
277
183
|
**Gold Sponsors**
|
|
278
|
-
- [Your Name or Company](https://your-link-here.com)
|
|
184
|
+
- [Your Name or Company (with a link) Here!](https://your-link-here.com)
|
|
279
185
|
|
|
280
186
|
**Platinum Sponsors**
|
|
281
|
-
- [Your Name or Company](https://your-link-here.com)
|
|
187
|
+
- [Your Name or Company (with a link) Here!](https://your-link-here.com)
|
|
188
|
+
- **AND** 30-min one-to-one video meeting on AI, business automations, vacation rentals industry, development, tools, or a subject of your choice.
|
|
282
189
|
|
|
283
190
|
Want to see your name or company here? [Become a sponsor!](https://github.com/sponsors/TechSpokes)
|
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", {
|
|
@@ -94,5 +98,6 @@ emitClient(path.join(outDir, "client.ts"), compiled, {
|
|
|
94
98
|
// @ts-ignore runtime-only options for emitter
|
|
95
99
|
importExt,
|
|
96
100
|
attributesKey: String(argv["attributes-key"]),
|
|
101
|
+
clientName: argv["client-name"],
|
|
97
102
|
});
|
|
98
103
|
console.log(`✅ Generated TypeScript client in ${outDir}`);
|
|
@@ -22,6 +22,21 @@ export type CompiledType = {
|
|
|
22
22
|
declaredType: string;
|
|
23
23
|
}>;
|
|
24
24
|
jsdoc?: string;
|
|
25
|
+
base?: string;
|
|
26
|
+
localAttrs?: Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
tsType: string;
|
|
29
|
+
use?: "required" | "optional";
|
|
30
|
+
declaredType: string;
|
|
31
|
+
}>;
|
|
32
|
+
localElems?: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
tsType: string;
|
|
35
|
+
min: number;
|
|
36
|
+
max: number | "unbounded";
|
|
37
|
+
nillable?: boolean;
|
|
38
|
+
declaredType: string;
|
|
39
|
+
}>;
|
|
25
40
|
};
|
|
26
41
|
export type CompiledAlias = {
|
|
27
42
|
name: string;
|
|
@@ -43,8 +58,11 @@ export type CompiledCatalog = {
|
|
|
43
58
|
soapAction: string;
|
|
44
59
|
inputElement?: QName;
|
|
45
60
|
outputElement?: QName;
|
|
61
|
+
security?: string[];
|
|
46
62
|
}>;
|
|
47
63
|
wsdlTargetNS: string;
|
|
64
|
+
serviceName?: string;
|
|
65
|
+
wsdlUri: string;
|
|
48
66
|
};
|
|
49
67
|
/**
|
|
50
68
|
* Compile a WSDL catalog into an internal representation (CompiledCatalog).
|
|
@@ -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;
|
|
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;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,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;IAEH,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,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;CACJ,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,CA6gBxF"}
|
|
@@ -13,6 +13,56 @@ function makeInlineTypeName(parentTypeName, propName, _max) {
|
|
|
13
13
|
}
|
|
14
14
|
return `${base}Anon`;
|
|
15
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
|
+
}
|
|
16
66
|
/**
|
|
17
67
|
* Compile a WSDL catalog into an internal representation (CompiledCatalog).
|
|
18
68
|
* Steps:
|
|
@@ -198,23 +248,18 @@ export function compileCatalog(cat, _opts) {
|
|
|
198
248
|
// resolves type refs or @ref, applies min/max occurrence and nillable flags
|
|
199
249
|
const collectParticles = (ownerTypeName, node) => {
|
|
200
250
|
const out = [];
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
];
|
|
206
|
-
for (const grp of groups) {
|
|
207
|
-
for (const e of getChildrenWithLocalName(grp, "element")) {
|
|
251
|
+
// process a compositor or element container recursively
|
|
252
|
+
const recurse = (groupNode) => {
|
|
253
|
+
// handle direct element children
|
|
254
|
+
for (const e of getChildrenWithLocalName(groupNode, "element")) {
|
|
208
255
|
const nameOrRef = e["@_name"] || e["@_ref"];
|
|
209
|
-
if (!nameOrRef)
|
|
256
|
+
if (!nameOrRef)
|
|
210
257
|
continue;
|
|
211
|
-
}
|
|
212
258
|
const propName = e["@_name"];
|
|
213
259
|
const min = e["@_minOccurs"] ? Number(e["@_minOccurs"]) : 1;
|
|
214
260
|
const maxAttr = e["@_maxOccurs"];
|
|
215
261
|
const max = maxAttr === "unbounded" ? "unbounded" : maxAttr ? Number(maxAttr) : 1;
|
|
216
262
|
const nillable = e["@_nillable"] === "true";
|
|
217
|
-
// inline complexType: create a nested interface with a generated name
|
|
218
263
|
const inlineComplex = getFirstWithLocalName(e, "complexType");
|
|
219
264
|
const inlineSimple = getFirstWithLocalName(e, "simpleType");
|
|
220
265
|
if (inlineComplex) {
|
|
@@ -223,19 +268,24 @@ export function compileCatalog(cat, _opts) {
|
|
|
223
268
|
out.push({ name: propName || nameOrRef, tsType: rec.name, min, max, nillable, declaredType: `{${schemaNS}}${rec.name}` });
|
|
224
269
|
}
|
|
225
270
|
else if (inlineSimple) {
|
|
226
|
-
// inline simpleType (e.g., list or enumeration)
|
|
227
271
|
const r = compileSimpleTypeNode(inlineSimple, schemaNS, prefixes);
|
|
228
272
|
out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
|
|
229
273
|
}
|
|
230
274
|
else {
|
|
231
|
-
// named type or ref: resolve via QName
|
|
232
275
|
const t = e["@_type"] || e["@_ref"];
|
|
233
276
|
const q = t ? resolveQName(t, schemaNS, prefixes) : { ns: XS, local: "string" };
|
|
234
277
|
const r = resolveTypeRef(q, schemaNS, prefixes);
|
|
235
278
|
out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
|
|
236
279
|
}
|
|
237
280
|
}
|
|
238
|
-
|
|
281
|
+
// recurse into nested compositor groups
|
|
282
|
+
for (const comp of ["sequence", "all", "choice"]) {
|
|
283
|
+
for (const sub of getChildrenWithLocalName(groupNode, comp)) {
|
|
284
|
+
recurse(sub);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
recurse(node);
|
|
239
289
|
return out;
|
|
240
290
|
};
|
|
241
291
|
// Result accumulators
|
|
@@ -248,21 +298,26 @@ export function compileCatalog(cat, _opts) {
|
|
|
248
298
|
const res = getFirstWithLocalName(complexContent, "restriction");
|
|
249
299
|
const node = ext || res;
|
|
250
300
|
if (node) {
|
|
301
|
+
// handle complexContent extension: record base and separate local additions
|
|
302
|
+
let baseName;
|
|
251
303
|
const baseAttr = node["@_base"];
|
|
252
304
|
if (baseAttr) {
|
|
253
305
|
const baseQ = resolveQName(baseAttr, schemaNS, prefixes);
|
|
254
306
|
const baseRec = findComplexRec(baseQ);
|
|
255
307
|
if (baseRec) {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
308
|
+
const baseType = getOrCompileComplex(baseRec.node["@_name"], baseRec.node, baseRec.tns, baseRec.prefixes);
|
|
309
|
+
baseName = baseType.name;
|
|
310
|
+
// inherit base members
|
|
311
|
+
attrs.push(...baseType.attrs);
|
|
312
|
+
elems.push(...baseType.elems);
|
|
260
313
|
}
|
|
261
314
|
}
|
|
262
|
-
// local additions/overrides
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
315
|
+
// collect local additions/overrides
|
|
316
|
+
const locals = collectAttributes(node);
|
|
317
|
+
const localElems = collectParticles(outName, node);
|
|
318
|
+
attrs.push(...locals);
|
|
319
|
+
elems.push(...localElems);
|
|
320
|
+
const result = { name: outName, ns: schemaNS, attrs, elems, base: baseName, localAttrs: locals, localElems };
|
|
266
321
|
compiledMap.set(key, result);
|
|
267
322
|
inProgress.delete(key);
|
|
268
323
|
return result;
|
|
@@ -469,11 +524,38 @@ export function compileCatalog(cat, _opts) {
|
|
|
469
524
|
return { name, soapAction: bOps.get(name) || "", inputElement, outputElement };
|
|
470
525
|
})
|
|
471
526
|
.filter((x) => x != null));
|
|
527
|
+
// --- WS-Policy: scan for security requirements (inline policies only) ---
|
|
528
|
+
const bindingPolicies = getChildrenWithLocalName(soapBinding || {}, "Policy");
|
|
529
|
+
const bindingSec = collectSecurityFromPolicyNodes(bindingPolicies);
|
|
530
|
+
for (const op of ops) {
|
|
531
|
+
const bo = opsNodes.find(o => o?.["@_name"] === op.name) || {};
|
|
532
|
+
const opPolicies = getChildrenWithLocalName(bo, "Policy");
|
|
533
|
+
const opSec = collectSecurityFromPolicyNodes(opPolicies);
|
|
534
|
+
const secSet = new Set([...bindingSec, ...opSec]);
|
|
535
|
+
op.security = Array.from(secSet);
|
|
536
|
+
}
|
|
537
|
+
// --- Service discovery (for client naming) ---
|
|
538
|
+
let serviceName;
|
|
539
|
+
const soapBindingName = soapBinding?.["@_name"];
|
|
540
|
+
const serviceDefs = normalizeArray(defs?.["wsdl:service"] || defs?.["service"]);
|
|
541
|
+
const serviceUsingBinding = serviceDefs.find(s => {
|
|
542
|
+
const ports = getChildrenWithLocalName(s, "port");
|
|
543
|
+
return ports.some(p => {
|
|
544
|
+
const bq = p?.["@_binding"];
|
|
545
|
+
if (!bq || !soapBindingName)
|
|
546
|
+
return false;
|
|
547
|
+
const q = resolveQName(bq, tns, cat.prefixMap);
|
|
548
|
+
return q.local === soapBindingName; // match by local name
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
serviceName = serviceUsingBinding?.["@_name"] || serviceDefs[0]?.["@_name"];
|
|
472
552
|
return {
|
|
473
553
|
types: typesList,
|
|
474
554
|
aliases: aliasList,
|
|
475
555
|
meta: { attrSpec, childType, propMeta },
|
|
476
556
|
operations: ops,
|
|
477
557
|
wsdlTargetNS: defs?.["@_targetNamespace"] || "",
|
|
558
|
+
serviceName,
|
|
559
|
+
wsdlUri: cat.wsdlUri,
|
|
478
560
|
};
|
|
479
561
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -23,6 +23,11 @@ export type CompilerOptions = {
|
|
|
23
23
|
* Attribute bag key for the runtime mapper (node-soap).
|
|
24
24
|
*/
|
|
25
25
|
attributesKey?: string;
|
|
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;
|
|
26
31
|
/**
|
|
27
32
|
* Controls low-level mapping of XSD primitives to TypeScript types. Safe defaults are provided.
|
|
28
33
|
*/
|
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;;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;;OAEG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAChC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,eAM5B,CAAC"}
|
|
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"}
|
|
@@ -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;AAGpD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
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,QAmEpH"}
|
|
@@ -7,7 +7,27 @@ export function emitClient(outFile, compiled, opts) {
|
|
|
7
7
|
lines.push(`import { ATTR_SPEC, CHILD_TYPE, PROP_META } from "./meta${ext}";`);
|
|
8
8
|
lines.push(`import type * as T from "./types${ext}";`);
|
|
9
9
|
lines.push("");
|
|
10
|
-
|
|
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
|
|
22
|
+
const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
23
|
+
const reserved = new Set([
|
|
24
|
+
"break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
|
|
25
|
+
"do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if",
|
|
26
|
+
"import", "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw",
|
|
27
|
+
"true", "try", "typeof", "var", "void", "while", "with", "as", "implements", "interface",
|
|
28
|
+
"let", "package", "private", "protected", "public", "static", "yield", "constructor"
|
|
29
|
+
]);
|
|
30
|
+
lines.push(`export class ${className} {`);
|
|
11
31
|
lines.push(` constructor(private source: string | any, private attributesKey: string = "${opts.attributesKey || "$attributes"}") {}`);
|
|
12
32
|
lines.push(` async _client() {`);
|
|
13
33
|
lines.push(` if (typeof this.source === 'string') return createSoapClient({ wsdlUrl: this.source });`);
|
|
@@ -21,14 +41,24 @@ export function emitClient(outFile, compiled, opts) {
|
|
|
21
41
|
const outTs = outTypeName ? `T.${outTypeName}` : `any`;
|
|
22
42
|
const inMetaKey = inTypeName ?? m;
|
|
23
43
|
const outMetaKey = outTypeName ?? m;
|
|
24
|
-
|
|
25
|
-
|
|
44
|
+
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);
|
|
26
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()."); }`);
|
|
56
|
+
}
|
|
27
57
|
lines.push(` const meta = { ATTR_SPEC, CHILD_TYPE, PROP_META } as const;`);
|
|
28
|
-
lines.push(` const soapArgs = toSoapArgs(args as any,
|
|
58
|
+
lines.push(` const soapArgs = toSoapArgs(args as any, ${JSON.stringify(inMetaKey)}, meta, this.attributesKey);`);
|
|
29
59
|
lines.push(` return new Promise((resolve, reject) => {`);
|
|
30
|
-
lines.push(`
|
|
31
|
-
lines.push(` if (err) reject(err); else resolve(fromSoapResult(result,
|
|
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));`);
|
|
32
62
|
lines.push(` });`);
|
|
33
63
|
lines.push(` });`);
|
|
34
64
|
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
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typesEmitter.d.ts","sourceRoot":"","sources":["../../src/emit/typesEmitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
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"}
|
|
@@ -11,37 +11,71 @@ export function emitTypes(outFile, compiled) {
|
|
|
11
11
|
const lines = [];
|
|
12
12
|
// Convenience lookups
|
|
13
13
|
const typeNames = new Set(compiled.types.map((t) => t.name));
|
|
14
|
+
const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
15
|
+
const emitPropName = (name) => (isValidIdent(name) ? name : JSON.stringify(name));
|
|
14
16
|
//
|
|
15
17
|
// 1) Named simple types (aliases) first
|
|
16
18
|
//
|
|
19
|
+
// sort aliases by name to ensure consistent order
|
|
20
|
+
compiled.aliases.sort((a, b) => a.name.localeCompare(b.name));
|
|
17
21
|
for (const a of compiled.aliases) {
|
|
18
|
-
const ann = a.jsdoc ?
|
|
22
|
+
const ann = a.jsdoc ? `/** @xsd ${a.jsdoc} */\n` : "";
|
|
19
23
|
lines.push(`${ann}export type ${a.name} = ${a.tsType};`);
|
|
20
24
|
lines.push("");
|
|
21
25
|
}
|
|
22
26
|
//
|
|
23
27
|
// 2) Complex types as interfaces
|
|
24
28
|
//
|
|
29
|
+
// sort types by name to ensure consistent order
|
|
30
|
+
compiled.types.sort((a, b) => a.name.localeCompare(b.name));
|
|
25
31
|
for (const t of compiled.types) {
|
|
26
|
-
// Detect mis-mapped simpleContent extension
|
|
27
|
-
|
|
32
|
+
// Detect complexContent extension via compiled metadata or mis-mapped simpleContent extension
|
|
33
|
+
const complexBase = t.base;
|
|
34
|
+
// Detect mis-mapped simpleContent extension: single "$value" whose tsType is another named interface
|
|
28
35
|
const valueElems = (t.elems || []).filter((e) => e.name === "$value" &&
|
|
29
36
|
(e.max === 1 || e.max === undefined) &&
|
|
30
37
|
typeof e.tsType === "string" &&
|
|
31
38
|
typeNames.has(e.tsType));
|
|
32
|
-
const isSimpleContentExtension = (t.elems?.length || 0) === 1 && valueElems.length === 1;
|
|
33
|
-
const baseName = isSimpleContentExtension ? valueElems[0].tsType : undefined;
|
|
34
|
-
// Header
|
|
39
|
+
const isSimpleContentExtension = !complexBase && (t.elems?.length || 0) === 1 && valueElems.length === 1;
|
|
40
|
+
const baseName = complexBase ?? (isSimpleContentExtension ? valueElems[0].tsType : undefined);
|
|
41
|
+
// Header: extend base type if applicable
|
|
35
42
|
if (baseName) {
|
|
36
43
|
lines.push(`export interface ${t.name} extends ${baseName} {`);
|
|
37
44
|
}
|
|
38
45
|
else {
|
|
39
46
|
lines.push(`export interface ${t.name} {`);
|
|
40
47
|
}
|
|
48
|
+
// Prepare lists: for complexContent extension use only local additions
|
|
49
|
+
const attrsToEmit = complexBase ? (t.localAttrs || []) : (t.attrs || []);
|
|
50
|
+
// Elements list similar
|
|
51
|
+
let elementsToEmit = complexBase ? (t.localElems || []) : (t.elems || []);
|
|
52
|
+
// SimpleContent extension special handling drops synthetic $value
|
|
53
|
+
if (isSimpleContentExtension && !complexBase) {
|
|
54
|
+
const idx = elementsToEmit.findIndex(e => e.name === "$value");
|
|
55
|
+
if (idx >= 0)
|
|
56
|
+
elementsToEmit.splice(idx, 1);
|
|
57
|
+
}
|
|
41
58
|
//
|
|
42
59
|
// Attributes — with JSDoc on every attribute
|
|
43
60
|
//
|
|
44
|
-
|
|
61
|
+
if (0 < attrsToEmit.length) {
|
|
62
|
+
// add attributes header comment
|
|
63
|
+
lines.push("");
|
|
64
|
+
lines.push(" /**");
|
|
65
|
+
lines.push((1 === t.attrs.length) ? " * Attribute." : " * Attributes.");
|
|
66
|
+
lines.push(" */");
|
|
67
|
+
// Sort the elements with the following logic: required first (sorted a-z), then optional (sorted a-z)
|
|
68
|
+
attrsToEmit.sort((a, b) => {
|
|
69
|
+
// Required attributes come before optional attributes
|
|
70
|
+
if (a.use === "required" && b.use !== "required")
|
|
71
|
+
return -1; // `a` is required, b is optional
|
|
72
|
+
if (a.use !== "required" && b.use === "required")
|
|
73
|
+
return 1; // `a` is optional, b is required
|
|
74
|
+
// Within the same group (required or optional), sort alphabetically
|
|
75
|
+
return a.name.localeCompare(b.name);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const a of attrsToEmit) {
|
|
45
79
|
const opt = a.use === "required" ? "" : "?";
|
|
46
80
|
const annObj = {
|
|
47
81
|
kind: "attribute",
|
|
@@ -49,40 +83,54 @@ export function emitTypes(outFile, compiled) {
|
|
|
49
83
|
use: a.use || "optional",
|
|
50
84
|
};
|
|
51
85
|
const ann = ` /** @xsd ${JSON.stringify(annObj)} */`;
|
|
86
|
+
lines.push("");
|
|
52
87
|
lines.push(ann);
|
|
53
|
-
lines.push(` ${a.name}${opt}: ${a.tsType};`);
|
|
88
|
+
lines.push(` ${emitPropName(a.name)}${opt}: ${a.tsType};`);
|
|
54
89
|
}
|
|
55
90
|
//
|
|
56
91
|
// Elements — with JSDoc on every element
|
|
57
92
|
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
93
|
+
// elementsToEmit already prepared above
|
|
94
|
+
if (0 < elementsToEmit.length) {
|
|
95
|
+
// add elements header comment
|
|
96
|
+
lines.push("");
|
|
97
|
+
lines.push(" /**");
|
|
98
|
+
lines.push((1 === elementsToEmit.length) ? " * Child element." : " * Children elements.");
|
|
99
|
+
lines.push(" */");
|
|
100
|
+
// Sort the elements with the following logic: required first (sorted a-z), then optional (sorted a-z), and finally "$value" if present.
|
|
101
|
+
elementsToEmit.sort((a, b) => {
|
|
102
|
+
// Special case: $value is always last
|
|
103
|
+
if (a.name === "$value")
|
|
104
|
+
return 1;
|
|
105
|
+
if (b.name === "$value")
|
|
106
|
+
return -1;
|
|
107
|
+
// Required elements come before optional elements
|
|
108
|
+
if (a.min !== 0 && b.min === 0)
|
|
109
|
+
return -1; // `a` is required, b is optional
|
|
110
|
+
if (a.min === 0 && b.min !== 0)
|
|
111
|
+
return 1; // `a` is optional, b is required
|
|
112
|
+
// Within the same group (required or optional), sort alphabetically
|
|
113
|
+
return a.name.localeCompare(b.name);
|
|
114
|
+
});
|
|
64
115
|
}
|
|
65
|
-
// Ensure "$value" is last if present
|
|
66
|
-
elementsToEmit.sort((a, b) => {
|
|
67
|
-
if (a.name === "$value" && b.name !== "$value")
|
|
68
|
-
return 1;
|
|
69
|
-
if (a.name !== "$value" && b.name === "$value")
|
|
70
|
-
return -1;
|
|
71
|
-
return 0;
|
|
72
|
-
});
|
|
73
116
|
for (const e of elementsToEmit) {
|
|
74
|
-
const isArray = e.max === "unbounded" ||
|
|
75
|
-
(typeof e.max === "number" && e.max > 1);
|
|
117
|
+
const isArray = e.max === "unbounded" || (e.max > 1);
|
|
76
118
|
const arr = isArray ? "[]" : "";
|
|
77
119
|
const opt = (e.min ?? 0) === 0 ? "?" : "";
|
|
78
120
|
const annObj = {
|
|
79
|
-
kind
|
|
121
|
+
// if a.name === "$value", the kind should be "scalar value"
|
|
122
|
+
kind: e.name === "$value" ? "scalar value" : "element",
|
|
80
123
|
type: e.declaredType,
|
|
81
124
|
occurs: { min: e.min, max: e.max, nillable: e.nillable ?? false },
|
|
82
125
|
};
|
|
126
|
+
// if the a.name === "$value" and we have more child elements, add an empty line before the annotation
|
|
127
|
+
if ((e.name === "$value") && (1 < elementsToEmit.length)) {
|
|
128
|
+
lines.push("");
|
|
129
|
+
}
|
|
83
130
|
const ann = ` /** @xsd ${JSON.stringify(annObj)} */`;
|
|
131
|
+
lines.push("");
|
|
84
132
|
lines.push(ann);
|
|
85
|
-
lines.push(` ${e.name}${opt}: ${e.tsType}${arr};`);
|
|
133
|
+
lines.push(` ${emitPropName(e.name)}${opt}: ${e.tsType}${arr};`);
|
|
86
134
|
}
|
|
87
135
|
lines.push("}");
|
|
88
136
|
lines.push("");
|
package/dist/util/xml.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../src/util/xml.ts"],"names":[],"mappings":"AAAA,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,
|
|
1
|
+
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../src/util/xml.ts"],"names":[],"mappings":"AAAA,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"}
|
package/dist/util/xml.js
CHANGED
|
@@ -25,7 +25,35 @@ export function getFirstWithLocalName(node, local) {
|
|
|
25
25
|
}
|
|
26
26
|
/** Simple PascalCase helper used across emitters/parsers. */
|
|
27
27
|
export function pascal(s) {
|
|
28
|
-
|
|
28
|
+
const raw = String(s ?? "");
|
|
29
|
+
// Split on underscores to preserve them literally
|
|
30
|
+
const segments = raw.split("_");
|
|
31
|
+
const cased = segments.map(seg => {
|
|
32
|
+
// Uppercase letters after common separators (start, space, dash, dot, colon, slash)
|
|
33
|
+
const up = seg.replace(/(^|[-\s.:\/])([A-Za-z0-9_$])/g, (_m, _sep, c) => String(c).toUpperCase());
|
|
34
|
+
// Remove disallowed identifier characters but preserve A-Z, a-z, 0-9, _ and $
|
|
35
|
+
return up.replace(/[^A-Za-z0-9_$]/g, "");
|
|
36
|
+
});
|
|
37
|
+
let out = cased.join("_");
|
|
38
|
+
if (!out)
|
|
39
|
+
out = "_"; // fallback
|
|
40
|
+
if (/^[0-9]/.test(out))
|
|
41
|
+
out = `_${out}`; // ensure valid identifier start
|
|
42
|
+
// guard against TypeScript reserved keywords when the identifier equals them exactly
|
|
43
|
+
const reserved = [
|
|
44
|
+
"break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
|
|
45
|
+
"do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if",
|
|
46
|
+
"import", "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw",
|
|
47
|
+
"true", "try", "typeof", "var", "void", "while", "with", "as", "implements", "interface",
|
|
48
|
+
"let", "package", "private", "protected", "public", "static", "yield", "any", "boolean",
|
|
49
|
+
"constructor", "declare", "get", "module", "require", "number", "set", "string", "symbol",
|
|
50
|
+
"type", "from", "of"
|
|
51
|
+
];
|
|
52
|
+
const lower = out.toLowerCase();
|
|
53
|
+
if (reserved.includes(lower)) {
|
|
54
|
+
out = `_${out}`;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
29
57
|
}
|
|
30
58
|
export function resolveQName(qname, defaultNS, prefixes) {
|
|
31
59
|
if (!qname)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techspokes/typescript-wsdl-client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
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",
|