typed-soap 0.0.1 → 0.0.2

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Custom deserializer that fills gaps in the `soap` package's built-in
3
+ * XML-to-JS type conversion. Soap natively handles int, integer, short,
4
+ * long, float, double, decimal, boolean, dateTime, and date. Everything
5
+ * else falls through as a raw string. This map adds the missing numeric
6
+ * types so our generated TypeScript types match the actual runtime values.
7
+ */
8
+ export declare const typedSoapDeserializer: Record<string, (text: string) => number>;
9
+ //# sourceMappingURL=deserializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deserializer.d.ts","sourceRoot":"","sources":["../src/deserializer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAU1E,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Custom deserializer that fills gaps in the `soap` package's built-in
3
+ * XML-to-JS type conversion. Soap natively handles int, integer, short,
4
+ * long, float, double, decimal, boolean, dateTime, and date. Everything
5
+ * else falls through as a raw string. This map adds the missing numeric
6
+ * types so our generated TypeScript types match the actual runtime values.
7
+ */
8
+ export const typedSoapDeserializer = {
9
+ byte: (text) => parseInt(text, 10),
10
+ unsignedByte: (text) => parseInt(text, 10),
11
+ unsignedShort: (text) => parseInt(text, 10),
12
+ unsignedInt: (text) => parseInt(text, 10),
13
+ unsignedLong: (text) => parseInt(text, 10),
14
+ negativeInteger: (text) => parseInt(text, 10),
15
+ nonNegativeInteger: (text) => parseInt(text, 10),
16
+ positiveInteger: (text) => parseInt(text, 10),
17
+ nonPositiveInteger: (text) => parseInt(text, 10),
18
+ };
19
+ //# sourceMappingURL=deserializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deserializer.js","sourceRoot":"","sources":["../src/deserializer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA6C;IAC7E,IAAI,EAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,YAAY,EAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,aAAa,EAAO,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,WAAW,EAAS,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,YAAY,EAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,eAAe,EAAK,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,eAAe,EAAK,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IAChD,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;CACjD,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,20 @@
1
- export declare function createSoapWrapper(): void;
1
+ import type { ServiceDefinition, InferClient, SoapClientOptions } from "./types.js";
2
+ export type { ServiceDefinition, OperationDefinition, InferClient, SoapClientOptions } from "./types.js";
3
+ /**
4
+ * Creates a type-safe SOAP client from a WSDL URL or file path.
5
+ *
6
+ * The generic parameter `T` should be a generated `ServiceDefinition`
7
+ * produced by `tsoap-cli`. The returned client has fully typed methods
8
+ * matching the WSDL's services, ports, and operations.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { createSoapClient } from 'typed-soap';
13
+ * import type { MyServiceDef } from './generated/my-service.js';
14
+ *
15
+ * const client = await createSoapClient<MyServiceDef>('http://example.com?wsdl');
16
+ * const result = await client.MyService.MyPort.MyOp({ arg: 'value' });
17
+ * ```
18
+ */
19
+ export declare function createSoapClient<T extends ServiceDefinition>(wsdlUrl: string, options?: SoapClientOptions): Promise<InferClient<T>>;
2
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,SAEhC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIpF,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEzG;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,iBAAiB,EAChE,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAgCzB"}
package/dist/index.js CHANGED
@@ -1,4 +1,45 @@
1
- export function createSoapWrapper() {
2
- // TODO: implement runtime SOAP client wrapper
1
+ import * as soap from "soap";
2
+ import { typedSoapDeserializer } from "./deserializer.js";
3
+ import { createClientProxy } from "./proxy.js";
4
+ /**
5
+ * Creates a type-safe SOAP client from a WSDL URL or file path.
6
+ *
7
+ * The generic parameter `T` should be a generated `ServiceDefinition`
8
+ * produced by `tsoap-cli`. The returned client has fully typed methods
9
+ * matching the WSDL's services, ports, and operations.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { createSoapClient } from 'typed-soap';
14
+ * import type { MyServiceDef } from './generated/my-service.js';
15
+ *
16
+ * const client = await createSoapClient<MyServiceDef>('http://example.com?wsdl');
17
+ * const result = await client.MyService.MyPort.MyOp({ arg: 'value' });
18
+ * ```
19
+ */
20
+ export async function createSoapClient(wsdlUrl, options) {
21
+ if (!wsdlUrl) {
22
+ throw new Error("createSoapClient: wsdlUrl is required");
23
+ }
24
+ const { endpoint, customDeserializer: userDeserializer, ...soapOptions } = options ?? {};
25
+ const mergedOptions = {
26
+ ...soapOptions,
27
+ customDeserializer: {
28
+ ...typedSoapDeserializer,
29
+ ...userDeserializer,
30
+ },
31
+ };
32
+ let client;
33
+ try {
34
+ client = await soap.createClientAsync(wsdlUrl, mergedOptions);
35
+ }
36
+ catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ throw new Error(`Failed to create SOAP client from "${wsdlUrl}": ${message}`);
39
+ }
40
+ if (endpoint) {
41
+ client.setEndpoint(endpoint);
42
+ }
43
+ return createClientProxy(client);
3
44
  }
