11import { makePromiseKit } from '@endo/promise-kit' ;
2- import type { Struct } from '@metamask/superstruct' ;
32import { assert as assertStruct } from '@metamask/superstruct' ;
43import { assertIsJsonRpcResponse , isJsonRpcFailure } from '@metamask/utils' ;
5- import type { Json , JsonRpcParams } from '@metamask/utils' ;
4+ import type { JsonRpcParams } from '@metamask/utils' ;
65import { makeCounter } from '@ocap/utils' ;
76import type { PromiseCallbacks } from '@ocap/utils' ;
87
9- export type MethodSignature <
10- Method extends string ,
11- Params extends JsonRpcParams ,
12- Result extends Json ,
13- > = ( method : Method , params : Params ) => Promise < Result > ;
14-
15- type ExtractMethodName <
16- Methods extends MethodSignature < string , JsonRpcParams , Json > ,
17- > = Methods extends (
18- method : infer Method ,
19- params : JsonRpcParams ,
20- ) => Promise < Json >
21- ? Method
22- : never ;
23-
24- type ExtractParams <
25- Methods extends MethodSignature < string , JsonRpcParams , Json > ,
26- > = Methods extends ( method : string , params : infer Params ) => Promise < Json >
27- ? Params
28- : never ;
29-
30- type ExtractResult <
31- Methods extends MethodSignature < string , JsonRpcParams , Json > ,
32- > = Methods extends (
33- method : string ,
34- params : JsonRpcParams ,
35- ) => Promise < infer Result >
36- ? Result
37- : never ;
38-
39- export type MethodSpec < Method extends string , Result extends Json > = {
40- method : Method ;
41- result : Struct < Result > ;
42- } ;
43-
44- type MethodSpecs < Methods extends MethodSignature < string , JsonRpcParams , Json > > =
45- Record <
46- ExtractMethodName < Methods > ,
47- MethodSpec < ExtractMethodName < Methods > , ExtractResult < Methods > >
48- > ;
8+ import type {
9+ MethodSpec ,
10+ ExtractParams ,
11+ ExtractResult ,
12+ ExtractMethod ,
13+ MethodSpecRecord ,
14+ } from './utils.ts' ;
4915
5016type RpcPayload = {
5117 method : string ;
5218 params : JsonRpcParams ;
5319} ;
5420
55- type SendMessage = ( messageId : string , payload : RpcPayload ) => Promise < void > ;
21+ export type SendMessage = (
22+ messageId : string ,
23+ payload : RpcPayload ,
24+ ) => Promise < void > ;
5625
57- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58- export class RpcClient < Methods extends MethodSignature < string , any , any > > {
59- readonly #methods: MethodSpecs < Methods > ;
26+ export class RpcClient <
27+ // The class picks up its type from the `methods` argument,
28+ // so using `any` in this constraint is safe.
29+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30+ Methods extends MethodSpecRecord < MethodSpec < string , any , any > > ,
31+ > {
32+ readonly #methods: Methods ;
6033
6134 readonly #prefix: string ;
6235
@@ -66,20 +39,16 @@ export class RpcClient<Methods extends MethodSignature<string, any, any>> {
6639
6740 readonly #sendMessage: SendMessage ;
6841
69- constructor (
70- methods : MethodSpecs < Methods > ,
71- sendMessage : SendMessage ,
72- prefix : string ,
73- ) {
42+ constructor ( methods : Methods , sendMessage : SendMessage , prefix : string ) {
7443 this . #methods = methods ;
7544 this . #sendMessage = sendMessage ;
7645 this . #prefix = prefix ;
7746 }
7847
79- async call < Method extends Methods > (
80- method : ExtractMethodName < Method > ,
81- params : ExtractParams < Method > ,
82- ) : Promise < ExtractResult < Method > > {
48+ async call < Method extends ExtractMethod < Methods > > (
49+ method : Method ,
50+ params : ExtractParams < Method , Methods > ,
51+ ) : Promise < ExtractResult < Method , Methods > > {
8352 const response = await this . #createMessage( {
8453 method,
8554 params,
@@ -90,10 +59,23 @@ export class RpcClient<Methods extends MethodSignature<string, any, any>> {
9059 throw new Error ( `${ response . error . message } ` ) ;
9160 }
9261
93- assertResult ( response . result , this . #methods [ method ] . result ) ;
62+ this . #assertResult ( method , response . result ) ;
9463 return response . result ;
9564 }
9665
66+ #assertResult< Method extends ExtractMethod < Methods > > (
67+ method : Method ,
68+ result : unknown ,
69+ ) : asserts result is ExtractResult < Method , Methods > {
70+ try {
71+ // @ts -expect-error - TODO: For unknown reasons, TypeScript fails to recognize that
72+ // `Method` must be a key of `this.#methods`.
73+ assertStruct ( result , this . #methods[ method ] . result ) ;
74+ } catch ( error ) {
75+ throw new Error ( `Invalid result: ${ ( error as Error ) . message } ` ) ;
76+ }
77+ }
78+
9779 async #createMessage( payload : RpcPayload ) : Promise < unknown > {
9880 const { promise, reject, resolve } = makePromiseKit < unknown > ( ) ;
9981 const messageId = this . #nextMessageId( ) ;
@@ -128,19 +110,3 @@ export class RpcClient<Methods extends MethodSignature<string, any, any>> {
128110 return `${ this . #prefix} :${ this . #messageCounter( ) } ` ;
129111 }
130112}
131-
132- /**
133- * @param result - The result to assert.
134- * @param struct - The struct to assert the result against.
135- * @throws If the result is invalid.
136- */
137- function assertResult < Result extends Json > (
138- result : unknown ,
139- struct : Struct < Result > ,
140- ) : asserts result is Result {
141- try {
142- assertStruct ( result , struct ) ;
143- } catch ( error ) {
144- throw new Error ( `Invalid result: ${ ( error as Error ) . message } ` ) ;
145- }
146- }
0 commit comments