@techspokes/typescript-wsdl-client 0.7.0 → 0.7.1

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
@@ -6,7 +6,6 @@
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@techspokes%2Ftypescript-wsdl-client.svg)](https://www.npmjs.com/package/@techspokes/typescript-wsdl-client)
7
7
  [![GitHub Stars](https://img.shields.io/github/stars/techspokes/typescript-wsdl-client?style=social)](https://github.com/techspokes/typescript-wsdl-client/stargazers)
8
8
  [![GitHub Forks](https://img.shields.io/github/forks/techspokes/typescript-wsdl-client?style=social)](https://github.com/techspokes/typescript-wsdl-client/network/members)
9
- [![GitHub Watchers](https://img.shields.io/github/watchers/techspokes/typescript-wsdl-client?style=social)](https://github.com/techspokes/typescript-wsdl-client/watchers)
10
9
  [![TechSpokes Org](https://img.shields.io/badge/org-techspokes-181717?logo=github)](https://github.com/techspokes)
11
10
  [![Sponsor](https://img.shields.io/badge/sponsor-GitHub-blue?logo=github-sponsors)](https://github.com/sponsors/TechSpokes)
12
11
 
@@ -16,19 +15,19 @@
16
15
  ## 1. Why This Project (What Sets It Apart)
17
16
  Most generators stop at loosely typed stubs or leak XML complexity into your application layer. This tool focuses on **correct flattening and determinism**:
18
17
 
19
- | Core Differentiator | What You Get |
20
- |---------------------|--------------|
21
- | Attribute + Element Flattening | Attributes and child elements appear as peer properties (no nested wrapper noise). |
18
+ | Core Differentiator | What You Get |
19
+ |----------------------------------|-------------------------------------------------------------------------------------------------|
20
+ | Attribute + Element Flattening | Attributes and child elements appear as peer properties (no nested wrapper noise). |
22
21
  | `$value` Text Content Convention | Simple & mixed content always represented as a `$value` property (collision-safe & documented). |
23
- | Inheritance Resolution | `complexContent` and `simpleContent` extensions are merged or extended consistently. |
24
- | Choice Strategy | Predictable `all-optional` modeling today (future advanced discriminators planned). |
25
- | WS‑Policy Security Hints | Inline scan of policies surfaces required auth hints (e.g. `usernameToken`, `https`). |
26
- | Deterministic Output | Sorted declarations and stable alias resolution for diff‑friendly regeneration. |
27
- | Primitive Mapping Controls | Explicit flags for long / big integer / decimal / temporal families (string‑first safety). |
28
- | Catalog Introspection | One JSON artifact (`catalog.json`) to drive further tooling (including OpenAPI). |
29
- | OpenAPI 3.1 Bridge | Mirrors the exact TypeScript model — no divergence between runtime and spec. |
30
- | Multi‑format Output | `--format json|yaml|both` with always‑on validation (unless disabled). |
31
- | One‑Shot Pipeline | Single pass (parse → TS → OpenAPI) for CI & automation. |
22
+ | Inheritance Resolution | `complexContent` and `simpleContent` extensions are merged or extended consistently. |
23
+ | Choice Strategy | Predictable `all-optional` modeling today (future advanced discriminators planned). |
24
+ | WS‑Policy Security Hints | Inline scan of policies surfaces required auth hints (e.g. `usernameToken`, `https`). |
25
+ | Deterministic Output | Sorted declarations and stable alias resolution for diff‑friendly regeneration. |
26
+ | Primitive Mapping Controls | Explicit flags for long / big integer / decimal / temporal families (string‑first safety). |
27
+ | Catalog Introspection | One JSON artifact (`catalog.json`) to drive further tooling (including OpenAPI). |
28
+ | OpenAPI 3.1 Bridge | Mirrors the exact TypeScript model — no divergence between runtime and spec. |
29
+ | Multi‑format Output | `--format json |yaml|both` with always‑on validation (unless disabled). |
30
+ | One‑Shot Pipeline | Single pass (parse → TS → OpenAPI) for CI & automation. |
32
31
 
33
32
  **Vendor**: [TechSpokes](https://www.techspokes.com) · **Maintainer**: Serge Liatko ([@sergeliatko](https://github.com/sergeliatko))
34
33
 
@@ -76,11 +75,11 @@ import { Weather } from "../services/third-party/weather/client.js";
76
75
  ```
77
76
 
78
77
  ### 4.2 What Gets Generated
79
- | File | Purpose |
80
- |------|---------|
81
- | `client.ts` | Strongly typed wrapper with one method per operation. |
82
- | `types.ts` | Flattened interfaces & literal/enum aliases. |
83
- | `utils.ts` | Metadata (attribute vs element, occurrence, nillable). |
78
+ | File | Purpose |
79
+ |----------------|-------------------------------------------------------------------------|
80
+ | `client.ts` | Strongly typed wrapper with one method per operation. |
81
+ | `types.ts` | Flattened interfaces & literal/enum aliases. |
82
+ | `utils.ts` | Metadata (attribute vs element, occurrence, nillable). |
84
83
  | `catalog.json` | (If `--catalog`) compiled representation for debugging / OpenAPI reuse. |
85
84
 
86
85
  ### 4.3 Key Modeling Rules Recap
@@ -92,21 +91,21 @@ import { Weather } from "../services/third-party/weather/client.js";
92
91
  - **Inheritance**: extensions merged or emitted as extends; simpleContent base collapsed logically.
93
92
 
94
93
  ### 4.4 CLI Flags (SOAP Client)
95
- | Flag | Default | Description |
96
- |------|---------|-------------|
97
- | `--wsdl` | (required) | Local path or URL to WSDL. |
98
- | `--out` | (required) | Output directory. |
99
- | `--imports` | `js` | Intra‑generated import style: `js`, `ts`, `bare`. |
100
- | `--catalog` | `false` | Emit `catalog.json`. |
101
- | `--client-name` | derived | Override exported class name. |
102
- | `--attributes-key` | `$attributes` | Attribute bag key. |
103
- | `--choice` | `all-optional` | Current choice strategy. |
104
- | `--nillable-as-optional` | `false` | Treat nillable elements as optional props. |
105
- | `--fail-on-unresolved` | `true` | Fail build on unresolved references. |
106
- | `--int64-as` | `string` | Map 64‑bit integer types. |
107
- | `--bigint-as` | `string` | Map arbitrary-size integer family. |
108
- | `--decimal-as` | `string` | Map `xs:decimal`. |
109
- | `--date-as` | `string` | Map date/time/duration primitives. |
94
+ | Flag | Default | Description |
95
+ |--------------------------|----------------|---------------------------------------------------|
96
+ | `--wsdl` | (required) | Local path or URL to WSDL. |
97
+ | `--out` | (required) | Output directory. |
98
+ | `--imports` | `js` | Intra‑generated import style: `js`, `ts`, `bare`. |
99
+ | `--catalog` | `false` | Emit `catalog.json`. |
100
+ | `--client-name` | derived | Override exported class name. |
101
+ | `--attributes-key` | `$attributes` | Attribute bag key. |
102
+ | `--choice` | `all-optional` | Current choice strategy. |
103
+ | `--nillable-as-optional` | `false` | Treat nillable elements as optional props. |
104
+ | `--fail-on-unresolved` | `true` | Fail build on unresolved references. |
105
+ | `--int64-as` | `string` | Map 64‑bit integer types. |
106
+ | `--bigint-as` | `string` | Map arbitrary-size integer family. |
107
+ | `--decimal-as` | `string` | Map `xs:decimal`. |
108
+ | `--date-as` | `string` | Map date/time/duration primitives. |
110
109
 
111
110
  ### 4.5 Example With Safer Numeric Decisions
112
111
  ```bash
@@ -140,28 +139,31 @@ npx wsdl-tsc openapi --catalog ./src/integrations/soap/hotel/catalog.json --out
140
139
  ```
141
140
 
142
141
  ### 5.1 Formats & Validation
143
- | Flag | Purpose |
144
- |------|---------|
145
- | `--format json|yaml|both` | Output format (default json). |
146
- | `--yaml` | (Deprecated) alias for `--format yaml` when format absent. |
147
- | `--validate/--no-validate` | Validation on by default. |
148
- | `--out` | Base path or explicit file (extension optional). |
142
+ | Flag | Purpose |
143
+ |----------------------------|------------------------------------------------------------|
144
+ | `--format json | yaml |both` | Output format (default json). |
145
+ | `--yaml` | (Deprecated) alias for `--format yaml` when format absent. |
146
+ | `--validate/--no-validate` | Validation on by default. |
147
+ | `--out` | Base path or explicit file (extension optional). |
149
148
 
150
149
  ### 5.2 Core Schema Parity
151
150
  The OpenAPI schemas reproduce the **exact** flattening & naming used in `types.ts` — crucial for avoiding drift between SOAP and REST surfaces.
152
151
 
153
152
  ### 5.3 Additional Flags (Selected)
154
- | Flag | Description |
155
- |------|-------------|
156
- | `--basePath` | Prefix for REST path segments (e.g. `/v1/booking`). |
157
- | `--pathStyle kebab|asis|lower` | Control operation name → path transformation. |
158
- | `--method` | Default HTTP method (per‑op override via `--ops`). |
159
- | `--tag-style` | Tag inference: `default`, `service`, `first`. |
160
- | `--security` | Path to `security.json` (schemes + headers + overrides). |
161
- | `--tags` | Path to `tags.json` (explicit operation → tag map). |
162
- | `--ops` | Path to `ops.json` (method/summary/description/deprecated). |
163
- | `--closedSchemas` | Apply `additionalProperties:false` globally. |
164
- | `--pruneUnusedSchemas` | Emit only reachable schemas. |
153
+ | Flag | Description |
154
+ |------------------------|--------------------------------------------------------------------------------|
155
+ | `--basePath` | Prefix for REST path segments (e.g. `/v1/booking`). |
156
+ | `--pathStyle kebab | asis |lower` | Control operation name → path transformation. |
157
+ | `--method` | Default HTTP method (per‑op override via `--ops`). |
158
+ | `--tag-style` | Tag inference: `default`, `service`, `first`. |
159
+ | `--security` | Path to `security.json` (schemes + headers + overrides). |
160
+ | `--tags` | Path to `tags.json` (explicit operation → tag map). |
161
+ | `--ops` | Path to `ops.json` (method/summary/description/deprecated). |
162
+ | `--closedSchemas` | Apply `additionalProperties:false` globally. |
163
+ | `--pruneUnusedSchemas` | Emit only reachable schemas. |
164
+ | `--servers` | Comma‑separated server base URLs for the spec (default: `/` if none provided). |
165
+
166
+ Deterministic ordering: all path keys, HTTP methods, component schema names, securitySchemes, parameters, component section keys, and operation tag arrays are alphabetically sorted for diff‑friendly output. The generator also omits a custom `jsonSchemaDialect` declaration to maximize IDE/tool compatibility (JetBrains warning avoidance) unless a future flag introduces non‑default dialect selection.
165
167
 
166
168
  ### 5.4 Programmatic OpenAPI Generation
167
169
  ```ts
@@ -169,10 +171,62 @@ import { generateOpenAPI } from "@techspokes/typescript-wsdl-client";
169
171
  const { jsonPath, yamlPath } = await generateOpenAPI({
170
172
  wsdl: "./wsdl/Hotel.wsdl",
171
173
  outFile: "./openapi/hotel",
172
- format: "both"
174
+ format: "both",
175
+ servers: ["https://api.example.com/v1"] // optional; defaults to ["/"] when omitted
173
176
  });
174
177
  ```
175
178
 
179
+ ### 5.5 Standard Response Envelope (Always On Since 0.7.1)
180
+ To provide a consistent, debuggable, gateway‑friendly REST surface over SOAP operations, all responses are wrapped in a **standard envelope**. You can customize naming, but disabling the envelope is no longer supported.
181
+
182
+ #### Collision Avoidance
183
+ If the base name you are concatenating already ends with the leading token of the namespace (first CamelCase word), an underscore is inserted to avoid unreadable duplicates. Examples (default `ResponseEnvelope`):
184
+ - Payload type `WeatherResponse` → `WeatherResponse_ResponseEnvelope` (instead of `WeatherResponseResponseEnvelope`).
185
+ - Service `Booking` + default envelope → `BookingResponseEnvelope` (no underscore since `Booking` does not end with `Response`).
186
+ The same rule applies to the error namespace (e.g. `StatusErrorObject`, `StatusError_ErrorObject`). This keeps the intent obvious and signals to developers they might want to pick a shorter custom namespace.
187
+
188
+ Core properties (always present in the base envelope):
189
+ | Field | Type | Purpose |
190
+ |-------|------|---------|
191
+ | `status` | string | High‑level machine status (e.g. `SUCCESS`, `FAILURE`, `PENDING`). |
192
+ | `message` | string \| null | Diagnostic / log message (not for end‑user UI). |
193
+ | `data` | any \| null | Operation payload (per‑operation extension specializes this). |
194
+ | `error` | Error object \| null | Populated on failures; null on success. |
195
+
196
+ Error object schema fields:
197
+ | Field | Type | Notes |
198
+ |-------|------|-------|
199
+ | `code` | string | Stable machine error code. |
200
+ | `message` | string | Brief description. |
201
+ | `details` | object \| null | Arbitrary extra info (trace IDs, validation detail, etc.). |
202
+
203
+ #### Naming Rules
204
+ * Base envelope component: `${serviceName}ResponseEnvelope` (override with `--envelope-namespace`).
205
+ * Error object component: `${serviceName}ErrorObject` (override with `--error-namespace`).
206
+ * Per operation extension: `<PayloadType|OperationName><EnvelopeNamespace>` (alphabetically sorted for stable diffs) which refines `data` to that operation's output type.
207
+ * When you pass an explicit namespace flag its value is used verbatim (no service name prefix).
208
+
209
+ #### CLI Flags
210
+ | Flag | Description |
211
+ |-------------------------------|------------------------------------------------|
212
+ | `--envelope-namespace <Name>` | Override base envelope component name segment. |
213
+ | `--error-namespace <Name>` | Override error object component name segment. |
214
+
215
+ #### Example
216
+ ```bash
217
+ npx wsdl-tsc openapi \
218
+ --wsdl ./wsdl/Hotel.wsdl \
219
+ --out ./openapi/hotel \
220
+ --format json \
221
+ --envelope-namespace ApiEnvelope \
222
+ --error-namespace ApiError
223
+ ```
224
+ Yields components in order:
225
+ 1. `HotelApiEnvelope` (base)
226
+ 2. `<Payload…>ApiEnvelope` extension schemas (A→Z)
227
+ 3. `HotelApiError` (error object)
228
+ 4. Remaining domain schemas.
229
+
176
230
  ---
177
231
  ## 6. One‑Shot Pipeline (SOAP Client + OpenAPI Together)
178
232
  Ideal for CI: single WSDL parse → TS artifacts + catalog + OpenAPI (validated).
@@ -180,14 +234,14 @@ Ideal for CI: single WSDL parse → TS artifacts + catalog + OpenAPI (validated)
180
234
  npx wsdl-tsc pipeline --wsdl ./wsdl/Hotel.wsdl --out ./src/integrations/soap/hotel --format both
181
235
  ```
182
236
 
183
- | Pipeline Flag | Default | Notes |
184
- |---------------|---------|-------|
185
- | All SOAP flags | — | Same semantics as base generation. |
186
- | All OpenAPI flags | — | Same semantics as standalone openapi. |
187
- | `--openapi-out` | derived | Override OpenAPI base file. |
188
- | `--format` | json | Multi-format control. |
189
- | `--validate/--no-validate` | validate | Spec validation toggle. |
190
- | `--tag-style` | default | Tag inference. |
237
+ | Pipeline Flag | Default | Notes |
238
+ |----------------------------|----------|---------------------------------------|
239
+ | All SOAP flags | — | Same semantics as base generation. |
240
+ | All OpenAPI flags | — | Same semantics as standalone openapi. |
241
+ | `--openapi-out` | derived | Override OpenAPI base file. |
242
+ | `--format` | json | Multi-format control. |
243
+ | `--validate/--no-validate` | validate | Spec validation toggle. |
244
+ | `--tag-style` | default | Tag inference. |
191
245
 
192
246
  Programmatic single pass:
193
247
  ```ts
@@ -218,11 +272,11 @@ Supported `scheme`: `none|basic|bearer|apiKey|oauth2`.
218
272
 
219
273
  ---
220
274
  ## 8. Tag Inference Strategies
221
- | Strategy | Behavior |
222
- |----------|----------|
223
- | `default` | Single tag = service name (fallback `SOAP`). |
224
- | `service` | Always service name (even if operation prefix differs). |
225
- | `first` | First lexical segment of CamelCase operation (e.g. `GetCityWeatherByZIP` → `Get`). |
275
+ | Strategy | Behavior |
276
+ |-----------|------------------------------------------------------------------------------------|
277
+ | `default` | Single tag = service name (fallback `SOAP`). |
278
+ | `service` | Always service name (even if operation prefix differs). |
279
+ | `first` | First lexical segment of CamelCase operation (e.g. `GetCityWeatherByZIP` → `Get`). |
226
280
 
227
281
  Provide `tags.json` for explicit mapping when heuristics are insufficient.
228
282
 
@@ -253,24 +307,24 @@ const Price = {
253
307
 
254
308
  ---
255
309
  ## 10. Advanced Topics
256
- | Topic | Notes |
257
- |-------|-------|
258
- | Primitive mapping philosophy | Defaults prefer string to avoid precision loss. |
259
- | Choice flattening | Represented as optional union of fields – simpler consumption. |
260
- | Array wrappers | Single repeated child w/out attributes collapses to an array schema in OpenAPI. |
261
- | Validation | Uses `@apidevtools/swagger-parser`; disable with `--no-validate`. |
262
- | Future | Discriminated unions for choices, richer policy extraction, snapshot OpenAPI tests. |
310
+ | Topic | Notes |
311
+ |------------------------------|-------------------------------------------------------------------------------------|
312
+ | Primitive mapping philosophy | Defaults prefer string to avoid precision loss. |
313
+ | Choice flattening | Represented as optional union of fields – simpler consumption. |
314
+ | Array wrappers | Single repeated child w/out attributes collapses to an array schema in OpenAPI. |
315
+ | Validation | Uses `@apidevtools/swagger-parser`; disable with `--no-validate`. |
316
+ | Future | Discriminated unions for choices, richer policy extraction, snapshot OpenAPI tests. |
263
317
 
264
318
  ---
265
319
  ## 11. Troubleshooting
266
- | Symptom | Resolution |
267
- |---------|------------|
268
- | WSDL fetch fails | Curl the URL, check TLS/proxy, retry with local copy. |
269
- | Unresolved types | Re-run with `--fail-on-unresolved=false` to inspect partial graph. |
270
- | Missing schema in OpenAPI | Ensure the global element exists (catalog shows compiled symbols). |
271
- | Wrong array modeling | Check `maxOccurs` in WSDL; tool only arrays when `max>1` or `unbounded`. |
272
- | Auth errors at runtime | Provide a proper `soap.ISecurity` instance (`WSSecurity`, etc.). |
273
- | Date/time confusion | Use `--date-as Date` for runtime Date objects. |
320
+ | Symptom | Resolution |
321
+ |---------------------------|--------------------------------------------------------------------------|
322
+ | WSDL fetch fails | Curl the URL, check TLS/proxy, retry with local copy. |
323
+ | Unresolved types | Re-run with `--fail-on-unresolved=false` to inspect partial graph. |
324
+ | Missing schema in OpenAPI | Ensure the global element exists (catalog shows compiled symbols). |
325
+ | Wrong array modeling | Check `maxOccurs` in WSDL; tool only arrays when `max>1` or `unbounded`. |
326
+ | Auth errors at runtime | Provide a proper `soap.ISecurity` instance (`WSSecurity`, etc.). |
327
+ | Date/time confusion | Use `--date-as Date` for runtime Date objects. |
274
328
 
275
329
  Enable SOAP wire logging:
276
330
  ```bash
@@ -279,18 +333,18 @@ NODE_DEBUG=soap node app.js
279
333
 
280
334
  ---
281
335
  ## 12. Programmatic Reference (Summary)
282
- | Function | Purpose |
283
- |----------|---------|
284
- | `compileWsdlToProject` | WSDL → TS artifacts. |
285
- | `generateOpenAPI` | WSDL or catalog → OpenAPI (json / yaml / both). |
286
- | `runGenerationPipeline` | One pass: compile + TS emit + OpenAPI. |
336
+ | Function | Purpose |
337
+ |-------------------------|-------------------------------------------------|
338
+ | `compileWsdlToProject` | WSDL → TS artifacts. |
339
+ | `generateOpenAPI` | WSDL or catalog → OpenAPI (json / yaml / both). |
340
+ | `runGenerationPipeline` | One pass: compile + TS emit + OpenAPI. |
287
341
 
288
342
  ---
289
343
  ## 13. Contributing
290
344
  1. Fork & branch.
291
345
  2. `npm i && npm run build`.
292
- 3. Use `npm run smoke:gen`, `npm run smoke:pipeline`, `npm run smoke:openapi:validate`.
293
- - Note: These smoke tests use `examples/minimal/weather.wsdl` for quick validation.
346
+ 3. Use `npm run smoke:gen`, `npm run smoke:pipeline`.
347
+ - Pipeline smoke validates envelope, ordering & validation.
294
348
  4. Add a bullet under **Unreleased** in `CHANGELOG.md`.
295
349
 
296
350
  See also: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`.
package/dist/cli.js CHANGED
@@ -85,6 +85,15 @@ if (rawArgs[0] === "openapi") {
85
85
  choices: ["default", "first", "service"],
86
86
  default: "default",
87
87
  desc: "Heuristic for inferring tags when no --tags map provided"
88
+ })
89
+ // Standard envelope feature flags
90
+ .option("envelope-namespace", {
91
+ type: "string",
92
+ desc: "Override the standard response envelope base name segment (default <ServiceName>ResponseEnvelope or provided segment alone)"
93
+ })
94
+ .option("error-namespace", {
95
+ type: "string",
96
+ desc: "Override the standard error object schema name segment (default <ServiceName>ErrorObject or provided segment alone)"
88
97
  })
89
98
  .strict()
90
99
  .help()
@@ -129,6 +138,8 @@ if (rawArgs[0] === "openapi") {
129
138
  format: inferredFormat,
130
139
  skipValidate: openapiArgv.validate === false,
131
140
  tagStyle: openapiArgv["tag-style"],
141
+ envelopeNamespace: openapiArgv["envelope-namespace"],
142
+ errorNamespace: openapiArgv["error-namespace"],
132
143
  });
133
144
  console.log(`✅ OpenAPI generation complete (${inferredFormat || 'json'})`);
134
145
  process.exit(0);
@@ -172,6 +183,9 @@ if (rawArgs[0] === "pipeline") {
172
183
  .option("ops", { type: "string" })
173
184
  .option("closedSchemas", { type: "boolean", default: false })
174
185
  .option("pruneUnusedSchemas", { type: "boolean", default: false })
186
+ // Envelope feature flags
187
+ .option("envelope-namespace", { type: "string" })
188
+ .option("error-namespace", { type: "string" })
175
189
  .strict()
176
190
  .help()
177
191
  .parse();
@@ -225,6 +239,8 @@ if (rawArgs[0] === "pipeline") {
225
239
  opsFile: pipelineArgv.ops,
226
240
  closedSchemas: pipelineArgv.closedSchemas,
227
241
  pruneUnusedSchemas: pipelineArgv.pruneUnusedSchemas,
242
+ envelopeNamespace: pipelineArgv["envelope-namespace"],
243
+ errorNamespace: pipelineArgv["error-namespace"],
228
244
  }
229
245
  });
230
246
  console.log(`✅ Pipeline complete (format=${format || 'json'})`);
@@ -1 +1 @@
1
- {"version":3,"file":"buildSchemas.d.ts","sourceRoot":"","sources":["../../src/openapi/buildSchemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAgB,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEhG;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAmIpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,CA0DpG"}
1
+ {"version":3,"file":"buildSchemas.d.ts","sourceRoot":"","sources":["../../src/openapi/buildSchemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAgB,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEhG;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAmIpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,CA8CpG"}
@@ -192,16 +192,5 @@ export function buildSchemas(compiled, opts) {
192
192
  delete schemas[k];
193
193
  }
194
194
  }
195
- // Standard error envelope (always include)
196
- if (!schemas.ErrorEnvelope) {
197
- schemas.ErrorEnvelope = {
198
- type: "object",
199
- properties: {
200
- message: { type: "string" },
201
- faultCode: { type: "string" },
202
- detail: { type: "object", additionalProperties: true },
203
- },
204
- };
205
- }
206
195
  return schemas;
207
196
  }
@@ -25,6 +25,8 @@ import type { PathStyle } from "./casing.js";
25
25
  * @property {"default"|"first"|"service"} [tagStyle] - Heuristic for deriving tags
26
26
  * @property {"json"|"yaml"|"both"} [format] - Output format (default: json)
27
27
  * @property {boolean} [skipValidate] - Skip validation (default: false)
28
+ * @property {string} [envelopeNamespace] - Namespace segment for envelope (default ResponseEnvelope)
29
+ * @property {string} [errorNamespace] - Namespace segment for error object (default ErrorObject)
28
30
  */
29
31
  export interface GenerateOpenAPIOptions {
30
32
  wsdl?: string;
@@ -48,6 +50,8 @@ export interface GenerateOpenAPIOptions {
48
50
  compiledCatalog?: CompiledCatalog;
49
51
  format?: "json" | "yaml" | "both";
50
52
  skipValidate?: boolean;
53
+ envelopeNamespace?: string;
54
+ errorNamespace?: string;
51
55
  }
52
56
  export declare function generateOpenAPI(opts: GenerateOpenAPIOptions): Promise<{
53
57
  doc: any;
@@ -1 +1 @@
1
- {"version":3,"file":"generateOpenAPI.d.ts","sourceRoot":"","sources":["../../src/openapi/generateOpenAPI.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAiB,KAAK,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAInF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC;IAC3E,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAgJD"}
1
+ {"version":3,"file":"generateOpenAPI.d.ts","sourceRoot":"","sources":["../../src/openapi/generateOpenAPI.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAiB,KAAK,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAInF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC;IAC3E,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAyRD"}
@@ -5,13 +5,22 @@
5
5
  * It bridges the gap between SOAP web services and REST APIs by creating an OpenAPI
6
6
  * representation that mirrors the TypeScript model generated by the WSDL client generator.
7
7
  *
8
- * The generator supports:
8
+ * Core capabilities:
9
9
  * - Multiple input sources (WSDL URL/path, pre-compiled catalog, or in-memory catalog)
10
- * - Customizable paths, operations, and schemas
11
- * - Security scheme configuration
12
- * - Custom operation tagging and metadata
13
- * - Multiple output formats (JSON, YAML, or both)
14
- * - OpenAPI validation
10
+ * - Deterministic schema + path emission mirroring generated TypeScript
11
+ * - Security scheme + header parameter integration (via security.json)
12
+ * - Operation tagging heuristics + explicit tag + op override files
13
+ * - Multi-format output (json|yaml|both) with optional validation
14
+ * - Always-on Standard Response Envelope (since 0.7.1): automatically wraps all
15
+ * responses in a base envelope plus per-payload extensions, providing stable
16
+ * top-level fields (status, message, data, error). Naming is customizable via
17
+ * --envelope-namespace / --error-namespace.
18
+ *
19
+ * Naming Rules:
20
+ * - If a namespace flag is provided its value is used verbatim as the component name.
21
+ * - Otherwise `${serviceName}ResponseEnvelope` / `${serviceName}ErrorObject` are used.
22
+ * - Each operation output produces an extension schema named `<PayloadType|OpName><EnvelopeNamespace>`.
23
+ * - All schemas, paths, methods, securitySchemes, and parameters are alphabetically sorted for diff friendliness.
15
24
  */
16
25
  import * as fs from "fs";
17
26
  import * as path from "path";
@@ -69,7 +78,7 @@ export async function generateOpenAPI(opts) {
69
78
  // Build components.schemas
70
79
  const schemas = buildSchemas(compiled, {
71
80
  closedSchemas: opts.closedSchemas,
72
- pruneUnusedSchemas: opts.pruneUnusedSchemas
81
+ pruneUnusedSchemas: opts.pruneUnusedSchemas,
73
82
  });
74
83
  // Build paths
75
84
  const tagStyle = opts.tagStyle || "default";
@@ -90,34 +99,166 @@ export async function generateOpenAPI(opts) {
90
99
  opSecurity: securityBuilt.opSecurity,
91
100
  opHeaderParameters: securityBuilt.opHeaderParameters,
92
101
  });
93
- // Apply tag heuristics for operations missing explicit tag if style=first
94
- if (tagStyle === "first") {
95
- for (const p of Object.values(paths)) {
96
- for (const methodObj of Object.values(p)) {
97
- if (Array.isArray(methodObj.tags) && methodObj.tags[0] === "General") {
98
- const opId = methodObj.operationId || "Op";
99
- const seg = opId.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean)[0] || "General";
100
- methodObj.tags = [seg];
101
- }
102
+ // --- Standard Envelope (always enabled since 0.7.1) ---
103
+ const serviceName = compiled.serviceName || "Service";
104
+ const envelopeNamespace = opts.envelopeNamespace || "ResponseEnvelope";
105
+ const errorNamespace = opts.errorNamespace || "ErrorObject";
106
+ function leadingToken(ns) {
107
+ return ns.match(/^[A-Z][a-z0-9]*/)?.[0] || ns;
108
+ }
109
+ function joinWithNamespace(base, ns) {
110
+ const token = leadingToken(ns);
111
+ return base.endsWith(token) ? `${base}_${ns}` : `${base}${ns}`;
112
+ }
113
+ const baseEnvelopeName = opts.envelopeNamespace
114
+ ? envelopeNamespace
115
+ : joinWithNamespace(serviceName, envelopeNamespace);
116
+ const errorSchemaName = opts.errorNamespace
117
+ ? errorNamespace
118
+ : joinWithNamespace(serviceName, errorNamespace);
119
+ const errorSchema = {
120
+ type: "object",
121
+ properties: {
122
+ code: { type: "string" },
123
+ message: { type: "string" },
124
+ details: { anyOf: [{ type: "object", additionalProperties: true }, { type: "null" }] },
125
+ },
126
+ required: ["code", "message"],
127
+ additionalProperties: false,
128
+ description: "Standard error object for REST responses (not reused for underlying SOAP faults).",
129
+ };
130
+ const baseEnvelopeSchema = {
131
+ type: "object",
132
+ properties: {
133
+ status: { type: "string", description: "Machine-readable high-level status (e.g. SUCCESS, FAILURE, PENDING)." },
134
+ message: {
135
+ anyOf: [{ type: "string" }, { type: "null" }],
136
+ description: "Diagnostic/logging message (not for end-user display)."
137
+ },
138
+ data: { anyOf: [{}, { type: "null" }], description: "Primary payload; per-operation extension refines the shape." },
139
+ error: {
140
+ anyOf: [{ $ref: `#/components/schemas/${errorSchemaName}` }, { type: "null" }],
141
+ description: "Error details when status indicates failure; null otherwise."
142
+ },
143
+ },
144
+ required: ["status", "data", "error", "message"],
145
+ additionalProperties: true,
146
+ description: "Standard API response envelope base schema.",
147
+ };
148
+ const extensionSchemas = {};
149
+ for (const op of compiled.operations) {
150
+ const payloadType = op.outputElement?.local;
151
+ const payloadRef = payloadType && schemas[payloadType] ? { $ref: `#/components/schemas/${payloadType}` } : { type: "object" };
152
+ const baseForExt = payloadType || op.name;
153
+ const extName = joinWithNamespace(baseForExt, envelopeNamespace);
154
+ if (extensionSchemas[extName])
155
+ continue; // de-dupe
156
+ extensionSchemas[extName] = {
157
+ allOf: [
158
+ { $ref: `#/components/schemas/${baseEnvelopeName}` },
159
+ { type: "object", properties: { data: { anyOf: [payloadRef, { type: "null" }] } }, additionalProperties: true }
160
+ ],
161
+ description: `Envelope for ${payloadType || op.name} operation output wrapping its payload in 'data'.`
162
+ };
163
+ }
164
+ // Merge all schemas: add base + extensions + error then original schemas, then final alphabetical sort of all keys
165
+ const mergedSchemas = {
166
+ [baseEnvelopeName]: baseEnvelopeSchema,
167
+ ...extensionSchemas,
168
+ [errorSchemaName]: errorSchema,
169
+ ...schemas,
170
+ };
171
+ const sortedSchemaKeys = Object.keys(mergedSchemas).sort((a, b) => a.localeCompare(b));
172
+ const finalSchemas = {};
173
+ for (const k of sortedSchemaKeys)
174
+ finalSchemas[k] = mergedSchemas[k];
175
+ // Update paths response schemas to reference extension envelopes
176
+ for (const pathItem of Object.values(paths)) {
177
+ for (const methodObj of Object.values(pathItem)) {
178
+ const opId = methodObj.operationId;
179
+ if (!opId)
180
+ continue;
181
+ const op = compiled.operations.find(o => o.name === opId);
182
+ if (!op)
183
+ continue;
184
+ const payloadType = op.outputElement?.local;
185
+ const baseForExt = payloadType || op.name;
186
+ const extName = joinWithNamespace(baseForExt, envelopeNamespace);
187
+ if (methodObj.responses?.["200"]) {
188
+ methodObj.responses["200"].description = "Successful operation (standard envelope)";
189
+ methodObj.responses["200"].content = { "application/json": { schema: { $ref: `#/components/schemas/${extName}` } } };
190
+ }
191
+ if (methodObj.responses?.default) {
192
+ methodObj.responses.default.description = "Error response (standard envelope with populated error object)";
193
+ methodObj.responses.default.content = { "application/json": { schema: { $ref: `#/components/schemas/${baseEnvelopeName}` } } };
194
+ }
195
+ }
196
+ }
197
+ // --- End Standard Envelope ---
198
+ // Sort paths and methods alphabetically for diff-friendly output
199
+ const sortedPathKeys = Object.keys(paths).sort((a, b) => a.localeCompare(b));
200
+ const sortedPaths = {};
201
+ for (const p of sortedPathKeys) {
202
+ const ops = paths[p];
203
+ const methodKeys = Object.keys(ops).sort((a, b) => a.localeCompare(b));
204
+ const sortedOps = {};
205
+ for (const m of methodKeys)
206
+ sortedOps[m] = ops[m];
207
+ sortedPaths[p] = sortedOps;
208
+ }
209
+ // Sort securitySchemes & parameters if present
210
+ let sortedSecuritySchemes;
211
+ if (securityBuilt.securitySchemes) {
212
+ sortedSecuritySchemes = {};
213
+ for (const k of Object.keys(securityBuilt.securitySchemes).sort((a, b) => a.localeCompare(b))) {
214
+ sortedSecuritySchemes[k] = securityBuilt.securitySchemes[k];
215
+ }
216
+ }
217
+ let sortedParameters;
218
+ if (Object.keys(securityBuilt.headerParameters).length) {
219
+ sortedParameters = {};
220
+ for (const k of Object.keys(securityBuilt.headerParameters).sort((a, b) => a.localeCompare(b))) {
221
+ sortedParameters[k] = securityBuilt.headerParameters[k];
222
+ }
223
+ }
224
+ // Ensure operation tags arrays are deterministic (alphabetical unique) for diff-friendly output
225
+ for (const p of Object.keys(sortedPaths)) {
226
+ const pathItem = sortedPaths[p];
227
+ for (const m of Object.keys(pathItem)) {
228
+ const op = pathItem[m];
229
+ if (Array.isArray(op.tags)) {
230
+ const tags = Array.from(new Set(op.tags)).map(t => String(t));
231
+ tags.sort((a, b) => a.localeCompare(b));
232
+ op.tags = tags;
102
233
  }
103
234
  }
104
235
  }
236
+ // Build components object then sort its top-level keys for stable ordering
237
+ const componentsRaw = {
238
+ schemas: finalSchemas,
239
+ ...(sortedSecuritySchemes ? { securitySchemes: sortedSecuritySchemes } : {}),
240
+ ...(sortedParameters ? { parameters: sortedParameters } : {}),
241
+ };
242
+ const componentKeyOrder = Object.keys(componentsRaw).sort((a, b) => a.localeCompare(b));
243
+ const components = {};
244
+ for (const k of componentKeyOrder)
245
+ components[k] = componentsRaw[k];
105
246
  const doc = {
106
247
  openapi: "3.1.0",
107
- jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema",
248
+ // NOTE: jsonSchemaDialect intentionally omitted unless a future flag requires non-default dialect.
108
249
  info: { title, version: infoVersion },
109
- paths,
110
- components: {
111
- schemas,
112
- ...(securityBuilt.securitySchemes ? { securitySchemes: securityBuilt.securitySchemes } : {}),
113
- ...(Object.keys(securityBuilt.headerParameters).length ? { parameters: securityBuilt.headerParameters } : {}),
114
- },
250
+ paths: sortedPaths,
251
+ components,
115
252
  };
116
253
  if (opts.description)
117
254
  doc.info.description = opts.description;
118
255
  if (opts.servers && opts.servers.length) {
119
256
  doc.servers = opts.servers.map(u => ({ url: u }));
120
257
  }
258
+ else {
259
+ // Provide a deterministic default relative server for spec completeness & gateway friendliness.
260
+ doc.servers = [{ url: "/" }];
261
+ }
121
262
  if (opts.skipValidate !== true) {
122
263
  try {
123
264
  const parser = await import("@apidevtools/swagger-parser");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techspokes/typescript-wsdl-client",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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",
@@ -55,7 +55,7 @@
55
55
  "test": "exit 0",
56
56
  "smoke": "tsx src/cli.ts --help",
57
57
  "smoke:gen": "rimraf tmp && tsx src/cli.ts --wsdl examples/minimal/weather.wsdl --out tmp && tsc -p tsconfig.smoke.json",
58
- "smoke:pipeline": "tsx src/cli.ts pipeline --wsdl examples/minimal/weather.wsdl --out tmp --clean --format both --tag-style service --openapi-out tmp/openapi",
58
+ "smoke:pipeline": "tsx src/cli.ts pipeline --wsdl examples/minimal/weather.wsdl --out tmp --clean --format both --tag-style service --openapi-out tmp/openapi --servers https://example.com/api",
59
59
  "ci": "npm run build && npm run typecheck && npm run smoke && npm run smoke:gen && npm run smoke:pipeline"
60
60
  },
61
61
  "devDependencies": {