From e1940904ad099406ddfac85cf5c9c511304ffe21 Mon Sep 17 00:00:00 2001 From: lwin Date: Thu, 5 Feb 2026 12:11:37 +0800 Subject: [PATCH 1/3] chore: replace hardcoded error codes with rpc error constants --- package-lock.json | 20 -------------------- src/jrpc/jrpc.ts | 5 +++-- src/jrpc/jrpcEngine.ts | 14 +++++++------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba9fa8cb..2ce803a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,7 +120,6 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1657,7 +1656,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -3391,7 +3389,6 @@ "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -4686,7 +4683,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4876,7 +4872,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -5736,7 +5731,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5783,7 +5777,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6582,7 +6575,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -8104,7 +8096,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8165,7 +8156,6 @@ "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8263,7 +8253,6 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -11621,7 +11610,6 @@ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", @@ -12872,7 +12860,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13565,7 +13552,6 @@ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -13753,7 +13739,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14904,7 +14889,6 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -15047,7 +15031,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15349,7 +15332,6 @@ "integrity": "sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -15442,7 +15424,6 @@ "integrity": "sha512-xQroKAadK503CrmbzCISvQUjeuvEZzv6U0wlnlVFOi5i3gnzfH4onyQ29f3lzpe0FresAiTAd3aqK0Bi/jLI8w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.7", "@vitest/mocker": "4.0.7", @@ -15559,7 +15540,6 @@ "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", diff --git a/src/jrpc/jrpc.ts b/src/jrpc/jrpc.ts index 08b95096..66171f42 100644 --- a/src/jrpc/jrpc.ts +++ b/src/jrpc/jrpc.ts @@ -1,5 +1,6 @@ import { Duplex } from "readable-stream"; +import { errorCodes } from "./errors"; import { AsyncJRPCMiddleware, ConsoleLike, IdMap, JRPCMiddleware, JRPCRequest, JRPCResponse, Json, ReturnHandlerCallback } from "./interfaces"; import { SafeEventEmitter } from "./safeEventEmitter"; import { SerializableError } from "./serializableError"; @@ -21,7 +22,7 @@ export function createErrorMiddleware(log: ConsoleLike): JRPCMiddleware { } else { if (returnHandler) { if (typeof returnHandler !== "function") { - end(new SerializableError({ code: -32603, message: "JRPCEngine: 'next' return handlers must be functions" })); + end(new SerializableError({ code: errorCodes.rpc.internal, message: "JRPCEngine: 'next' return handlers must be functions" })); } returnHandlers.push(returnHandler); } @@ -152,10 +152,10 @@ export class JRPCEngine extends SafeEventEmitter { */ private static _checkForCompletion(_req: JRPCRequest, res: JRPCResponse, isComplete: boolean): void { if (!("result" in res) && !("error" in res)) { - throw new SerializableError({ code: -32603, message: "Response has no error or result for request" }); + throw new SerializableError({ code: errorCodes.rpc.internal, message: "Response has no error or result for request" }); } if (!isComplete) { - throw new SerializableError({ code: -32603, message: "Nothing ended request" }); + throw new SerializableError({ code: errorCodes.rpc.internal, message: "Nothing ended request" }); } } @@ -312,12 +312,12 @@ export class JRPCEngine extends SafeEventEmitter { */ private async _handle(callerReq: JRPCRequest, cb: (error: unknown, response: JRPCResponse) => void): Promise { if (!callerReq || Array.isArray(callerReq) || typeof callerReq !== "object") { - const error = new SerializableError({ code: -32603, message: "request must be plain object" }); + const error = new SerializableError({ code: errorCodes.rpc.invalidRequest, message: "request must be plain object" }); return cb(error, { id: undefined, jsonrpc: "2.0", error }); } if (typeof callerReq.method !== "string") { - const error = new SerializableError({ code: -32603, message: "method must be string" }); + const error = new SerializableError({ code: errorCodes.rpc.invalidRequest, message: "method must be string" }); return cb(error, { id: callerReq.id, jsonrpc: "2.0", error }); } From 77f69c92404baaf098f5c51db8f016c1b21f4a5d Mon Sep 17 00:00:00 2001 From: lwin Date: Thu, 5 Feb 2026 12:26:57 +0800 Subject: [PATCH 2/3] chore: revert package-lock.json --- package-lock.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2ce803a9..ba9fa8cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,6 +120,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1656,6 +1657,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -3389,6 +3391,7 @@ "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -4683,6 +4686,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4872,6 +4876,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -5731,6 +5736,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5777,6 +5783,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6575,6 +6582,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -8096,6 +8104,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8156,6 +8165,7 @@ "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8253,6 +8263,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -11610,6 +11621,7 @@ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", @@ -12860,6 +12872,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13552,6 +13565,7 @@ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -13739,6 +13753,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14889,6 +14904,7 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -15031,6 +15047,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15332,6 +15349,7 @@ "integrity": "sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -15424,6 +15442,7 @@ "integrity": "sha512-xQroKAadK503CrmbzCISvQUjeuvEZzv6U0wlnlVFOi5i3gnzfH4onyQ29f3lzpe0FresAiTAd3aqK0Bi/jLI8w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.7", "@vitest/mocker": "4.0.7", @@ -15540,6 +15559,7 @@ "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", From ee219ea158ac14c2f353a28c93fb60483ae99ec9 Mon Sep 17 00:00:00 2001 From: lwin Date: Thu, 5 Feb 2026 17:31:06 +0800 Subject: [PATCH 3/3] chore: updated tests --- src/jrpc/errors/utils.ts | 13 +++++++++++++ src/jrpc/jrpcEngine.ts | 27 ++++++++++++++++++++++----- test/jrpcEngine.test.ts | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 test/jrpcEngine.test.ts diff --git a/src/jrpc/errors/utils.ts b/src/jrpc/errors/utils.ts index 2f61f8f1..abd5300d 100644 --- a/src/jrpc/errors/utils.ts +++ b/src/jrpc/errors/utils.ts @@ -9,6 +9,19 @@ declare type PropertyKey = string | number | symbol; type ErrorValueKey = keyof typeof errorValues; +export function isValidNumber(value: unknown): value is number { + try { + if (typeof value === "number" && Number.isInteger(value)) { + return true; + } + + const parsedValue = Number(value.toString()); + return Number.isInteger(parsedValue); + } catch { + return false; + } +} + /** * Returns whether the given code is valid. * A code is valid if it is an integer. diff --git a/src/jrpc/jrpcEngine.ts b/src/jrpc/jrpcEngine.ts index 0a995203..2127a11c 100644 --- a/src/jrpc/jrpcEngine.ts +++ b/src/jrpc/jrpcEngine.ts @@ -2,7 +2,7 @@ import { Duplex } from "readable-stream"; import { log } from "../utils/logger"; import { errorCodes, JsonRpcError } from "./errors"; -import { getMessageFromCode, serializeJrpcError } from "./errors/utils"; +import { getMessageFromCode, isValidNumber, serializeJrpcError } from "./errors/utils"; import { JRPCEngineEndCallback, JRPCEngineNextCallback, @@ -29,7 +29,7 @@ function constructFallbackError(error: Error): JRPCError { stack = "Stack trace is not available.", data = "", } = error as { message?: string; code?: number; stack?: string; data?: string }; - const codeNumber = parseInt(code?.toString() || errorCodes.rpc.internal.toString()); + const codeNumber = isValidNumber(code) ? parseInt(code.toString()) : errorCodes.rpc.internal; return { message: message || error?.toString() || getMessageFromCode(codeNumber), code: codeNumber, @@ -268,6 +268,17 @@ export class JRPCEngine extends SafeEventEmitter { ): Promise[] | void> { // The order here is important try { + if (reqs.length === 0) { + const error = new SerializableError({ + code: errorCodes.rpc.invalidRequest, + message: "Request batch must contain plain objects. Received an empty array", + }); + const response: JRPCResponse[] = [{ id: undefined, jsonrpc: "2.0" as const, error }]; + if (cb) { + return cb(error, response); + } + return response; + } // 2. Wait for all requests to finish, or throw on some kind of fatal // error const responses = await Promise.all( @@ -312,12 +323,18 @@ export class JRPCEngine extends SafeEventEmitter { */ private async _handle(callerReq: JRPCRequest, cb: (error: unknown, response: JRPCResponse) => void): Promise { if (!callerReq || Array.isArray(callerReq) || typeof callerReq !== "object") { - const error = new SerializableError({ code: errorCodes.rpc.invalidRequest, message: "request must be plain object" }); + const error = new SerializableError({ + code: errorCodes.rpc.invalidRequest, + message: `Requests must be plain objects. Received: ${typeof callerReq}`, + }); return cb(error, { id: undefined, jsonrpc: "2.0", error }); } - if (typeof callerReq.method !== "string") { - const error = new SerializableError({ code: errorCodes.rpc.invalidRequest, message: "method must be string" }); + if (typeof callerReq.method !== "string" || !callerReq.method) { + const error = new SerializableError({ + code: errorCodes.rpc.invalidRequest, + message: `Must specify a string method. Received: ${typeof callerReq.method}`, + }); return cb(error, { id: callerReq.id, jsonrpc: "2.0", error }); } diff --git a/test/jrpcEngine.test.ts b/test/jrpcEngine.test.ts new file mode 100644 index 00000000..b9e6e4ca --- /dev/null +++ b/test/jrpcEngine.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; + +import { errorCodes } from "../src/jrpc/errors"; +import { JRPCEngine } from "../src/jrpc/jrpcEngine"; + +describe("JRPCEngine request validation", () => { + it("returns invalidRequest for non-object requests", async () => { + const engine = new JRPCEngine(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const response = await engine.handle(123 as any); + expect(response.error?.code).toBe(errorCodes.rpc.invalidRequest); + expect(response.id).toBeUndefined(); + expect(response.jsonrpc).toBe("2.0"); + }); + + it("returns invalidRequest for non-string method", async () => { + const engine = new JRPCEngine(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const response = await engine.handle({ id: 1, jsonrpc: "2.0", method: 123 as any }); + expect(response.error?.code).toBe(errorCodes.rpc.invalidRequest); + expect(response.id).toBe(1); + expect(response.jsonrpc).toBe("2.0"); + }); + + it("returns invalidRequest for empty batch requests", async () => { + const engine = new JRPCEngine(); + const responses = await engine.handle([]); + expect(responses).toHaveLength(1); + expect(responses[0].error?.code).toBe(errorCodes.rpc.invalidRequest); + expect(responses[0].id).toBeUndefined(); + expect(responses[0].jsonrpc).toBe("2.0"); + }); +});