Skip to content

TypeScript WSDL → SOAP client generator with full xs:attribute, complex types, sequences, inheritance, and namespace-collision merging.

License

Notifications You must be signed in to change notification settings

TechSpokes/typescript-wsdl-client

TypeScript WSDL Client

License: MIT CI npm version npm downloads GitHub Stars GitHub Forks TechSpokes Org Sponsor

Transform complex WSDL/XSD definitions into type-safe TypeScript SOAP clients with optional OpenAPI 3.1 specs and production-ready REST gateways.

Use This If You Need...

  • 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 $value convention, inheritance resolution

Vendor: TechSpokes · Maintainer: Serge Liatko (@sergeliatko)


Table of Contents


Installation

npm install --save-dev @techspokes/typescript-wsdl-client
npm install soap   # Runtime dependency for SOAP calls

Requirements:

  • Node.js 20.0.0 or later
  • soap package (runtime dependency for generated clients)

Quick Start (60 Seconds)

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-app

Start the server:

cd tmp/app
cp .env.example .env
# Edit .env to set WSDL_SOURCE if needed
npx tsx server.js

Test 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?

  1. Parsed the WSDL and compiled types
  2. Generated a TypeScript SOAP client with full type safety
  3. Created an OpenAPI 3.1 spec matching the client types
  4. Built Fastify gateway handlers that call SOAP and return JSON
  5. Created a runnable Express-style app with health/OpenAPI endpoints

What You Get (Outputs)

TypeScript SOAP Client

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 3.1 Specification

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-parser by default

Fastify REST Gateway

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 });

Runnable Application

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)

Standard Response Envelope

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 */ }
  }
}

Core Concepts

Flattening and $value

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)
}

Primitive Mapping Defaults (String-First Safety)

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

Deterministic Generation

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 as Intermediate Artifact

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:

  • client command: {client-dir}/catalog.json
  • openapi command: {openapi-dir}/catalog.json
  • pipeline command: First available output directory

Common Workflows

Which Command Should I Run?

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

Workflow 1: Generate Everything (Recommended)

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 v1

Output:

  • Client: ./src/services/weather/client.ts, types.ts, utils.ts, catalog.json
  • OpenAPI: ./docs/weather-api.json
  • Gateway: ./src/gateway/weather/ (routes, schemas, plugin)

Workflow 2: SOAP Client Only

Generate TypeScript client for direct SOAP integration:

npx wsdl-tsc client \
  --wsdl-source ./wsdl/Hotel.wsdl \
  --client-dir ./src/services/hotel

Usage:

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,
});

Workflow 3: OpenAPI for Documentation

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/v1

Use the spec:

  • Import into Postman, Insomnia, or Swagger UI
  • Generate client SDKs with OpenAPI Generator
  • Share as REST API documentation

Workflow 4: REST Gateway Over SOAP

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 v1

Or 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 v1

Workflow 5: CI/CD Integration

Cache 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.json

Workflow 6: Debugging Complex WSDL

Inspect 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'

Command Reference

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)

Command: pipeline (Recommended)

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

Usage

npx wsdl-tsc pipeline \
  --wsdl-source <file|url> \
  [--catalog-file <path>] \
  [--client-dir <path>] \
  [--openapi-file <path>] \
  [--gateway-dir <path>] \
  [options]

Required Flags

Flag Description
--wsdl-source Path or URL to WSDL file

Output Flags

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-file only: {openapi-file-dir}/catalog.json
  • With --gateway-dir only: {gateway-dir}/catalog.json

Note: At least one of --client-dir, --openapi-file, or --gateway-dir must be provided.

Generation Control Flags

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)

Optional Flags

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-dir provided)
  • --gateway-version-prefix (required if --gateway-dir provided)
  • --gateway-default-status-codes
  • --gateway-stub-handlers (default: false)

Examples

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 v1

With 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-app

Client + 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 both

With 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 string

Pipeline Workflow

