@asyncswap/jsonrpc 0.3.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # @asyncswap/jsonrpc
2
+
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 1c3a46e: fix example
8
+
9
+ ## 0.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - dc1718c: refactor of types dir to global types
14
+ - 00ba692: - Converts error codes as enums
15
+ - Uses generic types on rpc request and response methods, and results
16
+ - 07dc4e5: improve docs example and updates to build config
17
+
18
+ ### Patch Changes
19
+
20
+ - e15733c: update scripts and make changeset in monorepo
21
+ - e3ac747: patch
22
+ - abddcdc: add source map to typescript build build
23
+ - ded3e6b: update
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # jsonrpc
2
+
3
+ ## `@asyncswap/jsonrpc`
4
+
5
+ A minimal jsonrpc server and client library.
6
+
7
+ ```sh
8
+ bun add @asyncswap/jsonrpc
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### RPC Server
14
+
15
+ ```ts
16
+ import { JsonRpcServer } from "@asyncswap/jsonrpc";
17
+
18
+ const server = new JsonRpcServer()
19
+
20
+ server.register("add", ([a,b]: [number, number]) => a + b)
21
+ server.register("ping", () => "pong")
22
+
23
+ Bun.serve({
24
+ port: 4444,
25
+ async fetch(req) {
26
+ if (req.method !== "POST") {
27
+ return new Response("JSON-RPC only", { status: 405 })
28
+ }
29
+ const payload = await req.json();
30
+ const response = await server.handle(payload);
31
+ if (!response) {
32
+ return new Response(null, { status: 204 })
33
+ }
34
+ return Response.json(response);
35
+ }
36
+ })
37
+
38
+ console.log("JSON-RPC running on http://localhost:4444")
39
+ ```
40
+
41
+ ### RPC Client
42
+
43
+ ```ts
44
+ import { initializeRpcClient } from "@asyncswap/jsonrpc";
45
+ const url = "http://localhost:4444";
46
+ const client = initializeRpcClient(url);
47
+ const result = await client.call(
48
+ "ping",
49
+ []
50
+ );
51
+ console.log(result)
52
+ ```
@@ -0,0 +1,9 @@
1
+ export declare class JsonRpcClient {
2
+ private send;
3
+ private id;
4
+ constructor(send: (req: JsonRpcRequest<unknown>) => Promise<JsonRpcResponse<unknown, number>>);
5
+ call<Method = string, Result = unknown, E = unknown>(method: Method, params?: unknown[]): Promise<Result | E>;
6
+ notify<Method = string>(method: Method, params?: unknown[]): Promise<JsonRpcResponse<unknown, number>>;
7
+ }
8
+ export declare function initializeRpcClient(url: string, jwtToken?: string, timeout?: number): JsonRpcClient;
9
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAa;IAIxB,OAAO,CAAC,IAAI;IAHb,OAAO,CAAC,EAAE,CAAK;gBAGN,IAAI,EAAE,CACb,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,KACxB,OAAO,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAGzC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EACxD,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAAE,GAChB,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IActB,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE;CAQ1D;AAED,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,GAAE,MAAa,GACpB,aAAa,CA2Bf"}
package/dist/client.js ADDED
@@ -0,0 +1,57 @@
1
+ export class JsonRpcClient {
2
+ send;
3
+ id = 0;
4
+ constructor(send) {
5
+ this.send = send;
6
+ }
7
+ async call(method, params) {
8
+ const request = {
9
+ jsonrpc: "2.0",
10
+ method,
11
+ params,
12
+ id: ++this.id,
13
+ };
14
+ const response = await this.send(request);
15
+ if ("error" in response) {
16
+ return response.error;
17
+ }
18
+ return response.result;
19
+ }
20
+ notify(method, params) {
21
+ const request = {
22
+ jsonrpc: "2.0",
23
+ method,
24
+ params,
25
+ };
26
+ return this.send(request);
27
+ }
28
+ }
29
+ export function initializeRpcClient(url, jwtToken, timeout = 5000) {
30
+ const client = new JsonRpcClient(async (req) => {
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
33
+ try {
34
+ const headers = {
35
+ "Content-Type": "application/json",
36
+ };
37
+ if (jwtToken) {
38
+ headers["Authorization"] = `Bearer ${jwtToken}`;
39
+ }
40
+ const res = await fetch(url, {
41
+ method: "POST",
42
+ headers,
43
+ body: JSON.stringify(req),
44
+ });
45
+ return (await res.json());
46
+ }
47
+ catch (err) {
48
+ clearTimeout(timeoutId);
49
+ if (err instanceof Error && err.name === "AbortError") {
50
+ throw new Error(`Req timeout after ${timeout}ms`);
51
+ }
52
+ throw err;
53
+ }
54
+ });
55
+ return client;
56
+ }
57
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAa;IAIhB;IAHD,EAAE,GAAG,CAAC,CAAC;IAEf,YACS,IAEsC;QAFtC,SAAI,GAAJ,IAAI,CAEkC;IAC3C,CAAC;IAEL,KAAK,CAAC,IAAI,CACT,MAAc,EACd,MAAkB;QAElB,MAAM,OAAO,GAA2B;YACvC,OAAO,EAAE,KAAK;YACd,MAAM;YACN,MAAM;YACN,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE;SACb,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YACzB,OAAO,QAAQ,CAAC,KAAU,CAAC;QAC5B,CAAC;QACD,OAAO,QAAQ,CAAC,MAAgB,CAAC;IAClC,CAAC;IAED,MAAM,CAAkB,MAAc,EAAE,MAAkB;QACzD,MAAM,OAAO,GAA2B;YACvC,OAAO,EAAE,KAAK;YACd,MAAM;YACN,MAAM;SACN,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;CACD;AAED,MAAM,UAAU,mBAAmB,CAClC,GAAW,EACX,QAAiB,EACjB,UAAkB,IAAI;IAEtB,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,KAAK,EAAE,GAA4B,EAAE,EAAE;QACvE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;QAChE,IAAI,CAAC;YACJ,MAAM,OAAO,GAA2B;gBACvC,cAAc,EAAE,kBAAkB;aAClC,CAAC;YACF,IAAI,QAAQ,EAAE,CAAC;gBACd,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,QAAQ,EAAE,CAAC;YACjD,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;aACzB,CAAC,CAAC;YAEH,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBACvD,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,IAAI,CAAC,CAAC;YACnD,CAAC;YACD,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./client";
2
+ export * from "./server";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./client";
2
+ export * from "./server";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
@@ -0,0 +1,16 @@
1
+ export declare enum JsonRpcErrorCodes {
2
+ INVALID_REQUEST = -32600,
3
+ METHOD_NOT_FOUND = -32601,
4
+ INVALID_PARAMS = -32602,
5
+ INTERNAL_ERROR = -32603,
6
+ PARSE_ERROR = -32700,
7
+ REQUEST_ABORTED = -32800
8
+ }
9
+ export type Handler<Result> = (params: any) => any | Promise<Result>;
10
+ export declare class JsonRpcServer {
11
+ private methods;
12
+ register(method: string, handler: Handler<any>): void;
13
+ handle<Result, Method = string>(raw: unknown): Promise<JsonRpcResponse<Result, JsonRpcErrorCodes | number> | JsonRpcResponse<Result, JsonRpcErrorCodes | number>[] | null>;
14
+ private error;
15
+ }
16
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,oBAAY,iBAAiB;IAC5B,eAAe,SAAS;IACxB,gBAAgB,SAAS;IACzB,cAAc,SAAS;IACvB,cAAc,SAAS;IACvB,WAAW,SAAS;IACpB,eAAe,SAAS;CACxB;AACD,MAAM,MAAM,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAErE,qBAAa,aAAa;IACzB,OAAO,CAAC,OAAO,CAAmC;IAElD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC;IAIxC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EACnC,GAAG,EAAE,OAAO,GACV,OAAO,CACP,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAAC,GACnD,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAAC,EAAE,GACrD,IAAI,CACN;IAsED,OAAO,CAAC,KAAK;CAYb"}
package/dist/server.js ADDED
@@ -0,0 +1,66 @@
1
+ export var JsonRpcErrorCodes;
2
+ (function (JsonRpcErrorCodes) {
3
+ JsonRpcErrorCodes[JsonRpcErrorCodes["INVALID_REQUEST"] = -32600] = "INVALID_REQUEST";
4
+ JsonRpcErrorCodes[JsonRpcErrorCodes["METHOD_NOT_FOUND"] = -32601] = "METHOD_NOT_FOUND";
5
+ JsonRpcErrorCodes[JsonRpcErrorCodes["INVALID_PARAMS"] = -32602] = "INVALID_PARAMS";
6
+ JsonRpcErrorCodes[JsonRpcErrorCodes["INTERNAL_ERROR"] = -32603] = "INTERNAL_ERROR";
7
+ JsonRpcErrorCodes[JsonRpcErrorCodes["PARSE_ERROR"] = -32700] = "PARSE_ERROR";
8
+ JsonRpcErrorCodes[JsonRpcErrorCodes["REQUEST_ABORTED"] = -32800] = "REQUEST_ABORTED";
9
+ })(JsonRpcErrorCodes || (JsonRpcErrorCodes = {}));
10
+ export class JsonRpcServer {
11
+ methods = new Map();
12
+ register(method, handler) {
13
+ this.methods.set(method, handler);
14
+ }
15
+ async handle(raw) {
16
+ // handle batch
17
+ if (Array.isArray(raw)) {
18
+ if (raw.length === 0) {
19
+ return this.error(null, JsonRpcErrorCodes.INVALID_REQUEST, "Invalid request");
20
+ }
21
+ const responses = await Promise.all(raw.map((item) => this.handle(item)));
22
+ const filtered = responses.filter((r) => r !== null);
23
+ return filtered.length > 0 ? filtered : null;
24
+ }
25
+ // handle single response
26
+ const req = raw;
27
+ if (typeof req !== "object" ||
28
+ req === null ||
29
+ req.jsonrpc !== "2.0" ||
30
+ typeof req.method !== "string") {
31
+ return this.error(null, JsonRpcErrorCodes.INVALID_REQUEST, "Invalid request");
32
+ }
33
+ const id = typeof req?.id === "string" ||
34
+ typeof req?.id === "number" ||
35
+ req?.id === null
36
+ ? req.id
37
+ : null;
38
+ const handler = this.methods.get(req.method);
39
+ if (!handler) {
40
+ return id === null
41
+ ? null
42
+ : this.error(req.id, JsonRpcErrorCodes.METHOD_NOT_FOUND, "Method not found");
43
+ }
44
+ try {
45
+ const result = await handler(req.params);
46
+ if (req.id === undefined)
47
+ return null; // notification
48
+ return {
49
+ jsonrpc: "2.0",
50
+ result,
51
+ id: req.id,
52
+ };
53
+ }
54
+ catch (err) {
55
+ return this.error(req.id ?? null, JsonRpcErrorCodes.INTERNAL_ERROR, "Internal error", err instanceof Error ? err.message : err);
56
+ }
57
+ }
58
+ error(id, code, message, data) {
59
+ return {
60
+ jsonrpc: "2.0",
61
+ error: { code, message, data },
62
+ id: id ?? null,
63
+ };
64
+ }
65
+ }
66
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/example.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { JsonRpcServer } from "./src";
2
+
3
+ const server = new JsonRpcServer();
4
+
5
+ server.register("add", ([a, b]: [number, number]) => a + b);
6
+ server.register("ping", () => "pong");
7
+
8
+ Bun.serve({
9
+ port: 4444,
10
+ async fetch(req) {
11
+ if (req.method !== "POST") {
12
+ return new Response("JSON-RPC only", { status: 405 });
13
+ }
14
+ const payload = await req.json();
15
+ const response = await server.handle(payload);
16
+ if (!response) {
17
+ return new Response(null, { status: 204 });
18
+ }
19
+ return Response.json(response);
20
+ },
21
+ });
22
+
23
+ console.log("JSON-RPC running on http://localhost:4444");
24
+
25
+ import { initializeRpcClient } from "./src";
26
+
27
+ const url = "http://localhost:4444";
28
+ const client = initializeRpcClient(url);
29
+ const result = await client.call("ping", []);
30
+ console.log(result);
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@asyncswap/jsonrpc",
3
+ "description": "A minimal jsonrpc spec implementation.",
4
+ "author": "Meek Msaki",
5
+ "version": "0.3.1",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "keywords": [
11
+ "jsonrpc",
12
+ "2.0",
13
+ "rpc",
14
+ "bun"
15
+ ],
16
+ "homepage": "https://github.com/asyncswap/eth-libs/tree/main/packages/jsonrpc",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/asyncswap/eth-libs.git",
20
+ "directory": "packages/jsonrpc"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/asyncswap/eth-libs/issues"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "directory": "dist"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "publish": "bun run build && bun publish --access public",
32
+ "format": "bun biome format --write"
33
+ },
34
+ "devDependencies": {
35
+ "@biomejs/biome": "2.3.11",
36
+ "@types/bun": "latest"
37
+ },
38
+ "peerDependencies": {
39
+ "typescript": "^5"
40
+ }
41
+ }
package/src/client.ts ADDED
@@ -0,0 +1,68 @@
1
+ export class JsonRpcClient {
2
+ private id = 0;
3
+
4
+ constructor(
5
+ private send: (
6
+ req: JsonRpcRequest<unknown>,
7
+ ) => Promise<JsonRpcResponse<unknown, number>>,
8
+ ) { }
9
+
10
+ async call<Method = string, Result = unknown, E = unknown>(
11
+ method: Method,
12
+ params?: unknown[],
13
+ ): Promise<Result | E> {
14
+ const request: JsonRpcRequest<Method> = {
15
+ jsonrpc: "2.0",
16
+ method,
17
+ params,
18
+ id: ++this.id,
19
+ };
20
+ const response = await this.send(request);
21
+ if ("error" in response) {
22
+ return response.error as E;
23
+ }
24
+ return response.result as Result;
25
+ }
26
+
27
+ notify<Method = string>(method: Method, params?: unknown[]) {
28
+ const request: JsonRpcRequest<Method> = {
29
+ jsonrpc: "2.0",
30
+ method,
31
+ params,
32
+ };
33
+ return this.send(request);
34
+ }
35
+ }
36
+
37
+ export function initializeRpcClient(
38
+ url: string,
39
+ jwtToken?: string,
40
+ timeout: number = 5000,
41
+ ): JsonRpcClient {
42
+ const client = new JsonRpcClient(async (req: JsonRpcRequest<unknown>) => {
43
+ const controller = new AbortController();
44
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
45
+ try {
46
+ const headers: Record<string, string> = {
47
+ "Content-Type": "application/json",
48
+ };
49
+ if (jwtToken) {
50
+ headers["Authorization"] = `Bearer ${jwtToken}`;
51
+ }
52
+ const res = await fetch(url, {
53
+ method: "POST",
54
+ headers,
55
+ body: JSON.stringify(req),
56
+ });
57
+
58
+ return (await res.json()) as JsonRpcResponse<unknown, number>;
59
+ } catch (err) {
60
+ clearTimeout(timeoutId);
61
+ if (err instanceof Error && err.name === "AbortError") {
62
+ throw new Error(`Req timeout after ${timeout}ms`);
63
+ }
64
+ throw err;
65
+ }
66
+ });
67
+ return client;
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./client";
2
+ export * from "./server";
package/src/server.ts ADDED
@@ -0,0 +1,106 @@
1
+ export enum JsonRpcErrorCodes {
2
+ INVALID_REQUEST = -32600,
3
+ METHOD_NOT_FOUND = -32601,
4
+ INVALID_PARAMS = -32602,
5
+ INTERNAL_ERROR = -32603,
6
+ PARSE_ERROR = -32700,
7
+ REQUEST_ABORTED = -32800,
8
+ }
9
+ export type Handler<Result> = (params: any) => any | Promise<Result>;
10
+
11
+ export class JsonRpcServer {
12
+ private methods = new Map<string, Handler<any>>();
13
+
14
+ register(method: string, handler: Handler<any>) {
15
+ this.methods.set(method, handler);
16
+ }
17
+
18
+ async handle<Result, Method = string>(
19
+ raw: unknown,
20
+ ): Promise<
21
+ | JsonRpcResponse<Result, JsonRpcErrorCodes | number>
22
+ | JsonRpcResponse<Result, JsonRpcErrorCodes | number>[]
23
+ | null
24
+ > {
25
+ // handle batch
26
+ if (Array.isArray(raw)) {
27
+ if (raw.length === 0) {
28
+ return this.error(
29
+ null,
30
+ JsonRpcErrorCodes.INVALID_REQUEST,
31
+ "Invalid request",
32
+ );
33
+ }
34
+ const responses = await Promise.all(raw.map((item) => this.handle(item)));
35
+ const filtered = responses.filter(
36
+ (r): r is JsonRpcResponse<Result, JsonRpcErrorCodes | number> =>
37
+ r !== null,
38
+ );
39
+ return filtered.length > 0 ? filtered : null;
40
+ }
41
+
42
+ // handle single response
43
+ const req = raw as Partial<JsonRpcRequest<Method>>;
44
+
45
+ if (
46
+ typeof req !== "object" ||
47
+ req === null ||
48
+ req.jsonrpc !== "2.0" ||
49
+ typeof req.method !== "string"
50
+ ) {
51
+ return this.error(
52
+ null,
53
+ JsonRpcErrorCodes.INVALID_REQUEST,
54
+ "Invalid request",
55
+ );
56
+ }
57
+
58
+ const id =
59
+ typeof (req as any)?.id === "string" ||
60
+ typeof (req as any)?.id === "number" ||
61
+ (req as any)?.id === null
62
+ ? (req as any).id
63
+ : null;
64
+
65
+ const handler = this.methods.get(req.method);
66
+ if (!handler) {
67
+ return id === null
68
+ ? null
69
+ : this.error(
70
+ req.id,
71
+ JsonRpcErrorCodes.METHOD_NOT_FOUND,
72
+ "Method not found",
73
+ );
74
+ }
75
+
76
+ try {
77
+ const result = await handler(req.params);
78
+ if (req.id === undefined) return null; // notification
79
+ return {
80
+ jsonrpc: "2.0",
81
+ result,
82
+ id: req.id,
83
+ };
84
+ } catch (err) {
85
+ return this.error(
86
+ req.id ?? null,
87
+ JsonRpcErrorCodes.INTERNAL_ERROR,
88
+ "Internal error",
89
+ err instanceof Error ? err.message : err,
90
+ );
91
+ }
92
+ }
93
+
94
+ private error<Result, Method = string>(
95
+ id: JsonRpcRequest<Method>["id"],
96
+ code: JsonRpcErrorCodes | number,
97
+ message: string,
98
+ data?: unknown,
99
+ ): JsonRpcResponse<Result, JsonRpcErrorCodes | number> {
100
+ return {
101
+ jsonrpc: "2.0",
102
+ error: { code, message, data },
103
+ id: id ?? null,
104
+ };
105
+ }
106
+ }
@@ -0,0 +1,5 @@
1
+ declare global {
2
+ export type JsonRpcId = string | number | null;
3
+ }
4
+
5
+ export { };
@@ -0,0 +1,10 @@
1
+ declare global {
2
+ export interface JsonRpcRequest<Method> {
3
+ jsonrpc: "2.0";
4
+ method: Method;
5
+ params?: unknown[];
6
+ id?: JsonRpcId;
7
+ }
8
+ }
9
+
10
+ export { };
@@ -0,0 +1,24 @@
1
+ declare global {
2
+ export interface JsonRpcSuccess<Result> {
3
+ jsonrpc: "2.0";
4
+ result: Result;
5
+ id: JsonRpcId;
6
+ }
7
+
8
+ export interface JsonRpcError<ErrorCode> {
9
+ jsonrpc: "2.0";
10
+ error: JsonRpcErrorObject<ErrorCode>;
11
+ id: JsonRpcId;
12
+ }
13
+
14
+ export interface JsonRpcErrorObject<ErrorCode> {
15
+ code: ErrorCode;
16
+ message: string;
17
+ data?: unknown;
18
+ }
19
+ export type JsonRpcResponse<Result, ErrorCode> =
20
+ | JsonRpcSuccess<Result>
21
+ | JsonRpcError<ErrorCode>;
22
+ }
23
+
24
+ export { };
package/tsconfig.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": [
5
+ "ESNext"
6
+ ],
7
+ "target": "ESNext",
8
+ "module": "Preserve",
9
+ "moduleDetection": "force",
10
+ "jsx": "react-jsx",
11
+ "allowJs": true,
12
+ // Bundler mode
13
+ "sourceMap": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "moduleResolution": "bundler",
17
+ "outDir": "dist",
18
+ "rootDir": "src",
19
+ "allowImportingTsExtensions": false,
20
+ "verbatimModuleSyntax": true,
21
+ "noEmit": false,
22
+ // Best practices
23
+ "strict": true,
24
+ "skipLibCheck": true,
25
+ "noFallthroughCasesInSwitch": true,
26
+ "noUncheckedIndexedAccess": true,
27
+ "noImplicitOverride": true,
28
+ // Some stricter flags (disabled by default)
29
+ "noUnusedLocals": false,
30
+ "noUnusedParameters": false,
31
+ "noPropertyAccessFromIndexSignature": false
32
+ },
33
+ "include": [
34
+ "src/**/*",
35
+ "global.d.ts"
36
+ ],
37
+ "exclude": [
38
+ "example.ts"
39
+ ]
40
+ }