diff --git a/libs/rpc/src/client/index.test.ts b/libs/rpc/src/client/index.test.ts index 718de8f4..fe5d511d 100644 --- a/libs/rpc/src/client/index.test.ts +++ b/libs/rpc/src/client/index.test.ts @@ -1,18 +1,19 @@ import { expect, test } from "vitest" import { createPrimClient } from "./index" -type ExampleModule = { hello: (name?: string) => string; goodbye: () => string; anError: () => void } +type ExampleModule = { hello: (name?: string) => Promise; goodbye: () => string; anError: () => void } +const exampleModule = { + hello(name?: string) { + return ["Hello", name].filter(given => given).join(" ") + }, + anError() { + throw "Uh-oh" + }, +} test("static import", async () => { - const client = createPrimClient<() => Promise>({ - module: { - hello(name: string) { - return ["Hello", name].filter(given => given).join(" ") - }, - anError() { - throw "Uh-oh" - }, - }, + const client = createPrimClient, typeof exampleModule>({ + module: exampleModule, }) // not async since module was provided locally expect(client.hello()).toBe("Hello") @@ -23,12 +24,15 @@ test("static import", async () => { test("dynamic import", async () => { const client = createPrimClient({ - module: new Promise>(r => + module: new Promise(r => r({ // goodbye() { return "Bye!" }, hello(name: string) { return ["Hello", name].filter(given => given).join(" ") }, + anError() { + throw "Uh-oh" + }, }) ), }) diff --git a/libs/rpc/src/client/index.ts b/libs/rpc/src/client/index.ts index 8afcb912..3433af02 100644 --- a/libs/rpc/src/client/index.ts +++ b/libs/rpc/src/client/index.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { JsonHandler, PrimOptions, PromisifiedModule } from "../interfaces" +import type { PrimOptions, PromisifiedModule } from "../interfaces" import { createPrimOptions } from "../options" import { isDefined } from "emery" import { createMethodCatcher } from "./proxy" @@ -7,15 +7,16 @@ import { handlePotentialPromise } from "./wrapper" import { getUnfulfilledModule, handleLocalModule } from "./local" export function createPrimClient< - ModuleType extends PrimOptions["module"] = object, - JsonHandlerType extends PrimOptions["jsonHandler"] = JsonHandler, ->(options?: PrimOptions) { - options = createPrimOptions>(options) + ModuleType extends GivenOptions["module"], + OverrideModule extends GivenOptions["module"] = never, + GivenOptions extends PrimOptions = PrimOptions, +>(options?: GivenOptions) { + options = createPrimOptions(options) const providedModule = getUnfulfilledModule(options.module) const providedMethodPlugin = isDefined(options.methodPlugin) const providedCallbackPlugin = isDefined(options.callbackPlugin) - // the returned client will catch all method calls given on it - return createMethodCatcher>({ + // the returned client will catch all method calls given on it recursively + return createMethodCatcher>({ onMethod(rpc, next) { // if module method was provided (and is not dynamic import), intercept call and return synchronously if (providedModule instanceof Promise) return next diff --git a/libs/rpc/src/interfaces.ts b/libs/rpc/src/interfaces.ts index 23f24b1b..9a1dcb6c 100644 --- a/libs/rpc/src/interfaces.ts +++ b/libs/rpc/src/interfaces.ts @@ -125,21 +125,47 @@ type FunctionAndForm = { (formLike: SubmitEvent | FormData | HTMLFormElement): Result } +// type PromisifiedModuleDirectWithoutOverride< +// ModuleGiven extends object, +// Recursive extends true | false = true, +// Keys extends keyof ModuleGiven = Extract, +// > = ConditionalExcept< +// { +// [Key in Keys]: ModuleGiven[Key] extends ((...args: infer A) => infer R) & object +// ? FunctionAndForm>> & PromisifiedModuleDirect +// : ModuleGiven[Key] extends object +// ? Recursive extends true +// ? PromisifiedModuleDirect +// : never +// : never +// }, +// never +// > + // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyFunction = (...args: any[]) => any // NOTE: consider condition of checking `.rpc` property on function (but also remember that it may be in allow list) type PromisifiedModuleDirect< ModuleGiven extends object, Recursive extends true | false = true, + ModuleOverride extends object = never, Keys extends keyof ModuleGiven = Extract, > = ConditionalExcept< { - [Key in Keys]: ModuleGiven[Key] extends ((...args: infer A) => infer R) & object - ? FunctionAndForm>> & PromisifiedModuleDirect + [Key in Keys]: ModuleGiven[Key] extends (...args: infer A) => infer R + ? Key extends keyof ModuleOverride + ? ModuleOverride[Key] extends (...args: infer A2) => infer R2 + ? FunctionAndForm & PromisifiedModuleDirect + : FunctionAndForm>> & PromisifiedModuleDirect + : FunctionAndForm>> & PromisifiedModuleDirect : ModuleGiven[Key] extends object ? Recursive extends true - ? PromisifiedModuleDirect - : never + ? Key extends keyof ModuleOverride + ? ModuleOverride[Key] extends object + ? PromisifiedModuleDirect + : PromisifiedModuleDirect + : PromisifiedModuleDirect + : PromisifiedModuleDirect : never }, never @@ -179,24 +205,30 @@ type PromisifiedModuleDirect< // void promTest.abc("what") // NOTE: this is a non-recursive version of default `Awaited` type that comes with TypeScript -type PromisifiedModuleDynamicImport = ModuleGiven extends object & { +type PromisifiedModuleDynamicImport< + ModuleGiven extends object, + ModuleOverride extends object = never, +> = ModuleGiven extends object & { // eslint-disable-next-line @typescript-eslint/no-explicit-any then: (onfulfilled: infer F, ...args: infer _) => any } ? // eslint-disable-next-line @typescript-eslint/no-explicit-any F extends (value: infer V, ...args: infer _) => any ? V extends object - ? PromisifiedModuleDirect + ? PromisifiedModuleDirect : never : never - : PromisifiedModuleDirect + : PromisifiedModuleDirect // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyFunctionReturnsPromise = (...args: any[]) => PromiseLike // If given a function that returns a promise, get the returned promise (pattern used often with dynamic imports) -export type PromisifiedModule = Given extends AnyFunctionReturnsPromise - ? PromisifiedModuleDynamicImport> - : PromisifiedModuleDynamicImport +export type PromisifiedModule< + Given extends object, + ModuleOverride extends object = never, +> = Given extends AnyFunctionReturnsPromise + ? PromisifiedModuleDynamicImport, ModuleOverride> + : PromisifiedModuleDynamicImport export type RemoveFunctionWrapper = Given extends AnyFunctionReturnsPromise ? ReturnType