rpc-express-toolkit 2.3.3 → 3.0.1
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/README.md +221 -9
- package/examples/example.js +17 -18
- package/examples/server-client-example.js +120 -114
- package/examples/ssl-development-example.js +100 -80
- package/package.json +5 -5
- package/src/batch.js +256 -237
- package/src/clients/rpc-client.js +125 -44
- package/src/clients/rpc-client.min.js +1 -1
- package/src/clients/rpc-client.min.mjs +1 -1
- package/src/clients/rpc-client.mjs +159 -96
- package/src/index.d.ts +220 -58
- package/src/index.js +319 -115
- package/src/logger.js +184 -153
- package/src/middleware.js +229 -224
- package/src/validation.js +220 -221
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/rpc-express-toolkit)
|
|
5
|
-
[](https://github.com/n-car/rpc-express-toolkit/actions/workflows/ci.yml)
|
|
6
|
-
[](https://coveralls.io/github/n-car/rpc-express-toolkit?branch=main)
|
|
5
|
+
<!-- [](https://github.com/n-car/rpc-express-toolkit/actions/workflows/ci.yml) -->
|
|
6
|
+
<!-- [](https://coveralls.io/github/n-car/rpc-express-toolkit?branch=main) -->
|
|
7
7
|
|
|
8
8
|
An enterprise-ready JSON-RPC 2.0 toolkit for Express.js applications with simplified APIs, structured logging, middleware system, schema validation, batch processing, and comprehensive BigInt/Date serialization support.
|
|
9
9
|
|
|
@@ -66,12 +66,13 @@ const express = require('express');
|
|
|
66
66
|
const { RpcEndpoint } = require('rpc-express-toolkit');
|
|
67
67
|
|
|
68
68
|
const app = express();
|
|
69
|
+
// Configure JSON middleware yourself
|
|
69
70
|
app.use(express.json());
|
|
70
71
|
|
|
71
72
|
// Context object to pass to method handlers
|
|
72
73
|
const context = { database: db, config: config };
|
|
73
74
|
|
|
74
|
-
// Create JSON-RPC server
|
|
75
|
+
// Create JSON-RPC server (validates JSON middleware compatibility)
|
|
75
76
|
const rpc = new RpcEndpoint(app, context);
|
|
76
77
|
|
|
77
78
|
// Add simple methods
|
|
@@ -173,6 +174,22 @@ const rpc = new RpcEndpoint(app, context, {
|
|
|
173
174
|
removeAdditional: true
|
|
174
175
|
},
|
|
175
176
|
|
|
177
|
+
// Safe serialization options
|
|
178
|
+
safeStringEnabled: true, // Add 'S:' prefix to strings (default: true)
|
|
179
|
+
safeDateEnabled: true, // Add 'D:' prefix to dates (default: true)
|
|
180
|
+
warnOnUnsafeString: true, // Warn when BigInt found with safeStringEnabled=false (default: true)
|
|
181
|
+
warnOnUnsafeDate: true, // Warn when Date found with safeDateEnabled=false (default: true)
|
|
182
|
+
|
|
183
|
+
// Other middleware options
|
|
184
|
+
cors: true,
|
|
185
|
+
rateLimit: { windowMs: 15 * 60 * 1000, max: 100 },
|
|
186
|
+
jsonOptions: { // Options to pass to express.json()
|
|
187
|
+
limit: '10mb', // Body size limit (default: uses maxBodySize)
|
|
188
|
+
strict: true, // Only parse arrays and objects
|
|
189
|
+
type: 'application/json' // Content-Type to parse
|
|
190
|
+
},
|
|
191
|
+
warnOnUnsafeDate: true, // Warn when Date found with safeDateEnabled=false (default: true)
|
|
192
|
+
|
|
176
193
|
// Feature flags
|
|
177
194
|
healthCheck: true,
|
|
178
195
|
metrics: true,
|
|
@@ -214,6 +231,67 @@ rpc.addMethod('getUser', {
|
|
|
214
231
|
|
|
215
232
|
### Middleware System
|
|
216
233
|
|
|
234
|
+
**JSON Middleware Validation**
|
|
235
|
+
|
|
236
|
+
RpcEndpoint validates that your Express router can handle JSON parsing properly:
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
const express = require('express');
|
|
240
|
+
const { RpcEndpoint } = require('rpc-express-toolkit');
|
|
241
|
+
|
|
242
|
+
const app = express();
|
|
243
|
+
app.use(express.json()); // You must configure JSON middleware
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// RpcEndpoint validates JSON middleware compatibility during construction
|
|
247
|
+
const rpc = new RpcEndpoint(app, context);
|
|
248
|
+
console.log('✅ JSON middleware validation passed');
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error('❌ JSON middleware validation failed:', error.message);
|
|
251
|
+
// This router cannot handle JSON-RPC requests properly.
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Benefits of JSON Middleware Validation:**
|
|
256
|
+
|
|
257
|
+
- 🛡️ **Early Error Detection**: Catches JSON parsing issues at startup, not runtime
|
|
258
|
+
- 🎯 **Express Version Compatibility**: Ensures Express ≥4.16.0 for `express.json()` support
|
|
259
|
+
- 📋 **Clear Error Messages**: Provides specific solutions for configuration problems
|
|
260
|
+
- 🔍 **Debug Information**: Logs validation status for troubleshooting
|
|
261
|
+
|
|
262
|
+
**JSON Middleware Validation:**
|
|
263
|
+
|
|
264
|
+
The system validates JSON middleware functionality during RpcEndpoint construction:
|
|
265
|
+
|
|
266
|
+
**Customizing JSON Options:**
|
|
267
|
+
```javascript
|
|
268
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
269
|
+
endpoint: '/api',
|
|
270
|
+
jsonOptions: {
|
|
271
|
+
limit: '50mb', // Increase body size limit
|
|
272
|
+
strict: false, // Allow non-objects/arrays
|
|
273
|
+
type: ['application/json', 'text/plain'] // Accept multiple content types
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Common JSON Validation Errors:**
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
// Error: Express version too old
|
|
282
|
+
// Solution: Update Express to ≥4.16.0
|
|
283
|
+
npm install express@latest
|
|
284
|
+
|
|
285
|
+
// Error: No JSON middleware configured
|
|
286
|
+
// Solution: Add JSON middleware to your router
|
|
287
|
+
app.use(express.json());
|
|
288
|
+
|
|
289
|
+
// Error: body-parser required for older Express
|
|
290
|
+
// Solution: Install and configure body-parser
|
|
291
|
+
npm install body-parser
|
|
292
|
+
app.use(require('body-parser').json());
|
|
293
|
+
```
|
|
294
|
+
|
|
217
295
|
## API Reference
|
|
218
296
|
|
|
219
297
|
### RpcEndpoint Class
|
|
@@ -505,6 +583,144 @@ const bigNum = await client.call('getBigNumber'); // Returns BigInt
|
|
|
505
583
|
const date = await client.call('getCurrentTime'); // Returns Date object
|
|
506
584
|
```
|
|
507
585
|
|
|
586
|
+
### Handling Numeric Strings with Leading Zeros
|
|
587
|
+
|
|
588
|
+
When working with numeric strings that need to preserve leading zeros (like IDs, codes, or references), be aware of BigInt auto-conversion behavior:
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
// ✅ CORRECT: Numeric strings without 'n' suffix are preserved
|
|
592
|
+
await client.call('processCode', { code: '0123456' });
|
|
593
|
+
// → Server receives: '0123456' (string)
|
|
594
|
+
// → Client receives: '0123456' (string) ✅
|
|
595
|
+
|
|
596
|
+
// ❌ POTENTIAL ISSUE: Strings ending with 'n' are converted to BigInt
|
|
597
|
+
await client.call('processRef', { ref: '012312031203120301230123123n' });
|
|
598
|
+
// → Server receives: '012312031203120301230123123n' (string)
|
|
599
|
+
// → Client receives: 12312031203120301230123123n (BigInt, leading zero lost!) ❌
|
|
600
|
+
|
|
601
|
+
// ✅ SOLUTIONS for strings that should remain strings:
|
|
602
|
+
|
|
603
|
+
// Solution 1: Use schema validation to force string type
|
|
604
|
+
rpc.addMethod('processRef', (req, ctx, params) => {
|
|
605
|
+
// params.ref will always be a string due to schema validation
|
|
606
|
+
return { processed: params.ref };
|
|
607
|
+
}, {
|
|
608
|
+
type: 'object',
|
|
609
|
+
properties: {
|
|
610
|
+
ref: { type: 'string' } // Forces string type, prevents BigInt conversion
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Solution 2: Use escape prefix for problematic strings
|
|
615
|
+
await client.call('processRef', { ref: 'S:012312031203120301230123123n' });
|
|
616
|
+
// Server strips prefix: const actualRef = params.ref.replace(/^S:/, '');
|
|
617
|
+
|
|
618
|
+
// Solution 3: Use different suffix
|
|
619
|
+
await client.call('processRef', { ref: '012312031203120301230123123_id' });
|
|
620
|
+
// → Remains string: '012312031203120301230123123_id' ✅
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Note:** Only strings explicitly ending with 'n' and containing only digits are auto-converted to BigInt. This preserves leading zeros for most numeric strings while maintaining BigInt functionality for intentional use.
|
|
624
|
+
|
|
625
|
+
### Safe Serialization with Prefixes
|
|
626
|
+
|
|
627
|
+
The toolkit provides safe serialization options to prevent confusion between strings, BigInt values, and dates:
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
// Enable safe serialization (default: true for both)
|
|
631
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
632
|
+
safeStringEnabled: true, // Strings get 'S:' prefix
|
|
633
|
+
safeDateEnabled: true // Dates get 'D:' prefix
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Example serialization with safe options enabled:
|
|
637
|
+
rpc.addMethod('getComplexData', () => {
|
|
638
|
+
return {
|
|
639
|
+
message: "Hello World", // → "S:Hello World"
|
|
640
|
+
timestamp: new Date(), // → "D:2023-06-15T10:30:00.000Z"
|
|
641
|
+
count: 42n, // → "42n"
|
|
642
|
+
value: 123 // → 123 (unchanged)
|
|
643
|
+
};
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Client automatically deserializes based on server headers:
|
|
647
|
+
const client = new RpcClient('http://localhost:3000/api');
|
|
648
|
+
const result = await client.call('getComplexData');
|
|
649
|
+
// result.message: "Hello World" (string, prefix removed)
|
|
650
|
+
// result.timestamp: Date object
|
|
651
|
+
// result.count: 42n (BigInt)
|
|
652
|
+
// result.value: 123 (number)
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
#### Cross-Configuration Support
|
|
656
|
+
|
|
657
|
+
Client and server can have different safe options - they communicate automatically via HTTP headers:
|
|
658
|
+
|
|
659
|
+
```javascript
|
|
660
|
+
// Server with safe dates disabled
|
|
661
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
662
|
+
safeStringEnabled: true, // S: prefix for strings
|
|
663
|
+
safeDateEnabled: false // No prefix for dates
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// Client with safe dates enabled
|
|
667
|
+
const client = new RpcClient('http://localhost:3000/api', {
|
|
668
|
+
safeStringEnabled: true, // S: prefix for strings
|
|
669
|
+
safeDateEnabled: true // D: prefix for dates
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// Data flows correctly despite different configurations:
|
|
673
|
+
// Client sends: { date: "D:2023-01-01T00:00:00.000Z" }
|
|
674
|
+
// Server receives: Date object (auto-deserialized using client's headers)
|
|
675
|
+
// Server responds: { date: "2023-01-01T00:00:00.000Z" } (no prefix, per server config)
|
|
676
|
+
// Client receives: Date object (auto-deserialized using server's headers)
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
#### Benefits of Safe Prefixes
|
|
680
|
+
|
|
681
|
+
- **Prevents ambiguity:** `"123n"` vs `"S:123n"` (string) vs `123n` (BigInt)
|
|
682
|
+
- **Cross-configuration:** Different client/server settings work seamlessly
|
|
683
|
+
- **Compact:** Short prefixes (`S:`, `D:`) minimize payload overhead
|
|
684
|
+
- **Automatic:** No manual intervention required, handled by headers
|
|
685
|
+
- **Backward compatible:** Can be disabled if needed
|
|
686
|
+
|
|
687
|
+
### Disabling Serialization Warnings
|
|
688
|
+
|
|
689
|
+
When safe options are disabled (`safeStringEnabled: false` or `safeDateEnabled: false`), the toolkit warns about potential data type confusion. You can disable these warnings to reduce console noise:
|
|
690
|
+
|
|
691
|
+
```javascript
|
|
692
|
+
// Disable all serialization warnings
|
|
693
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
694
|
+
safeStringEnabled: false, // Disable safe strings
|
|
695
|
+
safeDateEnabled: false, // Disable safe dates
|
|
696
|
+
warnOnUnsafeString: false, // Disable BigInt warnings
|
|
697
|
+
warnOnUnsafeDate: false // Disable Date warnings
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Disable only specific warnings
|
|
701
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
702
|
+
safeStringEnabled: false, // Still disabled
|
|
703
|
+
safeDateEnabled: false, // Still disabled
|
|
704
|
+
warnOnUnsafeString: false, // No BigInt warnings
|
|
705
|
+
warnOnUnsafeDate: true // Keep Date warnings
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Client also supports warning options
|
|
709
|
+
const client = new RpcClient('http://localhost:3000/api', {
|
|
710
|
+
safeStringEnabled: false,
|
|
711
|
+
warnOnUnsafeString: false // Disable client-side warnings
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
**Warning types:**
|
|
716
|
+
- `warnOnUnsafeString`: Warns when BigInt values are serialized without `safeStringEnabled`
|
|
717
|
+
- `warnOnUnsafeDate`: Warns when Date values are serialized without `safeDateEnabled`
|
|
718
|
+
|
|
719
|
+
**Use cases for disabling warnings:**
|
|
720
|
+
- Production environments where warnings would clutter logs
|
|
721
|
+
- Legacy applications that intentionally use unsafe serialization
|
|
722
|
+
- Bulk data processing where performance is critical
|
|
723
|
+
|
|
508
724
|
### Error Handling
|
|
509
725
|
|
|
510
726
|
```javascript
|
|
@@ -576,8 +792,8 @@ const devClientWithCA = new RpcClient('https://localhost:3000/api', {
|
|
|
576
792
|
ca // Pass your self-signed CA certificate
|
|
577
793
|
// or: agent: new require('https').Agent({ ca, rejectUnauthorized: true })
|
|
578
794
|
});
|
|
795
|
+
```
|
|
579
796
|
|
|
580
|
-
**⚠️ Security Warning**: Use `rejectUnauthorized: false` for development only. For better security, always prefer the `ca` solution to accept only your self-signed CA. In production always use valid certificates signed by a trusted CA.
|
|
581
797
|
```
|
|
582
798
|
|
|
583
799
|
**⚠️ Security Warning**: Use `rejectUnauthorized: false` for development only. For better security, always prefer the `ca` solution to accept only your self-signed CA. In production always use valid certificates signed by a trusted CA.
|
|
@@ -620,12 +836,8 @@ rpc.use('beforeCall', builtInMiddlewares.methodWhitelist([
|
|
|
620
836
|
|
|
621
837
|
// Request timing
|
|
622
838
|
rpc.use('beforeCall', builtInMiddlewares.timing());
|
|
839
|
+
```
|
|
623
840
|
```
|
|
624
|
-
if (!token || !isValidToken(token)) { // Replace `isValidToken` with your validation logic
|
|
625
|
-
throw new Error('Invalid or missing token');
|
|
626
|
-
}
|
|
627
|
-
return 'OK';
|
|
628
|
-
});
|
|
629
841
|
|
|
630
842
|
// Example token validation function (replace with your actual logic)
|
|
631
843
|
function isValidToken(token) {
|
package/examples/example.js
CHANGED
|
@@ -11,31 +11,30 @@ const context = { user: 'admin' };
|
|
|
11
11
|
const rpc = new RpcEndpoint(app, context);
|
|
12
12
|
|
|
13
13
|
rpc.addMethod('greet', (req, ctx, params) => {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const { name } = params;
|
|
15
|
+
return `Hello, ${name}!`;
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
rpc.addMethod('getTime', (
|
|
19
|
-
return new Date().toISOString();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
rpc.addMethod('invalid-token', (req, ctx, params) => {
|
|
23
|
-
// Example: Validate the token from request headers
|
|
24
|
-
const token = req.headers.authorization;
|
|
25
|
-
if (!token || !isValidToken(token)) { // Replace `isValidToken` with your validation logic
|
|
26
|
-
throw new Error('Invalid or missing token');
|
|
27
|
-
}
|
|
28
|
-
return 'OK';
|
|
29
|
-
});
|
|
18
|
+
rpc.addMethod('getTime', () => new Date().toISOString());
|
|
30
19
|
|
|
31
20
|
// Example token validation function (replace with your actual logic)
|
|
32
21
|
function isValidToken(token) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
// Simple example: Check if the token matches a predefined value
|
|
23
|
+
const expectedToken = 'my-secret-token'; // Replace with your actual token handling logic
|
|
24
|
+
return token === expectedToken;
|
|
36
25
|
}
|
|
37
26
|
|
|
27
|
+
rpc.addMethod('invalid-token', (req) => {
|
|
28
|
+
// Example: Validate the token from request headers
|
|
29
|
+
const token = req.headers.authorization;
|
|
30
|
+
if (!token || !isValidToken(token)) {
|
|
31
|
+
// Replace `isValidToken` with your validation logic
|
|
32
|
+
throw new Error('Invalid or missing token');
|
|
33
|
+
}
|
|
34
|
+
return 'OK';
|
|
35
|
+
});
|
|
36
|
+
|
|
38
37
|
const PORT = 3000;
|
|
39
38
|
app.listen(PORT, () => {
|
|
40
|
-
|
|
39
|
+
console.log(`Server listening on port ${PORT}`);
|
|
41
40
|
});
|
|
@@ -1,114 +1,120 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const { RpcEndpoint, RpcClient } = require('../src/index');
|
|
3
|
-
|
|
4
|
-
// === SERVER SETUP ===
|
|
5
|
-
const app = express();
|
|
6
|
-
app.use(express.json());
|
|
7
|
-
|
|
8
|
-
const context = {
|
|
9
|
-
database: new Map(),
|
|
10
|
-
userId: 'system'
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// Create JSON-RPC endpoint with clean names
|
|
14
|
-
const rpc = new RpcEndpoint(app, context);
|
|
15
|
-
|
|
16
|
-
// Add some methods
|
|
17
|
-
rpc.addMethod('saveData', (req, ctx, params) => {
|
|
18
|
-
const { key, value } = params;
|
|
19
|
-
ctx.database.set(key, value);
|
|
20
|
-
return { success: true, message: `Data saved with key: ${key}` };
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
rpc.addMethod('getData', (req, ctx, params) => {
|
|
24
|
-
const { key } = params;
|
|
25
|
-
const value = ctx.database.get(key);
|
|
26
|
-
if (value === undefined) {
|
|
27
|
-
throw new Error(`Key '${key}' not found`);
|
|
28
|
-
}
|
|
29
|
-
return { key, value };
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
rpc.addMethod('getAllData', (req, ctx) => {
|
|
33
|
-
return Array.from(ctx.database.entries()).map(([key, value]) => ({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
console.log('
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log('✅
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
console.log('
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { RpcEndpoint, RpcClient } = require('../src/index');
|
|
3
|
+
|
|
4
|
+
// === SERVER SETUP ===
|
|
5
|
+
const app = express();
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
|
|
8
|
+
const context = {
|
|
9
|
+
database: new Map(),
|
|
10
|
+
userId: 'system',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Create JSON-RPC endpoint with clean names
|
|
14
|
+
const rpc = new RpcEndpoint(app, context);
|
|
15
|
+
|
|
16
|
+
// Add some methods
|
|
17
|
+
rpc.addMethod('saveData', (req, ctx, params) => {
|
|
18
|
+
const { key, value } = params;
|
|
19
|
+
ctx.database.set(key, value);
|
|
20
|
+
return { success: true, message: `Data saved with key: ${key}` };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
rpc.addMethod('getData', (req, ctx, params) => {
|
|
24
|
+
const { key } = params;
|
|
25
|
+
const value = ctx.database.get(key);
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
throw new Error(`Key '${key}' not found`);
|
|
28
|
+
}
|
|
29
|
+
return { key, value };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
rpc.addMethod('getAllData', (req, ctx) => {
|
|
33
|
+
return Array.from(ctx.database.entries()).map(([key, value]) => ({
|
|
34
|
+
key,
|
|
35
|
+
value,
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
rpc.addMethod('testBigInt', (req, ctx, params) => {
|
|
40
|
+
const bigNumber = BigInt(params.number || '123456789012345678901234567890');
|
|
41
|
+
return {
|
|
42
|
+
original: params.number,
|
|
43
|
+
bigint: bigNumber,
|
|
44
|
+
doubled: bigNumber * 2n,
|
|
45
|
+
timestamp: new Date(),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Start server
|
|
50
|
+
const PORT = process.env.PORT || 3000;
|
|
51
|
+
const server = app.listen(PORT, () => {
|
|
52
|
+
console.log(`🚀 JSON-RPC Server running on http://localhost:${PORT}`);
|
|
53
|
+
console.log(`📡 Endpoint: http://localhost:${PORT}/api`);
|
|
54
|
+
|
|
55
|
+
// === CLIENT DEMO ===
|
|
56
|
+
setTimeout(async () => {
|
|
57
|
+
console.log('\n🔧 Testing client...');
|
|
58
|
+
|
|
59
|
+
const client = new RpcClient(`http://localhost:${PORT}/api`);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Test 1: Save some data
|
|
63
|
+
console.log('📝 Saving data...');
|
|
64
|
+
const saveResult = await client.call('saveData', {
|
|
65
|
+
key: 'test',
|
|
66
|
+
value: 'Hello World!',
|
|
67
|
+
});
|
|
68
|
+
console.log('✅ Save result:', saveResult);
|
|
69
|
+
|
|
70
|
+
// Test 2: Get data back
|
|
71
|
+
console.log('\n📖 Getting data...');
|
|
72
|
+
const getData = await client.call('getData', { key: 'test' });
|
|
73
|
+
console.log('✅ Get result:', getData);
|
|
74
|
+
|
|
75
|
+
// Test 3: Test BigInt handling
|
|
76
|
+
console.log('\n🔢 Testing BigInt...');
|
|
77
|
+
const bigIntResult = await client.call('testBigInt', {
|
|
78
|
+
number: '999999999999999999999999999999',
|
|
79
|
+
});
|
|
80
|
+
console.log('✅ BigInt result:', bigIntResult);
|
|
81
|
+
console.log(' - Original BigInt type:', typeof bigIntResult.bigint);
|
|
82
|
+
console.log(' - Doubled BigInt type:', typeof bigIntResult.doubled);
|
|
83
|
+
console.log(' - Timestamp type:', typeof bigIntResult.timestamp);
|
|
84
|
+
|
|
85
|
+
// Test 4: Get all data
|
|
86
|
+
console.log('\n📊 Getting all data...');
|
|
87
|
+
const allData = await client.call('getAllData');
|
|
88
|
+
console.log('✅ All data:', allData);
|
|
89
|
+
|
|
90
|
+
// Test 5: Test error handling
|
|
91
|
+
console.log('\n❌ Testing error handling...');
|
|
92
|
+
try {
|
|
93
|
+
await client.call('getData', { key: 'nonexistent' });
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.log('✅ Error caught correctly:', error.message);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('\n✨ All tests completed successfully!');
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('❌ Client error:', error);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Close server after demo
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
server.close(() => {
|
|
106
|
+
console.log('\n👋 Server closed');
|
|
107
|
+
process.exit(0);
|
|
108
|
+
});
|
|
109
|
+
}, 1000);
|
|
110
|
+
}, 1000);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Handle graceful shutdown
|
|
114
|
+
process.on('SIGINT', () => {
|
|
115
|
+
console.log('\n🛑 Shutting down server...');
|
|
116
|
+
server.close(() => {
|
|
117
|
+
console.log('👋 Server closed');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
});
|
|
120
|
+
});
|