@techspokes/typescript-wsdl-client 0.16.1 → 0.18.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 +5 -0
- package/dist/app/generateApp.d.ts.map +1 -1
- package/dist/app/generateApp.js +2 -1
- package/dist/cli.js +46 -2
- package/dist/client/generateClient.d.ts.map +1 -1
- package/dist/client/generateClient.js +62 -6
- package/dist/client/generateOperations.d.ts.map +1 -1
- package/dist/client/generateOperations.js +27 -4
- package/dist/compiler/schemaCompiler.d.ts +40 -11
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +81 -6
- package/dist/compiler/shapeResolver.d.ts +18 -0
- package/dist/compiler/shapeResolver.d.ts.map +1 -0
- package/dist/compiler/shapeResolver.js +280 -0
- package/dist/gateway/generateGateway.d.ts.map +1 -1
- package/dist/gateway/generateGateway.js +2 -1
- package/dist/gateway/generators.d.ts +13 -1
- package/dist/gateway/generators.d.ts.map +1 -1
- package/dist/gateway/generators.js +98 -13
- package/dist/gateway/helpers.d.ts +16 -0
- package/dist/gateway/helpers.d.ts.map +1 -1
- package/dist/gateway/helpers.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
- package/dist/openapi/generateOpenAPI.js +28 -0
- package/dist/pipeline.d.ts +13 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +17 -1
- package/dist/runtime/ndjson.d.ts +24 -0
- package/dist/runtime/ndjson.d.ts.map +1 -0
- package/dist/runtime/ndjson.js +30 -0
- package/dist/runtime/streamXml.d.ts +45 -0
- package/dist/runtime/streamXml.d.ts.map +1 -0
- package/dist/runtime/streamXml.js +212 -0
- package/dist/test/generators.d.ts.map +1 -1
- package/dist/test/generators.js +50 -0
- package/dist/test/mockData.d.ts +6 -0
- package/dist/test/mockData.d.ts.map +1 -1
- package/dist/test/mockData.js +6 -2
- package/dist/util/cli.d.ts.map +1 -1
- package/dist/util/cli.js +3 -1
- package/dist/util/runtimeSource.d.ts +2 -0
- package/dist/util/runtimeSource.d.ts.map +1 -0
- package/dist/util/runtimeSource.js +38 -0
- package/dist/util/streamConfig.d.ts +59 -0
- package/dist/util/streamConfig.d.ts.map +1 -0
- package/dist/util/streamConfig.js +230 -0
- package/docs/README.md +1 -0
- package/docs/api-reference.md +146 -0
- package/docs/architecture.md +27 -5
- package/docs/cli-reference.md +30 -0
- package/docs/concepts.md +51 -0
- package/docs/configuration.md +40 -0
- package/docs/decisions/002-streamable-responses.md +308 -0
- package/docs/gateway-guide.md +37 -0
- package/docs/generated-code.md +21 -0
- package/docs/migration-playbook.md +33 -0
- package/docs/migration.md +31 -6
- package/docs/output-anatomy.md +49 -0
- package/docs/production.md +32 -0
- package/docs/start-here.md +33 -0
- package/docs/supported-patterns.md +2 -0
- package/docs/testing.md +14 -0
- package/docs/troubleshooting.md +18 -0
- package/package.json +9 -5
- package/src/runtime/clientStreamMethods.tpl.txt +183 -0
- package/src/runtime/ndjson.ts +32 -0
- package/src/runtime/operationsStreamHelper.tpl.txt +13 -0
- package/src/runtime/streamXml.ts +293 -0
|
@@ -183,6 +183,34 @@ export async function generateOpenAPI(opts) {
|
|
|
183
183
|
const op = compiled.operations.find(o => o.name === opId);
|
|
184
184
|
if (!op)
|
|
185
185
|
continue;
|
|
186
|
+
if (op.stream) {
|
|
187
|
+
// Stream operations bypass the standard JSON envelope for 200. Emit
|
|
188
|
+
// the configured media type with an itemSchema carried under the
|
|
189
|
+
// x-wsdl-tsc-stream extension so generated tooling can find the
|
|
190
|
+
// record schema without pretending NDJSON is a single JSON document.
|
|
191
|
+
// Errors raised before the first byte still use the standard envelope.
|
|
192
|
+
const recordType = op.stream.recordTypeName;
|
|
193
|
+
const itemRef = { $ref: `#/components/schemas/${recordType}` };
|
|
194
|
+
if (methodObj.responses?.["200"]) {
|
|
195
|
+
methodObj.responses["200"] = {
|
|
196
|
+
description: "Successful streamed SOAP operation response",
|
|
197
|
+
content: {
|
|
198
|
+
[op.stream.mediaType]: {
|
|
199
|
+
schema: { type: "string" },
|
|
200
|
+
"x-wsdl-tsc-stream": {
|
|
201
|
+
format: op.stream.format,
|
|
202
|
+
itemSchema: itemRef,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (methodObj.responses?.default) {
|
|
209
|
+
methodObj.responses.default.description = "Error raised before any stream record was emitted (standard envelope).";
|
|
210
|
+
methodObj.responses.default.content = { "application/json": { schema: { $ref: `#/components/schemas/${baseEnvelopeName}` } } };
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
186
214
|
const payloadType = op.outputTypeName ?? op.outputElement?.local;
|
|
187
215
|
const baseForExt = payloadType || op.name;
|
|
188
216
|
const extName = joinWithNamespace(baseForExt, envelopeNamespace);
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type GenerateOpenAPIOptions } from "./openapi/generateOpenAPI.js";
|
|
2
2
|
import { type GenerateGatewayOptions } from "./gateway/generateGateway.js";
|
|
3
3
|
import { type CompilerOptions } from "./config.js";
|
|
4
|
+
import { type StreamConfig } from "./util/streamConfig.js";
|
|
4
5
|
/**
|
|
5
6
|
* Configuration options for the generation pipeline
|
|
6
7
|
*
|
|
@@ -45,6 +46,18 @@ export interface PipelineOptions {
|
|
|
45
46
|
force?: boolean;
|
|
46
47
|
flattenArrayWrappers?: boolean;
|
|
47
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Path to a stream configuration JSON file. Loaded and parsed once before
|
|
51
|
+
* compilation; the parsed value takes precedence over `streamConfig` when
|
|
52
|
+
* both are set. Leave undefined to preserve byte-for-byte buffered output.
|
|
53
|
+
*/
|
|
54
|
+
streamConfigFile?: string;
|
|
55
|
+
/**
|
|
56
|
+
* In-memory stream configuration. Useful for programmatic callers that
|
|
57
|
+
* do not want to round-trip through a file. Ignored when
|
|
58
|
+
* `streamConfigFile` is also provided.
|
|
59
|
+
*/
|
|
60
|
+
streamConfig?: StreamConfig;
|
|
48
61
|
}
|
|
49
62
|
/**
|
|
50
63
|
* Runs the complete generation pipeline from WSDL to TypeScript artifacts and optionally OpenAPI/Gateway/App
|
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAC,KAAK,eAAe,EAAyB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAC,KAAK,eAAe,EAAyB,MAAM,aAAa,CAAC;AAEzE,OAAO,EAAuB,KAAK,YAAY,EAAC,MAAM,wBAAwB,CAAC;AAG/E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1G,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,iBAAiB,CAAC,GAAG;QAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IACF;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,CAAC;CAAE,CAAC,CAiKhH"}
|
package/dist/pipeline.js
CHANGED
|
@@ -19,6 +19,8 @@ import { generateOpenAPI } from "./openapi/generateOpenAPI.js";
|
|
|
19
19
|
import { generateGateway } from "./gateway/generateGateway.js";
|
|
20
20
|
import { resolveCompilerOptions } from "./config.js";
|
|
21
21
|
import { emitClientArtifacts, reportCompilationStats, reportOpenApiSuccess, success } from "./util/cli.js";
|
|
22
|
+
import { loadStreamConfigFile } from "./util/streamConfig.js";
|
|
23
|
+
import { applyShapeCatalogs } from "./compiler/shapeResolver.js";
|
|
22
24
|
/**
|
|
23
25
|
* Runs the complete generation pipeline from WSDL to TypeScript artifacts and optionally OpenAPI/Gateway/App
|
|
24
26
|
*
|
|
@@ -52,10 +54,24 @@ export async function runGenerationPipeline(opts) {
|
|
|
52
54
|
wsdl: opts.wsdl,
|
|
53
55
|
out: workingDir,
|
|
54
56
|
});
|
|
57
|
+
// Resolve stream configuration (file path takes precedence over in-memory).
|
|
58
|
+
const streamConfig = opts.streamConfigFile
|
|
59
|
+
? loadStreamConfigFile(opts.streamConfigFile)
|
|
60
|
+
: opts.streamConfig;
|
|
55
61
|
// Step 1: Load and parse the WSDL document
|
|
56
62
|
const wsdlCatalog = await loadWsdl(opts.wsdl);
|
|
57
63
|
// Step 2: Compile the WSDL into a structured catalog
|
|
58
|
-
const compiled = compileCatalog(wsdlCatalog, finalCompiler);
|
|
64
|
+
const compiled = compileCatalog(wsdlCatalog, finalCompiler, streamConfig);
|
|
65
|
+
// Step 2b: Apply companion-catalog shape resolution when a stream config is
|
|
66
|
+
// provided. Relative paths in shapeCatalogs resolve against the stream
|
|
67
|
+
// config file's directory, or the WSDL's directory when the config came in
|
|
68
|
+
// programmatically.
|
|
69
|
+
if (streamConfig) {
|
|
70
|
+
const shapeBaseDir = opts.streamConfigFile
|
|
71
|
+
? path.dirname(path.resolve(opts.streamConfigFile))
|
|
72
|
+
: path.dirname(path.resolve(opts.wsdl));
|
|
73
|
+
await applyShapeCatalogs(compiled, streamConfig, { baseDir: shapeBaseDir });
|
|
74
|
+
}
|
|
59
75
|
// Report compilation statistics
|
|
60
76
|
reportCompilationStats(wsdlCatalog, compiled);
|
|
61
77
|
// Step 3: Emit catalog.json (always, to the specified catalogOut path)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NDJSON adapter for record iterables.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 of ADR-002. Given an `AsyncIterable<T>` (typically the output of
|
|
5
|
+
* `parseRecords`), produce a Node `Readable` that emits one JSON-encoded line
|
|
6
|
+
* per record and respects downstream backpressure.
|
|
7
|
+
*
|
|
8
|
+
* Terminal-error policy (Q3 resolved): the stream aborts on source errors.
|
|
9
|
+
* Before-first-byte errors surface as the stream's `error` event before any
|
|
10
|
+
* bytes are pushed, so Fastify can translate them into a normal JSON error
|
|
11
|
+
* envelope. Errors after the first byte propagate as `error` events too, but
|
|
12
|
+
* callers should treat them as a truncated response.
|
|
13
|
+
*/
|
|
14
|
+
import { Readable } from "node:stream";
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an async iterable of records in a Node `Readable` stream that emits
|
|
17
|
+
* NDJSON (one JSON document per line, LF-terminated). Downstream backpressure
|
|
18
|
+
* is honored via `Readable.from`'s default behavior: the iterator's `next()`
|
|
19
|
+
* is not called until the internal buffer has room.
|
|
20
|
+
*
|
|
21
|
+
* Source errors are forwarded to the returned stream's `error` event.
|
|
22
|
+
*/
|
|
23
|
+
export declare function toNdjson<T>(records: AsyncIterable<T>): Readable;
|
|
24
|
+
//# sourceMappingURL=ndjson.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ndjson.d.ts","sourceRoot":"","sources":["../../src/runtime/ndjson.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAC;AAErC;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAE/D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NDJSON adapter for record iterables.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 of ADR-002. Given an `AsyncIterable<T>` (typically the output of
|
|
5
|
+
* `parseRecords`), produce a Node `Readable` that emits one JSON-encoded line
|
|
6
|
+
* per record and respects downstream backpressure.
|
|
7
|
+
*
|
|
8
|
+
* Terminal-error policy (Q3 resolved): the stream aborts on source errors.
|
|
9
|
+
* Before-first-byte errors surface as the stream's `error` event before any
|
|
10
|
+
* bytes are pushed, so Fastify can translate them into a normal JSON error
|
|
11
|
+
* envelope. Errors after the first byte propagate as `error` events too, but
|
|
12
|
+
* callers should treat them as a truncated response.
|
|
13
|
+
*/
|
|
14
|
+
import { Readable } from "node:stream";
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an async iterable of records in a Node `Readable` stream that emits
|
|
17
|
+
* NDJSON (one JSON document per line, LF-terminated). Downstream backpressure
|
|
18
|
+
* is honored via `Readable.from`'s default behavior: the iterator's `next()`
|
|
19
|
+
* is not called until the internal buffer has room.
|
|
20
|
+
*
|
|
21
|
+
* Source errors are forwarded to the returned stream's `error` event.
|
|
22
|
+
*/
|
|
23
|
+
export function toNdjson(records) {
|
|
24
|
+
return Readable.from(encode(records), { objectMode: false, encoding: "utf-8" });
|
|
25
|
+
}
|
|
26
|
+
async function* encode(records) {
|
|
27
|
+
for await (const record of records) {
|
|
28
|
+
yield JSON.stringify(record) + "\n";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog-driven parse specification. `recordPath` is an ordered XML element
|
|
3
|
+
* path from the SOAP body payload down to the repeated record element. The
|
|
4
|
+
* path is matched as a suffix of the open-tag stack, so callers may either
|
|
5
|
+
* pre-strip the SOAP envelope or feed the entire response body.
|
|
6
|
+
*
|
|
7
|
+
* Duplicate local names in `recordPath` are supported and expected (Escapia's
|
|
8
|
+
* EVRN content service nests two elements named `EVRN_UnitDescriptiveInfoRS`).
|
|
9
|
+
*/
|
|
10
|
+
export interface RecordParseSpec {
|
|
11
|
+
recordPath: string[];
|
|
12
|
+
/** TypeScript type name of the record, used to look up propMeta. */
|
|
13
|
+
recordTypeName: string;
|
|
14
|
+
/** Attribute bag key to stash XML attributes under. Defaults to "$attributes". */
|
|
15
|
+
attributesKey?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Compiled-catalog child-type map: `childType[typeName][propName] = tsType`.
|
|
18
|
+
* Used to descend into nested complex types and to detect array-valued
|
|
19
|
+
* props (trailing `[]`). Optional — absent means occurrence-based array
|
|
20
|
+
* detection only.
|
|
21
|
+
*/
|
|
22
|
+
childType?: Record<string, Record<string, string>>;
|
|
23
|
+
/**
|
|
24
|
+
* Compiled-catalog prop-meta map: carries min/max/nillable/declaredType.
|
|
25
|
+
* When present, `max > 1` or `max === "unbounded"` drives array emission
|
|
26
|
+
* even for props that happen to occur just once in a given record.
|
|
27
|
+
*/
|
|
28
|
+
propMeta?: Record<string, Record<string, PropMeta>>;
|
|
29
|
+
}
|
|
30
|
+
export interface PropMeta {
|
|
31
|
+
min?: number;
|
|
32
|
+
max?: number | "unbounded";
|
|
33
|
+
nillable?: boolean;
|
|
34
|
+
declaredType?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Consume an async iterable of XML bytes/strings and yield parsed records.
|
|
38
|
+
*
|
|
39
|
+
* The returned iterator is single-pass; iteration must complete (or be
|
|
40
|
+
* interrupted by the consumer via `break`/`return`) before the upstream
|
|
41
|
+
* source is released. Errors from the source or from the SAX parser abort
|
|
42
|
+
* the iteration via a rejected `next()`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseRecords<T = unknown>(source: AsyncIterable<string | Uint8Array>, spec: RecordParseSpec): AsyncIterable<T>;
|
|
45
|
+
//# sourceMappingURL=streamXml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamXml.d.ts","sourceRoot":"","sources":["../../src/runtime/streamXml.ts"],"names":[],"mappings":"AAsBA;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAuB,YAAY,CAAC,CAAC,GAAG,OAAO,EAC7C,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,EAC1C,IAAI,EAAE,eAAe,GACpB,aAAa,CAAC,CAAC,CAAC,CAqGlB"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming SOAP-payload to record iterator.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 of ADR-002. Driven by saxes (chunk-boundary safe — proven in
|
|
5
|
+
* test/research/sax-record-path.test.ts) and the compiled-catalog metadata
|
|
6
|
+
* that the buffered client already relies on. The parser accepts an async
|
|
7
|
+
* iterable of bytes/strings (typically an upstream SOAP HTTP response) and
|
|
8
|
+
* yields fully-materialized record objects as the corresponding end tags
|
|
9
|
+
* close. Consumers never see partial records.
|
|
10
|
+
*
|
|
11
|
+
* Open questions resolved here:
|
|
12
|
+
* Q3 (terminal error policy): stream aborts. Errors that happen before the
|
|
13
|
+
* first record bubble out as a rejected promise from the first
|
|
14
|
+
* iterator.next(). Errors after a record was emitted throw from a
|
|
15
|
+
* later iterator.next() — callers are expected to treat that as a
|
|
16
|
+
* truncated stream.
|
|
17
|
+
* Q4 (saxes placement): runtime dependency of the wsdl-tsc package; the
|
|
18
|
+
* generated client imports the emitted copy of this module and
|
|
19
|
+
* inherits saxes via its own dependency tree.
|
|
20
|
+
*/
|
|
21
|
+
import { SaxesParser } from "saxes";
|
|
22
|
+
/**
|
|
23
|
+
* Consume an async iterable of XML bytes/strings and yield parsed records.
|
|
24
|
+
*
|
|
25
|
+
* The returned iterator is single-pass; iteration must complete (or be
|
|
26
|
+
* interrupted by the consumer via `break`/`return`) before the upstream
|
|
27
|
+
* source is released. Errors from the source or from the SAX parser abort
|
|
28
|
+
* the iteration via a rejected `next()`.
|
|
29
|
+
*/
|
|
30
|
+
export async function* parseRecords(source, spec) {
|
|
31
|
+
const parser = new SaxesParser({ xmlns: false, position: false });
|
|
32
|
+
const recordPath = spec.recordPath;
|
|
33
|
+
if (recordPath.length === 0) {
|
|
34
|
+
throw new Error("parseRecords: recordPath must not be empty");
|
|
35
|
+
}
|
|
36
|
+
const attrsKey = spec.attributesKey ?? "$attributes";
|
|
37
|
+
// Global tag stack, maintained across the entire document.
|
|
38
|
+
const stack = [];
|
|
39
|
+
// Records materialized during the current chunk write, awaiting yield.
|
|
40
|
+
const pending = [];
|
|
41
|
+
// When a parser.on('error') fires we buffer the error for re-throw on the
|
|
42
|
+
// next yield cycle; saxes emits 'error' and continues unless we stop it.
|
|
43
|
+
let parseError = null;
|
|
44
|
+
const openNodes = [];
|
|
45
|
+
parser.on("opentag", (tag) => {
|
|
46
|
+
stack.push(tag.name);
|
|
47
|
+
if (openNodes.length === 0) {
|
|
48
|
+
// Not yet inside a record. Check whether entering the path tail.
|
|
49
|
+
if (tailMatches(stack, recordPath)) {
|
|
50
|
+
openNodes.push(newNode(tag, spec.recordTypeName, null, attrsKey));
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Inside a record: descend.
|
|
55
|
+
const parent = openNodes[openNodes.length - 1];
|
|
56
|
+
parent.hadChildren = true;
|
|
57
|
+
const parentType = parent.typeName;
|
|
58
|
+
const childTypeRaw = parentType ? spec.childType?.[parentType]?.[tag.name] : undefined;
|
|
59
|
+
const childTypeName = childTypeRaw ? stripArraySuffix(childTypeRaw) : null;
|
|
60
|
+
openNodes.push(newNode(tag, childTypeName, tag.name, attrsKey));
|
|
61
|
+
});
|
|
62
|
+
const appendText = (t) => {
|
|
63
|
+
if (openNodes.length > 0)
|
|
64
|
+
openNodes[openNodes.length - 1].textBuf.push(t);
|
|
65
|
+
};
|
|
66
|
+
parser.on("text", appendText);
|
|
67
|
+
parser.on("cdata", appendText);
|
|
68
|
+
parser.on("closetag", (_tag) => {
|
|
69
|
+
if (openNodes.length > 0) {
|
|
70
|
+
const closing = openNodes.pop();
|
|
71
|
+
const value = materialize(closing, attrsKey);
|
|
72
|
+
if (openNodes.length === 0) {
|
|
73
|
+
// We just closed the record root. Stack tail must match once more.
|
|
74
|
+
if (tailMatches(stack, recordPath)) {
|
|
75
|
+
pending.push(value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
assignChild(openNodes[openNodes.length - 1], closing.propName, value, spec);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
stack.pop();
|
|
83
|
+
});
|
|
84
|
+
parser.on("error", (err) => {
|
|
85
|
+
parseError = err;
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
for await (const chunk of source) {
|
|
89
|
+
if (parseError)
|
|
90
|
+
throw parseError;
|
|
91
|
+
const text = typeof chunk === "string" ? chunk : decodeUtf8(chunk);
|
|
92
|
+
parser.write(text);
|
|
93
|
+
if (parseError)
|
|
94
|
+
throw parseError;
|
|
95
|
+
while (pending.length > 0) {
|
|
96
|
+
yield pending.shift();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
parser.close();
|
|
100
|
+
if (parseError)
|
|
101
|
+
throw parseError;
|
|
102
|
+
while (pending.length > 0) {
|
|
103
|
+
yield pending.shift();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
// Best-effort cleanup: detach handlers so the parser can be GC'd even
|
|
108
|
+
// when the consumer aborts iteration early.
|
|
109
|
+
parser.off("opentag");
|
|
110
|
+
parser.off("closetag");
|
|
111
|
+
parser.off("text");
|
|
112
|
+
parser.off("cdata");
|
|
113
|
+
parser.off("error");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function newNode(tag, typeName, propName, attrsKey) {
|
|
117
|
+
const attrs = tag.attributes;
|
|
118
|
+
const obj = {};
|
|
119
|
+
let isNil = false;
|
|
120
|
+
const attrKeys = Object.keys(attrs);
|
|
121
|
+
if (attrKeys.length > 0) {
|
|
122
|
+
// Detect xsi:nil and drop it from the attribute bag — it's a wire-level
|
|
123
|
+
// concern that should not pollute the user-visible record.
|
|
124
|
+
const cleaned = {};
|
|
125
|
+
for (const k of attrKeys) {
|
|
126
|
+
if ((k === "xsi:nil" || k === "nil") && attrs[k] === "true") {
|
|
127
|
+
isNil = true;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
cleaned[k] = attrs[k];
|
|
131
|
+
}
|
|
132
|
+
if (Object.keys(cleaned).length > 0) {
|
|
133
|
+
obj[attrsKey] = cleaned;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
obj,
|
|
138
|
+
typeName,
|
|
139
|
+
propName,
|
|
140
|
+
textBuf: [],
|
|
141
|
+
hadChildren: false,
|
|
142
|
+
isNil,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function materialize(node, attrsKey) {
|
|
146
|
+
if (node.isNil)
|
|
147
|
+
return null;
|
|
148
|
+
if (!node.hadChildren) {
|
|
149
|
+
// Leaf with no child elements: it's simple text. Preserve attributes via
|
|
150
|
+
// a `$value` pairing when present, mirroring how the buffered mapper
|
|
151
|
+
// surfaces simpleContent-with-attributes types.
|
|
152
|
+
const text = node.textBuf.join("");
|
|
153
|
+
if (attrsKey in node.obj) {
|
|
154
|
+
return { ...node.obj, $value: text };
|
|
155
|
+
}
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
return node.obj;
|
|
159
|
+
}
|
|
160
|
+
function assignChild(parent, propName, value, spec) {
|
|
161
|
+
const parentType = parent.typeName;
|
|
162
|
+
const propMetaEntry = parentType ? spec.propMeta?.[parentType]?.[propName] : undefined;
|
|
163
|
+
const childTypeHint = parentType ? spec.childType?.[parentType]?.[propName] : undefined;
|
|
164
|
+
// Array if: (a) propMeta says max > 1 or "unbounded", or (b) childType hint
|
|
165
|
+
// ends in `[]`, or (c) the slot is already occupied (implicit repetition).
|
|
166
|
+
const metaSaysArray = propMetaEntry?.max === "unbounded" ||
|
|
167
|
+
(typeof propMetaEntry?.max === "number" && propMetaEntry.max > 1);
|
|
168
|
+
const hintSaysArray = !!childTypeHint && childTypeHint.endsWith("[]");
|
|
169
|
+
const existing = parent.obj[propName];
|
|
170
|
+
const slotTaken = existing !== undefined;
|
|
171
|
+
if (metaSaysArray || hintSaysArray) {
|
|
172
|
+
if (Array.isArray(existing)) {
|
|
173
|
+
existing.push(value);
|
|
174
|
+
}
|
|
175
|
+
else if (slotTaken) {
|
|
176
|
+
parent.obj[propName] = [existing, value];
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
parent.obj[propName] = [value];
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (slotTaken) {
|
|
184
|
+
// Schema said scalar but the wire repeated it. Promote to array rather
|
|
185
|
+
// than drop data.
|
|
186
|
+
if (Array.isArray(existing)) {
|
|
187
|
+
existing.push(value);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
parent.obj[propName] = [existing, value];
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
parent.obj[propName] = value;
|
|
195
|
+
}
|
|
196
|
+
function stripArraySuffix(tsType) {
|
|
197
|
+
return tsType.endsWith("[]") ? tsType.slice(0, -2) : tsType;
|
|
198
|
+
}
|
|
199
|
+
function tailMatches(stack, path) {
|
|
200
|
+
if (stack.length < path.length)
|
|
201
|
+
return false;
|
|
202
|
+
const offset = stack.length - path.length;
|
|
203
|
+
for (let i = 0; i < path.length; i++) {
|
|
204
|
+
if (stack[offset + i] !== path[i])
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
const UTF8_DECODER = new TextDecoder("utf-8");
|
|
210
|
+
function decodeUtf8(buf) {
|
|
211
|
+
return UTF8_DECODER.decode(buf);
|
|
212
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/test/generators.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAGnD,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAerF;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,qBAAqB,EAAE,EACnC,KAAK,EAAE,iBAAiB,GACvB,MAAM,
|
|
1
|
+
{"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/test/generators.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAGnD,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAerF;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,qBAAqB,EAAE,EACnC,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAgFR;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,UAAU,GACrB,MAAM,CAsCR;AAED;;;;;;;GAOG;AAEH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,qBAAqB,EAAE,EACnC,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAuER;AAED;;;;;;;GAOG;AAEH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,qBAAqB,EAAE,EACnC,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAiIR;AAED;;GAEG;AAEH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,qBAAqB,EAAE,EACnC,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAuER;AAED;;GAEG;AAEH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,qBAAqB,EAAE,GAClC,MAAM,CAoCR;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,MAAM,CAgFR;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,MAAM,CAgDR;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,OAAO,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAkEf"}
|
package/dist/test/generators.js
CHANGED
|
@@ -63,15 +63,40 @@ export function emitMockClientHelper(testDir, clientDir, importsMode, clientMeta
|
|
|
63
63
|
const operationsType = `${clientMeta.className}Operations`;
|
|
64
64
|
// Sort operations for deterministic output
|
|
65
65
|
const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
|
|
66
|
+
const hasStream = sortedOps.some((op) => !!op.stream);
|
|
66
67
|
const methodEntries = sortedOps.map((op) => {
|
|
67
68
|
const mockData = mocks.get(op.operationId);
|
|
68
69
|
const response = mockData?.response ?? {};
|
|
70
|
+
if (op.stream) {
|
|
71
|
+
// For stream operations, the mock emits a single-record iterable derived
|
|
72
|
+
// from the mock data. Tests that need multi-record sequences can pass an
|
|
73
|
+
// override via createMockClient({...}).
|
|
74
|
+
const recordJson = JSON.stringify(response, null, 6).replace(/\n/g, "\n ");
|
|
75
|
+
return ` ${op.operationId}: async () => ({
|
|
76
|
+
records: asyncIterableOf(${recordJson}),
|
|
77
|
+
headers: {},
|
|
78
|
+
})`;
|
|
79
|
+
}
|
|
69
80
|
const responseJson = JSON.stringify(response, null, 6).replace(/\n/g, "\n ");
|
|
70
81
|
return ` ${op.operationId}: async () => ({
|
|
71
82
|
response: ${responseJson},
|
|
72
83
|
headers: {},
|
|
73
84
|
})`;
|
|
74
85
|
}).join(",\n");
|
|
86
|
+
const streamHelper = hasStream
|
|
87
|
+
? `
|
|
88
|
+
/**
|
|
89
|
+
* Wraps a single record (or an array of records) as an async iterable, matching
|
|
90
|
+
* the StreamOperationResponse.records shape the real client would return.
|
|
91
|
+
*/
|
|
92
|
+
function asyncIterableOf<T>(value: T | T[]): AsyncIterable<T> {
|
|
93
|
+
const items = Array.isArray(value) ? value : [value];
|
|
94
|
+
return (async function* () {
|
|
95
|
+
for (const item of items) yield item;
|
|
96
|
+
})();
|
|
97
|
+
}
|
|
98
|
+
`
|
|
99
|
+
: "";
|
|
75
100
|
return [
|
|
76
101
|
"/**",
|
|
77
102
|
" * Mock SOAP Client Helper",
|
|
@@ -101,6 +126,7 @@ export function emitMockClientHelper(testDir, clientDir, importsMode, clientMeta
|
|
|
101
126
|
" ...overrides,",
|
|
102
127
|
" } as " + operationsType + ";",
|
|
103
128
|
"}",
|
|
129
|
+
...(streamHelper ? [streamHelper] : []),
|
|
104
130
|
"",
|
|
105
131
|
].join("\n");
|
|
106
132
|
}
|
|
@@ -169,6 +195,30 @@ export function emitRoutesTest(testDir, importsMode, operations, mocks) {
|
|
|
169
195
|
const requestPayload = JSON.stringify(mockData?.request ?? {}, null, 4).replace(/\n/g, "\n ");
|
|
170
196
|
const hint = formatOperationHint(op.summary, op.description);
|
|
171
197
|
const hintComment = hint ? ` // ${hint}\n` : "";
|
|
198
|
+
if (op.stream) {
|
|
199
|
+
// Stream routes return NDJSON lines, not a single JSON envelope. Assert
|
|
200
|
+
// on content-type and at least one parseable record per line.
|
|
201
|
+
return `${hintComment} it("${op.method.toUpperCase()} ${op.path} streams ${op.stream.format} records", async () => {
|
|
202
|
+
const app = await createTestApp();
|
|
203
|
+
try {
|
|
204
|
+
const res = await app.inject({
|
|
205
|
+
method: "${op.method.toUpperCase()}",
|
|
206
|
+
url: "${op.path}",
|
|
207
|
+
payload: ${requestPayload},
|
|
208
|
+
});
|
|
209
|
+
expect(res.statusCode).toBe(200);
|
|
210
|
+
expect(String(res.headers["content-type"] ?? "")).toContain(${JSON.stringify(op.stream.mediaType)});
|
|
211
|
+
const lines = res.body.split("\\n").filter((l: string) => l.length > 0);
|
|
212
|
+
expect(lines.length).toBeGreaterThan(0);
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
const record = JSON.parse(line);
|
|
215
|
+
expect(record).toBeDefined();
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
await app.close();
|
|
219
|
+
}
|
|
220
|
+
});`;
|
|
221
|
+
}
|
|
172
222
|
return `${hintComment} it("${op.method.toUpperCase()} ${op.path} returns SUCCESS envelope", async () => {
|
|
173
223
|
const app = await createTestApp();
|
|
174
224
|
try {
|
package/dist/test/mockData.d.ts
CHANGED
|
@@ -22,6 +22,12 @@ export interface CatalogForMocks {
|
|
|
22
22
|
name: string;
|
|
23
23
|
inputTypeName?: string;
|
|
24
24
|
outputTypeName?: string;
|
|
25
|
+
stream?: {
|
|
26
|
+
format: "ndjson" | "json-array";
|
|
27
|
+
mediaType: string;
|
|
28
|
+
recordTypeName: string;
|
|
29
|
+
recordPath: string[];
|
|
30
|
+
};
|
|
25
31
|
}>;
|
|
26
32
|
types?: Array<{
|
|
27
33
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mockData.d.ts","sourceRoot":"","sources":["../../src/test/mockData.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;YACvC,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;YAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;SACpB,CAAC,CAAC,CAAC;KACL,CAAC;IACF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"mockData.d.ts","sourceRoot":"","sources":["../../src/test/mockData.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;YACvC,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;YAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;SACpB,CAAC,CAAC,CAAC;KACL,CAAC;IACF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE;YACP,MAAM,EAAE,QAAQ,GAAG,YAAY,CAAC;YAChC,SAAS,EAAE,MAAM,CAAC;YAClB,cAAc,EAAE,MAAM,CAAC;YACvB,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC/B,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;SAAE,CAAC,CAAC;KAC3D,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAwCjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE,eAAe,EACtB,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoDzB;AAWD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,GAAG,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CA+BtD"}
|
package/dist/test/mockData.js
CHANGED
|
@@ -170,8 +170,12 @@ export function generateAllOperationMocks(catalog, opts) {
|
|
|
170
170
|
if (shouldFlatten && op.inputTypeName && request != null && typeof request === "object" && !Array.isArray(request)) {
|
|
171
171
|
request = flattenMockPayload(request, op.inputTypeName, childTypeMap, arrayWrappers);
|
|
172
172
|
}
|
|
173
|
-
// Response
|
|
174
|
-
|
|
173
|
+
// Response: for stream ops we generate a single record of the configured
|
|
174
|
+
// recordType so the mock client can yield it through asyncIterableOf().
|
|
175
|
+
// Otherwise the SOAP-shaped outputType envelope (pre-unwrap).
|
|
176
|
+
const response = op.stream
|
|
177
|
+
? generateOperationPayload(op.stream.recordTypeName, catalog)
|
|
178
|
+
: generateOperationPayload(op.outputTypeName, catalog);
|
|
175
179
|
result.set(op.name, { request, response });
|
|
176
180
|
}
|
|
177
181
|
return result;
|
package/dist/util/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/util/cli.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAc1E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAMhE;AAED;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,GAAE,MAAU,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/util/cli.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAc1E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAMhE;AAED;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,GAAE,MAAU,GAAG,KAAK,CAY7E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,IAAI,CASP;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE;IAAE,OAAO,EAAE,GAAG,EAAE,CAAA;CAAE,EAC/B,QAAQ,EAAE;IAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAAC,UAAU,EAAE,GAAG,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAAE,GAChF,IAAI,CAON;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,GAAG,EACb,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,IAAI,EACrD,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,IAAI,EACpD,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,IAAI,EACpD,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,IAAI,GACzD,IAAI,CAWN;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,IAAI,CAUN"}
|
package/dist/util/cli.js
CHANGED
|
@@ -95,7 +95,9 @@ export function info(message) {
|
|
|
95
95
|
* @param exitCode - Process exit code (default: 1)
|
|
96
96
|
*/
|
|
97
97
|
export function handleCLIError(errorObj, exitCode = 1) {
|
|
98
|
-
|
|
98
|
+
// Known structured errors carry richer context; print their multi-line form.
|
|
99
|
+
if (errorObj instanceof WsdlCompilationError ||
|
|
100
|
+
(errorObj instanceof Error && typeof errorObj.toUserMessage === "function")) {
|
|
99
101
|
error(errorObj.toUserMessage());
|
|
100
102
|
}
|
|
101
103
|
else {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtimeSource.d.ts","sourceRoot":"","sources":["../../src/util/runtimeSource.ts"],"names":[],"mappings":"AAsCA,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI1D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers to load wsdl-tsc's own runtime modules as source text, for embedding
|
|
3
|
+
* into generated client and gateway output.
|
|
4
|
+
*
|
|
5
|
+
* The runtime modules live at `src/runtime/*.ts` and are published alongside
|
|
6
|
+
* `dist/` via the `files` field in package.json. Resolving via
|
|
7
|
+
* `import.meta.url` works both in dev (tsx runs the .ts directly from src/) and
|
|
8
|
+
* in a published install (generator code lives under dist/, and `../../src/runtime/…`
|
|
9
|
+
* climbs to the package root where the source .ts files sit beside `dist/`).
|
|
10
|
+
*/
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const HEADER = (sourceName) => `// -----------------------------------------------------------------------------
|
|
16
|
+
// Embedded from @techspokes/typescript-wsdl-client runtime (${sourceName}).
|
|
17
|
+
// This file is generated output — do not edit; regenerate via wsdl-tsc.
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
`;
|
|
20
|
+
function resolveRuntimeSourcePath(filename) {
|
|
21
|
+
// Walk candidates in order: dev layout (src/util -> src/runtime),
|
|
22
|
+
// then published layout (dist/util -> ../src/runtime), then package root.
|
|
23
|
+
const candidates = [
|
|
24
|
+
path.resolve(MODULE_DIR, "..", "runtime", filename),
|
|
25
|
+
path.resolve(MODULE_DIR, "..", "..", "src", "runtime", filename),
|
|
26
|
+
path.resolve(MODULE_DIR, "..", "..", "..", "src", "runtime", filename),
|
|
27
|
+
];
|
|
28
|
+
for (const c of candidates) {
|
|
29
|
+
if (fs.existsSync(c))
|
|
30
|
+
return c;
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Could not locate runtime source "${filename}". Looked in:\n${candidates.join("\n")}`);
|
|
33
|
+
}
|
|
34
|
+
export function loadRuntimeSource(filename) {
|
|
35
|
+
const abs = resolveRuntimeSourcePath(filename);
|
|
36
|
+
const raw = fs.readFileSync(abs, "utf-8");
|
|
37
|
+
return HEADER(filename) + "\n" + raw;
|
|
38
|
+
}
|