@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 +140 -86
- package/dist/cli.js +16 -0
- package/dist/openapi/buildSchemas.d.ts.map +1 -1
- package/dist/openapi/buildSchemas.js +0 -11
- package/dist/openapi/generateOpenAPI.d.ts +4 -0
- package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
- package/dist/openapi/generateOpenAPI.js +164 -23
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/@techspokes/typescript-wsdl-client)
|
|
7
7
|
[](https://github.com/techspokes/typescript-wsdl-client/stargazers)
|
|
8
8
|
[](https://github.com/techspokes/typescript-wsdl-client/network/members)
|
|
9
|
-
[](https://github.com/techspokes/typescript-wsdl-client/watchers)
|
|
10
9
|
[](https://github.com/techspokes)
|
|
11
10
|
[](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
|
|
20
|
-
|
|
21
|
-
| Attribute + Element Flattening
|
|
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
|
|
24
|
-
| Choice Strategy
|
|
25
|
-
| WS‑Policy Security Hints
|
|
26
|
-
| Deterministic Output
|
|
27
|
-
| Primitive Mapping Controls
|
|
28
|
-
| Catalog Introspection
|
|
29
|
-
| OpenAPI 3.1 Bridge
|
|
30
|
-
| Multi‑format Output
|
|
31
|
-
| One‑Shot Pipeline
|
|
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
|
|
80
|
-
|
|
81
|
-
| `client.ts`
|
|
82
|
-
| `types.ts`
|
|
83
|
-
| `utils.ts`
|
|
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
|
|
96
|
-
|
|
97
|
-
| `--wsdl`
|
|
98
|
-
| `--out`
|
|
99
|
-
| `--imports`
|
|
100
|
-
| `--catalog`
|
|
101
|
-
| `--client-name`
|
|
102
|
-
| `--attributes-key`
|
|
103
|
-
| `--choice`
|
|
104
|
-
| `--nillable-as-optional` | `false`
|
|
105
|
-
| `--fail-on-unresolved`
|
|
106
|
-
| `--int64-as`
|
|
107
|
-
| `--bigint-as`
|
|
108
|
-
| `--decimal-as`
|
|
109
|
-
| `--date-as`
|
|
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
|
|
144
|
-
|
|
145
|
-
| `--format json|yaml|both` | Output format (default json). |
|
|
146
|
-
| `--yaml`
|
|
147
|
-
| `--validate/--no-validate` | Validation on by default.
|
|
148
|
-
| `--out`
|
|
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
|
|
155
|
-
|
|
156
|
-
| `--basePath`
|
|
157
|
-
| `--pathStyle kebab|asis|lower` | Control operation name → path transformation. |
|
|
158
|
-
| `--method`
|
|
159
|
-
| `--tag-style`
|
|
160
|
-
| `--security`
|
|
161
|
-
| `--tags`
|
|
162
|
-
| `--ops`
|
|
163
|
-
| `--closedSchemas`
|
|
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
|
|
184
|
-
|
|
185
|
-
| All SOAP flags
|
|
186
|
-
| All OpenAPI flags
|
|
187
|
-
| `--openapi-out`
|
|
188
|
-
| `--format`
|
|
189
|
-
| `--validate/--no-validate` | validate | Spec validation toggle.
|
|
190
|
-
| `--tag-style`
|
|
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
|
|
222
|
-
|
|
223
|
-
| `default` | Single tag = service name (fallback `SOAP`).
|
|
224
|
-
| `service` | Always service name (even if operation prefix differs).
|
|
225
|
-
| `first`
|
|
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
|
|
257
|
-
|
|
258
|
-
| Primitive mapping philosophy | Defaults prefer string to avoid precision loss.
|
|
259
|
-
| Choice flattening
|
|
260
|
-
| Array wrappers
|
|
261
|
-
| Validation
|
|
262
|
-
| Future
|
|
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
|
|
267
|
-
|
|
268
|
-
| WSDL fetch fails
|
|
269
|
-
| Unresolved types
|
|
270
|
-
| Missing schema in OpenAPI | Ensure the global element exists (catalog shows compiled symbols).
|
|
271
|
-
| Wrong array modeling
|
|
272
|
-
| Auth errors at runtime
|
|
273
|
-
| Date/time confusion
|
|
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
|
|
283
|
-
|
|
284
|
-
| `compileWsdlToProject`
|
|
285
|
-
| `generateOpenAPI`
|
|
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
|
|
293
|
-
-
|
|
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,
|
|
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":"
|
|
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
|
-
*
|
|
8
|
+
* Core capabilities:
|
|
9
9
|
* - Multiple input sources (WSDL URL/path, pre-compiled catalog, or in-memory catalog)
|
|
10
|
-
* -
|
|
11
|
-
* - Security scheme
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
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
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|