@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,202 @@
1
+ /**
2
+ * Adapter Type Definitions
3
+ *
4
+ * Common interfaces and types for WebSocket adapters.
5
+ */
6
+
7
+ import type { RpcPeer } from "../peers/default.js";
8
+ import type {
9
+ Driver,
10
+ EventHandler,
11
+ InferInput,
12
+ InferOutput,
13
+ MethodDef,
14
+ RpcSchema,
15
+ StringKeys,
16
+ } from "../schema.js";
17
+ import type {
18
+ IEventController,
19
+ IMethodController,
20
+ IRpcOptions,
21
+ } from "../types.js";
22
+
23
+ // =============================================================================
24
+ // Multi-Connection Adapter Types
25
+ // =============================================================================
26
+
27
+ /**
28
+ * Options for driver method calls on multi-connection adapters
29
+ */
30
+ export interface MultiCallOptions {
31
+ /** Connection ID(s) to call. If omitted, calls all connections. */
32
+ ids?: string | string[];
33
+ /** Request timeout in milliseconds */
34
+ timeout?: number;
35
+ }
36
+
37
+ /**
38
+ * Result of a driver method call on a multi-connection adapter
39
+ *
40
+ * Each result contains the peer ID and either a success value or an error.
41
+ *
42
+ * @typeParam T - The expected return type of the remote method
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const results = await server.driver.ping({});
47
+ * for (const { id, result } of results) {
48
+ * if (result.success) {
49
+ * console.log(`Peer ${id} responded:`, result.value);
50
+ * } else {
51
+ * console.error(`Peer ${id} failed:`, result.error);
52
+ * }
53
+ * }
54
+ * ```
55
+ */
56
+ export type MultiCallResult<T> = {
57
+ /** Peer ID that this result is from */
58
+ id: string;
59
+ /** Success or failure result */
60
+ result: { success: true; value: T } | { success: false; error: Error };
61
+ };
62
+
63
+ /**
64
+ * Driver type for multi-connection adapters
65
+ *
66
+ * Methods accept an optional second argument with `ids` and `timeout`.
67
+ * Returns an array of results, one per connection called.
68
+ *
69
+ * @typeParam TRemoteSchema - Schema defining the remote methods available
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * // Call all connected peers
74
+ * const allResults = await server.driver.getData({});
75
+ *
76
+ * // Call specific peer by ID
77
+ * const oneResult = await server.driver.getData({}, { ids: "peer-123" });
78
+ *
79
+ * // Call multiple peers with timeout
80
+ * const someResults = await server.driver.getData({}, {
81
+ * ids: ["peer-1", "peer-2"],
82
+ * timeout: 5000,
83
+ * });
84
+ * ```
85
+ */
86
+ export type MultiDriver<TRemoteSchema extends RpcSchema> =
87
+ TRemoteSchema["methods"] extends Record<string, MethodDef>
88
+ ? {
89
+ [K in StringKeys<TRemoteSchema["methods"]>]: <
90
+ O extends MultiCallOptions,
91
+ >(
92
+ input: TRemoteSchema["methods"] extends Record<string, MethodDef>
93
+ ? InferInput<TRemoteSchema["methods"][K]>
94
+ : never,
95
+ options?: O,
96
+ ) => Promise<
97
+ O extends { ids: infer I }
98
+ ? string extends I
99
+ ? MultiCallResult<
100
+ TRemoteSchema["methods"] extends Record<string, MethodDef>
101
+ ? InferOutput<TRemoteSchema["methods"][K]>
102
+ : never
103
+ >
104
+ : Array<
105
+ MultiCallResult<
106
+ TRemoteSchema["methods"] extends Record<string, MethodDef>
107
+ ? InferOutput<TRemoteSchema["methods"][K]>
108
+ : never
109
+ >
110
+ >
111
+ : never
112
+ >;
113
+ }
114
+ : never;
115
+
116
+ /**
117
+ * Event emitter type for client callbacks
118
+ */
119
+ export interface IAdapterHooks<TRemoteSchema extends RpcSchema> {
120
+ /** Called when WebSocket connection opens */
121
+ onConnect?(): void;
122
+
123
+ /** Called when WebSocket connection closes */
124
+ onDisconnect?(code: number, reason: string): void;
125
+
126
+ /** Called when attempting to reconnect */
127
+ onReconnect?(attempt: number, delay: number): void;
128
+
129
+ /** Called when reconnection fails after max attempts */
130
+ onReconnectFailed?(): void;
131
+
132
+ /** Called when receiving an event from the server */
133
+ onEvent?: EventHandler<TRemoteSchema>;
134
+ }
135
+
136
+ export interface IMultiAdapterHooks<
137
+ TLocalSchema extends RpcSchema,
138
+ TRemoteSchema extends RpcSchema,
139
+ > {
140
+ /** Called when a peer connects */
141
+ onConnect?(peer: RpcPeer<TLocalSchema, TRemoteSchema>): void;
142
+
143
+ /** Called when a peer disconnects */
144
+ onDisconnect?(peer: RpcPeer<TLocalSchema, TRemoteSchema>): void;
145
+
146
+ /** Called when an event is received from a peer */
147
+ onEvent?: EventHandler<
148
+ TRemoteSchema,
149
+ [peer: RpcPeer<TLocalSchema, TRemoteSchema>]
150
+ >;
151
+
152
+ /** Called when a peer encounters an error */
153
+ onError?(
154
+ peer: RpcPeer<TLocalSchema, TRemoteSchema> | null,
155
+ error: Error,
156
+ ): void;
157
+
158
+ onClose?(): void;
159
+ }
160
+
161
+ export interface IConnectionAdapter<
162
+ TLocalSchema extends RpcSchema,
163
+ TRemoteSchema extends RpcSchema,
164
+ > extends IRpcOptions<TLocalSchema, TRemoteSchema>,
165
+ IMethodController<TLocalSchema>,
166
+ IEventController<TLocalSchema, TRemoteSchema> {
167
+ /** Driver for calling remote methods on connected peer */
168
+ readonly driver: Driver<TRemoteSchema>;
169
+
170
+ readonly hooks: IAdapterHooks<TRemoteSchema>;
171
+ }
172
+
173
+ /**
174
+ * Interface for adapters that manage multiple connections
175
+ *
176
+ * Extends IRpcConnection - `emit()` broadcasts to all connected peers.
177
+ * Implemented by RpcServer and Cloudflare DO adapter.
178
+ */
179
+ export interface IMultiConnectionAdapter<
180
+ TLocalSchema extends RpcSchema,
181
+ TRemoteSchema extends RpcSchema,
182
+ > extends IRpcOptions<TLocalSchema, TRemoteSchema>,
183
+ IMethodController<TLocalSchema>,
184
+ IEventController<TLocalSchema, TRemoteSchema, [ids?: string[]]> {
185
+ /** Driver for calling remote methods on connected peers */
186
+ readonly driver: MultiDriver<TRemoteSchema>;
187
+
188
+ readonly hooks: IMultiAdapterHooks<TLocalSchema, TRemoteSchema>;
189
+
190
+ /** Get count of connected peers */
191
+ getConnectionCount(): number;
192
+
193
+ /** Get all connected peer IDs */
194
+ getConnectionIds(): string[];
195
+ }
196
+
197
+ // Re-export reconnection utilities from utils
198
+ export {
199
+ calculateReconnectDelay,
200
+ defaultReconnectOptions,
201
+ type ReconnectOptions,
202
+ } from "../utils/reconnect.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * CBOR codec using cbor-x
3
+ *
4
+ * Requires optional peer dependency: cbor-x
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { createCborCodec, CborCodec } from "@igoforth/ws-rpc/codecs/cbor";
9
+ *
10
+ * // Create a typed codec
11
+ * const MyDataCodec = createCborCodec(MyDataSchema);
12
+ * const bytes = MyDataCodec.encode(data); // Uint8Array
13
+ * const decoded = MyDataCodec.decode(bytes); // validated MyData
14
+ *
15
+ * // Or use the generic codec
16
+ * const bytes = CborCodec.encode({ any: "data" });
17
+ * ```
18
+ */
19
+
20
+ import { Decoder, Encoder } from "cbor-x";
21
+ import * as z from "zod";
22
+ import { createBinaryCodecFactory } from "./factory";
23
+
24
+ const decode = <T>(data: Uint8Array<ArrayBuffer>): T =>
25
+ new Decoder({ bundleStrings: true }).decode(data) as T;
26
+
27
+ const encode = (data: unknown): Uint8Array<ArrayBuffer> =>
28
+ new Encoder({ bundleStrings: true }).encode(data) as Uint8Array<ArrayBuffer>;
29
+
30
+ export const createCborCodec = createBinaryCodecFactory(
31
+ (value) => encode(value),
32
+ (bytes) => decode(bytes),
33
+ "cbor",
34
+ );
35
+
36
+ /**
37
+ * Default CBOR codec for unknown values
38
+ *
39
+ * Use when you need to serialize arbitrary data without schema validation.
40
+ * The decoded value will be `unknown` and should be validated separately.
41
+ */
42
+ export const CborCodec = createCborCodec(z.unknown());
@@ -0,0 +1,210 @@
1
+ /**
2
+ * RPC Codecs
3
+ *
4
+ * Zod-based codecs for serialization with built-in validation.
5
+ * Provides factories for creating codecs that encode to string or binary.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // JSON codec with validation
10
+ * const MyDataCodec = jsonCodec(MyDataSchema);
11
+ * const encoded = MyDataCodec.encode(data); // string
12
+ * const decoded = MyDataCodec.decode(encoded); // validated MyData
13
+ *
14
+ * // Safe decode with error handling
15
+ * const result = MyDataCodec.safeDecode(encoded);
16
+ * if (result.success) {
17
+ * console.log(result.data);
18
+ * } else {
19
+ * console.error(result.error);
20
+ * }
21
+ * ```
22
+ */
23
+
24
+ import * as z from "zod";
25
+ import type { LiteralStringUnion } from "../schema";
26
+
27
+ /**
28
+ * Type alias for a Zod codec that encodes to string
29
+ */
30
+ export type StringCodec<T extends z.ZodType = z.ZodType> = z.ZodCodec<
31
+ z.ZodString,
32
+ T
33
+ >;
34
+
35
+ /**
36
+ * Type alias for a Zod codec that encodes to Uint8Array
37
+ */
38
+ export type BinaryCodec<T extends z.ZodType = z.ZodType> = z.ZodCodec<
39
+ z.ZodCustom<Uint8Array<ArrayBuffer>>,
40
+ T
41
+ >;
42
+
43
+ // ZodCodec<ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, T>;
44
+
45
+ /**
46
+ * Options for codec factories
47
+ */
48
+ export interface CodecOptions {
49
+ /** Custom error message for parse failures */
50
+ errorMessage?: string;
51
+ }
52
+
53
+ /**
54
+ * Codec factory signature for custom serialization libraries
55
+ *
56
+ * Implement this interface to create codecs using libraries like
57
+ * superjson, devalue, or MessagePack.
58
+ */
59
+ export interface CodecFactory<TEncoded> {
60
+ <T extends z.ZodType>(
61
+ schema: T,
62
+ options?: CodecOptions,
63
+ ): z.ZodCodec<
64
+ string extends TEncoded ? z.ZodString : z.ZodCustom<TEncoded>,
65
+ T
66
+ >;
67
+ }
68
+
69
+ /**
70
+ * Create a custom string codec factory
71
+ *
72
+ * Use this to integrate custom serialization libraries that encode to strings.
73
+ *
74
+ * @param serialize - Function to serialize a value to string
75
+ * @param deserialize - Function to deserialize a string to a value
76
+ * @param formatName - Name of the format for error messages
77
+ * @returns A codec factory function
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import superjson from "superjson";
82
+ *
83
+ * const superJsonCodec = createStringCodecFactory(
84
+ * superjson.stringify,
85
+ * superjson.parse,
86
+ * "superjson"
87
+ * );
88
+ *
89
+ * // Now supports Date, Map, Set, BigInt, etc.
90
+ * const DataCodec = superJsonCodec(z.object({
91
+ * timestamp: z.date(),
92
+ * values: z.map(z.string(), z.number()),
93
+ * }));
94
+ * ```
95
+ */
96
+ export function createStringCodecFactory(
97
+ serialize: (value: unknown) => string,
98
+ deserialize: (text: string) => unknown,
99
+ formatName: LiteralStringUnion<z.core.$ZodStringFormats>,
100
+ ): CodecFactory<string> {
101
+ return <T extends z.ZodType>(
102
+ schema: T,
103
+ options?: CodecOptions,
104
+ ): StringCodec<T> => {
105
+ return z.codec(z.string(), schema, {
106
+ decode: (text, ctx) => {
107
+ try {
108
+ return deserialize(text) as z.util.MaybeAsync<z.input<T>>;
109
+ } catch (err) {
110
+ ctx.issues.push({
111
+ code: "invalid_format",
112
+ format: formatName,
113
+ input: text,
114
+ message:
115
+ options?.errorMessage ??
116
+ (err instanceof Error ? err.message : `Invalid ${formatName}`),
117
+ });
118
+ return z.NEVER;
119
+ }
120
+ },
121
+ encode: (value) => serialize(value),
122
+ });
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Create a custom binary codec factory
128
+ *
129
+ * Use this to integrate binary serialization libraries like MessagePack or CBOR.
130
+ *
131
+ * @param serialize - Function to serialize a value to Uint8Array
132
+ * @param deserialize - Function to deserialize a Uint8Array to a value
133
+ * @param formatName - Name of the format for error messages
134
+ * @returns A codec factory function
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * import { encode, decode } from "@msgpack/msgpack";
139
+ *
140
+ * const msgpackCodec = createBinaryCodecFactory(
141
+ * (v) => new Uint8Array(encode(v)),
142
+ * decode,
143
+ * "msgpack"
144
+ * );
145
+ *
146
+ * const DataCodec = msgpackCodec(MySchema);
147
+ * const bytes = DataCodec.encode(data); // Uint8Array
148
+ * const data = DataCodec.decode(bytes); // validated
149
+ * ```
150
+ */
151
+ export function createBinaryCodecFactory(
152
+ serialize: (value: unknown) => Uint8Array<ArrayBuffer>,
153
+ deserialize: (bytes: Uint8Array<ArrayBuffer>) => unknown,
154
+ formatName: string,
155
+ ): CodecFactory<Uint8Array<ArrayBuffer>> {
156
+ return <T extends z.ZodType>(
157
+ schema: T,
158
+ options?: CodecOptions,
159
+ ): BinaryCodec<T> => {
160
+ return z.codec(z.instanceof(Uint8Array), schema, {
161
+ decode: (bytes, ctx) => {
162
+ try {
163
+ return deserialize(bytes) as z.util.MaybeAsync<z.input<T>>;
164
+ } catch (err) {
165
+ ctx.issues.push({
166
+ code: "invalid_format",
167
+ format: formatName,
168
+ input: String(bytes),
169
+ message:
170
+ options?.errorMessage ??
171
+ (err instanceof Error ? err.message : `Invalid ${formatName}`),
172
+ });
173
+ return z.NEVER;
174
+ }
175
+ },
176
+ encode: (value) => serialize(value),
177
+ });
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Wire codec - either string or binary
183
+ */
184
+ export type WireCodec<T extends z.ZodType = z.ZodType> =
185
+ | StringCodec<T>
186
+ | BinaryCodec<T>;
187
+
188
+ /**
189
+ * Wire data - what gets sent over WebSocket
190
+ */
191
+ export type WireData = string | Uint8Array<ArrayBuffer>;
192
+
193
+ /**
194
+ * Helper to check if a codec encodes to string
195
+ */
196
+ export function isStringCodec(
197
+ codec: z.ZodCodec<z.ZodType, z.ZodType>,
198
+ ): codec is StringCodec {
199
+ // Check if the "from" schema accepts strings
200
+ return codec._zod.def.in._zod.def.type === "string";
201
+ }
202
+
203
+ /**
204
+ * Helper to check if a codec encodes to binary
205
+ */
206
+ export function isBinaryCodec(
207
+ codec: z.ZodCodec<z.ZodType, z.ZodType>,
208
+ ): codec is BinaryCodec {
209
+ return !isStringCodec(codec);
210
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * RPC Codecs
3
+ *
4
+ * Zod-based codecs for serialization with built-in validation.
5
+ *
6
+ * Core exports (always available):
7
+ * - Factory functions for creating custom codecs
8
+ * - JSON codec (uses built-in JSON.stringify/parse)
9
+ *
10
+ * Optional binary codecs (require peer dependencies):
11
+ * - MessagePack: import from "@igoforth/ws-rpc/codecs/msgpack"
12
+ * - CBOR: import from "@igoforth/ws-rpc/codecs/cbor"
13
+ */
14
+
15
+ // Core types and factories
16
+ export {
17
+ type BinaryCodec,
18
+ type CodecFactory,
19
+ type CodecOptions,
20
+ createBinaryCodecFactory,
21
+ createStringCodecFactory,
22
+ isBinaryCodec,
23
+ isStringCodec,
24
+ type StringCodec,
25
+ type WireCodec,
26
+ type WireData,
27
+ } from "./factory";
28
+
29
+ // JSON codec (always available)
30
+ export { createJsonCodec, JsonCodec } from "./json";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * JSON Codec
3
+ *
4
+ * String-based codec using native JSON serialization.
5
+ * Suitable for simple data types (no Date, Map, Set, BigInt support).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { createJsonCodec } from "@igoforth/ws-rpc/codecs";
10
+ *
11
+ * const UserCodec = createJsonCodec(z.object({
12
+ * id: z.string(),
13
+ * name: z.string(),
14
+ * }));
15
+ *
16
+ * const encoded = UserCodec.encode({ id: "1", name: "John" });
17
+ * const decoded = UserCodec.decode(encoded);
18
+ * ```
19
+ */
20
+ import * as z from "zod";
21
+ import { createStringCodecFactory } from "./factory";
22
+
23
+ /**
24
+ * Factory for creating JSON codecs with Zod schema validation
25
+ *
26
+ * @param schema - Zod schema for validation
27
+ * @param options - Optional codec configuration
28
+ * @returns A codec that encodes to JSON string and validates on decode
29
+ */
30
+ export const createJsonCodec = createStringCodecFactory(
31
+ JSON.stringify,
32
+ JSON.parse,
33
+ "json_string",
34
+ );
35
+
36
+ /**
37
+ * Default JSON codec for unknown values
38
+ *
39
+ * Use when you need to serialize arbitrary data without schema validation.
40
+ * The decoded value will be `unknown` and should be validated separately.
41
+ */
42
+ export const JsonCodec = createJsonCodec(z.unknown());
@@ -0,0 +1,36 @@
1
+ /**
2
+ * MessagePack codec using @msgpack/msgpack
3
+ *
4
+ * Requires optional peer dependency: @msgpack/msgpack
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { createMsgpackCodec, MsgpackCodec } from "@igoforth/ws-rpc/codecs/msgpack";
9
+ *
10
+ * // Create a typed codec
11
+ * const MyDataCodec = createMsgpackCodec(MyDataSchema);
12
+ * const bytes = MyDataCodec.encode(data); // Uint8Array
13
+ * const decoded = MyDataCodec.decode(bytes); // validated MyData
14
+ *
15
+ * // Or use the generic codec
16
+ * const bytes = MsgpackCodec.encode({ any: "data" });
17
+ * ```
18
+ */
19
+
20
+ import { decode, encode } from "@msgpack/msgpack";
21
+ import * as z from "zod";
22
+ import { createBinaryCodecFactory } from "./factory";
23
+
24
+ export const createMsgpackCodec = createBinaryCodecFactory(
25
+ (value) => encode(value) as Uint8Array<ArrayBuffer>,
26
+ (bytes) => decode(bytes),
27
+ "msgpack",
28
+ );
29
+
30
+ /**
31
+ * Default MessagePack codec for unknown values
32
+ *
33
+ * Use when you need to serialize arbitrary data without schema validation.
34
+ * The decoded value will be `unknown` and should be validated separately.
35
+ */
36
+ export const MsgpackCodec = createMsgpackCodec(z.unknown());
package/src/errors.ts ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * RPC Error Classes
3
+ *
4
+ * Custom error types for RPC operations.
5
+ */
6
+
7
+ import { RpcErrorCodes } from "./protocol.js";
8
+
9
+ /**
10
+ * Base class for all RPC errors
11
+ *
12
+ * @param code - RPC error code (from RpcErrorCodes)
13
+ * @param message - Human-readable error message
14
+ * @param data - Optional additional error data
15
+ */
16
+ export class RpcError extends Error {
17
+ readonly code: number;
18
+ readonly data?: unknown;
19
+
20
+ constructor(code: number, message: string, data?: unknown) {
21
+ super(message);
22
+ this.name = "RpcError";
23
+ this.code = code;
24
+ this.data = data;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Thrown when an RPC request times out waiting for a response
30
+ *
31
+ * @param method - Name of the method that timed out
32
+ * @param timeoutMs - Timeout duration in milliseconds
33
+ */
34
+ export class RpcTimeoutError extends RpcError {
35
+ readonly method: string;
36
+ readonly timeoutMs: number;
37
+
38
+ constructor(method: string, timeoutMs: number) {
39
+ super(
40
+ RpcErrorCodes.TIMEOUT,
41
+ `RPC request '${method}' timed out after ${timeoutMs}ms`,
42
+ );
43
+ this.name = "RpcTimeoutError";
44
+ this.method = method;
45
+ this.timeoutMs = timeoutMs;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Thrown when the remote handler returns an error
51
+ *
52
+ * @param method - Name of the remote method that failed
53
+ * @param code - RPC error code from the remote
54
+ * @param message - Error message from the remote
55
+ * @param data - Optional additional error data from the remote
56
+ */
57
+ export class RpcRemoteError extends RpcError {
58
+ readonly method: string;
59
+
60
+ constructor(method: string, code: number, message: string, data?: unknown) {
61
+ super(code, message, data);
62
+ this.name = "RpcRemoteError";
63
+ this.method = method;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Thrown when the WebSocket connection closes while a request is pending
69
+ *
70
+ * @param message - Optional custom message (defaults to "WebSocket connection closed")
71
+ */
72
+ export class RpcConnectionClosed extends RpcError {
73
+ constructor(message = "WebSocket connection closed") {
74
+ super(RpcErrorCodes.CONNECTION_CLOSED, message);
75
+ this.name = "RpcConnectionClosed";
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Thrown when input or output validation fails
81
+ *
82
+ * @param message - Description of the validation failure
83
+ * @param data - Optional Zod error or validation details
84
+ */
85
+ export class RpcValidationError extends RpcError {
86
+ constructor(message: string, data?: unknown) {
87
+ super(RpcErrorCodes.VALIDATION_ERROR, message, data);
88
+ this.name = "RpcValidationError";
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Thrown when a requested method doesn't exist on the provider
94
+ *
95
+ * @param method - Name of the method that was not found
96
+ */
97
+ export class RpcMethodNotFoundError extends RpcError {
98
+ readonly method: string;
99
+
100
+ constructor(method: string) {
101
+ super(RpcErrorCodes.METHOD_NOT_FOUND, `Method '${method}' not found`);
102
+ this.name = "RpcMethodNotFoundError";
103
+ this.method = method;
104
+ }
105
+ }