@techspokes/typescript-wsdl-client 1.1.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +22 -3
  2. package/dist/checkedPlanning.js +27 -4
  3. package/dist/cli.js +17 -1
  4. package/dist/client/checkedDirectRuntime.d.ts +11 -1
  5. package/dist/client/checkedDirectRuntime.js +19 -2
  6. package/dist/client/directClientRuntime.d.ts +18 -1
  7. package/dist/client/directClientRuntime.js +15 -3
  8. package/dist/client/directClientVersion.d.ts +2 -0
  9. package/dist/client/directClientVersion.js +2 -0
  10. package/dist/client/generateClient.js +72 -16
  11. package/dist/client/generateDirectClient.js +14 -9
  12. package/dist/client/generateOperations.js +30 -11
  13. package/dist/client/generateUtils.js +5 -1
  14. package/dist/compiler/canonicalGraph.d.ts +4 -15
  15. package/dist/compiler/canonicalGraph.js +1 -8
  16. package/dist/compiler/componentRecordProjection.d.ts +3 -0
  17. package/dist/compiler/componentRecordProjection.js +10 -0
  18. package/dist/compiler/declarationIdentity.d.ts +22 -0
  19. package/dist/compiler/declarationIdentity.js +9 -0
  20. package/dist/compiler/directCodec.d.ts +22 -2
  21. package/dist/compiler/directCodec.js +40 -7
  22. package/dist/compiler/directOperation.d.ts +28 -5
  23. package/dist/compiler/directOperation.js +84 -14
  24. package/dist/compiler/directSoap.d.ts +40 -10
  25. package/dist/compiler/directSoap.js +151 -18
  26. package/dist/compiler/directSoapSource.js +3 -2
  27. package/dist/compiler/directSourceCapsule.js +69 -4
  28. package/dist/compiler/directStructure.js +6 -2
  29. package/dist/compiler/directStructurePlan.d.ts +10 -1
  30. package/dist/compiler/directStructurePlan.js +27 -7
  31. package/dist/compiler/directValues.d.ts +15 -3
  32. package/dist/compiler/directValues.js +20 -4
  33. package/dist/compiler/legacyDeclarations.d.ts +26 -0
  34. package/dist/compiler/legacyDeclarations.js +144 -0
  35. package/dist/compiler/projectionScalar.d.ts +18 -1
  36. package/dist/compiler/projectionScalar.js +47 -13
  37. package/dist/compiler/rpcStringValues.d.ts +30 -0
  38. package/dist/compiler/rpcStringValues.js +196 -0
  39. package/dist/compiler/rpcTypedValues.d.ts +48 -0
  40. package/dist/compiler/rpcTypedValues.js +190 -0
  41. package/dist/compiler/schemaCompiler.js +44 -109
  42. package/dist/compiler/semanticCatalog.d.ts +2 -2
  43. package/dist/compiler/soapHeaders.d.ts +10 -4
  44. package/dist/compiler/soapHeaders.js +28 -9
  45. package/dist/gateway/generateCheckedGateway.js +26 -6
  46. package/dist/gateway/generateComposedGateway.d.ts +16 -0
  47. package/dist/gateway/generateComposedGateway.js +65 -0
  48. package/dist/gateway/generateGateway.js +1 -0
  49. package/dist/gateway/generators.js +19 -4
  50. package/dist/gateway/helpers.d.ts +1 -0
  51. package/dist/gateway/helpers.js +15 -0
  52. package/dist/generation/componentShape.js +25 -1
  53. package/dist/index.d.ts +5 -1
  54. package/dist/index.js +3 -1
  55. package/dist/loader/orderedSyntax.d.ts +4 -0
  56. package/dist/loader/orderedSyntax.js +18 -4
  57. package/dist/multiServicePipeline.d.ts +41 -0
  58. package/dist/multiServicePipeline.js +287 -0
  59. package/dist/openapi/composeOpenAPI.d.ts +29 -0
  60. package/dist/openapi/composeOpenAPI.js +322 -0
  61. package/dist/pipeline.d.ts +1 -1
  62. package/dist/pipeline.js +2 -0
  63. package/docs/agent-skill.md +10 -1
  64. package/docs/api-reference.md +127 -9
  65. package/docs/checked-gateway.md +10 -0
  66. package/docs/checked-generation.md +11 -5
  67. package/docs/cli-reference.md +25 -5
  68. package/docs/concepts.md +4 -5
  69. package/docs/configuration.md +10 -3
  70. package/docs/corpus.md +1 -1
  71. package/docs/gateway-guide.md +104 -27
  72. package/docs/generated-code.md +12 -6
  73. package/docs/migration-playbook.md +4 -0
  74. package/docs/migration.md +134 -6
  75. package/docs/output-anatomy.md +20 -17
  76. package/docs/production.md +7 -5
  77. package/docs/start-here.md +12 -2
  78. package/docs/supported-patterns.md +23 -0
  79. package/docs/testing.md +2 -0
  80. package/docs/troubleshooting.md +3 -1
  81. package/llms.txt +9 -1
  82. package/package.json +2 -2
  83. package/src/runtime/clientStreamMethods.tpl.txt +156 -148
  84. package/src/runtime/operationsStreamHelper.tpl.txt +6 -6
  85. package/src/runtime/streamXml.ts +48 -20
package/README.md CHANGED
@@ -9,13 +9,25 @@ Turn legacy WSDL/SOAP services into typed TypeScript clients and optional REST A
9
9
 
