js-rpc2 1.1.1 → 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.1",
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 {
@@ -632,11 +648,13 @@ export function createRPCProxy(apiInvoke) {
632
648
  */
633
649
 
634
650
  /**
651
+ * @template T
635
652
  * @param {{
636
653
  * rpcKey: string;
637
- * extension: object;
654
+ * extension: ExtensionApi<T>;
638
655
  * logger?: (msg:string)=>void;
639
656
  * async?: boolean;
657
+ * context?:any;
640
658
  * }} param
641
659
  */
642
660
  export function createRpcServerHelper(param) {
@@ -644,12 +662,13 @@ export function createRpcServerHelper(param) {
644
662
  const encode = createEncodeStream(rpc_key_iv)
645
663
  const decode = createDecodeStream(rpc_key_iv)
646
664
  let writer = encode.writable.getWriter()
665
+ let context = param.context
647
666
  decode.readable.pipeTo(new WritableStream({
648
667
  async write(buffer) {
649
668
  if (param.async) {
650
- rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger).catch(console.error)
669
+ rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger, context).catch(console.error)
651
670
  } else {
652
- await rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger)
671
+ await rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger, context)
653
672
  }
654
673
  },
655
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
  },
@@ -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
  },
@@ -276,6 +283,54 @@ test('测试RPC调用-KoaRouter', async () => {
276
283
  console.info('over!')
277
284
  })
278
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
+
279
334
  /**
280
335
  * @param {(ac: AbortController) => Promise<void>} func
281
336
  */
@@ -299,6 +354,7 @@ test('error-stack', async () => {
299
354
  let server = createServer(app.callback()).listen(9000)
300
355
 
301
356
  class RpcApi {
357
+ asyncLocalStorage = new AsyncLocalStorage()
302
358
  async hello() {
303
359
  new URL('/')
304
360
  }
@@ -329,4 +385,59 @@ test('error-stack', async () => {
329
385
  }
330
386
  server.close()
331
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])
332
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;