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 +2 -2
- package/src/lib.js +17 -6
- package/src/lib.test.js +139 -1
- package/src/server.js +13 -4
- package/src/types.ts +3 -0
package/package.json
CHANGED
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
|
-
* @
|
542
|
+
* @template T
|
543
|
+
* @param {ExtensionApi<T>} extension
|
539
544
|
* @param {WritableStreamDefaultWriter<Uint8Array>} writer
|
540
545
|
* @param {Uint8Array} buffer
|
541
|
-
* @param {(msg:string)=>void}
|
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:
|
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:
|
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:
|
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