@techspokes/typescript-wsdl-client 0.10.4 → 0.11.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 +26 -0
- package/dist/cli.js +18 -1
- package/dist/client/generateOperations.d.ts +13 -0
- package/dist/client/generateOperations.d.ts.map +1 -0
- package/dist/client/generateOperations.js +71 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +15 -1
- package/dist/gateway/generateGateway.d.ts +1 -0
- package/dist/gateway/generateGateway.d.ts.map +1 -1
- package/dist/gateway/generateGateway.js +4 -2
- package/dist/gateway/generators.d.ts +2 -15
- package/dist/gateway/generators.d.ts.map +1 -1
- package/dist/gateway/generators.js +111 -27
- package/dist/gateway/helpers.d.ts +4 -2
- package/dist/gateway/helpers.d.ts.map +1 -1
- package/dist/gateway/helpers.js +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/loader/wsdlLoader.d.ts.map +1 -1
- package/dist/loader/wsdlLoader.js +30 -4
- package/dist/openapi/generateOpenAPI.d.ts +1 -0
- package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
- package/dist/openapi/generateOpenAPI.js +1 -0
- package/dist/openapi/generateSchemas.d.ts +1 -0
- package/dist/openapi/generateSchemas.d.ts.map +1 -1
- package/dist/openapi/generateSchemas.js +4 -3
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +6 -1
- package/dist/util/builder.d.ts.map +1 -1
- package/dist/util/builder.js +1 -0
- package/dist/util/cli.d.ts +3 -2
- package/dist/util/cli.d.ts.map +1 -1
- package/dist/util/cli.js +14 -4
- package/dist/util/errors.d.ts +37 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +37 -0
- package/docs/README.md +1 -0
- package/docs/cli-reference.md +2 -0
- package/docs/concepts.md +29 -2
- package/docs/generated-code.md +24 -0
- package/docs/testing.md +193 -0
- package/package.json +9 -4
|
@@ -14,6 +14,7 @@ import { XMLParser } from "fast-xml-parser";
|
|
|
14
14
|
import { fetchText } from "./fetch.js";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { getChildrenWithLocalName } from "../util/tools.js";
|
|
17
|
+
import { WsdlCompilationError } from "../util/errors.js";
|
|
17
18
|
// Configure XML parser to preserve attributes with @_ prefix
|
|
18
19
|
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
|
|
19
20
|
/**
|
|
@@ -32,12 +33,37 @@ const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_
|
|
|
32
33
|
*/
|
|
33
34
|
export async function loadWsdl(wsdlUrlOrPath) {
|
|
34
35
|
// Fetch and parse the WSDL document
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
let wsdlUri;
|
|
37
|
+
let text;
|
|
38
|
+
try {
|
|
39
|
+
const fetched = await fetchText(wsdlUrlOrPath);
|
|
40
|
+
wsdlUri = fetched.uri;
|
|
41
|
+
text = fetched.text;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
throw new WsdlCompilationError(`Failed to load WSDL document: ${err instanceof Error ? err.message : String(err)}`, {
|
|
45
|
+
file: wsdlUrlOrPath,
|
|
46
|
+
suggestion: "Verify the file path or URL is correct and accessible.",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
let wsdlXml;
|
|
50
|
+
try {
|
|
51
|
+
wsdlXml = parser.parse(text);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
throw new WsdlCompilationError(`Failed to parse WSDL/XSD document as XML: ${err instanceof Error ? err.message : String(err)}`, {
|
|
55
|
+
file: wsdlUrlOrPath,
|
|
56
|
+
suggestion: "Validate the XML with an external tool (e.g., xmllint) and fix any syntax errors.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
37
59
|
// Extract the WSDL definitions node (with or without namespace prefix)
|
|
38
60
|
const defs = wsdlXml["wsdl:definitions"] || wsdlXml["definitions"];
|
|
39
|
-
if (!defs)
|
|
40
|
-
throw new
|
|
61
|
+
if (!defs) {
|
|
62
|
+
throw new WsdlCompilationError("Not a WSDL 1.1 file: missing <wsdl:definitions> root element.", {
|
|
63
|
+
file: wsdlUrlOrPath,
|
|
64
|
+
suggestion: "Ensure the file is a valid WSDL 1.1 document with a <definitions> root element.",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
41
67
|
// Extract namespace prefixes declared at the WSDL level
|
|
42
68
|
const prefixMap = {};
|
|
43
69
|
for (const [k, v] of Object.entries(defs)) {
|
|
@@ -76,6 +76,7 @@ export interface GenerateOpenAPIOptions {
|
|
|
76
76
|
skipValidate?: boolean;
|
|
77
77
|
envelopeNamespace?: string;
|
|
78
78
|
errorNamespace?: string;
|
|
79
|
+
flattenArrayWrappers?: boolean;
|
|
79
80
|
}
|
|
80
81
|
export declare function generateOpenAPI(opts: GenerateOpenAPIOptions): Promise<{
|
|
81
82
|
doc: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateOpenAPI.d.ts","sourceRoot":"","sources":["../../src/openapi/generateOpenAPI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAAiB,KAAK,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAKnF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"generateOpenAPI.d.ts","sourceRoot":"","sources":["../../src/openapi/generateOpenAPI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAAiB,KAAK,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAKnF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC;IAC3E,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CA0RD"}
|
|
@@ -80,6 +80,7 @@ export async function generateOpenAPI(opts) {
|
|
|
80
80
|
const schemas = generateSchemas(compiled, {
|
|
81
81
|
closedSchemas: opts.closedSchemas,
|
|
82
82
|
pruneUnusedSchemas: opts.pruneUnusedSchemas,
|
|
83
|
+
flattenArrayWrappers: opts.flattenArrayWrappers,
|
|
83
84
|
});
|
|
84
85
|
// Build paths
|
|
85
86
|
const tagStyle = opts.tagStyle || "default";
|
|
@@ -24,6 +24,7 @@ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
|
|
|
24
24
|
export interface GenerateSchemasOptions {
|
|
25
25
|
closedSchemas?: boolean;
|
|
26
26
|
pruneUnusedSchemas?: boolean;
|
|
27
|
+
flattenArrayWrappers?: boolean;
|
|
27
28
|
}
|
|
28
29
|
export type ComponentsSchemas = Record<string, any>;
|
|
29
30
|
/**
|
|
@@ -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;
|
|
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;AAwIpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,sBAAsB,GAAG,iBAAiB,CA+C1G"}
|
|
@@ -47,7 +47,7 @@ function isArrayWrapper(t) {
|
|
|
47
47
|
return null;
|
|
48
48
|
return { itemType: e.tsType };
|
|
49
49
|
}
|
|
50
|
-
function buildComplexSchema(t, closed, knownTypeNames, aliasNames) {
|
|
50
|
+
function buildComplexSchema(t, closed, knownTypeNames, aliasNames, flattenWrappers) {
|
|
51
51
|
// Use knownTypeNames/aliasNames to validate $ref targets so we surface
|
|
52
52
|
// compiler issues early instead of emitting dangling references in OpenAPI output.
|
|
53
53
|
function refOrPrimitive(ts) {
|
|
@@ -77,7 +77,7 @@ function buildComplexSchema(t, closed, knownTypeNames, aliasNames) {
|
|
|
77
77
|
return { $ref: `#/components/schemas/${ts}` };
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
const arrayWrap = isArrayWrapper(t);
|
|
80
|
+
const arrayWrap = flattenWrappers ? isArrayWrapper(t) : null;
|
|
81
81
|
if (arrayWrap) {
|
|
82
82
|
const item = refOrPrimitive(String(arrayWrap.itemType));
|
|
83
83
|
return { type: "array", items: item };
|
|
@@ -148,6 +148,7 @@ function buildComplexSchema(t, closed, knownTypeNames, aliasNames) {
|
|
|
148
148
|
*/
|
|
149
149
|
export function generateSchemas(compiled, opts) {
|
|
150
150
|
const closed = !!opts.closedSchemas;
|
|
151
|
+
const flattenWrappers = opts.flattenArrayWrappers !== false; // default true
|
|
151
152
|
const schemas = {};
|
|
152
153
|
const typeNames = new Set(compiled.types.map(t => t.name));
|
|
153
154
|
const aliasNames = new Set(compiled.aliases.map(a => a.name));
|
|
@@ -156,7 +157,7 @@ export function generateSchemas(compiled, opts) {
|
|
|
156
157
|
schemas[a.name] = buildAliasSchema(a);
|
|
157
158
|
}
|
|
158
159
|
for (const t of compiled.types) {
|
|
159
|
-
schemas[t.name] = buildComplexSchema(t, closed, typeNames, aliasNames);
|
|
160
|
+
schemas[t.name] = buildComplexSchema(t, closed, typeNames, aliasNames, flattenWrappers);
|
|
160
161
|
}
|
|
161
162
|
if (opts.pruneUnusedSchemas) {
|
|
162
163
|
// Root references: each operation's inputElement.local, outputElement.local
|
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"
|
|
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;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,CA4HhH"}
|
package/dist/pipeline.js
CHANGED
|
@@ -13,6 +13,7 @@ import { compileCatalog } from "./compiler/schemaCompiler.js";
|
|
|
13
13
|
import { generateClient } from "./client/generateClient.js";
|
|
14
14
|
import { generateTypes } from "./client/generateTypes.js";
|
|
15
15
|
import { generateUtils } from "./client/generateUtils.js";
|
|
16
|
+
import { generateOperations } from "./client/generateOperations.js";
|
|
16
17
|
import { generateCatalog } from "./compiler/generateCatalog.js";
|
|
17
18
|
import { generateOpenAPI } from "./openapi/generateOpenAPI.js";
|
|
18
19
|
import { generateGateway } from "./gateway/generateGateway.js";
|
|
@@ -63,7 +64,7 @@ export async function runGenerationPipeline(opts) {
|
|
|
63
64
|
success(`Compiled catalog written to ${opts.catalogOut}`);
|
|
64
65
|
// Step 4: Optionally generate TypeScript client artifacts
|
|
65
66
|
if (opts.clientOutDir) {
|
|
66
|
-
emitClientArtifacts(opts.clientOutDir, compiled, generateClient, generateTypes, generateUtils);
|
|
67
|
+
emitClientArtifacts(opts.clientOutDir, compiled, generateClient, generateTypes, generateUtils, generateOperations);
|
|
67
68
|
}
|
|
68
69
|
// Step 4: Optionally generate OpenAPI specification
|
|
69
70
|
let openapiDoc;
|
|
@@ -99,6 +100,10 @@ export async function runGenerationPipeline(opts) {
|
|
|
99
100
|
clientDir: opts.clientOutDir ? path.resolve(opts.clientOutDir) : undefined,
|
|
100
101
|
// Reuse the same imports mode as the TypeScript client/types/utils emitters
|
|
101
102
|
imports: finalCompiler.imports,
|
|
103
|
+
// Pass catalog for runtime unwrap generation
|
|
104
|
+
catalogFile: path.resolve(opts.catalogOut),
|
|
105
|
+
// Thread flatten flag from OpenAPI options
|
|
106
|
+
flattenArrayWrappers: opts.openapi?.flattenArrayWrappers,
|
|
102
107
|
});
|
|
103
108
|
success(`Gateway code generated in ${gatewayOutDir}`);
|
|
104
109
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/util/builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,+BAA+B,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,CAgBhF;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,EAC5C,OAAO,EAAE,MAAM,EAAE,GAChB,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/util/builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,+BAA+B,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,CAgBhF;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,EAC5C,OAAO,EAAE,MAAM,EAAE,GAChB,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAAC,CAqBtF"}
|
package/dist/util/builder.js
CHANGED
|
@@ -47,6 +47,7 @@ export function buildOpenApiOptionsFromArgv(argv, format, servers) {
|
|
|
47
47
|
tagsFile: argv["openapi-tags-file"],
|
|
48
48
|
title: argv["openapi-title"],
|
|
49
49
|
version: argv["openapi-version-tag"],
|
|
50
|
+
flattenArrayWrappers: argv["openapi-flatten-array-wrappers"],
|
|
50
51
|
asYaml: format === "yaml",
|
|
51
52
|
};
|
|
52
53
|
}
|
package/dist/util/cli.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export declare function reportCompilationStats(wsdlCatalog: {
|
|
|
81
81
|
operations: any[];
|
|
82
82
|
}): void;
|
|
83
83
|
/**
|
|
84
|
-
* Emit TypeScript client artifacts (client.ts, types.ts, utils.ts)
|
|
84
|
+
* Emit TypeScript client artifacts (client.ts, types.ts, utils.ts, operations.ts)
|
|
85
85
|
*
|
|
86
86
|
* Note: catalog.json is NOT emitted by this function - it should be handled
|
|
87
87
|
* separately by the caller using generateCatalog() with explicit --catalog-file path.
|
|
@@ -91,8 +91,9 @@ export declare function reportCompilationStats(wsdlCatalog: {
|
|
|
91
91
|
* @param generateClient - Client generator function
|
|
92
92
|
* @param generateTypes - Types generator function
|
|
93
93
|
* @param generateUtils - Utils generator function
|
|
94
|
+
* @param generateOperations - Operations interface generator function
|
|
94
95
|
*/
|
|
95
|
-
export declare function emitClientArtifacts(outDir: string, compiled: any, generateClient: (path: string, compiled: any) => void, generateTypes: (path: string, compiled: any) => void, generateUtils: (path: string, compiled: any) => void): void;
|
|
96
|
+
export declare function emitClientArtifacts(outDir: string, compiled: any, generateClient: (path: string, compiled: any) => void, generateTypes: (path: string, compiled: any) => void, generateUtils: (path: string, compiled: any) => void, generateOperations?: (path: string, compiled: any) => void): void;
|
|
96
97
|
/**
|
|
97
98
|
* Validate gateway generation requirements
|
|
98
99
|
*
|
package/dist/util/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/util/cli.ts"],"names":[],"mappings":"
|
|
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,CAAA;CAAE,GAC5C,IAAI,CAIN;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
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import fs from "node:fs";
|
|
10
|
+
import { WsdlCompilationError } from "./errors.js";
|
|
10
11
|
/**
|
|
11
12
|
* Parse comma-separated status codes from CLI argument
|
|
12
13
|
*
|
|
@@ -94,8 +95,13 @@ export function info(message) {
|
|
|
94
95
|
* @param exitCode - Process exit code (default: 1)
|
|
95
96
|
*/
|
|
96
97
|
export function handleCLIError(errorObj, exitCode = 1) {
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
if (errorObj instanceof WsdlCompilationError) {
|
|
99
|
+
error(errorObj.toUserMessage());
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const message = errorObj instanceof Error ? errorObj.message : String(errorObj);
|
|
103
|
+
error(message);
|
|
104
|
+
}
|
|
99
105
|
process.exit(exitCode);
|
|
100
106
|
}
|
|
101
107
|
/**
|
|
@@ -125,7 +131,7 @@ export function reportCompilationStats(wsdlCatalog, compiled) {
|
|
|
125
131
|
info(`Operations: ${compiled.operations.length}`);
|
|
126
132
|
}
|
|
127
133
|
/**
|
|
128
|
-
* Emit TypeScript client artifacts (client.ts, types.ts, utils.ts)
|
|
134
|
+
* Emit TypeScript client artifacts (client.ts, types.ts, utils.ts, operations.ts)
|
|
129
135
|
*
|
|
130
136
|
* Note: catalog.json is NOT emitted by this function - it should be handled
|
|
131
137
|
* separately by the caller using generateCatalog() with explicit --catalog-file path.
|
|
@@ -135,12 +141,16 @@ export function reportCompilationStats(wsdlCatalog, compiled) {
|
|
|
135
141
|
* @param generateClient - Client generator function
|
|
136
142
|
* @param generateTypes - Types generator function
|
|
137
143
|
* @param generateUtils - Utils generator function
|
|
144
|
+
* @param generateOperations - Operations interface generator function
|
|
138
145
|
*/
|
|
139
|
-
export function emitClientArtifacts(outDir, compiled, generateClient, generateTypes, generateUtils) {
|
|
146
|
+
export function emitClientArtifacts(outDir, compiled, generateClient, generateTypes, generateUtils, generateOperations) {
|
|
140
147
|
fs.mkdirSync(outDir, { recursive: true });
|
|
141
148
|
generateClient(path.join(outDir, "client.ts"), compiled);
|
|
142
149
|
generateTypes(path.join(outDir, "types.ts"), compiled);
|
|
143
150
|
generateUtils(path.join(outDir, "utils.ts"), compiled);
|
|
151
|
+
if (generateOperations) {
|
|
152
|
+
generateOperations(path.join(outDir, "operations.ts"), compiled);
|
|
153
|
+
}
|
|
144
154
|
success(`Generated TypeScript client in ${outDir}`);
|
|
145
155
|
}
|
|
146
156
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error types for WSDL compilation
|
|
3
|
+
*
|
|
4
|
+
* Provides context-rich error messages with element names, namespaces,
|
|
5
|
+
* and actionable suggestions for common issues.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Context information attached to WSDL compilation errors
|
|
9
|
+
*/
|
|
10
|
+
export interface WsdlErrorContext {
|
|
11
|
+
/** Element or type name that triggered the error */
|
|
12
|
+
element?: string;
|
|
13
|
+
/** XML namespace where the error occurred */
|
|
14
|
+
namespace?: string;
|
|
15
|
+
/** File or URI of the source document */
|
|
16
|
+
file?: string;
|
|
17
|
+
/** Referenced-by context (e.g., "element 'bar' in type 'Baz'") */
|
|
18
|
+
referencedBy?: string;
|
|
19
|
+
/** Actionable suggestion for resolving the error */
|
|
20
|
+
suggestion?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Structured error for WSDL/XSD compilation failures.
|
|
24
|
+
*
|
|
25
|
+
* Extends Error with context fields for element, namespace, file, and suggestion.
|
|
26
|
+
* The CLI error handler formats these into multi-line user-friendly messages.
|
|
27
|
+
*/
|
|
28
|
+
export declare class WsdlCompilationError extends Error {
|
|
29
|
+
readonly context: WsdlErrorContext;
|
|
30
|
+
readonly name = "WsdlCompilationError";
|
|
31
|
+
constructor(message: string, context?: WsdlErrorContext);
|
|
32
|
+
/**
|
|
33
|
+
* Formats the error into a user-friendly multi-line message.
|
|
34
|
+
*/
|
|
35
|
+
toUserMessage(): string;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/util/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aAK3B,OAAO,EAAE,gBAAgB;IAJ3C,SAAkB,IAAI,0BAA0B;gBAG9C,OAAO,EAAE,MAAM,EACC,OAAO,GAAE,gBAAqB;IAKhD;;OAEG;IACH,aAAa,IAAI,MAAM;CASxB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error types for WSDL compilation
|
|
3
|
+
*
|
|
4
|
+
* Provides context-rich error messages with element names, namespaces,
|
|
5
|
+
* and actionable suggestions for common issues.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Structured error for WSDL/XSD compilation failures.
|
|
9
|
+
*
|
|
10
|
+
* Extends Error with context fields for element, namespace, file, and suggestion.
|
|
11
|
+
* The CLI error handler formats these into multi-line user-friendly messages.
|
|
12
|
+
*/
|
|
13
|
+
export class WsdlCompilationError extends Error {
|
|
14
|
+
context;
|
|
15
|
+
name = "WsdlCompilationError";
|
|
16
|
+
constructor(message, context = {}) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.context = context;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Formats the error into a user-friendly multi-line message.
|
|
22
|
+
*/
|
|
23
|
+
toUserMessage() {
|
|
24
|
+
const parts = [this.message];
|
|
25
|
+
if (this.context.element)
|
|
26
|
+
parts.push(` Element: ${this.context.element}`);
|
|
27
|
+
if (this.context.namespace)
|
|
28
|
+
parts.push(` Namespace: ${this.context.namespace}`);
|
|
29
|
+
if (this.context.referencedBy)
|
|
30
|
+
parts.push(` Referenced by: ${this.context.referencedBy}`);
|
|
31
|
+
if (this.context.file)
|
|
32
|
+
parts.push(` File: ${this.context.file}`);
|
|
33
|
+
if (this.context.suggestion)
|
|
34
|
+
parts.push(` Suggestion: ${this.context.suggestion}`);
|
|
35
|
+
return parts.join("\n");
|
|
36
|
+
}
|
|
37
|
+
}
|
package/docs/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Human-maintained reference documents for `@techspokes/typescript-wsdl-client`. T
|
|
|
13
13
|
- [generated-code.md](generated-code.md) – Using clients and types
|
|
14
14
|
- [migration.md](migration.md) – Upgrade paths between versions
|
|
15
15
|
- [production.md](production.md) – CI/CD, validation, logging, limitations
|
|
16
|
+
- [testing.md](testing.md) – Testing patterns and mock client examples
|
|
16
17
|
- [troubleshooting.md](troubleshooting.md) – Common issues and debugging
|
|
17
18
|
|
|
18
19
|
## Conventions
|
package/docs/cli-reference.md
CHANGED
|
@@ -87,6 +87,7 @@ The catalog is auto-placed alongside the first available output directory: `{cli
|
|
|
87
87
|
| `--openapi-method` | `post` | Default HTTP method |
|
|
88
88
|
| `--openapi-tag-style` | `default` | Tag inference: default, service, or first |
|
|
89
89
|
| `--openapi-closed-schemas` | `false` | Add additionalProperties: false |
|
|
90
|
+
| `--openapi-flatten-array-wrappers` | `true` | Flatten ArrayOf* wrappers to plain arrays; emit runtime unwrap in gateway |
|
|
90
91
|
| `--openapi-prune-unused-schemas` | `false` | Emit only referenced schemas |
|
|
91
92
|
| `--openapi-envelope-namespace` | `ResponseEnvelope` | Envelope component name suffix |
|
|
92
93
|
| `--openapi-error-namespace` | `ErrorObject` | Error object name suffix |
|
|
@@ -107,6 +108,7 @@ The catalog is auto-placed alongside the first available output directory: `{cli
|
|
|
107
108
|
| `--gateway-decorator-name` | {serviceSlug}Client | Fastify decorator name |
|
|
108
109
|
| `--gateway-skip-plugin` | `false` | Skip plugin.ts generation |
|
|
109
110
|
| `--gateway-skip-runtime` | `false` | Skip runtime.ts generation |
|
|
111
|
+
| `--openapi-flatten-array-wrappers` | `true` | Generate runtime unwrap for ArrayOf* wrapper types |
|
|
110
112
|
|
|
111
113
|
### App Scaffold Flags
|
|
112
114
|
|
package/docs/concepts.md
CHANGED
|
@@ -163,7 +163,8 @@ interface MyType {
|
|
|
163
163
|
## Array Wrapper Flattening
|
|
164
164
|
|
|
165
165
|
A complex type whose only child is a single repeated element with no attributes
|
|
166
|
-
collapses to an array schema in OpenAPI.
|
|
166
|
+
collapses to an array schema in OpenAPI. Controlled by
|
|
167
|
+
`--openapi-flatten-array-wrappers` (default `true`).
|
|
167
168
|
|
|
168
169
|
```xml
|
|
169
170
|
<xs:complexType name="ArrayOfForecast">
|
|
@@ -173,7 +174,7 @@ collapses to an array schema in OpenAPI.
|
|
|
173
174
|
</xs:complexType>
|
|
174
175
|
```
|
|
175
176
|
|
|
176
|
-
|
|
177
|
+
With flattening enabled (default), the OpenAPI schema becomes a plain array:
|
|
177
178
|
|
|
178
179
|
```json
|
|
179
180
|
{
|
|
@@ -184,6 +185,32 @@ The resulting OpenAPI schema:
|
|
|
184
185
|
}
|
|
185
186
|
```
|
|
186
187
|
|
|
188
|
+
The TypeScript types preserve the wrapper structure:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
export interface ArrayOfForecast {
|
|
192
|
+
Forecast?: Forecast[];
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Runtime Unwrap
|
|
197
|
+
|
|
198
|
+
Because the SOAP client returns wrapper-shaped objects (`{ Forecast: [...] }`)
|
|
199
|
+
while the OpenAPI schema expects flat arrays, the generated gateway includes an
|
|
200
|
+
`unwrapArrayWrappers()` function in `runtime.ts`. Route handlers call it
|
|
201
|
+
automatically before serialization. This bridges the TS-type/schema gap without
|
|
202
|
+
requiring consumers to transform responses manually.
|
|
203
|
+
|
|
204
|
+
### Disabling Flattening
|
|
205
|
+
|
|
206
|
+
Pass `--openapi-flatten-array-wrappers false` to preserve the wrapper object
|
|
207
|
+
structure in OpenAPI schemas. When disabled:
|
|
208
|
+
|
|
209
|
+
- ArrayOf* types emit as `type: "object"` with their inner element as a property
|
|
210
|
+
- No `unwrapArrayWrappers()` function is generated in `runtime.ts`
|
|
211
|
+
- Route handlers pass SOAP responses through unmodified
|
|
212
|
+
- The OpenAPI schema matches the TypeScript types exactly
|
|
213
|
+
|
|
187
214
|
## Inheritance Flattening
|
|
188
215
|
|
|
189
216
|
Three XSD inheritance patterns are supported.
|
package/docs/generated-code.md
CHANGED
|
@@ -104,3 +104,27 @@ Key features of the generated handlers:
|
|
|
104
104
|
- **Envelope wrapping** — `buildSuccessEnvelope()` wraps the raw SOAP response in the standard `{ status, message, data, error }` envelope
|
|
105
105
|
|
|
106
106
|
See [Gateway Guide](gateway-guide.md) for the full architecture and [CLI Reference](cli-reference.md) for generation flags.
|
|
107
|
+
|
|
108
|
+
## Operations Interface
|
|
109
|
+
|
|
110
|
+
The generated `operations.ts` exports a typed interface (`{ServiceName}Operations`) that mirrors the concrete client class methods. Use it for dependency injection and testing:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import type { WeatherOperations } from "./client/operations.js";
|
|
114
|
+
|
|
115
|
+
const mock: WeatherOperations = {
|
|
116
|
+
GetCityWeatherByZIP: async (args) => ({
|
|
117
|
+
response: { GetCityWeatherByZIPResult: { Success: true } },
|
|
118
|
+
headers: {},
|
|
119
|
+
}),
|
|
120
|
+
// ...other operations
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The gateway plugin accepts any `WeatherOperations` implementation, so you can pass the mock directly:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
app.register(weatherGateway, { client: mock });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
See [Testing Guide](testing.md) for full integration test patterns.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
Guide to running and writing tests for wsdl-tsc development and for testing generated code in consumer projects.
|
|
4
|
+
|
|
5
|
+
See [README](../README.md) for quick start and [CONTRIBUTING](../CONTRIBUTING.md) for development setup.
|
|
6
|
+
|
|
7
|
+
## Test Architecture
|
|
8
|
+
|
|
9
|
+
The project uses three layers of testing:
|
|
10
|
+
|
|
11
|
+
1. **Unit tests** — Pure function tests for utilities, parsers, and type mapping
|
|
12
|
+
2. **Snapshot tests** — Baseline comparisons for all generated pipeline output
|
|
13
|
+
3. **Integration tests** — End-to-end gateway tests using Fastify's `inject()` with mock clients
|
|
14
|
+
|
|
15
|
+
All tests use [Vitest](https://vitest.dev/) and run in under 3 seconds.
|
|
16
|
+
|
|
17
|
+
## Running Tests
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm test # All Vitest tests
|
|
21
|
+
npm run test:unit # Unit tests only
|
|
22
|
+
npm run test:snap # Snapshot tests only
|
|
23
|
+
npm run test:integration # Integration tests only
|
|
24
|
+
npm run test:watch # Watch mode for development
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For the full CI pipeline including smoke tests:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run ci
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Unit Tests
|
|
34
|
+
|
|
35
|
+
Unit tests cover pure functions with no I/O or side effects:
|
|
36
|
+
|
|
37
|
+
- **`tools.test.ts`** — `pascal()`, `resolveQName()`, `explodePascal()`, `pascalToSnakeCase()`, `normalizeArray()`, `getChildrenWithLocalName()`, `getFirstWithLocalName()`
|
|
38
|
+
- **`casing.test.ts`** — `toPathSegment()` with kebab, asis, and lower styles
|
|
39
|
+
- **`primitives.test.ts`** — `xsdToTsPrimitive()` covering all XSD types (string-like, boolean, integers, decimals, floats, dates, any)
|
|
40
|
+
- **`errors.test.ts`** — `WsdlCompilationError` construction and `toUserMessage()` formatting
|
|
41
|
+
- **`schema-alignment.test.ts`** — Cross-validates TypeScript types, JSON schemas, and catalog.json for consistency
|
|
42
|
+
|
|
43
|
+
### Writing Unit Tests
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { describe, it, expect } from "vitest";
|
|
47
|
+
import { pascal } from "../../src/util/tools.js";
|
|
48
|
+
|
|
49
|
+
describe("pascal", () => {
|
|
50
|
+
it("converts kebab-case", () => {
|
|
51
|
+
expect(pascal("get-weather")).toBe("GetWeather");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Snapshot Tests
|
|
57
|
+
|
|
58
|
+
Snapshot tests capture the complete output of the pipeline as baselines. When a generator change intentionally alters output, the snapshot diff shows exactly what changed.
|
|
59
|
+
|
|
60
|
+
### How It Works
|
|
61
|
+
|
|
62
|
+
1. The pipeline runs against `examples/minimal/weather.wsdl` into a temp directory
|
|
63
|
+
2. Each generated file is read and compared against the stored snapshot
|
|
64
|
+
3. A file inventory snapshot detects added or removed files
|
|
65
|
+
|
|
66
|
+
### Updating Snapshots
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx vitest run test/snapshot -u
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Always review the diff before committing updated snapshots.
|
|
73
|
+
|
|
74
|
+
### What's Covered
|
|
75
|
+
|
|
76
|
+
- Client output: `client.ts`, `types.ts`, `utils.ts`, `operations.ts`, `catalog.json`
|
|
77
|
+
- OpenAPI: `openapi.json`
|
|
78
|
+
- Gateway core: `plugin.ts`, `routes.ts`, `schemas.ts`, `runtime.ts`, `_typecheck.ts`
|
|
79
|
+
- Gateway routes: one handler per WSDL operation
|
|
80
|
+
- Gateway schemas: all model and operation JSON schema files
|
|
81
|
+
- File inventory: complete listing of generated files
|
|
82
|
+
|
|
83
|
+
## Integration Tests
|
|
84
|
+
|
|
85
|
+
Integration tests verify the generated gateway works end-to-end by:
|
|
86
|
+
|
|
87
|
+
1. Running the pipeline in `beforeAll` to generate gateway code
|
|
88
|
+
2. Dynamically importing the generated plugin
|
|
89
|
+
3. Creating a Fastify instance with the plugin and a mock client
|
|
90
|
+
4. Using `fastify.inject()` to send HTTP requests and verify responses
|
|
91
|
+
|
|
92
|
+
### Mock Client Pattern
|
|
93
|
+
|
|
94
|
+
The generated `operations.ts` provides a typed interface for creating test doubles:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import type { WeatherOperations } from "../client/operations.js";
|
|
98
|
+
|
|
99
|
+
function createMockClient(): WeatherOperations {
|
|
100
|
+
return {
|
|
101
|
+
GetCityWeatherByZIP: async (args) => ({
|
|
102
|
+
response: {
|
|
103
|
+
GetCityWeatherByZIPResult: {
|
|
104
|
+
Success: true,
|
|
105
|
+
ResponseText: "City Found",
|
|
106
|
+
State: "NY",
|
|
107
|
+
City: "New York",
|
|
108
|
+
Temperature: "72",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
headers: {},
|
|
112
|
+
}),
|
|
113
|
+
GetCityForecastByZIP: async (args) => ({
|
|
114
|
+
response: {
|
|
115
|
+
GetCityForecastByZIPResult: {
|
|
116
|
+
Success: true,
|
|
117
|
+
ResponseText: "Forecast Found",
|
|
118
|
+
// Use SOAP wrapper shape — unwrapArrayWrappers() handles conversion
|
|
119
|
+
ForecastResult: { Forecast: [] },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
headers: {},
|
|
123
|
+
}),
|
|
124
|
+
GetWeatherInformation: async (args) => ({
|
|
125
|
+
response: {
|
|
126
|
+
// Use SOAP wrapper shape — unwrapArrayWrappers() handles conversion
|
|
127
|
+
GetWeatherInformationResult: { WeatherDescription: [] },
|
|
128
|
+
},
|
|
129
|
+
headers: {},
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Each method returns the same `{ response, headers }` shape as the real SOAP client. Use the wrapper object structure matching TypeScript types — the generated `unwrapArrayWrappers()` function handles conversion to the flat array shape expected by JSON schemas.
|
|
136
|
+
|
|
137
|
+
### Using the Mock with Fastify
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import Fastify from "fastify";
|
|
141
|
+
|
|
142
|
+
const app = Fastify();
|
|
143
|
+
await app.register(weatherGateway, {
|
|
144
|
+
client: createMockClient(),
|
|
145
|
+
prefix: "/v1/weather",
|
|
146
|
+
});
|
|
147
|
+
await app.ready();
|
|
148
|
+
|
|
149
|
+
const res = await app.inject({
|
|
150
|
+
method: "POST",
|
|
151
|
+
url: "/v1/weather/get-city-weather-by-zip",
|
|
152
|
+
payload: { ZIP: "10001" },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(res.statusCode).toBe(200);
|
|
156
|
+
expect(res.json().status).toBe("SUCCESS");
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Dynamic Import of Generated Code
|
|
160
|
+
|
|
161
|
+
Integration tests dynamically import generated `.ts` files from temp directories. This works because Vitest's Vite module resolution handles TypeScript imports, JSON import attributes, and bare specifiers from the project's `node_modules`:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { pathToFileURL } from "node:url";
|
|
165
|
+
|
|
166
|
+
const pluginModule = await import(pathToFileURL(join(outDir, "gateway", "plugin.ts")).href);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Known Issues
|
|
170
|
+
|
|
171
|
+
### ArrayOf* Schema-Type Mismatch (Resolved)
|
|
172
|
+
|
|
173
|
+
JSON schemas flatten SOAP `ArrayOf*` wrapper types to plain `type: "array"` (when `--openapi-flatten-array-wrappers` is `true`, the default), while TypeScript types preserve the wrapper structure (e.g., `ArrayOfForecast = { Forecast?: Forecast[] }`).
|
|
174
|
+
|
|
175
|
+
This mismatch is resolved by the generated `unwrapArrayWrappers()` function in `runtime.ts`. Route handlers call it automatically to strip wrapper objects before Fastify serialization. Mock clients should return the real SOAP wrapper structure — the unwrap function handles the conversion.
|
|
176
|
+
|
|
177
|
+
When `--openapi-flatten-array-wrappers false` is used, ArrayOf* types are emitted as `type: "object"` and no unwrap function is generated. In this mode, mock data should use the wrapper object shape matching both the TypeScript types and the JSON schemas.
|
|
178
|
+
|
|
179
|
+
### Error Details Serialization
|
|
180
|
+
|
|
181
|
+
The `classifyError()` function puts `err.message` (a string) in the `details` field for connection and timeout errors, but the error JSON schema defines `details` as `object | null`. This causes Fastify serialization failures for 503/504 error responses. Test error classification directly via `classifyError()` rather than through Fastify's `inject()` for these error types.
|
|
182
|
+
|
|
183
|
+
## For Consumer Projects
|
|
184
|
+
|
|
185
|
+
If you're using wsdl-tsc as a dependency and want to test your integration:
|
|
186
|
+
|
|
187
|
+
1. Generate code with `npx wsdl-tsc pipeline`
|
|
188
|
+
2. Import the operations interface from `operations.ts`
|
|
189
|
+
3. Create a mock client implementing the interface
|
|
190
|
+
4. Register the generated gateway plugin with your mock client
|
|
191
|
+
5. Use Fastify's `inject()` to test routes without a running server
|
|
192
|
+
|
|
193
|
+
The operations interface is the recommended seam for dependency injection and testing. It's a pure TypeScript interface with no runtime dependencies on the `soap` package.
|