@techspokes/typescript-wsdl-client 0.2.2 → 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 CHANGED
@@ -11,285 +11,160 @@
11
11
  [![TechSpokes Org](https://img.shields.io/badge/org-techspokes-181717?logo=github)](https://github.com/techspokes)
12
12
  [![Sponsor TechSpokes](https://img.shields.io/badge/sponsor-GitHub-blue?logo=github-sponsors)](https://github.com/sponsors/TechSpokes)
13
13
 
14
- **TypeScript WSDL → SOAP client generator.**
15
- Reads WSDL/XSD (with imports) and emits a small, typed client you can compile into your app.
14
+ ## Introduction
16
15
 
17
- * `xs:attribute` support (attributes become first-class fields, not bags)
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
- Vendor: **[TechSpokes](https://www.techspokes.com)** · Contact: **[contact page](https://www.techspokes.com/contact/)**
27
- Maintainer: **Serge Liatko** ([@sergeliatko](https://github.com/sergeliatko)) · GitHub org: [@techspokes](https://github.com/techspokes)
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
- ## Quick start
29
+ ## Installation
32
30
 
33
- Install the generator (dev-time) in your project:
31
+ Install the generator as a development dependency:
34
32
 
35
33
  ```bash
36
34
  npm i -D @techspokes/typescript-wsdl-client
37
- # your app will use node-soap at runtime:
35
+ # Your app will use node-soap at runtime:
38
36
  npm i soap
39
37
  ```
40
38
 
41
- Generate into your app (recommended under `src/generated/...`):
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 generated client:
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, // or configure after creation: (await createSoapClient(...)).setSecurity(security)
61
+ security,
62
62
  });
63
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.
69
- const rs = await client.MyOperation({
64
+ const client = new MyServiceSoapClient(soapClient, "$attributes");
65
+ const response = await client.MyOperation({
70
66
  MyOperationRQ: {
71
- MyAttribute: "passed as attribute",
72
- MyElement: "passed as child element",
73
- $value: "passed text content here",
74
- }
67
+ MyElement: {
68
+ MyAttribute: "value",
69
+ ChildElementA: "valueA",
70
+ },
71
+ },
75
72
  });
76
73
 
77
- console.log(rs);
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
- ## Security and WS-Policy hints
79
+ ## Features
85
80
 
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.
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
- 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:
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
- **Required**
96
+ ### Required Flags
97
+ - `--wsdl`: Path or URL to the WSDL file.
98
+ - `--out`: Output directory for the generated files.
121
99
 
122
- * `--wsdl` — WSDL path or URL
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
- ### 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
-
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
- Override these defaults using the CLI flags above as needed for your use case.
114
+ ## Generated Files
154
115
 
155
- # What gets generated
116
+ The generator produces the following files in the output directory:
156
117
 
157
118
  ```
158
119
  <out>/
159
- types.ts # interfaces + type aliases (with @xsd JSDoc: original XML type/occurs)
160
- client.ts # thin operation wrapper (calls into runtime)
161
- runtime.ts # small SOAP runtime: createSoapClient, toSoapArgs, fromSoapResult
162
- meta.ts # ATTR_SPEC, CHILD_TYPE, PROP_META for JSON ⇄ SOAP mapping
163
- operations.ts # compiled operation metadata (name, soapAction, etc.)
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
- ## Using in different app module systems
178
-
179
- ### ESM / NodeNext (recommended)
129
+ ## Advanced Usage
180
130
 
181
- Service `tsconfig.json`:
131
+ ### Programmatic API
182
132
 
183
- ```json
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, xsdToTsPrimitive, type CompilerOptions } from "@techspokes/typescript-wsdl-client";
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
- * **“I don’t see `runtime.ts`”** — You should. Ensure you’re on a recent version and check that the output directory is writable.
267
- * **“Cannot find './runtime.js'”** Your app must be `module: "NodeNext"` when using `--imports js`.
268
- Use `--imports bare` for CommonJS apps.
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
- ## Contributing
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
- - See CONTRIBUTING.md for setup and workflow.
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
- ## Community
162
+ ## Contributing
286
163
 
287
- - Contributing guide: CONTRIBUTING.md
288
- - Code of Conduct: CODE_OF_CONDUCT.md
289
- - Security policy: SECURITY.md
290
- - Support: SUPPORT.md
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) – 30-min one-to-one video meeting on AI, business automations, vacation rentals industry, development, tools, or a subject of your choice.
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;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"}
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"}
@@ -248,23 +248,18 @@ export function compileCatalog(cat, _opts) {
248
248
  // resolves type refs or @ref, applies min/max occurrence and nillable flags
249
249
  const collectParticles = (ownerTypeName, node) => {
250
250
  const out = [];
251
- const groups = [
252
- ...getChildrenWithLocalName(node, "sequence"),
253
- ...getChildrenWithLocalName(node, "all"),
254
- ...getChildrenWithLocalName(node, "choice"),
255
- ];
256
- for (const grp of groups) {
257
- 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")) {
258
255
  const nameOrRef = e["@_name"] || e["@_ref"];
259
- if (!nameOrRef) {
256
+ if (!nameOrRef)
260
257
  continue;
261
- }
262
258
  const propName = e["@_name"];
263
259
  const min = e["@_minOccurs"] ? Number(e["@_minOccurs"]) : 1;
264
260
  const maxAttr = e["@_maxOccurs"];
265
261
  const max = maxAttr === "unbounded" ? "unbounded" : maxAttr ? Number(maxAttr) : 1;
266
262
  const nillable = e["@_nillable"] === "true";
267
- // inline complexType: create a nested interface with a generated name
268
263
  const inlineComplex = getFirstWithLocalName(e, "complexType");
269
264
  const inlineSimple = getFirstWithLocalName(e, "simpleType");
270
265
  if (inlineComplex) {
@@ -273,19 +268,24 @@ export function compileCatalog(cat, _opts) {
273
268
  out.push({ name: propName || nameOrRef, tsType: rec.name, min, max, nillable, declaredType: `{${schemaNS}}${rec.name}` });
274
269
  }
275
270
  else if (inlineSimple) {
276
- // inline simpleType (e.g., list or enumeration)
277
271
  const r = compileSimpleTypeNode(inlineSimple, schemaNS, prefixes);
278
272
  out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
279
273
  }
280
274
  else {
281
- // named type or ref: resolve via QName
282
275
  const t = e["@_type"] || e["@_ref"];
283
276
  const q = t ? resolveQName(t, schemaNS, prefixes) : { ns: XS, local: "string" };
284
277
  const r = resolveTypeRef(q, schemaNS, prefixes);
285
278
  out.push({ name: propName || nameOrRef, tsType: r.tsType, min, max, nillable, declaredType: r.declared });
286
279
  }
287
280
  }
288
- }
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);
289
289
  return out;
290
290
  };
291
291
  // Result accumulators
@@ -298,21 +298,26 @@ export function compileCatalog(cat, _opts) {
298
298
  const res = getFirstWithLocalName(complexContent, "restriction");
299
299
  const node = ext || res;
300
300
  if (node) {
301
+ // handle complexContent extension: record base and separate local additions
302
+ let baseName;
301
303
  const baseAttr = node["@_base"];
302
304
  if (baseAttr) {
303
305
  const baseQ = resolveQName(baseAttr, schemaNS, prefixes);
304
306
  const baseRec = findComplexRec(baseQ);
305
307
  if (baseRec) {
306
- const base = getOrCompileComplex(baseRec.node["@_name"], baseRec.node, baseRec.tns, baseRec.prefixes);
307
- // inherit base
308
- mergeAttrs(attrs, base.attrs);
309
- mergeElems(elems, base.elems);
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);
310
313
  }
311
314
  }
312
- // local additions/overrides
313
- mergeAttrs(attrs, collectAttributes(node));
314
- mergeElems(elems, collectParticles(outName, node));
315
- const result = { name: outName, ns: schemaNS, attrs, elems };
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 };
316
321
  compiledMap.set(key, result);
317
322
  inProgress.delete(key);
318
323
  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,QAkDpH"}
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(` async ${m}(args: ${inTs}): Promise<${outTs}> {`);
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, "${inMetaKey}", meta, this.attributesKey);`);
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(` c['${m}'](soapArgs, (err: any, result: any) => {`);
46
- lines.push(` if (err) reject(err); else resolve(fromSoapResult(result, "${outMetaKey}", meta, this.attributesKey));`);
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,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QA+FnE"}
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 ? ` /** @xsd ${a.jsdoc} */\n` : "";
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
- // single "$value" whose tsType is another named interface ⇒ extend that interface
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
- for (const a of t.attrs || []) {
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
- const elementsToEmit = [...(t.elems || [])];
59
- // If we detected simpleContent extension, drop the synthetic $value (we're extending instead).
60
- if (isSimpleContentExtension) {
61
- const idx = elementsToEmit.findIndex((e) => e.name === "$value");
62
- if (idx >= 0)
63
- elementsToEmit.splice(idx, 1);
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: "element",
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("");
@@ -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,CAExC;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"}
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
- return s.replace(/(^|[_\-\s])(\w)/g, (_, __, c) => c.toUpperCase()).replace(/[^A-Za-z0-9]/g, "");
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.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",