@techspokes/typescript-wsdl-client 0.2.2 → 0.2.6
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 +69 -193
- package/dist/compiler/schemaCompiler.d.ts +15 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +44 -42
- package/dist/emit/clientEmitter.d.ts.map +1 -1
- package/dist/emit/clientEmitter.js +19 -4
- 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,285 +11,160 @@
|
|
|
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
34
|
npm i -D @techspokes/typescript-wsdl-client
|
|
37
|
-
#
|
|
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
|
-
// class name defaults to <ServiceName>SoapClient (from WSDL), e.g., MyServiceSoapClient
|
|
53
55
|
import { MyServiceSoapClient } from "./generated/my/client.js";
|
|
54
|
-
|
|
55
|
-
// optional: pass security straight into createSoapClient (node-soap security instance)
|
|
56
56
|
import soap from "soap";
|
|
57
|
-
const security = new soap.WSSecurity("user", "pass");
|
|
58
57
|
|
|
58
|
+
const security = new soap.WSSecurity("user", "pass");
|
|
59
59
|
const soapClient = await createSoapClient({
|
|
60
60
|
wsdlUrl: "https://example.com/MyService?wsdl",
|
|
61
|
-
security,
|
|
61
|
+
security,
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
const client = new MyServiceSoapClient(soapClient, "$attributes");
|
|
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.
|
|
69
|
-
const rs = await client.MyOperation({
|
|
64
|
+
const client = new MyServiceSoapClient(soapClient, "$attributes");
|
|
65
|
+
const response = await client.MyOperation({
|
|
70
66
|
MyOperationRQ: {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
MyElement: {
|
|
68
|
+
MyAttribute: "value",
|
|
69
|
+
ChildElementA: "valueA",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
75
72
|
});
|
|
76
73
|
|
|
77
|
-
console.log(
|
|
74
|
+
console.log(response);
|
|
78
75
|
```
|
|
79
76
|
|
|
80
|
-
> The generator always emits TypeScript sources (`*.ts`). You compile them with your app.
|
|
81
|
-
|
|
82
77
|
---
|
|
83
78
|
|
|
84
|
-
##
|
|
79
|
+
## Features
|
|
85
80
|
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
|
|
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.
|
|
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.
|
|
92
85
|
|
|
93
86
|
---
|
|
94
87
|
|
|
95
|
-
## CLI
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Local development
|
|
88
|
+
## CLI Usage
|
|
102
89
|
|
|
103
|
-
|
|
90
|
+
The CLI is the primary way to generate SOAP clients.
|
|
104
91
|
|
|
105
92
|
```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
93
|
wsdl-tsc --wsdl <path-or-url> --out <dir> [options]
|
|
118
94
|
```
|
|
119
95
|
|
|
120
|
-
|
|
96
|
+
### Required Flags
|
|
97
|
+
- `--wsdl`: Path or URL to the WSDL file.
|
|
98
|
+
- `--out`: Output directory for the generated files.
|
|
121
99
|
|
|
122
|
-
|
|
123
|
-
* `--out` — output directory (created if missing)
|
|
124
|
-
|
|
125
|
-
**Options**
|
|
100
|
+
### Options
|
|
126
101
|
|
|
127
102
|
| Flag | Type | Choices | Default | Description |
|
|
128
103
|
|---------------------|-----------|--------------------------------|--------------|------------------------------------------------------------------|
|
|
129
104
|
| `--imports` | string | js, ts, bare | js | Intra-generated import specifiers: '.js', '.ts', or bare |
|
|
130
105
|
| `--ops-ts` | boolean | true, false | true | Emit `operations.ts` instead of JSON |
|
|
131
106
|
| `--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) |
|
|
133
107
|
| `--int64-as` | string | string, number, bigint | string | How to map xs:long/xs:unsignedLong |
|
|
134
108
|
| `--bigint-as` | string | string, number | string | How to map xs:integer family (positive/nonNegative/etc.) |
|
|
135
109
|
| `--decimal-as` | string | string, number | string | How to map xs:decimal (money/precision) |
|
|
136
110
|
| `--date-as` | string | string, Date | string | How to map date/time/duration types |
|
|
137
111
|
|
|
138
|
-
|
|
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
|
-
|
|
145
|
-
**Primitive mapping (safe defaults)**
|
|
146
|
-
|
|
147
|
-
Defaults are **string-first** to avoid precision & timezone surprises:
|
|
148
|
-
|
|
149
|
-
* `xs:decimal` → `string` (money/precision safe)
|
|
150
|
-
* 64-bit integers → `string` (you can opt into `bigint` or `number`)
|
|
151
|
-
* dates/times → `string` (transport-friendly, no implicit tz conversion)
|
|
112
|
+
---
|
|
152
113
|
|
|
153
|
-
|
|
114
|
+
## Generated Files
|
|
154
115
|
|
|
155
|
-
|
|
116
|
+
The generator produces the following files in the output directory:
|
|
156
117
|
|
|
157
118
|
```
|
|
158
119
|
<out>/
|
|
159
|
-
types.ts # interfaces
|
|
160
|
-
client.ts #
|
|
161
|
-
runtime.ts #
|
|
162
|
-
meta.ts #
|
|
163
|
-
operations.ts #
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Example: if `User` has `@Id` and `@CreatedAt`, you’ll see:
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
interface User {
|
|
170
|
-
Id?: string;
|
|
171
|
-
CreatedAt?: string; // or Date if you chose --date-as Date
|
|
172
|
-
}
|
|
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)
|
|
173
125
|
```
|
|
174
126
|
|
|
175
127
|
---
|
|
176
128
|
|
|
177
|
-
##
|
|
178
|
-
|
|
179
|
-
### ESM / NodeNext (recommended)
|
|
129
|
+
## Advanced Usage
|
|
180
130
|
|
|
181
|
-
|
|
131
|
+
### Programmatic API
|
|
182
132
|
|
|
183
|
-
|
|
184
|
-
{
|
|
185
|
-
"compilerOptions": {
|
|
186
|
-
"target": "ES2022",
|
|
187
|
-
"module": "NodeNext",
|
|
188
|
-
"moduleResolution": "NodeNext",
|
|
189
|
-
"strict": true,
|
|
190
|
-
"esModuleInterop": true,
|
|
191
|
-
"skipLibCheck": true
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Generate with `--imports js` and import `./client.js`, `./runtime.js`.
|
|
197
|
-
|
|
198
|
-
### CommonJS
|
|
199
|
-
|
|
200
|
-
Keep your app `module: "CommonJS"` and generate with `--imports bare` (imports like `./runtime`).
|
|
201
|
-
TypeScript will compile to `require("./runtime")` cleanly.
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## Recommended workflow
|
|
206
|
-
|
|
207
|
-
* **Vendor** your WSDL(s) in `spec/wsdl/` for reproducible builds.
|
|
208
|
-
* Generate into `src/generated/<service>/` and **commit the generated files** (deterministic CI/Docker).
|
|
209
|
-
* Build your app (the generated code compiles with it).
|
|
210
|
-
|
|
211
|
-
**Example scripts (app `package.json`):**
|
|
212
|
-
|
|
213
|
-
```json
|
|
214
|
-
{
|
|
215
|
-
"scripts": {
|
|
216
|
-
"codegen:my": "wsdl-tsc --wsdl ./spec/wsdl/MyService.wsdl --out ./src/generated/my --imports js --ops-ts",
|
|
217
|
-
"prebuild": "npm run codegen:my",
|
|
218
|
-
"build": "tsc -p tsconfig.json"
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## Programmatic API (optional)
|
|
133
|
+
You can use the generator programmatically for custom workflows:
|
|
226
134
|
|
|
227
135
|
```ts
|
|
228
|
-
import { compileCatalog
|
|
229
|
-
import { loadWsdlCatalog } from "@techspokes/typescript-wsdl-client/internal-or-your-loader"; // if you expose it
|
|
136
|
+
import { compileCatalog } from "@techspokes/typescript-wsdl-client";
|
|
230
137
|
|
|
231
138
|
const catalog = await loadWsdlCatalog("./spec/wsdl/MyService.wsdl");
|
|
232
139
|
const compiled = compileCatalog(catalog, {
|
|
233
|
-
primitive: { decimalAs: "string", dateAs: "string" }
|
|
140
|
+
primitive: { decimalAs: "string", dateAs: "string" },
|
|
234
141
|
});
|
|
235
|
-
// …write your own emitters or use the built-ins in the CLI.
|
|
236
|
-
```
|
|
237
142
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
## Primitive mapping rationale
|
|
241
|
-
|
|
242
|
-
Defaults are **string-first** to avoid precision & timezone surprises:
|
|
243
|
-
|
|
244
|
-
* `xs:decimal` → `string` (money/precision safe)
|
|
245
|
-
* 64-bit integers → `string` (you can opt into `bigint` or `number`)
|
|
246
|
-
* dates/times → `string` (transport-friendly, no implicit tz conversion)
|
|
247
|
-
|
|
248
|
-
Change per your needs with the CLI flags above.
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## Minimal test you can run
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
# generate from a local WSDL
|
|
256
|
-
npx wsdl-tsc --wsdl ./spec/wsdl/MyService.wsdl --out ./tmp --imports js --ops-ts
|
|
257
|
-
|
|
258
|
-
# quick typecheck of generated output (NodeNext)
|
|
259
|
-
npx tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 ./tmp/*.ts
|
|
143
|
+
// Use the compiled output as needed.
|
|
260
144
|
```
|
|
261
145
|
|
|
262
146
|
---
|
|
263
147
|
|
|
264
148
|
## Troubleshooting
|
|
265
149
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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`.
|
|
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.
|
|
271
153
|
|
|
272
154
|
---
|
|
273
155
|
|
|
274
|
-
##
|
|
275
|
-
|
|
276
|
-
Issues and PRs welcome. Please include a **minimal WSDL/XSD** fixture that reproduces the case.
|
|
277
|
-
Node 20+ supported.
|
|
156
|
+
## Roadmap
|
|
278
157
|
|
|
279
|
-
|
|
280
|
-
- See CODE_OF_CONDUCT.md for community expectations.
|
|
281
|
-
- Security reports: see SECURITY.md.
|
|
158
|
+
Please see the [ROADMAP.md](ROADMAP.md) for planned features and improvements.
|
|
282
159
|
|
|
283
160
|
---
|
|
284
161
|
|
|
285
|
-
##
|
|
162
|
+
## Contributing
|
|
286
163
|
|
|
287
|
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
290
|
-
-
|
|
291
|
-
- Roadmap: ROADMAP.md
|
|
292
|
-
- 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.
|
|
293
168
|
|
|
294
169
|
---
|
|
295
170
|
|
|
@@ -306,9 +181,10 @@ MIT © TechSpokes.
|
|
|
306
181
|
- Your Name Here!
|
|
307
182
|
|
|
308
183
|
**Gold Sponsors**
|
|
309
|
-
- [Your Name or Company](https://your-link-here.com)
|
|
184
|
+
- [Your Name or Company (with a link) Here!](https://your-link-here.com)
|
|
310
185
|
|
|
311
186
|
**Platinum Sponsors**
|
|
312
|
-
- [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.
|
|
313
189
|
|
|
314
190
|
Want to see your name or company here? [Become a sponsor!](https://github.com/sponsors/TechSpokes)
|
|
@@ -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;
|
|
@@ -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,CA2fxF"}
|
|
@@ -176,21 +176,10 @@ export function compileCatalog(cat, _opts) {
|
|
|
176
176
|
function getOrCompileComplex(name, cnode, schemaNS, prefixes) {
|
|
177
177
|
const outName = pascal(name);
|
|
178
178
|
const key = `${schemaNS}|${outName}`;
|
|
179
|
-
|
|
180
|
-
if (present) {
|
|
181
|
-
return present;
|
|
182
|
-
}
|
|
183
|
-
if (inProgress.has(key)) {
|
|
184
|
-
// minimal cycle break
|
|
185
|
-
return { name: outName, ns: schemaNS, attrs: [], elems: [] };
|
|
186
|
-
}
|
|
187
|
-
inProgress.add(key);
|
|
188
|
-
// mergeAttrs: combine base attributes with local ones, overriding duplicates by name
|
|
179
|
+
// hoisted helpers for merging and collecting
|
|
189
180
|
const mergeAttrs = (into, list) => {
|
|
190
|
-
// build index of existing attribute names
|
|
191
181
|
const idx = new Map();
|
|
192
182
|
into.forEach((a, i) => idx.set(a.name, i));
|
|
193
|
-
// for each new attr, add or override existing
|
|
194
183
|
for (const a of list) {
|
|
195
184
|
const pos = idx.get(a.name);
|
|
196
185
|
if (pos == null) {
|
|
@@ -198,11 +187,10 @@ export function compileCatalog(cat, _opts) {
|
|
|
198
187
|
into.push(a);
|
|
199
188
|
}
|
|
200
189
|
else {
|
|
201
|
-
into[pos] = a;
|
|
190
|
+
into[pos] = a;
|
|
202
191
|
}
|
|
203
192
|
}
|
|
204
193
|
};
|
|
205
|
-
// mergeElems: combine base elements (particles) with local ones, preserving unique names and overriding duplicates
|
|
206
194
|
const mergeElems = (into, list) => {
|
|
207
195
|
const idx = new Map();
|
|
208
196
|
into.forEach((e, i) => idx.set(e.name, i));
|
|
@@ -213,12 +201,10 @@ export function compileCatalog(cat, _opts) {
|
|
|
213
201
|
into.push(e);
|
|
214
202
|
}
|
|
215
203
|
else {
|
|
216
|
-
into[pos] = e;
|
|
204
|
+
into[pos] = e;
|
|
217
205
|
}
|
|
218
206
|
}
|
|
219
207
|
};
|
|
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
|
|
222
208
|
const collectAttributes = (node) => {
|
|
223
209
|
const out = [];
|
|
224
210
|
const attrs = getChildrenWithLocalName(node, "attribute");
|
|
@@ -243,28 +229,20 @@ export function compileCatalog(cat, _opts) {
|
|
|
243
229
|
}
|
|
244
230
|
return out;
|
|
245
231
|
};
|
|
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
|
|
249
232
|
const collectParticles = (ownerTypeName, node) => {
|
|
250
233
|
const out = [];
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
];
|
|
256
|
-
for (const grp of groups) {
|
|
257
|
-
for (const e of getChildrenWithLocalName(grp, "element")) {
|
|
234
|
+
// process a compositor or element container recursively
|
|
235
|
+
const recurse = (groupNode) => {
|
|
236
|
+
// handle direct element children
|
|
237
|
+
for (const e of getChildrenWithLocalName(groupNode, "element")) {
|
|
258
238
|
const nameOrRef = e["@_name"] || e["@_ref"];
|
|
259
|
-
if (!nameOrRef)
|
|
239
|
+
if (!nameOrRef)
|
|
260
240
|
continue;
|
|
261
|
-
}
|
|
262
241
|
const propName = e["@_name"];
|
|
263
242
|
const min = e["@_minOccurs"] ? Number(e["@_minOccurs"]) : 1;
|
|
264
243
|
const maxAttr = e["@_maxOccurs"];
|
|
265
244
|
const max = maxAttr === "unbounded" ? "unbounded" : maxAttr ? Number(maxAttr) : 1;
|
|
266
245
|
const nillable = e["@_nillable"] === "true";
|
|
267
|
-
// inline complexType: create a nested interface with a generated name
|
|
268
246
|
const inlineComplex = getFirstWithLocalName(e, "complexType");
|
|
269
247
|
const inlineSimple = getFirstWithLocalName(e, "simpleType");
|
|
270
248
|
if (inlineComplex) {
|
|
@@ -273,22 +251,41 @@ export function compileCatalog(cat, _opts) {
|
|
|
273
251
|
out.push({ name: propName || nameOrRef, tsType: rec.name, min, max, nillable, declaredType: `{${schemaNS}}${rec.name}` });
|
|
274
252
|
}
|
|
275
253
|
else if (inlineSimple) {
|
|
276
|
-
// inline simpleType (e.g., list or enumeration)
|
|
277
254
|
const r = compileSimpleTypeNode(inlineSimple, schemaNS, prefixes);
|
|
278
255
|
out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
|
|
279
256
|
}
|
|
280
257
|
else {
|
|
281
|
-
// named type or ref: resolve via QName
|
|
282
258
|
const t = e["@_type"] || e["@_ref"];
|
|
283
259
|
const q = t ? resolveQName(t, schemaNS, prefixes) : { ns: XS, local: "string" };
|
|
284
260
|
const r = resolveTypeRef(q, schemaNS, prefixes);
|
|
285
261
|
out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
|
|
286
262
|
}
|
|
287
263
|
}
|
|
288
|
-
|
|
264
|
+
// recurse into nested compositor groups
|
|
265
|
+
for (const comp of ["sequence", "all", "choice"]) {
|
|
266
|
+
for (const sub of getChildrenWithLocalName(groupNode, comp)) {
|
|
267
|
+
recurse(sub);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
recurse(node);
|
|
289
272
|
return out;
|
|
290
273
|
};
|
|
291
|
-
|
|
274
|
+
const present = compiledMap.get(key);
|
|
275
|
+
if (present) {
|
|
276
|
+
// On duplicate definitions, merge new attributes and elements
|
|
277
|
+
const newAttrs = collectAttributes(cnode);
|
|
278
|
+
const newElems = collectParticles(outName, cnode);
|
|
279
|
+
mergeAttrs(present.attrs, newAttrs);
|
|
280
|
+
mergeElems(present.elems, newElems);
|
|
281
|
+
return present;
|
|
282
|
+
}
|
|
283
|
+
if (inProgress.has(key)) {
|
|
284
|
+
// minimal cycle break
|
|
285
|
+
return { name: outName, ns: schemaNS, attrs: [], elems: [] };
|
|
286
|
+
}
|
|
287
|
+
inProgress.add(key);
|
|
288
|
+
// result accumulators
|
|
292
289
|
const attrs = [];
|
|
293
290
|
const elems = [];
|
|
294
291
|
// Inheritance: complexContent
|
|
@@ -298,21 +295,26 @@ export function compileCatalog(cat, _opts) {
|
|
|
298
295
|
const res = getFirstWithLocalName(complexContent, "restriction");
|
|
299
296
|
const node = ext || res;
|
|
300
297
|
if (node) {
|
|
298
|
+
// handle complexContent extension: record base and separate local additions
|
|
299
|
+
let baseName;
|
|
301
300
|
const baseAttr = node["@_base"];
|
|
302
301
|
if (baseAttr) {
|
|
303
302
|
const baseQ = resolveQName(baseAttr, schemaNS, prefixes);
|
|
304
303
|
const baseRec = findComplexRec(baseQ);
|
|
305
304
|
if (baseRec) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
305
|
+
const baseType = getOrCompileComplex(baseRec.node["@_name"], baseRec.node, baseRec.tns, baseRec.prefixes);
|
|
306
|
+
baseName = baseType.name;
|
|
307
|
+
// inherit base members
|
|
308
|
+
attrs.push(...baseType.attrs);
|
|
309
|
+
elems.push(...baseType.elems);
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
-
// local additions/overrides
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
// collect local additions/overrides
|
|
313
|
+
const locals = collectAttributes(node);
|
|
314
|
+
const localElems = collectParticles(outName, node);
|
|
315
|
+
attrs.push(...locals);
|
|
316
|
+
elems.push(...localElems);
|
|
317
|
+
const result = { name: outName, ns: schemaNS, attrs, elems, base: baseName, localAttrs: locals, localElems };
|
|
316
318
|
compiledMap.set(key, result);
|
|
317
319
|
inProgress.delete(key);
|
|
318
320
|
return result;
|
|
@@ -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"}
|
|
@@ -18,6 +18,15 @@ export function emitClient(outFile, compiled, opts) {
|
|
|
18
18
|
return noExt ? pascal(noExt) : "";
|
|
19
19
|
})();
|
|
20
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
|
+
]);
|
|
21
30
|
lines.push(`export class ${className} {`);
|
|
22
31
|
lines.push(` constructor(private source: string | any, private attributesKey: string = "${opts.attributesKey || "$attributes"}") {}`);
|
|
23
32
|
lines.push(` async _client() {`);
|
|
@@ -33,17 +42,23 @@ export function emitClient(outFile, compiled, opts) {
|
|
|
33
42
|
const inMetaKey = inTypeName ?? m;
|
|
34
43
|
const outMetaKey = outTypeName ?? m;
|
|
35
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)}]`;
|
|
36
51
|
lines.push(` /** SOAPAction: ${op.soapAction}${secHints.length ? `\n * Security (WSDL policy hint): ${secHints.join(", ")}` : ""} */`);
|
|
37
|
-
lines.push(
|
|
52
|
+
lines.push(methodHeader);
|
|
38
53
|
lines.push(` const c: any = await this._client();`);
|
|
39
54
|
if (secHints.length) {
|
|
40
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()."); }`);
|
|
41
56
|
}
|
|
42
57
|
lines.push(` const meta = { ATTR_SPEC, CHILD_TYPE, PROP_META } as const;`);
|
|
43
|
-
lines.push(` const soapArgs = toSoapArgs(args as any,
|
|
58
|
+
lines.push(` const soapArgs = toSoapArgs(args as any, ${JSON.stringify(inMetaKey)}, meta, this.attributesKey);`);
|
|
44
59
|
lines.push(` return new Promise((resolve, reject) => {`);
|
|
45
|
-
lines.push(`
|
|
46
|
-
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));`);
|
|
47
62
|
lines.push(` });`);
|
|
48
63
|
lines.push(` });`);
|
|
49
64
|
lines.push(` }`);
|
|
@@ -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.6",
|
|
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",
|