@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/README.md CHANGED
@@ -62,6 +62,31 @@ curl -X POST http://localhost:3000/get-weather-information \
62
62
  | Fastify Gateway | plugin.ts, routes/, schemas/ | Production REST handlers |
63
63
  | Catalog | catalog.json | Compiled WSDL (debuggable, cacheable) |
64
64
 
65
+ ## Testing With Generated Code
66
+
67
+ The generated `operations.ts` provides a typed interface for mocking the SOAP client without importing the concrete class or the `soap` package:
68
+
69
+ ```typescript
70
+ import type { WeatherOperations } from "./generated/client/operations.js";
71
+
72
+ const mockClient: WeatherOperations = {
73
+ GetCityWeatherByZIP: async (args) => ({
74
+ response: { GetCityWeatherByZIPResult: { Success: true, City: "Test" } },
75
+ headers: {},
76
+ }),
77
+ // ... other operations
78
+ };
79
+
80
+ // Use with the gateway plugin
81
+ import Fastify from "fastify";
82
+ import { weatherGateway } from "./generated/gateway/plugin.js";
83
+
84
+ const app = Fastify();
85
+ await app.register(weatherGateway, { client: mockClient, prefix: "/v1/weather" });
86
+ ```
87
+
88
+ See [Testing Guide](docs/testing.md) for integration test patterns and mock examples.
89
+
65
90
  ## Commands
66
91
 
67
92
  | Command | Purpose |
@@ -85,6 +110,7 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
85
110
  | [Gateway Guide](docs/gateway-guide.md) | Fastify integration and error handling |
86
111
  | [Configuration](docs/configuration.md) | Security, tags, operations config files |
87
112
  | [Production Guide](docs/production.md) | CI/CD, validation, logging, limitations |
113
+ | [Testing Guide](docs/testing.md) | Testing patterns and mock client examples |
88
114
  | [Troubleshooting](docs/troubleshooting.md) | Common issues and debugging |
89
115
  | [Working With Generated Code](docs/generated-code.md) | Using clients and types |
90
116
  | [Architecture](docs/architecture.md) | Internal pipeline for contributors |
package/dist/cli.js CHANGED
@@ -19,6 +19,7 @@ import { generateTypes } from "./client/generateTypes.js";
19
19
  import { generateUtils } from "./client/generateUtils.js";
20
20
  import { generateCatalog } from "./compiler/generateCatalog.js";
21
21
  import { generateClient } from "./client/generateClient.js";
22
+ import { generateOperations } from "./client/generateOperations.js";
22
23
  import { generateOpenAPI } from "./openapi/generateOpenAPI.js";
23
24
  import { runGenerationPipeline } from "./pipeline.js";
24
25
  import { resolveCompilerOptions } from "./config.js";
@@ -183,7 +184,7 @@ if (rawArgs[0] === "client") {
183
184
  success(`Compiled catalog written to ${catalogOutPath}`);
184
185
  }
185
186
  // Emit client artifacts (excluding catalog since we already emitted it above if needed)
186
- emitClientArtifacts(clientOutDir, compiled, generateClient, generateTypes, generateUtils);
187
+ emitClientArtifacts(clientOutDir, compiled, generateClient, generateTypes, generateUtils, generateOperations);
187
188
  process.exit(0);
188
189
  }
