@bigmistqke/rpc 0.1.3 → 0.1.5
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/.claude/settings.local.json +7 -0
- package/dist/fetch-node.js +140 -1
- package/dist/fetch-node.js.map +1 -1
- package/dist/fetch.d.ts +1 -1
- package/dist/fetch.js +148 -7
- package/dist/fetch.js.map +1 -1
- package/dist/handle-18d6fe9b.d.ts +24 -0
- package/dist/messenger.d.ts +33 -11
- package/dist/messenger.js +149 -39
- package/dist/messenger.js.map +1 -1
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +69 -28
- package/dist/stream.js.map +1 -1
- package/dist/{types-a5ce9c9a.d.ts → types-9f54da43.d.ts} +5 -1
- package/dist/websocket.d.ts +50 -0
- package/dist/websocket.js +617 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +5 -1
- package/src/core.ts +100 -0
- package/src/fetch/index.ts +2 -1
- package/src/fetch/node.ts +1 -1
- package/src/handle.ts +48 -0
- package/src/{messenger.ts → messenger/index.ts} +67 -25
- package/src/{message-protocol.ts → protocol.ts} +11 -1
- package/src/server-send-events/index.ts +3 -2
- package/src/stream/index.ts +3 -14
- package/src/types.ts +6 -1
- package/src/utils.ts +0 -32
- package/src/websocket/index.ts +142 -0
- package/test/messenger.test.ts +249 -35
- package/test/{message-protocol.test.ts → protocol.test.ts} +7 -7
- package/test/sse.test.ts +3 -3
- package/test/stream.test.ts +1 -5
- package/test/utils.test.ts +1 -2
- package/test/websocket.test.ts +514 -0
- package/tsup.config.ts +2 -1
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { $WEBSOCKET, expose, handle, rpc } from '../src/websocket'
|
|
3
|
+
import {
|
|
4
|
+
$MESSENGER_ERROR,
|
|
5
|
+
$MESSENGER_HANDLE,
|
|
6
|
+
$MESSENGER_REQUEST,
|
|
7
|
+
$MESSENGER_RESPONSE,
|
|
8
|
+
$MESSENGER_RPC_REQUEST,
|
|
9
|
+
} from '../src/protocol'
|
|
10
|
+
|
|
11
|
+
// Helper to flush pending promises
|
|
12
|
+
const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0))
|
|
13
|
+
|
|
14
|
+
// Mock WebSocket - simulates JSON serialization round-trip
|
|
15
|
+
function createMockWebSocket() {
|
|
16
|
+
const handlers: Array<(event: unknown) => void> = []
|
|
17
|
+
return {
|
|
18
|
+
send: vi.fn(),
|
|
19
|
+
close: vi.fn(),
|
|
20
|
+
addEventListener: vi.fn((type: string, handler: (event: unknown) => void) => {
|
|
21
|
+
if (type === 'message') {
|
|
22
|
+
handlers.push(handler)
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
_emit(data: any) {
|
|
26
|
+
// Simulate JSON round-trip like a real WebSocket
|
|
27
|
+
const serialized = JSON.stringify(data)
|
|
28
|
+
handlers.forEach(h => h({ data: serialized }))
|
|
29
|
+
},
|
|
30
|
+
_handlers: handlers,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Mock WebSocket pair - wires two sockets together with JSON serialization
|
|
35
|
+
function createMockWebSocketPair() {
|
|
36
|
+
const ws1 = createMockWebSocket()
|
|
37
|
+
const ws2 = createMockWebSocket()
|
|
38
|
+
|
|
39
|
+
// Wire sockets together: send on one emits on the other (with JSON round-trip)
|
|
40
|
+
ws1.send = vi.fn((data: string) => {
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
const parsed = JSON.parse(data)
|
|
43
|
+
ws2._emit(parsed)
|
|
44
|
+
}, 0)
|
|
45
|
+
})
|
|
46
|
+
ws2.send = vi.fn((data: string) => {
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
const parsed = JSON.parse(data)
|
|
49
|
+
ws1._emit(parsed)
|
|
50
|
+
}, 0)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return { ws1, ws2 }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('expose', () => {
|
|
57
|
+
it('should handle RPC requests', async () => {
|
|
58
|
+
const ws = createMockWebSocket()
|
|
59
|
+
|
|
60
|
+
expose(
|
|
61
|
+
{
|
|
62
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
63
|
+
},
|
|
64
|
+
{ to: ws },
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
ws._emit({
|
|
68
|
+
[$MESSENGER_REQUEST]: 1,
|
|
69
|
+
payload: {
|
|
70
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
71
|
+
topics: ['greet'],
|
|
72
|
+
args: ['World'],
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
await flushPromises()
|
|
77
|
+
|
|
78
|
+
expect(ws.send).toHaveBeenCalledWith(
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
[$MESSENGER_RESPONSE]: 1,
|
|
81
|
+
payload: 'Hello, World!',
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should handle nested method calls', async () => {
|
|
87
|
+
const ws = createMockWebSocket()
|
|
88
|
+
|
|
89
|
+
expose(
|
|
90
|
+
{
|
|
91
|
+
user: {
|
|
92
|
+
profile: {
|
|
93
|
+
getName: () => 'John Doe',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{ to: ws },
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
ws._emit({
|
|
101
|
+
[$MESSENGER_REQUEST]: 1,
|
|
102
|
+
payload: {
|
|
103
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
104
|
+
topics: ['user', 'profile', 'getName'],
|
|
105
|
+
args: [],
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
await flushPromises()
|
|
110
|
+
|
|
111
|
+
expect(ws.send).toHaveBeenCalledWith(
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
[$MESSENGER_RESPONSE]: 1,
|
|
114
|
+
payload: 'John Doe',
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should handle async methods', async () => {
|
|
120
|
+
const ws = createMockWebSocket()
|
|
121
|
+
|
|
122
|
+
expose(
|
|
123
|
+
{
|
|
124
|
+
asyncMethod: async () => {
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
126
|
+
return 'async result'
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{ to: ws },
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
ws._emit({
|
|
133
|
+
[$MESSENGER_REQUEST]: 1,
|
|
134
|
+
payload: {
|
|
135
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
136
|
+
topics: ['asyncMethod'],
|
|
137
|
+
args: [],
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
await new Promise(resolve => setTimeout(resolve, 20))
|
|
142
|
+
|
|
143
|
+
expect(ws.send).toHaveBeenCalledWith(
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
[$MESSENGER_RESPONSE]: 1,
|
|
146
|
+
payload: 'async result',
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should handle void-returning methods (undefined survives JSON round-trip)', async () => {
|
|
152
|
+
const ws = createMockWebSocket()
|
|
153
|
+
|
|
154
|
+
expose(
|
|
155
|
+
{
|
|
156
|
+
doSomething: () => {},
|
|
157
|
+
},
|
|
158
|
+
{ to: ws },
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
ws._emit({
|
|
162
|
+
[$MESSENGER_REQUEST]: 1,
|
|
163
|
+
payload: {
|
|
164
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
165
|
+
topics: ['doSomething'],
|
|
166
|
+
args: [],
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
await flushPromises()
|
|
171
|
+
|
|
172
|
+
// payload is undefined which gets stripped by JSON.stringify
|
|
173
|
+
// ResponseShape.validate should still pass with optional payload
|
|
174
|
+
const sent = JSON.parse(ws.send.mock.calls[0]![0])
|
|
175
|
+
expect(sent[$MESSENGER_RESPONSE]).toBe(1)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('should return handle response when method returns handle()', async () => {
|
|
179
|
+
const ws = createMockWebSocket()
|
|
180
|
+
|
|
181
|
+
expose(
|
|
182
|
+
{
|
|
183
|
+
init: () =>
|
|
184
|
+
handle({
|
|
185
|
+
getValue: () => 42,
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
{ to: ws },
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
ws._emit({
|
|
192
|
+
[$MESSENGER_REQUEST]: 1,
|
|
193
|
+
payload: {
|
|
194
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
195
|
+
topics: ['init'],
|
|
196
|
+
args: [],
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
await flushPromises()
|
|
201
|
+
|
|
202
|
+
const sent = JSON.parse(ws.send.mock.calls[0]![0])
|
|
203
|
+
expect(sent[$MESSENGER_RESPONSE]).toBe(1)
|
|
204
|
+
expect(sent.payload[$MESSENGER_HANDLE]).toContain('__rpc_handle_')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should handle namespaced RPC calls from handle()', async () => {
|
|
208
|
+
const ws = createMockWebSocket()
|
|
209
|
+
const getValue = vi.fn(() => 42)
|
|
210
|
+
|
|
211
|
+
expose(
|
|
212
|
+
{
|
|
213
|
+
init: () =>
|
|
214
|
+
handle({
|
|
215
|
+
getValue,
|
|
216
|
+
}),
|
|
217
|
+
},
|
|
218
|
+
{ to: ws },
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// First call init to get namespace ID
|
|
222
|
+
ws._emit({
|
|
223
|
+
[$MESSENGER_REQUEST]: 1,
|
|
224
|
+
payload: {
|
|
225
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
226
|
+
topics: ['init'],
|
|
227
|
+
args: [],
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
await flushPromises()
|
|
232
|
+
|
|
233
|
+
// Extract namespace ID from response
|
|
234
|
+
const initResponse = JSON.parse(ws.send.mock.calls[0]![0])
|
|
235
|
+
const namespaceId = initResponse.payload[$MESSENGER_HANDLE]
|
|
236
|
+
|
|
237
|
+
// Now call method on the handle
|
|
238
|
+
ws._emit({
|
|
239
|
+
[$MESSENGER_REQUEST]: 2,
|
|
240
|
+
payload: {
|
|
241
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
242
|
+
topics: [namespaceId, 'getValue'],
|
|
243
|
+
args: [],
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
await flushPromises()
|
|
248
|
+
|
|
249
|
+
expect(getValue).toHaveBeenCalled()
|
|
250
|
+
const response = JSON.parse(ws.send.mock.calls[1]![0])
|
|
251
|
+
expect(response).toEqual({
|
|
252
|
+
[$MESSENGER_RESPONSE]: 2,
|
|
253
|
+
payload: 42,
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('should handle errors in RPC methods', async () => {
|
|
258
|
+
const ws = createMockWebSocket()
|
|
259
|
+
|
|
260
|
+
expose(
|
|
261
|
+
{
|
|
262
|
+
failingMethod: () => {
|
|
263
|
+
throw new Error('test error')
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{ to: ws },
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
ws._emit({
|
|
270
|
+
[$MESSENGER_REQUEST]: 1,
|
|
271
|
+
payload: {
|
|
272
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
273
|
+
topics: ['failingMethod'],
|
|
274
|
+
args: [],
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
await flushPromises()
|
|
279
|
+
|
|
280
|
+
const sent = JSON.parse(ws.send.mock.calls[0]![0])
|
|
281
|
+
expect(sent[$MESSENGER_ERROR]).toBe(1)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('should ignore non-request messages', async () => {
|
|
285
|
+
const ws = createMockWebSocket()
|
|
286
|
+
|
|
287
|
+
expose(
|
|
288
|
+
{
|
|
289
|
+
test: () => 'ok',
|
|
290
|
+
},
|
|
291
|
+
{ to: ws },
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
ws._emit({ random: 'data' })
|
|
295
|
+
ws._emit({ [$MESSENGER_RESPONSE]: 1, payload: 'response' })
|
|
296
|
+
|
|
297
|
+
await flushPromises()
|
|
298
|
+
|
|
299
|
+
expect(ws.send).not.toHaveBeenCalled()
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should ignore invalid JSON', async () => {
|
|
303
|
+
const ws = createMockWebSocket()
|
|
304
|
+
|
|
305
|
+
expose(
|
|
306
|
+
{
|
|
307
|
+
test: () => 'ok',
|
|
308
|
+
},
|
|
309
|
+
{ to: ws },
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
// Send raw invalid JSON string directly to handlers
|
|
313
|
+
ws._handlers.forEach(h => h({ data: 'not valid json{' }))
|
|
314
|
+
|
|
315
|
+
await flushPromises()
|
|
316
|
+
|
|
317
|
+
expect(ws.send).not.toHaveBeenCalled()
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
describe('rpc', () => {
|
|
322
|
+
it('should call remote methods and receive results', async () => {
|
|
323
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
324
|
+
|
|
325
|
+
expose({ add: (a: number, b: number) => a + b }, { to: ws2 })
|
|
326
|
+
|
|
327
|
+
const proxy = rpc<{ add: (a: number, b: number) => number }>(ws1)
|
|
328
|
+
|
|
329
|
+
const result = await proxy.add(2, 3)
|
|
330
|
+
expect(result).toBe(5)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('should handle nested method calls via proxy', async () => {
|
|
334
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
335
|
+
|
|
336
|
+
expose(
|
|
337
|
+
{
|
|
338
|
+
math: {
|
|
339
|
+
multiply: (a: number, b: number) => a * b,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{ to: ws2 },
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
const proxy = rpc<{ math: { multiply: (a: number, b: number) => number } }>(ws1)
|
|
346
|
+
|
|
347
|
+
const result = await proxy.math.multiply(4, 5)
|
|
348
|
+
expect(result).toBe(20)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('should handle errors from remote methods', async () => {
|
|
352
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
353
|
+
|
|
354
|
+
expose(
|
|
355
|
+
{
|
|
356
|
+
failingMethod: () => {
|
|
357
|
+
throw new Error('Remote error')
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{ to: ws2 },
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
const proxy = rpc<{ failingMethod: () => void }>(ws1)
|
|
364
|
+
|
|
365
|
+
await expect(proxy.failingMethod()).rejects.toThrow()
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('should handle multiple concurrent requests', async () => {
|
|
369
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
370
|
+
|
|
371
|
+
expose(
|
|
372
|
+
{
|
|
373
|
+
delayed: async (ms: number, value: string) => {
|
|
374
|
+
await new Promise(resolve => setTimeout(resolve, ms))
|
|
375
|
+
return value
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{ to: ws2 },
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
const proxy = rpc<{ delayed: (ms: number, value: string) => Promise<string> }>(ws1)
|
|
382
|
+
|
|
383
|
+
const [result1, result2, result3] = await Promise.all([
|
|
384
|
+
proxy.delayed(30, 'first'),
|
|
385
|
+
proxy.delayed(10, 'second'),
|
|
386
|
+
proxy.delayed(20, 'third'),
|
|
387
|
+
])
|
|
388
|
+
|
|
389
|
+
expect(result1).toBe('first')
|
|
390
|
+
expect(result2).toBe('second')
|
|
391
|
+
expect(result3).toBe('third')
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('should handle void-returning methods', async () => {
|
|
395
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
396
|
+
|
|
397
|
+
const sideEffect = vi.fn()
|
|
398
|
+
expose(
|
|
399
|
+
{
|
|
400
|
+
fire: () => {
|
|
401
|
+
sideEffect()
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
{ to: ws2 },
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
const proxy = rpc<{ fire: () => void }>(ws1)
|
|
408
|
+
|
|
409
|
+
await proxy.fire()
|
|
410
|
+
expect(sideEffect).toHaveBeenCalled()
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should create sub-proxy when method returns handle()', async () => {
|
|
414
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
415
|
+
|
|
416
|
+
expose(
|
|
417
|
+
{
|
|
418
|
+
init: (multiplier: number) =>
|
|
419
|
+
handle({
|
|
420
|
+
multiply: (a: number) => a * multiplier,
|
|
421
|
+
}),
|
|
422
|
+
},
|
|
423
|
+
{ to: ws2 },
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
const proxy = rpc<{
|
|
427
|
+
init: (multiplier: number) => { multiply: (a: number) => number }
|
|
428
|
+
}>(ws1)
|
|
429
|
+
|
|
430
|
+
const calculator = await proxy.init(10)
|
|
431
|
+
const result = await calculator.multiply(5)
|
|
432
|
+
expect(result).toBe(50)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should handle async methods that return handle()', async () => {
|
|
436
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
437
|
+
|
|
438
|
+
expose(
|
|
439
|
+
{
|
|
440
|
+
asyncInit: async (config: { prefix: string }) => {
|
|
441
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
442
|
+
return handle({
|
|
443
|
+
greet: (name: string) => `${config.prefix} ${name}!`,
|
|
444
|
+
})
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
{ to: ws2 },
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
const proxy = rpc<{
|
|
451
|
+
asyncInit: (config: { prefix: string }) => Promise<{ greet: (name: string) => string }>
|
|
452
|
+
}>(ws1)
|
|
453
|
+
|
|
454
|
+
const greeter = await proxy.asyncInit({ prefix: 'Hello,' })
|
|
455
|
+
const result = await greeter.greet('World')
|
|
456
|
+
expect(result).toBe('Hello, World!')
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it('should handle nested handle() calls', async () => {
|
|
460
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
461
|
+
|
|
462
|
+
expose(
|
|
463
|
+
{
|
|
464
|
+
createOuter: () =>
|
|
465
|
+
handle({
|
|
466
|
+
createInner: () =>
|
|
467
|
+
handle({
|
|
468
|
+
getValue: () => 'nested value',
|
|
469
|
+
}),
|
|
470
|
+
}),
|
|
471
|
+
},
|
|
472
|
+
{ to: ws2 },
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
const proxy = rpc<{
|
|
476
|
+
createOuter: () => {
|
|
477
|
+
createInner: () => {
|
|
478
|
+
getValue: () => string
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}>(ws1)
|
|
482
|
+
|
|
483
|
+
const outer = await proxy.createOuter()
|
|
484
|
+
const inner = await outer.createInner()
|
|
485
|
+
const result = await inner.getValue()
|
|
486
|
+
expect(result).toBe('nested value')
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('should expose $WEBSOCKET with the original websocket', () => {
|
|
490
|
+
const ws = createMockWebSocket()
|
|
491
|
+
const proxy = rpc<{ test: () => void }>(ws)
|
|
492
|
+
|
|
493
|
+
// Access the underlying websocket via $WEBSOCKET
|
|
494
|
+
const isSame = proxy[$WEBSOCKET] === ws
|
|
495
|
+
expect(isSame).toBe(true)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it('should support bidirectional RPC on the same websocket', async () => {
|
|
499
|
+
const { ws1, ws2 } = createMockWebSocketPair()
|
|
500
|
+
|
|
501
|
+
// Side A exposes methods and calls side B
|
|
502
|
+
expose({ ping: () => 'pong' }, { to: ws1 })
|
|
503
|
+
const proxyB = rpc<{ echo: (msg: string) => string }>(ws1)
|
|
504
|
+
|
|
505
|
+
// Side B exposes methods and calls side A
|
|
506
|
+
expose({ echo: (msg: string) => msg }, { to: ws2 })
|
|
507
|
+
const proxyA = rpc<{ ping: () => string }>(ws2)
|
|
508
|
+
|
|
509
|
+
const [pong, echoed] = await Promise.all([proxyA.ping(), proxyB.echo('hello')])
|
|
510
|
+
|
|
511
|
+
expect(pong).toBe('pong')
|
|
512
|
+
expect(echoed).toBe('hello')
|
|
513
|
+
})
|
|
514
|
+
})
|
package/tsup.config.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { defineConfig } from 'tsup'
|
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
entry: {
|
|
5
|
-
messenger: 'src/messenger.ts',
|
|
5
|
+
messenger: 'src/messenger/index.ts',
|
|
6
|
+
websocket: 'src/websocket/index.ts',
|
|
6
7
|
stream: 'src/stream/index.ts',
|
|
7
8
|
fetch: 'src/fetch/index.ts',
|
|
8
9
|
'fetch-node': 'src/fetch/node.ts',
|