Skip to content

Latest commit

 

History

History
207 lines (148 loc) · 7.99 KB

File metadata and controls

207 lines (148 loc) · 7.99 KB

Working With Generated Code

Guide to using the TypeScript SOAP clients and types generated by wsdl-tsc.

See README for quick start and Concepts for type modeling details.

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

The source parameter accepts a URL or local file path to the WSDL.

When generating a runnable app with a security config, the scaffold can create security.ts to build soap.IOptions and soap.ISecurity from environment variables. The generated helper supports common node-soap profiles such as Basic auth, bearer auth, WS-Security UsernameToken, TLS client certificates, X509 signing, and NTLM. Secrets are read at runtime and are not embedded in generated source.

Calling Operations

const forecast = await client.GetCityForecastByZIP({
  ZIP: "10001"
});

console.log(forecast.GetCityForecastByZIPResult.Success);
console.log(forecast.GetCityForecastByZIPResult.ForecastResult);

Operations without input still require an empty object:

const info = await client.GetWeatherInformation({});
console.log(info.GetWeatherInformationResult.WeatherDescriptions);

Calling Stream Operations

Operations opted into streaming with --stream-config return StreamOperationResponse<RecordType> instead of the buffered shape. records is a single-pass AsyncIterable<RecordType> that pulls bytes from the upstream SOAP response on demand, so memory stays bounded regardless of payload size.

const result = await client.UnitDescriptiveInfoStream({});
for await (const record of result.records) {
  console.log(record);
}

Because records is single-pass, each response can be iterated once. Headers captured before the first record are available on result.headers, and the serialized SOAP envelope that was sent upstream is on result.requestRaw when populated.

Streaming requires the saxes runtime dependency. The generated app scaffold pins saxes ^6.0.0 automatically; consumers that integrate the generated client into their own project must install it explicitly.

See Stream Configuration for how to opt specific operations in, and ADR-002 for the rationale and terminal-error policy.

Attributes and Text Content

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

const price = {
  currencyCode: "USD",
  $value: "123.45"
};

See Concepts for details on the flattening model and $value convention.

Working With Arrays

Repeated elements are automatically typed as arrays:

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

Working With Choice Unions

--client-choice-mode union changes generated xs:choice types from parallel optional fields into a base object plus an exclusive branch union. The default mode remains all-optional.

export type SearchRequest = SearchRequestChoiceBase & SearchRequestChoice1;

export interface SearchRequestChoiceBase {
  tenantId: string;
}

export type SearchRequestChoice1 =
  | { email: string; phone?: never }
  | { phone: number; email?: never };

Generated gateway test suites use one valid branch in union-mode mocks. They also include validation cases that reject payloads with multiple choice branches and payloads that omit a required choice branch.

Type Safety

All operations and types are fully typed:

const result: GetCityWeatherByZIPResponse = await client.GetCityWeatherByZIP({
  ZIP: "10001"
});

result.GetCityWeatherByZIPResult.Temperature;

Autocomplete and type checking work across all generated interfaces.

Documentation Comments

Generated types.ts, operations.ts, and client.ts include source documentation when present in WSDL/XSD.

wsdl:documentation on operations is emitted as method comments in operations.ts and client.ts. xs:annotation/xs:documentation on complex types, attributes, and elements is emitted as comments in types.ts.

/**
 * Thing payload.
 */
export interface Thing {
  /**
   * Display name.
   *
   * @xsd {"kind":"element","type":"xs:string","occurs":{"min":1,"max":1,"nillable":false}}
   */
  name: string;
}

The existing @xsd metadata annotations are preserved for runtime marshaling and tooling.

OpenAPI generation also propagates these docs into description fields:

  • Operation descriptions come from wsdl:documentation by default
  • Operation summaries default to the first sentence of wsdl:documentation
  • --openapi-ops-file description overrides operation documentation when provided
  • --openapi-ops-file summary overrides the default summary when provided
  • Schema and property descriptions come from xs:annotation/xs:documentation

Gateway Route Handlers

When generating a Fastify gateway (--gateway-dir), each SOAP operation gets a fully typed route handler. The handler imports the request type from the client, uses Fastify's Body: T generic for type inference, and wraps the SOAP response in a standard envelope.

import type { FastifyInstance } from "fastify";
import type { GetCityForecastByZIP } from "../../client/types.js";
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<{ Body: GetCityForecastByZIP }>({
    method: "POST",
    url: "/get-city-forecast-by-zip",
    schema,
    handler: async (request) => {
      const client = fastify.weatherClient;
      const result = await client.GetCityForecastByZIP(request.body as GetCityForecastByZIP);
      return buildSuccessEnvelope(result.response);
    },
  });
}

Key features of the generated handlers:

  • Body: T generic: Fastify infers request.body type from the route generic, enabling IDE autocomplete and compile-time checks
  • JSON Schema validation: the schema import provides Fastify with request/response validation at runtime, before the handler runs
  • Envelope wrapping: buildSuccessEnvelope() wraps the raw SOAP response in the standard { status, message, data, error } envelope
  • Route file header comments include propagated operation summary and description when present

Stream-configured operations generate a different handler shape: the response serialization schema is omitted and the handler streams result.records through the helper for the configured format. NDJSON handlers use reply.type("application/x-ndjson") with toNdjson(result.records), and JSON array handlers use reply.type("application/json") with toJsonArray(result.records). See the Gateway Guide Streaming Handlers section for the full example and terminal-error policy.

See Gateway Guide for the full architecture and CLI Reference for generation flags.

Operations Interface

The generated operations.ts exports a typed interface ({ServiceName}Operations) that mirrors the concrete client class methods. Use it for dependency injection and testing:

import type { WeatherOperations } from "./client/operations.js";

const mock: WeatherOperations = {
  GetCityWeatherByZIP: async (args) => ({
    response: { GetCityWeatherByZIPResult: { Success: true } },
    headers: {},
  }),
  // ...other operations
};

The gateway plugin accepts any WeatherOperations implementation, so you can pass the mock directly:

app.register(weatherGateway, { client: mock });

For stream operations, mock implementations return an async-iterable records field. See the Testing Guide for the full pattern with async generators.

See Testing Guide for full integration test patterns.