Skip to content

Commit 852ebb9

Browse files
committed
Add an InMemoryTransport
1 parent c82d633 commit 852ebb9

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

src/inMemory.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { InMemoryTransport } from "./inMemory.js";
2+
import { JSONRPCMessage } from "./types.js";
3+
4+
describe("InMemoryTransport", () => {
5+
let clientTransport: InMemoryTransport;
6+
let serverTransport: InMemoryTransport;
7+
8+
beforeEach(() => {
9+
[clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
10+
});
11+
12+
test("should create linked pair", () => {
13+
expect(clientTransport).toBeDefined();
14+
expect(serverTransport).toBeDefined();
15+
});
16+
17+
test("should start without error", async () => {
18+
await expect(clientTransport.start()).resolves.not.toThrow();
19+
await expect(serverTransport.start()).resolves.not.toThrow();
20+
});
21+
22+
test("should send message from client to server", async () => {
23+
const message: JSONRPCMessage = {
24+
jsonrpc: "2.0",
25+
method: "test",
26+
id: 1,
27+
};
28+
29+
let receivedMessage: JSONRPCMessage | undefined;
30+
serverTransport.onmessage = (msg) => {
31+
receivedMessage = msg;
32+
};
33+
34+
await clientTransport.send(message);
35+
expect(receivedMessage).toEqual(message);
36+
});
37+
38+
test("should send message from server to client", async () => {
39+
const message: JSONRPCMessage = {
40+
jsonrpc: "2.0",
41+
method: "test",
42+
id: 1,
43+
};
44+
45+
let receivedMessage: JSONRPCMessage | undefined;
46+
clientTransport.onmessage = (msg) => {
47+
receivedMessage = msg;
48+
};
49+
50+
await serverTransport.send(message);
51+
expect(receivedMessage).toEqual(message);
52+
});
53+
54+
test("should handle close", async () => {
55+
let clientClosed = false;
56+
let serverClosed = false;
57+
58+
clientTransport.onclose = () => {
59+
clientClosed = true;
60+
};
61+
62+
serverTransport.onclose = () => {
63+
serverClosed = true;
64+
};
65+
66+
await clientTransport.close();
67+
expect(clientClosed).toBe(true);
68+
expect(serverClosed).toBe(true);
69+
});
70+
71+
test("should throw error when sending after close", async () => {
72+
await clientTransport.close();
73+
await expect(
74+
clientTransport.send({ jsonrpc: "2.0", method: "test", id: 1 }),
75+
).rejects.toThrow("Not connected");
76+
});
77+
78+
test("should queue messages sent before start", async () => {
79+
const message: JSONRPCMessage = {
80+
jsonrpc: "2.0",
81+
method: "test",
82+
id: 1,
83+
};
84+
85+
let receivedMessage: JSONRPCMessage | undefined;
86+
serverTransport.onmessage = (msg) => {
87+
receivedMessage = msg;
88+
};
89+
90+
await clientTransport.send(message);
91+
await serverTransport.start();
92+
expect(receivedMessage).toEqual(message);
93+
});
94+
});

src/inMemory.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Transport } from "./shared/transport.js";
2+
import { JSONRPCMessage } from "./types.js";
3+
4+
/**
5+
* In-memory transport for creating clients and servers that talk to each other within the same process.
6+
*/
7+
export class InMemoryTransport implements Transport {
8+
private _otherTransport?: InMemoryTransport;
9+
private _messageQueue: JSONRPCMessage[] = [];
10+
11+
onclose?: () => void;
12+
onerror?: (error: Error) => void;
13+
onmessage?: (message: JSONRPCMessage) => void;
14+
15+
/**
16+
* Creates a pair of linked in-memory transports that can communicate with each other. One should be passed to a Client and one to a Server.
17+
*/
18+
static createLinkedPair(): [InMemoryTransport, InMemoryTransport] {
19+
const clientTransport = new InMemoryTransport();
20+
const serverTransport = new InMemoryTransport();
21+
clientTransport._otherTransport = serverTransport;
22+
serverTransport._otherTransport = clientTransport;
23+
return [clientTransport, serverTransport];
24+
}
25+
26+
async start(): Promise<void> {
27+
// Process any messages that were queued before start was called
28+
while (this._messageQueue.length > 0) {
29+
const message = this._messageQueue.shift();
30+
if (message) {
31+
this.onmessage?.(message);
32+
}
33+
}
34+
}
35+
36+
async close(): Promise<void> {
37+
const other = this._otherTransport;
38+
this._otherTransport = undefined;
39+
await other?.close();
40+
this.onclose?.();
41+
}
42+
43+
async send(message: JSONRPCMessage): Promise<void> {
44+
if (!this._otherTransport) {
45+
throw new Error("Not connected");
46+
}
47+
48+
if (this._otherTransport.onmessage) {
49+
this._otherTransport.onmessage(message);
50+
} else {
51+
this._otherTransport._messageQueue.push(message);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)