js-rpc2 1.1.0 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-rpc2",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "js web websocket http rpc",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/lib.js CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @import { ExtensionApi } from "./types.js"
3
+ */
4
+
1
5
  const JS_RPC_WITH_CRYPTO = true
2
6
 
3
7
  export const sleep = (/** @type {number} */ timeout) => new Promise((resolve) => setTimeout(resolve, timeout))
@@ -535,12 +539,14 @@ export function parseRpcItemData(array) {
535
539
  }
536
540
 
537
541
  /**
538
- * @param {object} extension
542
+ * @template T
543
+ * @param {ExtensionApi<T>} extension
539
544
  * @param {WritableStreamDefaultWriter<Uint8Array>} writer
540
545
  * @param {Uint8Array} buffer
541
- * @param {(msg:string)=>void} [logger]
546
+ * @param {(msg:string)=>void} logger
547
+ * @param {any} context
542
548
  */
543
- export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger) {
549
+ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger, context) {
544
550
  /** @type{RPC_DATA} */
545
551
  let box = null
546
552
  let dataId = 0
@@ -570,7 +576,17 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
570
576
  params.push(p.data)
571
577
  }
572
578
  }
573
- let ret = await extension[fnName](...params)
579
+ let store = extension.asyncLocalStorage
580
+ let ret = await new Promise((resolve, reject) => {
581
+ store.run(context, async () => {
582
+ try {
583
+ let ret = await extension[fnName](...params)
584
+ resolve(ret)
585
+ } catch (error) {
586
+ reject(error)
587
+ }
588
+ })
589
+ })
574
590
  if (Array.isArray(ret)) {
575
591
  box = { id: o.id, type: RPC_TYPE_RETURN_ARRAY, data: buildRpcItemData(ret), }
576
592
  } else {
@@ -589,7 +605,8 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
589
605
  }
590
606
  if (logger) {
591
607
  logger(`time: ${Date.now() - time}ms ${fnName}(${params.map(o => {
592
- if (typeof o == 'function') { return Function }
608
+ if (typeof o == 'function') { return `Function()` }
609
+ if (o instanceof Uint8Array) { return `Uint8Array(${o.length})` }
593
610
  return o
594
611
  }).join(', ')})`)
595
612
  }
