@techspokes/typescript-wsdl-client 1.1.8 → 1.2.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 (73) hide show
  1. package/README.md +17 -3
  2. package/dist/checkedPlanning.js +27 -4
  3. package/dist/cli.js +17 -1
  4. package/dist/client/generateClient.js +72 -16
  5. package/dist/client/generateOperations.js +30 -11
  6. package/dist/client/generateUtils.js +5 -1
  7. package/dist/compiler/canonicalGraph.d.ts +4 -15
  8. package/dist/compiler/canonicalGraph.js +1 -8
  9. package/dist/compiler/componentRecordProjection.d.ts +3 -0
  10. package/dist/compiler/componentRecordProjection.js +10 -0
  11. package/dist/compiler/declarationIdentity.d.ts +22 -0
  12. package/dist/compiler/declarationIdentity.js +9 -0
  13. package/dist/compiler/directCodec.d.ts +22 -2
  14. package/dist/compiler/directCodec.js +40 -7
  15. package/dist/compiler/directOperation.d.ts +26 -4
  16. package/dist/compiler/directOperation.js +59 -5
  17. package/dist/compiler/directSoap.js +24 -7
  18. package/dist/compiler/directSourceCapsule.js +59 -3
  19. package/dist/compiler/directStructure.js +6 -2
  20. package/dist/compiler/directStructurePlan.d.ts +10 -1
  21. package/dist/compiler/directStructurePlan.js +27 -7
  22. package/dist/compiler/directValues.d.ts +15 -3
  23. package/dist/compiler/directValues.js +20 -4
  24. package/dist/compiler/legacyDeclarations.d.ts +26 -0
  25. package/dist/compiler/legacyDeclarations.js +144 -0
  26. package/dist/compiler/projectionScalar.d.ts +18 -1
  27. package/dist/compiler/projectionScalar.js +47 -13
  28. package/dist/compiler/rpcStringValues.d.ts +30 -0
  29. package/dist/compiler/rpcStringValues.js +196 -0
  30. package/dist/compiler/rpcTypedValues.d.ts +48 -0
  31. package/dist/compiler/rpcTypedValues.js +190 -0
  32. package/dist/compiler/schemaCompiler.js +44 -109
  33. package/dist/compiler/semanticCatalog.d.ts +2 -2
  34. package/dist/gateway/generateComposedGateway.d.ts +16 -0
  35. package/dist/gateway/generateComposedGateway.js +65 -0
  36. package/dist/gateway/generateGateway.js +1 -0
  37. package/dist/gateway/generators.js +19 -4
  38. package/dist/gateway/helpers.d.ts +1 -0
  39. package/dist/gateway/helpers.js +15 -0
  40. package/dist/generation/componentShape.js +25 -1
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +2 -0
  43. package/dist/loader/orderedSyntax.d.ts +4 -0
  44. package/dist/loader/orderedSyntax.js +18 -4
  45. package/dist/multiServicePipeline.d.ts +41 -0
  46. package/dist/multiServicePipeline.js +287 -0
  47. package/dist/openapi/composeOpenAPI.d.ts +29 -0
  48. package/dist/openapi/composeOpenAPI.js +322 -0
  49. package/dist/pipeline.d.ts +1 -1
  50. package/dist/pipeline.js +2 -0
  51. package/docs/agent-skill.md +10 -1
  52. package/docs/api-reference.md +127 -9
  53. package/docs/checked-gateway.md +2 -0
  54. package/docs/checked-generation.md +11 -5
  55. package/docs/cli-reference.md +25 -5
  56. package/docs/concepts.md +4 -5
  57. package/docs/configuration.md +10 -3
  58. package/docs/corpus.md +1 -1
  59. package/docs/gateway-guide.md +104 -27
  60. package/docs/generated-code.md +12 -6
  61. package/docs/migration-playbook.md +4 -0
  62. package/docs/migration.md +124 -6
  63. package/docs/output-anatomy.md +20 -17
  64. package/docs/production.md +7 -5
  65. package/docs/start-here.md +12 -2
  66. package/docs/supported-patterns.md +17 -0
  67. package/docs/testing.md +2 -0
  68. package/docs/troubleshooting.md +3 -1
  69. package/llms.txt +9 -1
  70. package/package.json +2 -2
  71. package/src/runtime/clientStreamMethods.tpl.txt +156 -148
  72. package/src/runtime/operationsStreamHelper.tpl.txt +6 -6
  73. package/src/runtime/streamXml.ts +48 -20
