@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.
@@ -1,11 +1,12 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { expose, rpc, createResponder } from '../src/messenger'
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/message-protocol'
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 and call methods', async () => {
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(methods, { to: port })
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
- const methods = {
155
- user: {
156
- profile: {
157
- getName: () => 'John Doe',
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 ignore non-RPC payloads', () => {
190
+ it('should handle async methods', async () => {
185
191
  const port = createMockMessagePort()
186
- const methods = {
187
- test: vi.fn(),
188
- }
189
192
 
190
- expose(methods, { to: port })
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: { notRpc: true },
205
+ payload: {
206
+ [$MESSENGER_RPC_REQUEST]: true,
207
+ topics: ['asyncMethod'],
208
+ args: [],
209
+ },
195
210
  })
196
211
 
197
- expect(methods.test).not.toHaveBeenCalled()
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
- // Note: Currently, errors in expose() are caught and logged but not rethrown,
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]![1]({
292
510
  data: {
293
511
  [$MESSENGER_REQUEST]: 1,
@@ -302,11 +520,7 @@ describe('Window vs Worker handling', () => {
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, it, expect } from 'vitest'
1
+ import { describe, expect, it } from 'vitest'
2
2
  import {
3
- RequestShape,
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
- } from '../src/message-protocol'
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 { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
- import { expose, rpc, isSSEResponse, Payload } from '../src/server-send-events/index'
3
- import { $MESSENGER_REQUEST, $MESSENGER_RPC_REQUEST } from '../src/message-protocol'
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 {
@@ -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()
@@ -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,