@techspokes/typescript-wsdl-client 0.25.0 → 0.27.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 (38) hide show
  1. package/README.md +3 -2
  2. package/dist/app/generateApp.js +1 -1
  3. package/dist/client/generateTypes.d.ts.map +1 -1
  4. package/dist/client/generateTypes.js +136 -65
  5. package/dist/compiler/schemaCompiler.d.ts +19 -0
  6. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  7. package/dist/compiler/schemaCompiler.js +152 -49
  8. package/dist/compiler/shapeResolver.js +15 -0
  9. package/dist/openapi/generateSchemas.d.ts.map +1 -1
  10. package/dist/openapi/generateSchemas.js +39 -3
  11. package/dist/test/generateTests.js +1 -1
  12. package/dist/test/generators.d.ts +2 -2
  13. package/dist/test/generators.d.ts.map +1 -1
  14. package/dist/test/generators.js +84 -3
  15. package/dist/test/mockData.d.ts +19 -0
  16. package/dist/test/mockData.d.ts.map +1 -1
  17. package/dist/test/mockData.js +9 -0
  18. package/docs/README.md +1 -0
  19. package/docs/api-reference.md +21 -0
  20. package/docs/cli-reference.md +37 -8
  21. package/docs/concepts.md +18 -4
  22. package/docs/generated-code.md +18 -0
  23. package/docs/migration-playbook.md +1 -1
  24. package/docs/migration.md +1 -1
  25. package/docs/output-anatomy.md +1 -1
  26. package/docs/production.md +3 -3
  27. package/docs/releases/v0.25.2.md +32 -0
  28. package/docs/releases/v0.26.0.md +32 -0
  29. package/docs/releases/v0.27.0.md +31 -0
  30. package/docs/roadmap/README.md +89 -0
  31. package/docs/roadmap/v1.0-choice-union-mode.md +90 -0
  32. package/docs/roadmap/v1.0-contract-audit.md +74 -0
  33. package/docs/roadmap/v1.0-json-array-streaming.md +99 -0
  34. package/docs/roadmap/v1.0-openapi-fastify-compatibility.md +97 -0
  35. package/docs/roadmap/v1.0-release-candidate-gates.md +77 -0
  36. package/docs/roadmap/v1.0-wsdl-coverage-matrix.md +89 -0
  37. package/docs/supported-patterns.md +2 -1
  38. package/package.json +4 -4
@@ -67,7 +67,7 @@ function isSyntheticAliasSelfWrapper(t, aliasNames) {
67
67
  const e = t.elems[0];
68
68
  return e.name === "$value" && e.tsType === t.name;
69
69
  }
