@techspokes/typescript-wsdl-client 1.3.0 → 1.4.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 +8 -0
- package/dist/checkedPipeline.d.ts +73 -2
- package/dist/checkedPipeline.js +118 -8
- package/dist/checkedPlanning.d.ts +5 -2
- package/dist/checkedPlanning.js +30 -5
- package/dist/checkedResponseInspection.d.ts +100 -0
- package/dist/checkedResponseInspection.js +499 -0
- package/dist/cli.js +55 -1
- package/dist/client/checkedDirectRuntime.d.ts +8 -0
- package/dist/client/checkedDirectRuntime.js +22 -14
- package/dist/client/directClientRuntime.d.ts +10 -3
- package/dist/client/directClientRuntime.js +14 -8
- package/dist/client/directClientVersion.d.ts +2 -0
- package/dist/client/directClientVersion.js +2 -0
- package/dist/client/generateDirectClient.js +7 -7
- package/dist/compiler/componentRecordProjection.d.ts +1 -0
- package/dist/compiler/componentRecordProjection.js +15 -4
- package/dist/compiler/directCodec.js +3 -11
- package/dist/compiler/directSoap.d.ts +50 -5
- package/dist/compiler/directSoap.js +204 -98
- package/dist/compiler/sourceCorrections.d.ts +118 -0
- package/dist/compiler/sourceCorrections.js +794 -0
- package/dist/gateway/generateCheckedGateway.js +9 -10
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/util/readBoundedFile.d.ts +9 -0
- package/dist/util/readBoundedFile.js +36 -0
- package/dist/util/xmlBytes.d.ts +9 -0
- package/dist/util/xmlBytes.js +24 -0
- package/docs/api-reference.md +28 -1
- package/docs/checked-gateway.md +44 -0
- package/docs/cli-reference.md +16 -1
- package/docs/migration.md +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,10 @@ 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.4.0
|
|
13
|
+
|
|
14
|
+
Version 1.4.0 adds explicit JSON source corrections, offline SOAP response inspection, and checked SOAP 1.1 protocol faults without invented application values. Existing runtime bindings remain available; regenerate matched SOAP 1.1 artifacts to adopt the additive V3 binding. Read the [1.3.x migration guidance](docs/migration.md#from-13x-to-140).
|
|
15
|
+
|
|
12
16
|
## Upgrading to 1.3.0
|
|
13
17
|
|
|
14
18
|
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.
|
|
@@ -254,8 +258,10 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
|
|
|
254
258
|
| [Agent Skill Artifact](docs/agent-skill.md) | Release ZIP for consumer-project AI agents |
|
|
255
259
|
| [Version Migration](docs/migration.md) | Upgrading between package versions |
|
|
256
260
|
| [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 |
|
|
261
|
+
| [1.4.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.4.0.md) | Contract corrections, offline inspection and SOAP 1.1 protocol faults |
|
|
257
262
|
| [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 |
|
|
258
263
|
| [XML/JSON Mapping Contract](docs/mapping-contract.md) | Checked keyed, namespace, nil and group representations |
|
|
264
|
+
| [Protocol Fault Compatibility](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/decisions/005-checked-protocol-fault-compatibility.md) | Runtime adoption and complete-source inspection decisions |
|
|
259
265
|
| [Product Corpus](docs/corpus.md) | Representative public inputs and their executable evidence |
|
|
260
266
|
|
|
261
267
|
### Contributing
|
|
@@ -328,6 +334,8 @@ Built and maintained by [TechSpokes](https://www.techspokes.com). Created by [Se
|
|
|
328
334
|
|
|
329
335
|
## Schema assessment diagnostics
|
|
330
336
|
|
|
337
|
+
For saved SOAP responses, use `wsdl-tsc inspect-response` to assess a checked contract offline and optionally produce a source correction draft. XML-only reports leave HTTP checks unassessed. Checked generation accepts consumer-owned external JSON corrections through `sourceCorrectionsFile`; generated runtime artifacts use the effective metadata without needing the recipe. See [source corrections and response inspection](docs/checked-gateway.md#consumer-owned-source-corrections) for configuration, limits and the update workflow.
|
|
338
|
+
|
|
331
339
|
`assess` is an additive diagnostic command. It writes a versioned JSON report without generating artifacts or dispatching SOAP.
|
|
332
340
|
|
|
333
341
|
```bash
|
|
@@ -2,12 +2,14 @@ import type { PipelineOptions } from './pipeline.js';
|
|
|
2
2
|
import type { DirectOperationSelector } from './compiler/directOperation.js';
|
|
3
3
|
import type { ReconstructedComponentSource } from './compiler/directSourceCapsule.js';
|
|
4
4
|
import type { GeneratedComponentClient } from './client/generateDirectClient.js';
|
|
5
|
-
import type { SchemaInput } from './loader/schemaInput.js';
|
|
5
|
+
import type { SchemaInput, SchemaInputOptions } from './loader/schemaInput.js';
|
|
6
|
+
import { prepareCorrectedSourceInputs } from './compiler/sourceCorrections.js';
|
|
6
7
|
import type { ComponentRecordProjectionSpec, ComponentRecordProjectionAssociation, ComponentRecordProjection } from './compiler/componentRecordProjection.js';
|
|
7
8
|
import type { ApplicationGroupsOptions, ApplicationGroupTarget, ApplicationGroupRecord } from './compiler/applicationGroups.js';
|
|
8
9
|
/** Explicit service operations and independently authorized snapshots for component generation. */
|
|
9
10
|
export interface CheckedComponentOptions {
|
|
10
11
|
readonly applicationGroups?: ApplicationGroupsOptions;
|
|
12
|
+
readonly sourceCorrectionsFile?: string;
|
|
11
13
|
readonly service: {
|
|
12
14
|
readonly namespace: string;
|
|
13
15
|
readonly local: string;
|
|
@@ -85,6 +87,57 @@ export declare function parseCheckedComponentOptions(value: unknown): CheckedCom
|
|
|
85
87
|
* @throws TypeError For malformed or unsupported controls.
|
|
86
88
|
*/
|
|
87
89
|
export declare function parseCheckedComponentChoices(value: unknown): Partial<CheckedComponentOptions>;
|
|
90
|
+
export interface PreparedCheckedSourceInputs {
|
|
91
|
+
readonly main: SchemaInput;
|
|
92
|
+
readonly companions: ReadonlyMap<string, SchemaInput>;
|
|
93
|
+
readonly provenance: Awaited<ReturnType<typeof prepareCorrectedSourceInputs>>['provenance'] & {
|
|
94
|
+
readonly recipeFile: {
|
|
95
|
+
readonly digest: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Acquire the complete authorized original source closure for correction and diagnostic inventory.
|
|
101
|
+
* @param source Main source location, retaining its configured logical URI.
|
|
102
|
+
* @param config Explicit loading scope and companion sources.
|
|
103
|
+
* @param acquiredMain Already acquired main closure, reused without another source read.
|
|
104
|
+
* @returns Original main and companion inputs with their shared loading policy and work accounting.
|
|
105
|
+
* @throws For unavailable or unauthorized sources and exhausted source limits.
|
|
106
|
+
* @constraints Acquisition does not qualify sources or grant response authority.
|
|
107
|
+
*/
|
|
108
|
+
export declare function acquireCheckedSourceInputs(source: string, config: Pick<CheckedComponentOptions, 'fileRoots' | 'sourceOrigins' | 'sourceResources'> & Partial<CheckedComponentOptions>, acquiredMain?: SchemaInput): Promise<{
|
|
109
|
+
main: Readonly<{
|
|
110
|
+
root: import("./loader/orderedSyntax.js").SyntaxDocument;
|
|
111
|
+
documents: readonly import("./loader/orderedSyntax.js").SyntaxDocument[];
|
|
112
|
+
schemas: readonly import("./loader/schemaInput.js").SchemaInterpretation[];
|
|
113
|
+
resources: readonly import("./loader/schemaResources.js").ResourceMetadata[];
|
|
114
|
+
resourceAliases: ReadonlyMap<string, string>;
|
|
115
|
+
edges: readonly import("./loader/schemaInput.js").ResolutionEdge[];
|
|
116
|
+
limits: import("./loader/schemaResources.js").LoadingLimits;
|
|
117
|
+
metrics: Readonly<{
|
|
118
|
+
resources: number;
|
|
119
|
+
totalBytes: number;
|
|
120
|
+
ioMs: number;
|
|
121
|
+
}>;
|
|
122
|
+
}>;
|
|
123
|
+
companions: Map<string, Readonly<{
|
|
124
|
+
root: import("./loader/orderedSyntax.js").SyntaxDocument;
|
|
125
|
+
documents: readonly import("./loader/orderedSyntax.js").SyntaxDocument[];
|
|
126
|
+
schemas: readonly import("./loader/schemaInput.js").SchemaInterpretation[];
|
|
127
|
+
resources: readonly import("./loader/schemaResources.js").ResourceMetadata[];
|
|
128
|
+
resourceAliases: ReadonlyMap<string, string>;
|
|
129
|
+
edges: readonly import("./loader/schemaInput.js").ResolutionEdge[];
|
|
130
|
+
limits: import("./loader/schemaResources.js").LoadingLimits;
|
|
131
|
+
metrics: Readonly<{
|
|
132
|
+
resources: number;
|
|
133
|
+
totalBytes: number;
|
|
134
|
+
ioMs: number;
|
|
135
|
+
}>;
|
|
136
|
+
}>>;
|
|
137
|
+
loading: SchemaInputOptions;
|
|
138
|
+
}>;
|
|
139
|
+
/** Correct and reload the complete authorized project closure without mutating original sources. */
|
|
140
|
+
export declare function prepareCheckedSourceInputs(source: string, config: Pick<CheckedComponentOptions, 'fileRoots' | 'sourceOrigins' | 'sourceResources'> & Partial<CheckedComponentOptions>, acquiredMain?: SchemaInput): Promise<PreparedCheckedSourceInputs>;
|
|
88
141
|
/**
|
|
89
142
|
* Generate the bounded preview without creating a flattened catalog authority.
|
|
90
143
|
* @param opts Public pipeline options with the checked model explicitly selected.
|
|
@@ -104,7 +157,25 @@ export declare function prepareComponentStages(wsdl: string, config: CheckedComp
|
|
|
104
157
|
gatewayDir: string;
|
|
105
158
|
openapiFile: string;
|
|
106
159
|
basePath: string;
|
|
107
|
-
}): Promise<{
|
|
160
|
+
}, sourcePreparation?: PreparedCheckedSourceInputs): Promise<{
|
|
161
|
+
sourceCorrections?: (Readonly<{
|
|
162
|
+
version: 1;
|
|
163
|
+
canonicalRecipeDigest: string;
|
|
164
|
+
resources: readonly Readonly<{
|
|
165
|
+
uri: string;
|
|
166
|
+
originalDigest: string;
|
|
167
|
+
effectiveDigest: string;
|
|
168
|
+
changed: boolean;
|
|
169
|
+
}>[];
|
|
170
|
+
corrections: readonly Readonly<{
|
|
171
|
+
kind: "set-occurs" | "set-wildcard-namespaces" | "insert-element";
|
|
172
|
+
target: import("./compiler/sourceCorrections.js").SourceCorrectionSelector;
|
|
173
|
+
}>[];
|
|
174
|
+
}> & {
|
|
175
|
+
readonly recipeFile: {
|
|
176
|
+
readonly digest: string;
|
|
177
|
+
};
|
|
178
|
+
}) | undefined;
|
|
108
179
|
expected: ReconstructedComponentSource[];
|
|
109
180
|
projections: Map<string, ComponentRecordProjection>;
|
|
110
181
|
namespaces: Map<string, string>;
|
package/dist/checkedPipeline.js
CHANGED
|
@@ -9,12 +9,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
10
|
import { types } from 'node:util';
|
|
11
11
|
import { createHash } from 'node:crypto';
|
|
12
|
+
import { constants as bufferConstants } from 'node:buffer';
|
|
12
13
|
import { resolveResourceUri } from './loader/schemaResources.js';
|
|
13
14
|
import { selectComponentOperations, inspectDirectOperation } from './compiler/directOperation.js';
|
|
14
15
|
import { prepareComponentSourceForOperation } from './compiler/directSourceCapsule.js';
|
|
15
16
|
import { inspectComponentSourceService } from './compiler/directSourceCapsule.js';
|
|
16
17
|
import { generateComponentClient, writeComponentServiceClient } from './client/generateDirectClient.js';
|
|
17
|
-
import { copyOriginalSchemaResource, prepareSchemaSnapshotFiles } from './loader/schemaInput.js';
|
|
18
|
+
import { copyOriginalSchemaResource, loadSemanticSchemaInput, prepareSchemaSnapshotFiles } from './loader/schemaInput.js';
|
|
19
|
+
import { prepareCorrectedSourceInputs, SourceCorrectionError } from './compiler/sourceCorrections.js';
|
|
18
20
|
import { createApplicationBudget } from './compiler/semanticBudget.js';
|
|
19
21
|
import { directLoadingLimits } from './compiler/directExecutionPolicy.js';
|
|
20
22
|
import { generateComponentServiceGateway } from './gateway/generateCheckedGateway.js';
|
|
@@ -86,7 +88,7 @@ function parseComponentOptions(value, resolved) {
|
|
|
86
88
|
throw new TypeError('Known unique standard libraries required');
|
|
87
89
|
return ids.sort();
|
|
88
90
|
};
|
|
89
|
-
const data = record(value, ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins', 'sourceUri', 'namespaceLocations', 'basePath', 'signal', 'standardLibraries', 'recordProjections', 'applicationGroups'], resolved ? ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins'] : []);
|
|
91
|
+
const data = record(value, ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins', 'sourceUri', 'namespaceLocations', 'basePath', 'signal', 'standardLibraries', 'recordProjections', 'applicationGroups', 'sourceCorrectionsFile'], resolved ? ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins'] : []);
|
|
90
92
|
if (['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins'].some(key => Object.hasOwn(data, key) && data[key] === undefined))
|
|
91
93
|
throw new TypeError('Explicit component choices must not be undefined');
|
|
92
94
|
const selectors = data.selectors === undefined ? undefined : array(data.selectors).map(selector);
|
|
@@ -142,6 +144,7 @@ function parseComponentOptions(value, resolved) {
|
|
|
142
144
|
if (data.signal !== undefined && !(data.signal instanceof AbortSignal))
|
|
143
145
|
throw new TypeError('Native AbortSignal required');
|
|
144
146
|
return { ...(selectors ? { selectors } : {}), ...(data.service === undefined ? {} : { service: name(data.service) }),
|
|
147
|
+
...(data.sourceCorrectionsFile === undefined ? {} : { sourceCorrectionsFile: text(data.sourceCorrectionsFile) }),
|
|
145
148
|
...(Object.hasOwn(data, 'applicationGroups') ? { applicationGroups: parseApplicationGroups(data.applicationGroups) } : {}),
|
|
146
149
|
...(data.port === undefined ? {} : { port: text(data.port) }), ...(fileRoots ? { fileRoots } : {}),
|
|
147
150
|
...(sourceResources ? { sourceResources } : {}), ...(sourceOrigins ? { sourceOrigins } : {}),
|
|
@@ -149,6 +152,98 @@ function parseComponentOptions(value, resolved) {
|
|
|
149
152
|
...(standardLibraries ? { standardLibraries } : {}), ...(recordProjections ? { recordProjections } : {}),
|
|
150
153
|
...(namespaceLocations ? { namespaceLocations } : {}), ...(basePath ? { basePath } : {}), ...(data.signal ? { signal: data.signal } : {}) };
|
|
151
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Acquire the complete authorized original source closure for correction and diagnostic inventory.
|
|
157
|
+
* @param source Main source location, retaining its configured logical URI.
|
|
158
|
+
* @param config Explicit loading scope and companion sources.
|
|
159
|
+
* @param acquiredMain Already acquired main closure, reused without another source read.
|
|
160
|
+
* @returns Original main and companion inputs with their shared loading policy and work accounting.
|
|
161
|
+
* @throws For unavailable or unauthorized sources and exhausted source limits.
|
|
162
|
+
* @constraints Acquisition does not qualify sources or grant response authority.
|
|
163
|
+
*/
|
|
164
|
+
export async function acquireCheckedSourceInputs(source, config, acquiredMain) {
|
|
165
|
+
const tick = () => config.signal?.throwIfAborted();
|
|
166
|
+
const budget = createApplicationBudget();
|
|
167
|
+
tick();
|
|
168
|
+
const loading = { policy: { fileRoots: config.fileRoots, allowedOrigins: config.sourceOrigins },
|
|
169
|
+
namespaceLocations: new Map(config.namespaceLocations?.map(item => [item.namespace, item.location])), signal: config.signal,
|
|
170
|
+
limits: acquiredMain?.limits, chargeWork: amount => { tick(); budget.chargeWork(amount); } };
|
|
171
|
+
const acquire = async (reference, main) => {
|
|
172
|
+
tick();
|
|
173
|
+
const prepared = await prepareSchemaSnapshotFiles(reference, config.sourceResources, { ...loading,
|
|
174
|
+
...(main && config.sourceUri ? { sourceUri: config.sourceUri } : {}) });
|
|
175
|
+
return loadSemanticSchemaInput(main && config.sourceUri ? config.sourceUri : prepared.source, prepared.loading);
|
|
176
|
+
};
|
|
177
|
+
const main = acquiredMain ?? await acquire(source, true);
|
|
178
|
+
loading.limits = main.limits;
|
|
179
|
+
const companions = new Map();
|
|
180
|
+
for (const projection of config.recordProjections ?? []) {
|
|
181
|
+
tick();
|
|
182
|
+
const key = resolveResourceUri(projection.source);
|
|
183
|
+
if (!companions.has(key))
|
|
184
|
+
companions.set(key, key === main.root.uri ? main : await acquire(projection.source, false));
|
|
185
|
+
}
|
|
186
|
+
return { main, companions, loading };
|
|
187
|
+
}
|
|
188
|
+
/** Correct and reload the complete authorized project closure without mutating original sources. */
|
|
189
|
+
export async function prepareCheckedSourceInputs(source, config, acquiredMain) {
|
|
190
|
+
if (!config.sourceCorrectionsFile)
|
|
191
|
+
throw new TypeError('Source correction preparation requires sourceCorrectionsFile');
|
|
192
|
+
const { main, companions, loading } = await acquireCheckedSourceInputs(source, config, acquiredMain);
|
|
193
|
+
const tick = () => config.signal?.throwIfAborted();
|
|
194
|
+
const recipeFile = path.resolve(config.sourceCorrectionsFile);
|
|
195
|
+
tick();
|
|
196
|
+
const handle = await fs.promises.open(recipeFile, 'r');
|
|
197
|
+
let bytes;
|
|
198
|
+
try {
|
|
199
|
+
const stat = await handle.stat();
|
|
200
|
+
const maximum = Math.min(main.limits.resourceBytes, bufferConstants.MAX_LENGTH - 1);
|
|
201
|
+
if (!stat.isFile() || !Number.isSafeInteger(stat.size) || stat.size > maximum)
|
|
202
|
+
throw new RangeError('Source correction recipe exceeds the source resource byte capacity');
|
|
203
|
+
// @constraints One extra byte detects growth without permitting an unbounded read after the size check.
|
|
204
|
+
const buffer = Buffer.alloc(stat.size + 1);
|
|
205
|
+
let offset = 0;
|
|
206
|
+
while (offset < buffer.length) {
|
|
207
|
+
tick();
|
|
208
|
+
const result = await handle.read(buffer, offset, buffer.length - offset, null);
|
|
209
|
+
if (!result.bytesRead)
|
|
210
|
+
break;
|
|
211
|
+
offset += result.bytesRead;
|
|
212
|
+
}
|
|
213
|
+
tick();
|
|
214
|
+
if (offset !== stat.size)
|
|
215
|
+
throw new TypeError('Source correction recipe changed during acquisition');
|
|
216
|
+
bytes = buffer.subarray(0, offset);
|
|
217
|
+
loading.chargeWork?.(512 + bytes.byteLength * 8);
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
await handle.close();
|
|
221
|
+
}
|
|
222
|
+
tick();
|
|
223
|
+
let recipe;
|
|
224
|
+
try {
|
|
225
|
+
recipe = JSON.parse(bytes.toString('utf8'));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
throw new SourceCorrectionError('invalid-recipe', 'Source correction file must contain valid JSON');
|
|
229
|
+
}
|
|
230
|
+
const corrected = prepareCorrectedSourceInputs([main, ...companions.values()], recipe, config.signal);
|
|
231
|
+
const effective = new Map();
|
|
232
|
+
const reload = async (input) => {
|
|
233
|
+
const previous = effective.get(input.root.uri);
|
|
234
|
+
if (previous)
|
|
235
|
+
return previous;
|
|
236
|
+
const result = await loadSemanticSchemaInput(input.root.uri, { ...loading, offlineResources: corrected.resources });
|
|
237
|
+
effective.set(input.root.uri, result);
|
|
238
|
+
return result;
|
|
239
|
+
};
|
|
240
|
+
const effectiveMain = await reload(main);
|
|
241
|
+
const effectiveCompanions = new Map();
|
|
242
|
+
for (const [key, input] of companions)
|
|
243
|
+
effectiveCompanions.set(key, await reload(input));
|
|
244
|
+
return { main: effectiveMain, companions: effectiveCompanions, provenance: { ...corrected.provenance,
|
|
245
|
+
recipeFile: { digest: createHash('sha256').update(bytes).digest('hex') } } };
|
|
246
|
+
}
|
|
152
247
|
/**
|
|
153
248
|
* Generate the bounded preview without creating a flattened catalog authority.
|
|
154
249
|
* @param opts Public pipeline options with the checked model explicitly selected.
|
|
@@ -210,7 +305,9 @@ export async function runCheckedPipeline(opts) {
|
|
|
210
305
|
basePath: planning.config.basePath ?? `/${versionSlug}/${serviceSlug}` }, planning.native, planning.report);
|
|
211
306
|
}
|
|
212
307
|
/** Prepare through native owners only; internal handles remain within this invocation. */
|
|
213
|
-
export async function prepareComponentStages(wsdl, config, acquired, outputs = { clientDir: 'client', gatewayDir: 'gateway', openapiFile: 'openapi.json', basePath: '/v1/service' }) {
|
|
308
|
+
export async function prepareComponentStages(wsdl, config, acquired, outputs = { clientDir: 'client', gatewayDir: 'gateway', openapiFile: 'openapi.json', basePath: '/v1/service' }, sourcePreparation) {
|
|
309
|
+
const correctedSources = sourcePreparation ?? (config.sourceCorrectionsFile ? await prepareCheckedSourceInputs(wsdl, config, acquired) : undefined);
|
|
310
|
+
acquired = correctedSources?.main ?? acquired;
|
|
214
311
|
const tick = () => { if (config.signal?.aborted)
|
|
215
312
|
throw new Error('Checked component generation cancelled'); };
|
|
216
313
|
const namespaces = new Map(config.namespaceLocations?.map(location => [location.namespace, location.location]));
|
|
@@ -242,10 +339,16 @@ export async function prepareComponentStages(wsdl, config, acquired, outputs = {
|
|
|
242
339
|
for (const group of groups.values()) {
|
|
243
340
|
tick();
|
|
244
341
|
const first = group[0];
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
342
|
+
const companionInput = correctedSources?.companions.get(resolveResourceUri(first.source));
|
|
343
|
+
const companionOffline = companionInput ? new Map([...companionInput.resourceAliases].map(([alias, uri]) => {
|
|
344
|
+
const resource = companionInput.resources.find(item => item.uri === uri);
|
|
345
|
+
return [alias, { bytes: copyOriginalSchemaResource(companionInput, uri, loading.chargeWork), digest: resource.digest, canonicalUri: uri }];
|
|
346
|
+
})) : undefined;
|
|
347
|
+
const source = companionInput ? { source: companionInput.root.uri, loading: { ...loading, offlineResources: companionOffline } }
|
|
348
|
+
: await prepareSchemaSnapshotFiles(first.source, config.sourceResources, { policy: {
|
|
349
|
+
fileRoots: config.fileRoots, allowedOrigins: config.sourceOrigins
|
|
350
|
+
}, namespaceLocations: namespaces,
|
|
351
|
+
limits: directLoadingLimits(budget), signal: config.signal, chargeWork: amount => { tick(); budget.chargeWork(amount); } });
|
|
249
352
|
const companions = await selectComponentOperations(source.source, group.map(item => item.recordSelector), { loading: source.loading,
|
|
250
353
|
signal: config.signal, standardLibraries: first.standardLibraries?.map(acquireStandardSchemaLibrary) });
|
|
251
354
|
for (const [index, entry] of group.entries()) {
|
|
@@ -340,7 +443,8 @@ export async function prepareComponentStages(wsdl, config, acquired, outputs = {
|
|
|
340
443
|
}
|
|
341
444
|
}
|
|
342
445
|
}
|
|
343
|
-
return { expected, projections, namespaces, refusals, layout, generatedClients, gateway, applicationGroups
|
|
446
|
+
return { expected, projections, namespaces, refusals, layout, generatedClients, gateway, applicationGroups,
|
|
447
|
+
...(correctedSources ? { sourceCorrections: correctedSources.provenance } : {}) };
|
|
344
448
|
}
|
|
345
449
|
function prepareComponentLayout(expected, projections, tick) {
|
|
346
450
|
const inspection = inspectDirectOperation(expected[0].contract), input = inspection.source;
|
|
@@ -421,6 +525,12 @@ async function runComponentStages(opts, config, outputs, native, report) {
|
|
|
421
525
|
...(native.applicationGroups ? { applicationGroups: native.applicationGroups } : {}),
|
|
422
526
|
selection: { selector: config.selectors[0], selectors: config.selectors, service: config.service, port: config.port }, operations };
|
|
423
527
|
fs.writeFileSync(path.join(clientDir, 'inventory.json'), JSON.stringify(inventory, null, 2) + '\n');
|
|
528
|
+
// @why This generator-owned manifest must describe the current generation, including removal of corrections.
|
|
529
|
+
const correctionManifest = path.join(clientDir, 'source-corrections.json');
|
|
530
|
+
if (native.sourceCorrections)
|
|
531
|
+
fs.writeFileSync(correctionManifest, JSON.stringify(native.sourceCorrections, null, 2) + '\n');
|
|
532
|
+
else
|
|
533
|
+
fs.rmSync(correctionManifest, { force: true });
|
|
424
534
|
const component = { source: root, capsules: Object.fromEntries(Object.entries(contracts).map(([name, file]) => [name, path.resolve(clientDir, file)])),
|
|
425
535
|
sourceResources, sourceOrigins: config.sourceOrigins, ...(new URL(input.root.uri).protocol === 'file:' ? {} : { sourceUri: input.root.uri }),
|
|
426
536
|
...(namespaceLocations.length ? { namespaceLocations } : {}),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { prepareComponentStages } from './checkedPipeline.js';
|
|
2
|
-
import type { CheckedComponentOptions } from './checkedPipeline.js';
|
|
2
|
+
import type { CheckedComponentOptions, PreparedCheckedSourceInputs } from './checkedPipeline.js';
|
|
3
3
|
import type { DirectOperationSelector } from './compiler/directOperation.js';
|
|
4
|
+
import type { SchemaInput } from './loader/schemaInput.js';
|
|
4
5
|
import type { ExpandedName } from './loader/orderedSyntax.js';
|
|
5
6
|
type Category = 'missing-dependency' | 'permission' | 'ambiguous-selection' | 'invalid-source' | 'unsupported-capability' | 'inconclusive' | 'cancelled' | 'resource-limit' | 'operational-failure' | 'configuration';
|
|
6
7
|
export interface ComponentPlanBlocker {
|
|
@@ -115,9 +116,11 @@ export declare function prepareCheckedComponentPlan(source: string, choices?: un
|
|
|
115
116
|
gatewayDir: string;
|
|
116
117
|
openapiFile: string;
|
|
117
118
|
basePath: string;
|
|
118
|
-
}): Promise<{
|
|
119
|
+
}, inspectionSelector?: DirectOperationSelector): Promise<{
|
|
119
120
|
report: CheckedComponentPlan;
|
|
120
121
|
config?: CheckedComponentOptions;
|
|
121
122
|
native?: Awaited<ReturnType<typeof prepareComponentStages>>;
|
|
123
|
+
sources?: PreparedCheckedSourceInputs;
|
|
124
|
+
sourceInput?: SchemaInput;
|
|
122
125
|
}>;
|
|
123
126
|
export {};
|
package/dist/checkedPlanning.js
CHANGED
|
@@ -6,9 +6,10 @@ import path from 'node:path';
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { types } from 'node:util';
|
|
9
|
-
import { parseCheckedComponentOptions, parseCheckedComponentChoices, prepareComponentStages } from './checkedPipeline.js';
|
|
9
|
+
import { parseCheckedComponentOptions, parseCheckedComponentChoices, prepareComponentStages, prepareCheckedSourceInputs } from './checkedPipeline.js';
|
|
10
10
|
import { inspectDirectOperation } from './compiler/directOperation.js';
|
|
11
11
|
import { loadSemanticSchemaInput, prepareSchemaSnapshotFiles } from './loader/schemaInput.js';
|
|
12
|
+
import { SourceCorrectionError } from './compiler/sourceCorrections.js';
|
|
12
13
|
import { resolveResourceUri } from './loader/schemaResources.js';
|
|
13
14
|
import { syntaxElements, syntaxAttribute, resolveLexicalQName, WSDL_NAMESPACE, XSD_NAMESPACE, SchemaLoadingError } from './loader/orderedSyntax.js';
|
|
14
15
|
const same = (a, b) => a.namespace === b.namespace && a.local === b.local;
|
|
@@ -55,7 +56,7 @@ export function writeCheckedPlanningFile(file, value, force = false) {
|
|
|
55
56
|
fs.writeFileSync(file, JSON.stringify(value, null, 2) + '\n', { flag: force ? 'w' : 'wx' });
|
|
56
57
|
}
|
|
57
58
|
// @constraints Internal composition only. Native handles are never attached to the public report.
|
|
58
|
-
export async function prepareCheckedComponentPlan(source, choices = {}, outputs) {
|
|
59
|
+
export async function prepareCheckedComponentPlan(source, choices = {}, outputs, inspectionSelector) {
|
|
59
60
|
const documents = [], blockers = [];
|
|
60
61
|
let complete = false, config;
|
|
61
62
|
let intake;
|
|
@@ -104,6 +105,8 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
|
|
|
104
105
|
order(operations);
|
|
105
106
|
};
|
|
106
107
|
let native;
|
|
108
|
+
let correctedSources;
|
|
109
|
+
let sourceInput;
|
|
107
110
|
let preparingNative = false;
|
|
108
111
|
try {
|
|
109
112
|
if (!choices || typeof choices !== 'object' || types.isProxy(choices) || ![null, Object.prototype].includes(Object.getPrototypeOf(choices)))
|
|
@@ -136,7 +139,25 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
|
|
|
136
139
|
namespaceLocations: new Map(intake.namespaceLocations?.map(n => [n.namespace, n.location])),
|
|
137
140
|
onDocument: (document) => { documents.push(document); } };
|
|
138
141
|
const prepared = Object.hasOwn(explicit, 'sourceResources') ? await prepareSchemaSnapshotFiles(source, intake.sourceResources, loading) : undefined;
|
|
139
|
-
|
|
142
|
+
let input = await loadSemanticSchemaInput(intake.sourceUri ?? prepared?.source ?? source, prepared?.loading ?? loading);
|
|
143
|
+
sourceInput = input;
|
|
144
|
+
correctedSources = intake.sourceCorrectionsFile ? await prepareCheckedSourceInputs(source, intake, input) : undefined;
|
|
145
|
+
if (correctedSources) {
|
|
146
|
+
input = correctedSources.main;
|
|
147
|
+
sourceInput = input;
|
|
148
|
+
documents.splice(0, documents.length, ...input.documents);
|
|
149
|
+
}
|
|
150
|
+
// @why Correction pins authenticate the complete project closure. Narrow only
|
|
151
|
+
// after preparation, so inspecting one operation cannot discard pinned companions.
|
|
152
|
+
if (inspectionSelector) {
|
|
153
|
+
const selected = (candidate) => candidate.operation === inspectionSelector.operation
|
|
154
|
+
&& same(candidate.portType, inspectionSelector.portType) && same(candidate.binding, inspectionSelector.binding);
|
|
155
|
+
if (intake.selectors && !intake.selectors.some(selected))
|
|
156
|
+
throw new TypeError('Inspection selector is not a configured operation');
|
|
157
|
+
intake = { ...intake, selectors: [inspectionSelector],
|
|
158
|
+
...(intake.recordProjections ? { recordProjections: intake.recordProjections.filter(entry => selected(entry.selector)) } : {}) };
|
|
159
|
+
explicit.selectors = [inspectionSelector];
|
|
160
|
+
}
|
|
140
161
|
complete = true;
|
|
141
162
|
inventory();
|
|
142
163
|
const possible = candidates.filter(c => (!Object.hasOwn(explicit, 'service') || intake.service !== undefined && same(c.service, intake.service))
|
|
@@ -178,7 +199,7 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
|
|
|
178
199
|
config = { ...config, sourceResources: [...config.sourceResources, ...input.resources.filter(r => r.uri !== input.root.uri && !known.has(r.uri) && new URL(r.uri).protocol === 'file:')
|
|
179
200
|
.map(r => ({ uri: r.uri, file: fileURLToPath(r.uri) }))] };
|
|
180
201
|
preparingNative = true;
|
|
181
|
-
native = await prepareComponentStages(source, config, !prepared && new URL(input.root.uri).protocol !== 'file:' ? input : undefined, outputs);
|
|
202
|
+
native = await prepareComponentStages(source, config, correctedSources ? input : !prepared && new URL(input.root.uri).protocol !== 'file:' ? input : undefined, outputs, correctedSources);
|
|
182
203
|
if (native.expected.length) {
|
|
183
204
|
const fresh = inspectDirectOperation(native.expected[0].contract).source;
|
|
184
205
|
if (input.resources.length !== fresh.resources.length || input.resources.some(r => !fresh.resources.some(n => n.uri === r.uri && n.digest === r.digest))) {
|
|
@@ -233,6 +254,9 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
|
|
|
233
254
|
}
|
|
234
255
|
if (intake?.signal?.aborted)
|
|
235
256
|
add('cancelled', complete ? 'native-preparation' : 'loading', 'caller-cancelled', 'Rerun with an active signal.', 'signal');
|
|
257
|
+
else if (error instanceof SourceCorrectionError) {
|
|
258
|
+
add(error.category === 'resource-limit' ? 'resource-limit' : error.category === 'cancelled' ? 'cancelled' : 'configuration', 'configuration', `source-corrections:${error.category}`, 'Review the pinned correction file against the original source inventory; regenerate stale drafts or supply replacement snapshots for unsupported edits.', 'checkedComponents.sourceCorrectionsFile', selectors);
|
|
259
|
+
}
|
|
236
260
|
else if (error instanceof SchemaLoadingError && error.message === 'Duplicate logical schema snapshot') {
|
|
237
261
|
add('configuration', 'configuration', 'duplicate-logical-snapshot', 'Supply unique logical snapshot identities.', 'checkedComponents.sourceResources');
|
|
238
262
|
}
|
|
@@ -264,5 +288,6 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
|
|
|
264
288
|
instructions: 'Save configuration as checked JSON. Replace REQUIRED_SCHEMA_LOCATION with an authorized schema path; paths resolve from the invocation working directory. Rerun the same pipeline with --checked-config. Reports never authorize generation.',
|
|
265
289
|
configuration: { ...Object.fromEntries(Object.entries(explicit).filter(([key]) => key !== 'signal')), namespaceLocations: unresolved }
|
|
266
290
|
} } : {}) };
|
|
267
|
-
return { report, ...(config ? { config } : {}), ...(native ? { native } : {}) }
|
|
291
|
+
return { report, ...(config ? { config } : {}), ...(native ? { native } : {}), ...(correctedSources ? { sources: correctedSources } : {}),
|
|
292
|
+
...(sourceInput ? { sourceInput } : {}) };
|
|
268
293
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { CheckedComponentOptions } from './checkedPipeline.js';
|
|
2
|
+
import type { DirectOperationSelector } from './compiler/directOperation.js';
|
|
3
|
+
import type { ObservedSoapResponseMetadata } from './compiler/directSoap.js';
|
|
4
|
+
import type { DirectXmlLimits } from './compiler/directCodec.js';
|
|
5
|
+
export interface CheckedResponseInspectionOptions {
|
|
6
|
+
readonly checkedComponents?: Partial<CheckedComponentOptions>;
|
|
7
|
+
readonly selector: DirectOperationSelector;
|
|
8
|
+
readonly responseBytes: Uint8Array;
|
|
9
|
+
readonly responseMetadata?: ObservedSoapResponseMetadata;
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
readonly limits?: DirectXmlLimits;
|
|
12
|
+
readonly includeCorrectionDraft?: boolean;
|
|
13
|
+
}
|
|
14
|
+
type Assessment = 'conforming' | 'mismatch' | 'refused' | 'unassessed';
|
|
15
|
+
interface ScopedOutcome {
|
|
16
|
+
readonly assessment: Assessment;
|
|
17
|
+
readonly rule?: string;
|
|
18
|
+
}
|
|
19
|
+
interface InspectionLocation {
|
|
20
|
+
readonly resource: string;
|
|
21
|
+
readonly digest: string;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
readonly line: number;
|
|
24
|
+
readonly column: number;
|
|
25
|
+
}
|
|
26
|
+
interface InspectionDiagnostic {
|
|
27
|
+
readonly scope: 'preparation' | 'response' | 'wrapper' | 'companion';
|
|
28
|
+
readonly kind: string;
|
|
29
|
+
readonly rule: string;
|
|
30
|
+
readonly guidance: string;
|
|
31
|
+
readonly source?: InspectionLocation;
|
|
32
|
+
readonly schemaPath?: string;
|
|
33
|
+
readonly payloadPath?: string;
|
|
34
|
+
}
|
|
35
|
+
/** A non-applicable recipe draft; null fields require an explicit consumer decision.
|
|
36
|
+
* @since Unreleased
|
|
37
|
+
*/
|
|
38
|
+
export interface CheckedResponseCorrectionDraft {
|
|
39
|
+
readonly version: 1;
|
|
40
|
+
readonly resources: readonly {
|
|
41
|
+
readonly uri: string;
|
|
42
|
+
readonly digest: string;
|
|
43
|
+
}[];
|
|
44
|
+
readonly corrections: readonly Readonly<Record<string, unknown>>[];
|
|
45
|
+
}
|
|
46
|
+
/** Bounded diagnostic data without XML, application values, native handles or acceptance authority.
|
|
47
|
+
* @since Unreleased
|
|
48
|
+
*/
|
|
49
|
+
export interface CheckedResponseInspectionReport {
|
|
50
|
+
readonly version: 'checked-response-inspection-v1';
|
|
51
|
+
readonly kind: 'conforming' | 'mismatch' | 'refused';
|
|
52
|
+
readonly scope: 'xml-only' | 'full-response';
|
|
53
|
+
readonly admitted: false;
|
|
54
|
+
readonly executed: false;
|
|
55
|
+
readonly selector: DirectOperationSelector;
|
|
56
|
+
readonly response: {
|
|
57
|
+
readonly digest?: string;
|
|
58
|
+
readonly digestAssessment: 'computed' | 'unassessed-limit' | 'unassessed-cancelled';
|
|
59
|
+
readonly byteLength: number;
|
|
60
|
+
};
|
|
61
|
+
readonly http: {
|
|
62
|
+
readonly assessment: Assessment | 'not-established';
|
|
63
|
+
readonly status?: number;
|
|
64
|
+
};
|
|
65
|
+
readonly fault: {
|
|
66
|
+
readonly assessment: 'unassessed' | 'conforming';
|
|
67
|
+
readonly observed?: boolean;
|
|
68
|
+
readonly declared?: boolean;
|
|
69
|
+
};
|
|
70
|
+
readonly resources: readonly {
|
|
71
|
+
readonly resource: string;
|
|
72
|
+
readonly digest: string;
|
|
73
|
+
}[];
|
|
74
|
+
readonly outcomes: {
|
|
75
|
+
readonly response: ScopedOutcome;
|
|
76
|
+
readonly wrapper?: ScopedOutcome;
|
|
77
|
+
readonly companion?: ScopedOutcome;
|
|
78
|
+
};
|
|
79
|
+
readonly diagnostics: readonly InspectionDiagnostic[];
|
|
80
|
+
readonly correctionDraft?: CheckedResponseCorrectionDraft;
|
|
81
|
+
readonly sourceCorrections?: {
|
|
82
|
+
readonly recipeDigest: string;
|
|
83
|
+
readonly resources: readonly {
|
|
84
|
+
readonly resource: string;
|
|
85
|
+
readonly originalDigest: string;
|
|
86
|
+
readonly effectiveDigest: string;
|
|
87
|
+
readonly changed: boolean;
|
|
88
|
+
}[];
|
|
89
|
+
};
|
|
90
|
+
readonly truncated: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** Inspect actual caller-owned SOAP bytes without constructing a request or contacting any endpoint.
|
|
93
|
+
* @param source Local WSDL path, retaining optional configured logical snapshot identities.
|
|
94
|
+
* @param options Full operation selector, closed checked choices, actual bytes and optional actual HTTP metadata.
|
|
95
|
+
* @returns A bounded value-redacted report; independently conforming companion diagnostics never erase outer failures.
|
|
96
|
+
* @throws TypeError For malformed invocation data; RangeError for invalid capacities; filesystem errors for unavailable input files.
|
|
97
|
+
* @sideEffects Reads authorized local metadata only; creates no files and performs no SOAP transport.
|
|
98
|
+
*/
|
|
99
|
+
export declare function inspectCheckedResponse(source: string, options: CheckedResponseInspectionOptions): Promise<CheckedResponseInspectionReport>;
|
|
100
|
+
export {};
|