js-rpc2 1.1.1 → 1.2.1

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.1",
4
4
  "description": "js web websocket http rpc",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -31,4 +31,4 @@
31
31
  "koa-router": "^13.0.1",
32
32
  "ws": "^8.18.0"
33
33
  }
34
- }
34
+ }
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
@@ -548,6 +554,8 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
548
554
  let params = []
549
555
  let time = Date.now()
550
556
  try {
557
+ let store = extension.asyncLocalStorage
558
+ store.enterWith(context)
551
559
  let o = parseRpcData(buffer)
552
560
  dataId = o.id
553
561
  let items = parseRpcItemData(o.data)
@@ -632,11 +640,13 @@ export function createRPCProxy(apiInvoke) {
632
640
  */
633
641
 
634
642
  /**
643
+ * @template T
635
644
  * @param {{
636
645
  * rpcKey: string;
637
- * extension: object;
646
+ * extension: ExtensionApi<T>;
638
647
  * logger?: (msg:string)=>void;
639
648
  * async?: boolean;
649
+ * context?:any;
640
650
  * }} param
641
651
  */
642
652
  export function createRpcServerHelper(param) {
@@ -644,12 +654,13 @@ export function createRpcServerHelper(param) {
644
654
  const encode = createEncodeStream(rpc_key_iv)
645
655
  const decode = createDecodeStream(rpc_key_iv)
646
656
  let writer = encode.writable.getWriter()
657
+ let context = param.context
647
658
  decode.readable.pipeTo(new WritableStream({
648
659
  async write(buffer) {
649
660
  if (param.async) {
650
- rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger).catch(console.error)
661
+ rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger, context).catch(console.error)
651
662
  } else {
652
- await rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger)
663
+ await rpcRunServerDecodeBuffer(param.extension, writer, buffer, param.logger, context)
653
664
  }
654
665
  },
655
666
  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,57 @@ 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
+ /** @type{AsyncLocalStorage<Koa.ParameterizedContext<Koa.DefaultState, Koa.DefaultContext, any>>} */
290
+ asyncLocalStorage: new AsyncLocalStorage(),
291
+ hello: async function (/** @type {string} */ name) {
292
+ let ctx = this.asyncLocalStorage.getStore()
293
+ strictEqual(ctx.path, '/abc')
294
+ return `hello ${name}`
295
+ },
296
+ }
297
+
298
+ await runWithAbortController(async (ac) => {
299
+ let server = createServer()
300
+ let app = new Koa()
301
+ let router = new Router()
302
+
303
+ ac.signal.addEventListener('abort', () => { server.close() })
304
+ createRpcServerKoaRouter({
305
+ path: '/abc',
306
+ router: router,
307
+ rpcKey: 'abc',
308
+ extension: extension,
309
+ logger(msg) {
310
+ console.info(msg)
311
+ },
312
+ })
313
+
314
+ server.addListener('request', app.callback())
315
+ app.use(router.routes())
316
+ app.use(router.allowedMethods())
317
+
318
+ server.listen(9000)
319
+ await sleep(100)
320
+
321
+ /** @type{typeof extension} */
322
+ let client = createRpcClientHttp({
323
+ url: `http://127.0.0.1:9000/abc`,
324
+ rpcKey: 'abc',
325
+ signal: ac.signal,
326
+ })
327
+ await sleep(100)
328
+
329
+ let string = await client.hello('asdfghjkl')
330
+ console.info(string)
331
+ strictEqual(string, 'hello asdfghjkl')
332
+ strictEqual(extension.asyncLocalStorage.getStore(), undefined)
333
+ })
334
+ console.info('over!')
335
+ })
336
+
279
337
  /**
280
338
  * @param {(ac: AbortController) => Promise<void>} func
281
339
  */
@@ -299,6 +357,7 @@ test('error-stack', async () => {
299
357
  let server = createServer(app.callback()).listen(9000)
300
358
 
301
359
  class RpcApi {
360
+ asyncLocalStorage = new AsyncLocalStorage()
302
361
  async hello() {
303
362
  new URL('/')
304
363
  }
@@ -329,4 +388,83 @@ test('error-stack', async () => {
329
388
  }
330
389
  server.close()
331
390
 
391
+ })
392
+
393
+ test('async-local-storage', async () => {
394
+ // node --test-name-pattern="^async-local-storage$" src/lib.test.js
395
+ let store = new AsyncLocalStorage()
396
+
397
+ let p1 = Promise.withResolvers()
398
+ store.run({ a: 1 }, async () => {
399
+ console.info('--- 1 1 ', store.getStore())
400
+ await sleep(100)
401
+ console.info('--- 1 2 ', store.getStore())
402
+ p1.resolve()
403
+ })
404
+ let p2 = Promise.withResolvers()
405
+ store.run({ a: 2 }, async () => {
406
+ console.info('--- 2 1 ', store.getStore())
407
+ await sleep(100)
408
+ console.info('--- 2 2 ', store.getStore())
409
+ p2.resolve()
410
+ })
411
+ await Promise.all([p1.promise, p2.promise])
412
+ })
413
+
414
+ test('async-local-storage-enterWith', async () => {
415
+ // node --test-name-pattern="^async-local-storage-enterWith$" src/lib.test.js
416
+ let store = new AsyncLocalStorage()
417
+ let p1 = Promise.withResolvers()
418
+ strictEqual(store.getStore(), undefined)
419
+ store.enterWith('v1')
420
+ strictEqual(store.getStore(), 'v1')
421
+ store.run('v2', async () => {
422
+ strictEqual(store.getStore(), 'v2')
423
+ let p2 = Promise.withResolvers()
424
+ setTimeout(() => {
425
+ strictEqual(store.getStore(), 'v2')
426
+ store.enterWith('v3')
427
+ strictEqual(store.getStore(), 'v3')
428
+ p2.resolve()
429
+ })
430
+ await p2.promise
431
+ strictEqual(store.getStore(), 'v2')
432
+ p1.resolve()
433
+ })
434
+ await p1.promise
435
+ strictEqual(store.getStore(), 'v1')
436
+ })
437
+
438
+ test('async-local-storage-stream', async () => {
439
+ // node --test-name-pattern="^async-local-storage-stream$" src/lib.test.js
440
+ let store = new AsyncLocalStorage()
441
+ let p1 = Promise.withResolvers()
442
+ let readable = new Readable({ read() { } })
443
+
444
+ store.run({ a: 1 }, async () => {
445
+ console.info('--- step 1 ', store.getStore())
446
+ !(async () => {
447
+ for (let i = 0; i < 3; i++) {
448
+ readable.push(`inner data:${i}`)
449
+ await sleep(10)
450
+ }
451
+ })()
452
+ await pipeline(readable, new Transform({
453
+ transform(chunk, _, callback) {
454
+ console.info(`--- step transform ${chunk} :`, store.getStore())
455
+ callback()
456
+ }
457
+ }))
458
+ console.info('--- step 2 ', store.getStore())
459
+ p1.resolve()
460
+ })
461
+
462
+ for (let i = 0; i < 3; i++) {
463
+ readable.push(`data:${i}`)
464
+ await sleep(10)
465
+ }
466
+ readable.push(null)
467
+ await sleep(100)
468
+
469
+ await Promise.all([p1.promise])
332
470
  })
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;