@techspokes/typescript-wsdl-client 0.16.1 → 0.17.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 (71) hide show
  1. package/README.md +5 -0
  2. package/dist/app/generateApp.d.ts.map +1 -1
  3. package/dist/app/generateApp.js +1 -0
  4. package/dist/cli.js +46 -2
  5. package/dist/client/generateClient.d.ts.map +1 -1
  6. package/dist/client/generateClient.js +62 -6
  7. package/dist/client/generateOperations.d.ts.map +1 -1
  8. package/dist/client/generateOperations.js +27 -4
  9. package/dist/compiler/schemaCompiler.d.ts +40 -11
  10. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  11. package/dist/compiler/schemaCompiler.js +81 -6
  12. package/dist/compiler/shapeResolver.d.ts +18 -0
  13. package/dist/compiler/shapeResolver.d.ts.map +1 -0
  14. package/dist/compiler/shapeResolver.js +280 -0
  15. package/dist/gateway/generateGateway.d.ts.map +1 -1
  16. package/dist/gateway/generateGateway.js +2 -1
  17. package/dist/gateway/generators.d.ts +13 -1
  18. package/dist/gateway/generators.d.ts.map +1 -1
  19. package/dist/gateway/generators.js +98 -13
  20. package/dist/gateway/helpers.d.ts +16 -0
  21. package/dist/gateway/helpers.d.ts.map +1 -1
  22. package/dist/gateway/helpers.js +1 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +16 -1
  26. package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
  27. package/dist/openapi/generateOpenAPI.js +28 -0
  28. package/dist/pipeline.d.ts +13 -0
  29. package/dist/pipeline.d.ts.map +1 -1
  30. package/dist/pipeline.js +17 -1
  31. package/dist/runtime/ndjson.d.ts +24 -0
  32. package/dist/runtime/ndjson.d.ts.map +1 -0
  33. package/dist/runtime/ndjson.js +30 -0
  34. package/dist/runtime/streamXml.d.ts +45 -0
  35. package/dist/runtime/streamXml.d.ts.map +1 -0
  36. package/dist/runtime/streamXml.js +212 -0
  37. package/dist/test/generators.d.ts.map +1 -1
  38. package/dist/test/generators.js +50 -0
  39. package/dist/test/mockData.d.ts +6 -0
  40. package/dist/test/mockData.d.ts.map +1 -1
  41. package/dist/test/mockData.js +6 -2
  42. package/dist/util/cli.d.ts.map +1 -1
  43. package/dist/util/cli.js +3 -1
  44. package/dist/util/runtimeSource.d.ts +2 -0
  45. package/dist/util/runtimeSource.d.ts.map +1 -0
  46. package/dist/util/runtimeSource.js +38 -0
  47. package/dist/util/streamConfig.d.ts +59 -0
  48. package/dist/util/streamConfig.d.ts.map +1 -0
  49. package/dist/util/streamConfig.js +230 -0
  50. package/docs/README.md +1 -0
  51. package/docs/api-reference.md +146 -0
  52. package/docs/architecture.md +27 -5
  53. package/docs/cli-reference.md +30 -0
  54. package/docs/concepts.md +51 -0
  55. package/docs/configuration.md +40 -0
  56. package/docs/decisions/002-streamable-responses.md +308 -0
  57. package/docs/gateway-guide.md +37 -0
  58. package/docs/generated-code.md +21 -0
  59. package/docs/migration-playbook.md +33 -0
  60. package/docs/migration.md +31 -6
  61. package/docs/output-anatomy.md +49 -0
  62. package/docs/production.md +32 -0
  63. package/docs/start-here.md +33 -0
  64. package/docs/supported-patterns.md +2 -0
  65. package/docs/testing.md +14 -0
  66. package/docs/troubleshooting.md +18 -0
  67. package/package.json +5 -2
  68. package/src/runtime/clientStreamMethods.tpl.txt +183 -0
  69. package/src/runtime/ndjson.ts +32 -0
  70. package/src/runtime/operationsStreamHelper.tpl.txt +13 -0
  71. 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);
@@ -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
@@ -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;AAGzE;;;;;;;;;;;;;;;;;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;CACH;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,CAiJhH"}
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,CAkDR;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,CA8CR;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"}
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"}
@@ -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 {
@@ -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;KACzB,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,CA2BtD"}
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"}
@@ -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 stays SOAP-shaped (pre-unwrap) since runtime unwrapArrayWrappers() handles it
174
- const response = generateOperationPayload(op.outputTypeName, catalog);
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;
@@ -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,CAQ7E;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"}
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
- if (errorObj instanceof WsdlCompilationError) {
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,2 @@
1
+ export declare function loadRuntimeSource(filename: string): string;
2
+ //# sourceMappingURL=runtimeSource.d.ts.map
@@ -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
+ }