@bigmistqke/rpc 0.1.3 → 0.1.4
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 +139 -0
- package/dist/fetch-node.js.map +1 -1
- package/dist/fetch.d.ts +1 -1
- package/dist/fetch.js +147 -6
- 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 +13 -8
- 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
- package/LICENSE +0 -21
package/test/messenger.test.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { describe,
|
|
2
|
-
import { expose,
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { createResponder, expose, handle, rpc } from '../src/messenger'
|
|
3
3
|
import {
|
|
4
|
+
$MESSENGER_ERROR,
|
|
5
|
+
$MESSENGER_HANDLE,
|
|
4
6
|
$MESSENGER_REQUEST,
|
|
5
7
|
$MESSENGER_RESPONSE,
|
|
6
|
-
$MESSENGER_ERROR,
|
|
7
8
|
$MESSENGER_RPC_REQUEST,
|
|
8
|
-
} from '../src/
|
|
9
|
+
} from '../src/protocol'
|
|
9
10
|
|
|
10
11
|
// Helper to flush pending promises
|
|
11
12
|
const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0))
|
|
@@ -121,14 +122,17 @@ describe('createResponder', () => {
|
|
|
121
122
|
})
|
|
122
123
|
|
|
123
124
|
describe('expose', () => {
|
|
124
|
-
it('should handle RPC requests
|
|
125
|
+
it('should handle RPC requests directly', async () => {
|
|
125
126
|
const port = createMockMessagePort()
|
|
126
|
-
const methods = {
|
|
127
|
-
greet: (name: string) => `Hello, ${name}!`,
|
|
128
|
-
}
|
|
129
127
|
|
|
130
|
-
expose(
|
|
128
|
+
expose(
|
|
129
|
+
{
|
|
130
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
131
|
+
},
|
|
132
|
+
{ to: port },
|
|
133
|
+
)
|
|
131
134
|
|
|
135
|
+
// Send RPC request directly (no init needed)
|
|
132
136
|
port._emit({
|
|
133
137
|
[$MESSENGER_REQUEST]: 1,
|
|
134
138
|
payload: {
|
|
@@ -151,15 +155,17 @@ describe('expose', () => {
|
|
|
151
155
|
|
|
152
156
|
it('should handle nested method calls', async () => {
|
|
153
157
|
const port = createMockMessagePort()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
|
|
159
|
+
expose(
|
|
160
|
+
{
|
|
161
|
+
user: {
|
|
162
|
+
profile: {
|
|
163
|
+
getName: () => 'John Doe',
|
|
164
|
+
},
|
|
158
165
|
},
|
|
159
166
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
expose(methods, { to: port })
|
|
167
|
+
{ to: port },
|
|
168
|
+
)
|
|
163
169
|
|
|
164
170
|
port._emit({
|
|
165
171
|
[$MESSENGER_REQUEST]: 1,
|
|
@@ -181,31 +187,167 @@ describe('expose', () => {
|
|
|
181
187
|
)
|
|
182
188
|
})
|
|
183
189
|
|
|
184
|
-
it('should
|
|
190
|
+
it('should handle async methods', async () => {
|
|
185
191
|
const port = createMockMessagePort()
|
|
186
|
-
const methods = {
|
|
187
|
-
test: vi.fn(),
|
|
188
|
-
}
|
|
189
192
|
|
|
190
|
-
expose(
|
|
193
|
+
expose(
|
|
194
|
+
{
|
|
195
|
+
asyncMethod: async () => {
|
|
196
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
197
|
+
return 'async result'
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{ to: port },
|
|
201
|
+
)
|
|
191
202
|
|
|
192
203
|
port._emit({
|
|
193
204
|
[$MESSENGER_REQUEST]: 1,
|
|
194
|
-
payload: {
|
|
205
|
+
payload: {
|
|
206
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
207
|
+
topics: ['asyncMethod'],
|
|
208
|
+
args: [],
|
|
209
|
+
},
|
|
195
210
|
})
|
|
196
211
|
|
|
197
|
-
|
|
212
|
+
await new Promise(resolve => setTimeout(resolve, 20))
|
|
213
|
+
|
|
214
|
+
expect(port.postMessage).toHaveBeenCalledWith(
|
|
215
|
+
{
|
|
216
|
+
[$MESSENGER_RESPONSE]: 1,
|
|
217
|
+
payload: 'async result',
|
|
218
|
+
},
|
|
219
|
+
[],
|
|
220
|
+
)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should return handle response when method returns handle()', async () => {
|
|
224
|
+
const port = createMockMessagePort()
|
|
225
|
+
|
|
226
|
+
expose(
|
|
227
|
+
{
|
|
228
|
+
init: () =>
|
|
229
|
+
handle({
|
|
230
|
+
getValue: () => 42,
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
{ to: port },
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
port._emit({
|
|
237
|
+
[$MESSENGER_REQUEST]: 1,
|
|
238
|
+
payload: {
|
|
239
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
240
|
+
topics: ['init'],
|
|
241
|
+
args: [],
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
await flushPromises()
|
|
246
|
+
|
|
247
|
+
// Should return a handle response with namespace ID
|
|
248
|
+
expect(port.postMessage).toHaveBeenCalledWith(
|
|
249
|
+
expect.objectContaining({
|
|
250
|
+
[$MESSENGER_RESPONSE]: 1,
|
|
251
|
+
payload: expect.objectContaining({
|
|
252
|
+
[$MESSENGER_HANDLE]: expect.stringContaining('__rpc_handle_'),
|
|
253
|
+
}),
|
|
254
|
+
}),
|
|
255
|
+
[],
|
|
256
|
+
)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should handle namespaced RPC calls from handle()', async () => {
|
|
260
|
+
const port = createMockMessagePort()
|
|
261
|
+
const getValue = vi.fn(() => 42)
|
|
262
|
+
|
|
263
|
+
expose(
|
|
264
|
+
{
|
|
265
|
+
init: () =>
|
|
266
|
+
handle({
|
|
267
|
+
getValue,
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
{ to: port },
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
// First call init to get namespace ID
|
|
274
|
+
port._emit({
|
|
275
|
+
[$MESSENGER_REQUEST]: 1,
|
|
276
|
+
payload: {
|
|
277
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
278
|
+
topics: ['init'],
|
|
279
|
+
args: [],
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
await flushPromises()
|
|
284
|
+
|
|
285
|
+
// Extract namespace ID from response
|
|
286
|
+
const initResponse = port.postMessage.mock.calls[0]![0]
|
|
287
|
+
const namespaceId = initResponse.payload[$MESSENGER_HANDLE]
|
|
288
|
+
|
|
289
|
+
// Now call method on the handle
|
|
290
|
+
port._emit({
|
|
291
|
+
[$MESSENGER_REQUEST]: 2,
|
|
292
|
+
payload: {
|
|
293
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
294
|
+
topics: [namespaceId, 'getValue'],
|
|
295
|
+
args: [],
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
await flushPromises()
|
|
300
|
+
|
|
301
|
+
expect(getValue).toHaveBeenCalled()
|
|
302
|
+
expect(port.postMessage).toHaveBeenLastCalledWith(
|
|
303
|
+
{
|
|
304
|
+
[$MESSENGER_RESPONSE]: 2,
|
|
305
|
+
payload: 42,
|
|
306
|
+
},
|
|
307
|
+
[],
|
|
308
|
+
)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('should handle errors in RPC methods', async () => {
|
|
312
|
+
const port = createMockMessagePort()
|
|
313
|
+
|
|
314
|
+
expose(
|
|
315
|
+
{
|
|
316
|
+
failingMethod: () => {
|
|
317
|
+
throw new Error('test error')
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{ to: port },
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
port._emit({
|
|
324
|
+
[$MESSENGER_REQUEST]: 1,
|
|
325
|
+
payload: {
|
|
326
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
327
|
+
topics: ['failingMethod'],
|
|
328
|
+
args: [],
|
|
329
|
+
},
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
await flushPromises()
|
|
333
|
+
|
|
334
|
+
expect(port.postMessage).toHaveBeenCalledWith(
|
|
335
|
+
expect.objectContaining({
|
|
336
|
+
[$MESSENGER_ERROR]: 1,
|
|
337
|
+
}),
|
|
338
|
+
undefined,
|
|
339
|
+
)
|
|
198
340
|
})
|
|
199
341
|
})
|
|
200
342
|
|
|
201
343
|
describe('rpc', () => {
|
|
202
|
-
it('should create a proxy that sends RPC requests', async () => {
|
|
344
|
+
it('should create a synchronous proxy that sends RPC requests', async () => {
|
|
203
345
|
const { port1, port2 } = createMockMessageChannel()
|
|
204
346
|
|
|
205
347
|
// Set up responder on port2
|
|
206
348
|
expose({ add: (a: number, b: number) => a + b }, { to: port2 })
|
|
207
349
|
|
|
208
|
-
// Create RPC proxy on port1
|
|
350
|
+
// Create RPC proxy on port1 (now synchronous)
|
|
209
351
|
const proxy = rpc<{ add: (a: number, b: number) => number }>(port1)
|
|
210
352
|
|
|
211
353
|
const result = await proxy.add(2, 3)
|
|
@@ -244,11 +386,7 @@ describe('rpc', () => {
|
|
|
244
386
|
|
|
245
387
|
const proxy = rpc<{ failingMethod: () => void }>(port1)
|
|
246
388
|
|
|
247
|
-
|
|
248
|
-
// so the response is undefined rather than an error rejection.
|
|
249
|
-
// This is a quirk of the current implementation.
|
|
250
|
-
const result = await proxy.failingMethod()
|
|
251
|
-
expect(result).toBeUndefined()
|
|
389
|
+
await expect(proxy.failingMethod()).rejects.toThrow()
|
|
252
390
|
})
|
|
253
391
|
|
|
254
392
|
it('should handle multiple concurrent requests', async () => {
|
|
@@ -276,6 +414,85 @@ describe('rpc', () => {
|
|
|
276
414
|
expect(result2).toBe('second')
|
|
277
415
|
expect(result3).toBe('third')
|
|
278
416
|
})
|
|
417
|
+
|
|
418
|
+
it('should create sub-proxy when method returns handle()', async () => {
|
|
419
|
+
const { port1, port2 } = createMockMessageChannel()
|
|
420
|
+
|
|
421
|
+
expose(
|
|
422
|
+
{
|
|
423
|
+
init: (multiplier: number) =>
|
|
424
|
+
handle({
|
|
425
|
+
multiply: (a: number) => a * multiplier,
|
|
426
|
+
}),
|
|
427
|
+
},
|
|
428
|
+
{ to: port2 },
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
const proxy = rpc<{
|
|
432
|
+
init: (multiplier: number) => { multiply: (a: number) => number }
|
|
433
|
+
}>(port1)
|
|
434
|
+
|
|
435
|
+
// Call init to get sub-proxy
|
|
436
|
+
const calculator = await proxy.init(10)
|
|
437
|
+
|
|
438
|
+
// Call method on sub-proxy
|
|
439
|
+
const result = await calculator.multiply(5)
|
|
440
|
+
expect(result).toBe(50)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('should handle async methods that return handle()', async () => {
|
|
444
|
+
const { port1, port2 } = createMockMessageChannel()
|
|
445
|
+
|
|
446
|
+
expose(
|
|
447
|
+
{
|
|
448
|
+
asyncInit: async (config: { prefix: string }) => {
|
|
449
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
450
|
+
return handle({
|
|
451
|
+
greet: (name: string) => `${config.prefix} ${name}!`,
|
|
452
|
+
})
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
{ to: port2 },
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
const proxy = rpc<{
|
|
459
|
+
asyncInit: (config: { prefix: string }) => Promise<{ greet: (name: string) => string }>
|
|
460
|
+
}>(port1)
|
|
461
|
+
|
|
462
|
+
const greeter = await proxy.asyncInit({ prefix: 'Hello,' })
|
|
463
|
+
const result = await greeter.greet('World')
|
|
464
|
+
expect(result).toBe('Hello, World!')
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
it('should handle nested handle() calls', async () => {
|
|
468
|
+
const { port1, port2 } = createMockMessageChannel()
|
|
469
|
+
|
|
470
|
+
expose(
|
|
471
|
+
{
|
|
472
|
+
createOuter: () =>
|
|
473
|
+
handle({
|
|
474
|
+
createInner: () =>
|
|
475
|
+
handle({
|
|
476
|
+
getValue: () => 'nested value',
|
|
477
|
+
}),
|
|
478
|
+
}),
|
|
479
|
+
},
|
|
480
|
+
{ to: port2 },
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
const proxy = rpc<{
|
|
484
|
+
createOuter: () => {
|
|
485
|
+
createInner: () => {
|
|
486
|
+
getValue: () => string
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}>(port1)
|
|
490
|
+
|
|
491
|
+
const outer = await proxy.createOuter()
|
|
492
|
+
const inner = await outer.createInner()
|
|
493
|
+
const result = await inner.getValue()
|
|
494
|
+
expect(result).toBe('nested value')
|
|
495
|
+
})
|
|
279
496
|
})
|
|
280
497
|
|
|
281
498
|
describe('Window vs Worker handling', () => {
|
|
@@ -288,6 +505,7 @@ describe('Window vs Worker handling', () => {
|
|
|
288
505
|
|
|
289
506
|
expose({ test: () => 'ok' }, { to: windowLike as any })
|
|
290
507
|
|
|
508
|
+
// Send RPC
|
|
291
509
|
windowLike.addEventListener.mock.calls[0] => {
|
|
|
302
520
|
await flushPromises()
|
|
303
521
|
|
|
304
522
|
// Window-style postMessage should include '*' as second argument
|
|
305
|
-
expect(windowLike.postMessage).toHaveBeenCalledWith(
|
|
306
|
-
expect.any(Object),
|
|
307
|
-
'*',
|
|
308
|
-
[],
|
|
309
|
-
)
|
|
523
|
+
expect(windowLike.postMessage).toHaveBeenCalledWith(expect.any(Object), '*', [])
|
|
310
524
|
})
|
|
311
525
|
|
|
312
526
|
it('should not use targetOrigin for Worker-like objects', async () => {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
ResponseShape,
|
|
5
|
-
ErrorShape,
|
|
6
|
-
RPCPayloadShape,
|
|
3
|
+
$MESSENGER_ERROR,
|
|
7
4
|
$MESSENGER_REQUEST,
|
|
8
5
|
$MESSENGER_RESPONSE,
|
|
9
|
-
$MESSENGER_ERROR,
|
|
10
6
|
$MESSENGER_RPC_REQUEST,
|
|
11
|
-
|
|
7
|
+
ErrorShape,
|
|
8
|
+
RequestShape,
|
|
9
|
+
ResponseShape,
|
|
10
|
+
RPCPayloadShape,
|
|
11
|
+
} from '../src/protocol'
|
|
12
12
|
|
|
13
13
|
describe('RequestShape', () => {
|
|
14
14
|
describe('validate', () => {
|
package/test/sse.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { $MESSENGER_REQUEST, $MESSENGER_RPC_REQUEST } from '../src/protocol'
|
|
3
|
+
import { expose, isSSEResponse, Payload, rpc } from '../src/server-send-events/index'
|
|
4
4
|
|
|
5
5
|
// Mock EventSource
|
|
6
6
|
class MockEventSource {
|
package/test/stream.test.ts
CHANGED
|
@@ -79,10 +79,7 @@ describe('rpc', () => {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// Set up endpoint 1 - reads from stream1, writes to its output stream
|
|
82
|
-
const endpoint1 = rpc<{ echo: (msg: string) => string }, typeof methods1>(
|
|
83
|
-
stream1,
|
|
84
|
-
methods1,
|
|
85
|
-
)
|
|
82
|
+
const endpoint1 = rpc<{ echo: (msg: string) => string }, typeof methods1>(stream1, methods1)
|
|
86
83
|
|
|
87
84
|
// Set up endpoint 2 - reads from stream2, writes to its output stream
|
|
88
85
|
const endpoint2 = rpc<{ ping: () => string }, typeof methods2>(stream2, methods2)
|
|
@@ -233,7 +230,6 @@ describe('rpc', () => {
|
|
|
233
230
|
enqueue2(decoder.decode(value))
|
|
234
231
|
}
|
|
235
232
|
})()
|
|
236
|
-
|
|
237
233
|
;(async () => {
|
|
238
234
|
while (true) {
|
|
239
235
|
const { done, value } = await reader2.read()
|
package/test/utils.test.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { callMethod, createCommander } from '../src/core'
|
|
2
3
|
import {
|
|
3
4
|
createIdAllocator,
|
|
4
5
|
createIdRegistry,
|
|
5
6
|
createPromiseRegistry,
|
|
6
7
|
defer,
|
|
7
|
-
createCommander,
|
|
8
|
-
callMethod,
|
|
9
8
|
createReadableStream,
|
|
10
9
|
streamToAsyncIterable,
|
|
11
10
|
createShape,
|