rpc-express-toolkit 4.2.1 → 4.3.2

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.
@@ -1,403 +0,0 @@
1
- /**
2
- * JSON-RPC 2.0 Client for browser and Node.js environments.
3
- * Handles BigInt and Date serialization/deserialization automatically
4
- * using explicit serializers (no global prototype extensions).
5
- */
6
-
7
- // Select fetch implementation (Node 18+ or browser)
8
- let fetchFn;
9
- if (typeof globalThis !== 'undefined' && globalThis.fetch) {
10
- fetchFn = globalThis.fetch.bind(globalThis);
11
- } else {
12
- throw new Error(
13
- 'globalThis.fetch is not available. Node.js 18+ is required.'
14
- );
15
- }
16
-
17
- class RpcClient {
18
- #endpoint;
19
-
20
- #defaultHeaders;
21
-
22
- #requestId;
23
-
24
- #fetchOptions;
25
-
26
- #options;
27
-
28
- /**
29
- * @param {string} endpoint The JSON-RPC endpoint URL.
30
- * @param {Object} [defaultHeaders={}] Optional default headers to include in requests.
31
- * @param {Object} [options={}] Optional configuration options.
32
- * @param {boolean} [options.rejectUnauthorized=true] Whether to reject unauthorized SSL certificates. Set to false for development with self-signed certificates.
33
- * @param {boolean} [options.safeEnabled=false] Whether to use safe serialization for both strings and dates to avoid confusion with BigInt/ISO values. Default false for JSON-RPC 2.0 compliance.
34
- * @param {boolean} [options.warnOnUnsafe=true] Whether to show educational warnings when BigInt/Date objects are serialized without safe mode. Default true.
35
- */
36
- constructor(endpoint, defaultHeaders = {}, options = {}) {
37
- this.#endpoint = endpoint;
38
- this.#defaultHeaders = {
39
- 'Content-Type': 'application/json', // Default header
40
- ...defaultHeaders, // Merge with user-provided defaults
41
- };
42
- // Initialize request ID counter with timestamp + random component for uniqueness
43
- this.#requestId = Date.now() * 1000 + Math.floor(Math.random() * 1000);
44
-
45
- // Store options
46
- this.#options = {
47
- safeEnabled: options.safeEnabled === true, // Default false
48
- warnOnUnsafe: options.warnOnUnsafe !== false, // Default true
49
- ...options,
50
- };
51
-
52
- // Store fetch options for Node.js environments
53
- this.#fetchOptions = {};
54
- // SSL validation: advanced options (agent/ca) have been removed for simplicity and compatibility.
55
- // To bypass self-signed certificates in development, set:
56
- // process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
57
- // See README for details and best practices.
58
- }
59
-
60
- /**
61
- * Generate a unique request ID
62
- * @returns {number} Unique request ID
63
- */
64
- #generateId() {
65
- return ++this.#requestId;
66
- }
67
-
68
- /**
69
- * Make a JSON-RPC call to the server.
70
- * @template T
71
- * @param {string} method The RPC method name.
72
- * @param {any} params The parameters to pass to the RPC method.
73
- * @param {string|number|null} [id] The request ID (auto-generated if not provided).
74
- * @param {Object} [overrideHeaders={}] Optional headers to override defaults for this request.
75
- * @returns {Promise<T>} The result of the RPC call.
76
- */
77
- async call(method, params, id = undefined, overrideHeaders = {}) {
78
- // Auto-generate ID if not provided (null means notification)
79
- const requestId = id === undefined ? this.#generateId() : id;
80
-
81
- // Build the payload according to the spec: params omitted if undefined/null
82
- const requestBody = {
83
- jsonrpc: '2.0',
84
- method,
85
- id: requestId,
86
- };
87
- if (params !== undefined && params !== null) {
88
- // Serialize parameters to handle BigInt and safe strings
89
- requestBody.params = this.serializeBigIntsAndDates(params);
90
- }
91
-
92
- try {
93
- const response = await fetchFn(this.#endpoint, {
94
- method: 'POST',
95
- headers: {
96
- ...this.#defaultHeaders,
97
- // Add safe options header so server knows what client expects
98
- 'X-RPC-Safe-Enabled': this.#options.safeEnabled ? 'true' : 'false',
99
- ...overrideHeaders,
100
- },
101
- // Params are pre-serialized (BigInt/Date) via serializeBigIntsAndDates
102
- body: JSON.stringify(requestBody),
103
- ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
104
- });
105
-
106
- if (!response.ok) {
107
- throw new Error(
108
- `HTTP Error: ${response.status} ${response.statusText}`
109
- );
110
- }
111
-
112
- const responseBody = await response.json();
113
-
114
- if (responseBody.error) {
115
- throw responseBody.error;
116
- }
117
-
118
- // Check server's safe options from response headers - strict compatibility checking
119
- const serverSafeHeader = response.headers.get('X-RPC-Safe-Enabled');
120
-
121
- // Errore rigoroso: se client ha safe enabled ma server non risponde con header
122
- if (this.#options.safeEnabled && serverSafeHeader === null) {
123
- throw new Error(
124
- 'RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). ' +
125
- 'This may indicate a version mismatch or non-toolkit server. ' +
126
- 'Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.'
127
- );
128
- }
129
-
130
- // Warning: se client ha safe disabled ma server offre compatibilità
131
- if (
132
- !this.#options.safeEnabled &&
133
- serverSafeHeader === 'true' &&
134
- this.#options.warnOnUnsafe
135
- ) {
136
- console.warn(
137
- '⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. ' +
138
- 'Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.'
139
- );
140
- }
141
-
142
- const serverSafeEnabled = serverSafeHeader === 'true';
143
-
144
- // Create deserialization options based on server's configuration
145
- const deserializationOptions = {
146
- safeEnabled: serverSafeEnabled,
147
- };
148
-
149
- // Convert back BigInts and Dates in the result using server's options
150
- return this.deserializeBigIntsAndDates(
151
- responseBody.result,
152
- deserializationOptions
153
- );
154
- } catch (error) {
155
- console.error('RPC call failed:', error);
156
- throw error;
157
- }
158
- }
159
-
160
- /**
161
- * Send a notification (no response expected).
162
- * @param {string} method The RPC method name.
163
- * @param {any} params The parameters to pass to the RPC method.
164
- * @param {Object} [overrideHeaders={}] Optional headers to override defaults for this request.
165
- * @returns {Promise<void>} Promise that resolves when notification is sent.
166
- */
167
- async notify(method, params = {}, overrideHeaders = {}) {
168
- await this.call(method, params, null, overrideHeaders);
169
- }
170
-
171
- /**
172
- * Make multiple JSON-RPC calls in a single batch request.
173
- * @template T
174
- * @param {Array<{method: string, params?: any, id?: string|number}>} requests Array of request objects.
175
- * @param {Object} [overrideHeaders={}] Optional headers to override defaults for this request.
176
- * @returns {Promise<Array<T>>} Array of results in the same order as requests.
177
- */
178
- async batch(requests, overrideHeaders = {}) {
179
- const batchRequests = requests.map((req) => {
180
- const obj = {
181
- jsonrpc: '2.0',
182
- method: req.method,
183
- id: req.id !== undefined ? req.id : this.#generateId(),
184
- };
185
- if (req.params !== undefined && req.params !== null) {
186
- // Serialize parameters to handle BigInt and safe strings
187
- obj.params = this.serializeBigIntsAndDates(req.params);
188
- }
189
- return obj;
190
- });
191
-
192
- try {
193
- const response = await fetchFn(this.#endpoint, {
194
- method: 'POST',
195
- headers: {
196
- ...this.#defaultHeaders,
197
- // Add safe options header so server knows what client expects
198
- 'X-RPC-Safe-Enabled': this.#options.safeEnabled ? 'true' : 'false',
199
- ...overrideHeaders,
200
- },
201
- // Params are pre-serialized per item
202
- body: JSON.stringify(batchRequests),
203
- ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
204
- });
205
-
206
- if (!response.ok) {
207
- throw new Error(
208
- `HTTP Error: ${response.status} ${response.statusText}`
209
- );
210
- }
211
-
212
- const responseBody = await response.json();
213
-
214
- // Check server's safe options from response headers - strict compatibility checking
215
- const serverSafeHeader = response.headers.get('X-RPC-Safe-Enabled');
216
-
217
- // Errore rigoroso: se client ha safe enabled ma server non risponde con header
218
- if (this.#options.safeEnabled && serverSafeHeader === null) {
219
- throw new Error(
220
- 'RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). ' +
221
- 'This may indicate a version mismatch or non-toolkit server. ' +
222
- 'Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.'
223
- );
224
- }
225
-
226
- // Warning: se client ha safe disabled ma server offre compatibilità
227
- if (
228
- !this.#options.safeEnabled &&
229
- serverSafeHeader === 'true' &&
230
- this.#options.warnOnUnsafe
231
- ) {
232
- console.warn(
233
- '⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. ' +
234
- 'Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.'
235
- );
236
- }
237
-
238
- const serverSafeEnabled = serverSafeHeader === 'true';
239
-
240
- // Create deserialization options based on server's configuration
241
- const deserializationOptions = {
242
- safeEnabled: serverSafeEnabled,
243
- };
244
-
245
- // Handle batch response
246
- if (Array.isArray(responseBody)) {
247
- return responseBody.map((res) => {
248
- if (res.error) {
249
- throw res.error;
250
- }
251
- return this.deserializeBigIntsAndDates(
252
- res.result,
253
- deserializationOptions
254
- );
255
- });
256
- }
257
- // Single response in batch
258
- if (responseBody.error) {
259
- throw responseBody.error;
260
- }
261
- return [
262
- this.deserializeBigIntsAndDates(
263
- responseBody.result,
264
- deserializationOptions
265
- ),
266
- ];
267
- } catch (error) {
268
- console.error('Batch RPC call failed:', error);
269
- throw error;
270
- }
271
- }
272
-
273
- /**
274
- * Recursively convert BigInt values to strings and Date objects to ISO strings
275
- * so they can be JSON-serialized.
276
- * @param {any} value
277
- * @returns {any}
278
- */
279
- serializeBigIntsAndDates(value) {
280
- if (typeof value === 'bigint') {
281
- // Convert BigInt to string with 'n' suffix for proper deserialization
282
- return `${value.toString()}n`;
283
- }
284
- if (value instanceof Date) {
285
- // Convert Date to ISO string with D: prefix if safeEnabled
286
- const isoString = value.toISOString();
287
- if (this.#options.safeEnabled) {
288
- return `D:${isoString}`;
289
- }
290
- // Show educational warning if enabled
291
- if (this.#options.warnOnUnsafe) {
292
- console.warn(
293
- '⚠️ Date serialization: Using plain ISO string format for JSON-RPC 2.0 compliance. Date objects will be deserialized as strings on the receiving end. Consider enabling safeEnabled or using string timestamps for better type safety.'
294
- );
295
- }
296
- return isoString;
297
- }
298
- if (typeof value === 'string') {
299
- // Add S: prefix if safeEnabled is true
300
- if (this.#options.safeEnabled) {
301
- return `S:${value}`;
302
- }
303
- // Show educational warning if enabled and string could be confused with BigInt
304
- if (this.#options.warnOnUnsafe && /^-?\d+n?$/.test(value)) {
305
- console.warn(
306
- `⚠️ String serialization: String "${value}" could be confused with BigInt. Consider enabling safeEnabled for disambiguation or use explicit typing.`
307
- );
308
- }
309
- return value;
310
- }
311
- if (Array.isArray(value)) {
312
- // Recurse into arrays
313
- return value.map((v) => this.serializeBigIntsAndDates(v));
314
- }
315
- if (value && typeof value === 'object') {
316
- // Recurse into plain objects
317
- const result = {};
318
- for (const [key, val] of Object.entries(value)) {
319
- result[key] = this.serializeBigIntsAndDates(val);
320
- }
321
- return result;
322
- }
323
-
324
- // If it's neither an array, an object, a Date, nor a bigint, return as-is
325
- return value;
326
- }
327
-
328
- /**
329
- * Recursively convert stringified BigInt values back to BigInt
330
- * and ISO 8601 date strings back to Date objects.
331
- *
332
- * @param {any} value The value to deserialize.
333
- * @param {Object} [options] Custom deserialization options (uses client options if not provided).
334
- * @param {boolean} [options.safeEnabled] Whether to expect safe prefixes for strings and dates.
335
- * @returns {any}
336
- */
337
- deserializeBigIntsAndDates(value, options = null) {
338
- // Use provided options or fall back to client options
339
- const safeEnabled = options
340
- ? options.safeEnabled
341
- : this.#options.safeEnabled;
342
-
343
- // More comprehensive ISO date regex that handles:
344
- // - UTC: 2023-01-01T12:00:00.000Z
345
- // - With timezone: 2023-01-01T12:00:00.000+01:00
346
- // - Without timezone: 2023-01-01T12:00:00.000 (treated as local)
347
- const ISO_DATE_REGEX =
348
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
349
-
350
- // 1. Check if it's a string that might be a BigInt, Date, or safe string
351
- if (typeof value === 'string') {
352
- // Safe string check: if safeEnabled and starts with S:
353
- if (safeEnabled && value.startsWith('S:')) {
354
- return value.substring(2); // Remove 'S:' prefix
355
- }
356
-
357
- // Safe date check: if safeEnabled and starts with D:
358
- if (safeEnabled && value.startsWith('D:')) {
359
- const isoString = value.substring(2); // Remove 'D:' prefix
360
- const date = new Date(isoString);
361
- // Double-check that we got a valid date
362
- if (!Number.isNaN(date.getTime())) {
363
- return date;
364
- }
365
- }
366
-
367
- // BigInt check: only convert strings that explicitly end with "n"
368
- // e.g., "42n", "-42n" but NOT "42", "0123456"
369
- if (/^-?\d+n$/.test(value)) {
370
- return BigInt(value.slice(0, -1)); // Remove 'n' and convert
371
- }
372
-
373
- // Date check: matches an ISO 8601 string (only if safeEnabled is false)
374
- if (!safeEnabled && ISO_DATE_REGEX.test(value)) {
375
- const date = new Date(value);
376
- // Ensure it's valid
377
- if (!Number.isNaN(date.getTime())) {
378
- return date;
379
- }
380
- }
381
- }
382
-
383
- // 2. If it's an array, handle each element
384
- if (Array.isArray(value)) {
385
- return value.map((v) => this.deserializeBigIntsAndDates(v, options));
386
- }
387
-
388
- // 3. If it's a plain object, recurse into each property
389
- if (value && typeof value === 'object') {
390
- return Object.fromEntries(
391
- Object.entries(value).map(([key, val]) => [
392
- key,
393
- this.deserializeBigIntsAndDates(val, options),
394
- ])
395
- );
396
- }
397
-
398
- // 4. Fallback for primitives, etc.
399
- return value;
400
- }
401
- }
402
-
403
- module.exports = RpcClient;
@@ -1 +0,0 @@
1
- let fetchFn;if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("globalThis.fetch is not available. Node.js 18+ is required.");fetchFn=globalThis.fetch.bind(globalThis);class RpcClient{#e;#t;#i;#s;#n;constructor(e,t={},i={}){this.#e=e,this.#t={"Content-Type":"application/json",...t},this.#i=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#n={safeEnabled:!0===i.safeEnabled,warnOnUnsafe:!1!==i.warnOnUnsafe,...i},this.#s={}}#a(){return++this.#i}async call(e,t,i=void 0,s={}){const n={jsonrpc:"2.0",method:e,id:void 0===i?this.#a():i};null!=t&&(n.params=this.serializeBigIntsAndDates(t));try{const e=await fetchFn(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...s},body:JSON.stringify(n),...this.#s});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const t=await e.json();if(t.error)throw t.error;const i=e.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===i)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===i&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===i};return this.deserializeBigIntsAndDates(t.result,a)}catch(e){throw console.error("RPC call failed:",e),e}}async notify(e,t={},i={}){await this.call(e,t,null,i)}async batch(e,t={}){const i=e.map(e=>{const t={jsonrpc:"2.0",method:e.method,id:void 0!==e.id?e.id:this.#a()};return void 0!==e.params&&null!==e.params&&(t.params=this.serializeBigIntsAndDates(e.params)),t});try{const e=await fetchFn(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...t},body:JSON.stringify(i),...this.#s});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const s=await e.json(),n=e.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===n)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===n&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===n};if(Array.isArray(s))return s.map(e=>{if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result,a)});if(s.error)throw s.error;return[this.deserializeBigIntsAndDates(s.result,a)]}catch(e){throw console.error("Batch RPC call failed:",e),e}}serializeBigIntsAndDates(e){if("bigint"==typeof e)return`${e.toString()}n`;if(e instanceof Date){const t=e.toISOString();return this.#n.safeEnabled?`D:${t}`:(this.#n.warnOnUnsafe&&console.warn("⚠️ Date serialization: Using plain ISO string format for JSON-RPC 2.0 compliance. Date objects will be deserialized as strings on the receiving end. Consider enabling safeEnabled or using string timestamps for better type safety."),t)}if("string"==typeof e)return this.#n.safeEnabled?`S:${e}`:(this.#n.warnOnUnsafe&&/^-?\d+n?$/.test(e)&&console.warn(`⚠️ String serialization: String "${e}" could be confused with BigInt. Consider enabling safeEnabled for disambiguation or use explicit typing.`),e);if(Array.isArray(e))return e.map(e=>this.serializeBigIntsAndDates(e));if(e&&"object"==typeof e){const t={};for(const[i,s]of Object.entries(e))t[i]=this.serializeBigIntsAndDates(s);return t}return e}deserializeBigIntsAndDates(e,t=null){const i=t?t.safeEnabled:this.#n.safeEnabled,s=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof e){if(i&&e.startsWith("S:"))return e.substring(2);if(i&&e.startsWith("D:")){const t=e.substring(2),i=new Date(t);if(!Number.isNaN(i.getTime()))return i}if(/^-?\d+n$/.test(e))return BigInt(e.slice(0,-1));if(!i&&s.test(e)){const t=new Date(e);if(!Number.isNaN(t.getTime()))return t}}return Array.isArray(e)?e.map(e=>this.deserializeBigIntsAndDates(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,i])=>[e,this.deserializeBigIntsAndDates(i,t)])):e}}module.exports=RpcClient;
@@ -1 +0,0 @@
1
- let e;if("undefined"==typeof BigInt||BigInt.prototype.toJSON||(BigInt.prototype.toJSON=function(){return this.toString()}),"undefined"!=typeof fetch)e=fetch;else try{e=require("node-fetch")}catch(t){if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("fetch is not available. Please install node-fetch or use Node.js 18+");e=globalThis.fetch}class t{#e;#t;#i;#s;#n;constructor(e,t={},i={}){this.#e=e,this.#t={"Content-Type":"application/json",...t},this.#i=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#n={safeEnabled:!0===i.safeEnabled,warnOnUnsafe:!1!==i.warnOnUnsafe,...i},this.#s={}}#r(){return++this.#i}async call(t,i,s=void 0,n={}){const r={jsonrpc:"2.0",method:t,id:void 0===s?this.#r():s};null!=i&&(r.params=this.serializeBigIntsAndDates(i));try{const t=await e(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...n},body:JSON.stringify(r),...this.#s});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const i=await t.json();if(i.error)throw i.error;const s=t.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===s)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===s&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===s};return this.deserializeBigIntsAndDates(i.result,a)}catch(e){throw console.error("RPC call failed:",e),e}}async notify(e,t={},i={}){await this.call(e,t,null,i)}async batch(t,i={}){const s=t.map(e=>{const t={jsonrpc:"2.0",method:e.method,id:void 0!==e.id?e.id:this.#r()};return void 0!==e.params&&null!==e.params&&(t.params=this.serializeBigIntsAndDates(e.params)),t});try{const t=await e(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...i},body:JSON.stringify(s),...this.#s});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const n=await t.json(),r=t.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===r)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===r&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===r};if(Array.isArray(n))return n.map(e=>{if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result,a)});if(n.error)throw n.error;return[this.deserializeBigIntsAndDates(n.result,a)]}catch(e){throw console.error("Batch RPC call failed:",e),e}}serializeBigIntsAndDates(e){if("bigint"==typeof e)return e.toString()+"n";if(e instanceof Date){const t=e.toISOString();return this.#n.safeEnabled?`D:${t}`:(this.#n.warnOnUnsafe&&console.warn("⚠️ Date serialization: Using plain ISO string format for JSON-RPC 2.0 compliance. Date objects will be deserialized as strings on the receiving end. Consider enabling safeEnabled or using string timestamps for better type safety."),t)}if("string"==typeof e)return this.#n.safeEnabled?"S:"+e:(this.#n.warnOnUnsafe&&/^-?\d+n?$/.test(e)&&console.warn(`⚠️ String serialization: String "${e}" could be confused with BigInt. Consider enabling safeEnabled for disambiguation or use explicit typing.`),e);if(Array.isArray(e))return e.map(e=>this.serializeBigIntsAndDates(e));if(e&&"object"==typeof e){const t={};for(const[i,s]of Object.entries(e))t[i]=this.serializeBigIntsAndDates(s);return t}return e}deserializeBigIntsAndDates(e,t=null){const i=t?t.safeEnabled:this.#n.safeEnabled,s=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof e){if(i&&e.startsWith("S:"))return e.substring(2);if(i&&e.startsWith("D:")){const t=e.substring(2),i=new Date(t);if(!isNaN(i.getTime()))return i}if(/^-?\d+n$/.test(e))return BigInt(e.slice(0,-1));if(!i&&s.test(e)){const t=new Date(e);if(!isNaN(t.getTime()))return t}}return Array.isArray(e)?e.map(e=>this.deserializeBigIntsAndDates(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,i])=>[e,this.deserializeBigIntsAndDates(i,t)])):e}}export default t;export{t as RpcClient};