The pipeline command executes these steps in order:

  1. Parse WSDL → Load and validate WSDL document
  2. Compile Catalog → Generate intermediate representation
  3. Emit Catalog → Write catalog.json (always)
  4. Generate Client → Emit TypeScript client files (if --client-dir)
  5. Generate OpenAPI → Create OpenAPI spec (if --openapi-file)
  6. Generate Gateway → Create Fastify gateway code (if --gateway-dir)
  7. Generate App → Create runnable application (if --generate-app)

All steps share the same parsed WSDL and compiled catalog, ensuring consistency.


Command: client

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

Usage

npx wsdl-tsc client --wsdl-source <file|url> --client-dir <path> [options]
# OR
npx wsdl-tsc client --catalog-file <path> --client-dir <path> [options]

Required Flags

Flag Description
--wsdl-source Path or URL to WSDL file (see note below)
--client-dir Output directory for generated TypeScript files

Optional Input Flags

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.

Generated Files

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

Optional Flags

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

Examples

Basic Generation (Default Catalog Location)

npx wsdl-tsc client \
  --wsdl-source examples/minimal/weather.wsdl \
  --client-dir tmp/client

Output: Generates client files and catalog at tmp/client/catalog.json.

With Custom Catalog Path

npx wsdl-tsc client \
  --wsdl-source examples/minimal/weather.wsdl \
  --client-dir tmp/client \
  --catalog-file build/shared-catalog.json

With Custom Numeric Mappings

npx 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 string

Output: Catalog generated at ./src/integrations/soap/hotel/catalog.json.

From Pre-compiled Catalog

# 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

Key Modeling Rules

  • Attributes & elements become peer properties (flattened)
  • Text content becomes $value property
  • Required attributes: use!="optional"; elements minOccurs>=1
  • Multiplicity: maxOccurs>1 or unbounded become 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

Command: openapi

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

Usage

npx wsdl-tsc openapi --wsdl-source <file|url> --openapi-file <path> [options]
# OR
npx wsdl-tsc openapi --catalog-file <path> --openapi-file <path> [options]

Required Flags

Flag Description
--openapi-file Output path for OpenAPI specification

Input Source Flags (Mutually Exclusive)

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.

Core Optional Flags

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

Path & Schema Customization

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

Response Envelope Customization

Flag Default Description
--openapi-envelope-namespace ResponseEnvelope Override envelope component name suffix
--openapi-error-namespace ErrorObject Override error object component name suffix

Configuration Files

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)

Examples

Basic JSON Output

npx wsdl-tsc openapi \
  --wsdl-source examples/minimal/weather.wsdl \
  --openapi-file ./docs/weather-api.json

Multi-Format with Validation

npx 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 /soap

From Pre-compiled Catalog

npx wsdl-tsc openapi \
  --catalog-file ./artifacts/hotel-catalog.json \
  --openapi-file ./docs/hotel-api.json \
  --openapi-format json

With Custom Configuration

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.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 service

Standard Response Envelope

All responses are wrapped in a standard envelope for consistency and debuggability (always-on since 0.7.1):

Base Envelope Structure

{
  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
}

Error Object Structure

{
  code: string;              // stable machine error code
  message: string;           // brief description
  details: object | null;    // arbitrary extra info
}

Naming Rules

  • Base envelope: ${serviceName}ResponseEnvelope (override with --openapi-envelope-namespace)
  • Error object: ${serviceName}ErrorObject (override with --openapi-error-namespace)
  • Per-operation extension: <PayloadType|OperationName><EnvelopeNamespace> (refines data field)

Collision Avoidance

If the payload type already ends with the namespace prefix, an underscore is inserted:

  • Payload WeatherResponse + default ResponseEnvelopeWeatherResponse_ResponseEnvelope
  • Payload Booking + default ResponseEnvelopeBookingResponseEnvelope

Example

npx wsdl-tsc openapi \
  --wsdl-source ./wsdl/Hotel.wsdl \
  --openapi-file ./docs/hotel-api.json \
  --openapi-envelope-namespace ApiEnvelope \
  --openapi-error-namespace ApiError

