electron-effect-rpc 0.1.0 → 0.2.0

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.
package/src/contract.ts DELETED
@@ -1,165 +0,0 @@
1
- import * as S from "@effect/schema/Schema";
2
-
3
- export type SchemaNoContext = S.Schema.AnyNoContext;
4
-
5
- export const NoError = S.Never;
6
- export type NoError = typeof NoError;
7
-
8
- export type ErrorSchema = SchemaNoContext | S.Schema<never, never, never>;
9
-
10
- export interface RpcMethod<
11
- Name extends string,
12
- Req extends SchemaNoContext,
13
- Res extends SchemaNoContext,
14
- Err extends ErrorSchema = NoError
15
- > {
16
- readonly name: Name;
17
- readonly req: Req;
18
- readonly res: Res;
19
- readonly err: Err;
20
- }
21
-
22
- export function rpc<
23
- const Name extends string,
24
- Req extends SchemaNoContext,
25
- Res extends SchemaNoContext,
26
- Err extends ErrorSchema
27
- >(name: Name, req: Req, res: Res, err: Err): RpcMethod<Name, Req, Res, Err>;
28
-
29
- export function rpc<
30
- const Name extends string,
31
- Req extends SchemaNoContext,
32
- Res extends SchemaNoContext
33
- >(name: Name, req: Req, res: Res): RpcMethod<Name, Req, Res, NoError>;
34
-
35
- export function rpc<const Name extends string>(
36
- name: Name,
37
- req: SchemaNoContext,
38
- res: SchemaNoContext,
39
- err: ErrorSchema = NoError
40
- ): RpcMethod<Name, SchemaNoContext, SchemaNoContext, ErrorSchema> {
41
- return { name, req, res, err };
42
- }
43
-
44
- export interface RpcEvent<
45
- Payload extends SchemaNoContext,
46
- Context extends SchemaNoContext | null,
47
- Name extends string = string
48
- > {
49
- readonly name: Name;
50
- readonly payload: Payload;
51
- readonly context: Context;
52
- }
53
-
54
- export function event<
55
- const Name extends string,
56
- Payload extends SchemaNoContext,
57
- Context extends SchemaNoContext
58
- >(name: Name, payload: Payload, context: Context): RpcEvent<Payload, Context, Name>;
59
-
60
- export function event<const Name extends string, Payload extends SchemaNoContext>(
61
- name: Name,
62
- payload: Payload
63
- ): RpcEvent<Payload, null, Name>;
64
-
65
- export function event<const Name extends string>(
66
- name: Name,
67
- payload: SchemaNoContext,
68
- context?: SchemaNoContext | null
69
- ): RpcEvent<SchemaNoContext, SchemaNoContext | null, Name> {
70
- return { name, payload, context: context ?? null };
71
- }
72
-
73
- export const exitSchemaFor = <
74
- Name extends string,
75
- Req extends SchemaNoContext,
76
- Res extends SchemaNoContext,
77
- Err extends ErrorSchema
78
- >(
79
- method: RpcMethod<Name, Req, Res, Err>
80
- ) =>
81
- S.Exit({
82
- success: method.res,
83
- failure: method.err,
84
- defect: S.Defect,
85
- });
86
-
87
- export type AnyMethod = RpcMethod<
88
- string,
89
- SchemaNoContext,
90
- SchemaNoContext,
91
- ErrorSchema
92
- >;
93
-
94
- export type AnyEvent = RpcEvent<SchemaNoContext, SchemaNoContext | null, string>;
95
-
96
- export type RpcInput<M extends AnyMethod> = S.Schema.Type<M["req"]>;
97
-
98
- export type RpcOutput<M extends AnyMethod> = S.Schema.Type<M["res"]>;
99
-
100
- export type RpcError<M extends AnyMethod> = S.Schema.Type<M["err"]>;
101
-
102
- export type RpcEventPayload<E extends AnyEvent> = S.Schema.Type<E["payload"]>;
103
-
104
- /** Extract a method from a tuple by its name string literal. */
105
- export type ExtractMethod<
106
- Methods extends readonly AnyMethod[],
107
- Name extends string
108
- > = Extract<Methods[number], { readonly name: Name }>;
109
-
110
- export interface RpcContract<
111
- Methods extends ReadonlyArray<AnyMethod>,
112
- Events extends ReadonlyArray<AnyEvent>
113
- > {
114
- readonly methods: Methods;
115
- readonly events: Events;
116
- }
117
-
118
- const collectDuplicates = (names: ReadonlyArray<string>): Array<string> => {
119
- const counts = new Map<string, number>();
120
- const duplicates: string[] = [];
121
-
122
- for (const name of names) {
123
- const next = (counts.get(name) ?? 0) + 1;
124
- counts.set(name, next);
125
- if (next === 2) {
126
- duplicates.push(name);
127
- }
128
- }
129
-
130
- return duplicates;
131
- };
132
-
133
- export const defineContract = <
134
- const Methods extends ReadonlyArray<AnyMethod>,
135
- const Events extends ReadonlyArray<AnyEvent>
136
- >(input: {
137
- readonly methods: Methods;
138
- readonly events: Events;
139
- }): RpcContract<Methods, Events> => {
140
- const { methods, events } = input;
141
-
142
- if (!Array.isArray(methods)) {
143
- throw new Error("RPC contract methods must be an array.");
144
- }
145
-
146
- if (!Array.isArray(events)) {
147
- throw new Error("RPC contract events must be an array.");
148
- }
149
-
150
- const duplicateMethods = collectDuplicates(methods.map((method) => method.name));
151
- if (duplicateMethods.length > 0) {
152
- throw new Error(
153
- `Duplicate RPC method name(s): ${duplicateMethods.join(", ")}`
154
- );
155
- }
156
-
157
- const duplicateEvents = collectDuplicates(events.map((event) => event.name));
158
- if (duplicateEvents.length > 0) {
159
- throw new Error(
160
- `Duplicate RPC event name(s): ${duplicateEvents.join(", ")}`
161
- );
162
- }
163
-
164
- return input;
165
- };
package/src/main.ts DELETED
@@ -1,169 +0,0 @@
1
- import * as S from "@effect/schema/Schema";
2
- import { Effect, PubSub, Stream } from "effect";
3
- import * as Runtime from "effect/Runtime";
4
- import {
5
- exitSchemaFor,
6
- type RpcContract,
7
- type RpcError,
8
- type RpcEventPayload,
9
- type RpcInput,
10
- type RpcOutput,
11
- } from "./contract.ts";
12
- import {
13
- defaultChannelPrefix,
14
- type AnyEvent,
15
- type AnyMethod,
16
- type EventBusOptions,
17
- type Implementations,
18
- type IpcMainLike,
19
- type RpcEventBus,
20
- type RpcServerOptions,
21
- } from "./types.ts";
22
-
23
- const resolveChannelPrefix = (prefix: EventBusOptions["channelPrefix"]) =>
24
- prefix ?? defaultChannelPrefix;
25
-
26
- export const createRpcServer = <
27
- const Methods extends ReadonlyArray<AnyMethod>,
28
- const Events extends ReadonlyArray<AnyEvent>,
29
- R = never
30
- >(
31
- contract: RpcContract<Methods, Events>,
32
- ipc: IpcMainLike,
33
- implementations: Implementations<RpcContract<Methods, Events>, R>,
34
- options?: RpcServerOptions<R>
35
- ): void => {
36
- const channelPrefix = resolveChannelPrefix(options?.channelPrefix);
37
- const runPromiseExit = <A, E>(effect: Effect.Effect<A, E, R>) => {
38
- if (options?.runtime) {
39
- return Runtime.runPromiseExit(options.runtime)(effect);
40
- }
41
-
42
- // @ts-expect-error -- default runtime only supports R=never when no runtime is provided
43
- return Effect.runPromiseExit(effect);
44
- };
45
- const implementationsByName: Implementations<RpcContract<Methods, Events>, R> &
46
- Record<string, unknown> = implementations;
47
-
48
- const methodNames = new Set(contract.methods.map((method) => method.name));
49
-
50
- for (const name in implementations) {
51
- if (!methodNames.has(name)) {
52
- throw new Error(`Implementation provided for unknown RPC method: ${name}`);
53
- }
54
- }
55
-
56
- contract.methods.forEach((method: Methods[number]) => {
57
- const impl = implementationsByName[method.name];
58
- if (!isImplementation<typeof method, R>(impl)) {
59
- throw new Error(`Missing implementation for RPC method: ${method.name}`);
60
- }
61
-
62
- const exitSchema = exitSchemaFor(method);
63
- const encodeExit = S.encodeUnknownSync(exitSchema);
64
- const decodeInput = S.decodeUnknownSync(method.req);
65
- const channel = `${channelPrefix.rpc}${method.name}`;
66
-
67
- ipc.handle(channel, async (_event, rawPayload) => {
68
- let input: RpcInput<typeof method>;
69
- try {
70
- input = decodeInput(rawPayload);
71
- } catch (cause) {
72
- const defectExit = await runPromiseExit(Effect.die(cause));
73
- return encodeExit(defectExit);
74
- }
75
-
76
- const exit = await runPromiseExit(impl(input));
77
- return encodeExit(exit);
78
- });
79
- });
80
- };
81
-
82
- type Envelope<E extends AnyEvent> = {
83
- readonly event: E;
84
- readonly payload: RpcEventPayload<E>;
85
- };
86
-
87
- const isImplementation = <M extends AnyMethod, R>(
88
- value: unknown
89
- ): value is (
90
- input: RpcInput<M>
91
- ) => Effect.Effect<RpcOutput<M>, RpcError<M>, R> => typeof value === "function";
92
-
93
- const encodePayload = <E extends AnyEvent>(
94
- event: E,
95
- payload: RpcEventPayload<E>
96
- ) =>
97
- Effect.try({
98
- try: () => S.encodeSync(event.payload)(payload),
99
- catch: (cause) =>
100
- cause instanceof Error ? cause : new Error(String(cause)),
101
- });
102
-
103
- const dispatchToRenderer = <E extends AnyEvent>(
104
- getWindow: EventBusOptions["getWindow"],
105
- channelPrefix: EventBusOptions["channelPrefix"],
106
- event: E,
107
- encoded: unknown
108
- ) =>
109
- Effect.sync(() => {
110
- const window = getWindow();
111
- if (window && !window.isDestroyed()) {
112
- const prefix = resolveChannelPrefix(channelPrefix);
113
- window.webContents.send(`${prefix.event}${event.name}`, encoded);
114
- }
115
- });
116
-
117
- export const createEventBus = <
118
- const Methods extends ReadonlyArray<AnyMethod>,
119
- const Events extends ReadonlyArray<AnyEvent>
120
- >(
121
- _contract: RpcContract<Methods, Events>,
122
- options: EventBusOptions
123
- ): RpcEventBus<RpcContract<Methods, Events>> => {
124
- const pubsub = Effect.runSync(
125
- PubSub.unbounded<Envelope<Events[number]>>()
126
- );
127
-
128
- Effect.runFork(
129
- Effect.scoped(
130
- Effect.gen(function* () {
131
- const dequeue = yield* PubSub.subscribe(pubsub);
132
- yield* Stream.fromQueue(dequeue, { shutdown: true }).pipe(
133
- Stream.runForEach(({ event, payload }) =>
134
- Effect.gen(function* () {
135
- const encodeResult = yield* Effect.either(
136
- encodePayload(event, payload)
137
- );
138
-
139
- if (encodeResult._tag === "Left") {
140
- return;
141
- }
142
-
143
- yield* dispatchToRenderer(
144
- options.getWindow,
145
- options.channelPrefix,
146
- event,
147
- encodeResult.right
148
- );
149
- })
150
- )
151
- );
152
- })
153
- ).pipe(
154
- Effect.catchAllCause(() => Effect.void),
155
- Effect.retry({ times: 3 }),
156
- Effect.catchAll(() => Effect.void)
157
- )
158
- );
159
-
160
- const emit = <E extends Events[number]>(
161
- event: E,
162
- payload: RpcEventPayload<E>
163
- ) =>
164
- Effect.flatMap(PubSub.publish(pubsub, { event, payload }), () =>
165
- Effect.void
166
- );
167
-
168
- return { emit };
169
- };
package/src/preload.ts DELETED
@@ -1,33 +0,0 @@
1
- import { contextBridge, ipcRenderer, type IpcRendererEvent } from "electron";
2
- import { defaultChannelPrefix, type ChannelPrefix } from "./types.ts";
3
-
4
- type Listener = (payload: unknown) => void;
5
-
6
- type BridgeOptions = {
7
- readonly rpcGlobal?: string;
8
- readonly eventsGlobal?: string;
9
- readonly channelPrefix?: ChannelPrefix;
10
- };
11
-
12
- export const exposeRpcBridge = (options?: BridgeOptions): void => {
13
- const rpcGlobal = options?.rpcGlobal ?? "rpc";
14
- const eventsGlobal = options?.eventsGlobal ?? "events";
15
- const channelPrefix = options?.channelPrefix ?? defaultChannelPrefix;
16
-
17
- const invoke = (method: string, payload: unknown): Promise<unknown> =>
18
- ipcRenderer.invoke(`${channelPrefix.rpc}${method}`, payload);
19
-
20
- const subscribe = (event: string, listener: Listener): (() => void) => {
21
- const wrapped = (_event: IpcRendererEvent, payload: unknown) =>
22
- listener(payload);
23
-
24
- ipcRenderer.on(`${channelPrefix.event}${event}`, wrapped);
25
-
26
- return () => {
27
- ipcRenderer.removeListener(`${channelPrefix.event}${event}`, wrapped);
28
- };
29
- };
30
-
31
- contextBridge.exposeInMainWorld(rpcGlobal, { invoke });
32
- contextBridge.exposeInMainWorld(eventsGlobal, { subscribe });
33
- };
package/src/renderer.ts DELETED
@@ -1,167 +0,0 @@
1
- import * as S from "@effect/schema/Schema";
2
- import { Cause, Exit } from "effect";
3
- import {
4
- exitSchemaFor,
5
- type RpcContract,
6
- type RpcEventPayload,
7
- type RpcInput,
8
- type RpcOutput,
9
- } from "./contract.ts";
10
- import {
11
- RpcDefectError,
12
- type AnyEvent,
13
- type AnyMethod,
14
- type EventSubscriber,
15
- type EventSubscriberOptions,
16
- type RpcCaller,
17
- type RpcClient,
18
- type RpcClientOptions,
19
- } from "./types.ts";
20
-
21
- const formatCause = (cause: unknown): string =>
22
- cause instanceof Error ? cause.message : String(cause);
23
-
24
- const requireInvoke = (options?: RpcClientOptions) => {
25
- if (!options?.invoke) {
26
- throw new Error("RpcClientOptions.invoke is required.");
27
- }
28
- return options.invoke;
29
- };
30
-
31
- const requireSubscribe = (options?: EventSubscriberOptions) => {
32
- if (!options?.subscribe) {
33
- throw new Error("EventSubscriberOptions.subscribe is required.");
34
- }
35
- return options.subscribe;
36
- };
37
-
38
- type MutableRpcClient<
39
- C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>
40
- > = {
41
- -readonly [Name in keyof RpcClient<C>]: RpcClient<C>[Name];
42
- };
43
-
44
- export const createRpcClient = <
45
- const Methods extends ReadonlyArray<AnyMethod>,
46
- const Events extends ReadonlyArray<AnyEvent>
47
- >(
48
- contract: RpcContract<Methods, Events>,
49
- options?: RpcClientOptions
50
- ): RpcClient<RpcContract<Methods, Events>> => {
51
- const invoke = requireInvoke(options);
52
-
53
- const call = async <M extends Methods[number]>(
54
- method: M,
55
- input: RpcInput<M>
56
- ): Promise<RpcOutput<M>> => {
57
- let encoded: unknown;
58
- try {
59
- encoded = S.encodeSync(method.req)(input);
60
- } catch (cause) {
61
- throw new Error(
62
- `RPC ${method.name} request encoding failed: ${formatCause(cause)}`
63
- );
64
- }
65
-
66
- const raw = await invoke(method.name, encoded);
67
-
68
- const exitSchema = exitSchemaFor(method);
69
- const decodeExit = S.decodeUnknownSync(exitSchema);
70
- let exit: ReturnType<typeof decodeExit>;
71
-
72
- try {
73
- exit = decodeExit(raw);
74
- } catch (cause) {
75
- throw new Error(
76
- `RPC ${method.name} response decoding failed: ${formatCause(cause)}`
77
- );
78
- }
79
-
80
- if (Exit.isSuccess(exit)) {
81
- return exit.value;
82
- }
83
-
84
- const cause = exit.cause;
85
- const failureOption = Cause.failureOption(cause);
86
- if (failureOption._tag === "Some") {
87
- throw failureOption.value;
88
- }
89
-
90
- const defectOption = Cause.dieOption(cause);
91
- if (defectOption._tag === "Some") {
92
- const defect = defectOption.value;
93
- if (defect instanceof Error) {
94
- throw new RpcDefectError(defect.message, defect);
95
- }
96
- throw new RpcDefectError(String(defect), defect);
97
- }
98
-
99
- throw new RpcDefectError(
100
- "RPC call was interrupted or failed unexpectedly",
101
- cause
102
- );
103
- };
104
-
105
- const client: MutableRpcClient<RpcContract<Methods, Events>> =
106
- Object.create(null);
107
- const clientRecord: Record<string, unknown> = client;
108
-
109
- contract.methods.forEach((method: Methods[number]) => {
110
- const caller: RpcCaller<typeof method> = (
111
- input?: RpcInput<typeof method>
112
- ) => {
113
- const payload = input ?? S.decodeUnknownSync(method.req)({});
114
- return call(method, payload);
115
- };
116
-
117
- clientRecord[method.name] = caller;
118
- });
119
-
120
- return client;
121
- };
122
-
123
- export const createEventSubscriber = <
124
- const Methods extends ReadonlyArray<AnyMethod>,
125
- const Events extends ReadonlyArray<AnyEvent>
126
- >(
127
- contract: RpcContract<Methods, Events>,
128
- options?: EventSubscriberOptions
129
- ): EventSubscriber<RpcContract<Methods, Events>> => {
130
- const subscribe = requireSubscribe(options);
131
- const eventMap = new Map<string, Events[number]>();
132
-
133
- for (const event of contract.events) {
134
- eventMap.set(event.name, event);
135
- }
136
-
137
- const subscribeEvent = <E extends Events[number]>(
138
- event: E,
139
- handler: (payload: RpcEventPayload<E>) => void
140
- ) => {
141
- const decoder = S.decodeUnknownSync(event.payload);
142
- return subscribe(event.name, (payload) => {
143
- const decoded = decoder(payload);
144
- handler(decoded);
145
- });
146
- };
147
-
148
- const subscribeByName = (
149
- name: Events[number]["name"],
150
- handler: (payload: unknown) => void
151
- ) => {
152
- const event = eventMap.get(name);
153
- if (!event) {
154
- throw new Error(`Unknown event: ${name}`);
155
- }
156
-
157
- const decoder = S.decodeUnknownSync(event.payload);
158
- return subscribe(name, (payload) => handler(decoder(payload)));
159
- };
160
-
161
- return {
162
- subscribe: subscribeEvent,
163
- subscribeByName,
164
- };
165
- };
166
-
167
- export { RpcDefectError } from "./types.ts";
package/src/testing.ts DELETED
@@ -1,33 +0,0 @@
1
- import type { RpcInvoke } from "./types.ts";
2
-
3
- export type Invocation = {
4
- readonly method: string;
5
- readonly payload: unknown;
6
- };
7
-
8
- export type InvokeStub = RpcInvoke & { readonly invocations: Invocation[] };
9
-
10
- export const createInvokeStub = (impl: RpcInvoke): InvokeStub => {
11
- const invocations: Invocation[] = [];
12
-
13
- const wrapped = Object.assign(
14
- async (method: string, payload: unknown) => {
15
- invocations.push({ method, payload });
16
- return impl(method, payload);
17
- },
18
- { invocations }
19
- );
20
-
21
- return wrapped;
22
- };
23
-
24
- export const createDeferred = <T>() => {
25
- let resolve: (value: T | PromiseLike<T>) => void = () => {};
26
- let reject: (reason?: unknown) => void = () => {};
27
- const promise = new Promise<T>((res, rej) => {
28
- resolve = res;
29
- reject = rej;
30
- });
31
-
32
- return { promise, resolve, reject };
33
- };
package/src/types.ts DELETED
@@ -1,134 +0,0 @@
1
- import type * as Effect from "effect/Effect";
2
- import type * as Runtime from "effect/Runtime";
3
- import type { BrowserWindow } from "electron";
4
- import type {
5
- AnyEvent,
6
- AnyMethod,
7
- ErrorSchema,
8
- ExtractMethod,
9
- RpcContract,
10
- RpcError,
11
- RpcEvent,
12
- RpcEventPayload,
13
- RpcInput,
14
- RpcMethod,
15
- RpcOutput,
16
- SchemaNoContext,
17
- } from "./contract.ts";
18
-
19
- export type {
20
- AnyEvent,
21
- AnyMethod,
22
- ErrorSchema,
23
- ExtractMethod,
24
- RpcContract,
25
- RpcError,
26
- RpcEvent,
27
- RpcEventPayload,
28
- RpcInput,
29
- RpcMethod,
30
- RpcOutput,
31
- SchemaNoContext,
32
- } from "./contract.ts";
33
-
34
- export type Implementations<
35
- C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>,
36
- R = never
37
- > = {
38
- readonly [Name in C["methods"][number]["name"]]: (
39
- input: RpcInput<ExtractMethod<C["methods"], Name>>
40
- ) => Effect.Effect<
41
- RpcOutput<ExtractMethod<C["methods"], Name>>,
42
- RpcError<ExtractMethod<C["methods"], Name>>,
43
- R
44
- >;
45
- };
46
-
47
- type IsEmptyObject<T> = keyof T extends never ? true : false;
48
-
49
- export type RpcCaller<M extends AnyMethod> =
50
- IsEmptyObject<RpcInput<M>> extends true
51
- ? () => Promise<RpcOutput<M>>
52
- : (input: RpcInput<M>) => Promise<RpcOutput<M>>;
53
-
54
- export type RpcClient<
55
- C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>
56
- > = {
57
- readonly [Name in C["methods"][number]["name"]]: RpcCaller<
58
- ExtractMethod<C["methods"], Name>
59
- >;
60
- };
61
-
62
- export class RpcDefectError extends Error {
63
- readonly _tag = "RpcDefectError";
64
-
65
- constructor(
66
- message: string,
67
- public readonly cause: unknown
68
- ) {
69
- super(message);
70
- this.name = "RpcDefectError";
71
- }
72
- }
73
-
74
- export type ChannelPrefix = {
75
- readonly rpc: string;
76
- readonly event: string;
77
- };
78
-
79
- export const defaultChannelPrefix: ChannelPrefix = {
80
- rpc: "rpc/",
81
- event: "event/",
82
- };
83
-
84
- export type IpcMainLike = {
85
- readonly handle: (
86
- channel: string,
87
- listener: (event: unknown, payload: unknown) => unknown
88
- ) => unknown;
89
- };
90
-
91
- export type RpcInvoke = (method: string, payload: unknown) => Promise<unknown>;
92
-
93
- /** Provide a Runtime when handlers require services (R). */
94
- export type RpcServerOptions<R = never> = {
95
- readonly channelPrefix?: ChannelPrefix;
96
- readonly runtime?: Runtime.Runtime<R>;
97
- };
98
-
99
- export type RpcClientOptions = {
100
- readonly invoke?: RpcInvoke;
101
- readonly channelPrefix?: ChannelPrefix;
102
- };
103
-
104
- export type EventBusOptions = {
105
- readonly channelPrefix?: ChannelPrefix;
106
- readonly getWindow: () => BrowserWindow | null;
107
- };
108
-
109
- export type EventSubscriberOptions = {
110
- readonly channelPrefix?: ChannelPrefix;
111
- readonly subscribe?: (name: string, handler: (payload: unknown) => void) => () => void;
112
- };
113
-
114
- export interface RpcEventBus<
115
- C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>
116
- > {
117
- readonly emit: <E extends C["events"][number]>(
118
- event: E,
119
- payload: RpcEventPayload<E>
120
- ) => Effect.Effect<void, never>;
121
- }
122
-
123
- export interface EventSubscriber<
124
- C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>
125
- > {
126
- readonly subscribe: <E extends C["events"][number]>(
127
- event: E,
128
- handler: (payload: RpcEventPayload<E>) => void
129
- ) => () => void;
130
- readonly subscribeByName: (
131
- name: C["events"][number]["name"],
132
- handler: (payload: unknown) => void
133
- ) => () => void;
134
- }