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.
package/package.json CHANGED
@@ -1,126 +1,124 @@
1
- {
2
- "name": "rpc-express-toolkit",
3
- "version": "4.2.1",
4
- "description": "Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, structured logging, middleware system, schema validation, comprehensive BigInt/Date support. JSON middleware validation and explicit configuration.",
5
- "main": "src/index.js",
6
- "types": "src/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./src/index.d.ts",
10
- "require": "./src/index.js",
11
- "import": "./src/index.js"
12
- },
13
- "./safe": {
14
- "types": "./src/safe.d.ts",
15
- "require": "./src/safe.js",
16
- "import": "./src/safe.js"
17
- }
18
- },
19
- "files": [
20
- "src/",
21
- "examples/",
22
- "README.md",
23
- "LICENSE"
24
- ],
25
- "scripts": {
26
- "test": "jest",
27
- "test:coverage": "jest --coverage",
28
- "test:watch": "jest --watch",
29
- "lint": "eslint src/ examples/",
30
- "lint:fix": "eslint src/ examples/ --fix",
31
- "clean": "node -e \"require(\u0027fs\u0027).existsSync(\u0027dist\u0027) \u0026\u0026 require(\u0027fs\u0027).rmSync(\u0027dist\u0027, {recursive: true})\"",
32
- "build": "npm run build:minify",
33
- "build:minify": "npm run build:js \u0026\u0026 npm run build:mjs",
34
- "build:js": "terser src/clients/rpc-client.js -o src/clients/rpc-client.min.js --compress --mangle --comments false --ecma 2020",
35
- "build:mjs": "terser src/clients/rpc-client.mjs -o src/clients/rpc-client.min.mjs --compress --mangle --comments false --module --ecma 2020",
36
- "prepublishOnly": "npm run build",
37
- "precommit": "npm run lint \u0026\u0026 npm test"
38
- },
39
- "repository": {
40
- "type": "git",
41
- "url": "git+https://github.com/n-car/rpc-express-toolkit.git"
42
- },
43
- "keywords": [
44
- "json-rpc",
45
- "jsonrpc",
46
- "json-rpc-2.0",
47
- "express",
48
- "toolkit",
49
- "rpc",
50
- "server",
51
- "client",
52
- "middleware",
53
- "validation",
54
- "schema",
55
- "ajv",
56
- "bigint",
57
- "date",
58
- "enterprise",
59
- "nodejs",
60
- "node",
61
- "browser",
62
- "ssl",
63
- "self-signed",
64
- "secure",
65
- "fetch",
66
- "batch",
67
- "typescript",
68
- "logging",
69
- "cors",
70
- "rate-limit",
71
- "authentication"
72
- ],
73
- "author": "Nicola Carpanese \u003cnicola.carpanese@gmail.com\u003e",
74
- "license": "MIT",
75
- "bugs": {
76
- "url": "https://github.com/n-car/rpc-express-toolkit/issues"
77
- },
78
- "homepage": "https://github.com/n-car/rpc-express-toolkit#readme",
79
- "engines": {
80
- "node": "\u003e=18.0.0"
81
- },
82
- "devDependencies": {
83
- "eslint": "^8.57.1",
84
- "eslint-config-airbnb-base": "^15.0.0",
85
- "eslint-config-prettier": "^9.1.2",
86
- "eslint-plugin-import": "^2.31.0",
87
- "eslint-plugin-prettier": "^5.5.3",
88
- "husky": "^8.0.0",
89
- "jest": "^29.7.0",
90
- "lint-staged": "^13.0.0",
91
- "prettier": "^3.6.2",
92
- "supertest": "^6.3.3",
93
- "terser": "^5.43.1"
94
- },
95
- "dependencies": {
96
- "ajv": "^8.12.0",
97
- "ajv-formats": "^2.1.1",
98
- "express": "^4.21.2",
99
- "nested-error-stacks": "^2.1.1"
100
- },
101
- "optionalDependencies": {
102
- "node-fetch": "^2.7.0"
103
- },
104
- "contributors": [
105
- {
106
- "name": "Nicola Carpanese",
107
- "email": "nicola.carpanese@gmail.com",
108
- "url": "https://github.com/n-car"
109
- }
110
- ],
111
- "husky": {
112
- "hooks": {
113
- "pre-commit": "lint-staged"
114
- }
115
- },
116
- "lint-staged": {
117
- "src/**/*.js": [
118
- "eslint --fix",
119
- "prettier --write"
120
- ],
121
- "examples/**/*.js": [
122
- "eslint --fix",
123
- "prettier --write"
124
- ]
125
- }
126
- }
1
+ {
2
+ "name": "rpc-express-toolkit",
3
+ "version": "4.3.2",
4
+ "description": "Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, structured logging, middleware system, schema validation, comprehensive BigInt/Date support. JSON middleware validation and explicit configuration.",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./src/index.d.ts",
10
+ "require": "./src/index.js",
11
+ "import": "./src/index.js"
12
+ },
13
+ "./safe": {
14
+ "types": "./src/safe.d.ts",
15
+ "require": "./src/safe.js",
16
+ "import": "./src/safe.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "src/",
21
+ "examples/",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "test": "jest",
27
+ "test:coverage": "jest --coverage",
28
+ "test:watch": "jest --watch",
29
+ "lint": "eslint src/ examples/",
30
+ "lint:fix": "eslint src/ examples/ --fix",
31
+ "clean": "node -e \"require(\u0027fs\u0027).existsSync(\u0027dist\u0027) \u0026\u0026 require(\u0027fs\u0027).rmSync(\u0027dist\u0027, {recursive: true})\"",
32
+ "build": "node -e \"require.resolve(\u0027rpc-toolkit-js-client\u0027)\"",
33
+ "prepublishOnly": "npm run build",
34
+ "precommit": "npm run lint \u0026\u0026 npm test"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/n-car/rpc-express-toolkit.git"
39
+ },
40
+ "keywords": [
41
+ "json-rpc",
42
+ "jsonrpc",
43
+ "json-rpc-2.0",
44
+ "express",
45
+ "toolkit",
46
+ "rpc",
47
+ "server",
48
+ "client",
49
+ "middleware",
50
+ "validation",
51
+ "schema",
52
+ "ajv",
53
+ "bigint",
54
+ "date",
55
+ "enterprise",
56
+ "nodejs",
57
+ "node",
58
+ "browser",
59
+ "ssl",
60
+ "self-signed",
61
+ "secure",
62
+ "fetch",
63
+ "batch",
64
+ "typescript",
65
+ "logging",
66
+ "cors",
67
+ "rate-limit",
68
+ "authentication"
69
+ ],
70
+ "author": "Nicola Carpanese \u003cnicola.carpanese@gmail.com\u003e",
71
+ "license": "MIT",
72
+ "bugs": {
73
+ "url": "https://github.com/n-car/rpc-express-toolkit/issues"
74
+ },
75
+ "homepage": "https://github.com/n-car/rpc-express-toolkit#readme",
76
+ "engines": {
77
+ "node": "\u003e=18.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "eslint": "^8.57.1",
81
+ "eslint-config-airbnb-base": "^15.0.0",
82
+ "eslint-config-prettier": "^9.1.2",
83
+ "eslint-plugin-import": "^2.31.0",
84
+ "eslint-plugin-prettier": "^5.5.3",
85
+ "husky": "^8.0.0",
86
+ "jest": "^29.7.0",
87
+ "lint-staged": "^13.0.0",
88
+ "prettier": "^3.6.2",
89
+ "supertest": "^6.3.3",
90
+ "terser": "^5.43.1"
91
+ },
92
+ "dependencies": {
93
+ "ajv": "^8.12.0",
94
+ "ajv-formats": "^2.1.1",
95
+ "express": "^4.21.2",
96
+ "nested-error-stacks": "^2.1.1",
97
+ "rpc-toolkit-js-client": "https://github.com/n-car/rpc-toolkit-js-client/archive/refs/tags/v1.1.0.tar.gz"
98
+ },
99
+ "optionalDependencies": {
100
+ "node-fetch": "^2.7.0"
101
+ },
102
+ "contributors": [
103
+ {
104
+ "name": "Nicola Carpanese",
105
+ "email": "nicola.carpanese@gmail.com",
106
+ "url": "https://github.com/n-car"
107
+ }
108
+ ],
109
+ "husky": {
110
+ "hooks": {
111
+ "pre-commit": "lint-staged"
112
+ }
113
+ },
114
+ "lint-staged": {
115
+ "src/**/*.js": [
116
+ "eslint --fix",
117
+ "prettier --write"
118
+ ],
119
+ "examples/**/*.js": [
120
+ "eslint --fix",
121
+ "prettier --write"
122
+ ]
123
+ }
124
+ }
package/src/batch.js CHANGED
@@ -2,6 +2,8 @@
2
2
  * @file BatchHandler Class
