• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

n-car / rpc-express-toolkit / 19709424414

26 Nov 2025 03:44PM UTC coverage: 70.306% (+7.7%) from 62.651%
19709424414

push

github

n-car
Add: Implementa introspection methods (__rpc.*)

Features:
- 5 metodi introspection: listMethods, describe, describeAll, version, capabilities
- enableIntrospection e introspectionPrefix configurabili
- exposeSchema e description support in addMethod()
- Reserved namespace protection
- 15 nuovi test (69 totali, coverage 72.79%)
- Fix batch.js per gestire handler come funzione o oggetto

Breaking: Nessuno (feature opt-in, default disabled)

262 of 402 branches covered (65.17%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

382 of 514 relevant lines covered (74.32%)

28.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.52
/src/batch.js
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;
65✔
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;
46✔
17
  }
18

19
  /**
20
   * Validate batch request
21
   * @param {Array} batch
22
   * @returns {Object}
23
   */
24
  validateBatch(batch) {
25
    if (!Array.isArray(batch)) {
4!
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) {
4!
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
4✔
47
      .map((req) => req.id)
8✔
48
      .filter((id) => id !== undefined && id !== null);
8✔
49
    const uniqueIds = new Set(ids);
4✔
50

51
    if (ids.length !== uniqueIds.size) {
4✔
52
      return {
1✔
53
        valid: false,
54
        error: {
55
          code: -32600,
56
          message: 'Invalid Request: Duplicate IDs in batch',
57
        },
58
      };
59
    }
60

61
    return { valid: true };
3✔
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);
4✔
74
    if (!validation.valid) {
4✔
75
      return [
1✔
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) => {
3✔
86
      try {
6✔
87
        return await this.processSingleRequest(request, req, context, index);
6✔
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);
3✔
102

103
    // Filter out notifications (requests without id)
104
    return results.filter((result) => result !== null);
6✔
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;
6✔
117

118
    // Validate JSON-RPC 2.0 request structure
119
    if (jsonrpc !== '2.0') {
6!
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') {
6!
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 methodConfig = this.endpoint.methods[method];
6✔
144
    if (!methodConfig) {
6!
UNCOV
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
    // Extract handler (support both function and config object)
157
    const handler =
158
      typeof methodConfig === 'function' ? methodConfig : methodConfig.handler;
6✔
159

160
    // If no id is provided, this is a notification - don't return response
161
    const isNotification = id === undefined || id === null;
6✔
162

163
    // Determine client safe mode from headers and deserialize params accordingly
164
    const clientSafeHeader = req.headers['x-rpc-safe-enabled'];
6✔
165
    const clientSafeEnabled = clientSafeHeader === 'true';
6✔
166
    const deserializedParams = params
6✔
167
      ? this.endpoint.deserializeBigIntsAndDates(params, {
168
          safeEnabled: clientSafeEnabled,
169
        })
170
      : params;
171

172
    // Initialize middleware context
173
    let middlewareContext = {
6✔
174
      req,
175
      res: null, // No response object for batch items
176
      method,
177
      params: deserializedParams,
178
      context,
179
      batchIndex,
180
      isNotification,
181
    };
182

183
    try {
6✔
184
      // Execute middleware
185
      if (this.endpoint.middleware) {
6!
186
        middlewareContext = await this.endpoint.middleware.execute(
6✔
187
          'beforeCall',
188
          middlewareContext
189
        );
190
      }
191

192
      // Execute the handler
193
      const result = await Promise.resolve(
6✔
194
        handler(req, context, middlewareContext.params)
195
      );
196

197
      // Execute after middleware
198
      if (this.endpoint.middleware) {
6!
199
        middlewareContext.result = result;
6✔
200
        await this.endpoint.middleware.execute('afterCall', middlewareContext);
6✔
201
      }
202

203
      // For notifications, return null (will be filtered out)
204
      if (isNotification) {
6✔
205
        return null;
2✔
206
      }
207

208
      // Serialize the result
209
      const safeResult = this.endpoint.serializeBigIntsAndDates(result);
4✔
210

211
      return {
4✔
212
        jsonrpc: '2.0',
213
        id,
214
        result: safeResult,
215
      };
216
    } catch (error) {
217
      // Execute error middleware
218
      if (this.endpoint.middleware) {
×
219
        try {
×
220
          await this.endpoint.middleware.execute('onError', {
×
221
            ...middlewareContext,
222
            error,
223
            batchIndex,
224
          });
225
        } catch (middlewareError) {
226
          // Ignore middleware errors in error handling
227
        }
228
      }
229

230
      // For notifications, return null even on error
231
      if (isNotification) {
×
232
        return null;
×
233
      }
234

235
      return {
×
236
        jsonrpc: '2.0',
237
        id: id || null,
×
238
        error: {
239
          code: error.code || -32603,
×
240
          message: error.message || 'Internal error',
×
241
          data: { batchIndex },
242
        },
243
      };
244
    }
245
  }
246

247
  /**
248
   * Get batch statistics
249
   * @param {Array} batch
250
   * @returns {Object}
251
   */
252
  getBatchStats(batch) {
253
    const notifications = batch.filter(
×
254
      (req) => req.id === undefined || req.id === null
×
255
    ).length;
256
    const requests = batch.length - notifications;
×
257
    const methods = [...new Set(batch.map((req) => req.method))];
×
258

259
    return {
×
260
      total: batch.length,
261
      requests,
262
      notifications,
263
      uniqueMethods: methods.length,
264
      methods,
265
    };
266
  }
267
}
268

269
module.exports = BatchHandler;
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc