@techspokes/typescript-wsdl-client 0.1.9 → 0.2.2

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