Transform complex WSDL/XSD definitions into type-safe TypeScript SOAP clients with optional OpenAPI 3.1 specs and production-ready REST gateways.
- ✅ TypeScript-first SOAP clients — Strongly typed, ergonomic client generation from WSDL
- ✅ OpenAPI 3.1 specs — Generate REST API documentation that mirrors your TypeScript types
- ✅ REST gateway over SOAP — Production-ready Fastify handlers with automatic request/response transformation
- ✅ CI-friendly determinism — Stable, diff-friendly output for safe regeneration in version control
- ✅ Predictable modeling — Flattened attributes, consistent
$valueconvention, inheritance resolution
Vendor: TechSpokes · Maintainer: Serge Liatko (@sergeliatko)
- Installation
- Quick Start (60 Seconds)
- What You Get (Outputs)
- Core Concepts
- Common Workflows
- Command Reference
- Configuration Files
- Working With Generated Clients
- Production Concerns
- Programmatic API
- Troubleshooting
- Contributing
- License
- Support
npm install --save-dev @techspokes/typescript-wsdl-client
npm install soap # Runtime dependency for SOAP callsRequirements:
- Node.js 20.0.0 or later
soappackage (runtime dependency for generated clients)
Generate a complete SOAP-to-REST stack in one command:
# Generate client, OpenAPI spec, gateway, and runnable app
npx wsdl-tsc pipeline \
--wsdl-source examples/minimal/weather.wsdl \
--client-dir ./tmp/client \
--openapi-file ./tmp/openapi.json \
--gateway-dir ./tmp/gateway \
--gateway-service-name weather \
--gateway-version-prefix v1 \
--generate-appStart the server:
cd tmp/app
cp .env.example .env
# Edit .env to set WSDL_SOURCE if needed
npx tsx server.jsTest it:
# Health check
curl http://localhost:3000/health
# Get OpenAPI spec
curl http://localhost:3000/openapi.json | jq .
# Call a SOAP operation via REST
curl -X POST http://localhost:3000/get-weather-information \
-H "Content-Type: application/json" \
-d '{}'What just happened?
- Parsed the WSDL and compiled types
- Generated a TypeScript SOAP client with full type safety
- Created an OpenAPI 3.1 spec matching the client types
- Built Fastify gateway handlers that call SOAP and return JSON
- Created a runnable Express-style app with health/OpenAPI endpoints
client/
├── client.ts # Strongly-typed SOAP client wrapper with methods
├── types.ts # Flattened interfaces, type aliases, and enums
├── utils.ts # Runtime metadata for JSON→SOAP conversion
└── catalog.json # Compiled schema representation (reusable)
Example usage:
import { Weather } from './client/client.js';
const client = new Weather({
source: 'https://example.com/weather.wsdl',
});
const result = await client.GetCityWeatherByZIP({ ZIP: '10001' });
console.log(result.GetCityWeatherByZIPResult);openapi.json # or .yaml — Complete REST API documentation
- Mirrors exact TypeScript type structure
- All responses wrapped in standard envelope (status, message, data, error)
- Deterministic ordering for version control
- Validates with
swagger-parserby default
gateway/
├── schemas/
│ ├── models/ # JSON Schema components with URN IDs
│ └── operations/ # Request/response validation schemas
├── routes/ # Route handlers (fully implemented)
├── schemas.ts # Schema registration module
├── routes.ts # Route aggregator
├── runtime.ts # Envelope builders, error handlers
└── plugin.ts # Fastify plugin wrapper
Example integration:
import Fastify from 'fastify';
import weatherGateway from './gateway/plugin.js';
import { Weather } from './client/client.js';
const app = Fastify({ logger: true });
const client = new Weather({ source: 'weather.wsdl' });
await app.register(weatherGateway, { client });
await app.listen({ port: 3000 });app/
├── server.js # Main entry point
├── config.js # Configuration with env var support
├── .env.example # Environment template
├── README.md # Usage instructions
└── openapi.json # OpenAPI spec (when --openapi-mode=copy)
All gateway responses follow this structure:
Success:
{
"status": "SUCCESS",
"message": null,
"data": { /* SOAP response */ },
"error": null
}Error:
{
"status": "ERROR",
"message": "Request validation failed",
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": { /* validation errors */ }
}
}Attributes and elements become peer properties — No nested wrapper noise:
<!-- WSDL -->
<xs:complexType name="Price">
<xs:simpleContent>
<xs:extension base="xs:decimal">
<xs:attribute name="currency" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>// Generated TypeScript
interface Price {
currency?: string; // attribute
$value: string; // text content (decimal mapped to string by default)
}Prevents precision loss and parsing errors at the cost of convenience:
| XSD Type | Default | Override Options | When to Override |
|---|---|---|---|
xs:long |
string |
number, bigint |
Use number if values fit JS range |
xs:integer |
string |
number |
Use string for arbitrary-size ints |
xs:decimal |
string |
number |
Use string for precise decimals |
xs:dateTime |
string |
Date |
Use Date if runtime parsing is okay |
Override with flags:
--client-int64-as number--client-decimal-as string--client-date-as Date
All output is stable and diff-friendly for CI/CD:
- ✅ Sorted type declarations
- ✅ Sorted OpenAPI paths, schemas, parameters
- ✅ Sorted JSON schema keys
- ✅ Stable alias resolution
- ✅ Consistent ordering of imports
Regenerate safely without spurious diffs in version control.
catalog.json is the compiled representation of your WSDL:
- Debuggable — Inspect types, operations, and metadata as JSON
- Cacheable — Reuse across client/OpenAPI/gateway generation
- Co-located — Automatically placed alongside generated output
Common locations:
clientcommand:{client-dir}/catalog.jsonopenapicommand:{openapi-dir}/catalog.jsonpipelinecommand: First available output directory
| I Want... | Use Command | Example |
|---|---|---|
| Everything (client + OpenAPI + gateway) | pipeline |
npx wsdl-tsc pipeline --wsdl-source service.wsdl --client-dir ./client --openapi-file ./api.json --gateway-dir ./gateway --gateway-service-name svc --gateway-version-prefix v1 |
| Only a TypeScript SOAP client | client |
npx wsdl-tsc client --wsdl-source service.wsdl --client-dir ./client |
| Only OpenAPI spec (for docs or SDKs) | openapi |
npx wsdl-tsc openapi --wsdl-source service.wsdl --openapi-file ./api.json |
| Only REST gateway (have OpenAPI already) | gateway |
npx wsdl-tsc gateway --openapi-file ./api.json --client-dir ./client --gateway-dir ./gateway --gateway-service-name svc --gateway-version-prefix v1 |
| Runnable server for testing | app |
npx wsdl-tsc app --client-dir ./client --gateway-dir ./gateway --openapi-file ./api.json |
| Debug/inspect WSDL compilation | compile |
npx wsdl-tsc compile --wsdl-source service.wsdl --catalog-file ./catalog.json |
Use pipeline for complete stack generation:
npx wsdl-tsc pipeline \
--wsdl-source examples/minimal/weather.wsdl \
--client-dir ./src/services/weather \
--openapi-file ./docs/weather-api.json \
--gateway-dir ./src/gateway/weather \
--gateway-service-name weather \
--gateway-version-prefix v1Output:
- Client:
./src/services/weather/client.ts,types.ts,utils.ts,catalog.json - OpenAPI:
./docs/weather-api.json - Gateway:
./src/gateway/weather/(routes, schemas, plugin)
Generate TypeScript client for direct SOAP integration:
npx wsdl-tsc client \
--wsdl-source ./wsdl/Hotel.wsdl \
--client-dir ./src/services/hotelUsage:
import soap from 'soap';
import { Hotel } from './src/services/hotel/client.js';
const client = new Hotel({
source: 'https://example.com/hotel.wsdl',
security: new soap.WSSecurity('username', 'password'),
});
const result = await client.SearchAvailableRooms({
checkIn: '2024-01-15',
checkOut: '2024-01-20',
guests: 2,
});Generate OpenAPI spec for API documentation or SDK generation:
npx wsdl-tsc openapi \
--wsdl-source ./wsdl/Booking.wsdl \
--openapi-file ./docs/booking-api.yaml \
--openapi-format yaml \
--openapi-title "Hotel Booking API" \
--openapi-version "1.0.0" \
--openapi-servers https://api.example.com/v1Use the spec:
- Import into Postman, Insomnia, or Swagger UI
- Generate client SDKs with OpenAPI Generator
- Share as REST API documentation
Build a REST API layer over legacy SOAP services:
# 1. Generate client + OpenAPI
npx wsdl-tsc pipeline \
--wsdl-source ./wsdl/Legacy.wsdl \
--client-dir ./src/services/legacy \
--openapi-file ./docs/legacy-api.json
# 2. Generate gateway
npx wsdl-tsc gateway \
--openapi-file ./docs/legacy-api.json \
--client-dir ./src/services/legacy \
--gateway-dir ./src/gateway/legacy \
--gateway-service-name legacy \
--gateway-version-prefix v1Or in one command:
npx wsdl-tsc pipeline \
--wsdl-source ./wsdl/Legacy.wsdl \
--client-dir ./src/services/legacy \
--openapi-file ./docs/legacy-api.json \
--gateway-dir ./src/gateway/legacy \
--gateway-service-name legacy \
--gateway-version-prefix v1Cache compiled catalog for faster multi-stage builds:
# Stage 1: Compile catalog (cacheable)
npx wsdl-tsc compile \
--wsdl-source ./wsdl/Service.wsdl \
--catalog-file ./build/service-catalog.json
# Stage 2: Generate client from catalog
npx wsdl-tsc client \
--catalog-file ./build/service-catalog.json \
--client-dir ./src/services/service
# Stage 3: Generate OpenAPI from catalog
npx wsdl-tsc openapi \
--catalog-file ./build/service-catalog.json \
--openapi-file ./docs/service-api.jsonInspect compiled types and operations:
# Compile to catalog
npx wsdl-tsc compile \
--wsdl-source ./wsdl/Complex.wsdl \
--catalog-file ./debug/catalog.json
# Inspect types
cat ./debug/catalog.json | jq '.types'
# Inspect operations
cat ./debug/catalog.json | jq '.operations'The tool provides six commands for different integration scenarios. Commands are listed in recommended order of use:
| Command | Purpose | Typical Use Case |
|---|---|---|
pipeline |
Run full pipeline: client + OpenAPI + gateway (+ app optional) | CI/CD automation, complete stack generation (recommended) |
client |
Generate TypeScript SOAP client from WSDL or catalog | Standard SOAP integration |
openapi |
Generate OpenAPI 3.1 spec from WSDL or catalog | Documentation, REST proxies, API gateways |
gateway |
Generate Fastify gateway with full handlers from OpenAPI spec | Production REST gateway with SOAP integration |
app |
Generate runnable Fastify app from client + gateway + OpenAPI | Local testing, quick iteration, demos |
compile |
Parse WSDL and emit catalog.json only |
Debugging, inspection, or multi-stage builds (advanced) |
Purpose: Run the complete generation pipeline in a single pass: WSDL parsing → TypeScript client → OpenAPI spec → Fastify gateway (+ app optional).
When to use:
- CI/CD automation
- Complete stack generation
- Ensuring all artifacts are generated from the same WSDL parse
- One-command development workflows
npx wsdl-tsc pipeline \
--wsdl-source <file|url> \
[--catalog-file <path>] \
[--client-dir <path>] \
[--openapi-file <path>] \
[--gateway-dir <path>] \
[options]| Flag | Description |
|---|---|
--wsdl-source |
Path or URL to WSDL file |
| Flag | Default | Description |
|---|---|---|
--catalog-file |
Co-located with first output (see below) | Output path for catalog.json (always generated) |
Catalog Default Location: The catalog is automatically placed alongside the first available output:
- With
--client-dir:{client-dir}/catalog.json - With
--openapi-fileonly:{openapi-file-dir}/catalog.json - With
--gateway-dironly:{gateway-dir}/catalog.json
Note: At least one of --client-dir, --openapi-file, or --gateway-dir must be provided.
| Flag | Description |
|---|---|
--client-dir |
Generate TypeScript client in this directory |
--openapi-file |
Generate OpenAPI spec at this path |
--gateway-dir |
Generate Fastify gateway in this directory |
--generate-app |
Generate runnable app (requires gateway) |
All flags from client, openapi, and gateway commands are supported. Key flags:
Client Flags:
--import-extensions(default:js)--client-attributes-key(default:$attributes)--client-class-name--client-int64-as(default:string)--client-bigint-as(default:string)--client-decimal-as(default:string)--client-date-as(default:string)--client-choice-mode(default:all-optional)--client-fail-on-unresolved(default:false)--client-nillable-as-optional(default:false)
OpenAPI Flags:
--openapi-format(default:json)--openapi-title--openapi-version(default:0.0.0)--openapi-description--openapi-servers(default:/)--openapi-base-path--openapi-path-style(default:kebab)--openapi-method(default:post)--openapi-tag-style(default:default)--openapi-closed-schemas(default:false)--openapi-prune-unused-schemas(default:false)--openapi-envelope-namespace(default:ResponseEnvelope)--openapi-error-namespace(default:ErrorObject)--openapi-validate(default:true)--openapi-security-config-file--openapi-tags-file--openapi-ops-file
Gateway Flags:
--gateway-service-name(required if--gateway-dirprovided)--gateway-version-prefix(required if--gateway-dirprovided)--gateway-default-status-codes--gateway-stub-handlers(default:false)
Complete Stack Generation:
npx wsdl-tsc pipeline \
--wsdl-source examples/minimal/weather.wsdl \
--client-dir tmp/client \
--openapi-file tmp/openapi.json \
--gateway-dir tmp/gateway \
--gateway-service-name weather \
--gateway-version-prefix v1With App Generation:
npx wsdl-tsc pipeline \
--wsdl-source examples/minimal/weather.wsdl \
--client-dir tmp/client \
--openapi-file tmp/openapi.json \
--gateway-dir tmp/gateway \
--gateway-service-name weather \
--gateway-version-prefix v1 \
--generate-appClient + OpenAPI Only:
npx wsdl-tsc pipeline \
--wsdl-source https://example.com/Hotel.wsdl \
--client-dir ./build/client \
--openapi-file ./docs/hotel-api.json \
--openapi-format bothWith Full Configuration:
npx wsdl-tsc pipeline \
--wsdl-source ./wsdl/Booking.wsdl \
--client-dir ./build/client \
--openapi-file ./docs/booking-api \
--gateway-dir ./build/gateway \
--openapi-format both \
--openapi-servers https://api.example.com/v1 \
--openapi-base-path /booking \
--openapi-security-config-file ./config/security.json \
--gateway-service-name booking \
--gateway-version-prefix v1 \
--client-int64-as number \
--client-decimal-as stringThe pipeline command executes these steps in order:
- Parse WSDL → Load and validate WSDL document
- Compile Catalog → Generate intermediate representation
- Emit Catalog → Write
catalog.json(always) - Generate Client → Emit TypeScript client files (if
--client-dir) - Generate OpenAPI → Create OpenAPI spec (if
--openapi-file) - Generate Gateway → Create Fastify gateway code (if
--gateway-dir) - Generate App → Create runnable application (if
--generate-app)
All steps share the same parsed WSDL and compiled catalog, ensuring consistency.
Purpose: Generate strongly-typed TypeScript SOAP client code from WSDL or a pre-compiled catalog.
When to use:
- Standard SOAP integration (most common use case)
- When you need TypeScript types and client methods for SOAP operations
- When building applications that consume SOAP services
npx wsdl-tsc client --wsdl-source <file|url> --client-dir <path> [options]
# OR
npx wsdl-tsc client --catalog-file <path> --client-dir <path> [options]| Flag | Description |
|---|---|
--wsdl-source |
Path or URL to WSDL file (see note below) |
--client-dir |
Output directory for generated TypeScript files |
| Flag | Default | Description |
|---|---|---|
--catalog-file |
{client-dir}/catalog.json |
Path to pre-compiled catalog.json (when not using --wsdl-source) |
Note: Provide either --wsdl-source (to compile from WSDL) or --catalog-file (to use pre-compiled catalog). When using --wsdl-source, the catalog is automatically generated in the client directory unless you override with --catalog-file.
| File | Purpose |
|---|---|
client.ts |
Strongly-typed SOAP client wrapper with one method per operation |
types.ts |
Flattened TypeScript interfaces, type aliases, and enums |
utils.ts |
Runtime metadata for JSON to SOAP conversion (attribute mapping, occurrence) |
catalog.json |
(When using --wsdl-source) Generated in client directory by default |
All flags from compile command, plus:
| Flag | Default | Description |
|---|---|---|
--import-extensions |
js |
Import style: js, ts, or bare |
--client-attributes-key |
$attributes |
Attribute bag key |
--client-class-name |
(derived) | Override client class name |
--client-int64-as |
string |
Map 64-bit integers |
--client-bigint-as |
string |
Map arbitrary-size integers |
--client-decimal-as |
string |
Map xs:decimal |
--client-date-as |
string |
Map date/time types |
--client-choice-mode |
all-optional |
Choice element strategy |
--client-fail-on-unresolved |
false |
Fail on unresolved references |
--client-nillable-as-optional |
false |
Treat nillable as optional |
npx wsdl-tsc client \
--wsdl-source examples/minimal/weather.wsdl \
--client-dir tmp/clientOutput: Generates client files and catalog at tmp/client/catalog.json.
npx wsdl-tsc client \
--wsdl-source examples/minimal/weather.wsdl \
--client-dir tmp/client \
--catalog-file build/shared-catalog.jsonnpx wsdl-tsc client \
--wsdl-source https://example.com/Hotel.wsdl \
--client-dir ./src/integrations/soap/hotel \
--client-int64-as number \
--client-decimal-as string \
--client-date-as stringOutput: Catalog generated at ./src/integrations/soap/hotel/catalog.json.
# First compile the catalog
npx wsdl-tsc compile --wsdl-source https://example.com/Hotel.wsdl --catalog-file build/hotel-catalog.json
# Then generate client from catalog
npx wsdl-tsc client \
--catalog-file build/hotel-catalog.json \
--client-dir ./src/services/hotel- Attributes & elements become peer properties (flattened)
- Text content becomes
$valueproperty - Required attributes:
use!="optional"; elementsminOccurs>=1 - Multiplicity:
maxOccurs>1orunboundedbecome arrays - Nillable:
nillable="true"preserved (optionally model as optional with--client-nillable-as-optional) - Inheritance: extensions merged or emitted as
extends; simpleContent base collapsed logically
Purpose: Generate OpenAPI 3.1 specification from WSDL or a pre-compiled catalog, mirroring the exact TypeScript type structure.
When to use:
- Creating REST API documentation for SOAP services
- Building API gateways or proxies
- Enabling REST-style access to SOAP operations
- Generating client SDKs in other languages
npx wsdl-tsc openapi --wsdl-source <file|url> --openapi-file <path> [options]
# OR
npx wsdl-tsc openapi --catalog-file <path> --openapi-file <path> [options]| Flag | Description |
|---|---|
--openapi-file |
Output path for OpenAPI specification |
| Flag | Default | Description |
|---|---|---|
--wsdl-source |
(none) | Path or URL to WSDL file |
--catalog-file |
{openapi-file-dir}/catalog.json |
Path to pre-compiled catalog.json |
Note: Provide either --wsdl-source or --catalog-file. When neither is provided, defaults to reading from the OpenAPI output directory. When using --wsdl-source, the catalog is automatically written to the OpenAPI output directory unless overridden.
| Flag | Default | Description |
|---|---|---|
--openapi-format |
json |
Output format: json, yaml, or both |
--openapi-title |
(derived) | API title in info section |
--openapi-version |
0.0.0 |
API version in info.version |
--openapi-description |
(empty) | API description in info section |
--openapi-servers |
/ |
Comma-separated server URLs |
--openapi-base-path |
(empty) | Base path prefix (e.g., /v1/soap) |
--openapi-validate |
true |
Validate spec with swagger-parser |
| Flag | Default | Description |
|---|---|---|
--openapi-path-style |
kebab |
Path transformation: kebab, asis, or lower |
--openapi-method |
post |
Default HTTP method for operations |
--openapi-tag-style |
default |
Tag inference: default, service, or first |
--openapi-closed-schemas |
false |
Add additionalProperties: false to all schemas |
--openapi-prune-unused-schemas |
false |
Emit only schemas referenced by operations |
| Flag | Default | Description |
|---|---|---|
--openapi-envelope-namespace |
ResponseEnvelope |
Override envelope component name suffix |
--openapi-error-namespace |
ErrorObject |
Override error object component name suffix |
| Flag | Description |
|---|---|
--openapi-security-config-file |
Path to security.json (schemes, headers, overrides) |
--openapi-tags-file |
Path to tags.json (explicit operation → tag map) |
--openapi-ops-file |
Path to ops.json (per-operation overrides) |
npx wsdl-tsc openapi \
--wsdl-source examples/minimal/weather.wsdl \
--openapi-file ./docs/weather-api.jsonnpx wsdl-tsc openapi \
--wsdl-source https://example.com/Hotel.wsdl \
--openapi-file ./docs/hotel-api \
--openapi-format both \
--openapi-servers https://api.example.com/v1,https://api-staging.example.com/v1 \
--openapi-base-path /soapnpx wsdl-tsc openapi \
--catalog-file ./artifacts/hotel-catalog.json \
--openapi-file ./docs/hotel-api.json \
--openapi-format jsonnpx wsdl-tsc openapi \
--wsdl-source ./wsdl/Booking.wsdl \
--openapi-file ./docs/booking-api.yaml \
--openapi-format yaml \
--openapi-title "Hotel Booking API" \
--openapi-version "1.2.0" \
--openapi-description "REST API for hotel booking SOAP service" \
--openapi-security-config-file ./config/security.json \
--openapi-tags-file ./config/tags.json \
--openapi-path-style kebab \
--openapi-method post \
--openapi-tag-style serviceAll responses are wrapped in a standard envelope for consistency and debuggability (always-on since 0.7.1):
{
status: string; // e.g., "SUCCESS", "FAILURE", "PENDING"
message: string | null; // diagnostic message (not for end-user UI)
data: T | null; // operation payload (typed per operation)
error: ErrorObject | null; // populated on failures
}{
code: string; // stable machine error code
message: string; // brief description
details: object | null; // arbitrary extra info
}- Base envelope:
${serviceName}ResponseEnvelope(override with--openapi-envelope-namespace) - Error object:
${serviceName}ErrorObject(override with--openapi-error-namespace) - Per-operation extension:
<PayloadType|OperationName><EnvelopeNamespace>(refinesdatafield)
If the payload type already ends with the namespace prefix, an underscore is inserted:
- Payload
WeatherResponse+ defaultResponseEnvelope→WeatherResponse_ResponseEnvelope - Payload
Booking+ defaultResponseEnvelope→BookingResponseEnvelope
npx wsdl-tsc openapi \
--wsdl-source ./wsdl/Hotel.wsdl \
--openapi-file ./docs/hotel-api.json \
--openapi-envelope-namespace ApiEnvelope \
--openapi-error-namespace ApiErrorProduces components:
HotelApiEnvelope(base)<Payload>ApiEnvelopeextension schemas (alphabetically sorted)HotelApiError(error object)- Domain schemas
| Strategy | Behavior |
|---|---|
default |
Single tag = service name (fallback SOAP) |
service |
Always service name (even if operation prefix differs) |
first |
First lexical segment of CamelCase operation (e.g., GetCityWeatherByZIP → Get) |
Use --openapi-tags-file for explicit mapping when heuristics are insufficient.
All generated OpenAPI specs have deterministic ordering:
- Path keys (alphabetically sorted)
- HTTP methods within paths (alphabetically sorted)
- Component schema names (alphabetically sorted)
- Security schemes (alphabetically sorted)
- Parameters (alphabetically sorted)
- Operation tag arrays (alphabetically sorted)
This ensures diff-friendly output for version control.
Purpose: Generate a production-ready Fastify gateway with fully functional route handlers from an OpenAPI 3.1 specification. This creates a complete REST API layer over your SOAP client with automatic request/response transformation and standardized envelope responses.
When to use:
- Building a REST API gateway for legacy SOAP services
- Creating a modern HTTP/JSON interface for SOAP operations
- Setting up request/response validation with JSON Schema
- Establishing Fastify routing structure with full handler implementations
What it generates:
- Fastify route registration files with complete handler implementations
- JSON Schema models with URN-based IDs
- Operation schemas (request/response validation)
- Schema and route registration modules
- Runtime utilities (envelope builders, error handlers)
- Fastify plugin wrapper for simplified integration
npx wsdl-tsc gateway \
--openapi-file <path> \
--client-dir <path> \
--gateway-dir <path> \
--gateway-service-name <slug> \
--gateway-version-prefix <slug> \
[options]| Flag | Description |
|---|---|
--openapi-file |
Path to OpenAPI 3.1 JSON or YAML file |
--client-dir |
Path to client directory (where client.ts is located) |
--gateway-dir |
Output directory for generated gateway code |
--gateway-service-name |
Service identifier for URN generation (e.g., weather, booking) |
--gateway-version-prefix |
Version prefix for URN generation (e.g., v1, v2, urn:1.0.2:schema) |
| Flag | Default | Description |
|---|---|---|
--import-extensions |
js |
Import style: js, ts, or bare |
--gateway-default-status-codes |
200,400,401,403,404,409,422,429,500,502,503,504 |
Comma-separated status codes to backfill |
--catalog-file |
(none) | Path to catalog.json for operation metadata |
--gateway-client-class-name |
(auto-detected) | Override SOAP client class name |
--gateway-decorator-name |
{serviceSlug}Client |
Fastify decorator name for client instance |
--gateway-stub-handlers |
false |
Generate stub handlers instead of full implementations |
--gateway-skip-plugin |
false |
Skip generating plugin.ts wrapper |
--gateway-skip-runtime |
false |
Skip generating runtime.ts utilities |
Note: Route URLs are derived from the OpenAPI document paths, which already include any base path configured during OpenAPI generation (via
--openapi-base-path). There is no separate route prefix option for the gateway.
{gateway-dir}/
├── schemas/
│ ├── models/ # JSON Schema components with URN IDs
│ │ ├── <schema1>.json
│ │ ├── <schema2>.json
│ │ └── ...
│ └── operations/ # Fastify operation schemas
│ ├── <operation1>.json
│ ├── <operation2>.json
│ └── ...
├── routes/ # Route registration files with full handlers
│ ├── <route1>.ts
│ ├── <route2>.ts
│ └── ...
├── schemas.ts # Schema registration module
├── routes.ts # Route aggregator module
├── runtime.ts # Envelope builders and error handler
└── plugin.ts # Fastify plugin wrapper (recommended entry point)
All generated JSON Schemas use deterministic URN identifiers:
urn:services:{serviceSlug}:{versionSlug}:schemas:{models|operations}:{schemaSlug}
Example: urn:services:weather:v1:schemas:models:getcityweatherbyzipresponse
The gateway generator enforces strict OpenAPI contract validation:
- All request/response bodies must use
$reftocomponents.schemas(no inline schemas) - Every operation must have a default response with
application/jsoncontent - All schemas referenced by operations must exist in
components.schemas
npx wsdl-tsc gateway \
--openapi-file ./docs/weather-api.json \
--client-dir ./src/services/weather \
--gateway-dir ./src/gateway/weather \
--gateway-service-name weather \
--gateway-version-prefix v1npx wsdl-tsc gateway \
--openapi-file ./docs/hotel-api.json \
--client-dir ./src/services/hotel \
--gateway-dir ./src/gateway/hotel \
--gateway-service-name hotel \
--gateway-version-prefix v2 \
--gateway-default-status-codes 200,400,401,404,500npx wsdl-tsc gateway \
--openapi-file ./docs/booking-api.yaml \
--client-dir ./src/services/booking \
--gateway-dir ./src/gateway/booking \
--gateway-service-name booking \
--gateway-version-prefix v1The generated gateway provides a Fastify plugin for simplified integration.
Your host application needs these dependencies:
npm install fastify fastify-pluginimport Fastify from 'fastify';
import weatherGateway from './gateway/plugin.js';
import { Weather } from './client/client.js';
const app = Fastify({ logger: true });
// Create and configure SOAP client
const weatherClient = new Weather({
source: 'https://example.com/weather.wsdl',
// security: new soap.WSSecurity('user', 'pass'), // if needed
});
// Register gateway plugin with client
await app.register(weatherGateway, {
client: weatherClient,
// Note: Route paths are determined by --openapi-base-path during generation.
// The prefix option here adds an ADDITIONAL runtime prefix on top of generated paths.
// Only use if you need to mount routes under a different sub-path at runtime.
});
await app.listen({ port: 3000 });The plugin automatically:
- Decorates Fastify with the SOAP client (
fastify.weatherClient) - Registers all JSON schemas for validation
- Installs a centralized error handler
- Registers all routes with full handler implementations
For more control, you can use the individual modules:
import Fastify from 'fastify';
import { registerSchemas_v1_weather } from './gateway/schemas.js';
import { registerRoutes_v1_weather } from './gateway/routes.js';
import { createGatewayErrorHandler_v1_weather } from './gateway/runtime.js';
import { Weather } from './client/client.js';
const app = Fastify({ logger: true });
// Manual setup
const weatherClient = new Weather({ source: 'weather.wsdl' });
app.decorate('weatherClient', weatherClient);
// Register schemas
await registerSchemas_v1_weather(app);
// Install error handler
app.setErrorHandler(createGatewayErrorHandler_v1_weather());
// Register routes (paths already include --openapi-base-path if configured)
await registerRoutes_v1_weather(app);
await app.listen({ port: 3000 });Route handlers are fully implemented and call the SOAP client automatically:
// Generated: routes/get-city-forecast-by-zip.ts
import type { FastifyInstance } from "fastify";
import schema from "../schemas/operations/getcityforecastbyzip.json" with { type: "json" };
import { buildSuccessEnvelope } from "../runtime.js";
export async function registerRoute_v1_weather_getcityforecastbyzip(fastify: FastifyInstance) {
fastify.route({
method: "POST",
url: "/get-city-forecast-by-zip",
schema,
handler: async (request) => {
const client = fastify.weatherClient;
const result = await client.GetCityForecastByZIP(request.body);
return buildSuccessEnvelope(result.response);
},
});
}All responses are wrapped in a standard envelope format:
Success Response:
{
"status": "SUCCESS",
"message": null,
"data": { /* SOAP response data */ },
"error": null
}
Error Response:
{
"status": "ERROR",
"message": "Request validation failed",
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": { /* validation errors */ }
}
}
The centralized error handler (runtime.ts) automatically classifies errors:
| Error Type | HTTP Status | Error Code |
|---|---|---|
| Validation errors | 400 | VALIDATION_ERROR |
| SOAP faults | 502 | SOAP_FAULT |
| Connection refused | 503 | SERVICE_UNAVAILABLE |
| Timeout | 504 | GATEWAY_TIMEOUT |
| Other errors | 500 | INTERNAL_ERROR |
If you prefer to implement handler logic manually or need custom transformation logic beyond the standard SOAP-to-REST mapping, use stub mode:
npx wsdl-tsc gateway \
--openapi-file ./docs/weather-api.json \
--client-dir ./src/services/weather \
--gateway-dir ./src/gateway/weather \
--gateway-service-name weather \
--gateway-version-prefix v1 \
--gateway-stub-handlersThis generates minimal handler stubs that throw "Not implemented" errors, allowing you to implement fully custom logic while keeping the routing and validation infrastructure.
Purpose: Generate a runnable Fastify application that integrates the generated client, gateway, and OpenAPI spec. This provides an immediately executable server for testing, development, and demonstrations.
When to use:
- Local testing and development
- Quick iteration on gateway configurations
- Demonstrating the generated API
- CI smoke testing
npx wsdl-tsc app \
--client-dir <path> \
--gateway-dir <path> \
--openapi-file <path> \
[--catalog-file <path>] \
[--app-dir <path>] \
[options]| Flag | Description |
|---|---|
--client-dir |
Path to client directory (where client.ts is located) |
--gateway-dir |
Path to gateway directory (where plugin.ts is located) |
--openapi-file |
Path to OpenAPI specification file |
| Flag | Default | Description |
|---|---|---|
--catalog-file |
{client-dir}/catalog.json |
Path to catalog.json (for metadata extraction) |
--app-dir |
{gateway-dir}/../app |
Output directory for generated app |
--import-extensions |
Inferred from catalog or js |
Import-extension mode: js, ts, or bare |
--host |
127.0.0.1 |
Default server host |
--port |
3000 |
Default server port |
--prefix |
"" (empty) |
Route prefix |
--logger |
true |
Enable Fastify logger |
--openapi-mode |
copy |
How to handle OpenAPI file: copy or reference |
# First generate client, OpenAPI, and gateway
npx wsdl-tsc pipeline \
--wsdl-source weather.wsdl \
--client-dir ./client \
--openapi-file ./openapi.json \
--gateway-dir ./gateway \
--gateway-service-name weather \
--gateway-version-prefix v1
# Then generate runnable app
npx wsdl-tsc app \
--client-dir ./client \
--gateway-dir ./gateway \
--openapi-file ./openapi.json \
--app-dir ./appnpx wsdl-tsc app \
--client-dir ./client \
--gateway-dir ./gateway \
--openapi-file ./openapi.json \
--app-dir ./my-app \
--host 0.0.0.0 \
--port 8080 \
--prefix /api/v1The app command generates the following files:
app/
├── server.js (or .ts) # Main application entry point
├── config.js (or .ts) # Configuration loader with env support
├── .env.example # Environment variable template
├── README.md # Usage instructions
└── openapi.json # OpenAPI spec (when --openapi-mode=copy)
# Copy environment template
cd app
cp .env.example .env
# Edit .env to configure WSDL source and other settings
# vim .env
# Run the server
npx tsx server.js # For TypeScript files
# or
node server.js # For JavaScript filesThe generated app loads configuration from environment variables with the following precedence:
- Environment variables (runtime overrides)
- Catalog defaults (from generation-time)
- Hard defaults (in config file)
| Variable | Default (from catalog or flags) | Description |
|---|---|---|
WSDL_SOURCE |
From catalog or required | WSDL URL or local file path |
HOST |
127.0.0.1 |
Server bind address |
PORT |
3000 |
Server listen port |
PREFIX |
"" (empty) |
Route prefix |
LOGGER |
true |
Enable Fastify logger |
The generated app serves the following endpoints:
GET /healthReturns: { "ok": true }
GET /openapi.jsonReturns: Complete OpenAPI 3.1 specification
All SOAP operations are exposed as REST endpoints. See the OpenAPI spec for complete API documentation.
# Start the server
cd app
npx tsx server.js
# Test health endpoint
curl http://localhost:3000/health
# Get OpenAPI spec
curl http://localhost:3000/openapi.json | jq .
# Call a gateway operation (example)
curl -X POST http://localhost:3000/get-weather-information \
-H "Content-Type: application/json" \
-d '{}'The app command can also be used via the pipeline with the --generate-app flag:
npx wsdl-tsc pipeline \
--wsdl-source weather.wsdl \
--client-dir ./client \
--openapi-file ./openapi.json \
--gateway-dir ./gateway \
--gateway-service-name weather \
--gateway-version-prefix v1 \
--generate-appThis generates all artifacts including the runnable app in a single command.
Purpose: Parse WSDL and generate only the intermediate catalog.json representation without TypeScript client code.
When to use:
- Multi-stage builds where you want to cache the parsed WSDL
- Debugging or inspecting the compiled schema structure
- Sharing a compiled catalog across multiple generation targets
npx wsdl-tsc compile --wsdl-source <file|url> --catalog-file <path> [options]| Flag | Description |
|---|---|
--wsdl-source |
Path or URL to the WSDL file |
--catalog-file |
Output path for catalog.json |
| Flag | Default | Description |
|---|---|---|
--import-extensions |
js |
Import specifier style: js, ts, or bare |
--client-attributes-key |
$attributes |
Attribute bag key for runtime mapper |
--client-class-name |
(derived) | Override generated client class name |
--client-int64-as |
string |
Map 64-bit integers: string, number, or bigint |
--client-bigint-as |
string |
Map arbitrary-size integers: string or number |
--client-decimal-as |
string |
Map xs:decimal: string or number |
--client-date-as |
string |
Map date/time types: string or Date |
--client-choice-mode |
all-optional |
Choice element strategy: all-optional or union |
--client-fail-on-unresolved |
false |
Fail build on unresolved type references |
--client-nillable-as-optional |
false |
Treat nillable elements as optional properties |
Basic Compilation:
npx wsdl-tsc compile \
--wsdl-source examples/minimal/weather.wsdl \
--catalog-file tmp/catalog.jsonWith Custom Mapping Options:
npx wsdl-tsc compile \
--wsdl-source https://example.com/Hotel.wsdl \
--catalog-file ./build/hotel-catalog.json \
--client-int64-as number \
--client-decimal-as stringFor Debugging:
# Compile to inspect types and operations
npx wsdl-tsc compile \
--wsdl-source ./wsdl/ComplexService.wsdl \
--catalog-file ./debug/catalog.json \
--client-fail-on-unresolved false
# Inspect types
cat ./debug/catalog.json | jq '.types'
# Inspect operations
cat ./debug/catalog.json | jq '.operations'catalog.json- Compiled schema representation including types, operations, and metadata
The catalog.json file contains the compiled WSDL representation:
{
"wsdlUri": "path/to/service.wsdl",
"targetNamespace": "http://example.com/service",
"serviceName": "WeatherService",
"types": [
{
"name": "GetWeatherRequest",
"properties": []
}
],
"operations": [
{
"name": "GetWeather",
"input": "GetWeatherRequest",
"output": "GetWeatherResponse"
}
],
"options": {
"imports": "js",
"catalog": true
}
}Key sections:
types- All compiled type definitions with properties and inheritanceoperations- SOAP operations with input/output type referencesoptions- Compiler options used during generation
This catalog can be reused with the client and openapi commands via --catalog-file.
Default behavior: Catalog files are co-located with their primary output files for better organization and discoverability.
Catalog Location by Command:
compile: Always requires explicit--catalog-file(no default)client: Defaults to{client-dir}/catalog.jsonopenapi: Defaults to{openapi-file-dir}/catalog.jsonpipeline: Intelligent cascade - first available:{client-dir}>{openapi-dir}>{gateway-dir}>tmp/
Common patterns:
-
Co-located with client (recommended for most projects):
npx wsdl-tsc client --wsdl-source service.wsdl --client-dir src/services/weather
Creates
src/services/weather/catalog.jsonautomatically. -
Shared catalog for multiple commands (custom location):
npx wsdl-tsc compile --wsdl-source service.wsdl --catalog-file build/shared-catalog.json npx wsdl-tsc client --catalog-file build/shared-catalog.json --client-dir src/client npx wsdl-tsc openapi --catalog-file build/shared-catalog.json --openapi-file docs/api.json
import soap from "soap";
import { Weather } from "./src/services/weather/client.js";
const client = new Weather({
source: "https://example.com/WeatherService?wsdl",
security: new soap.WSSecurity("username", "password")
});// Operation with input
const forecast = await client.GetCityForecastByZIP({
ZIP: "10001"
});
console.log(forecast.GetCityForecastByZIPResult.Success);
console.log(forecast.GetCityForecastByZIPResult.ForecastResult);
// Operation without input
const info = await client.GetWeatherInformation({});
console.log(info.GetWeatherInformationResult.WeatherDescriptions);When an element has both attributes and text content, use the $value convention:
const price = {
currencyCode: "USD", // attribute
$value: "123.45" // text content
};Repeated elements are automatically typed as arrays:
interface ForecastReturn {
Forecast: Forecast[]; // maxOccurs > 1
}All operations and types are fully typed:
// TypeScript knows the exact shape
const result: GetCityWeatherByZIPResponse = await client.GetCityWeatherByZIP({
ZIP: "10001"
});
// Autocomplete and type checking work
result.GetCityWeatherByZIPResult.Temperature; // number | string (depends on mapping)Define security schemes, headers, and per-operation overrides:
{
"global": {
"scheme": "bearer",
"bearer": { "bearerFormat": "JWT" },
"headers": [
{
"name": "X-Correlation-Id",
"required": false,
"schema": { "type": "string" }
}
]
},
"overrides": {
"CancelBooking": { "scheme": "apiKey" }
}
}Supported schemes: none, basic, bearer, apiKey, oauth2
Explicit operation → tag mapping:
{
"GetCityWeatherByZIP": ["Weather", "Forecast"],
"GetWeatherInformation": ["Weather", "Info"],
"CancelBooking": ["Booking", "Cancellation"]
}Per-operation overrides for method, summary, description, and deprecation:
{
"GetCityWeatherByZIP": {
"method": "get",
"summary": "Get weather forecast by ZIP code",
"description": "Returns a detailed weather forecast for the specified US ZIP code",
"deprecated": false
},
"LegacyOperation": {
"deprecated": true
}
}All generated code and specifications have stable, deterministic ordering for version control:
- ✅ TypeScript files: Sorted type declarations, imports, and exports
- ✅ OpenAPI specs: Sorted paths, HTTP methods, schemas, parameters, security schemes, tags
- ✅ JSON Schemas: Sorted property keys and component names
- ✅ Gateway routes: Alphabetically organized route files
- ✅ Catalog JSON: Consistent ordering of types and operations
Benefit: Safe regeneration in CI/CD without spurious diffs.
OpenAPI Validation (enabled by default):
- Uses
@apidevtools/swagger-parser - Validates schema structure
- Resolves all
$refreferences - Catches missing schemas and circular dependencies
- Disable with
--openapi-validate false
Gateway Contract Validation:
- All request/response bodies must use
$reftocomponents.schemas - Every operation must have a default response with
application/jsoncontent - All referenced schemas must exist in
components.schemas
Gateway Error Classification:
| Error Type | HTTP Status | Error Code | When It Occurs |
|---|---|---|---|
| Validation errors | 400 | VALIDATION_ERROR |
Request doesn't match JSON Schema |
| SOAP faults | 502 | SOAP_FAULT |
SOAP service returned a fault |
| Connection refused | 503 | SERVICE_UNAVAILABLE |
Cannot reach SOAP endpoint |
| Timeout | 504 | GATEWAY_TIMEOUT |
SOAP request exceeded timeout |
| Other errors | 500 | INTERNAL_ERROR |
Unexpected errors |
All errors are wrapped in the standard envelope format with error object populated.
Enable SOAP request/response debugging:
NODE_DEBUG=soap node app.jsThis logs full XML request/response payloads to console.
Caching Strategy:
# Step 1: Compile catalog (cacheable artifact)
npx wsdl-tsc compile \
--wsdl-source ./wsdl/Service.wsdl \
--catalog-file ./build/catalog.json
# Step 2: Generate code from cached catalog
npx wsdl-tsc client --catalog-file ./build/catalog.json --client-dir ./src/client
npx wsdl-tsc openapi --catalog-file ./build/catalog.json --openapi-file ./docs/api.jsonRecommended Build Script (package.json):
{
"scripts": {
"generate": "npx wsdl-tsc pipeline --wsdl-source ./wsdl/service.wsdl --client-dir ./src/client --openapi-file ./docs/api.json --gateway-dir ./src/gateway --gateway-service-name svc --gateway-version-prefix v1",
"build": "npm run generate && tsc",
"typecheck": "tsc --noEmit"
}
}Choice Elements:
- Current strategy:
all-optional(all branches optional) - Future: Discriminated union support (planned)
Union Types:
- Experimental
--client-choice-mode unionavailable - May require manual refinement for complex patterns
WS-Policy:
- Security hints extracted from policies
- Custom policies may require manual security configuration
Array Wrapper Flattening:
- Single-child sequences with
maxOccurs>1become array schemas - Sequences with multiple children preserve wrapper
All CLI commands are available as TypeScript functions for programmatic usage.
Generate TypeScript SOAP client from WSDL.
import { compileWsdlToProject } from "@techspokes/typescript-wsdl-client";
await compileWsdlToProject({
wsdl: "./wsdl/Hotel.wsdl",
outDir: "./src/services/hotel",
options: {
imports: "js",
catalog: true,
primitive: {
int64As: "number",
bigIntegerAs: "string",
decimalAs: "string",
dateAs: "string"
},
choice: "all-optional",
clientName: "HotelClient",
nillableAsOptional: false
}
});Type Signature:
// noinspection JSAnnotator
function compileWsdlToProject(input: {
wsdl: string;
outDir: string;
options?: Partial<CompilerOptions>;
}): Promise<void>;Options (CompilerOptions):
interface CompilerOptions {
wsdl: string;
out: string;
imports: "js" | "ts" | "bare";
catalog: boolean;
primitive: PrimitiveOptions;
choice?: "all-optional" | "union";
failOnUnresolved?: boolean;
attributesKey?: string;
clientName?: string;
nillableAsOptional?: boolean;
}
interface PrimitiveOptions {
int64As?: "string" | "number" | "bigint";
bigIntegerAs?: "string" | "number";
decimalAs?: "string" | "number";
dateAs?: "string" | "Date";
}Generate OpenAPI 3.1 specification from WSDL or catalog.
import { generateOpenAPI } from "@techspokes/typescript-wsdl-client";
const { doc, jsonPath, yamlPath } = await generateOpenAPI({
wsdl: "./wsdl/Hotel.wsdl",
outFile: "./docs/hotel-api",
format: "both",
title: "Hotel Booking API",
version: "1.0.0",
servers: ["https://api.example.com/v1"],
basePath: "/booking",
pathStyle: "kebab",
tagStyle: "service",
validate: true
});Type Signature:
// noinspection JSAnnotator
function generateOpenAPI(opts: GenerateOpenAPIOptions): Promise<{
doc: any;
jsonPath?: string;
yamlPath?: string;
}>;Options (GenerateOpenAPIOptions):
interface GenerateOpenAPIOptions {
// Input sources (mutually exclusive)
wsdl?: string;
catalogFile?: string;
compiledCatalog?: CompiledCatalog;
// Output
outFile?: string;
format?: "json" | "yaml" | "both";
// Metadata
title?: string;
version?: string;
description?: string;
servers?: string[];
// Path configuration
basePath?: string;
pathStyle?: "kebab" | "asis" | "lower";
defaultMethod?: string;
// Schema configuration
closedSchemas?: boolean;
pruneUnusedSchemas?: boolean;
// Tag configuration
tagStyle?: "default" | "first" | "service";
tagsFile?: string;
// Security & operations
securityConfigFile?: string;
opsFile?: string;
// Envelope customization
envelopeNamespace?: string;
errorNamespace?: string;
// Validation
validate?: boolean;
skipValidate?: boolean;
// Deprecated
asYaml?: boolean;
}Generate Fastify gateway code from OpenAPI specification.
import { generateGateway } from "@techspokes/typescript-wsdl-client";
await generateGateway({
openapiFile: "./docs/hotel-api.json",
outDir: "./src/gateway/hotel",
clientDir: "./src/services/hotel",
versionSlug: "v1",
serviceSlug: "hotel",
defaultResponseStatusCodes: [200, 400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504],
imports: "js"
});Type Signature:
// noinspection JSAnnotator
function generateGateway(opts: GenerateGatewayOptions): Promise<void>;Options (GenerateGatewayOptions):
interface GenerateGatewayOptions {
// Input sources (mutually exclusive)
openapiFile?: string;
openapiDocument?: any;
// Output
outDir: string;
// Client integration
clientDir?: string;
// URN configuration
versionSlug?: string;
serviceSlug?: string;
// Schema configuration
defaultResponseStatusCodes?: number[];
// Import style
imports?: "js" | "ts" | "bare";
}Run complete pipeline: client + OpenAPI + gateway in one pass.
import { runGenerationPipeline } from "@techspokes/typescript-wsdl-client";
const { compiled, openapiDoc } = await runGenerationPipeline({
wsdl: "./wsdl/Hotel.wsdl",
catalogOut: "./build/hotel-catalog.json",
clientOutDir: "./src/services/hotel",
compiler: {
imports: "js",
primitive: {
int64As: "number",
decimalAs: "string"
}
},
openapi: {
outFile: "./docs/hotel-api.json",
format: "both",
servers: ["https://api.example.com/v1"],
tagStyle: "service"
},
gateway: {
outDir: "./src/gateway/hotel",
versionSlug: "v1",
serviceSlug: "hotel"
}
});Type Signature:
// noinspection JSAnnotator
function runGenerationPipeline(opts: PipelineOptions): Promise<{
compiled: CompiledCatalog;
openapiDoc?: any;
}>;Options (PipelineOptions):
interface PipelineOptions {
// Input
wsdl: string;
// Catalog (always generated)
catalogOut: string;
// Client generation (optional)
clientOutDir?: string;
compiler?: Partial<CompilerOptions>;
// OpenAPI generation (optional)
openapi?: Omit<GenerateOpenAPIOptions, "wsdl" | "catalogFile" | "compiledCatalog"> & {
outFile?: string;
};
// Gateway generation (optional, requires openapi)
gateway?: Omit<GenerateGatewayOptions, "openapiFile" | "openapiDocument"> & {
outDir?: string;
};
}Default: String-first safety — Prevents precision loss and parsing errors at the cost of convenience.
| XSD Type | Default | Alternatives | Recommendation |
|---|---|---|---|
xs:long |
string |
number, bigint |
Use number if values fit safely in JS range |
xs:integer |
string |
number |
Use string for arbitrary-size integers |
xs:decimal |
string |
number |
Use string for precise decimal representation |
xs:dateTime |
string |
Date |
Use Date if runtime parsing is acceptable |
Current strategy: all-optional — All choice branches are emitted as optional properties.
// WSDL: <xs:choice>
interface MyType {
optionA?: string;
optionB?: number;
}Future: Discriminated unions for type-safe choice validation.
Single repeated child without attributes collapses to array schema in OpenAPI:
<xs:complexType name="ArrayOfForecast">
<xs:sequence>
<xs:element name="Forecast" type="tns:Forecast" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>OpenAPI Schema:
{
"ArrayOfForecast": {
"type": "array",
"items": { "$ref": "#/components/schemas/Forecast" }
}
}Extension (xs:extension):
- Base properties merged into derived type
- TypeScript:
extendswhen possible
Restriction (xs:restriction):
- Treated as base type with constraints
SimpleContent:
- Base value collapsed into
$valueproperty - Attributes remain as peer properties
OpenAPI validation uses @apidevtools/swagger-parser:
- Validates schema structure
- Resolves all
$refreferences - Catches missing schemas
- Detects circular dependencies
Disable with --openapi-validate false or validate: false in API.
| Symptom | Resolution |
|---|---|
| WSDL fetch fails | Curl the URL, check TLS/proxy settings, retry with local copy |
| Unresolved type references | Re-run with --client-fail-on-unresolved=false to inspect partial graph |
| Missing schema in OpenAPI | Ensure the global element exists (catalog shows compiled symbols) |
| Wrong array modeling | Check maxOccurs in WSDL; tool only arrays when maxOccurs>1 or unbounded |
| Authentication errors | Provide proper soap.ISecurity instance (WSSecurity, BasicAuthSecurity) |
| Date/time confusion | Use --client-date-as Date for runtime Date objects |
| TypeScript compilation errors | Check --import-extensions matches your tsconfig moduleResolution |
| Gateway validation failures | Ensure OpenAPI has valid $ref paths and all schemas in components.schemas |
| Catalog file not found | Catalog defaults to output directory (e.g., {client-dir}/catalog.json); use --catalog-file to specify custom location |
Debug SOAP requests/responses:
NODE_DEBUG=soap node app.jsnpx wsdl-tsc --help
npm run smoke:compile # Test catalog generation
npm run smoke:client # Test client generation
npm run smoke:openapi # Test OpenAPI generation
npm run smoke:gateway # Test gateway generation
npm run smoke:pipeline # Test complete pipelineEnsure your tsconfig.json is compatible:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}Examine the compiled catalog to understand type resolution:
# Compile to specific location
npx wsdl-tsc compile \
--wsdl-source ./wsdl/Hotel.wsdl \
--catalog-file build/hotel-catalog.json
# Inspect types, operations, and metadata
cat build/hotel-catalog.json | jq '.types'
cat build/hotel-catalog.json | jq '.operations'Or inspect catalog from client generation:
npx wsdl-tsc client \
--wsdl-source ./wsdl/Hotel.wsdl \
--client-dir ./src/services/hotel
cat ./src/services/hotel/catalog.json | jq '.types'The catalog is automatically placed at ./src/services/hotel/catalog.json.
We welcome contributions! Here's how to get started:
# Clone repository
git clone https://github.com/techspokes/typescript-wsdl-client.git
cd typescript-wsdl-client
# Install dependencies
npm install
# Build
npm run build
# Type check
npm run typecheck
# Run smoke tests
npm run smoke:compile
npm run smoke:client
npm run smoke:openapi
npm run smoke:gateway
npm run smoke:pipeline
# Run full CI suite
npm run ci- Fork & branch — Create a feature branch from
main - Make changes — Implement your feature or fix
- Test — Run smoke tests and verify functionality
- Update CHANGELOG — Add entry under
## [Unreleased]section - Commit — Use conventional commit format:
feat:,fix:,docs:, etc. - Submit PR — Create pull request with clear description
Version: <version> <type>(<optional-scope>): <imperative summary>
[optional body with details, rationale, breaking changes]
[optional footer with refs: Closes #123]
Types: feat, fix, docs, refactor, test, chore
Example:
Version: 0.8.0 feat(gateway): add support for YAML OpenAPI input
Gateway command now accepts both JSON and YAML OpenAPI files,
determined by file extension (.json, .yaml, .yml).
Closes #456
- Follow existing code style and conventions
- Keep PRs focused and scoped
- Update documentation for user-visible changes
- Add tests where applicable
- Ensure CI passes
See also: CONTRIBUTING.md, CODE_OF_CONDUCT.md
MIT © TechSpokes
Generated artifacts are fully yours with no restrictions or attribution required.
See LICENSE for full text.
Support ongoing development and maintenance:
GitHub Sponsors: https://github.com/sponsors/TechSpokes
Your organization could be featured here!
- Priority support for issues and feature requests
- Early access to new features
- Recognition in README and release notes
- Direct influence on roadmap priorities
- Support open source sustainability
Thank you for considering sponsorship!
- Documentation: README.md (you are here)
- Contributing Guide: CONTRIBUTING.md
- Roadmap: ROADMAP.md
- Changelog: CHANGELOG.md
- Security Policy: SECURITY.md
- Support: SUPPORT.md
- Issues: https://github.com/techspokes/typescript-wsdl-client/issues
- Discussions: https://github.com/techspokes/typescript-wsdl-client/discussions
- npm Package: https://www.npmjs.com/package/@techspokes/typescript-wsdl-client