@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,125 @@
|
|
|
1
|
+
import { f as MethodDef, h as StringKeys, i as EventHandler, l as InferOutput, m as RpcSchema, s as InferInput, t as Driver } from "../schema-CN5HHHku.js";
|
|
2
|
+
import "../factory-C1v0AEHY.js";
|
|
3
|
+
import "../json-54Z2bIIs.js";
|
|
4
|
+
import "../index-Be7jjS77.js";
|
|
5
|
+
import "../protocol-DA84zrc2.js";
|
|
6
|
+
import { a as IRpcOptions, n as IMethodController, t as IEventController } from "../types-Be-qmQu0.js";
|
|
7
|
+
import { t as RpcPeer } from "../default-xDNNMrg0.js";
|
|
8
|
+
import { n as calculateReconnectDelay, r as defaultReconnectOptions, t as ReconnectOptions } from "../reconnect-DbcN0R_1.js";
|
|
9
|
+
|
|
10
|
+
//#region src/adapters/types.d.ts
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for driver method calls on multi-connection adapters
|
|
14
|
+
*/
|
|
15
|
+
interface MultiCallOptions {
|
|
16
|
+
/** Connection ID(s) to call. If omitted, calls all connections. */
|
|
17
|
+
ids?: string | string[];
|
|
18
|
+
/** Request timeout in milliseconds */
|
|
19
|
+
timeout?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Result of a driver method call on a multi-connection adapter
|
|
23
|
+
*
|
|
24
|
+
* Each result contains the peer ID and either a success value or an error.
|
|
25
|
+
*
|
|
26
|
+
* @typeParam T - The expected return type of the remote method
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const results = await server.driver.ping({});
|
|
31
|
+
* for (const { id, result } of results) {
|
|
32
|
+
* if (result.success) {
|
|
33
|
+
* console.log(`Peer ${id} responded:`, result.value);
|
|
34
|
+
* } else {
|
|
35
|
+
* console.error(`Peer ${id} failed:`, result.error);
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
type MultiCallResult<T> = {
|
|
41
|
+
/** Peer ID that this result is from */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Success or failure result */
|
|
44
|
+
result: {
|
|
45
|
+
success: true;
|
|
46
|
+
value: T;
|
|
47
|
+
} | {
|
|
48
|
+
success: false;
|
|
49
|
+
error: Error;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Driver type for multi-connection adapters
|
|
54
|
+
*
|
|
55
|
+
* Methods accept an optional second argument with `ids` and `timeout`.
|
|
56
|
+
* Returns an array of results, one per connection called.
|
|
57
|
+
*
|
|
58
|
+
* @typeParam TRemoteSchema - Schema defining the remote methods available
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* // Call all connected peers
|
|
63
|
+
* const allResults = await server.driver.getData({});
|
|
64
|
+
*
|
|
65
|
+
* // Call specific peer by ID
|
|
66
|
+
* const oneResult = await server.driver.getData({}, { ids: "peer-123" });
|
|
67
|
+
*
|
|
68
|
+
* // Call multiple peers with timeout
|
|
69
|
+
* const someResults = await server.driver.getData({}, {
|
|
70
|
+
* ids: ["peer-1", "peer-2"],
|
|
71
|
+
* timeout: 5000,
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
type MultiDriver<TRemoteSchema extends RpcSchema> = TRemoteSchema["methods"] extends Record<string, MethodDef> ? { [K in StringKeys<TRemoteSchema["methods"]>]: <O extends MultiCallOptions>(input: TRemoteSchema["methods"] extends Record<string, MethodDef> ? InferInput<TRemoteSchema["methods"][K]> : never, options?: O) => Promise<O extends {
|
|
76
|
+
ids: infer I;
|
|
77
|
+
} ? string extends I ? MultiCallResult<TRemoteSchema["methods"] extends Record<string, MethodDef> ? InferOutput<TRemoteSchema["methods"][K]> : never> : Array<MultiCallResult<TRemoteSchema["methods"] extends Record<string, MethodDef> ? InferOutput<TRemoteSchema["methods"][K]> : never>> : never> } : never;
|
|
78
|
+
/**
|
|
79
|
+
* Event emitter type for client callbacks
|
|
80
|
+
*/
|
|
81
|
+
interface IAdapterHooks<TRemoteSchema extends RpcSchema> {
|
|
82
|
+
/** Called when WebSocket connection opens */
|
|
83
|
+
onConnect?(): void;
|
|
84
|
+
/** Called when WebSocket connection closes */
|
|
85
|
+
onDisconnect?(code: number, reason: string): void;
|
|
86
|
+
/** Called when attempting to reconnect */
|
|
87
|
+
onReconnect?(attempt: number, delay: number): void;
|
|
88
|
+
/** Called when reconnection fails after max attempts */
|
|
89
|
+
onReconnectFailed?(): void;
|
|
90
|
+
/** Called when receiving an event from the server */
|
|
91
|
+
onEvent?: EventHandler<TRemoteSchema>;
|
|
92
|
+
}
|
|
93
|
+
interface IMultiAdapterHooks<TLocalSchema extends RpcSchema, TRemoteSchema extends RpcSchema> {
|
|
94
|
+
/** Called when a peer connects */
|
|
95
|
+
onConnect?(peer: RpcPeer<TLocalSchema, TRemoteSchema>): void;
|
|
96
|
+
/** Called when a peer disconnects */
|
|
97
|
+
onDisconnect?(peer: RpcPeer<TLocalSchema, TRemoteSchema>): void;
|
|
98
|
+
/** Called when an event is received from a peer */
|
|
99
|
+
onEvent?: EventHandler<TRemoteSchema, [peer: RpcPeer<TLocalSchema, TRemoteSchema>]>;
|
|
100
|
+
/** Called when a peer encounters an error */
|
|
101
|
+
onError?(peer: RpcPeer<TLocalSchema, TRemoteSchema> | null, error: Error): void;
|
|
102
|
+
onClose?(): void;
|
|
103
|
+
}
|
|
104
|
+
interface IConnectionAdapter<TLocalSchema extends RpcSchema, TRemoteSchema extends RpcSchema> extends IRpcOptions<TLocalSchema, TRemoteSchema>, IMethodController<TLocalSchema>, IEventController<TLocalSchema, TRemoteSchema> {
|
|
105
|
+
/** Driver for calling remote methods on connected peer */
|
|
106
|
+
readonly driver: Driver<TRemoteSchema>;
|
|
107
|
+
readonly hooks: IAdapterHooks<TRemoteSchema>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Interface for adapters that manage multiple connections
|
|
111
|
+
*
|
|
112
|
+
* Extends IRpcConnection - `emit()` broadcasts to all connected peers.
|
|
113
|
+
* Implemented by RpcServer and Cloudflare DO adapter.
|
|
114
|
+
*/
|
|
115
|
+
interface IMultiConnectionAdapter<TLocalSchema extends RpcSchema, TRemoteSchema extends RpcSchema> extends IRpcOptions<TLocalSchema, TRemoteSchema>, IMethodController<TLocalSchema>, IEventController<TLocalSchema, TRemoteSchema, [ids?: string[]]> {
|
|
116
|
+
/** Driver for calling remote methods on connected peers */
|
|
117
|
+
readonly driver: MultiDriver<TRemoteSchema>;
|
|
118
|
+
readonly hooks: IMultiAdapterHooks<TLocalSchema, TRemoteSchema>;
|
|
119
|
+
/** Get count of connected peers */
|
|
120
|
+
getConnectionCount(): number;
|
|
121
|
+
/** Get all connected peer IDs */
|
|
122
|
+
getConnectionIds(): string[];
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { IAdapterHooks, IConnectionAdapter, IMultiAdapterHooks, IMultiConnectionAdapter, MultiCallOptions, MultiCallResult, MultiDriver, type ReconnectOptions, calculateReconnectDelay, defaultReconnectOptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "../schema-CN5HHHku.js";
|
|
2
|
+
import { n as CodecFactory } from "../factory-C1v0AEHY.js";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/codecs/cbor.d.ts
|
|
6
|
+
|
|
7
|
+
declare const createCborCodec: CodecFactory<Uint8Array<ArrayBuffer>>;
|
|
8
|
+
/**
|
|
9
|
+
* Default CBOR codec for unknown values
|
|
10
|
+
*
|
|
11
|
+
* Use when you need to serialize arbitrary data without schema validation.
|
|
12
|
+
* The decoded value will be `unknown` and should be validated separately.
|
|
13
|
+
*/
|
|
14
|
+
declare const CborCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, unknown>, z.ZodUnknown>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CborCodec, createCborCodec };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { t as createBinaryCodecFactory } from "../factory-3ziwTuZe.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { Decoder, Encoder } from "cbor-x";
|
|
4
|
+
|
|
5
|
+
//#region src/codecs/cbor.ts
|
|
6
|
+
/**
|
|
7
|
+
* CBOR codec using cbor-x
|
|
8
|
+
*
|
|
9
|
+
* Requires optional peer dependency: cbor-x
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { createCborCodec, CborCodec } from "@igoforth/ws-rpc/codecs/cbor";
|
|
14
|
+
*
|
|
15
|
+
* // Create a typed codec
|
|
16
|
+
* const MyDataCodec = createCborCodec(MyDataSchema);
|
|
17
|
+
* const bytes = MyDataCodec.encode(data); // Uint8Array
|
|
18
|
+
* const decoded = MyDataCodec.decode(bytes); // validated MyData
|
|
19
|
+
*
|
|
20
|
+
* // Or use the generic codec
|
|
21
|
+
* const bytes = CborCodec.encode({ any: "data" });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
const decode = (data) => new Decoder({ bundleStrings: true }).decode(data);
|
|
25
|
+
const encode = (data) => new Encoder({ bundleStrings: true }).encode(data);
|
|
26
|
+
const createCborCodec = createBinaryCodecFactory((value) => encode(value), (bytes) => decode(bytes), "cbor");
|
|
27
|
+
/**
|
|
28
|
+
* Default CBOR codec for unknown values
|
|
29
|
+
*
|
|
30
|
+
* Use when you need to serialize arbitrary data without schema validation.
|
|
31
|
+
* The decoded value will be `unknown` and should be validated separately.
|
|
32
|
+
*/
|
|
33
|
+
const CborCodec = createCborCodec(z.unknown());
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { CborCodec, createCborCodec };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import "../schema-CN5HHHku.js";
|
|
2
|
+
import { a as WireCodec, c as createStringCodecFactory, i as StringCodec, l as isBinaryCodec, n as CodecFactory, o as WireData, r as CodecOptions, s as createBinaryCodecFactory, t as BinaryCodec, u as isStringCodec } from "../factory-C1v0AEHY.js";
|
|
3
|
+
export { BinaryCodec, CodecFactory, CodecOptions, StringCodec, WireCodec, WireData, createBinaryCodecFactory, createStringCodecFactory, isBinaryCodec, isStringCodec };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import "../schema-CN5HHHku.js";
|
|
2
|
+
import { a as WireCodec, c as createStringCodecFactory, i as StringCodec, l as isBinaryCodec, n as CodecFactory, o as WireData, r as CodecOptions, s as createBinaryCodecFactory, t as BinaryCodec, u as isStringCodec } from "../factory-C1v0AEHY.js";
|
|
3
|
+
import { n as createJsonCodec, t as JsonCodec } from "../json-54Z2bIIs.js";
|
|
4
|
+
import "../index-Be7jjS77.js";
|
|
5
|
+
export { BinaryCodec, CodecFactory, CodecOptions, JsonCodec, StringCodec, WireCodec, WireData, createBinaryCodecFactory, createJsonCodec, createStringCodecFactory, isBinaryCodec, isStringCodec };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { i as isStringCodec, n as createStringCodecFactory, r as isBinaryCodec, t as createBinaryCodecFactory } from "../factory-3ziwTuZe.js";
|
|
2
|
+
import { n as createJsonCodec, t as JsonCodec } from "../json-Bshec-bZ.js";
|
|
3
|
+
import "../codecs-BmYG2d_U.js";
|
|
4
|
+
|
|
5
|
+
export { JsonCodec, createBinaryCodecFactory, createJsonCodec, createStringCodecFactory, isBinaryCodec, isStringCodec };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "../schema-CN5HHHku.js";
|
|
2
|
+
import { n as CodecFactory } from "../factory-C1v0AEHY.js";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/codecs/msgpack.d.ts
|
|
6
|
+
|
|
7
|
+
declare const createMsgpackCodec: CodecFactory<Uint8Array<ArrayBuffer>>;
|
|
8
|
+
/**
|
|
9
|
+
* Default MessagePack codec for unknown values
|
|
10
|
+
*
|
|
11
|
+
* Use when you need to serialize arbitrary data without schema validation.
|
|
12
|
+
* The decoded value will be `unknown` and should be validated separately.
|
|
13
|
+
*/
|
|
14
|
+
declare const MsgpackCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, unknown>, z.ZodUnknown>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { MsgpackCodec, createMsgpackCodec };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { t as createBinaryCodecFactory } from "../factory-3ziwTuZe.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { decode, encode } from "@msgpack/msgpack";
|
|
4
|
+
|
|
5
|
+
//#region src/codecs/msgpack.ts
|
|
6
|
+
/**
|
|
7
|
+
* MessagePack codec using @msgpack/msgpack
|
|
8
|
+
*
|
|
9
|
+
* Requires optional peer dependency: @msgpack/msgpack
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { createMsgpackCodec, MsgpackCodec } from "@igoforth/ws-rpc/codecs/msgpack";
|
|
14
|
+
*
|
|
15
|
+
* // Create a typed codec
|
|
16
|
+
* const MyDataCodec = createMsgpackCodec(MyDataSchema);
|
|
17
|
+
* const bytes = MyDataCodec.encode(data); // Uint8Array
|
|
18
|
+
* const decoded = MyDataCodec.decode(bytes); // validated MyData
|
|
19
|
+
*
|
|
20
|
+
* // Or use the generic codec
|
|
21
|
+
* const bytes = MsgpackCodec.encode({ any: "data" });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
const createMsgpackCodec = createBinaryCodecFactory((value) => encode(value), (bytes) => decode(bytes), "msgpack");
|
|
25
|
+
/**
|
|
26
|
+
* Default MessagePack codec for unknown values
|
|
27
|
+
*
|
|
28
|
+
* Use when you need to serialize arbitrary data without schema validation.
|
|
29
|
+
* The decoded value will be `unknown` and should be validated separately.
|
|
30
|
+
*/
|
|
31
|
+
const MsgpackCodec = createMsgpackCodec(z.unknown());
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { MsgpackCodec, createMsgpackCodec };
|
|
File without changes
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { n as RpcErrorCodes, t as JsonProtocol } from "./protocol-_mpoOPp6.js";
|
|
2
|
+
import { a as RpcTimeoutError, i as RpcRemoteError, o as RpcValidationError, r as RpcMethodNotFoundError, t as RpcConnectionClosed } from "./errors-5BfreE63.js";
|
|
3
|
+
import { v7 } from "uuid";
|
|
4
|
+
|
|
5
|
+
//#region src/peers/default.ts
|
|
6
|
+
/**
|
|
7
|
+
* RPC Peer
|
|
8
|
+
*
|
|
9
|
+
* Core bidirectional RPC implementation. Both client and server are "peers"
|
|
10
|
+
* that can call methods on each other.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Bidirectional RPC peer
|
|
14
|
+
*
|
|
15
|
+
* Both sides of a WebSocket connection are "peers" - they each implement
|
|
16
|
+
* some methods (provider) and can call methods on the other side (driver).
|
|
17
|
+
*/
|
|
18
|
+
var RpcPeer = class {
|
|
19
|
+
/** Unique identifier for this peer */
|
|
20
|
+
id;
|
|
21
|
+
/** WebSocket instance - protected for subclass access */
|
|
22
|
+
ws;
|
|
23
|
+
/** Protocol instance - protected for subclass access */
|
|
24
|
+
protocol;
|
|
25
|
+
localSchema;
|
|
26
|
+
remoteSchema;
|
|
27
|
+
provider;
|
|
28
|
+
onEventHandler;
|
|
29
|
+
defaultTimeout;
|
|
30
|
+
generateId;
|
|
31
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
32
|
+
requestCounter = 0;
|
|
33
|
+
closed = false;
|
|
34
|
+
/** Proxy for calling remote methods */
|
|
35
|
+
driver;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.id = options.id ?? v7();
|
|
38
|
+
this.ws = options.ws;
|
|
39
|
+
this.protocol = options.protocol ?? JsonProtocol;
|
|
40
|
+
this.localSchema = options.localSchema;
|
|
41
|
+
this.remoteSchema = options.remoteSchema;
|
|
42
|
+
this.provider = options.provider;
|
|
43
|
+
this.onEventHandler = options.onEvent;
|
|
44
|
+
this.defaultTimeout = options.timeout ?? 3e4;
|
|
45
|
+
this.generateId = options.generateId ?? (() => `${++this.requestCounter}`);
|
|
46
|
+
this.driver = this.createDriver();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a proxy that allows calling remote methods
|
|
50
|
+
*/
|
|
51
|
+
createDriver() {
|
|
52
|
+
const methods = this.remoteSchema.methods ?? {};
|
|
53
|
+
const driver = {};
|
|
54
|
+
for (const methodName of Object.keys(methods)) driver[methodName] = (input) => this.callMethod(methodName, input);
|
|
55
|
+
return driver;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Call a remote method and wait for the response (used by driver proxy)
|
|
59
|
+
*/
|
|
60
|
+
async callMethod(method, input, timeout) {
|
|
61
|
+
if (this.closed || this.ws.readyState !== 1) throw new RpcConnectionClosed();
|
|
62
|
+
const methodDef = this.remoteSchema.methods?.[method];
|
|
63
|
+
if (!methodDef) throw new RpcMethodNotFoundError(method);
|
|
64
|
+
const parseResult = methodDef.input.safeParse(input);
|
|
65
|
+
if (!parseResult.success) throw new RpcValidationError(`Invalid input for method '${method}'`, parseResult.error);
|
|
66
|
+
const id = this.generateId();
|
|
67
|
+
const timeoutMs = timeout ?? this.defaultTimeout;
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const timeoutHandle = setTimeout(() => {
|
|
70
|
+
this.pendingRequests.delete(id);
|
|
71
|
+
reject(new RpcTimeoutError(method, timeoutMs));
|
|
72
|
+
}, timeoutMs);
|
|
73
|
+
this.pendingRequests.set(id, {
|
|
74
|
+
resolve,
|
|
75
|
+
reject,
|
|
76
|
+
timeout: timeoutHandle,
|
|
77
|
+
method
|
|
78
|
+
});
|
|
79
|
+
this.ws.send(this.protocol.createRequest(id, method, parseResult.data));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Emit an event to the remote peer (fire-and-forget)
|
|
84
|
+
*/
|
|
85
|
+
emit(event, data) {
|
|
86
|
+
if (this.closed || this.ws.readyState !== 1) {
|
|
87
|
+
console.warn(`Cannot emit event '${String(event)}': connection closed`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const eventName = event;
|
|
91
|
+
const eventDef = this.localSchema.events?.[eventName];
|
|
92
|
+
if (!eventDef) {
|
|
93
|
+
console.warn(`Unknown event '${eventName}'`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const parseResult = eventDef.data.safeParse(data);
|
|
97
|
+
if (!parseResult.success) {
|
|
98
|
+
console.warn(`Invalid data for event '${eventName}':`, parseResult.error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.ws.send(this.protocol.createEvent(eventName, parseResult.data));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Handle an incoming WebSocket message
|
|
105
|
+
*
|
|
106
|
+
* Accepts string, ArrayBuffer, Uint8Array (including Node.js Buffer),
|
|
107
|
+
* or Uint8Array[] (for ws library's fragmented messages).
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* // Works directly with ws library's message event
|
|
112
|
+
* ws.on("message", (data) => peer.handleMessage(data));
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
handleMessage(data) {
|
|
116
|
+
const message = this.protocol.safeDecodeMessage(data);
|
|
117
|
+
if (!message) {
|
|
118
|
+
console.error("Failed to parse RPC message");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
switch (message.type) {
|
|
122
|
+
case "rpc:request":
|
|
123
|
+
this.handleRequest(message);
|
|
124
|
+
break;
|
|
125
|
+
case "rpc:response":
|
|
126
|
+
this.handleResponse(message);
|
|
127
|
+
break;
|
|
128
|
+
case "rpc:error":
|
|
129
|
+
this.handleError(message);
|
|
130
|
+
break;
|
|
131
|
+
case "rpc:event":
|
|
132
|
+
this.handleEvent(message);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Handle an incoming RPC request
|
|
138
|
+
*/
|
|
139
|
+
async handleRequest(request) {
|
|
140
|
+
const { id, method, params } = request;
|
|
141
|
+
const methodDef = this.localSchema.methods?.[method];
|
|
142
|
+
if (!methodDef) {
|
|
143
|
+
this.sendError(id, RpcErrorCodes.METHOD_NOT_FOUND, `Method '${method}' not found`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const parseResult = methodDef.input.safeParse(params);
|
|
147
|
+
if (!parseResult.success) {
|
|
148
|
+
this.sendError(id, RpcErrorCodes.INVALID_PARAMS, `Invalid params for '${method}'`, parseResult.error);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const handler = this.provider[method];
|
|
152
|
+
if (!handler) {
|
|
153
|
+
this.sendError(id, RpcErrorCodes.METHOD_NOT_FOUND, `Method '${method}' not implemented`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const result = await handler.call(this.provider, parseResult.data);
|
|
158
|
+
const outputResult = methodDef.output.safeParse(result);
|
|
159
|
+
if (!outputResult.success) {
|
|
160
|
+
this.sendError(id, RpcErrorCodes.INTERNAL_ERROR, `Invalid output from '${method}'`, outputResult.error);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.ws.send(this.protocol.createResponse(id, outputResult.data));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
166
|
+
this.sendError(id, RpcErrorCodes.INTERNAL_ERROR, message);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle an incoming RPC response
|
|
171
|
+
*/
|
|
172
|
+
handleResponse(response) {
|
|
173
|
+
const pending = this.pendingRequests.get(response.id);
|
|
174
|
+
if (!pending) {
|
|
175
|
+
console.warn(`Received response for unknown request: ${response.id}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
clearTimeout(pending.timeout);
|
|
179
|
+
this.pendingRequests.delete(response.id);
|
|
180
|
+
pending.resolve(response.result);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Handle an incoming RPC error
|
|
184
|
+
*/
|
|
185
|
+
handleError(error) {
|
|
186
|
+
const pending = this.pendingRequests.get(error.id);
|
|
187
|
+
if (!pending) {
|
|
188
|
+
console.warn(`Received error for unknown request: ${error.id}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
clearTimeout(pending.timeout);
|
|
192
|
+
this.pendingRequests.delete(error.id);
|
|
193
|
+
pending.reject(new RpcRemoteError(pending.method, error.code, error.message, error.data));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Handle an incoming event
|
|
197
|
+
*/
|
|
198
|
+
handleEvent(event) {
|
|
199
|
+
if (!this.onEventHandler) return;
|
|
200
|
+
const eventDef = this.remoteSchema.events?.[event.event];
|
|
201
|
+
if (!eventDef) {
|
|
202
|
+
console.warn(`Unknown event: ${event.event}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const parseResult = eventDef.data.safeParse(event.data);
|
|
206
|
+
if (!parseResult.success) {
|
|
207
|
+
console.warn(`Invalid data for event '${event.event}':`, parseResult.error);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this.onEventHandler(event.event, parseResult.data);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Send an error response
|
|
214
|
+
*/
|
|
215
|
+
sendError(id, code, message, data) {
|
|
216
|
+
if (this.ws.readyState !== 1) return;
|
|
217
|
+
this.ws.send(this.protocol.createError(id, code, message, data));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Mark the peer as closed and reject all pending requests
|
|
221
|
+
*/
|
|
222
|
+
close() {
|
|
223
|
+
this.closed = true;
|
|
224
|
+
for (const [, pending] of this.pendingRequests) {
|
|
225
|
+
clearTimeout(pending.timeout);
|
|
226
|
+
pending.reject(new RpcConnectionClosed());
|
|
227
|
+
}
|
|
228
|
+
this.pendingRequests.clear();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Check if the peer connection is open
|
|
232
|
+
*/
|
|
233
|
+
get isOpen() {
|
|
234
|
+
return !this.closed && this.ws.readyState === 1;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get the underlying WebSocket
|
|
238
|
+
*
|
|
239
|
+
* Use for advanced scenarios like DurableRpcPeer integration.
|
|
240
|
+
*/
|
|
241
|
+
getWebSocket() {
|
|
242
|
+
return this.ws;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get the number of pending requests
|
|
246
|
+
*/
|
|
247
|
+
get pendingCount() {
|
|
248
|
+
return this.pendingRequests.size;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
export { RpcPeer as t };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { a as InferEventData, h as StringKeys, i as EventHandler, m as RpcSchema, n as EventDef, p as Provider, t as Driver } from "./schema-CN5HHHku.js";
|
|
2
|
+
import { g as WireInput, u as RpcProtocol } from "./protocol-DA84zrc2.js";
|
|
3
|
+
import { a as IRpcOptions, r as IMinWebSocket } from "./types-Be-qmQu0.js";
|
|
4
|
+
|
|
5
|
+
//#region src/peers/default.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for creating an RpcPeer
|
|
9
|
+
*/
|
|
10
|
+
interface RpcPeerOptions<TLocalSchema extends RpcSchema, TRemoteSchema extends RpcSchema> extends IRpcOptions<TLocalSchema, TRemoteSchema> {
|
|
11
|
+
/** Unique identifier for this peer (auto-generated if not provided) */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** WebSocket instance */
|
|
14
|
+
ws: IMinWebSocket;
|
|
15
|
+
/** Implementation of local methods */
|
|
16
|
+
provider: Partial<Provider<TLocalSchema>>;
|
|
17
|
+
/**
|
|
18
|
+
* Protocol for encoding/decoding messages
|
|
19
|
+
*
|
|
20
|
+
* Defaults to JSON. Use createProtocol() with a binary codec for better performance.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { createProtocol, RpcMessageSchema } from "@igoforth/ws-rpc/protocol";
|
|
25
|
+
* import { createMsgpackCodec } from "@igoforth/ws-rpc/codecs/msgpack";
|
|
26
|
+
*
|
|
27
|
+
* const peer = new RpcPeer({
|
|
28
|
+
* protocol: createProtocol(createMsgpackCodec(RpcMessageSchema)),
|
|
29
|
+
* // ...
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
protocol?: RpcProtocol | undefined;
|
|
34
|
+
/** Handler for incoming events */
|
|
35
|
+
onEvent?: EventHandler<TRemoteSchema> | undefined;
|
|
36
|
+
/** Generate unique request IDs */
|
|
37
|
+
generateId?: (() => string) | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Bidirectional RPC peer
|
|
41
|
+
*
|
|
42
|
+
* Both sides of a WebSocket connection are "peers" - they each implement
|
|
43
|
+
* some methods (provider) and can call methods on the other side (driver).
|
|
44
|
+
*/
|
|
45
|
+
declare class RpcPeer<TLocalSchema extends RpcSchema, TRemoteSchema extends RpcSchema> {
|
|
46
|
+
/** Unique identifier for this peer */
|
|
47
|
+
readonly id: string;
|
|
48
|
+
/** WebSocket instance - protected for subclass access */
|
|
49
|
+
protected readonly ws: IMinWebSocket;
|
|
50
|
+
/** Protocol instance - protected for subclass access */
|
|
51
|
+
protected readonly protocol: RpcProtocol;
|
|
52
|
+
private readonly localSchema;
|
|
53
|
+
private readonly remoteSchema;
|
|
54
|
+
private readonly provider;
|
|
55
|
+
private readonly onEventHandler?;
|
|
56
|
+
private readonly defaultTimeout;
|
|
57
|
+
private readonly generateId;
|
|
58
|
+
private readonly pendingRequests;
|
|
59
|
+
private requestCounter;
|
|
60
|
+
private closed;
|
|
61
|
+
/** Proxy for calling remote methods */
|
|
62
|
+
readonly driver: Driver<TRemoteSchema>;
|
|
63
|
+
constructor(options: RpcPeerOptions<TLocalSchema, TRemoteSchema>);
|
|
64
|
+
/**
|
|
65
|
+
* Create a proxy that allows calling remote methods
|
|
66
|
+
*/
|
|
67
|
+
private createDriver;
|
|
68
|
+
/**
|
|
69
|
+
* Call a remote method and wait for the response (used by driver proxy)
|
|
70
|
+
*/
|
|
71
|
+
private callMethod;
|
|
72
|
+
/**
|
|
73
|
+
* Emit an event to the remote peer (fire-and-forget)
|
|
74
|
+
*/
|
|
75
|
+
emit<K extends StringKeys<TLocalSchema["events"]>>(event: K, data: TLocalSchema["events"] extends Record<string, EventDef> ? InferEventData<TLocalSchema["events"][K]> : never): void;
|
|
76
|
+
/**
|
|
77
|
+
* Handle an incoming WebSocket message
|
|
78
|
+
*
|
|
79
|
+
* Accepts string, ArrayBuffer, Uint8Array (including Node.js Buffer),
|
|
80
|
+
* or Uint8Array[] (for ws library's fragmented messages).
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* // Works directly with ws library's message event
|
|
85
|
+
* ws.on("message", (data) => peer.handleMessage(data));
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
handleMessage(data: WireInput): void;
|
|
89
|
+
/**
|
|
90
|
+
* Handle an incoming RPC request
|
|
91
|
+
*/
|
|
92
|
+
private handleRequest;
|
|
93
|
+
/**
|
|
94
|
+
* Handle an incoming RPC response
|
|
95
|
+
*/
|
|
96
|
+
private handleResponse;
|
|
97
|
+
/**
|
|
98
|
+
* Handle an incoming RPC error
|
|
99
|
+
*/
|
|
100
|
+
private handleError;
|
|
101
|
+
/**
|
|
102
|
+
* Handle an incoming event
|
|
103
|
+
*/
|
|
104
|
+
private handleEvent;
|
|
105
|
+
/**
|
|
106
|
+
* Send an error response
|
|
107
|
+
*/
|
|
108
|
+
private sendError;
|
|
109
|
+
/**
|
|
110
|
+
* Mark the peer as closed and reject all pending requests
|
|
111
|
+
*/
|
|
112
|
+
close(): void;
|
|
113
|
+
/**
|
|
114
|
+
* Check if the peer connection is open
|
|
115
|
+
*/
|
|
116
|
+
get isOpen(): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Get the underlying WebSocket
|
|
119
|
+
*
|
|
120
|
+
* Use for advanced scenarios like DurableRpcPeer integration.
|
|
121
|
+
*/
|
|
122
|
+
getWebSocket(): IMinWebSocket;
|
|
123
|
+
/**
|
|
124
|
+
* Get the number of pending requests
|
|
125
|
+
*/
|
|
126
|
+
get pendingCount(): number;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { RpcPeerOptions as n, RpcPeer as t };
|