@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.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +446 -0
  3. package/dist/adapters/client.d.ts +117 -0
  4. package/dist/adapters/client.js +241 -0
  5. package/dist/adapters/cloudflare-do.d.ts +72 -0
  6. package/dist/adapters/cloudflare-do.js +192 -0
  7. package/dist/adapters/index.d.ts +13 -0
  8. package/dist/adapters/index.js +16 -0
  9. package/dist/adapters/server.d.ts +10 -0
  10. package/dist/adapters/server.js +122 -0
  11. package/dist/adapters/types.d.ts +125 -0
  12. package/dist/adapters/types.js +3 -0
  13. package/dist/codecs/cbor.d.ts +16 -0
  14. package/dist/codecs/cbor.js +36 -0
  15. package/dist/codecs/factory.d.ts +3 -0
  16. package/dist/codecs/factory.js +3 -0
  17. package/dist/codecs/index.d.ts +5 -0
  18. package/dist/codecs/index.js +5 -0
  19. package/dist/codecs/json.d.ts +4 -0
  20. package/dist/codecs/json.js +4 -0
  21. package/dist/codecs/msgpack.d.ts +16 -0
  22. package/dist/codecs/msgpack.js +34 -0
  23. package/dist/codecs-BmYG2d_U.js +0 -0
  24. package/dist/default-BkrMd28n.js +253 -0
  25. package/dist/default-xDNNMrg0.d.ts +129 -0
  26. package/dist/durable-MZjkvyS6.js +165 -0
  27. package/dist/errors-5BfreE63.js +96 -0
  28. package/dist/errors.d.ts +69 -0
  29. package/dist/errors.js +7 -0
  30. package/dist/factory-3ziwTuZe.js +132 -0
  31. package/dist/factory-C1v0AEHY.d.ts +101 -0
  32. package/dist/index-Be7jjS77.d.ts +1 -0
  33. package/dist/index.d.ts +14 -0
  34. package/dist/index.js +14 -0
  35. package/dist/interface-C4S-WCqW.d.ts +120 -0
  36. package/dist/json-54Z2bIIs.d.ts +22 -0
  37. package/dist/json-Bshec-bZ.js +41 -0
  38. package/dist/memory-Bqb3KEVr.js +48 -0
  39. package/dist/memory-D1nGjzzH.d.ts +41 -0
  40. package/dist/multi-peer-BAi9yVzp.js +242 -0
  41. package/dist/peers/default.d.ts +8 -0
  42. package/dist/peers/default.js +8 -0
  43. package/dist/peers/durable.d.ts +136 -0
  44. package/dist/peers/durable.js +9 -0
  45. package/dist/peers/index.d.ts +10 -0
  46. package/dist/peers/index.js +9 -0
  47. package/dist/protocol-DA84zrc2.d.ts +211 -0
  48. package/dist/protocol-_mpoOPp6.js +192 -0
  49. package/dist/protocol.d.ts +6 -0
  50. package/dist/protocol.js +6 -0
  51. package/dist/reconnect-CGAA_1Gf.js +26 -0
  52. package/dist/reconnect-DbcN0R_1.d.ts +35 -0
  53. package/dist/schema-CN5HHHku.d.ts +108 -0
  54. package/dist/schema.d.ts +2 -0
  55. package/dist/schema.js +43 -0
  56. package/dist/server-zTjpJpoX.d.ts +209 -0
  57. package/dist/sql-CCjc6Bid.js +142 -0
  58. package/dist/sql-DPmHOeZy.d.ts +131 -0
  59. package/dist/storage/index.d.ts +8 -0
  60. package/dist/storage/index.js +7 -0
  61. package/dist/storage/interface.d.ts +3 -0
  62. package/dist/storage/interface.js +0 -0
  63. package/dist/storage/memory.d.ts +7 -0
  64. package/dist/storage/memory.js +6 -0
  65. package/dist/storage/sql.d.ts +7 -0
  66. package/dist/storage/sql.js +6 -0
  67. package/dist/types-Be-qmQu0.d.ts +111 -0
  68. package/dist/types-D_psiH09.js +13 -0
  69. package/dist/types.d.ts +7 -0
  70. package/dist/types.js +3 -0
  71. package/dist/utils/index.d.ts +2 -0
  72. package/dist/utils/index.js +3 -0
  73. package/dist/utils/reconnect.d.ts +2 -0
  74. package/dist/utils/reconnect.js +3 -0
  75. package/package.json +156 -0
  76. package/src/adapters/client.ts +396 -0
  77. package/src/adapters/cloudflare-do.ts +346 -0
  78. package/src/adapters/index.ts +16 -0
  79. package/src/adapters/multi-peer.ts +404 -0
  80. package/src/adapters/server.ts +192 -0
  81. package/src/adapters/types.ts +202 -0
  82. package/src/codecs/cbor.ts +42 -0
  83. package/src/codecs/factory.ts +210 -0
  84. package/src/codecs/index.ts +30 -0
  85. package/src/codecs/json.ts +42 -0
  86. package/src/codecs/msgpack.ts +36 -0
  87. package/src/errors.ts +105 -0
  88. package/src/index.ts +102 -0
  89. package/src/peers/default.ts +433 -0
  90. package/src/peers/durable.ts +280 -0
  91. package/src/peers/index.ts +13 -0
  92. package/src/protocol.ts +306 -0
  93. package/src/schema.ts +167 -0
  94. package/src/storage/index.ts +20 -0
  95. package/src/storage/interface.ts +146 -0
  96. package/src/storage/memory.ts +84 -0
  97. package/src/storage/sql.ts +266 -0
  98. package/src/types.ts +158 -0
  99. package/src/utils/index.ts +9 -0
  100. 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,3 @@
1
+ import { n as defaultReconnectOptions, t as calculateReconnectDelay } from "../reconnect-CGAA_1Gf.js";
2
+
3
+ export { 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,3 @@
1
+ import { i as isStringCodec, n as createStringCodecFactory, r as isBinaryCodec, t as createBinaryCodecFactory } from "../factory-3ziwTuZe.js";
2
+
3
+ export { 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,4 @@
1
+ import "../schema-CN5HHHku.js";
2
+ import "../factory-C1v0AEHY.js";
3
+ import { n as createJsonCodec, t as JsonCodec } from "../json-54Z2bIIs.js";
4
+ export { JsonCodec, createJsonCodec };
@@ -0,0 +1,4 @@
1
+ import "../factory-3ziwTuZe.js";
2
+ import { n as createJsonCodec, t as JsonCodec } from "../json-Bshec-bZ.js";
3
+
4
+ export { JsonCodec, createJsonCodec };
@@ -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 };