rpc-express-toolkit 2.3.3 → 3.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 +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/src/batch.js
CHANGED
|
@@ -1,237 +1,256 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
* @
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
return {
|
|
121
|
-
jsonrpc: '2.0',
|
|
122
|
-
id: id || null,
|
|
123
|
-
error: {
|
|
124
|
-
code: -32600,
|
|
125
|
-
message: `Invalid Request: '
|
|
126
|
-
data: { batchIndex }
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file BatchHandler Class
|
|
3
|
+
* @description Handles JSON-RPC 2.0 batch requests processing multiple requests in a single call
|
|
4
|
+
*/
|
|
5
|
+
class BatchHandler {
|
|
6
|
+
constructor(endpoint) {
|
|
7
|
+
this.endpoint = endpoint;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check if request is a batch request
|
|
12
|
+
* @param {any} body
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
isBatchRequest(body) {
|
|
16
|
+
return Array.isArray(body) && body.length > 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate batch request
|
|
21
|
+
* @param {Array} batch
|
|
22
|
+
* @returns {Object}
|
|
23
|
+
*/
|
|
24
|
+
validateBatch(batch) {
|
|
25
|
+
if (!Array.isArray(batch)) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
error: {
|
|
29
|
+
code: -32600,
|
|
30
|
+
message: 'Invalid Request: Batch must be an array',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (batch.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
error: {
|
|
39
|
+
code: -32600,
|
|
40
|
+
message: 'Invalid Request: Batch cannot be empty',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
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
|
+
return { valid: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Process a batch of JSON-RPC requests
|
|
66
|
+
* @param {Array} batch
|
|
67
|
+
* @param {Object} req
|
|
68
|
+
* @param {Object} res
|
|
69
|
+
* @param {any} context
|
|
70
|
+
* @returns {Promise<Array>}
|
|
71
|
+
*/
|
|
72
|
+
async processBatch(batch, req, res, context) {
|
|
73
|
+
const validation = this.validateBatch(batch);
|
|
74
|
+
if (!validation.valid) {
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
jsonrpc: '2.0',
|
|
78
|
+
id: null,
|
|
79
|
+
error: validation.error,
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Process all requests in parallel
|
|
85
|
+
const promises = batch.map(async (request, index) => {
|
|
86
|
+
try {
|
|
87
|
+
return await this.processSingleRequest(request, req, context, index);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
jsonrpc: '2.0',
|
|
91
|
+
id: request.id || null,
|
|
92
|
+
error: {
|
|
93
|
+
code: error.code || -32603,
|
|
94
|
+
message: error.message || 'Internal error',
|
|
95
|
+
data: { batchIndex: index },
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const results = await Promise.all(promises);
|
|
102
|
+
|
|
103
|
+
// Filter out notifications (requests without id)
|
|
104
|
+
return results.filter((result) => result !== null);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Process a single request within a batch
|
|
109
|
+
* @param {Object} request
|
|
110
|
+
* @param {Object} req
|
|
111
|
+
* @param {any} context
|
|
112
|
+
* @param {number} batchIndex
|
|
113
|
+
* @returns {Promise<Object|null>}
|
|
114
|
+
*/
|
|
115
|
+
async processSingleRequest(request, req, context, batchIndex) {
|
|
116
|
+
const { jsonrpc, method, params, id } = request;
|
|
117
|
+
|
|
118
|
+
// Validate JSON-RPC 2.0 request structure
|
|
119
|
+
if (jsonrpc !== '2.0') {
|
|
120
|
+
return {
|
|
121
|
+
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
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const handler = this.endpoint.methods[method];
|
|
144
|
+
if (!handler) {
|
|
145
|
+
return {
|
|
146
|
+
jsonrpc: '2.0',
|
|
147
|
+
id: id || null,
|
|
148
|
+
error: {
|
|
149
|
+
code: -32601,
|
|
150
|
+
message: `Method "${method}" not found`,
|
|
151
|
+
data: { batchIndex },
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If no id is provided, this is a notification - don't return response
|
|
157
|
+
const isNotification = id === undefined || id === null;
|
|
158
|
+
|
|
159
|
+
// Initialize middleware context
|
|
160
|
+
let middlewareContext = {
|
|
161
|
+
req,
|
|
162
|
+
res: null, // No response object for batch items
|
|
163
|
+
method,
|
|
164
|
+
params,
|
|
165
|
+
context,
|
|
166
|
+
batchIndex,
|
|
167
|
+
isNotification,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Execute middleware
|
|
172
|
+
if (this.endpoint.middleware) {
|
|
173
|
+
middlewareContext = await this.endpoint.middleware.execute(
|
|
174
|
+
'beforeCall',
|
|
175
|
+
middlewareContext
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Execute the handler
|
|
180
|
+
const result = await Promise.resolve(
|
|
181
|
+
handler(req, context, middlewareContext.params)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Execute after middleware
|
|
185
|
+
if (this.endpoint.middleware) {
|
|
186
|
+
middlewareContext.result = result;
|
|
187
|
+
await this.endpoint.middleware.execute('afterCall', middlewareContext);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// For notifications, return null (will be filtered out)
|
|
191
|
+
if (isNotification) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Serialize the result
|
|
196
|
+
const safeResult = this.endpoint.serializeBigIntsAndDates(result);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
jsonrpc: '2.0',
|
|
200
|
+
id,
|
|
201
|
+
result: safeResult,
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
// Execute error middleware
|
|
205
|
+
if (this.endpoint.middleware) {
|
|
206
|
+
try {
|
|
207
|
+
await this.endpoint.middleware.execute('onError', {
|
|
208
|
+
...middlewareContext,
|
|
209
|
+
error,
|
|
210
|
+
batchIndex,
|
|
211
|
+
});
|
|
212
|
+
} catch (middlewareError) {
|
|
213
|
+
// Ignore middleware errors in error handling
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// For notifications, return null even on error
|
|
218
|
+
if (isNotification) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
jsonrpc: '2.0',
|
|
224
|
+
id: id || null,
|
|
225
|
+
error: {
|
|
226
|
+
code: error.code || -32603,
|
|
227
|
+
message: error.message || 'Internal error',
|
|
228
|
+
data: { batchIndex },
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get batch statistics
|
|
236
|
+
* @param {Array} batch
|
|
237
|
+
* @returns {Object}
|
|
238
|
+
*/
|
|
239
|
+
getBatchStats(batch) {
|
|
240
|
+
const notifications = batch.filter(
|
|
241
|
+
(req) => req.id === undefined || req.id === null
|
|
242
|
+
).length;
|
|
243
|
+
const requests = batch.length - notifications;
|
|
244
|
+
const methods = [...new Set(batch.map((req) => req.method))];
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
total: batch.length,
|
|
248
|
+
requests,
|
|
249
|
+
notifications,
|
|
250
|
+
uniqueMethods: methods.length,
|
|
251
|
+
methods,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = BatchHandler;
|