@@ -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
- if (mapping.version !== profile.version || mapping.contextId !== contract.contextId
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 !== '1.1' || contract.binding.style !== 'document' || contract.binding.use !== 'literal'
43
+ || contract.binding.version !== '1.1' || !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,6 +19,27 @@ 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;
@@ -26,14 +47,15 @@ export interface DirectOperationContract {
26
47
  readonly correspondence: 'dated-XSD-2004-direct-v1' | 'dated-XSD-2004-components-v1';
27
48
  readonly binding: Readonly<{
28
49
  version: '1.1';
29
- style: 'document';
30
- use: 'literal';
50
+ style: 'document' | 'rpc';
51
+ use: 'literal' | 'encoded';
52
+ encoding?: string;
31
53
  transport: string;
32
54
  action: string;
33
55
  source: SyntaxSource;
34
56
  }>;
35
- readonly input: DirectMessageRoot;
36
- readonly output: DirectMessageRoot;
57
+ readonly input: DirectMessageRoot | RpcMessageRoot;
58
+ readonly output: DirectMessageRoot | RpcMessageRoot;
37
59
  readonly faults: readonly Readonly<{
38
60
  name: string;
39
61
  root: DirectMessageRoot;
@@ -1,7 +1,7 @@
1
1
  import { directLimit, directLoadingLimits } from './directExecutionPolicy.js';
2
2
  /** Transient source-owned schema selection. This module grants no runtime admission. */
3
3
  import { loadSemanticSchemaInput } from '../loader/schemaInput.js';
4
- import { SchemaLoadingError, WSDL_NAMESPACE, syntaxAttribute, syntaxElements, isXmlNCName } from '../loader/orderedSyntax.js';
4
+ import { SchemaLoadingError, WSDL_NAMESPACE, XSD_NAMESPACE, syntaxAttribute, syntaxElements, isXmlNCName } from '../loader/orderedSyntax.js';
5
5
  import { globalId } from './canonicalGraph.js';
6
6
  import { createApplicationBudget, DEFAULT_MAX_NODES } from './semanticBudget.js';
7
7
  import { qualifyDirectSource } from './checkedSourcePreparation.js';
@@ -10,6 +10,13 @@ import { prepareWsdlPolicyRequirements, assertWsdlPolicyRequirements } from './w
10
10
  const SOAP = 'http://schemas.xmlsoap.org/wsdl/soap/';
11
11
  const HTTP = 'http://schemas.xmlsoap.org/soap/http';
12
12
  const ADDRESSING_WSDL = 'http://www.w3.org/2006/05/addressing/wsdl';
13
+ /** Distinguish typed RPC parameters without inventing a document element.
14
+ * @param root Selected native message.
15
+ * @returns Whether the message uses the bounded RPC profile.
16
+ */
17
+ export function isRpcMessageRoot(root) {
18
+ return 'kind' in root && root.kind === 'rpc-typed-parts-v1';
19
+ }
13
20
  const issued = new WeakMap();
14
21
  /** Object identity authenticates the exact issued roots; context IDs and digests do not. */
15
22
  export function inspectDirectOperation(contract, expectedContext) {
@@ -181,7 +188,7 @@ async function selectOperationInRequest(source, selector, options, budget, profi
181
188
  portType: ['name'], operation: ['name', 'parameterOrder'], input: ['name', 'message'], output: ['name', 'message'], fault: ['name', 'message'],
182
189
  binding: ['name', 'type'], service: ['name'], port: ['name', 'binding'],
183
190
  };
184
- const soapAttributes = { binding: ['style', 'transport'], operation: ['soapAction', 'style'], body: ['use', 'parts'], fault: ['name', 'use'], address: ['location'] };
191
+ const soapAttributes = { binding: ['style', 'transport'], operation: ['soapAction', 'style'], body: profile === 'components' ? ['use', 'parts', 'namespace', 'encodingStyle'] : ['use', 'parts'], fault: ['name', 'use'], address: ['location'] };
185
192
  for (const document of input.documents) {
186
193
  const stack = [document.root];
187
194
  while (stack.length) {
@@ -256,7 +263,8 @@ async function selectOperationInRequest(source, selector, options, budget, profi
256
263
  return refuse('unsupported', 'soap-binding-style');
257
264
  if (attr(soapBinding, 'transport') !== HTTP)
258
265
  return refuse('unsupported', 'soap-transport');
259
- if ((attr(soapOperation, 'style') ?? attr(soapBinding, 'style') ?? 'document') !== 'document')
266
+ const style = attr(soapOperation, 'style') ?? attr(soapBinding, 'style') ?? 'document';
267
+ if (style !== 'document' && !(profile === 'components' && style === 'rpc'))
260
268
  return refuse('unsupported', 'soap-style');
261
269
  const action = attr(soapOperation, 'soapAction');
262
270
  if (action === undefined)
@@ -283,6 +291,7 @@ async function selectOperationInRequest(source, selector, options, budget, profi
283
291
  shape(boundSyntax, [soap(faultName === undefined ? 'body' : 'fault'), ws('documentation')]);
284
292
  const body = exactly(boundSyntax, SOAP, faultName === undefined ? 'body' : 'fault');
285
293
  shape(body, []);
294
+ attrs(body, faultName === undefined ? ['use', 'parts'] : ['use', 'name']);
286
295
  if (attr(body, 'use') !== 'literal')
287
296
  return refuse('unsupported', 'literal-required');
288
297
  if (faultName !== undefined && attr(body, 'name') !== faultName)
@@ -309,17 +318,61 @@ async function selectOperationInRequest(source, selector, options, budget, profi
309
318
  return refuse('selection-refused', 'element-type');
310
319
  return Object.freeze({ message: message.name, part: partName, element, type: element.type, source: part.source });
311
320
  };
321
+ const rpcRoot = (messageSyntax, boundSyntax, direction) => {
322
+ attrs(messageSyntax, ['name', 'message']);
323
+ attrs(boundSyntax, ['name']);
324
+ shape(messageSyntax, [ws('documentation')]);
325
+ shape(boundSyntax, [soap('body'), ws('documentation')]);
326
+ const body = exactly(boundSyntax, SOAP, 'body');
327
+ shape(body, []);
328
+ const encoding = 'http://schemas.xmlsoap.org/soap/encoding/';
329
+ if (attr(body, 'use') !== 'encoded' || attr(body, 'encodingStyle')?.trim() !== encoding)
330
+ return refuse('unsupported', 'rpc-string-encoding');
331
+ const bodyNamespace = attr(body, 'namespace');
332
+ if (!bodyNamespace || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(bodyNamespace))
333
+ return refuse('unsupported', 'rpc-body-namespace');
334
+ const message = reference(portType, messageSyntax, 'message');
335
+ if (message.kind !== 'wsdl' || message.role !== 'message')
336
+ return refuse('selection-refused', 'message-reference');
337
+ shape(message.syntax, [ws('part'), ws('documentation')]);
338
+ const declared = children(message.syntax).filter(c => c.name.namespace === WSDL_NAMESPACE && c.name.local === 'part');
339
+ if (!declared.length || direction === 'output' && declared.length !== 1)
340
+ return refuse('unsupported', 'rpc-single-string-result');
341
+ const names = new Set();
342
+ const parts = declared.map(part => {
343
+ attrs(part, ['name', 'type']);
344
+ shape(part, [ws('documentation')]);
345
+ const name = attr(part, 'name');
346
+ if (!name || !isXmlNCName(name) || names.has(name))
347
+ return refuse('selection-refused', 'rpc-part-name');
348
+ names.add(name);
349
+ const path = part.source.path.slice(message.syntax.source.path.length) || '/';
350
+ const refs = message.references.filter(r => r.path === path && r.attribute === 'type');
351
+ if (refs.length !== 1)
352
+ return refuse('unsupported', 'rpc-part-type-required');
353
+ const resolved = context.resolve(refs[0].reference);
354
+ if (direction === 'output' && (resolved.kind !== 'builtin' || resolved.name.namespace !== XSD_NAMESPACE || resolved.name.local !== 'string'))
355
+ return refuse('unsupported', 'rpc-string-part-required');
356
+ return Object.freeze({ name, type: refs[0].reference, source: part.source });
357
+ });
358
+ if (attr(body, 'parts') !== undefined && attr(body, 'parts').trim().split(/[\t\r\n ]+/).join(' ') !== parts.map(p => p.name).join(' '))
359
+ return refuse('unsupported', 'rpc-body-parts');
360
+ return Object.freeze({ kind: 'rpc-typed-parts-v1', message: message.name, parts, bodyNamespace, encoding,
361
+ wrapper: { namespace: bodyNamespace, local: ownedSelector.operation + (direction === 'output' ? 'Response' : '') }, source: body.source });
362
+ };
312
363
  const io = (direction, declaration) => {
313
364
  const boundIo = exactly(bound, WSDL_NAMESPACE, direction);
314
365
  const boundName = attr(boundIo, 'name');
315
366
  const expectedName = attr(declaration, 'name') ?? `${ownedSelector.operation}${direction === 'input' ? 'Request' : 'Response'}`;
316
367
  if (boundName !== undefined && boundName !== expectedName)
317
368
  return refuse('selection-refused', 'binding-message-name');
318
- return root(declaration, boundIo);
369
+ return style === 'rpc' ? rpcRoot(declaration, boundIo, direction) : root(declaration, boundIo);
319
370
  };
320
371
  const requestRoot = io('input', abstractInput), responseRoot = io('output', abstractOutput);
321
372
  const declaredFaults = children(abstract).filter(c => c.name.namespace === WSDL_NAMESPACE && c.name.local === 'fault');
322
373
  const boundFaults = children(bound).filter(c => c.name.namespace === WSDL_NAMESPACE && c.name.local === 'fault');
374
+ if (style === 'rpc' && (declaredFaults.length || boundFaults.length))
375
+ return refuse('unsupported', 'rpc-declared-fault');
323
376
  if (declaredFaults.length !== boundFaults.length)
324
377
  return refuse('selection-refused', 'fault-mapping');
325
378
  const faultNames = new Set();
@@ -340,7 +393,8 @@ async function selectOperationInRequest(source, selector, options, budget, profi
340
393
  const contract = { selector: ownedSelector, contextId: context.contextId,
341
394
  basis: profile === 'direct' ? 'native-direct-premises-v1' : 'native-component-premises-v1',
342
395
  correspondence: profile === 'direct' ? 'dated-XSD-2004-direct-v1' : 'dated-XSD-2004-components-v1',
343
- binding: { version: '1.1', style: 'document', use: 'literal', transport: HTTP, action, source: binding.context.source },
396
+ binding: { version: '1.1', style: style, use: style === 'rpc' ? 'encoded' : 'literal',
397
+ ...(style === 'rpc' ? { encoding: 'http://schemas.xmlsoap.org/soap/encoding/' } : {}), transport: HTTP, action, source: binding.context.source },
344
398
  input: requestRoot, output: responseRoot, faults,
345
399
  ...(policyOwner ? { policy: policyOwner.forBinding(binding.syntax), addressingActions: { required: false,
346
400
  input: attr(abstractInput, 'Action', ADDRESSING_WSDL), output: attr(abstractOutput, 'Action', ADDRESSING_WSDL),
@@ -171,6 +171,7 @@ async function exchangeSoap(contract, input, application, options, profile) {
171
171
  && (typeof allowEndpoint !== 'function' || allowEndpoint(new URL(endpoint.href)) !== true))
172
172
  return fail('unsupported', 'endpoint authorization');
173
173
  const action = contract.binding.action;
174
+ const rpc = contract.binding.style === 'rpc';
174
175
  if (/[^\x20-\x21\x23-\x7e]|\\/.test(action))
175
176
  return fail('unsupported', 'unsafe SOAPAction');
176
177
  const elements = (node) => {
@@ -191,9 +192,10 @@ async function exchangeSoap(contract, input, application, options, profile) {
191
192
  return fail(mapping.kind, mapping.rule);
192
193
  faults.set(fault.name, mapping);
193
194
  }
194
- const shape = (node) => {
195
+ const shape = (node, encodedScope = false) => {
195
196
  const children = elements(node);
196
- if (node.attributes.length || node.children.some(n => n.kind === 'text' && /[^\t\r\n ]/.test(n.value)))
197
+ if (node.attributes.some(a => !(encodedScope && a.name.namespace === SOAP && a.name.local === 'encodingStyle'
198
+ && a.value === 'http://schemas.xmlsoap.org/soap/encoding/')) || node.children.some(n => n.kind === 'text' && /[^\t\r\n ]/.test(n.value)))
197
199
  stop('protocol', 'SOAP protocol attributes or text');
198
200
  return children;
199
201
  };
@@ -201,7 +203,7 @@ async function exchangeSoap(contract, input, application, options, profile) {
201
203
  const bodyRoot = (root, incoming = false) => {
202
204
  if (!same(root.name, { namespace: SOAP, local: 'Envelope' }))
203
205
  stop('protocol', 'SOAP 1.1 Envelope required');
204
- const children = shape(root);
206
+ const children = shape(root, rpc);
205
207
  if (children[0]?.name.namespace === SOAP && children[0].name.local === 'Header') {
206
208
  if (profile.component && incoming) {
207
209
  const assessed = assessComponentSoapHeader(children[0], check);
@@ -219,13 +221,25 @@ async function exchangeSoap(contract, input, application, options, profile) {
219
221
  }
220
222
  if (children.length !== 1 || !same(children[0].name, { namespace: SOAP, local: 'Body' }))
221
223
  stop('protocol', 'one SOAP Body required');
222
- const body = shape(children[0]);
224
+ const body = shape(children[0], rpc);
225
+ if (rpc && !body.some(n => same(n.name, { namespace: SOAP, local: 'Fault' })))
226
+ return children[0];
223
227
  if (body.length !== 1)
224
228
  stop('protocol', 'one SOAP Body payload required');
225
229
  return body[0];
226
230
  };
227
231
  const wsdl = await profile.prepare(config, check);
228
232
  check();
233
+ // @why node-soap's RPC writer would wrap `_xml` as a parameter. This private
234
+ // instance retains its selected method/transport while native bytes own RPC construction.
235
+ const rpcArguments = Object.freeze({});
236
+ if (rpc)
237
+ wsdl.objectToRpcXML = (name, args) => {
238
+ check();
239
+ if (name !== contract.selector.operation || args !== rpcArguments)
240
+ stop('protocol', 'dependency RPC selection mismatch');
241
+ return Buffer.from(encoded.bytes).toString('utf8');
242
+ };
229
243
  // HTTP and codec byte limits bound buffering and output copies directly.
230
244
  agent = endpoint.protocol === 'https:' ? new HttpsAgent({ keepAlive: false, maxSockets: 1, rejectUnauthorized: true,
231
245
  ...(config.ca === undefined ? {} : { ca: config.ca }) }) : new Agent({ keepAlive: false, maxSockets: 1 });
@@ -290,7 +304,7 @@ async function exchangeSoap(contract, input, application, options, profile) {
290
304
  if (typeof method !== 'function')
291
305
  return fail('unsupported', 'dependency selected method unavailable');
292
306
  check();
293
- await new Promise((resolve, reject) => method({ _xml: Buffer.from(encoded.bytes).toString('utf8') }, (error) => error === complete ? resolve() : reject(error), {}, { SOAPAction: `"${action}"` }));
307
+ await new Promise((resolve, reject) => method(rpc ? rpcArguments : { _xml: Buffer.from(encoded.bytes).toString('utf8') }, (error) => error === complete ? resolve() : reject(error), {}, { SOAPAction: `"${action}"` }));
294
308
  check();
295
309
  if (timedOut)
296
310
  return fail('transport', 'SOAP deadline exceeded');
@@ -301,19 +315,22 @@ async function exchangeSoap(contract, input, application, options, profile) {
301
315
  const observed = response;
302
316
  if (observed.encoding && observed.encoding.toLowerCase() !== 'identity')
303
317
  return fail('unsupported', 'HTTP compression');
304
- const media = /^text\/xml(?:\s*;\s*charset\s*=\s*(?:"(utf-8|utf-16|utf-16le|utf-16be)"|(utf-8|utf-16|utf-16le|utf-16be)))?\s*$/i.exec(observed.contentType);
318
+ const media = /^text\/xml(?:\s*;\s*charset\s*=\s*(?:"(utf-8|utf-16|utf-16le|utf-16be|iso-8859-1)"|(utf-8|utf-16|utf-16le|utf-16be|iso-8859-1)))?\s*$/i.exec(observed.contentType);
305
319
  if (!media)
306
320
  return fail('protocol', 'SOAP response Content-Type');
307
321
  if (observed.status !== 200 && observed.status !== 500)
308
322
  return fail('transport', 'SOAP HTTP status');
309
323
  // Header charset is a protocol assertion, checked against actual BOM bytes.
310
324
  const charset = (media[1] ?? media[2])?.toLowerCase();
325
+ if (!profile.component && charset === 'iso-8859-1')
326
+ return fail('protocol', 'SOAP response Content-Type');
311
327
  const bom = observed.bytes[0] === 0xff && observed.bytes[1] === 0xfe ? 'utf-16le'
312
328
  : observed.bytes[0] === 0xfe && observed.bytes[1] === 0xff ? 'utf-16be' : 'utf-8';
313
- if (charset && charset !== bom && !(charset === 'utf-16' && bom.startsWith('utf-16')))
329
+ if (!profile.component && charset && charset !== bom && !(charset === 'utf-16' && bom.startsWith('utf-16')))
314
330
  return fail('protocol', 'SOAP charset disagrees with bytes');
315
331
  let faultInfo;
316
332
  const decoded = profile.decode(contract, output, observed.bytes, { signal, limits, uri: endpoint.href,
333
+ ...(profile.component ? { componentResponseEncoding: { charset } } : {}),
317
334
  selectPayload: root => {
318
335
  const payload = bodyRoot(root, true);
319
336
  if (!same(payload.name, { namespace: SOAP, local: 'Fault' })) {
@@ -316,6 +316,8 @@ export function checkDirectSourceCompatibility(expected, contract, selection, si
316
316
  const componentVersions = Object.freeze({ ...versions, scope: 'component-soap11-utf8-graph-v1', mapping: 'component-values-v1',
317
317
  runtimePolicy: 'explicit-http-verified-https-transport-v1' });
318
318
  const scopedComponentVersions = Object.freeze({ ...componentVersions, scope: 'component-soap11-utf8-graph-v2', mapping: 'component-values-and-record-scope-v1' });
319
+ const rpcComponentVersions = Object.freeze({ ...componentVersions, scope: 'component-soap11-rpc-strings-v1', mapping: 'component-rpc-string-values-v1' });
320
+ const typedRpcComponentVersions = Object.freeze({ ...componentVersions, scope: 'component-soap11-rpc-typed-latin1-v1', mapping: 'component-rpc-typed-values-v1' });
319
321
  const componentReconstructions = new WeakMap();
320
322
  function componentFailure(error, budget) {
321
323
  return Object.freeze({ ...failure(error, budget), repair: 'Regenerate matched component artifacts from explicitly authorized original WSDL and dependencies.' });
@@ -362,6 +364,17 @@ async function componentDescriptor(contract, selection, signal, responseProjecti
362
364
  if (mapping.kind !== 'component-mapping' && mapping.kind !== 'component-record-projection')
363
365
  throw new Stop(mapping.kind === 'resource-limit' || mapping.kind === 'cancelled' ? mapping.kind : 'unsupported', mapping.rule);
364
366
  mappings.push(mapping);
367
+ if ('parts' in mapping.root)
368
+ return { kind: 'rpc-typed-parts-v1', message: mapping.root.message,
369
+ wrapper: mapping.root.wrapper, bodyNamespace: mapping.root.bodyNamespace, encoding: contract.binding.encoding,
370
+ parts: mapping.root.parts.map(part => {
371
+ const resolved = inspectDirectOperation(contract).context.resolve(part.type);
372
+ const type = resolved.kind === 'builtin' ? resolved.name : resolved.kind === 'found' && resolved.node.identity.kind === 'global'
373
+ ? resolved.node.identity.name : undefined;
374
+ if (!type)
375
+ throw new Stop('unsupported', 'rpc-named-type-required');
376
+ return { name: part.name, type };
377
+ }) };
365
378
  return { element: mapping.root.element.name, message: mapping.root.message, part: mapping.root.part };
366
379
  };
367
380
  const input = make({ kind: 'input' }), output = make({ kind: 'output' });
@@ -371,6 +384,8 @@ async function componentDescriptor(contract, selection, signal, responseProjecti
371
384
  let projection = null;
372
385
  if (responseProjection) {
373
386
  const { companion } = inspectComponentRecordProjection(responseProjection, contract);
387
+ if ('parts' in companion.output)
388
+ throw new Stop('unsupported', 'record-projection-requires-document-output');
374
389
  const { context } = inspectDirectOperation(companion);
375
390
  projection = { spec: responseProjection.spec, source: { selector: companion.selector, action: companion.binding.action,
376
391
  output: { element: companion.output.element.name, message: companion.output.message, part: companion.output.part },
@@ -412,7 +427,11 @@ export async function createComponentSourceCapsule(contract, selection, signal,
412
427
  function componentCapsule(derived, budget) {
413
428
  const { compatibility, resources, libraries, projection } = derived;
414
429
  const extended = libraries.length > 0 || projection !== null;
415
- const value = { versions: extended ? scopedComponentVersions : componentVersions, resources, compatibility,
430
+ const rpc = 'kind' in compatibility.input && compatibility.input.kind === 'rpc-typed-parts-v1';
431
+ const typedRpc = rpc && compatibility.input.parts.some(part => part.type.namespace !== 'http://www.w3.org/2001/XMLSchema' || part.type.local !== 'string');
432
+ if (rpc && (extended || derived.applicationGroups))
433
+ throw new Stop('unsupported', 'rpc-artifact-extension');
434
+ const value = { versions: typedRpc ? typedRpcComponentVersions : rpc ? rpcComponentVersions : extended ? scopedComponentVersions : componentVersions, resources, compatibility,
416
435
  ...(extended ? { libraries, projection } : {}), ...(derived.applicationGroups ? { applicationGroups: derived.applicationGroups, applicationGroupIdentity: derived.applicationGroupIdentity } : {}) };
417
436
  return { ...value, fingerprint: hash(json(value, budget)) };
418
437
  }
@@ -470,13 +489,45 @@ function readComponent(bytes, budget, signal) {
470
489
  if (!/^[a-f0-9]{64}$/.test(parsed.applicationGroupIdentity))
471
490
  reject('application-group-identity');
472
491
  }
473
- const expectedVersions = extended ? scopedComponentVersions : componentVersions;
492
+ const typedRpc = !!parsed.versions && typeof parsed.versions === 'object' && parsed.versions.scope === typedRpcComponentVersions.scope;
493
+ const rpc = typedRpc || !!parsed.versions && typeof parsed.versions === 'object' && parsed.versions.scope === rpcComponentVersions.scope;
494
+ if (rpc && (extended || grouped))
495
+ reject('rpc-artifact-extension');
496
+ const expectedVersions = typedRpc ? typedRpcComponentVersions : rpc ? rpcComponentVersions : extended ? scopedComponentVersions : componentVersions;
474
497
  const actual = record(parsed.versions, Object.keys(expectedVersions));
475
498
  for (const key of Object.keys(expectedVersions))
476
499
  if (actual[key] !== expectedVersions[key])
477
500
  reject('unsupported-version-mode-or-policy');
478
501
  validateComponentResources(parsed.resources, budget, signal);
479
- const root = (value) => { const r = record(value, ['element', 'message', 'part']); name(r.element); name(r.message); ncname(r.part); };
502
+ const root = (value) => {
503
+ if (rpc) {
504
+ const r = record(value, ['kind', 'message', 'wrapper', 'bodyNamespace', 'encoding', 'parts']);
505
+ if (r.kind !== 'rpc-typed-parts-v1' || r.encoding !== 'http://schemas.xmlsoap.org/soap/encoding/')
506
+ reject('rpc-root-profile');
507
+ name(r.message);
508
+ name(r.wrapper);
509
+ text(r.bodyNamespace);
510
+ if (!Array.isArray(r.parts) || !r.parts.length)
511
+ return reject('rpc-parts-required');
512
+ const names = new Set();
513
+ for (const part of r.parts) {
514
+ check(budget, signal);
515
+ const p = record(part, ['name', 'type']);
516
+ ncname(p.name);
517
+ name(p.type);
518
+ if (names.has(p.name))
519
+ reject('duplicate-rpc-part');
520
+ names.add(p.name);
521
+ if (!typedRpc && (p.type.namespace !== 'http://www.w3.org/2001/XMLSchema' || p.type.local !== 'string'))
522
+ reject('rpc-part-type');
523
+ }
524
+ return;
525
+ }
526
+ const r = record(value, ['element', 'message', 'part']);
527
+ name(r.element);
528
+ name(r.message);
529
+ ncname(r.part);
530
+ };
480
531
  const selector = (value) => { const s = record(value, ['portType', 'binding', 'operation']); name(s.portType); name(s.binding); ncname(s.operation); };
481
532
  if (extended) {
482
533
  validateComponentLibraries(parsed.libraries);
@@ -520,6 +571,11 @@ function readComponent(bytes, budget, signal) {
520
571
  text(c.action);
521
572
  root(c.input);
522
573
  root(c.output);
574
+ if (rpc) {
575
+ const parts = c.input.parts;
576
+ if (typedRpc !== parts.some(part => part.type.namespace !== 'http://www.w3.org/2001/XMLSchema' || part.type.local !== 'string'))
577
+ reject('rpc-type-version');
578
+ }
523
579
  if (!Array.isArray(c.faults))
524
580
  return reject('faults-required');
525
581
  const names = new Set();
@@ -514,8 +514,12 @@ export function matchDirectOperation(contract, direction, payload, options = {})
514
514
  throw new Stop('unsupported', 'unknown-fault');
515
515
  root = selected.root;
516
516
  }
517
- else
518
- root = contract[ownedDirection.kind];
517
+ else {
518
+ const selected = contract[ownedDirection.kind];
519
+ if ('parts' in selected)
520
+ throw new Stop('unsupported', 'direct-document-root-required');
521
+ root = selected;
522
+ }
519
523
  const matched = matchDirectProjection(context, root.element, payload, { ...options, flatOnly: true });
520
524
  if (matched.kind !== 'projection-match')
521
525
  return matched;
@@ -1,7 +1,7 @@
1
1
  /** Single native direct slot interpretation shared by matching and value mapping. */
2
2
  import type { DirectMessageRoot } from './directOperation.js';
3
3
  import type { SemanticContext } from './semanticContext.js';
4
- import type { AttributeNode, ComplexTypeNode, ElementNode, NodeId, ParticleNode } from './canonicalGraph.js';
4
+ import type { AttributeNode, ComplexTypeNode, ElementNode, GraphNode, NodeId, ParticleNode, Reference } from './canonicalGraph.js';
5
5
  import type { ExpandedName } from '../loader/orderedSyntax.js';
6
6
  import type { ProjectionScalarList } from './projectionScalar.js';
7
7
  import type { SemanticParticle, SemanticParticleView } from './particleSemanticView.js';
@@ -143,6 +143,15 @@ export declare function selectProjectionAttribute(context: SemanticContext, reco
143
143
  * The checked-direct preview adapter below retains its flat-only grammar.
144
144
  */
145
145
  export declare function planDirectProjection(context: SemanticContext, root: ElementNode, check: () => void, unsupported: (detail?: DirectProjectionUnavailable) => never): DirectProjectionPlan;
146
+ /** Plan a source-owned type using native scalar, content and particle owners.
147
+ * @param context Native source context.
148
+ * @param owner Exact source graph node owning the type reference.
149
+ * @param reference Selected source type.
150
+ * @param check Cooperative checkpoint.
151
+ * @param unsupported Explicit refusal callback.
152
+ * @returns The same native projection used by document elements, without element facts.
153
+ */
154
+ export declare function planProjectionType(context: SemanticContext, owner: GraphNode, reference: Reference, check: () => void, unsupported: (detail?: DirectProjectionUnavailable) => never): DirectProjectionPlan;
146
155
  /** Adapt the native projection to the supported flat preview without widening admission. */
147
156
  export declare function planDirectRoot(context: SemanticContext, root: DirectMessageRoot, check: () => void, unsupported: () => never): DirectRootPlan;
148
157
  /** Flat-only projection adapter; its input is derived data, never native admission. */