Produces components:

  1. HotelApiEnvelope (base)
  2. <Payload>ApiEnvelope extension schemas (alphabetically sorted)
  3. HotelApiError (error object)
  4. Domain schemas

Tag Inference Strategies

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., GetCityWeatherByZIPGet)

Use --openapi-tags-file for explicit mapping when heuristics are insufficient.

Output Determinism

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.


Command: gateway

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

Usage

npx wsdl-tsc gateway \
  --openapi-file <path> \
  --client-dir <path> \
  --gateway-dir <path> \
  --gateway-service-name <slug> \
  --gateway-version-prefix <slug> \
  [options]

Required Flags

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)

Optional Flags

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.

Generated Output Structure

{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)

URN-Based Schema IDs

All generated JSON Schemas use deterministic URN identifiers:

urn:services:{serviceSlug}:{versionSlug}:schemas:{models|operations}:{schemaSlug}

Example: urn:services:weather:v1:schemas:models:getcityweatherbyzipresponse

Contract Assumptions

The gateway generator enforces strict OpenAPI contract validation:

  • All request/response bodies must use $ref to components.schemas (no inline schemas)
  • Every operation must have a default response with application/json content
  • All schemas referenced by operations must exist in components.schemas

Examples

Basic Gateway Generation

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

With Custom Status Codes

npx 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,500

From YAML OpenAPI

npx 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 v1

Integration Pattern

The generated gateway provides a Fastify plugin for simplified integration.

Prerequisites

Your host application needs these dependencies:

npm install fastify fastify-plugin

Using the Generated Plugin (Recommended)

import 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

Using Individual Components (Advanced)

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 });

Generated Handler Implementation

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);
    },
  });
}

Response Envelope

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 */ }
  }
}

Error Handling

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

Stub Handler Mode (Backward Compatible)

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-handlers

This generates minimal handler stubs that throw "Not implemented" errors, allowing you to implement fully custom logic while keeping the routing and validation infrastructure.


Command: app

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

Usage

npx wsdl-tsc app \
  --client-dir <path> \
  --gateway-dir <path> \
  --openapi-file <path> \
  [--catalog-file <path>] \
  [--app-dir <path>] \
  [options]

Required Flags

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

Optional Flags

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

Examples

Generate App After Pipeline

# 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 ./app

Generate App with Custom Configuration

npx 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/v1

Generated App Structure

The 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)

Running the Generated App

# 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 files

Configuration

The generated app loads configuration from environment variables with the following precedence:

  1. Environment variables (runtime overrides)
  2. Catalog defaults (from generation-time)
  3. Hard defaults (in config file)

Environment Variables

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

Endpoints

The generated app serves the following endpoints:

Health Check

GET /health

Returns: { "ok": true }

OpenAPI Specification

GET /openapi.json

Returns: Complete OpenAPI 3.1 specification

Gateway Routes

All SOAP operations are exposed as REST endpoints. See the OpenAPI spec for complete API documentation.

Example Usage

# 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 '{}'

Integration with Pipeline

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-app

This generates all artifacts including the runnable app in a single command.


Command: compile (Advanced)

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

Usage

npx wsdl-tsc compile --wsdl-source <file|url> --catalog-file <path> [options]

Required Flags

Flag Description
--wsdl-source Path or URL to the WSDL file
--catalog-file Output path for catalog.json

Optional Flags

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

Examples

Basic Compilation:

npx wsdl-tsc compile \
  --wsdl-source examples/minimal/weather.wsdl \
  --catalog-file tmp/catalog.json

With 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 string

For 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'

Output

  • catalog.json - Compiled schema representation including types, operations, and metadata

Catalog Structure

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 inheritance
  • operations - SOAP operations with input/output type references
  • options - Compiler options used during generation

This catalog can be reused with the client and openapi commands via --catalog-file.

Catalog Co-location

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.json
  • openapi: Defaults to {openapi-file-dir}/catalog.json
  • pipeline: Intelligent cascade - first available: {client-dir} > {openapi-dir} > {gateway-dir} > tmp/

Common patterns:

  1. 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.json automatically.

  2. 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

