@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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Generates a fully-typed operations interface for the SOAP client.
|
|
5
5
|
* This standalone interface enables mocking and testing without importing
|
|
6
6
|
* the concrete SOAP client class or its runtime dependencies.
|
|
7
|
+
* @since Unreleased
|
|
7
8
|
*/
|
|
8
9
|
import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
|
|
9
10
|
import { deriveClientName, pascal } from "../util/tools.js";
|
|
@@ -31,18 +32,25 @@ export function generateOperations(outFile, compiled) {
|
|
|
31
32
|
.map(line => line.replace(/\*\//g, "*\\/"));
|
|
32
33
|
// Collect type names used in method signatures for the import statement
|
|
33
34
|
const importedTypes = new Set();
|
|
35
|
+
const operationTypes = compiled.operations.map(operation => ({ operation,
|
|
36
|
+
input: operation.inputTypeName ?? (operation.inputElement ? pascal(operation.inputElement.local) : undefined),
|
|
37
|
+
output: operation.outputTypeName ?? (operation.outputElement ? pascal(operation.outputElement.local) : undefined),
|
|
38
|
+
}));
|
|
39
|
+
const reservedTypes = new Set(["Promise", "AsyncIterable", "AbortSignal", "StreamCallOptions", "StreamOperationResponse", `${clientName}Operations`]);
|
|
40
|
+
// @constraints Qualify model references when their public names overlap generated helpers or native transport types.
|
|
41
|
+
const namespaceModels = operationTypes.some(({ operation, input, output }) => [input, output, operation.stream?.recordTypeName].some(name => name !== undefined && reservedTypes.has(name)));
|
|
42
|
+
const modelType = (name) => namespaceModels ? `T.${name}` : name;
|
|
34
43
|
const methods = [];
|
|
35
44
|
// Does any operation opt into streaming? If so we also emit the
|
|
36
45
|
// StreamOperationResponse helper type below.
|
|
37
46
|
let anyStream = false;
|
|
38
|
-
for (const op of
|
|
39
|
-
const inTypeName = op.inputTypeName ?? (op.inputElement ? pascal(op.inputElement.local) : undefined);
|
|
40
|
-
const outTypeName = op.outputTypeName ?? (op.outputElement ? pascal(op.outputElement.local) : undefined);
|
|
47
|
+
for (const { operation: op, input: inTypeName, output: outTypeName } of operationTypes) {
|
|
41
48
|
if (!inTypeName && !outTypeName) {
|
|
42
49
|
continue;
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
const
|
|
51
|
+
// @constraints WSDL DTOs may be named Record and shadow the built-in utility type.
|
|
52
|
+
const inTs = inTypeName ? modelType(inTypeName) : "{[key: string]: unknown}";
|
|
53
|
+
const outTs = outTypeName ? modelType(outTypeName) : "unknown";
|
|
46
54
|
if (inTypeName)
|
|
47
55
|
importedTypes.add(inTypeName);
|
|
48
56
|
if (outTypeName)
|
|
@@ -56,8 +64,9 @@ export function generateOperations(outFile, compiled) {
|
|
|
56
64
|
const recordTs = op.stream.recordTypeName;
|
|
57
65
|
importedTypes.add(recordTs);
|
|
58
66
|
methods.push(`${docBlock} ${op.name}(\n` +
|
|
59
|
-
` args: ${inTs}
|
|
60
|
-
`
|
|
67
|
+
` args: ${inTs},\n` +
|
|
68
|
+
` options?: StreamCallOptions\n` +
|
|
69
|
+
` ): Promise<StreamOperationResponse<${modelType(recordTs)}>>;\n`);
|
|
61
70
|
}
|
|
62
71
|
else {
|
|
63
72
|
methods.push(`${docBlock} ${op.name}(\n` +
|
|
@@ -68,16 +77,26 @@ export function generateOperations(outFile, compiled) {
|
|
|
68
77
|
// Build sorted import list for deterministic output
|
|
69
78
|
const sortedImports = Array.from(importedTypes).sort();
|
|
70
79
|
const typeImport = sortedImports.length > 0
|
|
71
|
-
?
|
|
80
|
+
? namespaceModels
|
|
81
|
+
? `import type * as T from "./types${suffix}";\n\n`
|
|
82
|
+
: `import type {\n${sortedImports.map((t) => ` ${t},`).join("\n")}\n} from "./types${suffix}";\n\n`
|
|
72
83
|
: "";
|
|
73
84
|
// Emit the StreamOperationResponse helper type iff at least one operation
|
|
74
85
|
// is stream-configured. Kept inside operations.ts so that mocks and gateway
|
|
75
86
|
// code can import it without pulling in the SOAP runtime. The raw template
|
|
76
87
|
// lives in a sibling .tpl file so the IDE does not try to parse embedded
|
|
77
|
-
// TypeScript
|
|
78
|
-
// JavaScript assignment expressions.
|
|
88
|
+
// TypeScript generic defaults as JavaScript assignment expressions.
|
|
79
89
|
const streamHelper = anyStream
|
|
80
|
-
?
|
|
90
|
+
? `/**
|
|
91
|
+
* Per-call stream cancellation and total request/response deadline.
|
|
92
|
+
* @since Unreleased
|
|
93
|
+
*/
|
|
94
|
+
export interface StreamCallOptions {
|
|
95
|
+
signal?: AbortSignal;
|
|
96
|
+
timeoutMs?: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
` + loadRuntimeSource("operationsStreamHelper.tpl.txt").replace(/__CLIENT_NAME__/g, clientName)
|
|
81
100
|
: "";
|
|
82
101
|
const content = `/**
|
|
83
102
|
* Typed operations interface for the ${clientName} service.
|
|
@@ -48,7 +48,9 @@ export function generateUtils(outFile, compiled) {
|
|
|
48
48
|
return max === "unbounded" || (typeof max === "number" && max > 1);
|
|
49
49
|
}).sort(),
|
|
50
50
|
]));
|
|
51
|
-
const metas = JSON.stringify({ Attributes: attrSpec, AttributeTypes: compiled.meta.attrType ?? {}, ChildrenTypes: childType, RepeatedElements: repeatedElements
|
|
51
|
+
const metas = JSON.stringify({ Attributes: attrSpec, AttributeTypes: compiled.meta.attrType ?? {}, ChildrenTypes: childType, RepeatedElements: repeatedElements,
|
|
52
|
+
NonNillableElements: Object.fromEntries(Object.entries(compiled.meta.propMeta ?? {}).sort(([a], [b]) => a.localeCompare(b))
|
|
53
|
+
.map(([name, properties]) => [name, Object.keys(properties).filter(key => properties[key].nillable === false).sort()])) }, null, 2);
|
|
52
54
|
const dataTypes = `/**
|
|
53
55
|
* ${clientName} WSDL data types for proper serialization/deserialization.
|
|
54
56
|
* Used to distinguish between XML attributes and elements during conversion.
|
|
@@ -62,6 +64,8 @@ export interface ${clientName}DataTypes {
|
|
|
62
64
|
ChildrenTypes: Record<string, Readonly<Record<string, string>>>;
|
|
63
65
|
/** Maps type names to repeated child properties; optional for custom metadata compatibility. */
|
|
64
66
|
RepeatedElements?: Record<string, readonly string[]>;
|
|
67
|
+
/** Distinguishes empty complex nodes collapsed to null by node-soap from declared nullable children. */
|
|
68
|
+
NonNillableElements?: Record<string, readonly string[]>;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
export const ${clientConstant}_DATA_TYPES: ${clientName}DataTypes = ${metas} as const;\n`;
|
|
@@ -1,19 +1,11 @@
|
|
|
1
|
+
/** Internal, language-neutral S04 contract. S05 resolves and composes these references. */
|
|
2
|
+
import type { Identity, NodeId, SymbolRole } from "./declarationIdentity.js";
|
|
3
|
+
export { globalId, scopedId } from "./declarationIdentity.js";
|
|
4
|
+
export type { Identity, NodeId, SymbolRole } from "./declarationIdentity.js";
|
|
1
5
|
import type { ExpandedName, SyntaxAttribute, SyntaxElement, SyntaxSource } from "../loader/orderedSyntax.js";
|
|
2
6
|
export declare const GRAPH_MODEL: "xsd10-faithful-v1";
|
|
3
7
|
export declare const DEFAULT_GRAPH_NODES = 100000;
|
|
4
8
|
export declare const isBuiltinType: (local: string) => boolean;
|
|
5
|
-
export type NodeId = string;
|
|
6
|
-
export type SymbolRole = "element" | "attribute" | "type" | "group" | "attributeGroup" | "message" | "portType" | "binding" | "service";
|
|
7
|
-
export type Identity = Readonly<{
|
|
8
|
-
kind: "global";
|
|
9
|
-
role: SymbolRole;
|
|
10
|
-
name: ExpandedName;
|
|
11
|
-
} | {
|
|
12
|
-
kind: "scoped";
|
|
13
|
-
owner: NodeId;
|
|
14
|
-
path: string;
|
|
15
|
-
role: string;
|
|
16
|
-
}>;
|
|
17
9
|
export type GraphContext = Readonly<{
|
|
18
10
|
namespaces: Readonly<Record<string, string>>;
|
|
19
11
|
baseUri: string;
|
|
@@ -190,8 +182,5 @@ export declare class GraphError extends Error {
|
|
|
190
182
|
readonly source?: SyntaxSource | undefined;
|
|
191
183
|
constructor(category: "invalid-schema" | "resource-limit", message: string, source?: SyntaxSource | undefined);
|
|
192
184
|
}
|
|
193
|
-
export declare function globalId(role: SymbolRole, name: ExpandedName): NodeId;
|
|
194
|
-
export declare function scopedId(owner: NodeId, path: string, role: string): NodeId;
|
|
195
185
|
export declare function wsdlReferenceRoles(node: SyntaxElement): Record<string, SymbolRole>;
|
|
196
186
|
export declare function deepFreeze<T>(value: T): T;
|
|
197
|
-
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
export { globalId, scopedId } from "./declarationIdentity.js";
|
|
3
2
|
import { WSDL_NAMESPACE } from "../loader/orderedSyntax.js";
|
|
4
3
|
export const GRAPH_MODEL = "xsd10-faithful-v1";
|
|
5
4
|
export const DEFAULT_GRAPH_NODES = 100_000;
|
|
@@ -15,12 +14,6 @@ export class GraphError extends Error {
|
|
|
15
14
|
this.name = "GraphError";
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
|
-
export function globalId(role, name) {
|
|
19
|
-
return JSON.stringify(["global", role, name.namespace, name.local]);
|
|
20
|
-
}
|
|
21
|
-
export function scopedId(owner, path, role) {
|
|
22
|
-
return "scoped:" + createHash("sha256").update(JSON.stringify([owner, path, role])).digest("hex");
|
|
23
|
-
}
|
|
24
17
|
export function wsdlReferenceRoles(node) {
|
|
25
18
|
if (node.name.namespace !== WSDL_NAMESPACE)
|
|
26
19
|
return {};
|
|
@@ -101,6 +101,9 @@ export declare function decodeComponentRecordXml(outer: DirectOperationContract,
|
|
|
101
101
|
signal?: AbortSignal;
|
|
102
102
|
limits?: DirectXmlLimits;
|
|
103
103
|
uri?: string;
|
|
104
|
+
componentResponseEncoding?: {
|
|
105
|
+
charset?: string;
|
|
106
|
+
};
|
|
104
107
|
selectPayload?: (root: SyntaxElement) => {
|
|
105
108
|
payload: SyntaxElement;
|
|
106
109
|
mapping: ComponentRecordProjection | ComponentValueMapping;
|
|
@@ -46,6 +46,8 @@ export function createComponentRecordProjection(outer, companion, supplied, opti
|
|
|
46
46
|
try {
|
|
47
47
|
const result = budget.guard(() => {
|
|
48
48
|
check();
|
|
49
|
+
if ('parts' in outer.output || 'parts' in companion.output)
|
|
50
|
+
return failure('unsupported', 'record-projection-requires-document-output');
|
|
49
51
|
if (outer.basis !== 'native-component-premises-v1' || companion.basis !== 'native-component-premises-v1'
|
|
50
52
|
|| outer.correspondence !== 'dated-XSD-2004-components-v1' || companion.correspondence !== 'dated-XSD-2004-components-v1')
|
|
51
53
|
stop('component-record-source-authority');
|
|
@@ -75,6 +77,8 @@ export function createComponentRecordProjection(outer, companion, supplied, opti
|
|
|
75
77
|
if (mapping.kind !== 'component-mapping')
|
|
76
78
|
return mapping;
|
|
77
79
|
const projection = inspectComponentValueMapping(mapping, companion).projection;
|
|
80
|
+
if (!projection)
|
|
81
|
+
return failure('unsupported', 'record-projection-requires-document-mapping');
|
|
78
82
|
const records = new Map(projection.records.map(record => [record.type.id, record]));
|
|
79
83
|
if (association && !Object.hasOwn(requested, 'recordPath')) {
|
|
80
84
|
const candidates = [];
|
|
@@ -226,6 +230,8 @@ export function inspectComponentRecordValue(value, projection) {
|
|
|
226
230
|
*/
|
|
227
231
|
export function validateComponentRecordApplication(projection, application, options = {}) {
|
|
228
232
|
const owned = inspectComponentRecordProjection(projection), { context } = inspectDirectOperation(owned.companion);
|
|
233
|
+
if ('parts' in owned.mapping.root)
|
|
234
|
+
throw new TypeError('Record projection requires a document mapping');
|
|
229
235
|
return expandProjectionValue(context, owned.mapping.root.element, application, options);
|
|
230
236
|
}
|
|
231
237
|
/** Decode once and validate the declared wrapper plus its explicitly selected companion payload.
|
|
@@ -253,6 +259,8 @@ export function decodeComponentRecordXml(outer, projection, bytes, options = {})
|
|
|
253
259
|
return assessObservedComponentPayload(outer, mapping, observation, selected.payload, options);
|
|
254
260
|
}
|
|
255
261
|
const wrapper = selected.payload;
|
|
262
|
+
if ('parts' in outer.output)
|
|
263
|
+
throw new ScopeStop('unsupported', 'record-projection-requires-document-output');
|
|
256
264
|
if (!same(wrapper.name, outer.output.element.name))
|
|
257
265
|
throw new ScopeStop('mismatch', 'record-wrapper-root');
|
|
258
266
|
if (wrapper.attributes.length || wrapper.children.some(node => node.kind === 'text' && /[^\t\r\n ]/.test(node.value)))
|
|
@@ -284,6 +292,8 @@ export function decodeComponentRecordXml(outer, projection, bytes, options = {})
|
|
|
284
292
|
const decoded = assessObservedComponentPayload(owned.companion, owned.mapping, observation, payload, options);
|
|
285
293
|
if (decoded.kind !== 'component-decoded')
|
|
286
294
|
return decoded;
|
|
295
|
+
if (decoded.validated.kind !== 'projection-value')
|
|
296
|
+
throw new TypeError('Record projection requires a document value receipt');
|
|
287
297
|
const recordCount = countRecords(observation, payload, projection.spec.recordPath, check);
|
|
288
298
|
const validated = Object.freeze({ kind: 'component-record-value', scope: 'records-with-declared-wrapper-scope',
|
|
289
299
|
admitted: false, value: decoded.validated.value, recordCount, companion: decoded.validated });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type NodeId = string;
|
|
2
|
+
export type SymbolRole = "element" | "attribute" | "type" | "group" | "attributeGroup" | "message" | "portType" | "binding" | "service";
|
|
3
|
+
export type Identity = Readonly<{
|
|
4
|
+
kind: "global";
|
|
5
|
+
role: SymbolRole;
|
|
6
|
+
name: Readonly<{
|
|
7
|
+
namespace: string;
|
|
8
|
+
local: string;
|
|
9
|
+
}>;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "scoped";
|
|
12
|
+
owner: NodeId;
|
|
13
|
+
path: string;
|
|
14
|
+
role: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Encode XML symbol identity independently of source location and emitted language names. */
|
|
17
|
+
export declare function globalId(role: SymbolRole, name: Readonly<{
|
|
18
|
+
namespace: string;
|
|
19
|
+
local: string;
|
|
20
|
+
}>): NodeId;
|
|
21
|
+
/** Encode a declaration within its owner's scope using the caller's structural path convention. */
|
|
22
|
+
export declare function scopedId(owner: NodeId, path: string, role: string): NodeId;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** Encode XML symbol identity independently of source location and emitted language names. */
|
|
3
|
+
export function globalId(role, name) {
|
|
4
|
+
return JSON.stringify(["global", role, name.namespace, name.local]);
|
|
5
|
+
}
|
|
6
|
+
/** Encode a declaration within its owner's scope using the caller's structural path convention. */
|
|
7
|
+
export function scopedId(owner, path, role) {
|
|
8
|
+
return "scoped:" + createHash("sha256").update(JSON.stringify([owner, path, role])).digest("hex");
|
|
9
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { DirectOperationContract } from './directOperation.js';
|
|
|
2
2
|
import type { DirectValueMapping, DirectValueReceipt, DirectValueFailure, DirectExpandedValue, ComponentValueMapping, ProjectionExpandedValue, ProjectionValueReceipt } from './directValues.js';
|
|
3
3
|
import type { SyntaxElement, SyntaxSource } from '../loader/orderedSyntax.js';
|
|
4
4
|
import type { ResolverUsage } from './semanticBudget.js';
|
|
5
|
+
import type { RpcTypedValue } from './rpcTypedValues.js';
|
|
5
6
|
export interface DirectXmlLimits {
|
|
6
7
|
readonly maxBytes?: number;
|
|
7
8
|
readonly maxDepth?: number;
|
|
@@ -53,7 +54,7 @@ export interface ComponentEncodedXml {
|
|
|
53
54
|
readonly admitted: false;
|
|
54
55
|
readonly encoding: 'utf-8';
|
|
55
56
|
readonly bytes: Uint8Array;
|
|
56
|
-
readonly expanded: ProjectionExpandedValue;
|
|
57
|
+
readonly expanded: ProjectionExpandedValue | RpcExpandedValue;
|
|
57
58
|
readonly mapping: ComponentValueMapping;
|
|
58
59
|
readonly usage: ResolverUsage;
|
|
59
60
|
}
|
|
@@ -61,10 +62,23 @@ export interface ComponentDecodedXml {
|
|
|
61
62
|
readonly kind: 'component-decoded';
|
|
62
63
|
readonly admitted: false;
|
|
63
64
|
readonly payload: SyntaxElement;
|
|
64
|
-
readonly validated: ProjectionValueReceipt;
|
|
65
|
+
readonly validated: ProjectionValueReceipt | RpcValueReceipt;
|
|
65
66
|
readonly mapping: ComponentValueMapping;
|
|
66
67
|
readonly usage: ResolverUsage;
|
|
67
68
|
}
|
|
69
|
+
export interface RpcValueReceipt {
|
|
70
|
+
readonly kind: 'rpc-string-value' | 'rpc-typed-value';
|
|
71
|
+
readonly admitted: false;
|
|
72
|
+
readonly value: RpcTypedValue;
|
|
73
|
+
readonly usage: ResolverUsage;
|
|
74
|
+
}
|
|
75
|
+
export interface RpcExpandedValue {
|
|
76
|
+
readonly kind: 'rpc-string-expanded' | 'rpc-typed-expanded';
|
|
77
|
+
readonly admitted: false;
|
|
78
|
+
readonly payload: SyntaxElement;
|
|
79
|
+
readonly validated: RpcValueReceipt;
|
|
80
|
+
readonly usage: ResolverUsage;
|
|
81
|
+
}
|
|
68
82
|
export interface ComponentPayloadSelection {
|
|
69
83
|
readonly payload: SyntaxElement;
|
|
70
84
|
readonly mapping: ComponentValueMapping;
|
|
@@ -88,6 +102,9 @@ export declare function encodeComponentXml(contract: DirectOperationContract, ma
|
|
|
88
102
|
*/
|
|
89
103
|
export declare function observeComponentXml(contract: DirectOperationContract, mapping: unknown, bytes: Uint8Array, options?: Options & Readonly<{
|
|
90
104
|
uri?: string;
|
|
105
|
+
componentResponseEncoding?: {
|
|
106
|
+
charset?: string;
|
|
107
|
+
};
|
|
91
108
|
}>): ComponentXmlObservation | DirectCodecFailure;
|
|
92
109
|
/** Validate a genuine observed subtree through an independently qualified component mapping.
|
|
93
110
|
* @remarks XML capacities belong to the observation episode. This entry assesses the selected mapping's
|
|
@@ -111,6 +128,9 @@ export declare function decodeDirectXml(contract: DirectOperationContract, mappi
|
|
|
111
128
|
*/
|
|
112
129
|
export declare function decodeComponentXml(contract: DirectOperationContract, mapping: unknown, bytes: Uint8Array, options?: Options & Readonly<{
|
|
113
130
|
uri?: string;
|
|
131
|
+
componentResponseEncoding?: {
|
|
132
|
+
charset?: string;
|
|
133
|
+
};
|
|
114
134
|
selectPayload?: (root: SyntaxElement) => ComponentPayloadSelection;
|
|
115
135
|
}>): ComponentDecodedXml | DirectCodecFailure;
|
|
116
136
|
export {};
|
|
@@ -3,11 +3,13 @@ import { directDataOwnership, directLimit } from './directExecutionPolicy.js';
|
|
|
3
3
|
import { checkSemanticBudget } from './semanticBudget.js';
|
|
4
4
|
import { DataOwnershipLimitError } from './dataOwnership.js';
|
|
5
5
|
import { types } from 'node:util';
|
|
6
|
-
import { inspectDirectOperation } from './directOperation.js';
|
|
6
|
+
import { inspectDirectOperation, isRpcMessageRoot } from './directOperation.js';
|
|
7
7
|
import { inspectDirectValueMapping, expandDirectValue, assessDirectScalars, inspectComponentValueMapping, expandProjectionValue, assessProjectionScalars } from './directValues.js';
|
|
8
8
|
import { matchDirectOperation, matchDirectProjection } from './directStructure.js';
|
|
9
9
|
import { parseOrderedSyntax, SchemaLoadingError, XML_NAMESPACE } from '../loader/orderedSyntax.js';
|
|
10
10
|
import { isXmlNamespaceBinding } from './directStructurePlan.js';
|
|
11
|
+
import { expandRpcStrings, readRpcStrings } from './rpcStringValues.js';
|
|
12
|
+
import { expandRpcTyped, readRpcTyped } from './rpcTypedValues.js';
|
|
11
13
|
export const DIRECT_XML_LIMITS = Object.freeze({ maxBytes: 1024 * 1024, maxDepth: 256, maxNodes: 100_000, maxText: 1024 * 1024 });
|
|
12
14
|
const componentObservations = new WeakSet();
|
|
13
15
|
class Stop extends Error {
|
|
@@ -34,9 +36,11 @@ function runMapping(contract, supplied, options, profile, inspect, work) {
|
|
|
34
36
|
try {
|
|
35
37
|
const result = budget.guard(() => {
|
|
36
38
|
check();
|
|
37
|
-
|
|
39
|
+
const rpc = profile === componentProfile && (mapping.version === 'component-rpc-string-values-v1' || mapping.version === 'component-rpc-typed-values-v1') && isRpcMessageRoot(mapping.root)
|
|
40
|
+
&& contract.binding.style === 'rpc' && contract.binding.use === 'encoded' && contract.binding.encoding === 'http://schemas.xmlsoap.org/soap/encoding/';
|
|
41
|
+
if ((!rpc && mapping.version !== profile.version) || mapping.contextId !== contract.contextId
|
|
38
42
|
|| contract.basis !== profile.basis || contract.correspondence !== profile.correspondence
|
|
39
|
-
|| contract.binding.version
|
|
43
|
+
|| !(contract.binding.version === '1.1' || profile === componentProfile && contract.binding.version === '1.2' && !rpc) || !rpc && (contract.binding.style !== 'document' || contract.binding.use !== 'literal')
|
|
40
44
|
|| contract.binding.transport !== 'http://schemas.xmlsoap.org/soap/http')
|
|
41
45
|
throw new Stop('unsupported', profile.rule);
|
|
42
46
|
const direction = mapping.direction;
|
|
@@ -235,6 +239,16 @@ export function encodeDirectXml(contract, mapping, application, options = {}) {
|
|
|
235
239
|
export function encodeComponentXml(contract, mapping, application, options = {}) {
|
|
236
240
|
return runComponent(contract, mapping, options, (owned, session) => {
|
|
237
241
|
const { context } = inspectDirectOperation(contract), { budget, limits } = session;
|
|
242
|
+
if (isRpcMessageRoot(owned.root)) {
|
|
243
|
+
const control = { ...session, fail: (kind, rule, node) => { throw new Stop(kind, rule, node?.source); } };
|
|
244
|
+
const { rpcParts } = inspectComponentValueMapping(owned, contract);
|
|
245
|
+
const result = rpcParts ? expandRpcTyped(context, owned.root, rpcParts, application, control) : expandRpcStrings(owned.root, application, control);
|
|
246
|
+
const validated = { kind: rpcParts ? 'rpc-typed-value' : 'rpc-string-value', admitted: false, value: result.value, usage: budget.usage() };
|
|
247
|
+
const expanded = { kind: rpcParts ? 'rpc-typed-expanded' : 'rpc-string-expanded', admitted: false, payload: result.payload, validated, usage: budget.usage() };
|
|
248
|
+
directDataOwnership(budget).freeze(expanded);
|
|
249
|
+
const bytes = emitObservedXml(result.payload, session);
|
|
250
|
+
return Object.freeze({ kind: 'component-encoded', admitted: false, encoding: 'utf-8', bytes, expanded, mapping: owned, usage: budget.usage() });
|
|
251
|
+
}
|
|
238
252
|
// @why Application items include attributes; XML syntax-node/text caps count elements and character data.
|
|
239
253
|
// Encoded bytes bound inverse input capacity without treating those different counters as equal.
|
|
240
254
|
const expanded = expandProjectionValue(context, owned.root.element, application, { ...options,
|
|
@@ -247,7 +261,7 @@ export function encodeComponentXml(contract, mapping, application, options = {})
|
|
|
247
261
|
});
|
|
248
262
|
}
|
|
249
263
|
/** Retain the shared byte-copy, XML-version, entity and explicit capacity boundaries for both profiles. */
|
|
250
|
-
function parseObservedXml(bytes, { budget, limits, check }, sourceUri) {
|
|
264
|
+
function parseObservedXml(bytes, { budget, limits, check }, sourceUri, componentResponseEncoding) {
|
|
251
265
|
if (!types.isUint8Array(bytes) || types.isProxy(bytes))
|
|
252
266
|
throw new TypeError('XML requires Uint8Array bytes');
|
|
253
267
|
// Use native internal-slot access, avoiding subclass/own length, buffer and
|
|
@@ -266,7 +280,7 @@ function parseObservedXml(bytes, { budget, limits, check }, sourceUri) {
|
|
|
266
280
|
if (typeof uri !== 'string')
|
|
267
281
|
throw new TypeError('XML source URI must be a string');
|
|
268
282
|
check();
|
|
269
|
-
return parseOrderedSyntax(input, uri, { ...limits, xml10Only: true,
|
|
283
|
+
return parseOrderedSyntax(input, uri, { ...limits, xml10Only: true, componentResponseEncoding,
|
|
270
284
|
chargeWork: () => check(), reserveNode: () => { check(); budget.reserveNodes(1); } }).root;
|
|
271
285
|
}
|
|
272
286
|
function assertObservedPayload(root, payload, check) {
|
|
@@ -291,7 +305,7 @@ function assertObservedPayload(root, payload, check) {
|
|
|
291
305
|
*/
|
|
292
306
|
export function observeComponentXml(contract, mapping, bytes, options = {}) {
|
|
293
307
|
const result = runComponent(contract, mapping, options, (_owned, session) => {
|
|
294
|
-
const root = parseObservedXml(bytes, session, options.uri ?? 'urn:typescript-wsdl-client:observed-component-xml');
|
|
308
|
+
const root = parseObservedXml(bytes, session, options.uri ?? 'urn:typescript-wsdl-client:observed-component-xml', options.componentResponseEncoding);
|
|
295
309
|
directDataOwnership(session.budget).freeze(root);
|
|
296
310
|
return Object.freeze({ kind: 'observed-component-xml', root, usage: Object.freeze({ ...session.budget.usage() }) });
|
|
297
311
|
});
|
|
@@ -310,6 +324,15 @@ export function assessObservedComponentPayload(contract, mapping, observation, p
|
|
|
310
324
|
return runComponent(contract, mapping, options, (owned, { budget, check }) => {
|
|
311
325
|
assertObservedPayload(observation.root, payload, check);
|
|
312
326
|
const { context } = inspectDirectOperation(contract);
|
|
327
|
+
if (isRpcMessageRoot(owned.root)) {
|
|
328
|
+
const control = { budget, check, fail: (kind, rule, node) => { throw new Stop(kind, rule, node?.source); } };
|
|
329
|
+
const { rpcParts } = inspectComponentValueMapping(owned, contract);
|
|
330
|
+
const value = rpcParts && owned.direction.kind === 'input' ? readRpcTyped(context, owned.root, rpcParts, payload, control)
|
|
331
|
+
: readRpcStrings(owned.root, payload, owned.direction.kind === 'output', control);
|
|
332
|
+
const validated = { kind: rpcParts ? 'rpc-typed-value' : 'rpc-string-value', admitted: false, value, usage: budget.usage() };
|
|
333
|
+
directDataOwnership(budget).freeze(validated);
|
|
334
|
+
return { kind: 'component-decoded', admitted: false, payload, validated, mapping: owned, usage: budget.usage() };
|
|
335
|
+
}
|
|
313
336
|
const structural = matchDirectProjection(context, owned.root.element, payload, options);
|
|
314
337
|
if (structural.kind !== 'projection-match')
|
|
315
338
|
return structural;
|
|
@@ -358,10 +381,20 @@ export function decodeDirectXml(contract, mapping, bytes, options = {}) {
|
|
|
358
381
|
export function decodeComponentXml(contract, mapping, bytes, options = {}) {
|
|
359
382
|
return runComponent(contract, mapping, options, (owned, session) => {
|
|
360
383
|
const { budget, check } = session, { context } = inspectDirectOperation(contract);
|
|
361
|
-
const root = parseObservedXml(bytes, session, options.uri ?? 'urn:typescript-wsdl-client:observed-component-xml');
|
|
384
|
+
const root = parseObservedXml(bytes, session, options.uri ?? 'urn:typescript-wsdl-client:observed-component-xml', options.componentResponseEncoding);
|
|
362
385
|
const selection = options.selectPayload?.(root) ?? { payload: root, mapping: owned };
|
|
363
386
|
const selected = inspectComponentValueMapping(selection.mapping, contract).mapping;
|
|
364
387
|
assertObservedPayload(root, selection.payload, check);
|
|
388
|
+
if (isRpcMessageRoot(selected.root)) {
|
|
389
|
+
const control = { ...session, fail: (kind, rule, node) => { throw new Stop(kind, rule, node?.source); } };
|
|
390
|
+
const { rpcParts } = inspectComponentValueMapping(selected, contract);
|
|
391
|
+
const value = rpcParts && selected.direction.kind === 'input' ? readRpcTyped(context, selected.root, rpcParts, selection.payload, control)
|
|
392
|
+
: readRpcStrings(selected.root, selection.payload, selected.direction.kind === 'output', control);
|
|
393
|
+
const validated = { kind: rpcParts ? 'rpc-typed-value' : 'rpc-string-value', admitted: false, value, usage: budget.usage() };
|
|
394
|
+
directDataOwnership(budget).freeze(validated);
|
|
395
|
+
directDataOwnership(budget).freeze(selection.payload);
|
|
396
|
+
return { kind: 'component-decoded', admitted: false, payload: selection.payload, validated, mapping: selected, usage: budget.usage() };
|
|
397
|
+
}
|
|
365
398
|
const structural = matchDirectProjection(context, selected.root.element, selection.payload, options);
|
|
366
399
|
if (structural.kind !== 'projection-match')
|
|
367
400
|
return structural;
|
|
@@ -19,21 +19,44 @@ export interface DirectMessageRoot {
|
|
|
19
19
|
readonly type: Reference;
|
|
20
20
|
readonly source: SyntaxSource;
|
|
21
21
|
}
|
|
22
|
+
/** Source-owned RPC parameters; these are WSDL parts rather than schema element declarations.
|
|
23
|
+
* @since 1.1.9
|
|
24
|
+
*/
|
|
25
|
+
export interface RpcMessageRoot {
|
|
26
|
+
readonly kind: 'rpc-typed-parts-v1';
|
|
27
|
+
readonly message: ExpandedName;
|
|
28
|
+
readonly parts: readonly Readonly<{
|
|
29
|
+
name: string;
|
|
30
|
+
type: Reference;
|
|
31
|
+
source: SyntaxSource;
|
|
32
|
+
}>[];
|
|
33
|
+
readonly wrapper: ExpandedName;
|
|
34
|
+
readonly bodyNamespace: string;
|
|
35
|
+
readonly encoding: 'http://schemas.xmlsoap.org/soap/encoding/';
|
|
36
|
+
readonly source: SyntaxSource;
|
|
37
|
+
}
|
|
38
|
+
/** Distinguish typed RPC parameters without inventing a document element.
|
|
39
|
+
* @param root Selected native message.
|
|
40
|
+
* @returns Whether the message uses the bounded RPC profile.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isRpcMessageRoot(root: DirectMessageRoot | RpcMessageRoot): root is RpcMessageRoot;
|
|
22
43
|
export interface DirectOperationContract {
|
|
23
44
|
readonly selector: DirectOperationSelector;
|
|
24
45
|
readonly contextId: string;
|
|
25
46
|
readonly basis: 'native-direct-premises-v1' | 'native-component-premises-v1';
|
|
26
47
|
readonly correspondence: 'dated-XSD-2004-direct-v1' | 'dated-XSD-2004-components-v1';
|
|
27
48
|
readonly binding: Readonly<{
|
|
28
|
-
version: '1.1';
|
|
29
|
-
style: 'document';
|
|
30
|
-
use: 'literal';
|
|
49
|
+
version: '1.1' | '1.2';
|
|
50
|
+
style: 'document' | 'rpc';
|
|
51
|
+
use: 'literal' | 'encoded';
|
|
52
|
+
encoding?: string;
|
|
31
53
|
transport: string;
|
|
32
54
|
action: string;
|
|
55
|
+
actionRequired?: boolean;
|
|
33
56
|
source: SyntaxSource;
|
|
34
57
|
}>;
|
|
35
|
-
readonly input: DirectMessageRoot;
|
|
36
|
-
readonly output: DirectMessageRoot;
|
|
58
|
+
readonly input: DirectMessageRoot | RpcMessageRoot;
|
|
59
|
+
readonly output: DirectMessageRoot | RpcMessageRoot;
|
|
37
60
|
readonly faults: readonly Readonly<{
|
|
38
61
|
name: string;
|
|
39
62
|
root: DirectMessageRoot;
|