4
45
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB;IAC/B,8CAA8C;AAChD,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAI/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,EACf,OAA2B;IAE3B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,GAAG,WAAW,EAAE,GACtE,OAAO,IAAI,EAAE,CAAC;IAEhB,MAAM,aAAa,GAAkB;QACnC,GAAG,WAAW;QACd,kBAAkB,EAAE;YAClB,GAAG,qBAAqB;YACxB,GAAG,gBAAgB;SACpB;KACF,CAAC;IAEF,IAAI,MAAmB,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GACX,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,sCAAsC,OAAO,MAAM,OAAO,EAAE,CAC7D,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,iBAAiB,CAAC,MAAM,CAAmB,CAAC;AACrD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Client } from "soap";
2
+ /**
3
+ * Creates a 3-level nested Proxy over a soap Client so that
4
+ * `proxy.ServiceName.PortName.OperationName(args)` delegates to
5
+ * `client.ServiceName.PortName.OperationNameAsync(args)` and
6
+ * returns only the result (first element of the response tuple).
7
+ */
8
+ export declare function createClientProxy(client: Client): unknown;
9
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAqBnC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAkFzD"}
package/dist/proxy.js ADDED
@@ -0,0 +1,73 @@
1
+ const JS_INTERNALS = new Set([
2
+ "then",
3
+ "toJSON",
4
+ "valueOf",
5
+ "toString",
6
+ "inspect",
7
+ "constructor",
8
+ "asymmetricMatch",
9
+ "nodeType",
10
+ "tagName",
11
+ "$$typeof",
12
+ "@@__IMMUTABLE_ITERABLE__@@",
13
+ "@@__IMMUTABLE_RECORD__@@",
14
+ ]);
15
+ function isInternalAccess(prop) {
16
+ return typeof prop === "symbol" || JS_INTERNALS.has(prop);
17
+ }
18
+ /**
19
+ * Creates a 3-level nested Proxy over a soap Client so that
20
+ * `proxy.ServiceName.PortName.OperationName(args)` delegates to
21
+ * `client.ServiceName.PortName.OperationNameAsync(args)` and
22
+ * returns only the result (first element of the response tuple).
23
+ */
24
+ export function createClientProxy(client) {
25
+ const serviceDescription = client.describe();
26
+ return new Proxy({}, {
27
+ get(_target, serviceName) {
28
+ if (isInternalAccess(serviceName))
29
+ return undefined;
30
+ if (!(serviceName in serviceDescription)) {
31
+ const available = Object.keys(serviceDescription);
32
+ throw new Error(`Service "${String(serviceName)}" not found. ` +
33
+ `Available services: ${JSON.stringify(available)}`);
34
+ }
35
+ return new Proxy({}, {
36
+ get(_target, portName) {
37
+ if (isInternalAccess(portName))
38
+ return undefined;
39
+ const portDesc = serviceDescription[serviceName]?.[portName];
40
+ if (!portDesc) {
41
+ const available = Object.keys(serviceDescription[serviceName] ?? {});
42
+ throw new Error(`Port "${String(portName)}" not found on service "${String(serviceName)}". ` +
43
+ `Available ports: ${JSON.stringify(available)}`);
44
+ }
45
+ return new Proxy({}, {
46
+ get(_target, operationName) {
47
+ if (isInternalAccess(operationName))
48
+ return undefined;
49
+ if (!(operationName in portDesc)) {
50
+ const available = Object.keys(portDesc);
51
+ throw new Error(`Operation "${String(operationName)}" not found on ` +
52
+ `${String(serviceName)}.${String(portName)}. ` +
53
+ `Available operations: ${JSON.stringify(available)}`);
54
+ }
55
+ return async (input) => {
56
+ const serviceObj = client[serviceName];
57
+ const portObj = serviceObj?.[portName];
58
+ const method = portObj?.[operationName + "Async"];
59
+ if (typeof method !== "function") {
60
+ throw new Error(`Operation ${String(serviceName)}.${String(portName)}.${String(operationName)} ` +
61
+ `exists in WSDL but was not found on the SOAP client`);
62
+ }
63
+ const [result] = await method(input);
64
+ return result;
65
+ };
66
+ },
67
+ });
68
+ },
69
+ });
70
+ },
71
+ });
72
+ }
73
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { IOptions } from "soap";
2
+ export type OperationDefinition = {
3
+ input: unknown;
4
+ output: unknown;
5
+ };
6
+ export type ServiceDefinition = {
7
+ [service: string]: {
8
+ [port: string]: {
9
+ [operation: string]: OperationDefinition;
10
+ };
11
+ };
12
+ };
13
+ /**
14
+ * Transforms a static service definition into a callable client interface.
15
+ * Each `{ input: I; output: O }` becomes `(input: I) => Promise<O>`.
16
+ */
17
+ export type InferClient<T extends ServiceDefinition> = {
18
+ [S in keyof T]: {
19
+ [P in keyof T[S]]: {
20
+ [O in keyof T[S][P]]: T[S][P][O] extends OperationDefinition ? (input: T[S][P][O]["input"]) => Promise<T[S][P][O]["output"]> : never;
21
+ };
22
+ };
23
+ };
24
+ export interface SoapClientOptions extends IOptions {
25
+ /**
26
+ * Override the SOAP endpoint URL. If provided, this replaces
27
+ * the `<soap:address location="...">` from the WSDL.
28
+ */
29
+ endpoint?: string;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,CAAC,OAAO,EAAE,MAAM,GAAG;QACjB,CAAC,IAAI,EAAE,MAAM,GAAG;YACd,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAAC;SAC1C,CAAC;KACH,CAAC;CACH,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,iBAAiB,IAAI;KACpD,CAAC,IAAI,MAAM,CAAC,GAAG;SACb,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;aAChB,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,mBAAmB,GACxD,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAC7D,KAAK;SACV;KACF;CACF,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,QAAQ;IACjD;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "typed-soap",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "End-to-end type-safe SOAP client for TypeScript. Generate typed RPC wrappers directly from WSDLs.",
5
- "keywords": ["soap", "typescript", "wsdl", "rpc", "trpc", "types", "client", "node-soap", "generator"],
5
+ "keywords": [
6
+ "soap",
7
+ "typescript",
8
+ "wsdl",
9
+ "rpc",
10
+ "trpc",
11
+ "types",
12
+ "client",
13
+ "node-soap",
14
+ "generator"
15
+ ],
6
16
  "type": "module",
7
17
  "main": "./dist/index.js",
8
18
  "types": "./dist/index.d.ts",
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Custom deserializer that fills gaps in the `soap` package's built-in
3
+ * XML-to-JS type conversion. Soap natively handles int, integer, short,
4
+ * long, float, double, decimal, boolean, dateTime, and date. Everything
5
+ * else falls through as a raw string. This map adds the missing numeric
6
+ * types so our generated TypeScript types match the actual runtime values.
7
+ */
8
+ export const typedSoapDeserializer: Record<string, (text: string) => number> = {
9
+ byte: (text) => parseInt(text, 10),
10
+ unsignedByte: (text) => parseInt(text, 10),
11
+ unsignedShort: (text) => parseInt(text, 10),
12
+ unsignedInt: (text) => parseInt(text, 10),
13
+ unsignedLong: (text) => parseInt(text, 10),
14
+ negativeInteger: (text) => parseInt(text, 10),
15
+ nonNegativeInteger: (text) => parseInt(text, 10),
16
+ positiveInteger: (text) => parseInt(text, 10),
17
+ nonPositiveInteger: (text) => parseInt(text, 10),
18
+ };
package/src/index.ts CHANGED
@@ -1,3 +1,59 @@
1
- export function createSoapWrapper() {
2
- // TODO: implement runtime SOAP client wrapper
1
+ import * as soap from "soap";
2
+ import type { ServiceDefinition, InferClient, SoapClientOptions } from "./types.js";
3
+ import { typedSoapDeserializer } from "./deserializer.js";
4
+ import { createClientProxy } from "./proxy.js";
5
+
6
+ export type { ServiceDefinition, OperationDefinition, InferClient, SoapClientOptions } from "./types.js";
7
+
8
+ /**
9
+ * Creates a type-safe SOAP client from a WSDL URL or file path.
10
+ *
11
+ * The generic parameter `T` should be a generated `ServiceDefinition`
12
+ * produced by `tsoap-cli`. The returned client has fully typed methods
13
+ * matching the WSDL's services, ports, and operations.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { createSoapClient } from 'typed-soap';
18
+ * import type { MyServiceDef } from './generated/my-service.js';
19
+ *
20
+ * const client = await createSoapClient<MyServiceDef>('http://example.com?wsdl');
21
+ * const result = await client.MyService.MyPort.MyOp({ arg: 'value' });
22
+ * ```
23
+ */
24
+ export async function createSoapClient<T extends ServiceDefinition>(
25
+ wsdlUrl: string,
26
+ options?: SoapClientOptions,
27
+ ): Promise<InferClient<T>> {
28
+ if (!wsdlUrl) {
29
+ throw new Error("createSoapClient: wsdlUrl is required");
30
+ }
31
+
32
+ const { endpoint, customDeserializer: userDeserializer, ...soapOptions } =
33
+ options ?? {};
34
+
35
+ const mergedOptions: soap.IOptions = {
36
+ ...soapOptions,
37
+ customDeserializer: {
38
+ ...typedSoapDeserializer,
39
+ ...userDeserializer,
40
+ },
41
+ };
42
+
43
+ let client: soap.Client;
44
+ try {
45
+ client = await soap.createClientAsync(wsdlUrl, mergedOptions);
46
+ } catch (err) {
47
+ const message =
48
+ err instanceof Error ? err.message : String(err);
49
+ throw new Error(
50
+ `Failed to create SOAP client from "${wsdlUrl}": ${message}`,
51
+ );
52
+ }
53
+
54
+ if (endpoint) {
55
+ client.setEndpoint(endpoint);
56
+ }
57
+
58
+ return createClientProxy(client) as InferClient<T>;
3
59
  }
package/src/proxy.ts ADDED
@@ -0,0 +1,110 @@
1
+ import type { Client } from "soap";
2
+
3
+ const JS_INTERNALS = new Set([
4
+ "then",
5
+ "toJSON",
6
+ "valueOf",
7
+ "toString",
8
+ "inspect",
9
+ "constructor",
10
+ "asymmetricMatch",
11
+ "nodeType",
12
+ "tagName",
13
+ "$$typeof",
14
+ "@@__IMMUTABLE_ITERABLE__@@",
15
+ "@@__IMMUTABLE_RECORD__@@",
16
+ ]);
17
+
18
+ function isInternalAccess(prop: string | symbol): boolean {
19
+ return typeof prop === "symbol" || JS_INTERNALS.has(prop as string);
20
+ }
21
+
22
+ /**
23
+ * Creates a 3-level nested Proxy over a soap Client so that
24
+ * `proxy.ServiceName.PortName.OperationName(args)` delegates to
25
+ * `client.ServiceName.PortName.OperationNameAsync(args)` and
26
+ * returns only the result (first element of the response tuple).
27
+ */
28
+ export function createClientProxy(client: Client): unknown {
29
+ const serviceDescription = client.describe();
30
+
31
+ return new Proxy(
32
+ {},
33
+ {
34
+ get(_target, serviceName: string | symbol) {
35
+ if (isInternalAccess(serviceName)) return undefined;
36
+
37
+ if (!(serviceName in serviceDescription)) {
38
+ const available = Object.keys(serviceDescription);
39
+ throw new Error(
40
+ `Service "${String(serviceName)}" not found. ` +
41
+ `Available services: ${JSON.stringify(available)}`,
42
+ );
43
+ }
44
+
45
+ return new Proxy(
46
+ {},
47
+ {
48
+ get(_target, portName: string | symbol) {
49
+ if (isInternalAccess(portName)) return undefined;
50
+
51
+ const portDesc =
52
+ serviceDescription[serviceName as string]?.[portName as string];
53
+ if (!portDesc) {
54
+ const available = Object.keys(
55
+ serviceDescription[serviceName as string] ?? {},
56
+ );
57
+ throw new Error(
58
+ `Port "${String(portName)}" not found on service "${String(serviceName)}". ` +
59
+ `Available ports: ${JSON.stringify(available)}`,
60
+ );
61
+ }
62
+
63
+ return new Proxy(
64
+ {},
65
+ {
66
+ get(_target, operationName: string | symbol) {
67
+ if (isInternalAccess(operationName)) return undefined;
68
+
69
+ if (!(operationName in portDesc)) {
70
+ const available = Object.keys(portDesc);
71
+ throw new Error(
72
+ `Operation "${String(operationName)}" not found on ` +
73
+ `${String(serviceName)}.${String(portName)}. ` +
74
+ `Available operations: ${JSON.stringify(available)}`,
75
+ );
76
+ }
77
+
78
+ return async (input: unknown) => {
79
+ const serviceObj = (client as Record<string, unknown>)[
80
+ serviceName as string
81
+ ] as Record<string, unknown> | undefined;
82
+ const portObj = serviceObj?.[portName as string] as
83
+ | Record<string, unknown>
84
+ | undefined;
85
+ const method = portObj?.[
86
+ (operationName as string) + "Async"
87
+ ] as
88
+ | ((...args: unknown[]) => Promise<unknown[]>)
89
+ | undefined;
90
+
91
+ if (typeof method !== "function") {
92
+ throw new Error(
93
+ `Operation ${String(serviceName)}.${String(portName)}.${String(operationName)} ` +
94
+ `exists in WSDL but was not found on the SOAP client`,
95
+ );
96
+ }
97
+
98
+ const [result] = await method(input);
99
+ return result;
100
+ };
101
+ },
102
+ },
103
+ );
104
+ },
105
+ },
106
+ );
107
+ },
108
+ },
109
+ );
110
+ }
package/src/types.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { IOptions } from "soap";
2
+
3
+ export type OperationDefinition = {
4
+ input: unknown;
5
+ output: unknown;
6
+ };
7
+
8
+ export type ServiceDefinition = {
9
+ [service: string]: {
10
+ [port: string]: {
11
+ [operation: string]: OperationDefinition;
12
+ };
13
+ };
14
+ };
15
+
16
+ /**
17
+ * Transforms a static service definition into a callable client interface.
18
+ * Each `{ input: I; output: O }` becomes `(input: I) => Promise<O>`.
19
+ */
20
+ export type InferClient<T extends ServiceDefinition> = {
21
+ [S in keyof T]: {
22
+ [P in keyof T[S]]: {
23
+ [O in keyof T[S][P]]: T[S][P][O] extends OperationDefinition
24
+ ? (input: T[S][P][O]["input"]) => Promise<T[S][P][O]["output"]>
25
+ : never;
26
+ };
27
+ };
28
+ };
29
+
30
+ export interface SoapClientOptions extends IOptions {
31
+ /**
32
+ * Override the SOAP endpoint URL. If provided, this replaces
33
+ * the `<soap:address location="...">` from the WSDL.
34
+ */
35
+ endpoint?: string;
36
+ }