10
10
  Created from production integration work at [TechSpokes](https://www.techspokes.com); built for teams modernizing real enterprise SOAP integrations.
11
11
 
12
+ ## Upgrading to 1.3.0
13
+
14
+ Version 1.3.0 adds opt-in checked SOAP 1.2 document/literal execution through the existing adapter. Existing SOAP 1.1 and direct-runtime bindings retain their contracts. SOAP 1.2 clients use an additive runtime handshake; regenerate matched artifacts when adopting that binding. Read the [1.2.x migration guidance](docs/migration.md#from-12x-to-130) for adoption and evidence limits.
15
+
16
+ ## Upgrading to 1.2.0
17
+
18
+ Existing projects keep legacy generation unless they explicitly choose another profile. Regeneration can change affected DTO names, stream records and corrected scalar/simple-content results; 1.2.0 is not an unconditional drop-in regeneration upgrade. Read the [1.1.x migration guide](docs/migration.md#from-11x-to-120) before updating, including matched artifact regeneration, scaffold preservation, consumer checks and rollback.
19
+
20
+ Published npm starting points include 1.1.5 and 1.1.8; 1.1.6 and 1.1.7 were unpublished candidates. Native multi-service composition and checked generation are optional capabilities with separate adoption instructions, not automatic migrations of an existing gateway.
21
+
12
22
  ## Pick Your Path
13
23
 
14
24
  | Goal | Command | What you get |
15
25
  |------|---------|-------------|
16
26
  | Typed SOAP client | `npx wsdl-tsc client` | TypeScript types, typed operations, mockable interface |
17
27
  | OpenAPI 3.1 spec | `npx wsdl-tsc openapi` | OpenAPI JSON/YAML generated from WSDL |
18
- | Full SOAP-to-REST bridge | `npx wsdl-tsc pipeline` | Client + OpenAPI + Fastify gateway + runnable app |
28
+ | Full SOAP-to-REST bridge | `npx wsdl-tsc pipeline --init-app` | Client + OpenAPI + Fastify gateway + runnable app |
29
+ | Several SOAP sources behind one API | `npx wsdl-tsc pipeline --services-config services.json` | Separate legacy clients, one gateway plugin and combined OpenAPI |
30
+ | Checked document/literal or bounded RPC | `npx wsdl-tsc pipeline --model checked-components-v1` | Source-backed validation and buffered checked values |
19
31
 
20
32
  See [Start Here](docs/start-here.md) for detailed guidance on choosing a path.
21
33
 
@@ -31,7 +43,7 @@ Checked generation can opt into [application groups and configured names](docs/c
31
43
 
32
44
  ## Why Choose This
33
45
 
34
- Most tools in this space stop at one layer: a SOAP runtime, type generation, or spec conversion. This package generates the full stack from a single WSDL source.
46
+ Most tools in this space stop at one layer: a SOAP runtime, type generation, or spec conversion. This package generates the full stack from a WSDL source and can compose independent legacy sources into one REST gateway.
35
47
 
36
48
  - Typed client, OpenAPI spec, REST gateway, and runnable app from one command
37
49
  - Deterministic output safe for CI regeneration and version control
@@ -101,6 +113,10 @@ curl -X POST http://localhost:3000/get-weather-information \
101
113
  -H "Content-Type: application/json" -d '{}'
102
114
  ```
103
115
 
116
+ ### Compose several SOAP services
117
+
118
+ Use `pipeline --services-config ./services.json` to generate each source into separate client, catalog, OpenAPI and gateway paths, then emit one aggregate plugin and API document. The [multi-service guide](docs/gateway-guide.md#multi-service-setup) shows the configuration and generated `createClients` helper; each runtime client keeps its own WSDL source, endpoint and security settings. Native composition currently supports legacy generation; checked composition remains deferred.
119
+
104
120
  ## What Gets Generated
105
121
 
106
122
  | Output | Files | Purpose |
@@ -229,6 +245,7 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
229
245
  | [Configuration](docs/configuration.md) | Gateway security, upstream SOAP security, tags, operation overrides |
230
246
  | [Migration Playbook](docs/migration-playbook.md) | End-to-end SOAP modernization guide |
231
247
  | [Gateway Guide](docs/gateway-guide.md) | Fastify integration, inbound enforcement, and error handling |
248
+ | [Multi-Service Gateway Status](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/roadmap/multi-service-gateway.md) | Implemented legacy composition, mapping boundaries, and deferred checked composition |
232
249
  | [Testing Guide](docs/testing.md) | Consumer tests and separate maintainer verification guidance |
233
250
  | [Production Guide](docs/production.md) | CI/CD, validation, logging |
234
251
  | [Troubleshooting](docs/troubleshooting.md) | Common issues and debugging |
@@ -236,6 +253,8 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
236
253
  | [Core Concepts](docs/concepts.md) | Flattening, $value, primitives, determinism |
237
254
  | [Agent Skill Artifact](docs/agent-skill.md) | Release ZIP for consumer-project AI agents |
238
255
  | [Version Migration](docs/migration.md) | Upgrading between package versions |
256
+ | [1.2.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.2.0.md) | Composition, consumer compatibility and bounded RPC qualification |
257
+ | [1.3.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.3.0.md) | Checked SOAP 1.2 execution and preserved SOAP 1.1 compatibility |
239
258
  | [XML/JSON Mapping Contract](docs/mapping-contract.md) | Checked keyed, namespace, nil and group representations |
240
259
  | [Product Corpus](docs/corpus.md) | Representative public inputs and their executable evidence |
241
260
 
@@ -259,7 +278,7 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
259
278
  | [Direct Structural Matching](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/content-model-matching.md) | Internal structure-only matcher, evidence and scalar handoff |
260
279
  | [Architecture](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/architecture.md) | Internal pipeline for contributors |
261
280
  | [Reference Validation](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/reference-validation.md) | Internal evidence contract and Node tooling migration inventory |
262
- | [NOAA NDFD Qualification](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/noaa-ndfd-qualification.md) | Authentic source provenance and one-operation native refusal |
281
+ | [NOAA NDFD Qualification](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/noaa-ndfd-qualification.md) | All-12 owned RPC execution, ten live successes and two live GML refusals |
263
282
  | [File Naming And Path Organization](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/file-naming-and-path-organization.md) | Contributor convention for taxonomy paths and fixture retrieval |
264
283
 
265
284
  ### Decisions and research
@@ -166,10 +166,8 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
166
166
  && namespace !== XSD_NAMESPACE)
167
167
  missing.set(namespace, child.source);
168
168
  }
169
- for (const [namespace, at] of missing)
170
- add('missing-dependency', 'loading', `locationless-import:${namespace}`, 'Supply an authorized schema path for this namespace.', `checkedComponents.namespaceLocations[${JSON.stringify(namespace)}].location`, selectors.length ? selectors : operations.flatMap(o => o.binding ? [{ portType: o.portType, binding: o.binding, operation: o.operation }] : []), at);
171
- if (missing.size)
172
- unresolved = [...(intake.namespaceLocations ?? []), ...[...missing.keys()].sort().map(namespace => ({ namespace, location: 'REQUIRED_SCHEMA_LOCATION' }))];
169
+ // @why XSD 1.0 section 4.2.3 permits locationless imports. Native construction
170
+ // decides whether their components are needed; namespace URIs are never fetch locations.
173
171
  if (!blockers.length && config) {
174
172
  if (!Object.hasOwn(explicit, 'standardLibraries') && input.schemas.some(s => syntaxElements(s.syntax, XSD_NAMESPACE, 'import')
175
173
  .some(n => syntaxAttribute(n, 'namespace') === XSD_NAMESPACE && syntaxAttribute(n, 'schemaLocation') === undefined))) {
@@ -188,6 +186,31 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
188
186
  }
189
187
  }
190
188
  for (const refusal of native.refusals) {
189
+ if (['whole-source-qualification', 'source-preparation'].includes(refusal.rule) && missing.size) {
190
+ const referenced = new Set();
191
+ const pending = documents.map(document => document.root);
192
+ while (pending.length) {
193
+ const node = pending.pop();
194
+ for (const attribute of node.attributes)
195
+ if (attribute.name.namespace === ''
196
+ && ['type', 'ref', 'base', 'itemType', 'memberTypes', 'substitutionGroup', 'element'].includes(attribute.name.local)) {
197
+ for (const token of attribute.value.trim().split(/[\t\r\n ]+/)) {
198
+ try {
199
+ referenced.add(resolveLexicalQName(token, node).namespace);
200
+ }
201
+ catch { /* Native diagnostics retain malformed references. */ }
202
+ }
203
+ }
204
+ pending.push(...syntaxElements(node));
205
+ }
206
+ const needed = [...missing].filter(([namespace]) => referenced.has(namespace));
207
+ for (const [namespace, at] of needed)
208
+ add('missing-dependency', 'native-preparation', `locationless-import:${namespace}`, 'Supply an authorized schema path for required components in this namespace.', `checkedComponents.namespaceLocations[${JSON.stringify(namespace)}].location`, [refusal.selector], at);
209
+ if (needed.length) {
210
+ unresolved = [...(intake.namespaceLocations ?? []), ...needed.map(([namespace]) => namespace).sort().map(namespace => ({ namespace, location: 'REQUIRED_SCHEMA_LOCATION' }))];
211
+ continue;
212
+ }
213
+ }
191
214
  if (refusal.rule.startsWith('ambiguous-record-path:')) {
192
215
  add('ambiguous-selection', 'native-preparation', refusal.rule, 'Supply recordPath using expanded names to select one declared repeated complex record in this companion source.', 'checkedComponents.recordProjections[].recordPath', [refusal.selector], undefined, 'mapping-and-artifact');
193
216
  continue;
package/dist/cli.js CHANGED
@@ -50,6 +50,7 @@ function loadStreamConfigOrExit(filePath) {
50
50
  }
51
51
  // Process command line arguments, removing the first two elements (node executable and script path)
52
52
  const rawArgs = hideBin(process.argv);
53
+ const hasServicesConfig = rawArgs.some(argument => argument === "--services-config" || argument.startsWith("--services-config="));
53
54
  // Available commands
54
55
  const COMMANDS = ["compile", "client", "openapi", "gateway", "app", "pipeline", "assess"];
55
56
  // ---------------------------------------------------------------------------
@@ -613,10 +614,25 @@ if (rawArgs[0] === "app") {
613
614
  });
614
615
  process.exit(0);
615
616
  }
616
- if (rawArgs[0] === "pipeline") {
617
+ if (rawArgs[0] === "pipeline" && hasServicesConfig) {
618
+ const argv = await yargs(rawArgs.slice(1)).version(false).scriptName("wsdl-tsc pipeline")
619
+ .option("services-config", { type: "string", demandOption: true, desc: "Multi-service generation configuration; exclusive with single-source pipeline flags" })
620
+ .strict().help().parse();
621
+ try {
622
+ const { loadMultiServiceConfig, runMultiServicePipeline } = await import("./multiServicePipeline.js");
623
+ const result = await runMultiServicePipeline(await loadMultiServiceConfig(argv["services-config"]));
624
+ success(`Composed gateway written to ${result.pluginFile}`);
625
+ success(`Combined OpenAPI specification written to ${result.openapiFile}`);
626
+ }
627
+ catch (error) {
628
+ handleCLIError(error);
629
+ }
630
+ }
631
+ if (rawArgs[0] === "pipeline" && !hasServicesConfig) {
617
632
  const pipelineArgv = await yargs(rawArgs.slice(1))
618
633
  .version(false)
619
634
  .scriptName("wsdl-tsc pipeline")
635
+ .option("services-config", { type: "string", desc: "Compose independent service pipelines from JSON; exclusive with single-source flags" })
620
636
  .usage("$0 --wsdl-source <file|url> [--client-dir <dir>] [--openapi-file <path>] [--gateway-dir <dir> --gateway-service-name <slug> --gateway-version-prefix <slug>] [--catalog-file <path>] [options]")
621
637
  .option("wsdl-source", { type: "string", demandOption: true, desc: "Path or URL to the WSDL" })
622
638
  .option("model", { type: "string", choices: ["legacy", "checked-components-v1"], default: "legacy", desc: "Generation model; components derive unambiguous WSDL selections" })
@@ -1,4 +1,5 @@
1
- import { DIRECT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
1
+ import type { ComponentClientResult } from './directClientRuntime.js';
2
+ import { DIRECT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
2
3
  interface Name {
3
4
  readonly namespace: string;
4
5
  readonly local: string;
@@ -93,4 +94,13 @@ export declare const checkedComponentRuntimeV1: Readonly<{
93
94
  version: typeof COMPONENT_CLIENT_RUNTIME_VERSION;
94
95
  execute(expectedBase64: string, input: unknown, options: ComponentOptions, version: typeof COMPONENT_CLIENT_RUNTIME_VERSION): Promise<Failure | Success>;
95
96
  }>;
97
+ /** Additive SOAP 1.2 binding permits protocol faults without declared application detail.
98
+ * @since 1.3.0
99
+ * @remarks Source reconstruction, endpoint restrictions and native validation use the shared component runtime.
100
+ * @returns Native component values, SOAP 1.2 protocol faults or explicit refusal; no full-profile admission.
101
+ */
102
+ export declare const checkedComponentRuntimeV2: Readonly<{
103
+ version: typeof COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION;
104
+ execute(expectedBase64: string, input: unknown, options: ComponentOptions, version: typeof COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION): Promise<ComponentClientResult>;
105
+ }>;
96
106
  export {};
@@ -1,6 +1,6 @@
1
1
  import { types } from 'node:util';
2
- import { directClientRuntime, componentClientRuntime } from './directClientRuntime.js';
3
- import { DIRECT_CLIENT_RUNTIME_VERSION, DIRECT_CLIENT_RUNTIME_REPAIR, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_REPAIR } from './directClientVersion.js';
2
+ import { directClientRuntime, componentClientRuntime, componentSoap12ClientRuntime } from './directClientRuntime.js';
3
+ import { DIRECT_CLIENT_RUNTIME_VERSION, DIRECT_CLIENT_RUNTIME_REPAIR, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_REPAIR, COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
4
4
  function dataOptions(options, allowed) {
5
5
  return !!options && typeof options === 'object' && !types.isProxy(options)
6
6
  && [null, Object.prototype].includes(Object.getPrototypeOf(options))
@@ -41,3 +41,20 @@ export const checkedComponentRuntimeV1 = Object.freeze({
41
41
  return componentClientRuntime.execute(expectedBase64, input, options);
42
42
  },
43
43
  });
44
+ /** Additive SOAP 1.2 binding permits protocol faults without declared application detail.
45
+ * @since 1.3.0
46
+ * @remarks Source reconstruction, endpoint restrictions and native validation use the shared component runtime.
47
+ * @returns Native component values, SOAP 1.2 protocol faults or explicit refusal; no full-profile admission.
48
+ */
49
+ export const checkedComponentRuntimeV2 = Object.freeze({
50
+ version: COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION,
51
+ async execute(expectedBase64, input, options, version) {
52
+ if (version !== COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION)
53
+ return Object.freeze({ kind: 'incompatible-artifact',
54
+ rule: 'generated-runtime-version', repair: COMPONENT_CLIENT_RUNTIME_REPAIR, usage: Object.freeze({ nodes: 0, work: 0 }) });
55
+ if (!dataOptions(options, ['localSource', 'fileRoots', 'capsule', 'soap', 'sourceResources', 'sourceOrigins', 'sourceUri', 'namespaceLocations', 'standardLibraries', 'recordSources'])) {
56
+ return Object.freeze({ kind: 'unsupported', rule: 'data-only checked component options required', usage: Object.freeze({ nodes: 0, work: 0 }) });
57
+ }
58
+ return componentSoap12ClientRuntime.execute(expectedBase64, input, options);
59
+ },
60
+ });
@@ -1,6 +1,6 @@
1
1
  import type { SemanticBudgetLimits, ResolverUsage } from '../compiler/semanticBudget.js';
2
2
  import type { DirectCapsuleFailure } from '../compiler/directSourceCapsule.js';
3
- import type { DirectSoapOptions, ComponentSoapOptions, DirectSoapFailure } from '../compiler/directSoap.js';
3
+ import type { DirectSoapOptions, ComponentSoapOptions, DirectSoapFailure, ComponentProtocolFault, DeclaredSoapFault } from '../compiler/directSoap.js';
4
4
  import type { DirectOperationSelector } from '../compiler/directOperation.js';
5
5
  import type { DirectApplicationValue } from '../compiler/directValues.js';
6
6
  import { DIRECT_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
@@ -65,6 +65,16 @@ export interface DirectClientRuntime {
65
65
  readonly version: typeof DIRECT_CLIENT_RUNTIME_VERSION;
66
66
  execute(expectedBase64: string, input: unknown, options: DirectClientOptions): Promise<DirectClientResult>;
67
67
  }
68
+ /** Component-only protocol faults retain raw bytes without claiming an application value.
69
+ * @since 1.3.0
70
+ */
71
+ export type ComponentClientResult = Exclude<DirectClientResult, {
72
+ kind: 'soap-fault';
73
+ }> | ComponentProtocolFault | Readonly<Omit<Extract<DirectClientResult, {
74
+ kind: 'soap-fault';
75
+ }>, 'fault'> & {
76
+ fault: Omit<DeclaredSoapFault, 'codeNode'>;
77
+ }>;
68
78
  /** The generated literal is the expected artifact; caller bytes never choose its static contract. */
69
79
  export declare const directClientRuntime: DirectClientRuntime;
70
80
  /** Same artifact/execution chain, with explicit physical dependency snapshots and component-profile reconstruction. */
@@ -72,3 +82,10 @@ export declare const componentClientRuntime: Readonly<{
72
82
  version: "checked-component-runtime-v2";
73
83
  execute: (expectedBase64: string, input: unknown, options: ComponentClientOptions) => Promise<DirectClientResult>;
74
84
  }>;
85
+ /** Additive SOAP 1.2 injection shares reconstruction and transport with SOAP 1.1.
86
+ * @since 1.3.0
87
+ */
88
+ export declare const componentSoap12ClientRuntime: Readonly<{
89
+ version: "checked-component-soap12-runtime-v1";
90
+ execute: (expectedBase64: string, input: unknown, options: ComponentClientOptions) => Promise<ComponentClientResult>;
91
+ }>;
@@ -6,7 +6,7 @@ import { types } from 'node:util';
6
6
  import { createApplicationBudget } from '../compiler/semanticBudget.js';
7
7
  import { reconstructDirectSourceCapsuleInRequest, reconstructComponentSourceCapsuleInRequest, checkDirectSourceCompatibility, DIRECT_CAPSULE_LIMITS } from '../compiler/directSourceCapsule.js';
8
8
  import { exchangeDirectSoap, exchangeComponentSoap, directSoapOptionsShape } from '../compiler/directSoap.js';
9
- import { DIRECT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
9
+ import { DIRECT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
10
10
  import { prepareSchemaSnapshotFiles } from '../loader/schemaInput.js';
11
11
  import { SchemaLoadingError } from '../loader/orderedSyntax.js';
12
12
  /** The generated literal is the expected artifact; caller bytes never choose its static contract. */
@@ -19,7 +19,14 @@ export const componentClientRuntime = Object.freeze({
19
19
  version: COMPONENT_CLIENT_RUNTIME_VERSION,
20
20
  execute: (expectedBase64, input, options) => executeClient(expectedBase64, input, options, true),
21
21
  });
22
- async function executeClient(expectedBase64, input, options, component) {
22
+ /** Additive SOAP 1.2 injection shares reconstruction and transport with SOAP 1.1.
23
+ * @since 1.3.0
24
+ */
25
+ export const componentSoap12ClientRuntime = Object.freeze({
26
+ version: COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION,
27
+ execute: (expectedBase64, input, options) => executeClient(expectedBase64, input, options, true, true),
28
+ });
29
+ async function executeClient(expectedBase64, input, options, component, allowSoap12 = false) {
23
30
  // Constant-sized entry introspection precedes budget configuration and all reads.
24
31
  const entryFailure = () => ({ kind: 'unsupported', rule: 'data-only client options required', usage: { nodes: 0, work: 0 } });
25
32
  if (!options || typeof options !== 'object' || types.isProxy(options)
@@ -121,6 +128,8 @@ async function executeClient(expectedBase64, input, options, component) {
121
128
  standardLibraries: sources.standards, recordSources: sources.records }, budget);
122
129
  if (fresh.kind !== 'reconstructed-component')
123
130
  return fresh;
131
+ if (fresh.contract.binding.version === '1.2' && !allowSoap12)
132
+ return fail('incompatible-artifact', 'SOAP 1.2 runtime binding required');
124
133
  if (fresh.selection.port !== options.soap.port || fresh.selection.service.namespace !== options.soap.service.namespace
125
134
  || fresh.selection.service.local !== options.soap.service.local)
126
135
  return fail('incompatible-artifact', 'explicit source selection mismatch');
@@ -138,8 +147,11 @@ async function executeClient(expectedBase64, input, options, component) {
138
147
  // by the original adapter; no catch-and-cast turns a failure into typed success.
139
148
  result = await exchangeDirectSoap(fresh.contract, fresh.mappings[0], input, options.soap);
140
149
  }
141
- if (!('decoded' in result))
150
+ if (!('decoded' in result)) {
151
+ if (result.kind === 'soap-fault' && !allowSoap12)
152
+ return fail('incompatible-artifact', 'SOAP 1.2 runtime binding required');
142
153
  return result;
154
+ }
143
155
  if (options.soap.signal?.aborted)
144
156
  return fail('cancelled', 'cooperative-cancellation');
145
157
  const common = { value: result.decoded.validated.value, status: result.status, admitted: false,
@@ -3,4 +3,6 @@ export declare const DIRECT_CLIENT_RUNTIME_VERSION: "checked-direct-runtime-v1";
3
3
  export declare const DIRECT_CLIENT_RUNTIME_REPAIR = "Regenerate matched direct client and capsule with the installed runtime.";
4
4
  /** Component profile never impersonates the supported direct preview protocol. */
5
5
  export declare const COMPONENT_CLIENT_RUNTIME_VERSION: "checked-component-runtime-v2";
6
+ /** SOAP 1.2 can return protocol faults without declared application values. */
7
+ export declare const COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION: "checked-component-soap12-runtime-v1";
6
8
  export declare const COMPONENT_CLIENT_RUNTIME_REPAIR = "Regenerate matched component client and source artifacts with the installed runtime.";
@@ -3,4 +3,6 @@ export const DIRECT_CLIENT_RUNTIME_VERSION = 'checked-direct-runtime-v1';
3
3
  export const DIRECT_CLIENT_RUNTIME_REPAIR = 'Regenerate matched direct client and capsule with the installed runtime.';
4
4
  /** Component profile never impersonates the supported direct preview protocol. */
5
5
  export const COMPONENT_CLIENT_RUNTIME_VERSION = 'checked-component-runtime-v2';
6
+ /** SOAP 1.2 can return protocol faults without declared application values. */
7
+ export const COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION = 'checked-component-soap12-runtime-v1';
6
8
  export const COMPONENT_CLIENT_RUNTIME_REPAIR = 'Regenerate matched component client and source artifacts with the installed runtime.';
@@ -66,6 +66,18 @@ export function generateClient(outFile, compiled) {
66
66
  const soapOptions = soapImport("IOptions");
67
67
  const soapSecurity = soapImport("ISecurity");
68
68
  const soapFactory = soapImport("createClientAsync");
69
+ const soapHttpClient = soapImport("HttpClient");
70
+ const soapExOptions = soapImport("IExOptions");
71
+ const aliases = new Map(compiled.aliases.map(alias => [alias.name, alias.tsType]));
72
+ const scalarTypes = Object.fromEntries(compiled.aliases.flatMap(alias => {
73
+ let type = alias.tsType;
74
+ const visited = new Set([alias.name]);
75
+ while (aliases.has(type) && !visited.has(type)) {
76
+ visited.add(type);
77
+ type = aliases.get(type);
78
+ }
79
+ return ["string", "number", "boolean", "bigint"].includes(type) || type.endsWith("[]") ? [[alias.name, type]] : [];
80
+ }));
69
81
  // Track whether any operation opts into streaming, so we can emit the
70
82
  // supporting runtime helpers only when they're actually used.
71
83
  let anyStream = false;
@@ -97,12 +109,14 @@ export function generateClient(outFile, compiled) {
97
109
  * Streams records from the ${m} operation of the ${clientName}.${opDocStr}${secHintsStr}
98
110
  *
99
111
  * @param args - The request arguments for the ${m} operation.
112
+ * @param options - Optional cancellation signal and total stream deadline in milliseconds.
100
113
  * @returns A promise resolving to a streaming response whose \`records\` is an
101
114
  * async iterable of ${recordTs} objects parsed from the SOAP body as
102
115
  * chunks arrive. Iteration pulls bytes on demand.
103
116
  */
104
117
  async ${m}<HeadersType = Record<string, string>>(
105
- args: ${inTs}
118
+ args: ${inTs},
119
+ options?: StreamCallOptions
106
120
  ): Promise<StreamOperationResponse<T.${recordTs}, HeadersType>> {
107
121
  return this.callStream<${inTs}, T.${recordTs}, HeadersType>(
108
122
  args,
@@ -112,7 +126,8 @@ export function generateClient(outFile, compiled) {
112
126
  ${JSON.stringify(inputElementLocal)},
113
127
  ${JSON.stringify(inputElementNs)},
114
128
  ${JSON.stringify(op.soapAction ?? "")},
115
- ${JSON.stringify(op.stream.recordPath)}
129
+ ${JSON.stringify(op.stream.recordPath)},
130
+ options
116
131
  );
117
132
  }`;
118
133
  methods.push(methodTemplate);
@@ -144,7 +159,11 @@ export function generateClient(outFile, compiled) {
144
159
  // inside this template literal (which would flag `this`, `await`, `yield`
145
160
  // outside-of-function false positives).
146
161
  const streamMethodsBlock = anyStream
147
- ? "\n" + loadRuntimeSource("clientStreamMethods.tpl.txt").replace("__SOAP_CLIENT_TYPE__", soapClient.local)
162
+ ? "\n" + loadRuntimeSource("clientStreamMethods.tpl.txt")
163
+ .replaceAll("__SOAP_CLIENT_TYPE__", soapClient.local)
164
+ .replaceAll("__SOAP_HTTP_CLIENT__", soapHttpClient.local)
165
+ .replaceAll("__SOAP_EX_OPTIONS__", soapExOptions.local)
166
+ .replaceAll("__SOAP_SECURITY__", soapSecurity.local)
148
167
  : "";
149
168
  // noinspection JSFileReferences,JSUnresolvedReference,CommaExpressionJS,JSDuplicatedDeclaration,ReservedWordAsName,JSCommentMatchesSignature,JSValidateTypes,JSIgnoredPromiseFromCall,BadExpressionStatementJS,ES6UnusedImports,JSUnnecessarySemicolon,UnreachableCodeJS,JSUnusedLocalSymbols
150
169
  const classTemplate = `// noinspection JSAnnotator
@@ -153,13 +172,14 @@ export function generateClient(outFile, compiled) {
153
172
  * Generated ${clientName} client class.
154
173
  * This class wraps the node-soap client and provides strongly-typed methods for each operation.
155
174
  */
156
- import {${soapFactory.specifier}} from "soap";
157
- import type {${soapClient.specifier}, ${soapOptions.specifier}, ${soapSecurity.specifier}} from "soap";
175
+ import {${soapFactory.specifier}${anyStream ? `, ${soapHttpClient.specifier}` : ""}} from "soap";
176
+ import type {${soapClient.specifier}, ${soapOptions.specifier}, ${soapSecurity.specifier}${anyStream ? `, ${soapExOptions.specifier}` : ""}} from "soap";
158
177
  import type * as T from "./types${suffix}";
159
178
  import type {${clientName}DataTypes} from "./utils${suffix}";
160
179
  import {${clientConstant}_DATA_TYPES} from "./utils${suffix}";${anyStream ? `
161
180
  import {parseRecords, type RecordParseSpec} from "./streamXml${suffix}";
162
- import type {StreamOperationResponse} from "./operations${suffix}";` : ""}
181
+ import type {StreamOperationResponse, StreamCallOptions} from "./operations${suffix}";
182
+ export type {StreamCallOptions} from "./operations${suffix}";` : ""}
163
183
 
164
184
  /**
165
185
  * Represents the response structure for ${clientName} operations.
@@ -214,6 +234,8 @@ export class ${clientName} {
214
234
  protected attributesKeyOut: string;
215
235
  protected client?: ${soapClient.local};
216
236
  protected dataTypes: ${clientName}DataTypes = ${clientConstant}_DATA_TYPES;
237
+ protected scalarTypes: Readonly<Record<string, string>> = ${JSON.stringify(scalarTypes)};
238
+ ${anyStream ? ` protected streamRequestOptions: ${soapExOptions.local};\n` : ""}
217
239
 
218
240
  /**
219
241
  * Creates a new ${clientName} instance.
@@ -224,17 +246,20 @@ export class ${clientName} {
224
246
  * @param [options.security] - Optional SOAP security settings to apply.
225
247
  * @param [options.attributesKeyIn] - Key name for input attribute bags (default: "$attributes").
226
248
  * @param [options.attributesKeyOut] - Key name for output attribute bags (default: "attributes").
227
- */
249
+ ${anyStream ? ` * @param [options.streamRequestOptions] - Stream HTTP options such as verified HTTPS agents, separate from WSDL options.\n` : ""} */
228
250
  constructor(options: {
229
251
  source: string,
230
252
  options?: ${soapOptions.local},
231
253
  security?: ${soapSecurity.local},
232
254
  attributesKeyIn?: string,
233
- attributesKeyOut?: string
255
+ attributesKeyOut?: string${anyStream ? `,
256
+ /** HTTP request options for stream calls, including HTTPS agents and timeouts; WSDL options stay separate. */
257
+ streamRequestOptions?: ${soapExOptions.local}` : ""}
234
258
  }) {
235
259
  this.source = options.source;
236
260
  this.attributesKeyIn = options.attributesKeyIn ?? "$attributes";
237
261
  this.attributesKeyOut = options.attributesKeyOut ?? "attributes";
262
+ ${anyStream ? ` this.streamRequestOptions = options.streamRequestOptions ?? {};\n` : ""}
238
263
  if (options.options) {
239
264
  this.options = options.options;
240
265
  }
@@ -402,13 +427,28 @@ ${methodsBody}
402
427
  *
403
428
  * @param node - The raw SOAP response node.
404
429
  * @param typeName - Optional metadata key to guide deserialization.
430
+ * @param nonNillable - Whether parent schema metadata rules out a nil child, permitting empty-object recovery.
405
431
  * @returns The deserialized object matching your DTO shape.
432
+ * @throws TypeError for invalid boolean, numeric, or bigint lexical values.
406
433
  */
407
- protected fromSoapResult(node: any, typeName?: string): any {
408
- // Pass through null/undefined unchanged
434
+ protected fromSoapResult(node: any, typeName?: string, nonNillable = false): any {
435
+ // @why node-soap collapses empty complex elements to null; only a declared non-nillable child permits recovering its object DTO.
436
+ // @constraints Nullable children and unknown/custom metadata retain null, including explicit xsi:nil responses.
437
+ if (node === null && nonNillable && typeName && Object.hasOwn(this.dataTypes.Attributes, typeName)
438
+ && Object.keys(this.dataTypes.ChildrenTypes[typeName] ?? {}).length === 0) return {};
409
439
  if (node == null) {
410
440
  return node;
411
441
  }
442
+ if (typeName && Object.hasOwn(this.scalarTypes, typeName)) typeName = this.scalarTypes[typeName];
443
+ if (typeName?.endsWith("[]")) {
444
+ let itemType = typeName.slice(0, -2);
445
+ if (itemType.startsWith("(") && itemType.endsWith(")")) itemType = itemType.slice(1, -1);
446
+ if (typeof node === "string") {
447
+ const lexical = node.replace(/^[\\t\\r\\n ]+|[\\t\\r\\n ]+$/g, "");
448
+ return lexical === "" ? [] : lexical.split(/[\\t\\r\\n ]+/).map(item => this.fromSoapResult(item, itemType));
449
+ }
450
+ if (Array.isArray(node)) return node.map(item => this.fromSoapResult(item, itemType));
451
+ }
412
452
  const childrenTypes: Readonly<Record<string, string>> = (typeName && this.dataTypes?.ChildrenTypes?.[typeName]) || {};
413
453
  const attributeTypes: Readonly<Record<string, string>> = (typeName && this.dataTypes?.AttributeTypes?.[typeName]) || {};
414
454
  // XML boolean attributes arrive as lexical strings even when element booleans are parsed by node-soap.
@@ -418,8 +458,22 @@ ${methodsBody}
418
458
  if (lexical === "false" || lexical === "0") return false;
419
459
  throw new TypeError("Invalid XML boolean response value");
420
460
  }
461
+ if (typeof node === "string" && (typeName === "number" || typeName === "bigint")) {
462
+ const lexical = node.replace(/^[\\t\\r\\n ]+|[\\t\\r\\n ]+$/g, "");
463
+ if (typeName === "bigint") {
464
+ if (!/^[+-]?\\d+$/.test(lexical)) throw new TypeError("Invalid XML bigint response value");
465
+ return BigInt(lexical);
466
+ }
467
+ if (!/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(lexical)) {
468
+ throw new TypeError("Invalid XML numeric response value");
469
+ }
470
+ const number = Number(lexical);
471
+ if (!Number.isFinite(number)) throw new TypeError("Non-finite XML numeric response value");
472
+ return number;
473
+ }
421
474
  // A simple-content complex type retains its object shape when no attributes are present on the wire.
422
475
  if (typeof node !== "object") {
476
+ if (node === "" && typeName && Object.hasOwn(this.dataTypes.Attributes, typeName) && Object.keys(childrenTypes).length === 0) return {};
423
477
  return childrenTypes.$value ? {$value: this.fromSoapResult(node, childrenTypes.$value)} : node;
424
478
  }
425
479
  // Recursively process array elements
@@ -429,11 +483,12 @@ ${methodsBody}
429
483
 
430
484
  // Get child type mapping for recursive processing with correct types
431
485
  const repeatedElements: readonly string[] = (typeName && this.dataTypes?.RepeatedElements?.[typeName]) || [];
486
+ const nonNillableElements: readonly string[] = (typeName && this.dataTypes?.NonNillableElements?.[typeName]) || [];
432
487
  const result: any = {};
433
488
 
434
489
  // Preserve text content for mixed XML elements
435
- if ("$value" in node) {
436
- result.$value = (node as any).$value;
490
+ if ("$value" in node && (childrenTypes.$value || node.$value !== "")) {
491
+ result.$value = this.fromSoapResult(node.$value, childrenTypes.$value);
437
492
  }
438
493
 
439
494
  // Extract attributes from various node-soap attribute containers
@@ -455,10 +510,11 @@ ${methodsBody}
455
510
  // Recursively convert child elements with their specific type info
456
511
  const childType: string | undefined = childrenTypes[k];
457
512
  // node-soap can return a scalar when repetition is declared on a wrapping sequence (#141).
458
- const value = v != null && !Array.isArray(v) && repeatedElements.includes(k) ? [v] : v;
459
- result[k] = Array.isArray(value)
460
- ? value.map(node => this.fromSoapResult(node, childType))
461
- : this.fromSoapResult(value, childType);
513
+ const repeated = repeatedElements.includes(k);
514
+ const value = v != null && !Array.isArray(v) && repeated ? [v] : v;
515
+ result[k] = repeated && Array.isArray(value)
516
+ ? value.map(node => this.fromSoapResult(node, childType, nonNillableElements.includes(k)))
517
+ : this.fromSoapResult(value, childType, nonNillableElements.includes(k));
462
518
  }
463
519
 
464
520
  return result;
@@ -1,5 +1,5 @@
1
1
  import { directLimit } from '../compiler/directExecutionPolicy.js';
2
- import { DIRECT_CLIENT_RUNTIME_VERSION, DIRECT_CLIENT_RUNTIME_REPAIR, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_REPAIR } from './directClientVersion.js';
2
+ import { DIRECT_CLIENT_RUNTIME_VERSION, DIRECT_CLIENT_RUNTIME_REPAIR, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_REPAIR, COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
3
3
  /** Native-plan generator consumed by the opt-in checked pipeline. */
4
4
  import { checkSemanticBudget } from '../compiler/semanticBudget.js';
5
5
  import { writeFileSync, mkdirSync } from 'node:fs';
@@ -170,12 +170,15 @@ export async function generateComponentClient(expected, signal) {
170
170
  ...response.declarations, `export type Response = ${response.type};`,
171
171
  ...faults.flatMap(({ shape }, index) => [...shape.declarations, `export type Fault_${index} = ${shape.type};`])
172
172
  ];
173
+ const soap12 = expected.contract.binding.version === '1.2';
174
+ const runtimeVersion = soap12 ? COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION : COMPONENT_CLIENT_RUNTIME_VERSION;
173
175
  const lines = [
174
- '// Checked component client; inject checkedComponentRuntimeV1 from the package root.',
176
+ `// Checked component client; inject ${soap12 ? 'checkedComponentRuntimeV2' : 'checkedComponentRuntimeV1'} from the package root.`,
175
177
  '// Scalar facets, ordered occurrence correlation and wildcard membership remain native runtime duties.',
176
178
  ...declarations,
177
179
  'export interface Usage {readonly nodes: number; readonly work: number}',
178
180
  'export interface Name {readonly namespace: string; readonly local: string}',
181
+ ...(soap12 ? ['export interface Soap12Fault {readonly version: "1.2"; readonly code: Name; readonly subcodes: readonly Name[]; readonly reasons: readonly {readonly lang: string; readonly text: string}[]; readonly string: string; readonly node?: string; readonly role?: string}'] : []),
179
182
  'export interface Options {',
180
183
  ' readonly localSource: string; readonly fileRoots: readonly string[]; readonly capsule: Uint8Array;',
181
184
  ' readonly sourceResources: readonly {readonly uri: string; readonly file: string; readonly canonicalUri?: string}[]; readonly sourceOrigins: readonly string[];',
@@ -190,26 +193,28 @@ export async function generateComponentClient(expected, signal) {
190
193
  '}',
191
194
  'export interface Failure {readonly kind: "incompatible-artifact" | "unsupported" | "missing-authority" | "resource-limit" | "cancelled" | "inconclusive" | "invalid-schema" | "scalar-invalid" | "value-invalid" | "xml-invalid" | "mismatch" | "transport" | "protocol";',
192
195
  ' readonly rule: string; readonly repair?: string; readonly phase?: "input" | "response"; readonly usage: Usage}',
193
- 'export interface NativeSuccess {readonly kind: "soap-response" | "soap-fault"; readonly value: unknown; readonly admitted: false;',
196
+ `export interface NativeSuccess {readonly kind: "soap-response" | "soap-fault"; readonly value${soap12 ? '?' : ''}: unknown; readonly admitted: false;`,
194
197
  ' readonly status: number; readonly usage: Usage; readonly responseBytes: Uint8Array;',
195
- ' readonly fault?: {readonly name: string; readonly code: Name; readonly string: string; readonly actor?: string}}',
196
- `export interface Runtime {readonly version: ${literal(COMPONENT_CLIENT_RUNTIME_VERSION)}; execute(expectedBase64: string, input: unknown, options: Options, version: ${literal(COMPONENT_CLIENT_RUNTIME_VERSION)}): Promise<Failure | NativeSuccess>}`,
198
+ ` readonly fault?: {readonly name${soap12 ? '?' : ''}: string; readonly code: Name; readonly string: string; readonly actor?: string}}`,
199
+ `export interface Runtime {readonly version: ${literal(runtimeVersion)}; execute(expectedBase64: string, input: unknown, options: Options, version: ${literal(runtimeVersion)}): Promise<Failure | NativeSuccess>}`,
197
200
  'type Common = {readonly admitted: false; readonly status: number; readonly usage: Usage; readonly responseBytes: Uint8Array};',
198
201
  'export type Result = Failure | (Common & {readonly kind: "soap-response"; readonly value: Response})',
199
202
  ...faults.map(({ mapping }, index) => {
200
203
  if (mapping.direction.kind !== 'fault')
201
204
  throw new TypeError('Native fault mapping required');
202
- return ` | (Common & {readonly kind: "soap-fault"; readonly value: Fault_${index}; readonly fault: {readonly name: ${literal(mapping.direction.name)}; readonly code: Name; readonly string: string; readonly actor?: string}})`;
205
+ return soap12 ? ` | (Common & {readonly kind: "soap-fault"; readonly value: Fault_${index}; readonly fault: Soap12Fault & {readonly name: ${literal(mapping.direction.name)}}})`
206
+ : ` | (Common & {readonly kind: "soap-fault"; readonly value: Fault_${index}; readonly fault: {readonly name: ${literal(mapping.direction.name)}; readonly code: Name; readonly string: string; readonly actor?: string}})`;
203
207
  }),
204
- ...(faults.length ? [] : [' | (Common & {readonly kind: "soap-fault"; readonly value: never; readonly fault: {readonly name: never; readonly code: Name; readonly string: string; readonly actor?: string}})']),
208
+ ...(soap12 ? [' | (Common & {readonly kind: "soap-fault"; readonly value?: never; readonly fault: Soap12Fault & {readonly name?: never}})']
209
+ : faults.length ? [] : [' | (Common & {readonly kind: "soap-fault"; readonly value: never; readonly fault: {readonly name: never; readonly code: Name; readonly string: string; readonly actor?: string}})']),
205
210
  ';',
206
211
  `export const expectedCapsuleBase64 = ${literal(Buffer.from(capsule.bytes).toString('base64'))};`,
207
212
  `export const selection = ${literal(JSON.stringify({ selector: expected.contract.selector, ...expected.selection }))};`,
208
213
  'export function createClient(runtime: Runtime) {',
209
214
  ' return Object.freeze({',
210
215
  ` [${literal(expected.contract.selector.operation)}]: async (input: Request, options: Options): Promise<Result> => {`,
211
- ` if (runtime.version !== ${literal(COMPONENT_CLIENT_RUNTIME_VERSION)}) return {kind: "incompatible-artifact", rule: "generated-runtime-version", repair: ${literal(COMPONENT_CLIENT_RUNTIME_REPAIR)}, usage: {nodes: 0, work: 0}};`,
212
- ` return await runtime.execute(expectedCapsuleBase64, input, options, ${literal(COMPONENT_CLIENT_RUNTIME_VERSION)}) as Result;`,
216
+ ` if (runtime.version !== ${literal(runtimeVersion)}) return {kind: "incompatible-artifact", rule: "generated-runtime-version", repair: ${literal(COMPONENT_CLIENT_RUNTIME_REPAIR)}, usage: {nodes: 0, work: 0}};`,
217
+ ` return await runtime.execute(expectedCapsuleBase64, input, options, ${literal(runtimeVersion)}) as Result;`,
213
218
  ' },', ' });', '}', '',
214
219
  ];
215
220
  const source = composeGeneratedSource(lines.join('\n'), resolvePreamble('direct-client'));