comq 0.9.0 → 0.10.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/.github/dependabot.yml +2 -2
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/release.yaml +14 -15
- package/docs/reply-stream-topology-dark.jpg +0 -0
- package/docs/reply-stream-topology-light.jpg +0 -0
- package/examples/readme.md +19 -3
- package/examples/streams/consumer.js +36 -0
- package/examples/streams/producer.js +44 -0
- package/features/backpressure.feature +2 -2
- package/features/readme.md +2 -2
- package/features/rpc.streams.feature +81 -0
- package/features/rpc.streams.shards.feature +37 -0
- package/features/steps/.test/context.test.js +1 -1
- package/features/steps/.test/events.test.js +1 -1
- package/features/steps/.types/context.d.ts +9 -1
- package/features/steps/broker.js +2 -0
- package/features/steps/common.js +12 -0
- package/features/steps/context.js +9 -2
- package/features/steps/rpc.js +235 -0
- package/lerna.json +1 -2
- package/package.json +3 -2
- package/readme.md +136 -46
- package/source/.io/ReplyPipe.js +158 -0
- package/source/.io/ReplyStream.js +139 -0
- package/source/.io/const.js +10 -0
- package/source/.io/{replies.js → createReplyEmitter.js} +3 -6
- package/source/.io/index.js +5 -1
- package/source/attributes/failsafe.js +34 -0
- package/source/attributes/failsafe.test.js +133 -0
- package/source/attributes/index.js +13 -0
- package/source/attributes/lazy.js +79 -0
- package/source/attributes/lazy.test.js +299 -0
- package/source/attributes/memo.js +13 -0
- package/source/attributes/memo.test.js +36 -0
- package/source/attributes/recall.js +42 -0
- package/source/attributes/recall.test.js +92 -0
- package/source/attributes/track.js +32 -0
- package/source/attributes/track.test.js +133 -0
- package/source/channel.js +31 -18
- package/source/connect.js +2 -2
- package/source/connection.js +4 -11
- package/source/emitter.js +13 -0
- package/source/encode.js +1 -1
- package/source/events.js +2 -2
- package/source/io.js +187 -50
- package/source/shards/channel.js +13 -10
- package/source/shards/connection.js +2 -2
- package/test/channel.test.js +3 -1
- package/test/encode.test.js +2 -2
- package/test/encodings.js +1 -1
- package/test/io.diagnostics.test.js +2 -2
- package/test/io.request.test.js +4 -23
- package/test/shards/channel.test.js +1 -1
- package/test/shards/connection.test.js +1 -1
- package/types/channel.d.ts +13 -11
- package/types/connection.d.ts +3 -3
- package/types/diagnostic.d.ts +4 -3
- package/types/encoding.d.ts +2 -2
- package/types/index.d.ts +4 -4
- package/types/io.d.ts +33 -40
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { timeout } = require('@toa.io/generic')
|
|
5
|
+
|
|
6
|
+
const { lazy } = require('./lazy')
|
|
7
|
+
|
|
8
|
+
it('should be', async () => {
|
|
9
|
+
expect(lazy).toBeDefined()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// noinspection JSValidateTypes
|
|
13
|
+
/** @type {jest.MockedFn<(...args: any[]) => Promise<void>>} */
|
|
14
|
+
const action = jest.fn(async () => undefined)
|
|
15
|
+
|
|
16
|
+
// noinspection JSValidateTypes
|
|
17
|
+
/** @type {jest.MockedFn<(...args: any[]) => Promise<void>>} */
|
|
18
|
+
const initialize = jest.fn(async () => undefined)
|
|
19
|
+
|
|
20
|
+
const value = generate()
|
|
21
|
+
|
|
22
|
+
class LazyInitialized {
|
|
23
|
+
log = []
|
|
24
|
+
|
|
25
|
+
action = lazy(this, this.#initialize, this.#action)
|
|
26
|
+
|
|
27
|
+
async #initialize () {
|
|
28
|
+
await timeout(1)
|
|
29
|
+
await initialize()
|
|
30
|
+
this.log.push('initializer')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async #action (...args) {
|
|
34
|
+
await action(...args)
|
|
35
|
+
this.log.push('action')
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let instance
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest.clearAllMocks()
|
|
45
|
+
|
|
46
|
+
instance = new LazyInitialized()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should call initializer before action', async () => {
|
|
50
|
+
await instance.action()
|
|
51
|
+
|
|
52
|
+
expect(instance.log).toStrictEqual(['initializer', 'action'])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should pass arguments to action', async () => {
|
|
56
|
+
const args = [generate(), generate()]
|
|
57
|
+
|
|
58
|
+
await instance.action(...args)
|
|
59
|
+
|
|
60
|
+
expect(action).toHaveBeenCalledWith(...args)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should return value', async () => {
|
|
64
|
+
const output = await instance.action()
|
|
65
|
+
|
|
66
|
+
expect(output).toStrictEqual(value)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should call initializer once', async () => {
|
|
70
|
+
await instance.action()
|
|
71
|
+
await instance.action()
|
|
72
|
+
|
|
73
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
74
|
+
expect(instance.log).toStrictEqual(['initializer', 'action', 'action'])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should call initializer once on concurrent calls', async () => {
|
|
78
|
+
await Promise.all([instance.action(), instance.action()])
|
|
79
|
+
|
|
80
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
81
|
+
expect(instance.log).toStrictEqual(['initializer', 'action', 'action'])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should call initializer once per instance', async () => {
|
|
85
|
+
const instance2 = new LazyInitialized()
|
|
86
|
+
|
|
87
|
+
await instance.action()
|
|
88
|
+
await instance2.action()
|
|
89
|
+
|
|
90
|
+
expect(initialize).toHaveBeenCalledTimes(2)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// noinspection JSValidateTypes
|
|
94
|
+
/** @type {jest.MockedFn<(...args: any[]) => Promise<void>>} */
|
|
95
|
+
const initialize2 = jest.fn(async () => undefined)
|
|
96
|
+
|
|
97
|
+
class InitializerSet {
|
|
98
|
+
action = lazy(this, [this.#initialize1, this.#initialize2], this.#action)
|
|
99
|
+
|
|
100
|
+
async #action () {
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #initialize1 () {
|
|
105
|
+
await initialize()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async #initialize2 () {
|
|
109
|
+
await initialize2()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
it('should run list of initializers', async () => {
|
|
114
|
+
const instance = new InitializerSet()
|
|
115
|
+
|
|
116
|
+
await instance.action()
|
|
117
|
+
|
|
118
|
+
expect(initialize).toHaveBeenCalled()
|
|
119
|
+
expect(initialize2).toHaveBeenCalled()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
class InitializerIntersection {
|
|
123
|
+
do = lazy(this, [this.#initialize1, this.#initialize2], this.#do)
|
|
124
|
+
|
|
125
|
+
undo = lazy(this, this.#initialize2, this.#undo)
|
|
126
|
+
|
|
127
|
+
async #do () {
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async #undo () {
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async #initialize1 () {
|
|
136
|
+
await initialize()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async #initialize2 () {
|
|
140
|
+
await initialize2()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
it('should call intersecting initializers once', async () => {
|
|
145
|
+
jest.clearAllMocks()
|
|
146
|
+
|
|
147
|
+
const instance = new InitializerIntersection()
|
|
148
|
+
|
|
149
|
+
await instance.do()
|
|
150
|
+
await instance.undo()
|
|
151
|
+
|
|
152
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
153
|
+
expect(initialize2).toHaveBeenCalledTimes(1)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should call intersecting concurrent initializers once', async () => {
|
|
157
|
+
jest.clearAllMocks()
|
|
158
|
+
|
|
159
|
+
const instance = new InitializerIntersection()
|
|
160
|
+
|
|
161
|
+
await Promise.all([instance.do(), instance.undo()])
|
|
162
|
+
|
|
163
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
164
|
+
expect(initialize2).toHaveBeenCalledTimes(1)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
class InitializersWithArguments {
|
|
168
|
+
do = lazy(this, [this.#initialize1, this.#initialize2], this.#do)
|
|
169
|
+
|
|
170
|
+
async #do (a, b, c) {}
|
|
171
|
+
|
|
172
|
+
async #initialize1 () {
|
|
173
|
+
await initialize(arguments)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async #initialize2 (a, b) {
|
|
177
|
+
await initialize2(arguments)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
it('should pass arguments to initializers if expected', async () => {
|
|
182
|
+
jest.clearAllMocks()
|
|
183
|
+
|
|
184
|
+
const instance = new InitializersWithArguments()
|
|
185
|
+
|
|
186
|
+
await instance.do(1, 2, 3)
|
|
187
|
+
|
|
188
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
189
|
+
|
|
190
|
+
const args = initialize.mock.calls[0][0]
|
|
191
|
+
|
|
192
|
+
expect(args.length).toStrictEqual(0)
|
|
193
|
+
|
|
194
|
+
expect(initialize2).toHaveBeenCalledTimes(1)
|
|
195
|
+
|
|
196
|
+
const args2 = initialize2.mock.calls[0][0]
|
|
197
|
+
|
|
198
|
+
expect(args2.length).toStrictEqual(2)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should call conditions with different argument values', async () => {
|
|
202
|
+
jest.clearAllMocks()
|
|
203
|
+
|
|
204
|
+
const instance = new InitializersWithArguments()
|
|
205
|
+
|
|
206
|
+
await instance.do(1, 2, 3)
|
|
207
|
+
await instance.do(1, 2)
|
|
208
|
+
await instance.do(2, 2)
|
|
209
|
+
|
|
210
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
211
|
+
expect(initialize2).toHaveBeenCalledTimes(2)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
class OrderedInitializers {
|
|
215
|
+
log = []
|
|
216
|
+
|
|
217
|
+
do = lazy(this, [this.#initialize1, this.#initialize2], async () => undefined)
|
|
218
|
+
|
|
219
|
+
async #initialize1 () {
|
|
220
|
+
await timeout(1)
|
|
221
|
+
this.log.push(1)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async #initialize2 (_0, _1) {
|
|
225
|
+
this.log.push(2)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
it('should call initializers sequentially', async () => {
|
|
230
|
+
const instance = new OrderedInitializers()
|
|
231
|
+
|
|
232
|
+
await instance.do()
|
|
233
|
+
|
|
234
|
+
expect(instance.log).toStrictEqual([1, 2])
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should override argument values', async () => {
|
|
238
|
+
const method = /** @type {Function} */ jest.fn()
|
|
239
|
+
|
|
240
|
+
class Test {
|
|
241
|
+
do = lazy(this, this.#update, method)
|
|
242
|
+
|
|
243
|
+
#update (foo, bar) {
|
|
244
|
+
return [foo + ' updated', bar + ' updated']
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const test = new Test()
|
|
249
|
+
const foo = generate()
|
|
250
|
+
const bar = generate()
|
|
251
|
+
|
|
252
|
+
await test.do(foo, bar)
|
|
253
|
+
|
|
254
|
+
expect(method).toHaveBeenCalledWith(foo + ' updated', bar + ' updated')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('should partially override argument values', async () => {
|
|
258
|
+
const method = /** @type {Function} */ jest.fn()
|
|
259
|
+
|
|
260
|
+
class Test {
|
|
261
|
+
do = lazy(this, this.#update, method)
|
|
262
|
+
|
|
263
|
+
#update (foo, _) {
|
|
264
|
+
return [foo + ' updated']
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const test = new Test()
|
|
269
|
+
const foo = generate()
|
|
270
|
+
const bar = generate()
|
|
271
|
+
|
|
272
|
+
await test.do(foo, bar)
|
|
273
|
+
|
|
274
|
+
expect(method).toHaveBeenCalledWith(foo + ' updated', bar)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
describe('reset', () => {
|
|
278
|
+
it('should be', async () => {
|
|
279
|
+
expect(lazy.reset).toBeDefined()
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('should reset initialization', async () => {
|
|
283
|
+
const instance = new InitializerIntersection()
|
|
284
|
+
|
|
285
|
+
await instance.do()
|
|
286
|
+
await instance.undo()
|
|
287
|
+
|
|
288
|
+
expect(initialize).toHaveBeenCalledTimes(1)
|
|
289
|
+
expect(initialize2).toHaveBeenCalledTimes(1)
|
|
290
|
+
|
|
291
|
+
lazy.reset(instance)
|
|
292
|
+
|
|
293
|
+
await instance.do()
|
|
294
|
+
await instance.undo()
|
|
295
|
+
|
|
296
|
+
expect(initialize).toHaveBeenCalledTimes(2)
|
|
297
|
+
expect(initialize2).toHaveBeenCalledTimes(2)
|
|
298
|
+
})
|
|
299
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { memo } = require('./memo')
|
|
4
|
+
|
|
5
|
+
let fn
|
|
6
|
+
let memoized
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks()
|
|
10
|
+
|
|
11
|
+
fn = jest.fn(() => Math.random())
|
|
12
|
+
memoized = memo(fn)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should return value', async () => {
|
|
16
|
+
const value = memoized()
|
|
17
|
+
|
|
18
|
+
expect(value).toBe(fn.mock.results[0].value)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should pass arguments', async () => {
|
|
22
|
+
const args = [1, 2, 3]
|
|
23
|
+
|
|
24
|
+
memoized(...args)
|
|
25
|
+
|
|
26
|
+
expect(fn).toHaveBeenCalledWith(...args)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should memoize value', async () => {
|
|
30
|
+
const one = memoized()
|
|
31
|
+
const two = memoized()
|
|
32
|
+
|
|
33
|
+
expect(fn).toHaveBeenCalledTimes(1)
|
|
34
|
+
expect(one).toBe(fn.mock.results[0].value)
|
|
35
|
+
expect(two).toBe(fn.mock.results[0].value)
|
|
36
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function recall (context, method = undefined) {
|
|
4
|
+
if (method === undefined) return replay(context)
|
|
5
|
+
else return recorder(context, method)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const recorder = (context, method) => async function (...args) {
|
|
9
|
+
if (context[METHODS] === undefined) context[METHODS] = []
|
|
10
|
+
|
|
11
|
+
if (method[CALLS] === undefined) {
|
|
12
|
+
context[METHODS].push(method)
|
|
13
|
+
method[CALLS] = []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = await method.apply(context, args)
|
|
17
|
+
|
|
18
|
+
method[CALLS].push(args)
|
|
19
|
+
|
|
20
|
+
return result
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function replay (context) {
|
|
24
|
+
if (context[METHODS] === undefined) return
|
|
25
|
+
|
|
26
|
+
const promises = []
|
|
27
|
+
|
|
28
|
+
for (const method of context[METHODS]) {
|
|
29
|
+
for (const args of method[CALLS]) {
|
|
30
|
+
const promise = method.apply(context, args)
|
|
31
|
+
|
|
32
|
+
promises.push(promise)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await Promise.all(promises)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const METHODS = Symbol('context methods')
|
|
40
|
+
const CALLS = Symbol('method calls')
|
|
41
|
+
|
|
42
|
+
exports.recall = recall
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { recall } = require('./recall')
|
|
5
|
+
|
|
6
|
+
it('should be', async () => {
|
|
7
|
+
expect(recall).toBeDefined()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const context = { foo: generate() }
|
|
11
|
+
|
|
12
|
+
/** @type {jest.MockedFn<(...args: any[]) => any>} */
|
|
13
|
+
let method
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks()
|
|
17
|
+
|
|
18
|
+
method = /** @type {jest.MockedFn<(...args: any[]) => any>} */
|
|
19
|
+
jest.fn(function () { return this.foo })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should return function', async () => {
|
|
23
|
+
const func = recall(context, method)
|
|
24
|
+
|
|
25
|
+
expect(func).toBeInstanceOf(Function)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should return result', async () => {
|
|
29
|
+
const func = recall(context, method)
|
|
30
|
+
const output = await func()
|
|
31
|
+
|
|
32
|
+
expect(output).toStrictEqual(await method.mock.results[0].value)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should call method within the context', async () => {
|
|
36
|
+
const func = recall(context, method)
|
|
37
|
+
const output = await func()
|
|
38
|
+
|
|
39
|
+
expect(output).toStrictEqual(context.foo)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should pass arguments', async () => {
|
|
43
|
+
const args = [generate(), generate()]
|
|
44
|
+
const func = recall(context, method)
|
|
45
|
+
|
|
46
|
+
await func(...args)
|
|
47
|
+
|
|
48
|
+
expect(method).toHaveBeenCalledWith(...args)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should re-call', async () => {
|
|
52
|
+
const args1 = [generate(), generate()]
|
|
53
|
+
const args2 = [generate(), generate(), generate()]
|
|
54
|
+
const func = recall(context, method)
|
|
55
|
+
|
|
56
|
+
await func(...args1)
|
|
57
|
+
await func(...args2)
|
|
58
|
+
|
|
59
|
+
method.mockClear()
|
|
60
|
+
|
|
61
|
+
expect(method).not.toHaveBeenCalled()
|
|
62
|
+
|
|
63
|
+
await recall(context)
|
|
64
|
+
|
|
65
|
+
expect(method).toHaveBeenCalledWith(...args1)
|
|
66
|
+
expect(method).toHaveBeenCalledWith(...args2)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should not trow on empty re-call', async () => {
|
|
70
|
+
await expect(recall(context)).resolves.not.toThrow()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should not re-call those thrown exceptions', async () => {
|
|
74
|
+
expect.assertions(2)
|
|
75
|
+
|
|
76
|
+
method.mockImplementationOnce(async () => 1)
|
|
77
|
+
method.mockImplementationOnce(async () => { throw new Error() })
|
|
78
|
+
|
|
79
|
+
const func = recall(context, method)
|
|
80
|
+
|
|
81
|
+
await func()
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await func()
|
|
85
|
+
} catch (e) {
|
|
86
|
+
expect(e).toBeDefined()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await recall(context)
|
|
90
|
+
|
|
91
|
+
expect(method).toHaveBeenCalledTimes(3)
|
|
92
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function track (context, method = undefined) {
|
|
4
|
+
if (method === undefined) return promises(context)
|
|
5
|
+
|
|
6
|
+
context[KEY] ??= new Set()
|
|
7
|
+
|
|
8
|
+
const tracking = context[KEY]
|
|
9
|
+
|
|
10
|
+
return async function (...args) {
|
|
11
|
+
const promise = method.apply(this, args)
|
|
12
|
+
|
|
13
|
+
tracking.add(promise)
|
|
14
|
+
promise.catch(noop).finally(() => tracking.delete(promise))
|
|
15
|
+
|
|
16
|
+
return promise
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function promises (context) {
|
|
21
|
+
if (context[KEY] === undefined) return
|
|
22
|
+
|
|
23
|
+
const promises = context[KEY]
|
|
24
|
+
|
|
25
|
+
return Promise.all(promises)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const KEY = Symbol('context tracking key')
|
|
29
|
+
|
|
30
|
+
const noop = () => undefined
|
|
31
|
+
|
|
32
|
+
exports.track = track
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { promex } = require('@toa.io/generic')
|
|
5
|
+
|
|
6
|
+
const { track } = require('./track')
|
|
7
|
+
|
|
8
|
+
it('should be', async () => {
|
|
9
|
+
expect(track).toBeDefined()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
let done
|
|
13
|
+
let undone
|
|
14
|
+
|
|
15
|
+
const method1 = /** @type {jest.MockedFn<(a: string, b: string) => Promise>} */
|
|
16
|
+
jest.fn(async function () {
|
|
17
|
+
// should execute in context
|
|
18
|
+
expect(this.ok).toStrictEqual(1)
|
|
19
|
+
|
|
20
|
+
return done
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const method2 = /** @type {jest.MockedFn<(a: string, b: string) => Promise>} */
|
|
24
|
+
jest.fn(async function () { return undone })
|
|
25
|
+
|
|
26
|
+
class Test {
|
|
27
|
+
ok = 1
|
|
28
|
+
do = track(this, method1)
|
|
29
|
+
undo = track(this, method2)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @type {Test} */
|
|
33
|
+
let test
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks()
|
|
37
|
+
|
|
38
|
+
done = promex()
|
|
39
|
+
undone = promex()
|
|
40
|
+
test = new Test()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should return function', async () => {
|
|
44
|
+
expect(test.do).toBeInstanceOf(Function)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should execute method', async () => {
|
|
48
|
+
const result = generate()
|
|
49
|
+
|
|
50
|
+
done.resolve(result)
|
|
51
|
+
|
|
52
|
+
const args = [generate(), generate()]
|
|
53
|
+
|
|
54
|
+
const output = await test.do(...args)
|
|
55
|
+
|
|
56
|
+
expect(method1).toHaveBeenCalledWith(...args)
|
|
57
|
+
expect(output).toStrictEqual(result)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should track method execution', async () => {
|
|
61
|
+
expect.assertions(2)
|
|
62
|
+
|
|
63
|
+
setImmediate(async () => {
|
|
64
|
+
setImmediate(() => {
|
|
65
|
+
done.resolve()
|
|
66
|
+
|
|
67
|
+
expect(finished).toStrictEqual(false)
|
|
68
|
+
|
|
69
|
+
setImmediate(() => {
|
|
70
|
+
expect(finished).toStrictEqual(true)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
let finished = false
|
|
75
|
+
|
|
76
|
+
await track(test)
|
|
77
|
+
|
|
78
|
+
finished = true
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await test.do()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should track multiple methods', async () => {
|
|
85
|
+
expect.assertions(3 + 1) // + method1
|
|
86
|
+
|
|
87
|
+
setImmediate(async () => {
|
|
88
|
+
setImmediate(() => {
|
|
89
|
+
done.resolve()
|
|
90
|
+
|
|
91
|
+
expect(finished).toStrictEqual(false)
|
|
92
|
+
|
|
93
|
+
setImmediate(() => {
|
|
94
|
+
undone.resolve()
|
|
95
|
+
|
|
96
|
+
expect(finished).toStrictEqual(false)
|
|
97
|
+
setImmediate(() => expect(finished).toStrictEqual(true))
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
let finished = false
|
|
102
|
+
|
|
103
|
+
await track(test)
|
|
104
|
+
|
|
105
|
+
finished = true
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
await Promise.all([test.do(), test.undo()])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should resolve if methods haven\'t been called', async () => {
|
|
112
|
+
await track(this)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should handle exceptions', async () => {
|
|
116
|
+
expect.assertions(2)
|
|
117
|
+
|
|
118
|
+
const exception = new Error(generate())
|
|
119
|
+
|
|
120
|
+
class Bad {
|
|
121
|
+
do = track(this, async () => { throw exception })
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const b = new Bad()
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await b.do()
|
|
128
|
+
} catch (e) {
|
|
129
|
+
expect(e).toStrictEqual(exception)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await expect(track(b)).resolves.not.toThrow()
|
|
133
|
+
})
|