From 6467c8e6d51d621f3cb4770c0912009e6714a0e2 Mon Sep 17 00:00:00 2001 From: Ben Merckx Date: Tue, 20 Dec 2022 17:47:36 +0100 Subject: [PATCH 1/2] Support nesting objects in services --- src/client.ts | 36 +++++++++++++++++++++++++-------- src/server.ts | 7 ++++++- src/test/RequestAwareService.ts | 8 ++++++++ src/test/client.ts | 6 ++++++ src/test/service.ts | 6 ++++++ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/client.ts b/src/client.ts index f164b12..6b28f9f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -23,6 +23,8 @@ type Promisify = T extends (...args: any[]) => Promise ? T // already a promise : T extends (...args: infer A) => infer R ? (...args: A) => Promise + : T extends object + ? PromisifyMethods : T; // not a function; type PromisifyMethods = { @@ -60,21 +62,39 @@ export function rpcClient(url: string, options?: RpcOptions) { return result; }; + + function get(prop: string): any { + return new Proxy( + (...args: any) => request(prop.toString(), args), + { + get(_, childProp) { + if (isValidProp(childProp)) + return get(`${prop}.${childProp}`); + } + } + ) + } + return new Proxy( {}, { - /* istanbul ignore next */ - get(target, prop, receiver) { - if (typeof prop === "symbol") return; - if (prop.startsWith("$")) return; - if (prop in Object.prototype) return; - if (prop === "toJSON") return; - return (...args: any) => request(prop.toString(), args); - }, + get(_, prop) { + if (isValidProp(prop)) + return get(prop); + } } ) as PromisifyMethods; } +/* istanbul ignore next */ +function isValidProp(prop: string | symbol): prop is string { + if (typeof prop === "symbol") return false + if (prop.startsWith("$")) return false + if (prop in Object.prototype) return false + if (prop === "toJSON") return false + return true +} + function removeTrailingUndefs(values: any[]) { const a = [...values]; while (a.length && a[a.length - 1] === undefined) a.length--; diff --git a/src/server.ts b/src/server.ts index 04eefc8..0f085d8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -90,7 +90,12 @@ export async function handleRpc( error: { code: -32600, message: "Invalid Request" }, }; } - const { jsonrpc, method, params } = request; + const { jsonrpc, params } = request; + const path = request.method.split('.'); + const method = path.pop()!; + for (const property of path) + if (hasProperty(service, property)) + service = service[property] as object; if (!hasMethod(service, method)) { console.log("Method %s not found", method, service); return { diff --git a/src/test/RequestAwareService.ts b/src/test/RequestAwareService.ts index 76c6994..49c371a 100644 --- a/src/test/RequestAwareService.ts +++ b/src/test/RequestAwareService.ts @@ -23,4 +23,12 @@ export class RequestAwareService implements Service { echoHeader(name: string) { return this.headers?.[name.toLowerCase()]; } + + get recurse() { + return { + method() { + return 'recurse.method'; + } + } + } } diff --git a/src/test/client.ts b/src/test/client.ts index 3389839..1784b6d 100644 --- a/src/test/client.ts +++ b/src/test/client.ts @@ -49,3 +49,9 @@ tap.test("should throw on errors", async (t) => { const promise = client.sorry("Dave"); t.rejects(promise, new RpcError("Sorry Dave.", -32000)); }); + +tap.test("should support recursion on property access", async (t) => { + const client = rpcClient(apiUrl); + const result = await client.recurse.method(); + t.equal(result, "recurse.method"); +}); \ No newline at end of file diff --git a/src/test/service.ts b/src/test/service.ts index 07bc5f8..e91b436 100644 --- a/src/test/service.ts +++ b/src/test/service.ts @@ -14,6 +14,12 @@ export const service = { echoHeader(name: string): string | string[] | undefined { throw new Error("This service can't access request headers"); }, + + recurse: { + method() { + return 'recurse.method' + } + } }; export type Service = typeof service; From 6d21bf0a6739dcc9bf9b28efec1c4670ae840c72 Mon Sep 17 00:00:00 2001 From: Ben Merckx Date: Tue, 20 Dec 2022 17:49:06 +0100 Subject: [PATCH 2/2] Follow the programming style --- src/client.ts | 10 +++++----- src/test/service.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client.ts b/src/client.ts index 6b28f9f..acfb9be 100644 --- a/src/client.ts +++ b/src/client.ts @@ -88,11 +88,11 @@ export function rpcClient(url: string, options?: RpcOptions) { /* istanbul ignore next */ function isValidProp(prop: string | symbol): prop is string { - if (typeof prop === "symbol") return false - if (prop.startsWith("$")) return false - if (prop in Object.prototype) return false - if (prop === "toJSON") return false - return true + if (typeof prop === "symbol") return false; + if (prop.startsWith("$")) return false; + if (prop in Object.prototype) return false; + if (prop === "toJSON") return false; + return true; } function removeTrailingUndefs(values: any[]) { diff --git a/src/test/service.ts b/src/test/service.ts index e91b436..852d2d9 100644 --- a/src/test/service.ts +++ b/src/test/service.ts @@ -17,7 +17,7 @@ export const service = { recurse: { method() { - return 'recurse.method' + return 'recurse.method'; } } };