# Working With Generated Code Guide to using the TypeScript SOAP clients and types generated by wsdl-tsc. See [README](../README.md) for quick start and [Concepts](concepts.md) for type modeling details. ## Client Construction ```typescript 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 ```typescript 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: ```typescript const info = await client.GetWeatherInformation({}); console.log(info.GetWeatherInformationResult.WeatherDescriptions); ``` ## Calling Stream Operations Operations opted into streaming with `--stream-config` return `StreamOperationResponse` instead of the buffered shape. `records` is a single-pass `AsyncIterable` that pulls bytes from the upstream SOAP response on demand, so memory stays bounded regardless of payload size. ```typescript 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](configuration.md#stream-configuration) for how to opt specific operations in, and [ADR-002](decisions/002-streamable-responses.md) for the rationale and terminal-error policy. ## Attributes and Text Content When an element has both attributes and text content, use the $value convention: ```typescript const price = { currencyCode: "USD", $value: "123.45" }; ``` See [Concepts](concepts.md) for details on the flattening model and $value convention. ## Working With Arrays Repeated elements are automatically typed as arrays: ```typescript 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`. ```typescript 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: ```typescript 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`. ```typescript /** * 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. ```typescript 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](gateway-guide.md#streaming-handlers) for the full example and terminal-error policy. See [Gateway Guide](gateway-guide.md) for the full architecture and [CLI Reference](cli-reference.md) 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: ```typescript 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: ```typescript app.register(weatherGateway, { client: mock }); ``` For stream operations, mock implementations return an async-iterable `records` field. See the [Testing Guide](testing.md) for the full pattern with async generators. See [Testing Guide](testing.md) for full integration test patterns.