@@ -631,11 +648,13 @@ export function createRPCProxy(apiInvoke) {
631
648
  */
632
649
 
633
650
  /**
651
+ * @template T
634
652
  * @param {{
635
653
  * rpcKey: string;
636
- * extension: object;
654
+ * extension: ExtensionApi<T>;
637
655
  * logger?: (msg:string)=>void;
638
656
  * async?: boolean;
657
+ * context?:any;
639
658
  * }} param
640
659
  */
641
660
  export function createRpcServerHelper(param) {
@@ -643,12 +662,13 @@ export function createRpcServerHelper(param) {
643
662
  const encode = createEncodeStream(rpc_key_iv)
644
663
  const decode = createDecodeStream(rpc_key_iv)
645
664
  let writer = encode.writable.getWriter()
665
+ let context = param.context
646
666
  decode.readable.pipeTo(new WritableStream({
647
667
  async write(buffer) {
648
668
  if (param.async) {
649
- rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger).catch(console.error)
669
+ rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger, context).catch(console.error)
650
670
  } else {
651
- await rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger)
671
+ await rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger, context)
652
672
  }
653
673
  },
654
674
  async close() {
package/src/lib.test.js CHANGED
@@ -6,6 +6,9 @@ import { WebSocketServer } from 'ws'
6
6
  import Koa from 'koa'
7
7
  import Router from 'koa-router'
8
8
  import { createRpcServerKoaRouter, createRpcServerWebSocket } from './server.js'
9
+ import { AsyncLocalStorage } from 'node:async_hooks'
10
+ import { Readable, Transform } from 'node:stream'
11
+ import { pipeline } from 'node:stream/promises'
9
12
 
10
13
  test('basic', async () => {
11
14
  // node --test-name-pattern="^basic$" src/lib.test.js
@@ -18,6 +21,8 @@ test('basic', async () => {
18
21
  let server = createServer(app.callback()).listen(9000)
19
22
 
20
23
  class RpcApi {
24
+ asyncLocalStorage = new AsyncLocalStorage()
25
+
21
26
  /**
22
27
  *
23
28
  * @param {string} string
@@ -62,7 +67,7 @@ test('basic', async () => {
62
67
  url: `http://127.0.0.1:9000/rpc/abc`, // others
63
68
  })
64
69
 
65
- let ret = await rpc.hello('123', new Uint8Array(3), { q: 2, w: 3, e: 4 }, null, undefined, [1, 2, 3, 4], (message, num) => {
70
+ let ret = await rpc.hello('123', new Uint8Array(3), { q: 2, w: 3, e: 4 }, null, undefined, [1, 2, 3, 4], async (message, num) => {
66
71
  console.info('callback', message, num)
67
72
  })
68
73
  console.info(ret)
@@ -74,6 +79,7 @@ test('测试RPC调用-WebSocket', async () => {
74
79
  // node --test-name-pattern="^测试RPC调用-WebSocket$" src/lib.test.js
75
80
 
76
81
  const extension = {
82
+ asyncLocalStorage: new AsyncLocalStorage(),
77
83
  hello: async function (/** @type {string} */ name) {
78
84
  return `hello ${name}`
79
85
  },
@@ -133,13 +139,13 @@ test('测试RPC调用-WebSocket', async () => {
133
139
  console.info(string)
134
140
  strictEqual(string, 'hello asdfghjkl')
135
141
 
136
- let stringcallback = await client.callback('asdfghjkl', (progress) => {
142
+ let stringcallback = await client.callback('asdfghjkl', async (progress) => {
137
143
  console.info(`client : ${progress}`)
138
144
  })
139
145
  strictEqual(stringcallback, 'hello callback asdfghjkl')
140
146
 
141
147
  let callbackCount = 0
142
- let stringcallback2 = await client.callback2('asdfghjkl', (progress, buffer) => {
148
+ let stringcallback2 = await client.callback2('asdfghjkl', async (progress, buffer) => {
143
149
  console.info(`client : ${progress}`, buffer)
144
150
  callbackCount++
145
151
  })
@@ -168,6 +174,7 @@ test('测试RPC调用-WebSocket', async () => {
168
174
  test('测试RPC调用-KoaRouter', async () => {
169
175
  // node --test-name-pattern="^测试RPC调用-KoaRouter$" src/lib.test.js
170
176
  const extension = {
177
+ asyncLocalStorage: new AsyncLocalStorage(),
171
178
  hello: async function (/** @type {string} */ name) {
172
179
  return `hello ${name}`
173
180
  },
@@ -211,6 +218,9 @@ test('测试RPC调用-KoaRouter', async () => {
211
218
  router: router,
212
219
  rpcKey: '11474f3dfbb861700cb6c3864b328311',
213
220
  extension: extension,
221
+ logger(msg) {
222
+ console.info(msg)
223
+ },
214
224
  })
215
225
 
216
226
  server.addListener('request', app.callback())
@@ -233,7 +243,7 @@ test('测试RPC调用-KoaRouter', async () => {
233
243
  strictEqual(string, 'hello asdfghjkl')
234
244
 
235
245
  let callbackCount = 0
236
- let stringcallback = await client.callback('asdfghjkl', (progress) => {
246
+ let stringcallback = await client.callback('asdfghjkl', async (progress) => {
237
247
  console.info(`client : ${progress}`)
238
248
  callbackCount++
239
249
  })
@@ -241,7 +251,7 @@ test('测试RPC调用-KoaRouter', async () => {
241
251
  strictEqual(3, callbackCount)
242
252
  strictEqual(stringcallback, 'hello callback asdfghjkl')
243
253
 
244
- let stringcallback2 = await client.callback2('asdfghjkl', (progress, buffer) => {
254
+ let stringcallback2 = await client.callback2('asdfghjkl', async (progress, buffer) => {
245
255
  console.info(`client : ${progress}`, buffer)
246
256
  })
247
257
  strictEqual(stringcallback2, 'hello callback asdfghjkl')
@@ -273,6 +283,54 @@ test('测试RPC调用-KoaRouter', async () => {
273
283
  console.info('over!')
274
284
  })
275
285
 
286
+ test('测试RPC调用-KoaRouter-AsyncLocalStorage', async () => {
287
+ // node --test-name-pattern="^测试RPC调用-KoaRouter-AsyncLocalStorage$" src/lib.test.js
288
+ const extension = {
289
+ asyncLocalStorage: new AsyncLocalStorage(),
290
+ hello: async function (/** @type {string} */ name) {
291
+ console.info('asyncLocalStorage.getStore', this.asyncLocalStorage.getStore())
292
+ return `hello ${name}`
293
+ },
294
+ }
295
+
296
+ await runWithAbortController(async (ac) => {
297
+ let server = createServer()
298
+ let app = new Koa()
299
+ let router = new Router()
300
+
301
+ ac.signal.addEventListener('abort', () => { server.close() })
302
+ createRpcServerKoaRouter({
303
+ path: '/abc',
304
+ router: router,
305
+ rpcKey: 'abc',
306
+ extension: extension,
307
+ logger(msg) {
308
+ console.info(msg)
309
+ },
310
+ })
311
+
312
+ server.addListener('request', app.callback())
313
+ app.use(router.routes())
314
+ app.use(router.allowedMethods())
315
+
316
+ server.listen(9000)
317
+ await sleep(100)
318
+
319
+ /** @type{typeof extension} */
320
+ let client = createRpcClientHttp({
321
+ url: `http://127.0.0.1:9000/abc`,
322
+ rpcKey: 'abc',
323
+ signal: ac.signal,
324
+ })
325
+ await sleep(100)
326
+
327
+ let string = await client.hello('asdfghjkl')
328
+ console.info(string)
329
+ strictEqual(string, 'hello asdfghjkl')
330
+ })
331
+ console.info('over!')
332
+ })
333
+
276
334
  /**
277
335
  * @param {(ac: AbortController) => Promise<void>} func
278
336
  */
@@ -296,6 +354,7 @@ test('error-stack', async () => {
296
354
  let server = createServer(app.callback()).listen(9000)
297
355
 
298
356
  class RpcApi {
357
+ asyncLocalStorage = new AsyncLocalStorage()
299
358
  async hello() {
300
359
  new URL('/')
301
360
  }
@@ -326,4 +385,59 @@ test('error-stack', async () => {
326
385
  }
327
386
  server.close()
328
387
 
388
+ })
389
+
390
+ test('async-local-storage', async () => {
391
+ // node --test-name-pattern="^async-local-storage$" src/lib.test.js
392
+ let store = new AsyncLocalStorage()
393
+
394
+ let p1 = Promise.withResolvers()
395
+ store.run({ a: 1 }, async () => {
396
+ console.info('--- 1 1 ', store.getStore())
397
+ await sleep(100)
398
+ console.info('--- 1 2 ', store.getStore())
399
+ p1.resolve()
400
+ })
401
+ let p2 = Promise.withResolvers()
402
+ store.run({ a: 2 }, async () => {
403
+ console.info('--- 2 1 ', store.getStore())
404
+ await sleep(100)
405
+ console.info('--- 2 2 ', store.getStore())
406
+ p2.resolve()
407
+ })
408
+ await Promise.all([p1.promise, p2.promise])
409
+ })
410
+
411
+ test('async-local-storage-stream', async () => {
412
+ // node --test-name-pattern="^async-local-storage-stream$" src/lib.test.js
413
+ let store = new AsyncLocalStorage()
414
+ let p1 = Promise.withResolvers()
415
+ let readable = new Readable({ read() { } })
416
+
417
+ store.run({ a: 1 }, async () => {
418
+ console.info('--- step 1 ', store.getStore())
419
+ !(async () => {
420
+ for (let i = 0; i < 3; i++) {
421
+ readable.push(`inner data:${i}`)
422
+ await sleep(10)
423
+ }
424
+ })()
425
+ await pipeline(readable, new Transform({
426
+ transform(chunk, _, callback) {
427
+ console.info(`--- step transform ${chunk} :`, store.getStore())
428
+ callback()
429
+ }
430
+ }))
431
+ console.info('--- step 2 ', store.getStore())
432
+ p1.resolve()
433
+ })
434
+
435
+ for (let i = 0; i < 3; i++) {
436
+ readable.push(`data:${i}`)
437
+ await sleep(10)
438
+ }
439
+ readable.push(null)
440
+ await sleep(100)
441
+
442
+ await Promise.all([p1.promise])
329
443
  })
package/src/server.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Readable } from "node:stream"
2
2
  import { createRpcServerHelper } from "./lib.js"
3
+ import { AsyncLocalStorage } from "node:async_hooks"
3
4
 
4
5
  /**
5
6
  * @import {WebSocketServer} from 'ws'
7
+ * @import {ExtensionApi} from './types.js'
6
8
  */
7
9
 
8
10
  export { createRpcServerHelper }
@@ -12,15 +14,19 @@ export { createRpcServerHelper }
12
14
  */
13
15
 
14
16
  /**
17
+ * @template T
15
18
  * @param {{
16
19
  * path: string;
17
20
  * wss: WebSocketServer;
18
21
  * rpcKey:string;
19
- * extension: Object;
22
+ * extension: ExtensionApi<T>;
20
23
  * logger?:(msg:string)=>void;
21
24
  * }} param
22
25
  */
23
26
  export function createRpcServerWebSocket(param) {
27
+ if (!param.extension.asyncLocalStorage) {
28
+ param.extension.asyncLocalStorage = new AsyncLocalStorage()
29
+ }
24
30
  param.wss.on('connection', (ws, request) => {
25
31
  let url = request.url
26
32
  if (url != param.path) {
@@ -54,18 +60,21 @@ export function createRpcServerWebSocket(param) {
54
60
  }
55
61
 
56
62
  /**
57
- *
63
+ * @template T
58
64
  * @param {{
59
65
  * path: string;
60
66
  * router: Router<any, {}>;
61
67
  * rpcKey?:string;
62
68
  * logger?:(msg:string)=>void;
63
- * extension: Object;
69
+ * extension: ExtensionApi<T>;
64
70
  * }} param
65
71
  */
66
72
  export function createRpcServerKoaRouter(param) {
73
+ if (!param.extension.asyncLocalStorage) {
74
+ param.extension.asyncLocalStorage = new AsyncLocalStorage()
75
+ }
67
76
  param.router.post(param.path, async (ctx) => {
68
- let helper = createRpcServerHelper({ rpcKey: param.rpcKey, extension: param.extension, logger: param.logger })
77
+ let helper = createRpcServerHelper({ rpcKey: param.rpcKey, extension: param.extension, logger: param.logger, context: ctx })
69
78
  /** @type{ReadableStream} */
70
79
  let a = Readable.toWeb(ctx.req)
71
80
  await a.pipeTo(helper.writable)
package/src/types.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ export type ExtensionApi<T> = { asyncLocalStorage: AsyncLocalStorage<T> } & object;