rpc-express-toolkit 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,1071 +2,70 @@
2
2
 
3
3
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
4
  [![npm version](https://img.shields.io/npm/v/rpc-express-toolkit.svg)](https://www.npmjs.com/package/rpc-express-toolkit)
5
- <!-- [![Build Status](https://github.com/n-car/rpc-express-toolkit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/n-car/rpc-express-toolkit/actions/workflows/ci.yml) -->
6
- <!-- [![Coverage Status](https://coveralls.io/repos/github/n-car/rpc-express-toolkit/badge.svg?branch=main)](https://coveralls.io/github/n-car/rpc-express-toolkit?branch=main) -->
7
5
 
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.
6
+ Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, middleware, schema validation, batch support, and optional safe type disambiguation.
9
7
 
10
- ## Table of Contents
11
-
12
- - [Features](#features)
13
- - [Installation](#installation)
14
- - [Quick Start](#quick-start)
15
- - [Advanced Usage](#advanced-usage)
16
- - [Configuration Options](#configuration-options)
17
- - [Schema Validation](#schema-validation)
18
- - [Middleware System](#middleware-system)
19
- - [Batch Requests](#batch-requests)
20
- - [Structured Logging](#structured-logging)
21
- - [Built-in Middlewares](#built-in-middlewares)
22
- - [API Reference](#api-reference)
23
- - [Examples](#examples)
24
- - [Contributing](#contributing)
25
- - [License](#license)
26
-
27
- ## Features
28
-
29
- ### Core Features
30
- - **JSON-RPC 2.0 Compliance:** Fully adheres to the JSON-RPC 2.0 specification
31
- - **Server & Client Support:** Provides both server endpoint and client classes
32
- - **Asynchronous Support:** Handle asynchronous operations seamlessly with Promises
33
- - **BigInt & Date Serialization:** Robust serialization/deserialization with timezone support
34
- - **Cross-Platform:** Works in both browser and Node.js environments
35
- - **Error Handling:** Comprehensive error responses with sanitization options
36
-
37
- ### Enterprise Features
38
- - **🔧 Structured Logging:** Configurable logging with multiple transports and levels
39
- - **⚡ Middleware System:** Extensible middleware with built-in rate limiting, CORS, auth
40
- - **✅ Schema Validation:** JSON Schema validation with Ajv and schema builder utilities
41
- - **📦 Batch Processing:** Efficient batch request handling with concurrent processing
42
- - **📊 Health & Metrics:** Built-in health checks and metrics endpoints
43
- - **🔒 Security:** Method whitelisting, authentication, and error sanitization
44
- - **🎯 Performance:** Request timing, caching support, and optimized serialization
8
+ ## Quick Start
45
9
 
46
- ## Installation
10
+ ### Installation
47
11
 
48
12
  ```bash
49
13
  npm install rpc-express-toolkit
50
- ```
51
-
52
- Or using yarn:
53
-
54
- ```bash
14
+ # or
55
15
  yarn add rpc-express-toolkit
56
16
  ```
57
17
 
58
- **Note:** For Node.js environments older than v18, you may need to install `node-fetch` for HTTP requests:
59
-
60
- ## Quick Start
61
-
62
- ### Basic Setup
18
+ ### Server
63
19
 
64
20
  ```javascript
65
21
  const express = require('express');
66
22
  const { RpcEndpoint } = require('rpc-express-toolkit');
67
23
 
68
24
  const app = express();
69
- // Configure JSON middleware yourself
70
- app.use(express.json());
25
+ app.use(express.json()); // required
71
26
 
72
- // Context object to pass to method handlers
73
- const context = { database: db, config: config };
27
+ const context = { database: db, config };
74
28
 
75
- // Create JSON-RPC server (validates JSON middleware compatibility)
76
29
  const rpc = new RpcEndpoint(app, context);
77
-
78
- // Add simple methods
79
- rpc.addMethod('add', (req, ctx, params) => {
80
- const { a, b } = params;
81
- return a + b;
82
- });
83
-
84
- rpc.addMethod('getUser', async (req, ctx, params) => {
85
- const { userId } = params;
86
- const user = await ctx.database.users.findById(userId);
87
- return user;
88
- });
89
-
90
- app.listen(3000, () => {
91
- console.log('Server running on port 3000');
92
- });
93
- ```
94
-
95
-
96
- ### Client Usage
97
-
98
- ```javascript
99
- const { RpcClient } = require('rpc-express-toolkit');
100
-
101
- // Node.js client
102
- const client = new RpcClient('http://localhost:3000/api');
103
-
104
- // Make RPC calls
105
- const result = await client.call('add', { a: 5, b: 3 });
106
- console.log(result); // 8
107
-
108
- // Browser client (include the client script)
109
- const client = new RpcClient('/api');
110
- const user = await client.call('getUser', { userId: 123 });
111
- ```
112
-
113
-
114
- #### ⚠️ SSL and self-signed certificates in development (Node.js)
115
-
116
- If you need to connect to a server with a self-signed certificate during development, set the following environment variable **before** starting your Node.js process:
117
-
118
- ```js
119
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
120
- ```
121
-
122
- **Warning:**
123
- - This disables SSL certificate validation for all HTTPS requests in the Node.js process.
124
- - Use this only in development environments!
125
- - Never commit or enable this setting in production.
126
-
127
- Advanced options like `agent` and `ca` have been removed for simplicity and compatibility with modern Node.js. This is the only universal and reliable way to bypass SSL validation for development with self-signed certificates.
128
-
129
- ## Advanced Usage
130
-
131
- ### Configuration Options
132
-
133
- ```javascript
134
- const rpc = new RpcEndpoint(app, context, {
135
- endpoint: '/api',
136
-
137
- // Logging configuration
138
- logging: {
139
- level: 'info',
140
- format: 'json',
141
- transports: ['console', 'file'],
142
- file: {
143
- filename: 'rpc.log',
144
- maxsize: 10485760, // 10MB
145
- maxFiles: 5
146
- }
147
- },
148
-
149
- // Built-in middleware
150
- cors: {
151
- origin: ['http://localhost:3000'],
152
- credentials: true
153
- },
154
-
155
- rateLimit: {
156
- windowMs: 15 * 60 * 1000, // 15 minutes
157
- max: 100 // limit each IP to 100 requests per windowMs
158
- },
159
-
160
- auth: {
161
- required: true,
162
- verify: (req) => {
163
- const token = req.headers.authorization;
164
- return verifyToken(token);
165
- }
166
- },
167
-
168
- methodWhitelist: ['add', 'subtract', 'getUser'],
169
-
170
- // Schema validation
171
- validation: {
172
- strict: true,
173
- coerceTypes: true,
174
- removeAdditional: true
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
-
193
- // Feature flags
194
- healthCheck: true,
195
- metrics: true,
196
- timing: true
197
- });
198
- ```
199
-
200
- ### Schema Validation
201
-
202
- ```javascript
203
- const { SchemaBuilder, commonSchemas } = require('rpc-express-toolkit');
204
-
205
- // Using schema builder
206
- const addSchema = SchemaBuilder.object({
207
- a: SchemaBuilder.number().required(),
208
- b: SchemaBuilder.number().required()
209
- });
210
-
211
- // Register method with schema
212
- rpc.addMethod('add', {
213
- handler: (req, ctx, params) => params.a + params.b,
214
- schema: addSchema
215
- });
216
-
217
- // Using common schemas
218
- rpc.addMethod('getUser', {
219
- handler: async (req, ctx, params) => {
220
- return await ctx.database.users.findById(params.userId);
221
- },
222
- schema: {
223
- type: 'object',
224
- properties: {
225
- userId: commonSchemas.positiveInteger
226
- },
227
- required: ['userId']
228
- }
229
- });
230
- ```
231
-
232
- ### Middleware System
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
-
295
- ## API Reference
296
-
297
- ### RpcEndpoint Class
298
-
299
- #### Constructor
300
-
301
- ```javascript
302
- new RpcEndpoint(router, context, options)
303
- ```
304
-
305
- **Parameters:**
306
- - `router` (Express.Router): Express router instance
307
- - `context` (Object): Context object passed to method handlers
308
- - `options` (Object|string): Configuration options or endpoint path
309
-
310
- **Options:**
311
- - `endpoint` (string): Endpoint path (default: '/api')
312
- - `logging` (Object): Logging configuration
313
- - `cors` (Object): CORS configuration
314
- - `rateLimit` (Object): Rate limiting configuration
315
- - `auth` (Object): Authentication configuration
316
- - `methodWhitelist` (Array): Allowed method names
317
- - `validation` (Object): Schema validation options
318
- - `healthCheck` (boolean): Enable health check endpoint
319
- - `metrics` (boolean): Enable metrics endpoint
320
- - `timing` (boolean): Enable request timing
321
-
322
- #### Methods
323
-
324
- ##### `addMethod(name, handlerOrConfig, schema)`
325
-
326
- Register a new JSON-RPC method.
327
-
328
- ```javascript
329
- // Simple method
330
- rpc.addMethod('add', (req, ctx, params) => {
331
- return params.a + params.b;
332
- });
333
-
334
- // Method with schema validation
335
- rpc.addMethod('getUser', {
336
- handler: async (req, ctx, params) => {
337
- return await ctx.db.users.findById(params.userId);
338
- },
339
- schema: {
340
- type: 'object',
341
- properties: {
342
- userId: { type: 'number', minimum: 1 }
343
- },
344
- required: ['userId']
345
- }
346
- });
347
- ```
348
-
349
- ##### `use(hook, middleware)`
350
-
351
- Add middleware for specific execution hooks.
352
-
353
- ```javascript
354
- rpc.use('beforeCall', async (context) => {
355
- // Pre-processing
356
- return context;
357
- });
358
-
359
- rpc.use('afterCall', async (context) => {
360
- // Post-processing
361
- return context;
362
- });
363
-
364
- rpc.use('onError', async (context) => {
365
- // Error handling
366
- });
367
- ```
368
-
369
- ##### `removeMethod(name)`
370
-
371
- Remove a registered method.
372
-
373
- ```javascript
374
- rpc.removeMethod('methodName');
375
- ```
376
-
377
- ##### `getMethod(name)`
378
-
379
- Get method configuration.
380
-
381
- ```javascript
382
- const config = rpc.getMethod('methodName');
383
- ```
384
-
385
- ##### `listMethods()`
386
-
387
- List all registered methods.
388
-
389
- ```javascript
390
- const methods = rpc.listMethods();
391
- ```
392
-
393
- ##### `getMetrics()`
394
-
395
- Get current metrics data.
396
-
397
- ```javascript
398
- const metrics = rpc.getMetrics();
399
- ```
400
-
401
- #### Properties
402
-
403
- - `endpoint`: The endpoint path
404
- - `methods`: All registered methods
405
- - `logger`: Logger instance
406
- - `middleware`: Middleware manager
407
- - `validator`: Schema validator
408
-
409
- ### RpcClient Class
410
-
411
- #### Constructor
412
-
413
- ```javascript
414
- new RpcClient(baseUrl, options)
415
- ```
416
-
417
- #### Methods
418
-
419
-
420
- ##### `call(method, params, options)`
421
-
422
- Make a single RPC call.
423
-
424
- ```javascript
425
- // If the method does not require parameters, you can omit the params argument:
426
- const result = await client.call('ping');
427
-
428
- // If you pass params as undefined or null, the field will be omitted from the JSON-RPC payload (per spec):
429
- await client.call('ping', undefined);
430
- await client.call('ping', null);
431
-
432
- // If you pass an object or array, it will be sent as params:
433
- await client.call('add', { a: 1, b: 2 });
434
- ```
435
-
436
- > **Note:**
437
- > The `params` field is omitted from the JSON-RPC request if you pass `undefined` or `null`, as required by the JSON-RPC 2.0 specification. If you pass an object or array, it will be included as `params`.
438
-
439
- ##### `batch(requests)`
440
-
441
- Make batch RPC calls.
442
-
443
- ```javascript
444
- const results = await client.batch([
445
- { method: 'add', params: { a: 1, b: 2 } },
446
- { method: 'subtract', params: { a: 5, b: 3 } }
447
- ]);
448
- ```
449
-
450
- ##### `notify(method, params)`
451
-
452
- Send notification (no response expected).
453
-
454
- ```javascript
455
- await client.notify('logEvent', { event: 'user_login' });
456
- ```
457
-
458
- ### Logger Class
459
-
460
- Structured logging with configurable transports and levels.
461
-
462
- ```javascript
463
- const logger = new Logger({
464
- level: 'info',
465
- format: 'json',
466
- transports: ['console', 'file']
467
- });
468
-
469
- logger.info('Message', { key: 'value' });
470
- logger.error('Error', { error: err.message });
471
- ```
472
-
473
- ### SchemaValidator Class
474
-
475
- JSON Schema validation with Ajv.
476
-
477
- ```javascript
478
- const validator = new SchemaValidator({
479
- strict: true,
480
- coerceTypes: true
481
- });
482
-
483
- const result = validator.validate(data, schema);
484
- ```
485
-
486
- ### SchemaBuilder
487
-
488
- Utility for building JSON schemas.
489
-
490
- ```javascript
491
- const schema = SchemaBuilder.object({
492
- name: SchemaBuilder.string().required(),
493
- age: SchemaBuilder.number().min(0).max(150),
494
- email: SchemaBuilder.string().format('email')
495
- });
496
- ```
497
-
498
- ### BatchHandler
499
-
500
- Handles batch request processing with concurrency control.
501
-
502
- ```javascript
503
- const batchHandler = new BatchHandler(endpoint, {
504
- maxConcurrency: 10,
505
- timeout: 30000
506
- });
507
- ```
508
-
509
- ## Examples
510
-
511
- ### Enterprise Configuration
512
-
513
- ```javascript
514
- const express = require('express');
515
- const { RpcEndpoint, SchemaBuilder } = require('rpc-express-toolkit');
516
-
517
- const app = express();
518
- app.use(express.json());
519
-
520
- const rpc = new RpcEndpoint(app, { db, config }, {
521
- endpoint: '/api/v1',
522
- logging: {
523
- level: 'info',
524
- format: 'json',
525
- transports: ['console', 'file'],
526
- file: { filename: 'api.log' }
527
- },
528
- cors: {
529
- origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
530
- credentials: true
531
- },
532
- rateLimit: {
533
- windowMs: 15 * 60 * 1000,
534
- max: 100
535
- },
536
- auth: {
537
- required: true,
538
- verify: async (req) => {
539
- const token = req.headers.authorization?.replace('Bearer ', '');
540
- return await verifyJWT(token);
541
- }
542
- },
543
- validation: {
544
- strict: true,
545
- coerceTypes: true
546
- },
547
- healthCheck: true,
548
- metrics: true
549
- });
550
-
551
- // Add methods with validation
552
- rpc.addMethod('createUser', {
553
- handler: async (req, ctx, params) => {
554
- const user = await ctx.db.users.create(params);
555
- return { id: user.id, message: 'User created successfully' };
556
- },
557
- schema: SchemaBuilder.object({
558
- name: SchemaBuilder.string().min(1).max(100).required(),
559
- email: SchemaBuilder.string().format('email').required(),
560
- age: SchemaBuilder.number().min(0).max(150).optional()
561
- })
562
- });
30
+ rpc.addMethod('add', (req, ctx, params) => params.a + params.b);
563
31
 
564
32
  app.listen(3000);
565
33
  ```
566
34
 
567
- ### BigInt and Date Handling
35
+ ### Client
568
36
 
569
37
  ```javascript
570
- // BigInt serialization
571
- rpc.addMethod('getBigNumber', () => {
572
- return BigInt('9007199254740991123456789'); // Automatically serialized to string
573
- });
574
-
575
- // Date serialization
576
- rpc.addMethod('getCurrentTime', () => {
577
- return new Date(); // Automatically serialized to ISO string
578
- });
579
-
580
- // Client receives properly typed data
581
- const client = new RpcClient('http://localhost:3000/api');
582
- const bigNum = await client.call('getBigNumber'); // Returns BigInt
583
- const date = await client.call('getCurrentTime'); // Returns Date object
584
- ```
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
-
724
- ### Error Handling
725
-
726
- ```javascript
727
- rpc.addMethod('mayFail', (req, ctx, params) => {
728
- if (params.shouldFail) {
729
- const error = new Error('Something went wrong');
730
- error.code = -32001; // Custom error code
731
- error.data = { details: 'Additional error info' };
732
- throw error;
733
- }
734
- return 'success';
735
- });
736
-
737
- // Client error handling
738
- try {
739
- await client.call('mayFail', { shouldFail: true });
740
- } catch (error) {
741
- console.log(error.code); // -32001
742
- console.log(error.message); // 'Something went wrong'
743
- console.log(error.data); // { details: 'Additional error info' }
744
- }
745
- ```
746
- userId: 123,
747
- action: 'login',
748
- ip: req.ip
749
- });
750
-
751
- logger.error('Database error', {
752
- error: err.message,
753
- query: 'SELECT * FROM users',
754
- duration: 150
755
- });
756
-
757
- // RPC calls are automatically logged
758
- // [2024-01-01T10:00:00.000Z] INFO: RPC call - {"method":"getUser","params":{"userId":123},"id":"req-123","ip":"192.168.1.1"}
759
- // [2024-01-01T10:00:00.050Z] INFO: RPC success - {"method":"getUser","id":"req-123","duration":50,"resultSize":245}
760
- ```
761
-
762
- ### Client Configuration
763
-
764
- The `RpcClient` supports various configuration options for different environments, **inclusa la gestione di certificati self-signed**:
765
-
766
- ```javascript
767
- const fs = require('fs');
768
38
  const { RpcClient } = require('rpc-express-toolkit');
769
-
770
- // Basic client
771
- const client = new RpcClient('https://api.example.com/rpc');
772
-
773
- // Client con header personalizzati
774
- const clientWithHeaders = new RpcClient('https://api.example.com/rpc', {
775
- 'Authorization': 'Bearer your-token',
776
- 'X-API-Key': 'your-api-key',
777
- 'User-Agent': 'MyApp/1.0'
778
- });
779
-
780
- // Development client with self-signed certificate (accepts any certificate, DO NOT use in production)
781
- const devClient = new RpcClient('https://localhost:3000/api', {
782
- 'Authorization': 'Bearer dev-token'
783
- }, {
784
- rejectUnauthorized: false // Development only!
785
- });
786
-
787
- // Development client with custom self-signed CA (safer: accepts only your CA)
788
- const ca = fs.readFileSync('path/to/your/selfsigned-ca.pem');
789
- const devClientWithCA = new RpcClient('https://localhost:3000/api', {
790
- 'Authorization': 'Bearer dev-token'
791
- }, {
792
- ca // Pass your self-signed CA certificate
793
- // or: agent: new require('https').Agent({ ca, rejectUnauthorized: true })
794
- });
795
- ```
796
-
797
- ```
798
-
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.
800
-
801
- ### Built-in Middlewares
802
-
803
- ```javascript
804
- const { builtInMiddlewares } = require('rpc-express-toolkit');
805
-
806
- // Rate limiting
807
- rpc.use('beforeCall', builtInMiddlewares.rateLimit({
808
- windowMs: 15 * 60 * 1000, // 15 minutes
809
- max: 100, // limit each IP to 100 requests per windowMs
810
- message: 'Too many requests from this IP'
811
- }));
812
-
813
- // CORS
814
- rpc.use('beforeCall', builtInMiddlewares.cors({
815
- origin: ['http://localhost:3000', 'https://myapp.com'],
816
- credentials: true,
817
- methods: ['POST']
818
- }));
819
-
820
- // Authentication
821
- rpc.use('beforeCall', builtInMiddlewares.auth({
822
- required: true,
823
- verify: async (req) => {
824
- const token = req.headers.authorization?.replace('Bearer ', '');
825
- return await verifyJWT(token);
826
- },
827
- onError: (req, error) => {
828
- console.log('Auth failed:', error.message);
829
- }
830
- }));
831
-
832
- // Method whitelisting
833
- rpc.use('beforeCall', builtInMiddlewares.methodWhitelist([
834
- 'add', 'subtract', 'getUser', 'updateUser'
835
- ]));
836
-
837
- // Request timing
838
- rpc.use('beforeCall', builtInMiddlewares.timing());
839
- ```
840
- ```
841
-
842
- // Example token validation function (replace with your actual logic)
843
- function isValidToken(token) {
844
- // Simple example: Check if the token matches a predefined value
845
- const expectedToken = 'my-secret-token'; // Replace with your actual token handling logic
846
- return token === expectedToken;
847
- }
848
-
849
- // Start the server
850
- const PORT = process.env.PORT || 3000;
851
- app.listen(PORT, () => {
852
- console.log(`Server is running on port ${PORT}`);
853
- });
854
- ```
855
-
856
- ### Adding Methods
857
-
858
- You can add RPC methods using the `addMethod` function. Each method receives the `context` and `params` as arguments.
859
-
860
- ```javascript
861
- // Synchronous method
862
- rpc.addMethod('multiply', (req, ctx, params) => {
863
- const { a, b } = params;
864
- return a * b;
865
- });
866
-
867
- // Asynchronous method
868
- rpc.addMethod('divide', async (req, ctx, params) => {
869
- const { a, b } = params;
870
- if (b === 0) {
871
- throw new Error('Division by zero');
872
- }
873
- // Simulate async operation
874
- return Promise.resolve(a / b);
875
- });
876
- ```
877
-
878
- ## API
879
-
880
- ### RpcEndpoint Class
881
-
882
- A class to handle JSON-RPC 2.0 requests in Express.js applications.
883
-
884
- #### Constructor
885
-
886
- ```javascript
887
- new RpcEndpoint(router, context, endpoint = '/api')
888
- ```
889
-
890
- - **router**: *(Express.Router)* - The Express router to attach the endpoint to.
891
- - **context**: *(Object)* - The context object to pass to the method handlers.
892
- - **endpoint** *(optional)*: *(string)* - The endpoint path (default is `/api`).
893
-
894
- #### addMethod
895
-
896
- ```javascript
897
- addMethod(name, handler)
898
- ```
899
-
900
- - **name**: *(string)* - The name of the RPC method.
901
- - **handler**: *(Function)* - The function that handles the method. Receives `req` (the Request object) `context` and `params` as arguments.
902
-
903
- ```javascript
904
- (req, context, params) => { /* ... */ }
905
- ```
906
-
907
- #### Properties
908
-
909
- - **endpoint**: *(string)* - Returns the endpoint path.
910
- - **methods**: *(Object)* - Returns the registered methods.
911
-
912
- #### reply
913
-
914
- ```javascript
915
- reply(res, { id, result, error })
916
- ```
917
-
918
- - **res**: *(Express.Response)* - The Express response object.
919
- - **responsePayload**: *(Object)* - The JSON-RPC response payload.
920
-
921
- ```javascript
922
- {
923
- id: string | number | null,
924
- result: any,
925
- error: {
926
- code: number,
927
- message: string,
928
- data?: any
929
- }
930
- }
931
- ```
932
-
933
- #### serializeBigInts
934
-
935
- ```javascript
936
- serializeBigInts(value)
39
+ const client = new RpcClient('http://localhost:3000/api');
40
+ const sum = await client.call('add', { a: 1, b: 2 });
937
41
  ```
938
42
 
939
- - **value**: *(any)* - The value to serialize.
940
- - **returns**: *(any)* - The serialized value with `BigInt` converted to strings.
941
-
942
- Recursively converts `BigInt` values to strings to ensure JSON serialization compatibility.
943
-
944
- ## Examples
945
-
946
- Check out the [examples](./examples) directory for more usage examples.
947
-
948
- ### Example: Complete Express Server Integration
43
+ ### Safe Type Disambiguation (optional)
949
44
 
950
- ```javascript
951
- const express = require('express');
952
- const { RpcEndpoint } = require('rpc-express-toolkit');
45
+ Safe type disambiguation is **disabled by default** for maximum JSON-RPC 2.0 compatibility. The library will show educational warnings when you use BigInt or Date values, helping you understand when these types need special handling. Enable safe serialization with `safeEnabled: true` to add safe prefixes (`S:` for strings, `D:` for dates), or suppress warnings with `warnOnUnsafe: false`. See `README_ADVANCED.md#safe-serialization` for full details and best practices.
953
46
 
954
- const app = express();
47
+ ## Minimal API
955
48
 
956
- app.use(express.json());
49
+ - `new RpcEndpoint(router, context, options?)` — create and attach a JSON-RPC endpoint.
50
+ - `rpc.addMethod(name, handlerOrConfig)` — register a method.
51
+ - `new RpcClient(baseUrl, headers?, options?)` — client for making calls.
52
+ - `client.call(method, params?)` — single call.
53
+ - `client.batch([...])` — batch.
54
+ - `client.notify(method, params?)` — notification.
957
55
 
958
- const context = { user: 'admin' };
56
+ ## Full Details
959
57
 
960
- // Attach to the app router at POST /api endpoint
961
- const rpc = new RpcEndpoint(app, context);
962
-
963
- rpc.addMethod('greet', (req, ctx, params) => {
964
- const { name } = params;
965
- return `Hello, ${name}!`;
966
- });
967
-
968
- rpc.addMethod('getTime', () => {
969
- return new Date().toISOString();
970
- });
971
-
972
- const PORT = 3000;
973
- app.listen(PORT, () => {
974
- console.log(`Server listening on port ${PORT}`);
975
- });
976
- ```
58
+ For advanced configuration, middleware, structured logging, safe serialization, error handling, and more, see `README_ADVANCED.md`.
977
59
 
978
60
  ## Contributing
979
61
 
980
- Contributions are welcome! Please feel free to submit a Pull Request.
981
-
982
- ### Development Setup
983
-
984
- 1. Fork the repository
985
- 2. Clone your fork: `git clone https://github.com/yourusername/rpc-express-toolkit.git`
986
- 3. Install dependencies: `npm install`
987
- 4. Run tests: `npm test`
988
- 5. Run linting: `npm run lint`
989
-
990
- ### Running Tests
991
-
992
62
  ```bash
993
- npm test # Run all tests
994
- npm run test:watch # Run tests in watch mode
995
- npm run test:coverage # Run tests with coverage report
63
+ git clone https://github.com/n-car/rpc-express-toolkit.git
64
+ npm install
65
+ npm test
66
+ npm run lint
996
67
  ```
997
68
 
998
- ### Code Style
999
-
1000
- This project uses ESLint and Prettier for code formatting. Run `npm run lint` to check code style.
1001
-
1002
- ## Security
1003
-
1004
- ### Best Practices
1005
-
1006
- - Always validate input parameters using schema validation
1007
- - Use authentication middleware for sensitive operations
1008
- - Enable rate limiting to prevent abuse
1009
- - Sanitize error messages in production environments
1010
- - Use HTTPS in production
1011
- - Implement proper logging for audit trails
1012
-
1013
- ## Performance
1014
-
1015
- ### Optimization Tips
1016
-
1017
- - Use batch requests for multiple operations
1018
- - Implement caching where appropriate
1019
- - Use connection pooling for database operations
1020
- - Monitor metrics and logs for performance bottlenecks
1021
- - Use clustering for high-load applications
1022
-
1023
- ### Benchmarks
1024
-
1025
- Performance benchmarks on a typical server setup:
1026
-
1027
- - Single request: ~1-2ms overhead
1028
- - Batch request (10 items): ~5-10ms total
1029
- - Memory usage: ~50MB for 10,000 concurrent connections
1030
- - Throughput: ~5,000 requests/second
1031
-
1032
69
  ## License
1033
70
 
1034
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1035
-
1036
- ## Changelog
1037
-
1038
- ### v2.0.0 (Enterprise Edition)
1039
-
1040
- - ✨ **BREAKING**: Clean API with `RpcEndpoint` and `RpcClient` classes (100% backward compatible)
1041
- - ✨ **BREAKING**: Renamed package to `rpc-express-toolkit` for better clarity
1042
- - ✨ Added structured logging with configurable transports
1043
- - ✨ Implemented middleware system with built-in middlewares
1044
- - ✨ Added schema validation with Ajv and schema builder
1045
- - ✨ Implemented batch request processing
1046
- - ✨ Added health check and metrics endpoints
1047
- - ✨ Enhanced BigInt/Date serialization with timezone support
1048
- - ✨ Added authentication and authorization middleware
1049
- - ✨ Implemented rate limiting and CORS support
1050
- - ✨ Added method whitelisting and security features
1051
- - ✨ Clean API: `RpcEndpoint`/`RpcClient` classes
1052
- - 🔧 Improved error handling and sanitization
1053
- - 🔧 Enhanced client with batch and notification support
1054
- - 🔧 Added comprehensive test coverage
1055
- - 📚 Updated documentation with enterprise features
1056
-
1057
- ## Support
1058
-
1059
- - 📖 [Documentation](https://github.com/n-car/rpc-express-toolkit/wiki)
1060
- - 🐛 [Issue Tracker](https://github.com/n-car/rpc-express-toolkit/issues)
1061
- - 💬 [Discussions](https://github.com/n-car/rpc-express-toolkit/discussions)
1062
-
1063
- ## Acknowledgments
1064
-
1065
- - JSON-RPC 2.0 Specification
1066
- - Express.js community
1067
- - Ajv JSON Schema validator
1068
- - All contributors and users
1069
-
1070
- ---
1071
-
1072
- Made with ❤️ by n-car
71
+ MIT. See [LICENSE](LICENSE).