189
190
  /**
@@ -237,6 +238,11 @@ if (rawArgs[0] === "openapi") {
237
238
  type: "boolean",
238
239
  default: false,
239
240
  desc: "Emit additionalProperties:false for object schemas"
241
+ })
242
+ .option("openapi-flatten-array-wrappers", {
243
+ type: "boolean",
244
+ default: true,
245
+ desc: "Flatten ArrayOf* wrapper types to plain arrays in schemas (default: true)"
240
246
  })
241
247
  .option("openapi-prune-unused-schemas", {
242
248
  type: "boolean",
@@ -357,6 +363,11 @@ if (rawArgs[0] === "gateway") {
357
363
  type: "boolean",
358
364
  default: false,
359
365
  desc: "Skip generating runtime.ts utilities"
366
+ })
367
+ .option("openapi-flatten-array-wrappers", {
368
+ type: "boolean",
369
+ default: true,
370
+ desc: "Generate runtime unwrap for ArrayOf* wrapper types (default: true)"
360
371
  })
361
372
  .strict()
362
373
  .help()
@@ -388,6 +399,7 @@ if (rawArgs[0] === "gateway") {
388
399
  // Only override defaults if explicitly skipping (otherwise let generateGateway decide based on stubHandlers)
389
400
  emitPlugin: gatewayArgv["gateway-skip-plugin"] ? false : undefined,
390
401
  emitRuntime: gatewayArgv["gateway-skip-runtime"] ? false : undefined,
402
+ flattenArrayWrappers: gatewayArgv["openapi-flatten-array-wrappers"],
391
403
  });
392
404
  success(`Gateway code generated in ${outDir}`);
393
405
  process.exit(0);
@@ -569,6 +581,11 @@ if (rawArgs[0] === "pipeline") {
569
581
  .option("openapi-tags-file", { type: "string" })
570
582
  .option("openapi-ops-file", { type: "string" })
571
583
  .option("openapi-closed-schemas", { type: "boolean", default: false })
584
+ .option("openapi-flatten-array-wrappers", {
585
+ type: "boolean",
586
+ default: true,
587
+ desc: "Flatten ArrayOf* wrapper types to plain arrays in schemas (default: true)"
588
+ })
572
589
  .option("openapi-prune-unused-schemas", { type: "boolean", default: false })
573
590
  .option("openapi-envelope-namespace", { type: "string" })
574
591
  .option("openapi-error-namespace", { type: "string" })
@@ -0,0 +1,13 @@
1
+ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
2
+ /**
3
+ * Generates an operations.ts file with a fully-typed interface for all SOAP operations
4
+ *
5
+ * The interface mirrors the async method signatures of the generated client class
6
+ * but uses concrete input/output types from types.ts instead of generic parameters.
7
+ * This enables type-safe mocking without SOAP runtime dependencies.
8
+ *
9
+ * @param outFile - Path to the output TypeScript file
10
+ * @param compiled - The compiled WSDL catalog
11
+ */
12
+ export declare function generateOperations(outFile: string, compiled: CompiledCatalog): void;
13
+ //# sourceMappingURL=generateOperations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateOperations.d.ts","sourceRoot":"","sources":["../../src/client/generateOperations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIrE;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CA0DnF"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Operations Interface Generator
3
+ *
4
+ * Generates a fully-typed operations interface for the SOAP client.
5
+ * This standalone interface enables mocking and testing without importing
6
+ * the concrete SOAP client class or its runtime dependencies.
7
+ */
8
+ import fs from "node:fs";
9
+ import { deriveClientName, pascal } from "../util/tools.js";
10
+ import { error } from "../util/cli.js";
11
+ /**
12
+ * Generates an operations.ts file with a fully-typed interface for all SOAP operations
13
+ *
14
+ * The interface mirrors the async method signatures of the generated client class
15
+ * but uses concrete input/output types from types.ts instead of generic parameters.
16
+ * This enables type-safe mocking without SOAP runtime dependencies.
17
+ *
18
+ * @param outFile - Path to the output TypeScript file
19
+ * @param compiled - The compiled WSDL catalog
20
+ */
21
+ export function generateOperations(outFile, compiled) {
22
+ const ext = compiled.options.imports ?? "bare";
23
+ const suffix = ext === "bare" ? "" : `.${ext}`;
24
+ const clientName = deriveClientName(compiled);
25
+ // Collect type names used in method signatures for the import statement
26
+ const importedTypes = new Set();
27
+ const methods = [];
28
+ for (const op of compiled.operations) {
29
+ const inTypeName = op.inputElement ? pascal(op.inputElement.local) : undefined;
30
+ const outTypeName = op.outputElement ? pascal(op.outputElement.local) : undefined;
31
+ if (!inTypeName && !outTypeName) {
32
+ continue;
33
+ }
34
+ const inTs = inTypeName ?? "Record<string, unknown>";
35
+ const outTs = outTypeName ?? "unknown";
36
+ if (inTypeName)
37
+ importedTypes.add(inTypeName);
38
+ if (outTypeName)
39
+ importedTypes.add(outTypeName);
40
+ methods.push(` ${op.name}(\n` +
41
+ ` args: ${inTs}\n` +
42
+ ` ): Promise<{ response: ${outTs}; headers: unknown }>;\n`);
43
+ }
44
+ // Build sorted import list for deterministic output
45
+ const sortedImports = Array.from(importedTypes).sort();
46
+ const typeImport = sortedImports.length > 0
47
+ ? `import type {\n${sortedImports.map((t) => ` ${t},`).join("\n")}\n} from "./types${suffix}";\n\n`
48
+ : "";
49
+ const content = `/**
50
+ * Typed operations interface for the ${clientName} service.
51
+ *
52
+ * Implement this interface to create mock clients or alternative
53
+ * transport layers without depending on the SOAP runtime.
54
+ *
55
+ * Auto-generated - do not edit manually.
56
+ */
57
+ ${typeImport}/**
58
+ * All operations exposed by the ${clientName} SOAP service.
59
+ *
60
+ * The concrete ${clientName} class satisfies this interface.
61
+ * Use this type for dependency injection, mocking, or testing.
62
+ */
63
+ export interface ${clientName}Operations {
64
+ ${methods.join("\n")}}\n`;
65
+ try {
66
+ fs.writeFileSync(outFile, content, "utf8");
67
+ }
68
+ catch (e) {
69
+ error(`Failed to write operations interface to ${outFile}`);
70
+ }
71
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"schemaCompiler.d.ts","sourceRoot":"","sources":["../../src/compiler/schemaCompiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yBAAyB,CAAC;AAIzD;;;;;;GAMG;AACH,MAAM,MAAM,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;QAC9B,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;QAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;QAC9B,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;QAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACJ,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;KAC/C,CAAC;IACF,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,KAAK,CAAC;QACrB,aAAa,CAAC,EAAE,KAAK,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAiGF;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,GAAG,eAAe,CAylB1F"}
1
+ {"version":3,"file":"schemaCompiler.d.ts","sourceRoot":"","sources":["../../src/compiler/schemaCompiler.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { getChildrenWithLocalName, getFirstWithLocalName, normalizeArray, pascal, resolveQName, } from "../util/tools.js";
2
2
  import { xsdToTsPrimitive } from "../xsd/primitives.js";
3
+ import { WsdlCompilationError } from "../util/errors.js";
3
4
  // XML Schema namespace constant
4
5
  const XS = "http://www.w3.org/2001/XMLSchema";
5
6
  /**
@@ -194,7 +195,14 @@ export function compileCatalog(cat, options) {
194
195
  const t = getOrCompileComplex(q.local, crec.node, crec.tns, crec.prefixes);
195
196
  return { tsType: t.name, declared: `{${t.ns}}${q.local}` };
196
197
  }
197
- // fallback
198
+ // Unresolved type reference
199
+ if (options.failOnUnresolved) {
200
+ throw new WsdlCompilationError(`Unresolved type reference: "${q.local}" in namespace "${q.ns}".`, {
201
+ element: q.local,
202
+ namespace: q.ns,
203
+ suggestion: "Check that the XSD import for this namespace is included in the WSDL, or use --no-fail-on-unresolved to emit 'any'.",
204
+ });
205
+ }
198
206
  return { tsType: "any", declared: `{${q.ns}}${q.local}` };
199
207
  }
200
208
  function findComplexRec(q) {
@@ -565,6 +573,12 @@ export function compileCatalog(cat, options) {
565
573
  const tns = defs?.["@_targetNamespace"] || "";
566
574
  const bindingDefs = normalizeArray(defs?.["wsdl:binding"] || defs?.["binding"]);
567
575
  const soapBinding = bindingDefs.find(b => Object.keys(b).some(k => k === "soap:binding" || k === "soap12:binding")) || bindingDefs[0];
576
+ if (!soapBinding) {
577
+ throw new WsdlCompilationError("No SOAP binding found in the WSDL document.", {
578
+ file: cat.wsdlUri,
579
+ suggestion: "Ensure the WSDL defines a <wsdl:binding> element with a <soap:binding> or <soap12:binding> child.",
580
+ });
581
+ }
568
582
  // binding @type typically looks like "tns:MyPortType", so resolve via prefixes map
569
583
  const portTypeAttr = soapBinding?.["@_type"];
570
584
  const portTypeQName = portTypeAttr ? resolveQName(portTypeAttr, tns, cat.prefixMap) : undefined;
@@ -32,6 +32,7 @@ export interface GenerateGatewayOptions {
32
32
  emitPlugin?: boolean;
33
33
  emitRuntime?: boolean;
34
34
  stubHandlers?: boolean;
35
+ flattenArrayWrappers?: boolean;
35
36
  }
36
37
  /**
37
38
  * Generates Fastify gateway code from an OpenAPI 3.1 specification
@@ -1 +1 @@
1
- {"version":3,"file":"generateGateway.d.ts","sourceRoot":"","sources":["../../src/gateway/generateGateway.ts"],"names":[],"mappings":"AA6CA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAE/B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsJjF"}
1
+ {"version":3,"file":"generateGateway.d.ts","sourceRoot":"","sources":["../../src/gateway/generateGateway.ts"],"names":[],"mappings":"AA6CA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAE/B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyJjF"}
@@ -165,6 +165,8 @@ export async function generateGateway(opts) {
165
165
  });
166
166
  // Step 4: Emit schemas.ts module
167
167
  emitSchemasModule(outDir, modelsDir, versionSlug, serviceSlug);
168
+ // Determine whether to generate unwrap code for ArrayOf* wrappers
169
+ const shouldUnwrap = opts.flattenArrayWrappers !== false && catalog;
168
170
  // Step 5: Emit route files (with handlers or stubs)
169
171
  // Note: Route URLs come from OpenAPI paths which already include any base path
170
172
  if (stubHandlers) {
@@ -173,11 +175,11 @@ export async function generateGateway(opts) {
173
175
  }
174
176
  else {
175
177
  // Full handler mode: emit working implementations
176
- emitRouteFilesWithHandlers(outDir, routesDir, versionSlug, serviceSlug, operations, importsMode, clientMeta);
178
+ emitRouteFilesWithHandlers(outDir, routesDir, versionSlug, serviceSlug, operations, importsMode, clientMeta, shouldUnwrap ? catalog : undefined);
177
179
  }
178
180
  // Step 6: Emit runtime.ts (if enabled)
179
181
  if (emitRuntime) {
180
- emitRuntimeModule(outDir, versionSlug, serviceSlug);
182
+ emitRuntimeModule(outDir, versionSlug, serviceSlug, shouldUnwrap ? catalog : undefined);
181
183
  }
182
184
  // Step 7: Emit plugin.ts and type-check fixture (if enabled)
183
185
  if (emitPlugin) {
@@ -103,20 +103,7 @@ export declare function emitSchemasModule(outDir: string, modelsDir: string, ver
103
103
  * @param {"js"|"ts"|"bare"} importsMode - Import-extension mode for generated TypeScript modules
104
104
  */
105
105
  export declare function emitRouteFiles(outDir: string, routesDir: string, versionSlug: string, serviceSlug: string, operations: OperationMetadata[], importsMode: "js" | "ts" | "bare"): void;
106
- /**
107
- * Emits runtime.ts module with envelope builders and error handling utilities
108
- *
109
- * Generated code includes:
110
- * - Response envelope types (SuccessEnvelope, ErrorEnvelope)
111
- * - buildSuccessEnvelope() and buildErrorEnvelope() functions
112
- * - classifyError() for mapping errors to HTTP status codes
113
- * - createGatewayErrorHandler_{version}_{service}() factory function
114
- *
115
- * @param {string} outDir - Root output directory
116
- * @param {string} versionSlug - Version slug for function naming
117
- * @param {string} serviceSlug - Service slug for function naming
118
- */
119
- export declare function emitRuntimeModule(outDir: string, versionSlug: string, serviceSlug: string): void;
106
+ export declare function emitRuntimeModule(outDir: string, versionSlug: string, serviceSlug: string, catalog?: any): void;
120
107
  /**
121
108
  * Emits plugin.ts module as the primary Fastify plugin wrapper
122
109
  *
@@ -165,5 +152,5 @@ export declare function emitTypeCheckFixture(outDir: string, clientMeta: ClientM
165
152
  * @param {"js"|"ts"|"bare"} importsMode - Import-extension mode
166
153
  * @param {ClientMeta} clientMeta - Client class metadata
167
154
  */
168
- export declare function emitRouteFilesWithHandlers(outDir: string, routesDir: string, versionSlug: string, serviceSlug: string, operations: OperationMetadata[], importsMode: "js" | "ts" | "bare", clientMeta: ClientMeta): void;
155
+ export declare function emitRouteFilesWithHandlers(outDir: string, routesDir: string, versionSlug: string, serviceSlug: string, operations: OperationMetadata[], importsMode: "js" | "ts" | "bare", clientMeta: ClientMeta, catalog?: any): void;
169
156
  //# sourceMappingURL=generators.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/gateway/generators.ts"],"names":[],"mappings":"AAcA,OAAO,EAAC,KAAK,UAAU,EAAE,KAAK,eAAe,EAA6C,MAAM,cAAc,CAAC;AAE/G;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6BxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,0BAA0B,EAAE,MAAM,EAAE,EACpC,iBAAiB,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,GAAG,EACvH,UAAU,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,MAAM,EACnC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACnC,iBAAiB,EAAE,CA2HrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAsBN;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,IAAI,CA2CN;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAkLN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,iBAAiB,EAAE,GAC9B,IAAI,CAqGN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,IAAI,CAqBN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,UAAU,GACrB,IAAI,CAuFN"}
1
+ {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/gateway/generators.ts"],"names":[],"mappings":"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"}
@@ -288,9 +288,88 @@ export function emitRouteFiles(outDir, routesDir, versionSlug, serviceSlug, oper
288
288
  * @param {string} versionSlug - Version slug for function naming
289
289
  * @param {string} serviceSlug - Service slug for function naming
290
290
  */
291
- export function emitRuntimeModule(outDir, versionSlug, serviceSlug) {
291
+ /**
292
+ * Detects ArrayOf* wrapper types from catalog type metadata.
293
+ *
294
+ * An ArrayOf* wrapper has exactly one element with max "unbounded" (or > 1)
295
+ * and no attributes — mirroring the logic in generateSchemas.ts isArrayWrapper().
296
+ *
297
+ * @param catalog - Compiled catalog object
298
+ * @returns Record mapping wrapper type name to inner element property name
299
+ */
300
+ function detectArrayWrappers(catalog) {
301
+ const wrappers = {};
302
+ for (const t of catalog.types || []) {
303
+ if (t.attrs && t.attrs.length !== 0)
304
+ continue;
305
+ if (!t.elems || t.elems.length !== 1)
306
+ continue;
307
+ const e = t.elems[0];
308
+ if (e.max !== "unbounded" && !(e.max > 1))
309
+ continue;
310
+ wrappers[t.name] = e.name;
311
+ }
312
+ return wrappers;
313
+ }
314
+ export function emitRuntimeModule(outDir, versionSlug, serviceSlug, catalog) {
292
315
  const vSlug = slugName(versionSlug);
293
316
  const sSlug = slugName(serviceSlug);
317
+ // Build unwrap maps from catalog if provided
318
+ let unwrapSection = "";
319
+ if (catalog) {
320
+ const arrayWrappers = detectArrayWrappers(catalog);
321
+ const childTypes = catalog.meta?.childType || {};
322
+ // Only emit if there are actual wrapper types to unwrap
323
+ if (Object.keys(arrayWrappers).length > 0) {
324
+ unwrapSection = `
325
+ /**
326
+ * ArrayOf* wrapper type → inner element property name.
327
+ * These types are flattened to plain arrays in the OpenAPI schema,
328
+ * so the runtime must unwrap { InnerElement: [...] } → [...].
329
+ */
330
+ const ARRAY_WRAPPERS: Record<string, string> = ${JSON.stringify(arrayWrappers, null, 2)};
331
+
332
+ /**
333
+ * Type name → { propertyName: propertyTypeName } for recursive unwrapping.
334
+ */
335
+ const CHILDREN_TYPES: Record<string, Record<string, string>> = ${JSON.stringify(childTypes, null, 2)};
336
+
337
+ /**
338
+ * Recursively unwraps ArrayOf* wrapper objects in a SOAP response so the
339
+ * data matches the flattened OpenAPI array schemas.
340
+ *
341
+ * Safe to call on any response — returns data unchanged when the type
342
+ * has no wrapper fields.
343
+ *
344
+ * @param data - SOAP response data (potentially with wrapper objects)
345
+ * @param typeName - The type name for the current data level
346
+ * @returns Unwrapped data matching the OpenAPI schema shape
347
+ */
348
+ export function unwrapArrayWrappers(data: unknown, typeName: string): unknown {
349
+ if (data == null || typeof data !== "object") return data;
350
+
351
+ // If this type is itself a wrapper, unwrap it
352
+ if (typeName in ARRAY_WRAPPERS) {
353
+ const innerKey = ARRAY_WRAPPERS[typeName];
354
+ return (data as Record<string, unknown>)[innerKey] ?? [];
355
+ }
356
+
357
+ // Recurse into children whose types may contain wrappers
358
+ if (typeName in CHILDREN_TYPES) {
359
+ const children = CHILDREN_TYPES[typeName];
360
+ for (const [propName, propType] of Object.entries(children)) {
361
+ const val = (data as Record<string, unknown>)[propName];
362
+ if (val !== undefined) {
363
+ (data as Record<string, unknown>)[propName] = unwrapArrayWrappers(val, propType);
364
+ }
365
+ }
366
+ }
367
+
368
+ return data;
369
+ }
370
+ `;
371
+ }
372
+ }
294
373
  const runtimeTs = `/**
295
374
  * Gateway Runtime Utilities
296
375
  *
@@ -388,7 +467,7 @@ export function classifyError(err: unknown): ClassifiedError {
388
467
  httpStatus: 400,
389
468
  code: "VALIDATION_ERROR",
390
469
  message: "Request validation failed",
391
- details: (err as Record<string, unknown>).validation,
470
+ details: { validationErrors: (err as Record<string, unknown>).validation },
392
471
  };
393
472
  }
394
473
 
@@ -415,7 +494,7 @@ export function classifyError(err: unknown): ClassifiedError {
415
494
  httpStatus: 503,
416
495
  code: "SERVICE_UNAVAILABLE",
417
496
  message: "Unable to connect to SOAP service",
418
- details: err.message,
497
+ details: { message: err.message },
419
498
  };
420
499
  }
421
500
  if (err.message.includes("ETIMEDOUT") || err.message.includes("timeout")) {
@@ -423,7 +502,7 @@ export function classifyError(err: unknown): ClassifiedError {
423
502
  httpStatus: 504,
424
503
  code: "GATEWAY_TIMEOUT",
425
504
  message: "SOAP service request timed out",
426
- details: err.message,
505
+ details: { message: err.message },
427
506
  };
428
507
  }
429
508
  }
@@ -463,7 +542,7 @@ export function createGatewayErrorHandler_${vSlug}_${sSlug}() {
463
542
  };
464
543
  }
465
544
  `;
466
- fs.writeFileSync(path.join(outDir, "runtime.ts"), runtimeTs, "utf8");
545
+ fs.writeFileSync(path.join(outDir, "runtime.ts"), runtimeTs + unwrapSection, "utf8");
467
546
  }
468
547
  /**
469
548
  * Emits plugin.ts module as the primary Fastify plugin wrapper
@@ -485,11 +564,6 @@ export function emitPluginModule(outDir, versionSlug, serviceSlug, clientMeta, i
485
564
  const vSlug = slugName(versionSlug);
486
565
  const sSlug = slugName(serviceSlug);
487
566
  const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
488
- // Build named operations interface methods from operation metadata
489
- const operationMethods = operations
490
- .filter(op => op.clientMethodName)
491
- .map(op => ` ${op.clientMethodName}(args: unknown): Promise<{ response: unknown; headers: unknown }>;`)
492
- .join("\n");
493
567
  const pluginTs = `/**
494
568
  * ${clientMeta.className} Gateway Plugin
495
569
  *
@@ -499,10 +573,14 @@ export function emitPluginModule(outDir, versionSlug, serviceSlug, clientMeta, i
499
573
  import fp from "fastify-plugin";
500
574
  import type { FastifyInstance, FastifyPluginOptions } from "fastify";
501
575
  import type { ${clientMeta.className} } from "${clientMeta.pluginImportPath}";
576
+ import type { ${clientMeta.className}Operations } from "${clientMeta.operationsImportPath}";
502
577
  import { registerSchemas_${vSlug}_${sSlug} } from "./schemas${suffix}";
503
578
  import { registerRoutes_${vSlug}_${sSlug} } from "./routes${suffix}";
504
579
  import { createGatewayErrorHandler_${vSlug}_${sSlug} } from "./runtime${suffix}";
505
580
 
581
+ // Re-export the operations interface for convenience
582
+ export type { ${clientMeta.className}Operations };
583
+
506
584
  /**
507
585
  * Options for the ${clientMeta.className} gateway plugin
508
586
  */
@@ -510,8 +588,9 @@ export interface ${clientMeta.className}GatewayOptions extends FastifyPluginOpti
510
588
  /**
511
589
  * SOAP client instance (pre-configured).
512
590
  * The client should be instantiated with appropriate source and security settings.
591
+ * Accepts the concrete client class or any implementation of ${clientMeta.className}Operations.
513
592
  */
514
- client: ${clientMeta.className};
593
+ client: ${clientMeta.className}Operations;
515
594
  /**
516
595
  * Optional additional route prefix applied at runtime.
517
596
  * Note: If you used --openapi-base-path during generation, routes already have that prefix baked in.
@@ -520,21 +599,9 @@ export interface ${clientMeta.className}GatewayOptions extends FastifyPluginOpti
520
599
  prefix?: string;
521
600
  }
522
601
 
523
- /**
524
- * Convenience type listing all SOAP operations with \`args: unknown\` signatures.
525
- *
526
- * This interface is NOT used for the decorator type (which uses the concrete
527
- * client class for full type safety). It is exported as a lightweight alternative
528
- * for mocking or testing scenarios where importing the full client class is
529
- * undesirable.
530
- */
531
- export interface ${clientMeta.className}Operations {
532
- ${operationMethods}
533
- }
534
-
535
602
  declare module "fastify" {
536
603
  interface FastifyInstance {
537
- ${clientMeta.decoratorName}: ${clientMeta.className};
604
+ ${clientMeta.decoratorName}: ${clientMeta.className}Operations;
538
605
  }
539
606
  }
540
607
 
@@ -598,11 +665,19 @@ export function emitTypeCheckFixture(outDir, clientMeta, importsMode) {
598
665
  * Auto-generated. Not intended for runtime use.
599
666
  */
600
667
  import type { ${clientMeta.className} } from "${clientMeta.pluginImportPath}";
668
+ import type { ${clientMeta.className}Operations } from "${clientMeta.operationsImportPath}";
601
669
  import type { ${clientMeta.className}GatewayOptions } from "./plugin${suffix}";
602
670
 
603
- // This function verifies structural compatibility at the type level.
604
- // If the plugin options interface diverges from the client class, this
605
- // will produce a compile error with a clear message.
671
+ // Verify the concrete client class satisfies the operations interface.
672
+ // If the client class diverges from the operations interface, this
673
+ // will produce a compile error.
674
+ function _assertClientSatisfiesOps(client: ${clientMeta.className}): ${clientMeta.className}Operations {
675
+ return client;
676
+ }
677
+ void _assertClientSatisfiesOps;
678
+
679
+ // Verify the concrete client class is accepted by plugin options.
680
+ // This ensures the gateway plugin can be used with the generated client.
606
681
  function _assertClientCompatible(client: ${clientMeta.className}): void {
607
682
  const _opts: ${clientMeta.className}GatewayOptions = { client };
608
683
  void _opts;
@@ -630,13 +705,15 @@ void _assertClientCompatible;
630
705
  * @param {"js"|"ts"|"bare"} importsMode - Import-extension mode
631
706
  * @param {ClientMeta} clientMeta - Client class metadata
632
707
  */
633
- export function emitRouteFilesWithHandlers(outDir, routesDir, versionSlug, serviceSlug, operations, importsMode, clientMeta) {
708
+ export function emitRouteFilesWithHandlers(outDir, routesDir, versionSlug, serviceSlug, operations, importsMode, clientMeta, catalog) {
634
709
  fs.mkdirSync(routesDir, { recursive: true });
635
710
  // Sort operations for deterministic output
636
711
  operations.sort((a, b) => a.operationSlug.localeCompare(b.operationSlug));
637
712
  const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
638
713
  const vSlug = slugName(versionSlug);
639
714
  const sSlug = slugName(serviceSlug);
715
+ // Detect if unwrap code should be emitted
716
+ const hasUnwrap = catalog ? Object.keys(detectArrayWrappers(catalog)).length > 0 : false;
640
717
  let routesTs = `import type { FastifyInstance } from "fastify";\n`;
641
718
  operations.forEach((op) => {
642
719
  const fnName = `registerRoute_${vSlug}_${sSlug}_${op.operationSlug.replace(/[^a-zA-Z0-9_]/g, "_")}`;
@@ -661,6 +738,13 @@ export function emitRouteFilesWithHandlers(outDir, routesDir, versionSlug, servi
661
738
  const bodyArg = hasRequestType
662
739
  ? `request.body as ${reqTypeName}`
663
740
  : "request.body";
741
+ // Build the runtime import and return expression based on unwrap availability
742
+ const runtimeImport = hasUnwrap
743
+ ? `import { buildSuccessEnvelope, unwrapArrayWrappers } from "../runtime${suffix}";`
744
+ : `import { buildSuccessEnvelope } from "../runtime${suffix}";`;
745
+ const returnExpr = hasUnwrap
746
+ ? `return buildSuccessEnvelope(unwrapArrayWrappers(result.response, "${resTypeName}"));`
747
+ : `return buildSuccessEnvelope(result.response);`;
664
748
  // Note: op.path comes from OpenAPI and already includes any base path
665
749
  let routeTs = `/**
666
750
  * Route: ${op.method.toUpperCase()} ${op.path}
@@ -671,7 +755,7 @@ export function emitRouteFilesWithHandlers(outDir, routesDir, versionSlug, servi
671
755
  */
672
756
  import type { FastifyInstance } from "fastify";
673
757
  ${typeImport}import schema from "../schemas/operations/${op.operationSlug}.json" with { type: "json" };
674
- import { buildSuccessEnvelope } from "../runtime${suffix}";
758
+ ${runtimeImport}
675
759
 
676
760
  export async function ${fnName}(fastify: FastifyInstance) {
677
761
  fastify.route${routeGeneric}({
@@ -681,7 +765,7 @@ export async function ${fnName}(fastify: FastifyInstance) {
681
765
  handler: async (request) => {
682
766
  const client = fastify.${clientMeta.decoratorName};
683
767
  const result = await client.${clientMethod}(${bodyArg});
684
- return buildSuccessEnvelope(result.response);
768
+ ${returnExpr}
685
769
  },
686
770
  });
687
771
  }
@@ -141,9 +141,10 @@ export declare function buildParamSchemasForOperation(pathItem: any, operation:
141
141
  * @interface ClientMeta
142
142
  * @property {string} className - Client class name (e.g., "EVRNService")
143
143
  * @property {string} decoratorName - Fastify decorator name (e.g., "evrnserviceClient")
144
- * @property {string} importPath - Import path relative to routes/ directory — for future typed route handlers
145
- * @property {string} typesImportPath - Types import path relative to routes/ directory — for future typed route handlers
144
+ * @property {string} importPath - Import path relative to routes/ directory — for typed route handlers
145
+ * @property {string} typesImportPath - Types import path relative to routes/ directory — for typed route handlers
146
146
  * @property {string} pluginImportPath - Import path relative to gateway output directory — used by emitPluginModule()
147
+ * @property {string} operationsImportPath - Operations interface import path relative to gateway output directory
147
148
  */
148
149
  export interface ClientMeta {
149
150
  className: string;
@@ -151,6 +152,7 @@ export interface ClientMeta {
151
152
  importPath: string;
152
153
  typesImportPath: string;
153
154
  pluginImportPath: string;
155
+ operationsImportPath: string;
154
156
  }
155
157
  /**
156
158
  * Extended operation metadata for full handler generation
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/gateway/helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/gateway/helpers.ts"],"names":[],"mappings":"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"}
@@ -390,12 +390,16 @@ export function resolveClientMeta(opts, catalog) {
390
390
  const pluginImportPath = opts.clientDir
391
391
  ? `../client/client${suffix}`
392
392
  : `./client/client${suffix}`;
393
+ const operationsImportPath = opts.clientDir
394
+ ? `../client/operations${suffix}`
395
+ : `./client/operations${suffix}`;
393
396
  return {
394
397
  className,
395
398
  decoratorName,
396
399
  importPath,
397
400
  typesImportPath,
398
401
  pluginImportPath,
402
+ operationsImportPath,
399
403
  };
400
404
  }
401
405
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAUjD,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,qBAAqB,EAAC,MAAM,eAAe,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,GACjE,OAAO,CAAC,IAAI,CAAC,CAsDf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAWjD,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,eAAe,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAC,qBAAqB,EAAC,MAAM,eAAe,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,GACjE,OAAO,CAAC,IAAI,CAAC,CAwDf"}