@techspokes/typescript-wsdl-client 0.24.0 → 0.26.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 +3 -2
- package/dist/app/generateApp.js +1 -1
- package/dist/client/generateTypes.d.ts.map +1 -1
- package/dist/client/generateTypes.js +136 -65
- package/dist/compiler/schemaCompiler.d.ts +19 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +152 -49
- package/dist/compiler/shapeResolver.js +15 -0
- package/dist/gateway/generators.d.ts.map +1 -1
- package/dist/gateway/generators.js +11 -7
- package/dist/openapi/generateSchemas.d.ts.map +1 -1
- package/dist/openapi/generateSchemas.js +39 -3
- package/dist/test/generateTests.js +1 -1
- package/dist/test/generators.d.ts +2 -2
- package/dist/test/generators.d.ts.map +1 -1
- package/dist/test/generators.js +84 -3
- package/dist/test/mockData.d.ts +19 -0
- package/dist/test/mockData.d.ts.map +1 -1
- package/dist/test/mockData.js +9 -0
- package/docs/README.md +1 -0
- package/docs/api-reference.md +21 -0
- package/docs/cli-reference.md +37 -8
- package/docs/concepts.md +18 -4
- package/docs/generated-code.md +18 -0
- package/docs/migration-playbook.md +1 -1
- package/docs/migration.md +1 -1
- package/docs/output-anatomy.md +1 -1
- package/docs/production.md +3 -3
- package/docs/releases/v0.25.0.md +31 -0
- package/docs/releases/v0.25.2.md +32 -0
- package/docs/releases/v0.26.0.md +32 -0
- package/docs/roadmap/README.md +89 -0
- package/docs/roadmap/v1.0-choice-union-mode.md +90 -0
- package/docs/roadmap/v1.0-contract-audit.md +74 -0
- package/docs/roadmap/v1.0-json-array-streaming.md +99 -0
- package/docs/roadmap/v1.0-openapi-fastify-compatibility.md +97 -0
- package/docs/roadmap/v1.0-release-candidate-gates.md +77 -0
- package/docs/roadmap/v1.0-wsdl-coverage-matrix.md +89 -0
- package/docs/supported-patterns.md +2 -1
- package/package.json +4 -4
|
@@ -418,6 +418,10 @@ const ARRAY_WRAPPERS: Record<string, string> = ${JSON.stringify(arrayWrappers, n
|
|
|
418
418
|
*/
|
|
419
419
|
const CHILDREN_TYPES: Record<string, Record<string, string>> = ${JSON.stringify(childTypes, null, 2)};
|
|
420
420
|
|
|
421
|
+
function isSafeObjectKey(key: string): boolean {
|
|
422
|
+
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
|
|
423
|
+
}
|
|
424
|
+
|
|
421
425
|
/**
|
|
422
426
|
* Recursively unwraps ArrayOf* wrapper objects in a SOAP response so the
|
|
423
427
|
* data matches the flattened OpenAPI array schemas.
|
|
@@ -447,14 +451,14 @@ export function unwrapArrayWrappers(data: unknown, typeName: string): unknown {
|
|
|
447
451
|
// Recurse into children whose types may contain wrappers
|
|
448
452
|
if (typeName in CHILDREN_TYPES) {
|
|
449
453
|
const children = CHILDREN_TYPES[typeName];
|
|
454
|
+
const record = data as Record<string, unknown>;
|
|
450
455
|
for (const [propName, propType] of Object.entries(children)) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
456
|
+
if (!isSafeObjectKey(propName) || !Object.hasOwn(record, propName)) continue;
|
|
457
|
+
const val = record[propName];
|
|
458
|
+
if (Array.isArray(val)) {
|
|
459
|
+
record[propName] = val.map(item => unwrapArrayWrappers(item, propType));
|
|
460
|
+
} else {
|
|
461
|
+
record[propName] = unwrapArrayWrappers(val, propType);
|
|
458
462
|
}
|
|
459
463
|
}
|
|
460
464
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateSchemas.d.ts","sourceRoot":"","sources":["../../src/openapi/generateSchemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAgB,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEhG;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"generateSchemas.d.ts","sourceRoot":"","sources":["../../src/openapi/generateSchemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAgB,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEhG;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAkNpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,sBAAsB,GAAG,iBAAiB,CA2D1G"}
|
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/test/generators.js
CHANGED
|
@@ -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
|
|
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
|
-
})
|
|
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
|
*
|
package/dist/test/mockData.d.ts
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"mockData.d.ts","sourceRoot":"","sources":["../../src/test/mockData.ts"],"names":[],"mappings":"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"}
|
package/dist/test/mockData.js
CHANGED
|
@@ -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
|
|
package/docs/api-reference.md
CHANGED
|
@@ -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
|
package/docs/cli-reference.md
CHANGED
|
@@ -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` |
|
|
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
|
|
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
|
-
|
|
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
|
|
package/docs/generated-code.md
CHANGED
|
@@ -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
|
|
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` |
|
package/docs/output-anatomy.md
CHANGED
|
@@ -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
|
|
74
|
+
CLI OpenAPI generation always validates the generated spec using `@apidevtools/swagger-parser`.
|
|
75
75
|
|
|
76
76
|
### Stream Schema Extension
|
|
77
77
|
|
package/docs/production.md
CHANGED
|
@@ -20,7 +20,7 @@ Regenerate safely in CI/CD without spurious diffs.
|
|
|
20
20
|
|
|
21
21
|
### OpenAPI Validation
|
|
22
22
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,31 @@
|
|
|
1
|
+
# TypeScript WSDL Client v0.25.0
|
|
2
|
+
|
|
3
|
+
## Security Scan Hardening
|
|
4
|
+
|
|
5
|
+
This release resolves GitHub CodeQL findings that were reported after `v0.24.0` shipped.
|
|
6
|
+
|
|
7
|
+
## What This Improves
|
|
8
|
+
|
|
9
|
+
Generated gateway runtimes now avoid assigning through prototype-sensitive property names while recursively unwrapping SOAP array wrappers. Release preflight changelog parsing also avoids dynamic regular expression construction for version headers, so GitHub security scanning can validate the release tooling without sanitizer warnings.
|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
- Hardens generated gateway runtime unwrapping against `__proto__`, `constructor`, and `prototype` child keys.
|
|
14
|
+
- Replaces release preflight's dynamic changelog header regex with literal version matching.
|
|
15
|
+
- Adds regression coverage for both GitHub CodeQL findings.
|
|
16
|
+
|
|
17
|
+
## Upgrade Notes
|
|
18
|
+
|
|
19
|
+
No runtime upgrade steps. Regenerate gateway output to receive the hardened `runtime.ts` code in generated projects.
|
|
20
|
+
|
|
21
|
+
## Validation
|
|
22
|
+
|
|
23
|
+
- CI passed.
|
|
24
|
+
- NPM package contents were validated.
|
|
25
|
+
- Documentation links and TypeScript fenced snippets were validated.
|
|
26
|
+
- Agent skill artifact was validated and packaged.
|
|
27
|
+
- Release preflight passed against the target tag.
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
Release tag: `v0.25.0`.
|