Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/wet-beans-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@ckb-ccc/core": minor
---

refactor(core): rename JSON-RPC transport APIs with the `JsonRpcTransport` prefix

- `Transport` is now `JsonRpcTransport`
- `TransportHttp` is now `JsonRpcTransportHttp`
- `TransportWebSocket` is now `JsonRpcTransportWebSocket`
- `TransportFallback` is now `JsonRpcTransportFallback`
8 changes: 4 additions & 4 deletions packages/core/src/jsonRpc/requestor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RequestorJsonRpc } from "./requestor.js";
import {
JsonRpcPayload,
JsonRpcResponse,
Transport,
JsonRpcTransport,
} from "./transports/index.js";

function response(payload: JsonRpcPayload, result: unknown): JsonRpcResponse {
Expand Down Expand Up @@ -36,7 +36,7 @@ describe("RequestorJsonRpc", () => {
});
let active = 0;
let maxActive = 0;
const transport: Transport = {
const transport: JsonRpcTransport = {
async close() {},
async request(payload) {
active += 1;
Expand Down Expand Up @@ -64,7 +64,7 @@ describe("RequestorJsonRpc", () => {

it("advances the queue after a transport error", async () => {
let calls = 0;
const transport: Transport = {
const transport: JsonRpcTransport = {
async close() {},
async request(payload) {
calls += 1;
Expand All @@ -91,7 +91,7 @@ describe("RequestorJsonRpc", () => {

it("does not exhaust a larger concurrency limit after transport errors", async () => {
let calls = 0;
const transport: Transport = {
const transport: JsonRpcTransport = {
async close() {},
async request(payload) {
calls += 1;
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/jsonRpc/requestor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { transportFromUri } from "./transports/factory.js";
import { jsonRpcTransportFromUri } from "./transports/factory.js";
import {
JsonRpcPayload,
JsonRpcResponse,
Transport,
TransportFallback,
JsonRpcTransport,
JsonRpcTransportFallback,
} from "./transports/index.js";

/**
Expand All @@ -30,15 +30,15 @@ export type RequestorJsonRpcConfig = {
fallbacks?: string[];
timeout?: number;
maxConcurrent?: number;
transport?: Transport;
transport?: JsonRpcTransport;
};

export class RequestorJsonRpc {
public readonly maxConcurrent?: number;
private concurrent = 0;
private readonly pending: (() => void)[] = [];

public readonly transport: Transport;
public readonly transport: JsonRpcTransport;

private id = 0;

Expand All @@ -56,10 +56,10 @@ export class RequestorJsonRpc {
this.maxConcurrent = config?.maxConcurrent;
this.transport =
config?.transport ??
new TransportFallback(
new JsonRpcTransportFallback(
Array.from(
new Set([url_, ...(config?.fallbacks ?? [])]).values(),
(url) => transportFromUri(url, config),
(url) => jsonRpcTransportFromUri(url, config),
),
);
}
Expand Down
13 changes: 8 additions & 5 deletions packages/core/src/jsonRpc/transports/factory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { TransportHttp } from "./http.js";
import { TransportWebSocket } from "./webSocket.js";
import { JsonRpcTransportHttp } from "./http.js";
import { JsonRpcTransportWebSocket } from "./webSocket.js";

export function transportFromUri(uri: string, config?: { timeout?: number }) {
export function jsonRpcTransportFromUri(
uri: string,
config?: { timeout?: number },
) {
if (uri.startsWith("wss://") || uri.startsWith("ws://")) {
return new TransportWebSocket(uri, config?.timeout);
return new JsonRpcTransportWebSocket(uri, config?.timeout);
}

return new TransportHttp(uri, config?.timeout);
return new JsonRpcTransportHttp(uri, config?.timeout);
}
28 changes: 17 additions & 11 deletions packages/core/src/jsonRpc/transports/fallback.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, expect, it, vi } from "vitest";
import { TransportFallback } from "./fallback.js";
import { JsonRpcPayload, JsonRpcResponse, Transport } from "./transport.js";
import { JsonRpcTransportFallback } from "./fallback.js";
import {
JsonRpcPayload,
JsonRpcResponse,
JsonRpcTransport,
} from "./transport.js";

const payload: JsonRpcPayload = {
id: 0,
Expand All @@ -14,15 +18,17 @@ const response: JsonRpcResponse = {
result: "ok",
};

function makeTransport(handler: () => Promise<JsonRpcResponse>): Transport {
function makeTransport(
handler: () => Promise<JsonRpcResponse>,
): JsonRpcTransport {
return { request: () => handler(), async close() {} };
}

describe("TransportFallback", () => {
describe("JsonRpcTransportFallback", () => {
it("closes every transport", async () => {
const closeA = vi.fn();
const closeB = vi.fn();
const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
{ request: async () => response, close: closeA },
{ request: async () => response, close: closeB },
]);
Expand All @@ -39,7 +45,7 @@ describe("TransportFallback", () => {
throw error;
});
const closeB = vi.fn(async () => {});
const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
{ request: async () => response, close: closeA },
{ request: async () => response, close: closeB },
]);
Expand All @@ -51,22 +57,22 @@ describe("TransportFallback", () => {
});

it("returns result from the first healthy transport", async () => {
const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
makeTransport(() => Promise.resolve(response)),
]);
expect(await transport.request(payload)).toBe(response);
});

it("falls back to the next transport when the first fails", async () => {
const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
makeTransport(() => Promise.reject(new Error("fail"))),
makeTransport(() => Promise.resolve(response)),
]);
expect(await transport.request(payload)).toBe(response);
});

it("throws when all transports fail", async () => {
const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
makeTransport(() => Promise.reject(new Error("fail A"))),
makeTransport(() => Promise.reject(new Error("fail B"))),
]);
Expand All @@ -76,7 +82,7 @@ describe("TransportFallback", () => {
it("concurrent requests both succeed when the first transport is down", async () => {
// Transport A is always unavailable; transport B always succeeds.
// Two concurrent requests should each fall back to B independently.
const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
makeTransport(() => Promise.reject(new Error("A unavailable"))),
makeTransport(() => Promise.resolve(response)),
]);
Expand All @@ -100,7 +106,7 @@ describe("TransportFallback", () => {
let callsToA = 0;
let callsToB = 0;

const transport = new TransportFallback([
const transport = new JsonRpcTransportFallback([
makeTransport(() => {
callsToA += 1;
return Promise.reject(new Error("A unavailable"));
Expand Down
12 changes: 8 additions & 4 deletions packages/core/src/jsonRpc/transports/fallback.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { JsonRpcPayload, JsonRpcResponse, Transport } from "./transport.js";
import {
JsonRpcPayload,
JsonRpcResponse,
JsonRpcTransport,
} from "./transport.js";

export class TransportFallback implements Transport {
export class JsonRpcTransportFallback implements JsonRpcTransport {
// Current transport index
private i = 0;

constructor(private readonly transports: Transport[]) {}
constructor(private readonly transports: JsonRpcTransport[]) {}

async request(data: JsonRpcPayload): Promise<JsonRpcResponse> {
const startI = this.i;
let lastErr: unknown = new Error(
"TransportFallback requires at least one transport",
"JsonRpcTransportFallback requires at least one transport",
);

for (let tried = 0; tried < this.transports.length; tried += 1) {
Expand Down
17 changes: 9 additions & 8 deletions packages/core/src/jsonRpc/transports/http.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { TransportHttp } from "./http.js";
import { JsonRpcTransportHttp } from "./http.js";
import type { JsonRpcPayload, JsonRpcResponse } from "./transport.js";

const payload: JsonRpcPayload = {
Expand All @@ -19,7 +19,7 @@ afterEach(() => {
vi.unstubAllGlobals();
});

describe("TransportHttp", () => {
describe("JsonRpcTransportHttp", () => {
it("clears its timeout after a successful response", async () => {
vi.useFakeTimers();
vi.stubGlobal(
Expand All @@ -28,7 +28,7 @@ describe("TransportHttp", () => {
);

await expect(
new TransportHttp("https://example.com").request(payload),
new JsonRpcTransportHttp("https://example.com").request(payload),
).resolves.toEqual(response);
expect(vi.getTimerCount()).toBe(0);
});
Expand All @@ -41,7 +41,7 @@ describe("TransportHttp", () => {
);

await expect(
new TransportHttp("https://example.com").request(payload),
new JsonRpcTransportHttp("https://example.com").request(payload),
).rejects.toThrow("unavailable");
expect(vi.getTimerCount()).toBe(0);
});
Expand All @@ -56,7 +56,7 @@ describe("TransportHttp", () => {
);

await expect(
new TransportHttp("https://example.com").request(payload),
new JsonRpcTransportHttp("https://example.com").request(payload),
).rejects.toThrow("invalid JSON");
expect(vi.getTimerCount()).toBe(0);
});
Expand All @@ -75,9 +75,10 @@ describe("TransportHttp", () => {
),
);

const request = new TransportHttp("https://example.com", 1000).request(
payload,
);
const request = new JsonRpcTransportHttp(
"https://example.com",
1000,
).request(payload);
const rejection = expect(request).rejects.toThrow("aborted");
await vi.advanceTimersByTimeAsync(1000);

Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/jsonRpc/transports/http.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { JsonRpcPayload, JsonRpcResponse, Transport } from "./transport.js";
import {
JsonRpcPayload,
JsonRpcResponse,
JsonRpcTransport,
} from "./transport.js";

export class TransportHttp implements Transport {
export class JsonRpcTransportHttp implements JsonRpcTransport {
constructor(
private readonly url: string,
private readonly timeout = 30000,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/jsonRpc/transports/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ export type JsonRpcResponse<Result = unknown, Error = unknown> = {
| { result?: never; error: JsonRpcError<Error> }
);

export interface Transport {
export interface JsonRpcTransport {
/**
* Sends a JSON-RPC request to the server.
*
* @param payload - The JSON-RPC payload to send.
* @returns The JSON-RPC response.
*/
request(data: JsonRpcPayload): Promise<JsonRpcResponse>;
request(payload: JsonRpcPayload): Promise<JsonRpcResponse>;

/** Releases resources held by the transport. */
close(): Promise<void>;
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/jsonRpc/transports/webSocket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ vi.mock("isomorphic-ws", () => {
return { default: WebSocket };
});

import { TransportWebSocket } from "./webSocket.js";
import { JsonRpcTransportWebSocket } from "./webSocket.js";

describe("TransportWebSocket", () => {
describe("JsonRpcTransportWebSocket", () => {
beforeEach(() => {
mock.deferOpen = false;
mock.invalidResponse = false;
Expand All @@ -83,7 +83,7 @@ describe("TransportWebSocket", () => {
});

it("closes its socket", async () => {
const transport = new TransportWebSocket("ws://example.com");
const transport = new JsonRpcTransportWebSocket("ws://example.com");
await transport.request({
id: 0,
jsonrpc: "2.0",
Expand All @@ -101,7 +101,7 @@ describe("TransportWebSocket", () => {
it("ignores invalid JSON until the request times out", async () => {
vi.useFakeTimers();
mock.invalidResponse = true;
const transport = new TransportWebSocket("ws://example.com", 100);
const transport = new JsonRpcTransportWebSocket("ws://example.com", 100);

const request = expect(
transport.request({
Expand All @@ -122,7 +122,7 @@ describe("TransportWebSocket", () => {
vi.useFakeTimers();
const error = new Error("send failed");
mock.sendError = error;
const transport = new TransportWebSocket("ws://example.com", 100);
const transport = new JsonRpcTransportWebSocket("ws://example.com", 100);

await expect(
transport.request({
Expand All @@ -141,7 +141,7 @@ describe("TransportWebSocket", () => {
it("closes a connecting socket without sending after timeout", async () => {
vi.useFakeTimers();
mock.deferOpen = true;
const transport = new TransportWebSocket("ws://example.com", 100);
const transport = new JsonRpcTransportWebSocket("ws://example.com", 100);

const request = expect(
transport.request({
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/jsonRpc/transports/webSocket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import WebSocket from "isomorphic-ws";
import { JsonRpcPayload, JsonRpcResponse, Transport } from "./transport.js";
import {
JsonRpcPayload,
JsonRpcResponse,
JsonRpcTransport,
} from "./transport.js";

export class TransportWebSocket implements Transport {
export class JsonRpcTransportWebSocket implements JsonRpcTransport {
private ongoing: Map<
number,
[
Expand Down