3
3
  * @description Handles JSON-RPC 2.0 batch requests processing multiple requests in a single call
4
4
  */
5
+ const { addBatchIndex, hasOwn, validateEnvelope } = require('./protocol');
6
+
5
7
  class BatchHandler {
6
8
  constructor(endpoint) {
7
9
  this.endpoint = endpoint;
@@ -13,7 +15,7 @@ class BatchHandler {
13
15
  * @returns {boolean}
14
16
  */
15
17
  isBatchRequest(body) {
16
- return Array.isArray(body) && body.length > 0;
18
+ return Array.isArray(body);
17
19
  }
18
20
 
19
21
  /**
@@ -42,22 +44,6 @@ class BatchHandler {
42
44
  };
43
45
  }
44
46
 
45
- // Check for duplicate IDs in the batch
46
- const ids = batch
47
- .map((req) => req.id)
48
- .filter((id) => id !== undefined && id !== null);
49
- const uniqueIds = new Set(ids);
50
-
51
- if (ids.length !== uniqueIds.size) {
52
- return {
53
- valid: false,
54
- error: {
55
- code: -32600,
56
- message: 'Invalid Request: Duplicate IDs in batch',
57
- },
58
- };
59
- }
60
-
61
47
  return { valid: true };
62
48
  }
63
49
 
@@ -72,13 +58,11 @@ class BatchHandler {
72
58
  async processBatch(batch, req, res, context) {
73
59
  const validation = this.validateBatch(batch);
74
60
  if (!validation.valid) {
75
- return [
76
- {
77
- jsonrpc: '2.0',
78
- id: null,
79
- error: validation.error,
80
- },
81
- ];
61
+ return {
62
+ jsonrpc: '2.0',
63
+ id: null,
64
+ error: validation.error,
65
+ };
82
66
  }
83
67
 
84
68
  // Process all requests in parallel
@@ -88,11 +72,11 @@ class BatchHandler {
88
72
  } catch (error) {
89
73
  return {
90
74
  jsonrpc: '2.0',
91
- id: request.id || null,
75
+ id: request && hasOwn(request, 'id') ? request.id : null,
92
76
  error: {
93
77
  code: error.code || -32603,
94
78
  message: error.message || 'Internal error',
95
- data: { batchIndex: index },
79
+ data: addBatchIndex(error, index).data,
96
80
  },
97
81
  };
98
82
  }
@@ -113,38 +97,26 @@ class BatchHandler {
113
97
  * @returns {Promise<Object|null>}
114
98
  */
115
99
  async processSingleRequest(request, req, context, batchIndex) {
116
- const { jsonrpc, method, params, id } = request;
100
+ const { method, params, id } = request || {};
101
+ const envelope = validateEnvelope(request);
117
102
 
118
- // Validate JSON-RPC 2.0 request structure
119
- if (jsonrpc !== '2.0') {
103
+ if (!envelope.valid) {
120
104
  return {
121
105
  jsonrpc: '2.0',
122
- id: id || null,
123
- error: {
124
- code: -32600,
125
- message: `Invalid Request: 'jsonrpc' must be '2.0'`,
126
- data: { batchIndex },
127
- },
128
- };
129
- }
130
-
131
- if (typeof method !== 'string') {
132
- return {
133
- jsonrpc: '2.0',
134
- id: id || null,
135
- error: {
136
- code: -32600,
137
- message: `Invalid Request: 'method' must be a string`,
138
- data: { batchIndex },
139
- },
106
+ id: envelope.responseId,
107
+ error: addBatchIndex(envelope.error, batchIndex),
140
108
  };
141
109
  }
142
110
 
143
111
  const methodConfig = this.endpoint.methods[method];
144
112
  if (!methodConfig) {
113
+ if (envelope.isNotification) {
114
+ return null;
115
+ }
116
+
145
117
  return {
146
118
  jsonrpc: '2.0',
147
- id: id || null,
119
+ id,
148
120
  error: {
149
121
  code: -32601,
150
122
  message: `Method "${method}" not found`,
@@ -156,14 +128,44 @@ class BatchHandler {
156
128
  // Extract handler (support both function and config object)
157
129
  const handler =
158
130
  typeof methodConfig === 'function' ? methodConfig : methodConfig.handler;
131
+ const schema =
132
+ typeof methodConfig === 'object' ? methodConfig.schema : null;
159
133
 
160
134
  // If no id is provided, this is a notification - don't return response
161
- const isNotification = id === undefined || id === null;
135
+ const isNotification = !envelope.hasId;
162
136
 
163
137
  // Determine client safe mode from headers and deserialize params accordingly
164
138
  const clientSafeHeader = req.headers['x-rpc-safe-enabled'];
139
+ if (
140
+ this.endpoint.options.strictMode &&
141
+ this.endpoint.options.safeEnabled &&
142
+ !clientSafeHeader
143
+ ) {
144
+ if (isNotification) {
145
+ return null;
146
+ }
147
+
148
+ return {
149
+ jsonrpc: '2.0',
150
+ id,
151
+ error: {
152
+ code: -32600,
153
+ message:
154
+ 'RPC Compatibility Error: Server requires safe serialization header but client did not provide it.',
155
+ data: {
156
+ serverSafeEnabled: this.endpoint.options.safeEnabled,
157
+ requiredHeader: 'X-RPC-Safe-Enabled',
158
+ strictMode: true,
159
+ solution:
160
+ 'Update client to rpc-express-toolkit v4+ or disable server strict mode',
161
+ batchIndex,
162
+ },
163
+ },
164
+ };
165
+ }
166
+
165
167
  const clientSafeEnabled = clientSafeHeader === 'true';
166
- const deserializedParams = params
168
+ const deserializedParams = hasOwn(request, 'params')
167
169
  ? this.endpoint.deserializeBigIntsAndDates(params, {
168
170
  safeEnabled: clientSafeEnabled,
169
171
  })
@@ -189,6 +191,41 @@ class BatchHandler {
189
191
  );
190
192
  }
191
193
 
194
+ if (schema) {
195
+ if (this.endpoint.middleware) {
196
+ middlewareContext = await this.endpoint.middleware.execute(
197
+ 'beforeValidation',
198
+ middlewareContext
199
+ );
200
+ }
201
+
202
+ const validation = this.endpoint.validator.validate(
203
+ middlewareContext.params,
204
+ schema
205
+ );
206
+ if (!validation.valid) {
207
+ const error = new Error('Validation failed');
208
+ error.code = -32602;
209
+ error.data = {
210
+ validationErrors: validation.errors.map((err) => ({
211
+ field: err.instancePath || err.schemaPath,
212
+ message: err.message,
213
+ value: err.data,
214
+ })),
215
+ };
216
+ throw error;
217
+ }
218
+
219
+ middlewareContext.params = validation.data;
220
+
221
+ if (this.endpoint.middleware) {
222
+ middlewareContext = await this.endpoint.middleware.execute(
223
+ 'afterValidation',
224
+ middlewareContext
225
+ );
226
+ }
227
+ }
228
+
192
229
  // Execute the handler
193
230
  const result = await Promise.resolve(
194
231
  handler(req, context, middlewareContext.params)
@@ -234,11 +271,11 @@ class BatchHandler {
234
271
 
235
272
  return {
236
273
  jsonrpc: '2.0',
237
- id: id || null,
274
+ id,
238
275
  error: {
239
276
  code: error.code || -32603,
240
277
  message: error.message || 'Internal error',
241
- data: { batchIndex },
278
+ data: addBatchIndex(error, batchIndex).data,
242
279
  },
243
280
  };
244
281
  }
@@ -250,9 +287,7 @@ class BatchHandler {
250
287
  * @returns {Object}
251
288
  */
252
289
  getBatchStats(batch) {
253
- const notifications = batch.filter(
254
- (req) => req.id === undefined || req.id === null
255
- ).length;
290
+ const notifications = batch.filter((req) => !hasOwn(req, 'id')).length;
256
291
  const requests = batch.length - notifications;
257
292
  const methods = [...new Set(batch.map((req) => req.method))];
258
293
 
package/src/index.d.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  // Type definitions for rpc-express-toolkit
2
2
 
3
3
  import type { Router, Request, Response } from 'express';
4
+ import {
5
+ RpcClient as SharedRpcClient,
6
+ RpcError as SharedRpcError,
7
+ RpcHttpError as SharedRpcHttpError,
8
+ RpcSafeClient as SharedRpcSafeClient
9
+ } from 'rpc-toolkit-js-client';
10
+ import type {
11
+ RpcBatchRequest as SharedRpcBatchRequest,
12
+ RpcClientOptions as SharedRpcClientOptions
13
+ } from 'rpc-toolkit-js-client';
4
14
 
5
15
  /**
6
16
  * JSON-RPC handler function type
@@ -73,12 +83,9 @@ interface RateLimitConfig {
73
83
  }
74
84
 
75
85
  /**
76
- * Authentication configuration
86
+ * Authentication verifier. Return a truthy value to allow the call.
77
87
  */
78
- interface AuthConfig {
79
- required?: boolean;
80
- verify?: (req: Request) => boolean | Promise<boolean>;
81
- }
88
+ type AuthConfig = (req: Request) => boolean | Promise<boolean>;
82
89
 
83
90
  /**
84
91
  * Schema validation configuration
@@ -106,6 +113,8 @@ interface RpcEndpointOptions {
106
113
  logging?: LoggingConfig;
107
114
  warnOnUnsafe?: boolean;
108
115
  strictMode?: boolean;
116
+ maxSerializationDepth?: number;
117
+ maxDeserializationDepth?: number;
109
118
  autoJsonMiddleware?: boolean;
110
119
  jsonOptions?: {
111
120
  limit?: string;
@@ -123,10 +132,17 @@ interface RpcEndpointOptions {
123
132
  /**
124
133
  * RPC client configuration options
125
134
  */
126
- interface RpcClientOptions {
127
- safeEnabled?: boolean;
128
- warnOnUnsafe?: boolean;
129
- }
135
+ type RpcClientOptions = SharedRpcClientOptions;
136
+ type RpcBatchRequest = SharedRpcBatchRequest;
137
+ type RpcClient = SharedRpcClient;
138
+ type RpcError = SharedRpcError;
139
+ type RpcHttpError = SharedRpcHttpError;
140
+ type RpcSafeClient = SharedRpcSafeClient;
141
+
142
+ declare const RpcClient: typeof SharedRpcClient;
143
+ declare const RpcError: typeof SharedRpcError;
144
+ declare const RpcHttpError: typeof SharedRpcHttpError;
145
+ declare const RpcSafeClient: typeof SharedRpcSafeClient;
130
146
 
131
147
  /**
132
148
  * Deserialization options for safe prefixes
@@ -224,59 +240,6 @@ declare class RpcEndpoint<C = any> {
224
240
  deserializeBigIntsAndDates(value: any, options?: DeserializationOptions): any;
225
241
  }
226
242
 
227
- /**
228
- * JSON-RPC 2.0 Client for Node.js environments.
229
- */
230
- declare class RpcClient {
231
- /**
232
- * Client constructor.
233
- */
234
- constructor(endpoint: string, options?: RpcClientOptions);
235
- constructor(endpoint: string, defaultHeaders?: Record<string, string>, options?: RpcClientOptions);
236
-
237
- /**
238
- * Make a JSON-RPC call to the server.
239
- */
240
- call<T = any>(
241
- method: string,
242
- params?: any,
243
- id?: string | number | null,
244
- overrideHeaders?: Record<string, string>
245
- ): Promise<T>;
246
-
247
- /**
248
- * Make a JSON-RPC notification (no response expected).
249
- */
250
- notify(
251
- method: string,
252
- params?: any,
253
- overrideHeaders?: Record<string, string>
254
- ): Promise<void>;
255
-
256
- /**
257
- * Make a batch JSON-RPC call.
258
- */
259
- batch<T = any>(
260
- requests: Array<{
261
- method: string;
262
- params?: any;
263
- id?: string | number | null;
264
- notification?: boolean;
265
- }>,
266
- overrideHeaders?: Record<string, string>
267
- ): Promise<T[]>;
268
-
269
- /**
270
- * Recursively serialize BigInt and Date to JSON-safe string formats.
271
- */
272
- serializeBigIntsAndDates(value: any): any;
273
-
274
- /**
275
- * Recursively deserialize strings representing BigInt and Date.
276
- */
277
- deserializeBigIntsAndDates(value: any, options?: DeserializationOptions): any;
278
- }
279
-
280
243
  /**
281
244
  * Export types for external use
282
245
  */
@@ -292,9 +255,13 @@ export {
292
255
  ValidationConfig,
293
256
  RpcEndpointOptions,
294
257
  RpcClientOptions,
258
+ RpcBatchRequest,
295
259
  DeserializationOptions,
296
260
  RpcEndpoint,
297
- RpcClient
261
+ RpcClient,
262
+ RpcError,
263
+ RpcHttpError,
264
+ RpcSafeClient
298
265
  };
299
266
 
300
267
  /**
@@ -308,6 +275,9 @@ declare const Main: {
308
275
  serveScripts(router: Router, url?: string): void;
309
276
  RpcEndpoint: typeof RpcEndpoint;
310
277
  RpcClient: typeof RpcClient;
278
+ RpcError: typeof RpcError;
279
+ RpcHttpError: typeof RpcHttpError;
280
+ RpcSafeClient: typeof RpcSafeClient;
311
281
  Logger: any;
312
282
  MiddlewareManager: any;
313
283
  builtInMiddlewares: any;