WSDL TypeScript Client Generator
wsdl-tsclient is a utility for generating TypeScript SOAP clients and type definitions from WSDL (Web Services Description Language) files. It serves as a bridge between legacy SOAP services and modern TypeScript applications, offering compile-time safety and improved developer experience when interacting with SOAP APIs. The current stable version is 1.7.1. It maintains a consistent release cadence with minor versions introducing new features and bug fixes, indicated by recent releases like 1.7.0 and 1.7.1. Key differentiators include its reliance on `ts-morph` for robust code generation and `node-soap` for runtime client functionality, drawing inspiration from Java's `wsimport` and `openapi-generator`. This tool is particularly useful for projects needing to consume SOAP services with strong typing, without the complexity of manual type definition. It focuses purely on client and type generation, unlike some alternatives that offer OpenAPI conversion or REST gateway scaffolding.
Common errors
-
TypeError: createClientAsync is not a function
cause The `soap` package, which provides the `createClientAsync` function at runtime, is not installed as a dependency in the project using the generated client.fixRun `npm install soap` or `yarn add soap` in your project's root directory. -
Error: Maximum recursive definition name exceeded. This can lead to very long filenames.
cause The WSDL contains highly recursive type definitions or many types with very similar names, and `wsdl-tsclient` has reached its limit for creating unique, suffixed names.fixTry increasing the limit with the `--maxRecursiveDefinitionName` CLI option (e.g., `wsdl-tsclient ... --maxRecursiveDefinitionName 128`), or consider simplifying the WSDL schema if possible. -
Cannot find module 'soap' or its corresponding type declarations.
cause TypeScript cannot resolve the `soap` module. This usually means `soap` is not installed, or type declarations for `soap` are missing or not correctly configured in `tsconfig.json`.fixEnsure `soap` is installed (`npm i soap`) and, if using TypeScript, that `@types/soap` (if available and needed for an older `soap` version, though `soap` itself often ships types now) is also installed or that `skipLibCheck` is enabled in `tsconfig.json` if type errors persist. -
SyntaxError: Cannot use import statement outside a module
cause You are trying to run a generated client file (or `wsdl-tsclient` itself) that uses ESM `import` statements in a CommonJS-only Node.js environment without proper configuration (e.g., missing `"type": "module"` in `package.json` for ESM output, or not transpiling to CJS). This is particularly relevant if `--esm` flag was used during generation.fixIf the generated client is intended for ESM, ensure your `package.json` includes `"type": "module"` or use a bundler/transpiler configured for ESM. If targeting CommonJS, avoid the `--esm` flag during generation or ensure your Node.js environment correctly handles ESM interoperability.
Warnings
- gotcha The `soap` package, which is essential for the runtime functionality of the generated clients, is a peer dependency and must be installed manually alongside `wsdl-tsclient`. Forgetting to install `soap` will lead to runtime errors when attempting to use the generated client.
- breaking Older versions of `soap` (specifically before `^1.0.0`) had tighter constraints, which could cause issues. Version 1.6.0 of `wsdl-tsclient` relaxed these constraints, but it's important to use a compatible version of `soap` (preferably `^1.0.0` or newer) for stability.
- gotcha When WSDL definitions contain self-recursive or cyclic types, `wsdl-tsclient` might generate `any` types to prevent infinite recursion, potentially losing some type safety in those specific areas. This behavior was introduced to handle complex WSDLs more gracefully.
- gotcha The `--esm` CLI flag, introduced in v1.7.1, affects how imports are generated in the client code, specifically by adding `.js` suffixes to import paths for better ESM compatibility. Failing to use this flag for projects targeting pure ESM environments might lead to import resolution issues.
- gotcha WSDL definitions with identical names but different structures can lead to definition collisions. The `--maxRecursiveDefinitionName` option (since v1.2.0) sets a limit on how many times a suffix is added to disambiguate such names. Exceeding this limit will result in an error.
- gotcha Early versions (before 1.3.1) had issues with generated types missing attributes or having incorrect casing, leading to mismatches with the actual SOAP payload. This could cause runtime failures or incorrect data interpretation.
Install
-
npm install wsdl-tsclient -
yarn add wsdl-tsclient -
pnpm add wsdl-tsclient
Imports
- generateClient
const { generateClient } = require('wsdl-tsclient');import { generateClient } from 'wsdl-tsclient'; - createClientAsync
const { createClientAsync } = require('./generated/MyWsdl');import { createClientAsync } from './generated/MyWsdl'; - MyServicePort
import { IMyServicePort } from './generated/MyWsdl/MyServicePort';
Quickstart
import { generateClient } from 'wsdl-tsclient';
import { createClientAsync } from './generated/MyService'; // This path will vary based on your WSDL
import * as path from 'path';
import * as fs from 'fs';
async function runGenerationAndClient() {
const wsdlPath = path.resolve(__dirname, 'resources', 'MyService.wsdl');
const outputPath = path.resolve(__dirname, 'generated');
// Ensure output directory exists
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
// Placeholder WSDL content for demonstration
// In a real scenario, you'd have a physical WSDL file.
const dummyWsdlContent = `<?xml version="1.0" encoding="UTF-8"?>
<definitions name="MyService" targetNamespace="http://www.example.org/MyService/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<xsd:schema targetNamespace="http://www.example.org/MyService/">
<xsd:element name="SayHelloRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="SayHelloResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="greeting" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</types>
<message name="SayHelloRequest">
<part name="parameters" element="tns:SayHelloRequest"/>
</message>
<message name="SayHelloResponse">
<part name="parameters" element="tns:SayHelloResponse"/>
</message>
<portType name="MyService">
<operation name="SayHello">
<input message="tns:SayHelloRequest"/>
<output message="tns:SayHelloResponse"/>
</operation>
</portType>
<binding name="MyServiceSOAP" type="tns:MyService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="SayHello">
<soap:operation soapAction="http://www.example.org/MyService/SayHello"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="MyService">
<port name="MyServiceSOAP" binding="tns:MyServiceSOAP">
<soap:address location="http://localhost:8000/MyService"/>
</port>
</service>
</definitions>`;
// Save dummy WSDL for generation
const resourcesDir = path.resolve(__dirname, 'resources');
if (!fs.existsSync(resourcesDir)) {
fs.mkdirSync(resourcesDir, { recursive: true });
}
fs.writeFileSync(wsdlPath, dummyWsdlContent);
console.log(`Generating client from ${wsdlPath} to ${outputPath}...`);
await generateClient(wsdlPath, outputPath);
console.log('Client generated successfully!');
// Example of using the generated client (requires 'soap' package to be installed)
// In a real scenario, the 'soap' client would be configured to hit a live endpoint.
try {
const client = await createClientAsync(wsdlPath);
// Assuming the generated client has a 'MyService' service and 'MyServiceSOAP' port
const result = await client.MyService.MyServiceSOAP.SayHelloAsync({ name: 'World' });
console.log('SOAP Call Result:', result[0].greeting); // result[0] typically holds the response body
} catch (error) {
console.error('Error using generated client (ensure soap is installed and service is running):', error.message);
}
}
runGenerationAndClient().catch(console.error);