70
- function buildComplexSchema(t, closed, knownTypeNames, aliasNames, flattenWrappers) {
70
+ function buildComplexSchema(t, closed, knownTypeNames, aliasNames, flattenWrappers, choiceUnionMode) {
71
71
  // Use knownTypeNames/aliasNames to validate $ref targets so we surface
72
72
  // compiler issues early instead of emitting dangling references in OpenAPI output.
73
73
  function refOrPrimitive(ts) {
@@ -108,6 +108,8 @@ function buildComplexSchema(t, closed, knownTypeNames, aliasNames, flattenWrappe
108
108
  }
109
109
  const properties = {};
110
110
  const required = [];
111
+ const choiceGroups = choiceUnionMode ? (t.choiceGroups ?? []) : [];
112
+ const choiceElementNames = new Set(choiceGroups.flatMap((group) => group.branches.map((branch) => branch.name)));
111
113
  // attributes
112
114
  for (const a of t.attrs) {
113
115
  const propSchema = refOrPrimitive(a.tsType);
@@ -135,7 +137,7 @@ function buildComplexSchema(t, closed, knownTypeNames, aliasNames, flattenWrappe
135
137
  if (e.name === "$value") {
136
138
  // never required
137
139
  }
138
- else if (e.min >= 1) {
140
+ else if (e.min >= 1 && !choiceElementNames.has(e.name)) {
139
141
  required.push(e.name);
140
142
  }
141
143
  }
@@ -147,6 +149,40 @@ function buildComplexSchema(t, closed, knownTypeNames, aliasNames, flattenWrappe
147
149
  obj.required = Array.from(new Set(required));
148
150
  if (closed)
149
151
  obj.additionalProperties = false;
152
+ const choiceConstraints = choiceGroups
153
+ .map((group) => {
154
+ const branchNames = group.branches.map((branch) => branch.name);
155
+ const presenceSchema = (names) => {
156
+ if (names.length === 0)
157
+ return undefined;
158
+ if (names.length === 1)
159
+ return { required: [names[0]] };
160
+ return { anyOf: names.map((name) => ({ required: [name] })) };
161
+ };
162
+ const oneOf = group.branches.map((branch) => {
163
+ const peers = branchNames.filter((name) => name !== branch.name);
164
+ const schema = { required: [branch.name] };
165
+ const forbiddenPeers = presenceSchema(peers);
166
+ if (forbiddenPeers) {
167
+ schema.not = forbiddenPeers;
168
+ }
169
+ return schema;
170
+ });
171
+ if (group.min === 0) {
172
+ const anyBranch = presenceSchema(branchNames);
173
+ if (anyBranch) {
174
+ oneOf.push({ not: anyBranch });
175
+ }
176
+ }
177
+ return oneOf.length > 0 ? { oneOf } : undefined;
178
+ })
179
+ .filter((schema) => !!schema);
180
+ if (choiceConstraints.length === 1) {
181
+ obj.oneOf = choiceConstraints[0].oneOf;
182
+ }
183
+ else if (choiceConstraints.length > 1) {
184
+ obj.allOf = choiceConstraints;
185
+ }
150
186
  // inheritance via base => allOf
151
187
  if (t.base) {
152
188
  const baseName = t.base;
@@ -194,7 +230,7 @@ export function generateSchemas(compiled, opts) {
194
230
  if (isSyntheticAliasSelfWrapper(t, aliasNames)) {
195
231
  continue;
196
232
  }
197
- schemas[t.name] = buildComplexSchema(t, closed, typeNames, aliasNames, flattenWrappers);
233
+ schemas[t.name] = buildComplexSchema(t, closed, typeNames, aliasNames, flattenWrappers, compiled.options.choice === "union");
198
234
  }
199
235
  if (opts.pruneUnusedSchemas) {
200
236
  // Root references: operation type names when available, falling back to element local names.
@@ -127,7 +127,7 @@ export async function generateTests(opts) {
127
127
  // Emit gateway/envelope.test.ts
128
128
  writeTestFile(path.join(testDir, "gateway", "envelope.test.ts"), emitEnvelopeTest(testDir, importsMode, operations, mocks), force);
129
129
  // Emit gateway/validation.test.ts
130
- writeTestFile(path.join(testDir, "gateway", "validation.test.ts"), emitValidationTest(testDir, importsMode, operations), force);
130
+ writeTestFile(path.join(testDir, "gateway", "validation.test.ts"), emitValidationTest(testDir, importsMode, operations, mocks, catalog), force);
131
131
  // Emit runtime/classify-error.test.ts
132
132
  writeTestFile(path.join(testDir, "runtime", "classify-error.test.ts"), emitClassifyErrorTest(testDir, gatewayDir, importsMode), force);
133
133
  // Emit runtime/envelope-builders.test.ts
@@ -1,5 +1,5 @@
1
1
  import type { ClientMeta, ResolvedOperationMeta } from "../gateway/helpers.js";
2
- import type { CatalogForMocks } from "./mockData.js";
2
+ import { type CatalogForMocks } from "./mockData.js";
3
3
  /** Pre-computed mock data map passed from the orchestrator to all emitters. */
4
4
  export type OperationMocksMap = Map<string, {
5
5
  request: unknown;
@@ -57,7 +57,7 @@ export declare function emitEnvelopeTest(testDir: string, importsMode: "js" | "t
57
57
  /**
58
58
  * Emits gateway/validation.test.ts with invalid payload tests per route.
59
59
  */
60
- export declare function emitValidationTest(testDir: string, importsMode: "js" | "ts" | "bare", operations: ResolvedOperationMeta[]): string;
60
+ export declare function emitValidationTest(testDir: string, importsMode: "js" | "ts" | "bare", operations: ResolvedOperationMeta[], mocks?: OperationMocksMap, catalog?: CatalogForMocks): string;
61
61
  /**
62
62
  * Emits runtime/classify-error.test.ts with direct classifyError() unit tests.
63
63
  */
@@ -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,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"}
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,EAA0C,KAAK,eAAe,EAAC,MAAM,eAAe,CAAC;AAG5F,+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;AA6FrF;;;;;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,EACnC,KAAK,CAAC,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAwDR;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"}
@@ -9,6 +9,7 @@
9
9
  * via computeRelativeImport() from src/util/imports.ts.
10
10
  */
11
11
  import { computeRelativeImport } from "../util/imports.js";
12
+ import { generateMockData, generateMockPrimitive } from "./mockData.js";
12
13
  import { detectArrayWrappers, detectChildrenTypes } from "../util/catalogMeta.js";
13
14
  function formatOperationHint(summary, description) {
14
15
  const source = summary || description;
@@ -22,6 +23,66 @@ function formatOperationHint(summary, description) {
22
23
  .trim();
23
24
  return normalized || undefined;
24
25
  }
26
+ function isRecord(value) {
27
+ return value != null && typeof value === "object" && !Array.isArray(value);
28
+ }
29
+ function cloneRecord(value) {
30
+ return JSON.parse(JSON.stringify(value));
31
+ }
32
+ function mockChoiceBranchValue(branch, catalog) {
33
+ const isArray = branch.max === "unbounded" || (typeof branch.max === "number" && branch.max > 1);
34
+ const value = branch.tsType === "string" || branch.tsType === "number" || branch.tsType === "boolean"
35
+ ? generateMockPrimitive(branch.tsType, branch.name)
36
+ : generateMockData(branch.tsType, catalog);
37
+ return isArray ? [value] : value;
38
+ }
39
+ function buildInvalidChoicePayloads(op, mocks, catalog) {
40
+ if (catalog?.options?.choice !== "union" || !op.requestTypeName) {
41
+ return [];
42
+ }
43
+ const typeMeta = catalog.types?.find((type) => type.name === op.requestTypeName);
44
+ const groups = typeMeta?.choiceGroups ?? [];
45
+ if (groups.length === 0) {
46
+ return [];
47
+ }
48
+ const basePayload = mocks?.get(op.operationId)?.request;
49
+ if (!isRecord(basePayload)) {
50
+ return [];
51
+ }
52
+ const invalidPayloads = [];
53
+ for (const group of groups) {
54
+ if (group.branches.length < 2) {
55
+ continue;
56
+ }
57
+ const selectedBranch = group.branches.find((branch) => Object.hasOwn(basePayload, branch.name)) ?? group.branches[0];
58
+ const peerBranch = group.branches.find((branch) => branch.name !== selectedBranch.name);
59
+ if (!peerBranch) {
60
+ continue;
61
+ }
62
+ const payload = cloneRecord(basePayload);
63
+ if (!Object.hasOwn(payload, selectedBranch.name)) {
64
+ payload[selectedBranch.name] = mockChoiceBranchValue(selectedBranch, catalog);
65
+ }
66
+ payload[peerBranch.name] = mockChoiceBranchValue(peerBranch, catalog);
67
+ invalidPayloads.push({
68
+ kind: "invalid",
69
+ typeName: op.requestTypeName,
70
+ payload,
71
+ });
72
+ if (group.min >= 1) {
73
+ const missingPayload = cloneRecord(basePayload);
74
+ for (const branch of group.branches) {
75
+ delete missingPayload[branch.name];
76
+ }
77
+ invalidPayloads.push({
78
+ kind: "missing",
79
+ typeName: op.requestTypeName,
80
+ payload: missingPayload,
81
+ });
82
+ }
83
+ }
84
+ return invalidPayloads;
85
+ }
25
86
  /**
26
87
  * Emits vitest.config.ts content.
27
88
  *
@@ -467,11 +528,11 @@ describe("gateway — envelope structure", () => {
467
528
  * Emits gateway/validation.test.ts with invalid payload tests per route.
468
529
  */
469
530
  // noinspection JSUnusedLocalSymbols
470
- export function emitValidationTest(testDir, importsMode, operations) {
531
+ export function emitValidationTest(testDir, importsMode, operations, mocks, catalog) {
471
532
  const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
472
533
  const testAppImport = `../helpers/test-app${suffix}`;
473
534
  const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
474
- const testCases = sortedOps.map((op) => {
535
+ const arrayBodyCases = sortedOps.map((op) => {
475
536
  return ` it("${op.method.toUpperCase()} ${op.path} rejects array body", async () => {
476
537
  const app = await createTestApp();
477
538
  try {
@@ -485,7 +546,27 @@ export function emitValidationTest(testDir, importsMode, operations) {
485
546
  await app.close();
486
547
  }
487
548
  });`;
488
- }).join("\n\n");
549
+ });
550
+ const choiceCases = sortedOps.flatMap((op) => {
551
+ return buildInvalidChoicePayloads(op, mocks, catalog).map((invalid) => {
552
+ const payload = JSON.stringify(invalid.payload, null, 4).replace(/\n/g, "\n ");
553
+ const title = `${invalid.kind} ${invalid.typeName} choice payload`;
554
+ return ` it("${op.method.toUpperCase()} ${op.path} rejects ${title}", async () => {
555
+ const app = await createTestApp();
556
+ try {
557
+ const res = await app.inject({
558
+ method: "${op.method.toUpperCase()}",
559
+ url: "${op.path}",
560
+ payload: ${payload},
561
+ });
562
+ expect(res.statusCode).toBe(400);
563
+ } finally {
564
+ await app.close();
565
+ }
566
+ });`;
567
+ });
568
+ });
569
+ const testCases = [...arrayBodyCases, ...choiceCases].join("\n\n");
489
570
  return `/**
490
571
  * Gateway Validation Tests
491
572
  *
@@ -8,6 +8,9 @@ export interface MockDataOptions {
8
8
  * Compiled catalog structure (minimal interface for mock data generation)
9
9
  */
10
10
  export interface CatalogForMocks {
11
+ options?: {
12
+ choice?: "all-optional" | "union";
13
+ };
11
14
  meta?: {
12
15
  attrType?: Record<string, Record<string, string>>;
13
16
  childType?: Record<string, Record<string, string>>;
@@ -38,6 +41,22 @@ export interface CatalogForMocks {
38
41
  name: string;
39
42
  max: number | "unbounded";
40
43
  }>;
44
+ choiceGroups?: Array<{
45
+ name: string;
46
+ min: number;
47
+ max: number | "unbounded";
48
+ sourceOrder: number;
49
+ branches: Array<{
50
+ name: string;
51
+ tsType: string;
52
+ min: number;
53
+ max: number | "unbounded";
54
+ nillable?: boolean;
55
+ declaredType: string;
56
+ doc?: string;
57
+ sourceOrder: number;
58
+ }>;
59
+ }>;
41
60
  }>;
42
61
  aliases?: Array<{
43
62
  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;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"}
1
+ {"version":3,"file":"mockData.d.ts","sourceRoot":"","sources":["../../src/test/mockData.ts"],"names":[],"mappings":"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"}
@@ -100,7 +100,16 @@ export function generateMockData(typeName, catalog, opts, visited, depth) {
100
100
  const newVisited = new Set(currentVisited);
101
101
  newVisited.add(typeName);
102
102
  const result = {};
103
+ const typeMeta = catalog.types?.find((t) => t.name === typeName);
104
+ const choiceGroups = catalog.options?.choice === "union" ? (typeMeta?.choiceGroups ?? []) : [];
105
+ const choiceBranchNames = new Set(choiceGroups.flatMap((group) => group.branches.map((branch) => branch.name)));
106
+ const selectedChoiceBranchNames = new Set(choiceGroups
107
+ .map((group) => group.branches[0]?.name)
108
+ .filter((name) => !!name));
103
109
  for (const [propName, propType] of Object.entries(childTypes ?? {})) {
110
+ if (choiceBranchNames.has(propName) && !selectedChoiceBranchNames.has(propName)) {
111
+ continue;
112
+ }
104
113
  const meta = propMeta[propName];
105
114
  const isArray = meta?.max === "unbounded" || (typeof meta?.max === "number" && meta.max > 1);
106
115
  // Check if it's a primitive type
package/docs/README.md CHANGED
@@ -29,6 +29,7 @@ Human-maintained reference documents for `@techspokes/typescript-wsdl-client`. T
29
29
  - [concepts.md](concepts.md): flattening, `$value`, primitives, determinism
30
30
  - [architecture.md](architecture.md): internal pipeline for contributors
31
31
  - [agent-skill.md](agent-skill.md): release ZIP for consumer-project AI agents
32
+ - [roadmap/README.md](roadmap/README.md): implementation slices and release gates for 1.0
32
33
  - [decisions/002-streamable-responses.md](decisions/002-streamable-responses.md): opt-in streaming design (client `AsyncIterable`, gateway NDJSON, `x-wsdl-tsc-stream` OpenAPI extension); shipped in 0.17.0
33
34
  - [migration.md](migration.md): upgrading between package versions
34
35
 
@@ -68,6 +68,8 @@ interface PrimitiveOptions {
68
68
  }
69
69
  ```
70
70
 
71
+ `CompilerOptions.choice` defaults to `all-optional`. Set `choice: "union"` to model `xs:choice` groups as exclusive branch unions in generated client types and catalog-backed OpenAPI schemas.
72
+
71
73
  ## generateOpenAPI
72
74
 
73
75
  Generate an OpenAPI 3.1 specification from WSDL or catalog.
@@ -204,6 +206,25 @@ const { compiled, openapiDoc } = await runGenerationPipeline({
204
206
  });
205
207
  ```
206
208
 
209
+ Programmatic callers opt into choice union mode by passing `compiler: { choice: "union" }` to `runGenerationPipeline`:
210
+
211
+ ```typescript
212
+ await runGenerationPipeline({
213
+ wsdl: "./service.wsdl",
214
+ catalogOut: "./generated/catalog.json",
215
+ clientOutDir: "./generated/client",
216
+ openapi: {
217
+ outFile: "./generated/openapi.json"
218
+ },
219
+ gateway: {
220
+ outDir: "./generated/gateway",
221
+ serviceSlug: "service",
222
+ versionSlug: "v1"
223
+ },
224
+ compiler: { choice: "union" }
225
+ });
226
+ ```
227
+
207
228
  ### Type Signature
208
229
 
209
230
  ```typescript
@@ -54,6 +54,7 @@ The catalog is auto-placed alongside the first available output directory: `{cli
54
54
  | `--client-dir` | Generate TypeScript client in this directory |
55
55
  | `--openapi-file` | Generate OpenAPI spec at this path |
56
56
  | `--gateway-dir` | Generate Fastify gateway in this directory |
57
+ | `--clean` | Remove existing client output directory contents before generation |
57
58
 
58
59
  ### Client Flags
59
60
 
@@ -66,36 +67,37 @@ The catalog is auto-placed alongside the first available output directory: `{cli
66
67
  | `--client-bigint-as` | `string` | Map arbitrary-size integers |
67
68
  | `--client-decimal-as` | `string` | Map xs:decimal |
68
69
  | `--client-date-as` | `string` | Map date/time types |
69
- | `--client-choice-mode` | `all-optional` | Choice element strategy |
70
+ | `--client-choice-mode` | `all-optional` | Controls `xs:choice` modeling |
70
71
  | `--client-fail-on-unresolved` | `false` | Fail on unresolved references |
71
72
  | `--client-nillable-as-optional` | `false` | Treat nillable as optional |
72
73
 
74
+ `--client-choice-mode` accepts `all-optional` and `union`. The default `all-optional` mode emits parallel optional choice fields. The `union` mode emits exclusive generated client type branches and catalog-backed OpenAPI schema constraints.
75
+
73
76
  ### OpenAPI Flags
74
77
 
75
78
  | Flag | Default | Description |
76
79
  |------|---------|-------------|
77
80
  | `--openapi-format` | `json` | Output format: json, yaml, or both |
78
81
  | `--openapi-title` | (derived) | API title |
79
- | `--openapi-version` | `0.0.0` | API version |
80
- | `--openapi-description` | (empty) | API description |
82
+ | `--openapi-version-tag` | `0.0.0` | API version for `info.version` |
81
83
  | `--openapi-servers` | `/` | Comma-separated server URLs (see note below) |
82
-
83
- > When `--init-app` is used and `--openapi-servers` is not provided, servers default to `http://localhost:3000`. The app scaffold also rewrites the servers array in its local `openapi.json` copy to match the configured port and prefix.
84
-
85
84
  | `--openapi-base-path` | (empty) | Base path prefix |
86
85
  | `--openapi-path-style` | `kebab` | Path transform: kebab, asis, or lower |
87
- | `--openapi-method` | `post` | Default HTTP method |
86
+ | `--openapi-default-method` | `post` | Default HTTP method |
88
87
  | `--openapi-tag-style` | `default` | Tag inference: default, service, or first |
89
88
  | `--openapi-closed-schemas` | `false` | Add additionalProperties: false |
90
89
  | `--openapi-flatten-array-wrappers` | `true` | Flatten ArrayOf* wrappers to plain arrays; emit runtime unwrap in gateway |
91
90
  | `--openapi-prune-unused-schemas` | `false` | Emit only referenced schemas |
92
91
  | `--openapi-envelope-namespace` | `ResponseEnvelope` | Envelope component name suffix |
93
92
  | `--openapi-error-namespace` | `ErrorObject` | Error object name suffix |
94
- | `--openapi-validate` | `true` | Validate spec with swagger-parser |
95
93
  | `--openapi-security-config-file` | | Path to security.json; `--openapi-security-file` is accepted as an alias |
96
94
  | `--openapi-tags-file` | | Path to tags.json |
97
95
  | `--openapi-ops-file` | | Path to ops.json |
98
96
 
97
+ CLI OpenAPI generation always validates the generated spec with `@apidevtools/swagger-parser`.
98
+
99
+ When `--init-app` is used and `--openapi-servers` is not provided, servers default to `http://localhost:3000`. The app scaffold also rewrites the servers array in its local `openapi.json` copy to match the configured port and prefix.
100
+
99
101
  ### Gateway Flags
100
102
 
101
103
  | Flag | Default | Description |
@@ -201,6 +203,19 @@ npx wsdl-tsc pipeline \
201
203
  --init-app
202
204
  ```
203
205
 
206
+ With union-mode choice modeling:
207
+
208
+ ```bash
209
+ npx wsdl-tsc pipeline \
210
+ --wsdl-source ./service.wsdl \
211
+ --client-dir ./generated/client \
212
+ --openapi-file ./generated/openapi.json \
213
+ --gateway-dir ./generated/gateway \
214
+ --gateway-service-name service \
215
+ --gateway-version-prefix v1 \
216
+ --client-choice-mode union
217
+ ```
218
+
204
219
  With generated test suite:
205
220
 
206
221
  ```bash
@@ -257,6 +272,12 @@ Provide either `--wsdl-source` (compile from WSDL) or `--catalog-file` (use pre-
257
272
 
258
273
  All flags from the compile command apply, plus the client-specific flags listed in the pipeline section.
259
274
 
275
+ | Flag | Default | Description |
276
+ |------|---------|-------------|
277
+ | `--client-choice-mode` | `all-optional` | Controls `xs:choice` modeling |
278
+
279
+ `--client-choice-mode` accepts `all-optional` and `union`. The `client` command stores the selected mode in `catalog.json` when it compiles from WSDL and uses that catalog metadata when emitting generated TypeScript.
280
+
260
281
  ### Key Modeling Rules
261
282
 
262
283
  - Attributes and elements become peer properties (flattened)
@@ -537,6 +558,14 @@ npx wsdl-tsc compile --wsdl-source <file|url> --catalog-file <path> [options]
537
558
  | `--wsdl-source` | Path or URL to WSDL file |
538
559
  | `--catalog-file` | Output path for catalog.json |
539
560
 
561
+ ### Compiler Flags
562
+
563
+ | Flag | Default | Description |
564
+ |------|---------|-------------|
565
+ | `--client-choice-mode` | `all-optional` | Controls `xs:choice` modeling |
566
+
567
+ `--client-choice-mode` accepts `all-optional` and `union`. The `compile` command writes the selected mode to `catalog.json` so later catalog-backed generation stages can use the same choice strategy.
568
+
540
569
  ### Catalog Co-location
541
570
 
542
571
  Default behavior varies by command:
package/docs/concepts.md CHANGED
@@ -253,8 +253,7 @@ produces `WeatherResponse_ResponseEnvelope`.
253
253
 
254
254
  ## Choice Element Handling
255
255
 
256
- The current strategy is all-optional. All choice branches are emitted as
257
- optional properties on a single interface.
256
+ The default strategy is `all-optional`. Choice branches are emitted on a single interface using the occurrence metadata compiled from the WSDL.
258
257
 
259
258
  ```typescript
260
259
  // WSDL: <xs:choice>
@@ -264,6 +263,22 @@ interface MyType {
264
263
  }
265
264
  ```
266
265
 
266
+ `--client-choice-mode union` is opt-in. It emits a base object for non-choice fields and an exclusive generated branch union for choice fields. Peer branch properties use `?: never`, so TypeScript rejects payloads that include more than one branch.
267
+
268
+ ```typescript
269
+ type MyType = MyTypeChoiceBase & MyTypeChoice1;
270
+
271
+ interface MyTypeChoiceBase {
272
+ id: string;
273
+ }
274
+
275
+ type MyTypeChoice1 =
276
+ | { optionA: string; optionB?: never }
277
+ | { optionB: number; optionA?: never };
278
+ ```
279
+
280
+ OpenAPI generation also consumes the same choice metadata in union mode. It keeps choice member properties on the containing object and adds `oneOf` branch constraints with peer `not` checks so generated request schemas reject payloads that send multiple branches.
281
+
267
282
  ## Array Wrapper Flattening
268
283
 
269
284
  A complex type whose only child is a single repeated element with no attributes
@@ -339,8 +354,7 @@ OpenAPI output is validated with `@apidevtools/swagger-parser`. The validator
339
354
  checks schema structure, resolves all `$ref` references, catches missing
340
355
  schemas, and detects circular dependencies.
341
356
 
342
- Disable with `--openapi-validate false` or `validate: false` in the
343
- programmatic API.
357
+ CLI OpenAPI generation always validates the generated spec. Programmatic callers can set `skipValidate: true` when they need to inspect an intermediate state.
344
358
 
345
359
  ## Streaming vs Buffered Responses
346
360
 
@@ -83,6 +83,24 @@ interface ForecastReturn {
83
83
  }
84
84
  ```
85
85
 
86
+ ## Working With Choice Unions
87
+
88
+ `--client-choice-mode union` changes generated `xs:choice` types from parallel optional fields into a base object plus an exclusive branch union. The default mode remains `all-optional`.
89
+
90
+ ```typescript
91
+ export type SearchRequest = SearchRequestChoiceBase & SearchRequestChoice1;
92
+
93
+ export interface SearchRequestChoiceBase {
94
+ tenantId: string;
95
+ }
96
+
97
+ export type SearchRequestChoice1 =
98
+ | { email: string; phone?: never }
99
+ | { phone: number; email?: never };
100
+ ```
101
+
102
+ Generated gateway test suites use one valid branch in union-mode mocks. They also include validation cases that reject payloads with multiple choice branches and payloads that omit a required choice branch.
103
+
86
104
  ## Type Safety
87
105
 
88
106
  All operations and types are fully typed:
@@ -56,7 +56,7 @@ npx wsdl-tsc openapi \
56
56
 
57
57
  Open `openapi.json` and review the paths, request/response schemas, and descriptions. The spec is derived from the same compiled catalog as the TypeScript types, so they stay aligned.
58
58
 
59
- Validate the spec with any OpenAPI tool. Built-in validation runs by default; disable with `--openapi-validate false` if you need to inspect an intermediate state.
59
+ Validate the spec with any OpenAPI tool. Built-in CLI validation always runs; programmatic callers can set `skipValidate: true` when they need to inspect an intermediate state.
60
60
 
61
61
  ## Step 2b (Optional): Opt Into Streaming for Large Responses
62
62
 
package/docs/migration.md CHANGED
@@ -108,7 +108,7 @@ All CLI flags changed from camelCase to kebab-case. The URN format changed to a
108
108
 
109
109
  | Old Flag (0.7.x) | New Flag (0.8.x) |
110
110
  |-------------------|-------------------|
111
- | `--versionTag` | `--openapi-version` |
111
+ | `--versionTag` | `--openapi-version-tag` |
112
112
  | `--basePath` | `--openapi-base-path` |
113
113
  | `--pathStyle` | `--openapi-path-style` |
114
114
  | `--closedSchemas` | `--openapi-closed-schemas` |
@@ -71,7 +71,7 @@ Generated at the path specified by `--openapi-file`.
71
71
 
72
72
  The spec includes one POST path per WSDL operation, request and response schemas in `components/schemas`, and descriptions derived from WSDL documentation annotations. Schemas are generated from the same catalog used for TypeScript types, so the two outputs stay aligned.
73
73
 
74
- OpenAPI validation runs by default using `@apidevtools/swagger-parser`. Disable with `--openapi-validate false`.
74
+ CLI OpenAPI generation always validates the generated spec using `@apidevtools/swagger-parser`.
75
75
 
76
76
  ### Stream Schema Extension
77
77
 
@@ -20,7 +20,7 @@ Regenerate safely in CI/CD without spurious diffs.
20
20
 
21
21
  ### OpenAPI Validation
22
22
 
23
- Enabled by default using @apidevtools/swagger-parser. Validates schema structure, resolves all $ref references, catches missing schemas and circular dependencies. Disable with `--openapi-validate false`.
23
+ CLI OpenAPI generation always validates with `@apidevtools/swagger-parser`. It checks schema structure, resolves `$ref` references, and catches missing schemas and circular dependencies.
24
24
 
25
25
  ### Gateway Contract Validation
26
26
 
@@ -89,11 +89,11 @@ Errors raised before the first record use the normal gateway error envelope (cli
89
89
 
90
90
  ### Choice Elements
91
91
 
92
- Current strategy: all-optional (all branches optional). Discriminated union support is planned.
92
+ Default strategy: `all-optional`. The opt-in `--client-choice-mode union` mode emits TypeScript branch unions for generated client types and OpenAPI choice constraints for generated schemas.
93
93
 
94
94
  ### Union Types
95
95
 
96
- Experimental --client-choice-mode union available. May require manual refinement for complex patterns.
96
+ `--client-choice-mode union` is supported as an opt-in strategy. Future WSDL coverage matrix work will broaden fixture evidence for edge cases, but the released union mode is test-backed for compiler metadata, TypeScript output, OpenAPI constraints, generated mocks, and generated validation tests.
97
97
 
98
98
  ### WS-Policy
99
99
 
@@ -0,0 +1,32 @@
1
+ # TypeScript WSDL Client v0.25.2
2
+
3
+ ## Roadmap And CLI Contract Alignment
4
+
5
+ This release publishes the 1.0 readiness roadmap and tightens the CLI reference against the implemented command surface.
6
+
7
+ ## What This Improves
8
+
9
+ The 1.0 roadmap is now split into concrete implementation slices for contract audit, OpenAPI compatibility, choice union mode, JSON array streaming, WSDL coverage, and release gates. The CLI reference also matches the actual public flags, which reduces confusion around OpenAPI versioning, default methods, validation behavior, and pipeline cleanup.
10
+
11
+ ## Highlights
12
+
13
+ - Adds the 1.0 roadmap breakdown with independent implementation slice plans.
14
+ - Aligns CLI documentation with `--openapi-version-tag`, `--openapi-default-method`, and `--clean`.
15
+ - Adds contract coverage that compares public CLI declarations with the CLI reference.
16
+ - Keeps release dependency maintenance from downgrading newer pins when npm metadata points backward.
17
+
18
+ ## Upgrade Notes
19
+
20
+ No special upgrade steps.
21
+
22
+ ## Validation
23
+
24
+ - CI passed.
25
+ - NPM package contents were validated.
26
+ - Documentation links and TypeScript fenced snippets were validated.
27
+ - Agent skill artifact was validated and packaged.
28
+ - Release preflight passed against the target tag.
29
+
30
+ ## Notes
31
+
32
+ Release tag: `v0.25.2`.
@@ -0,0 +1,32 @@
1
+ # TypeScript WSDL Client v0.26.0
2
+
3
+ ## Choice Union Mode
4
+
5
+ This release completes opt-in `xs:choice` union mode for generated TypeScript clients, OpenAPI schemas, and generated gateway tests.
6
+
7
+ ## What This Improves
8
+
9
+ Developers can now choose stricter generated models for WSDL choice groups without changing the default output. The default `all-optional` mode remains stable, while `--client-choice-mode union` emits exclusive TypeScript branch unions and OpenAPI constraints that reject invalid branch combinations in generated Fastify gateways.
10
+
11
+ ## Highlights
12
+
13
+ - Completes opt-in `--client-choice-mode union` support across compiler metadata, TypeScript output, OpenAPI schemas, mocks, and generated tests.
14
+ - Aligns CLI, API, generated-code, production, supported-patterns, and roadmap documentation with the released union-mode behavior.
15
+ - Updates the packaged agent skill guidance so consumer-project agents can discover choice union mode and generated-code examples.
16
+ - Keeps `all-optional` as the default choice strategy for backward compatibility.
17
+
18
+ ## Upgrade Notes
19
+
20
+ No special upgrade steps. Existing users keep the default `all-optional` choice behavior unless they opt into `--client-choice-mode union`.
21
+
22
+ ## Validation
23
+
24
+ - CI passed.
25
+ - NPM package contents were validated.
26
+ - Documentation links and TypeScript fenced snippets were validated.
27
+ - Agent skill artifact was validated and packaged.
28
+ - Release preflight passed against the target tag.
29
+
30
+ ## Notes
31
+
32
+ Release tag: `v0.26.0`.