Working With Generated Clients

Client Construction

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")
});

Calling Operations

// 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);

Attributes & Text Content

When an element has both attributes and text content, use the $value convention:

const price = {
  currencyCode: "USD",  // attribute
  $value: "123.45"      // text content
};

Working With Arrays

Repeated elements are automatically typed as arrays:

interface ForecastReturn {
  Forecast: Forecast[];  // maxOccurs > 1
}

Type Safety

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)

Configuration Files

Security Configuration (security.json)

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

Tags Configuration (tags.json)

Explicit operation → tag mapping:

{
  "GetCityWeatherByZIP": ["Weather", "Forecast"],
  "GetWeatherInformation": ["Weather", "Info"],
  "CancelBooking": ["Booking", "Cancellation"]
}

Operations Configuration (ops.json)

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
  }
}

Production Concerns

Deterministic Output Guarantees

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.

Validation Behavior

OpenAPI Validation (enabled by default):

  • Uses @apidevtools/swagger-parser
  • Validates schema structure
  • Resolves all $ref references
  • Catches missing schemas and circular dependencies
  • Disable with --openapi-validate false

Gateway Contract Validation:

  • All request/response bodies must use $ref to components.schemas
  • Every operation must have a default response with application/json content
  • All referenced schemas must exist in components.schemas

Error Handling

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.

SOAP Wire Logging

Enable SOAP request/response debugging:

NODE_DEBUG=soap node app.js

This logs full XML request/response payloads to console.

CI/CD Tips

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.json

Recommended 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"
  }
}

Known Limitations

Choice Elements:

  • Current strategy: all-optional (all branches optional)
  • Future: Discriminated union support (planned)

Union Types:

  • Experimental --client-choice-mode union available
  • 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>1 become array schemas
  • Sequences with multiple children preserve wrapper

Programmatic API

All CLI commands are available as TypeScript functions for programmatic usage.

compileWsdlToProject

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";
}

generateOpenAPI

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;
}

generateGateway

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";
}

runGenerationPipeline

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;
  };
}

Advanced Topics

Primitive Mapping Philosophy

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

Choice Element Handling

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.

Array Wrapper Flattening

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" }
  }
}

Inheritance Flattening

Extension (xs:extension):

  • Base properties merged into derived type
  • TypeScript: extends when possible

Restriction (xs:restriction):

  • Treated as base type with constraints

SimpleContent:

  • Base value collapsed into $value property
  • Attributes remain as peer properties

Validation

OpenAPI validation uses @apidevtools/swagger-parser:

  • Validates schema structure
  • Resolves all $ref references
  • Catches missing schemas
  • Detects circular dependencies

Disable with --openapi-validate false or validate: false in API.


Troubleshooting

Common Issues

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

Enable SOAP Wire Logging

Debug SOAP requests/responses:

NODE_DEBUG=soap node app.js

Verify Installation

npx 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 pipeline

TypeScript Configuration

Ensure your tsconfig.json is compatible:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Catalog Inspection

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.


Contributing

We welcome contributions! Here's how to get started:

Development Setup

# 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

Making Changes

  1. Fork & branch — Create a feature branch from main
  2. Make changes — Implement your feature or fix
  3. Test — Run smoke tests and verify functionality
  4. Update CHANGELOG — Add entry under ## [Unreleased] section
  5. Commit — Use conventional commit format: feat:, fix:, docs:, etc.
  6. Submit PR — Create pull request with clear description

Commit Message Format

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

Guidelines

  • 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


License

MIT © TechSpokes

Generated artifacts are fully yours with no restrictions or attribution required.

See LICENSE for full text.


Sponsors

Support ongoing development and maintenance:

GitHub Sponsors: https://github.com/sponsors/TechSpokes

Current Sponsors

Your organization could be featured here!

Why Sponsor?

  • 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!


Links

About

TypeScript WSDL → SOAP client generator with full xs:attribute, complex types, sequences, inheritance, and namespace-collision merging.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors 4

  •  
  •  
  •  
  •