@metamask/kernel-rpc-methods
Version:
Utilities for implementing Ocap Kernel JSON-RPC methods
1 lines • 6.86 kB
Source Map (JSON)
{"version":3,"file":"RpcService.mjs","sourceRoot":"","sources":["../src/RpcService.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,6BAA6B;AAEjD,OAAO,EAAE,WAAW,EAAE,wBAAwB;AA0B9C;;GAEG;AACH,MAAM,OAAO,UAAU;IAUrB;;;;;OAKG;IACH,YACE,QAAkB,EAClB,KAA6C;;QAZtC,uCAAoB;QAEpB,oCAA+C;QAYtD,uBAAA,IAAI,wBAAa,QAAQ,MAAA,CAAC;QAC1B,uBAAA,IAAI,qBAAU,KAAK,MAAA,CAAC;IACtB,CAAC;IAED;;;;;OAKG;IACH,eAAe,CACb,MAAc;QAEd,IAAI,CAAC,uBAAA,IAAI,oDAAW,MAAf,IAAI,EAAY,MAAkD,CAAC,EAAE,CAAC;YACzE,MAAM,SAAS,CAAC,cAAc,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CACX,MAAc,EACd,MAAe;QAEf,MAAM,OAAO,GAAG,uBAAA,IAAI,qDAAY,MAAhB,IAAI,EAAa,MAAM,CAAC,CAAC;QACzC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAErC,MAAM,aAAa,GAAG,WAAW,CAAC,uBAAA,IAAI,yBAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9D,OAAO,MAAM,OAAO,CAAC,cAAc,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;CA0BF;uKAjBG,MAAc;IAEd,OAAO,WAAW,CAAC,uBAAA,IAAI,4BAAU,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC,2DAUC,MAAc;IAEd,OAAO,uBAAA,IAAI,4BAAU,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAGH;;;;GAIG;AACH,SAAS,YAAY,CACnB,MAAe,EACf,MAAsB;IAEtB,IAAI,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,mBAAoB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,WAAW,CAGlB,KAAY,EAAE,SAAsC;IACpD,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAClC,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAE;QACvB,MAAM,GAAG,GAAG,QAAoB,CAAC;QACjC,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,OAAO,UAAU,CAAC;IACpB,CAAC,EACD,EAAE,CACsB,CAAC;AAC7B,CAAC","sourcesContent":["import { rpcErrors } from '@metamask/rpc-errors';\nimport type { Struct } from '@metamask/superstruct';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json, JsonRpcParams } from '@metamask/utils';\n\nimport type { Handler } from './types.ts';\n\ntype ExtractHooks<Handlers> =\n // We only use this type to extract the hooks from the handlers,\n // so we can safely use `any` for the generic constraints.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Handlers extends Handler<string, any, any, infer Hooks> ? Hooks : never;\n\ntype ExtractMethods<Handlers> =\n // We only use this type to extract the hooks from the handlers,\n // so we can safely use `any` for the generic constraints.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Handlers extends Handler<infer Methods, any, any, any> ? Methods : never;\n\ntype HandlerRecord<\n Handlers extends Handler<\n string,\n JsonRpcParams,\n Json | void,\n Record<string, unknown>\n >,\n> = Record<Handlers['method'], Handlers>;\n\n/**\n * A registry for RPC method handlers that provides type-safe registration and execution.\n */\nexport class RpcService<\n // The class picks up its type from the `handlers` argument,\n // so using `any` in this constraint is safe.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Handlers extends HandlerRecord<Handler<string, any, any, any>>,\n> {\n readonly #handlers: Handlers;\n\n readonly #hooks: ExtractHooks<Handlers[keyof Handlers]>;\n\n /**\n * Create a new HandlerRegistry with the specified method handlers.\n *\n * @param handlers - A record mapping method names to their handler implementations.\n * @param hooks - The hooks to pass to the method implementation.\n */\n constructor(\n handlers: Handlers,\n hooks: ExtractHooks<Handlers[keyof Handlers]>,\n ) {\n this.#handlers = handlers;\n this.#hooks = hooks;\n }\n\n /**\n * Assert that a method is registered in this registry.\n *\n * @param method - The method name to check.\n * @throws If the method is not registered.\n */\n assertHasMethod(\n method: string,\n ): asserts method is ExtractMethods<Handlers[keyof Handlers]> {\n if (!this.#hasMethod(method as ExtractMethods<Handlers[keyof Handlers]>)) {\n throw rpcErrors.methodNotFound();\n }\n }\n\n /**\n * Execute a method with the provided parameters. Only the hooks specified in the\n * handler's `hooks` array will be passed to the implementation.\n *\n * @param method - The method name to execute.\n * @param params - The parameters to pass to the method implementation.\n * @returns The result of the method execution.\n * @throws If the parameters are invalid.\n */\n async execute<Method extends ExtractMethods<Handlers[keyof Handlers]>>(\n method: Method,\n params: unknown,\n ): Promise<ReturnType<Handlers[Method]['implementation']>> {\n const handler = this.#getHandler(method);\n assertParams(params, handler.params);\n\n const expectedHooks = selectHooks(this.#hooks, handler.hooks);\n return await handler.implementation(expectedHooks, params);\n }\n\n /**\n * Check if a method is registered in this registry.\n *\n * @param method - The method name to check.\n * @returns Whether the method is registered.\n */\n #hasMethod<Method extends ExtractMethods<Handlers[keyof Handlers]>>(\n method: Method,\n ): boolean {\n return hasProperty(this.#handlers, method);\n }\n\n /**\n * Get a handler for a specific method.\n *\n * @param method - The method name to get the handler for.\n * @returns The handler for the specified method.\n * @throws If the method is not registered.\n */\n #getHandler<Method extends ExtractMethods<Handlers[keyof Handlers]>>(\n method: Method,\n ): Handlers[Method] {\n return this.#handlers[method];\n }\n}\n\n/**\n * @param params - The parameters to assert.\n * @param struct - The struct to assert the parameters against.\n * @throws If the parameters are invalid.\n */\nfunction assertParams<Params extends JsonRpcParams>(\n params: unknown,\n struct: Struct<Params>,\n): asserts params is Params {\n try {\n struct.assert(params);\n } catch (error) {\n throw new Error(`Invalid params: ${(error as Error).message}`);\n }\n}\n\n/**\n * Returns the subset of the specified `hooks` that are included in the\n * `hookNames` array. This is a Principle of Least Authority (POLA) measure\n * to ensure that each RPC method implementation only has access to the\n * API \"hooks\" it needs to do its job.\n *\n * @param hooks - The hooks to select from.\n * @param hookNames - The names of the hooks to select.\n * @returns The selected hooks.\n * @template Hooks - The hooks to select from.\n * @template HookName - The names of the hooks to select.\n */\nfunction selectHooks<\n Hooks extends Record<string, unknown>,\n HookName extends keyof Hooks,\n>(hooks: Hooks, hookNames: { [Key in HookName]: true }): Pick<Hooks, HookName> {\n return Object.keys(hookNames).reduce<Partial<Pick<Hooks, HookName>>>(\n (hookSubset, hookName) => {\n const key = hookName as HookName;\n hookSubset[key] = hooks[key];\n return hookSubset;\n },\n {},\n ) as Pick<Hooks, HookName>;\n}\n"]}