@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.
- package/README.md +22 -3
- package/dist/checkedPlanning.js +27 -4
- package/dist/cli.js +17 -1
- package/dist/client/checkedDirectRuntime.d.ts +11 -1
- package/dist/client/checkedDirectRuntime.js +19 -2
- package/dist/client/directClientRuntime.d.ts +18 -1
- package/dist/client/directClientRuntime.js +15 -3
- package/dist/client/directClientVersion.d.ts +2 -0
- package/dist/client/directClientVersion.js +2 -0
- package/dist/client/generateClient.js +72 -16
- package/dist/client/generateDirectClient.js +14 -9
- package/dist/client/generateOperations.js +30 -11
- package/dist/client/generateUtils.js +5 -1
- package/dist/compiler/canonicalGraph.d.ts +4 -15
- package/dist/compiler/canonicalGraph.js +1 -8
- package/dist/compiler/componentRecordProjection.d.ts +3 -0
- package/dist/compiler/componentRecordProjection.js +10 -0
- package/dist/compiler/declarationIdentity.d.ts +22 -0
- package/dist/compiler/declarationIdentity.js +9 -0
- package/dist/compiler/directCodec.d.ts +22 -2
- package/dist/compiler/directCodec.js +40 -7
- package/dist/compiler/directOperation.d.ts +28 -5
- package/dist/compiler/directOperation.js +84 -14
- package/dist/compiler/directSoap.d.ts +40 -10
- package/dist/compiler/directSoap.js +151 -18
- package/dist/compiler/directSoapSource.js +3 -2
- package/dist/compiler/directSourceCapsule.js +69 -4
- package/dist/compiler/directStructure.js +6 -2
- package/dist/compiler/directStructurePlan.d.ts +10 -1
- package/dist/compiler/directStructurePlan.js +27 -7
- package/dist/compiler/directValues.d.ts +15 -3
- package/dist/compiler/directValues.js +20 -4
- package/dist/compiler/legacyDeclarations.d.ts +26 -0
- package/dist/compiler/legacyDeclarations.js +144 -0
- package/dist/compiler/projectionScalar.d.ts +18 -1
- package/dist/compiler/projectionScalar.js +47 -13
- package/dist/compiler/rpcStringValues.d.ts +30 -0
- package/dist/compiler/rpcStringValues.js +196 -0
- package/dist/compiler/rpcTypedValues.d.ts +48 -0
- package/dist/compiler/rpcTypedValues.js +190 -0
- package/dist/compiler/schemaCompiler.js +44 -109
- package/dist/compiler/semanticCatalog.d.ts +2 -2
- package/dist/compiler/soapHeaders.d.ts +10 -4
- package/dist/compiler/soapHeaders.js +28 -9
- package/dist/gateway/generateCheckedGateway.js +26 -6
- package/dist/gateway/generateComposedGateway.d.ts +16 -0
- package/dist/gateway/generateComposedGateway.js +65 -0
- package/dist/gateway/generateGateway.js +1 -0
- package/dist/gateway/generators.js +19 -4
- package/dist/gateway/helpers.d.ts +1 -0
- package/dist/gateway/helpers.js +15 -0
- package/dist/generation/componentShape.js +25 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/loader/orderedSyntax.d.ts +4 -0
- package/dist/loader/orderedSyntax.js +18 -4
- package/dist/multiServicePipeline.d.ts +41 -0
- package/dist/multiServicePipeline.js +287 -0
- package/dist/openapi/composeOpenAPI.d.ts +29 -0
- package/dist/openapi/composeOpenAPI.js +322 -0
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +2 -0
- package/docs/agent-skill.md +10 -1
- package/docs/api-reference.md +127 -9
- package/docs/checked-gateway.md +10 -0
- package/docs/checked-generation.md +11 -5
- package/docs/cli-reference.md +25 -5
- package/docs/concepts.md +4 -5
- package/docs/configuration.md +10 -3
- package/docs/corpus.md +1 -1
- package/docs/gateway-guide.md +104 -27
- package/docs/generated-code.md +12 -6
- package/docs/migration-playbook.md +4 -0
- package/docs/migration.md +134 -6
- package/docs/output-anatomy.md +20 -17
- package/docs/production.md +7 -5
- package/docs/start-here.md +12 -2
- package/docs/supported-patterns.md +23 -0
- package/docs/testing.md +2 -0
- package/docs/troubleshooting.md +3 -1
- package/llms.txt +9 -1
- package/package.json +2 -2
- package/src/runtime/clientStreamMethods.tpl.txt +156 -148
- package/src/runtime/operationsStreamHelper.tpl.txt +6 -6
- 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
|
|
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) |
|
|
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
|
package/dist/checkedPlanning.js
CHANGED
|
@@ -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
|
-
|
|
170
|
-
|
|
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 {
|
|
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
|
-
|
|
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")
|
|
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
|
-
//
|
|
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
|
|
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
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
`export interface Runtime {readonly version: ${literal(
|
|
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)}
|
|
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
|
-
...(
|
|
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(
|
|
212
|
-
` return await runtime.execute(expectedCapsuleBase64, input, options, ${literal(
|
|
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'));
|