@techspokes/typescript-wsdl-client 0.10.4 → 0.11.1

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 (42) hide show
  1. package/README.md +26 -0
  2. package/dist/cli.js +18 -1
  3. package/dist/client/generateOperations.d.ts +13 -0
  4. package/dist/client/generateOperations.d.ts.map +1 -0
  5. package/dist/client/generateOperations.js +71 -0
  6. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  7. package/dist/compiler/schemaCompiler.js +15 -1
  8. package/dist/gateway/generateGateway.d.ts +1 -0
  9. package/dist/gateway/generateGateway.d.ts.map +1 -1
  10. package/dist/gateway/generateGateway.js +4 -2
  11. package/dist/gateway/generators.d.ts +2 -15
  12. package/dist/gateway/generators.d.ts.map +1 -1
  13. package/dist/gateway/generators.js +114 -30
  14. package/dist/gateway/helpers.d.ts +4 -2
  15. package/dist/gateway/helpers.d.ts.map +1 -1
  16. package/dist/gateway/helpers.js +4 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -0
  19. package/dist/loader/wsdlLoader.d.ts.map +1 -1
  20. package/dist/loader/wsdlLoader.js +30 -4
  21. package/dist/openapi/generateOpenAPI.d.ts +1 -0
  22. package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
  23. package/dist/openapi/generateOpenAPI.js +1 -0
  24. package/dist/openapi/generateSchemas.d.ts +1 -0
  25. package/dist/openapi/generateSchemas.d.ts.map +1 -1
  26. package/dist/openapi/generateSchemas.js +4 -3
  27. package/dist/pipeline.d.ts.map +1 -1
  28. package/dist/pipeline.js +6 -1
  29. package/dist/util/builder.d.ts.map +1 -1
  30. package/dist/util/builder.js +1 -0
  31. package/dist/util/cli.d.ts +3 -2
  32. package/dist/util/cli.d.ts.map +1 -1
  33. package/dist/util/cli.js +14 -4
  34. package/dist/util/errors.d.ts +37 -0
  35. package/dist/util/errors.d.ts.map +1 -0
  36. package/dist/util/errors.js +37 -0
  37. package/docs/README.md +1 -0
  38. package/docs/cli-reference.md +2 -0
  39. package/docs/concepts.md +29 -2
  40. package/docs/generated-code.md +24 -0
  41. package/docs/testing.md +193 -0
  42. package/package.json +9 -4
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import { generateTypes } from "./client/generateTypes.js";
20
20
  import { generateUtils } from "./client/generateUtils.js";
21
21
  import { generateCatalog } from "./compiler/generateCatalog.js";
22
22
  import { generateClient } from "./client/generateClient.js";
23
+ import { generateOperations } from "./client/generateOperations.js";
23
24
  import { info } from "./util/cli.js";
24
25
  export { generateOpenAPI } from "./openapi/generateOpenAPI.js";
25
26
  export { generateGateway } from "./gateway/generateGateway.js";
@@ -76,6 +77,7 @@ export async function compileWsdlToProject(input) {
76
77
  // Emit artifacts
77
78
  const typesFile = path.join(input.outDir, "types.ts");
78
79
  const utilsFile = path.join(input.outDir, "utils.ts");
80
+ const operationsFile = path.join(input.outDir, "operations.ts");
79
81
  const catalogFile = path.join(input.outDir, "catalog.json");
80
82
  const clientFile = path.join(input.outDir, "client.ts");
81
83
  // Prepare output dir
@@ -89,6 +91,7 @@ export async function compileWsdlToProject(input) {
89
91
  generateClient(clientFile, compiled);
90
92
  generateTypes(typesFile, compiled);
91
93
  generateUtils(utilsFile, compiled);
94
+ generateOperations(operationsFile, compiled);
92
95
  if (compiled.options.catalog) {
93
96
  generateCatalog(catalogFile, compiled);
94
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wsdlLoader.d.ts","sourceRoot":"","sources":["../../src/loader/wsdlLoader.ts"],"names":[],"mappings":"AAiBA;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AAKF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAoC1E"}
1
+ {"version":3,"file":"wsdlLoader.d.ts","sourceRoot":"","sources":["../../src/loader/wsdlLoader.ts"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AAKF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAsE1E"}
@@ -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
- const { uri: wsdlUri, text } = await fetchText(wsdlUrlOrPath);
36
- const wsdlXml = parser.parse(text);
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 Error("Not a WSDL 1.1 file: missing wsdl:definitions");
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;CACzB;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,CAyRD"}
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;CAC9B;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,CA8C1G"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAgBA,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,CAuHhH"}
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,CAoBtF"}
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"}
@@ -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
  }
@@ -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
  *
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/util/cli.ts"],"names":[],"mappings":"AAWA;;;;;;;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,CAI7E;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;;;;;;;;;;;GAWG;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,GACnD,IAAI,CASN;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,IAAI,CAUN"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/util/cli.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAc1E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAMhE;AAED;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,GAAE,MAAU,GAAG,KAAK,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
- const message = errorObj instanceof Error ? errorObj.message : String(errorObj);
98
- error(message);
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
@@ -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
- The resulting OpenAPI schema:
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.
@@ -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.