@igoforth/ws-rpc 1.0.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/LICENSE +21 -0
- package/README.md +446 -0
- package/dist/adapters/client.d.ts +117 -0
- package/dist/adapters/client.js +241 -0
- package/dist/adapters/cloudflare-do.d.ts +72 -0
- package/dist/adapters/cloudflare-do.js +192 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +16 -0
- package/dist/adapters/server.d.ts +10 -0
- package/dist/adapters/server.js +122 -0
- package/dist/adapters/types.d.ts +125 -0
- package/dist/adapters/types.js +3 -0
- package/dist/codecs/cbor.d.ts +16 -0
- package/dist/codecs/cbor.js +36 -0
- package/dist/codecs/factory.d.ts +3 -0
- package/dist/codecs/factory.js +3 -0
- package/dist/codecs/index.d.ts +5 -0
- package/dist/codecs/index.js +5 -0
- package/dist/codecs/json.d.ts +4 -0
- package/dist/codecs/json.js +4 -0
- package/dist/codecs/msgpack.d.ts +16 -0
- package/dist/codecs/msgpack.js +34 -0
- package/dist/codecs-BmYG2d_U.js +0 -0
- package/dist/default-BkrMd28n.js +253 -0
- package/dist/default-xDNNMrg0.d.ts +129 -0
- package/dist/durable-MZjkvyS6.js +165 -0
- package/dist/errors-5BfreE63.js +96 -0
- package/dist/errors.d.ts +69 -0
- package/dist/errors.js +7 -0
- package/dist/factory-3ziwTuZe.js +132 -0
- package/dist/factory-C1v0AEHY.d.ts +101 -0
- package/dist/index-Be7jjS77.d.ts +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/interface-C4S-WCqW.d.ts +120 -0
- package/dist/json-54Z2bIIs.d.ts +22 -0
- package/dist/json-Bshec-bZ.js +41 -0
- package/dist/memory-Bqb3KEVr.js +48 -0
- package/dist/memory-D1nGjzzH.d.ts +41 -0
- package/dist/multi-peer-BAi9yVzp.js +242 -0
- package/dist/peers/default.d.ts +8 -0
- package/dist/peers/default.js +8 -0
- package/dist/peers/durable.d.ts +136 -0
- package/dist/peers/durable.js +9 -0
- package/dist/peers/index.d.ts +10 -0
- package/dist/peers/index.js +9 -0
- package/dist/protocol-DA84zrc2.d.ts +211 -0
- package/dist/protocol-_mpoOPp6.js +192 -0
- package/dist/protocol.d.ts +6 -0
- package/dist/protocol.js +6 -0
- package/dist/reconnect-CGAA_1Gf.js +26 -0
- package/dist/reconnect-DbcN0R_1.d.ts +35 -0
- package/dist/schema-CN5HHHku.d.ts +108 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +43 -0
- package/dist/server-zTjpJpoX.d.ts +209 -0
- package/dist/sql-CCjc6Bid.js +142 -0
- package/dist/sql-DPmHOeZy.d.ts +131 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.js +7 -0
- package/dist/storage/interface.d.ts +3 -0
- package/dist/storage/interface.js +0 -0
- package/dist/storage/memory.d.ts +7 -0
- package/dist/storage/memory.js +6 -0
- package/dist/storage/sql.d.ts +7 -0
- package/dist/storage/sql.js +6 -0
- package/dist/types-Be-qmQu0.d.ts +111 -0
- package/dist/types-D_psiH09.js +13 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +3 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/reconnect.d.ts +2 -0
- package/dist/utils/reconnect.js +3 -0
- package/package.json +156 -0
- package/src/adapters/client.ts +396 -0
- package/src/adapters/cloudflare-do.ts +346 -0
- package/src/adapters/index.ts +16 -0
- package/src/adapters/multi-peer.ts +404 -0
- package/src/adapters/server.ts +192 -0
- package/src/adapters/types.ts +202 -0
- package/src/codecs/cbor.ts +42 -0
- package/src/codecs/factory.ts +210 -0
- package/src/codecs/index.ts +30 -0
- package/src/codecs/json.ts +42 -0
- package/src/codecs/msgpack.ts +36 -0
- package/src/errors.ts +105 -0
- package/src/index.ts +102 -0
- package/src/peers/default.ts +433 -0
- package/src/peers/durable.ts +280 -0
- package/src/peers/index.ts +13 -0
- package/src/protocol.ts +306 -0
- package/src/schema.ts +167 -0
- package/src/storage/index.ts +20 -0
- package/src/storage/interface.ts +146 -0
- package/src/storage/memory.ts +84 -0
- package/src/storage/sql.ts +266 -0
- package/src/types.ts +158 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/reconnect.ts +51 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Durable Object RPC Adapter
|
|
3
|
+
*
|
|
4
|
+
* Mixin for @cloudflare/actors that adds RPC capabilities to Durable Objects.
|
|
5
|
+
* Manages RPC peers for each WebSocket connection with hibernation support.
|
|
6
|
+
*
|
|
7
|
+
* ## Usage
|
|
8
|
+
*
|
|
9
|
+
* The mixin requires that your class implements the methods defined in the
|
|
10
|
+
* local schema. TypeScript will enforce this at compile time.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* class MyDO extends withRpc(Actor, {
|
|
14
|
+
* localSchema: SignalSchema,
|
|
15
|
+
* remoteSchema: FilterSchema,
|
|
16
|
+
* }) {
|
|
17
|
+
* // Required: implement methods from SignalSchema
|
|
18
|
+
* async getWallets() {
|
|
19
|
+
* return { wallets: this.wallets };
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // Call methods on connected clients via driver
|
|
23
|
+
* async notifyClients() {
|
|
24
|
+
* // Call all connected peers
|
|
25
|
+
* const results = await this.driver.someClientMethod({});
|
|
26
|
+
*
|
|
27
|
+
* // Or call specific peer with timeout
|
|
28
|
+
* const results = await this.driver.someClientMethod({}, {
|
|
29
|
+
* ids: "peer-id",
|
|
30
|
+
* timeout: 5000,
|
|
31
|
+
* });
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ## Hibernation Handling
|
|
37
|
+
*
|
|
38
|
+
* When a DO hibernates, all in-memory state is lost but WebSocket connections
|
|
39
|
+
* remain open. This adapter handles hibernation by lazily recreating RpcPeer
|
|
40
|
+
* instances when messages arrive on connections that were established before
|
|
41
|
+
* hibernation.
|
|
42
|
+
*
|
|
43
|
+
* For hibernation-safe outgoing calls, use DurableRpcPeer which persists
|
|
44
|
+
* pending calls to durable storage.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import type { Actor } from "@cloudflare/actors";
|
|
48
|
+
import type { Constructor } from "type-fest";
|
|
49
|
+
import { RpcPeer } from "../peers/default.js";
|
|
50
|
+
import {
|
|
51
|
+
createDurableRpcPeerFactory,
|
|
52
|
+
DurableRpcPeer,
|
|
53
|
+
type DurableRpcPeerOptions,
|
|
54
|
+
} from "../peers/durable.js";
|
|
55
|
+
import type {
|
|
56
|
+
EventDef,
|
|
57
|
+
InferEventData,
|
|
58
|
+
Provider,
|
|
59
|
+
RpcSchema,
|
|
60
|
+
StringKeys,
|
|
61
|
+
} from "../schema.js";
|
|
62
|
+
import { SqlPendingCallStorage } from "../storage/sql.js";
|
|
63
|
+
import type { IRpcOptions } from "../types.js";
|
|
64
|
+
import { MultiPeerBase, type MultiPeerOptions } from "./multi-peer.js";
|
|
65
|
+
import type { IMultiAdapterHooks, IMultiConnectionAdapter } from "./types.js";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extended hooks for Durable Object adapter
|
|
69
|
+
*/
|
|
70
|
+
export interface IDOHooks<
|
|
71
|
+
TLocalSchema extends RpcSchema,
|
|
72
|
+
TRemoteSchema extends RpcSchema,
|
|
73
|
+
> extends IMultiAdapterHooks<TLocalSchema, TRemoteSchema> {
|
|
74
|
+
/** Called when a peer is recreated after hibernation */
|
|
75
|
+
onPeerRecreated?(
|
|
76
|
+
peer: RpcPeer<TLocalSchema, TRemoteSchema>,
|
|
77
|
+
ws: WebSocket,
|
|
78
|
+
): void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Concrete MultiPeerBase for Durable Objects using native WebSocket
|
|
83
|
+
*/
|
|
84
|
+
class DOMultiPeer<
|
|
85
|
+
TLocalSchema extends RpcSchema,
|
|
86
|
+
TRemoteSchema extends RpcSchema,
|
|
87
|
+
TActor,
|
|
88
|
+
> extends MultiPeerBase<TLocalSchema, TRemoteSchema, WebSocket> {
|
|
89
|
+
public override readonly hooks: IDOHooks<TLocalSchema, TRemoteSchema>;
|
|
90
|
+
private readonly _createPeer;
|
|
91
|
+
|
|
92
|
+
constructor(
|
|
93
|
+
options: MultiPeerOptions<TLocalSchema, TRemoteSchema> &
|
|
94
|
+
DurableRpcPeerOptions<TActor> & {
|
|
95
|
+
hooks?: IDOHooks<TLocalSchema, TRemoteSchema>;
|
|
96
|
+
},
|
|
97
|
+
) {
|
|
98
|
+
super(options);
|
|
99
|
+
this.hooks = options.hooks ?? {};
|
|
100
|
+
this._createPeer = createDurableRpcPeerFactory({
|
|
101
|
+
actor: options.actor,
|
|
102
|
+
storage: options.storage,
|
|
103
|
+
...(options.durableTimeout != null && {
|
|
104
|
+
durableTimeout: options.durableTimeout,
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create an RPC peer for a WebSocket connection.
|
|
111
|
+
* Override to use the actor instance as provider via closure.
|
|
112
|
+
*/
|
|
113
|
+
public createPeerWithProvider(
|
|
114
|
+
ws: WebSocket,
|
|
115
|
+
provider: Provider<TLocalSchema>,
|
|
116
|
+
): DurableRpcPeer<TLocalSchema, TRemoteSchema, TActor> {
|
|
117
|
+
const peer = this._createPeer({
|
|
118
|
+
ws,
|
|
119
|
+
localSchema: this.localSchema,
|
|
120
|
+
remoteSchema: this.remoteSchema,
|
|
121
|
+
provider,
|
|
122
|
+
onEvent: (event, data) => {
|
|
123
|
+
this.hooks.onEvent?.(peer, event, data);
|
|
124
|
+
},
|
|
125
|
+
timeout: this.timeout,
|
|
126
|
+
});
|
|
127
|
+
return peer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get or create RPC peer for a WebSocket
|
|
132
|
+
* Handles lazy recreation after hibernation.
|
|
133
|
+
*/
|
|
134
|
+
public getOrCreatePeer(
|
|
135
|
+
ws: WebSocket,
|
|
136
|
+
provider: Provider<TLocalSchema>,
|
|
137
|
+
isHibernationRecovery = false,
|
|
138
|
+
): RpcPeer<TLocalSchema, TRemoteSchema> {
|
|
139
|
+
let peer = this.getPeerFor(ws);
|
|
140
|
+
|
|
141
|
+
if (!peer) {
|
|
142
|
+
peer = this.createPeerWithProvider(ws, provider);
|
|
143
|
+
this.addPeer(ws, peer);
|
|
144
|
+
|
|
145
|
+
if (isHibernationRecovery) {
|
|
146
|
+
this.hooks.onPeerRecreated?.(peer, ws);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return peer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Connect a new WebSocket and create its peer
|
|
155
|
+
*/
|
|
156
|
+
public connectPeer(
|
|
157
|
+
ws: WebSocket,
|
|
158
|
+
provider: Provider<TLocalSchema>,
|
|
159
|
+
): RpcPeer<TLocalSchema, TRemoteSchema> {
|
|
160
|
+
const peer = this.createPeerWithProvider(ws, provider);
|
|
161
|
+
this.addPeer(ws, peer);
|
|
162
|
+
return peer;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Disconnect a WebSocket and remove its peer
|
|
167
|
+
*/
|
|
168
|
+
public disconnectPeer(ws: WebSocket): void {
|
|
169
|
+
this.removePeer(ws);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Handle an error on a WebSocket
|
|
174
|
+
*/
|
|
175
|
+
public handleError(ws: WebSocket, error: Error): void {
|
|
176
|
+
const peer = this.getPeerFor(ws);
|
|
177
|
+
if (peer) {
|
|
178
|
+
peer.close();
|
|
179
|
+
this.removePeer(ws);
|
|
180
|
+
}
|
|
181
|
+
this.hooks.onError?.(peer ?? null, error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Constructor type for the RPC mixin result.
|
|
187
|
+
*
|
|
188
|
+
* Subclasses must implement methods from TLocalSchema on `this`.
|
|
189
|
+
* Runtime enforces this when methods are called via RPC.
|
|
190
|
+
*/
|
|
191
|
+
export type RpcActorConstructor<
|
|
192
|
+
TBase extends Constructor<Actor<unknown>>,
|
|
193
|
+
TLocalSchema extends RpcSchema,
|
|
194
|
+
TRemoteSchema extends RpcSchema,
|
|
195
|
+
> = {
|
|
196
|
+
new (
|
|
197
|
+
...args: ConstructorParameters<TBase>
|
|
198
|
+
): InstanceType<TBase> & IMultiConnectionAdapter<TLocalSchema, TRemoteSchema>;
|
|
199
|
+
} & Omit<TBase, "new">;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a mixin that adds RPC capabilities to a Durable Object Actor.
|
|
203
|
+
*
|
|
204
|
+
* The resulting class requires implementation of all methods defined in
|
|
205
|
+
* `localSchema`. TypeScript enforces this at compile time.
|
|
206
|
+
*
|
|
207
|
+
* @param Base - The Actor class to extend
|
|
208
|
+
* @param options - RPC configuration including local/remote schemas and timeout
|
|
209
|
+
* @returns A new class with RPC capabilities mixed in
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* const ServerSchema = {
|
|
214
|
+
* methods: {
|
|
215
|
+
* getData: method({
|
|
216
|
+
* input: z.object({}),
|
|
217
|
+
* output: z.object({ data: z.array(z.string()) }),
|
|
218
|
+
* }),
|
|
219
|
+
* },
|
|
220
|
+
* events: {},
|
|
221
|
+
* } as const;
|
|
222
|
+
*
|
|
223
|
+
* class MyDO extends withRpc(Actor, {
|
|
224
|
+
* localSchema: ServerSchema,
|
|
225
|
+
* remoteSchema: ClientSchema,
|
|
226
|
+
* }) {
|
|
227
|
+
* // Required: implement methods from ServerSchema
|
|
228
|
+
* async getData() {
|
|
229
|
+
* return { data: this.dataList };
|
|
230
|
+
* }
|
|
231
|
+
*
|
|
232
|
+
* // Call methods on connected clients
|
|
233
|
+
* async notifyClients() {
|
|
234
|
+
* const results = await this.driver.clientMethod({ info: "update" });
|
|
235
|
+
* }
|
|
236
|
+
* }
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
export function withRpc<
|
|
240
|
+
TLocalSchema extends RpcSchema,
|
|
241
|
+
TRemoteSchema extends RpcSchema,
|
|
242
|
+
TEnv,
|
|
243
|
+
TBase extends Constructor<Actor<TEnv>> & {
|
|
244
|
+
prototype: Provider<TLocalSchema>;
|
|
245
|
+
},
|
|
246
|
+
>(
|
|
247
|
+
Base: TBase,
|
|
248
|
+
options: IRpcOptions<TLocalSchema, TRemoteSchema>,
|
|
249
|
+
): RpcActorConstructor<TBase, TLocalSchema, TRemoteSchema> {
|
|
250
|
+
// @ts-expect-error - TypeScript can't verify the anonymous class satisfies RpcActorConstructor
|
|
251
|
+
return class RpcActor extends Base {
|
|
252
|
+
private __rpc: DOMultiPeer<TLocalSchema, TRemoteSchema, this> | null = null;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Internal RPC manager (lazily initialized)
|
|
256
|
+
*
|
|
257
|
+
* Handles peer management, message routing, and durable storage.
|
|
258
|
+
* Uses SQL storage from the Durable Object for hibernation-safe calls.
|
|
259
|
+
*
|
|
260
|
+
* @throws Error if DurableObjectStorage is not available
|
|
261
|
+
*/
|
|
262
|
+
get _rpc() {
|
|
263
|
+
if (!this.storage.raw)
|
|
264
|
+
throw new Error("DurableObjectStorage not present in actor `raw`");
|
|
265
|
+
return (this.__rpc ??= new DOMultiPeer({
|
|
266
|
+
actor: this,
|
|
267
|
+
storage: new SqlPendingCallStorage(this.storage.raw.sql),
|
|
268
|
+
localSchema: options.localSchema,
|
|
269
|
+
remoteSchema: options.remoteSchema,
|
|
270
|
+
provider: this as Provider<TLocalSchema>,
|
|
271
|
+
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Driver for calling methods on connected clients
|
|
277
|
+
*/
|
|
278
|
+
get driver() {
|
|
279
|
+
return this._rpc.driver;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Emit an event to connected clients
|
|
284
|
+
*
|
|
285
|
+
* @param event - Event name from local schema
|
|
286
|
+
* @param data - Event data matching the schema
|
|
287
|
+
* @param ids - Optional array of peer IDs to emit to (broadcasts to all if omitted)
|
|
288
|
+
*/
|
|
289
|
+
public emit<K extends StringKeys<TLocalSchema["events"]>>(
|
|
290
|
+
event: K,
|
|
291
|
+
data: TLocalSchema["events"] extends Record<string, EventDef>
|
|
292
|
+
? InferEventData<TLocalSchema["events"][K]>
|
|
293
|
+
: never,
|
|
294
|
+
ids?: string[],
|
|
295
|
+
): void {
|
|
296
|
+
this._rpc.emit(event, data, ids);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get the number of connected peers
|
|
301
|
+
*/
|
|
302
|
+
public getConnectionCount() {
|
|
303
|
+
return this._rpc.getConnectionCount();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get the IDs of all connected peers
|
|
308
|
+
*/
|
|
309
|
+
public getConnectionIds() {
|
|
310
|
+
return this._rpc.getConnectionIds();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// =========================================================================
|
|
314
|
+
// Actor WebSocket Hooks (DO-specific, required by Actor class)
|
|
315
|
+
// =========================================================================
|
|
316
|
+
|
|
317
|
+
/** Called by Actor when WebSocket connects */
|
|
318
|
+
protected onWebSocketConnect(ws: WebSocket, _request: Request): void {
|
|
319
|
+
this._rpc.connectPeer(ws, this as Provider<TLocalSchema>);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Called by Actor when WebSocket message received (handles hibernation recovery) */
|
|
323
|
+
protected onWebSocketMessage(
|
|
324
|
+
ws: WebSocket,
|
|
325
|
+
message: ArrayBuffer | string,
|
|
326
|
+
): void {
|
|
327
|
+
const existingPeer = this._rpc.getPeerFor(ws);
|
|
328
|
+
const peer = this._rpc.getOrCreatePeer(
|
|
329
|
+
ws,
|
|
330
|
+
this as Provider<TLocalSchema>,
|
|
331
|
+
!existingPeer,
|
|
332
|
+
);
|
|
333
|
+
peer.handleMessage(message);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Called by Actor when WebSocket disconnects */
|
|
337
|
+
protected onWebSocketDisconnect(ws: WebSocket): void {
|
|
338
|
+
this._rpc.disconnectPeer(ws);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Called by Actor when WebSocket error occurs */
|
|
342
|
+
protected onWebSocketError(ws: WebSocket, error: Error): void {
|
|
343
|
+
this._rpc.handleError(ws, error);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter Exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
type ConnectionState,
|
|
7
|
+
RpcClient,
|
|
8
|
+
type RpcClientOptions,
|
|
9
|
+
} from "./client.js";
|
|
10
|
+
export {
|
|
11
|
+
type RpcActorConstructor,
|
|
12
|
+
withRpc,
|
|
13
|
+
} from "./cloudflare-do.js";
|
|
14
|
+
export { MultiPeerBase } from "./multi-peer.js";
|
|
15
|
+
export { RpcServer, type RpcServerOptions } from "./server.js";
|
|
16
|
+
export * from "./types.js";
|