@push-rpc/next 2.0.0-beta.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/.prettierrc.json +7 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/client/HttpClient.d.ts +10 -0
- package/dist/client/HttpClient.js +67 -0
- package/dist/client/HttpClient.js.map +1 -0
- package/dist/client/RemoteSubscriptions.d.ts +14 -0
- package/dist/client/RemoteSubscriptions.js +84 -0
- package/dist/client/RemoteSubscriptions.js.map +1 -0
- package/dist/client/RpcClientImpl.d.ts +22 -0
- package/dist/client/RpcClientImpl.js +96 -0
- package/dist/client/RpcClientImpl.js.map +1 -0
- package/dist/client/WebSocketConnection.d.ts +33 -0
- package/dist/client/WebSocketConnection.js +152 -0
- package/dist/client/WebSocketConnection.js.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.js +28 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/remote.d.ts +14 -0
- package/dist/client/remote.js +66 -0
- package/dist/client/remote.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +9 -0
- package/dist/logger.js.map +1 -0
- package/dist/rpc.d.ts +36 -0
- package/dist/rpc.js +32 -0
- package/dist/rpc.js.map +1 -0
- package/dist/server/ConnectionsServer.d.ts +13 -0
- package/dist/server/ConnectionsServer.js +60 -0
- package/dist/server/ConnectionsServer.js.map +1 -0
- package/dist/server/LocalSubscriptions.d.ts +12 -0
- package/dist/server/LocalSubscriptions.js +113 -0
- package/dist/server/LocalSubscriptions.js.map +1 -0
- package/dist/server/RpcServerImpl.d.ts +23 -0
- package/dist/server/RpcServerImpl.js +164 -0
- package/dist/server/RpcServerImpl.js.map +1 -0
- package/dist/server/http.d.ts +9 -0
- package/dist/server/http.js +83 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/index.d.ts +29 -0
- package/dist/server/index.js +31 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/local.d.ts +15 -0
- package/dist/server/local.js +46 -0
- package/dist/server/local.js.map +1 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +34 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/middleware.d.ts +2 -0
- package/dist/utils/middleware.js +31 -0
- package/dist/utils/middleware.js.map +1 -0
- package/dist/utils/promises.d.ts +5 -0
- package/dist/utils/promises.js +29 -0
- package/dist/utils/promises.js.map +1 -0
- package/dist/utils/throttle.d.ts +4 -0
- package/dist/utils/throttle.js +40 -0
- package/dist/utils/throttle.js.map +1 -0
- package/dist/utils/types.d.ts +1 -0
- package/dist/utils/types.js +3 -0
- package/dist/utils/types.js.map +1 -0
- package/example/api.ts +15 -0
- package/example/client.ts +16 -0
- package/example/server.ts +37 -0
- package/package.json +34 -0
- package/src/client/HttpClient.ts +80 -0
- package/src/client/RemoteSubscriptions.ts +121 -0
- package/src/client/RpcClientImpl.ts +177 -0
- package/src/client/WebSocketConnection.ts +183 -0
- package/src/client/index.ts +56 -0
- package/src/client/remote.ts +118 -0
- package/src/index.ts +18 -0
- package/src/logger.ts +12 -0
- package/src/rpc.ts +51 -0
- package/src/server/ConnectionsServer.ts +78 -0
- package/src/server/LocalSubscriptions.ts +155 -0
- package/src/server/RpcServerImpl.ts +252 -0
- package/src/server/http.ts +109 -0
- package/src/server/index.ts +65 -0
- package/src/server/local.ts +80 -0
- package/src/utils/json.ts +32 -0
- package/src/utils/middleware.ts +38 -0
- package/src/utils/promises.ts +25 -0
- package/src/utils/throttle.ts +48 -0
- package/src/utils/types.ts +1 -0
- package/tests/calls.ts +215 -0
- package/tests/connection.ts +107 -0
- package/tests/context.ts +176 -0
- package/tests/middleware.ts +112 -0
- package/tests/misc.ts +187 -0
- package/tests/subscriptions.ts +442 -0
- package/tests/testUtils.ts +52 -0
- package/tests/triggers.ts +138 -0
- package/tsconfig.cjs.json +20 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import {assert} from "chai"
|
|
2
|
+
import {createTestClient, startTestServer, testClient, testServer} from "./testUtils.js"
|
|
3
|
+
import {adelay} from "../src/utils/promises.js"
|
|
4
|
+
import {CallOptions, RpcErrors} from "../src/index.js"
|
|
5
|
+
|
|
6
|
+
describe("Subscriptions", () => {
|
|
7
|
+
it("subscribe delivers data", async () => {
|
|
8
|
+
const item = {r: "1"}
|
|
9
|
+
|
|
10
|
+
const services = await startTestServer({
|
|
11
|
+
test: {
|
|
12
|
+
item: async () => item,
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const client = await createTestClient<typeof services>()
|
|
17
|
+
|
|
18
|
+
let receivedItem
|
|
19
|
+
|
|
20
|
+
await client.test.item.subscribe(() => {
|
|
21
|
+
receivedItem = item
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
await adelay(50)
|
|
25
|
+
assert.deepEqual(receivedItem, item)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("error in supplier breaks subscribe", async () => {
|
|
29
|
+
const services = await startTestServer({
|
|
30
|
+
item: async () => {
|
|
31
|
+
throw new Error("AA")
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const client = await createTestClient<typeof services>()
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await client.item.subscribe(() => {})
|
|
39
|
+
assert.fail("Error expected")
|
|
40
|
+
} catch (e: any) {
|
|
41
|
+
assert.equal(e.message, "AA")
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("exception during subscribe do not create subscription", async () => {
|
|
46
|
+
const services = {
|
|
47
|
+
item: async () => {
|
|
48
|
+
throw new Error()
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await startTestServer(services)
|
|
53
|
+
|
|
54
|
+
const remote = await createTestClient<typeof services>()
|
|
55
|
+
|
|
56
|
+
remote.item
|
|
57
|
+
.subscribe(() => {})
|
|
58
|
+
.catch((e: any) => {
|
|
59
|
+
// ignored
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// pause the socket so that the server doesn't get the unsubscribe message
|
|
63
|
+
await adelay(20)
|
|
64
|
+
|
|
65
|
+
assert.equal(0, testServer?._allSubscriptions().length)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("2nd subscribe", async () => {
|
|
69
|
+
const item = {r: "1"}
|
|
70
|
+
|
|
71
|
+
const services = await startTestServer({
|
|
72
|
+
test: {
|
|
73
|
+
item: async () => item,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const remote = await createTestClient<typeof services>()
|
|
78
|
+
|
|
79
|
+
let item1
|
|
80
|
+
await remote.test.item.subscribe((item) => {
|
|
81
|
+
item1 = item
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
await adelay(50)
|
|
85
|
+
assert.deepEqual(item1, item)
|
|
86
|
+
|
|
87
|
+
let item2
|
|
88
|
+
await remote.test.item.subscribe((item) => {
|
|
89
|
+
item2 = item
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await adelay(50)
|
|
93
|
+
assert.deepEqual(item2, item)
|
|
94
|
+
|
|
95
|
+
// trigger sends item
|
|
96
|
+
item.r = "2"
|
|
97
|
+
services.test.item.trigger()
|
|
98
|
+
await adelay(50)
|
|
99
|
+
assert.deepEqual(item1, item)
|
|
100
|
+
assert.deepEqual(item2, item)
|
|
101
|
+
|
|
102
|
+
// a single subscription present on server
|
|
103
|
+
assert.equal(testServer?._allSubscriptions().length, 1)
|
|
104
|
+
assert.equal(testServer?._allSubscriptions()[0][0], "test/item")
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("concurrent subscribe cache", async () => {
|
|
108
|
+
const item = {r: "1"}
|
|
109
|
+
let supplied = 0
|
|
110
|
+
|
|
111
|
+
const server = {
|
|
112
|
+
test: {
|
|
113
|
+
item: async () => {
|
|
114
|
+
await adelay(1)
|
|
115
|
+
supplied++
|
|
116
|
+
return item
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await startTestServer(server)
|
|
122
|
+
|
|
123
|
+
const client = await createTestClient<typeof server>()
|
|
124
|
+
|
|
125
|
+
let item1
|
|
126
|
+
client.test.item.subscribe((item) => {
|
|
127
|
+
item1 = item
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
let item2
|
|
131
|
+
client.test.item.subscribe((item) => {
|
|
132
|
+
item2 = item
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
await adelay(50)
|
|
136
|
+
assert.deepEqual(item1, item)
|
|
137
|
+
assert.deepEqual(item2, item)
|
|
138
|
+
|
|
139
|
+
assert.equal(supplied, 1)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it("subscribe use client cached value", async () => {
|
|
143
|
+
const item = {r: "1"}
|
|
144
|
+
|
|
145
|
+
const server = {
|
|
146
|
+
test: {
|
|
147
|
+
item: async () => item,
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await startTestServer(server)
|
|
152
|
+
|
|
153
|
+
const client = await createTestClient<typeof server>()
|
|
154
|
+
|
|
155
|
+
let item1
|
|
156
|
+
await client.test.item.subscribe((item) => {
|
|
157
|
+
item1 = item
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
await adelay(50)
|
|
161
|
+
assert.deepEqual(item1, item)
|
|
162
|
+
|
|
163
|
+
item.r = "2"
|
|
164
|
+
|
|
165
|
+
let item2
|
|
166
|
+
client.test.item.subscribe((item) => {
|
|
167
|
+
item2 = item
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// cached version should be delivered
|
|
171
|
+
assert.deepEqual(item2, {r: "1"})
|
|
172
|
+
|
|
173
|
+
await adelay(50)
|
|
174
|
+
|
|
175
|
+
// and a new version after some time
|
|
176
|
+
assert.deepEqual(item2, item)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("unsubscribe topics on disconnect", async () => {
|
|
180
|
+
const item = {r: "1"}
|
|
181
|
+
|
|
182
|
+
const server = {
|
|
183
|
+
testUnsub: {
|
|
184
|
+
item: async () => item,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await startTestServer(server)
|
|
189
|
+
|
|
190
|
+
const remote = await createTestClient<typeof server>()
|
|
191
|
+
|
|
192
|
+
await remote.testUnsub.item.subscribe(() => {})
|
|
193
|
+
|
|
194
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
195
|
+
|
|
196
|
+
await testClient!.close()
|
|
197
|
+
|
|
198
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
199
|
+
|
|
200
|
+
// client's subscriptions are not removed intentionally not to lose existing handlers
|
|
201
|
+
assert.equal(testClient?._allSubscriptions().length, 1)
|
|
202
|
+
|
|
203
|
+
assert.equal(0, testServer?._allSubscriptions().length)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it("resubscribe on reconnect", async () => {
|
|
207
|
+
const item = {r: "1"}
|
|
208
|
+
|
|
209
|
+
const services = await startTestServer({
|
|
210
|
+
test: {
|
|
211
|
+
item: async () => item,
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const remote = await createTestClient<typeof services>()
|
|
216
|
+
|
|
217
|
+
let receivedItem
|
|
218
|
+
|
|
219
|
+
await remote.test.item.subscribe((item) => {
|
|
220
|
+
receivedItem = item
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// first notification right after subscription
|
|
224
|
+
await adelay(20)
|
|
225
|
+
assert.deepEqual(receivedItem, item)
|
|
226
|
+
|
|
227
|
+
// trigger sends item
|
|
228
|
+
item.r = "2"
|
|
229
|
+
services.test.item.trigger()
|
|
230
|
+
await adelay(20)
|
|
231
|
+
assert.deepEqual(receivedItem, item)
|
|
232
|
+
|
|
233
|
+
// disconnect & resubscribe
|
|
234
|
+
testClient?._webSocket()?.close()
|
|
235
|
+
await adelay(50)
|
|
236
|
+
|
|
237
|
+
// session should be re-subscribed, trigger should continue to send items
|
|
238
|
+
item.r = "3"
|
|
239
|
+
services.test.item.trigger()
|
|
240
|
+
await adelay(20)
|
|
241
|
+
assert.deepEqual(receivedItem, item)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it("double subscribe leaves session referenced on unsubscribe", async () => {
|
|
245
|
+
const services = await startTestServer({
|
|
246
|
+
item: async () => 1,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
const remote = await createTestClient<typeof services>()
|
|
250
|
+
|
|
251
|
+
const sub1 = () => {}
|
|
252
|
+
const sub2 = () => {}
|
|
253
|
+
|
|
254
|
+
await remote.item.subscribe(sub1)
|
|
255
|
+
await adelay(20)
|
|
256
|
+
|
|
257
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
258
|
+
assert.equal(1, testClient?._allSubscriptions().length)
|
|
259
|
+
|
|
260
|
+
await remote.item.subscribe(sub2)
|
|
261
|
+
await adelay(20)
|
|
262
|
+
|
|
263
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
264
|
+
assert.equal(2, testClient?._allSubscriptions().length)
|
|
265
|
+
|
|
266
|
+
await remote.item.unsubscribe(sub1)
|
|
267
|
+
await adelay(100)
|
|
268
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
269
|
+
assert.equal(1, testClient?._allSubscriptions().length)
|
|
270
|
+
|
|
271
|
+
await remote.item.unsubscribe(sub2)
|
|
272
|
+
await adelay(100)
|
|
273
|
+
assert.equal(0, testServer?._allSubscriptions().length)
|
|
274
|
+
assert.equal(0, testClient?._allSubscriptions().length)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it("double subscribe single consumer", async () => {
|
|
278
|
+
const services = await startTestServer({
|
|
279
|
+
item: async () => 1,
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
const remote = await createTestClient<typeof services>()
|
|
283
|
+
|
|
284
|
+
const sub = () => {}
|
|
285
|
+
|
|
286
|
+
await remote.item.subscribe(sub)
|
|
287
|
+
await adelay(20)
|
|
288
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
289
|
+
|
|
290
|
+
await remote.item.subscribe(sub)
|
|
291
|
+
await adelay(20)
|
|
292
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
293
|
+
|
|
294
|
+
await remote.item.unsubscribe(sub)
|
|
295
|
+
await adelay(20)
|
|
296
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
297
|
+
|
|
298
|
+
await remote.item.unsubscribe(sub)
|
|
299
|
+
await adelay(20)
|
|
300
|
+
assert.equal(0, testServer?._allSubscriptions().length)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it("double subscribe leaves session referenced on disconnect", async () => {
|
|
304
|
+
const services = await startTestServer({
|
|
305
|
+
item: async () => 1,
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
const remote = await createTestClient<typeof services>()
|
|
309
|
+
|
|
310
|
+
const sub1 = () => {}
|
|
311
|
+
const sub2 = () => {}
|
|
312
|
+
|
|
313
|
+
await remote.item.subscribe(sub1)
|
|
314
|
+
await adelay(20)
|
|
315
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
316
|
+
|
|
317
|
+
await remote.item.subscribe(sub2)
|
|
318
|
+
await adelay(20)
|
|
319
|
+
assert.equal(1, testServer?._allSubscriptions().length)
|
|
320
|
+
|
|
321
|
+
await remote.item.unsubscribe(sub1)
|
|
322
|
+
await adelay(100)
|
|
323
|
+
|
|
324
|
+
await testClient?.close()
|
|
325
|
+
await adelay(100)
|
|
326
|
+
|
|
327
|
+
assert.equal(0, testServer?._allSubscriptions().length)
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it("double subscribe unsubscribe bug", async () => {
|
|
331
|
+
let delivered = null
|
|
332
|
+
|
|
333
|
+
const services = await startTestServer({
|
|
334
|
+
test: {
|
|
335
|
+
item: async () => "ok-" + Date.now(),
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const client = await createTestClient<typeof services>()
|
|
340
|
+
|
|
341
|
+
const sub1 = (r: string) => {
|
|
342
|
+
delivered = r
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const sub2 = () => {}
|
|
346
|
+
|
|
347
|
+
await client.test.item.subscribe(sub1)
|
|
348
|
+
|
|
349
|
+
await client.test.item.subscribe(sub2)
|
|
350
|
+
|
|
351
|
+
assert.isOk(delivered)
|
|
352
|
+
delivered = null
|
|
353
|
+
|
|
354
|
+
await client.test.item.unsubscribe(sub2)
|
|
355
|
+
|
|
356
|
+
services.test.item.trigger()
|
|
357
|
+
|
|
358
|
+
await adelay(200)
|
|
359
|
+
|
|
360
|
+
assert.isOk(delivered)
|
|
361
|
+
delivered = null
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it.skip("unsubscribe before supply bug", async () => {
|
|
365
|
+
const services = await startTestServer({
|
|
366
|
+
item: async () => {
|
|
367
|
+
await adelay(20)
|
|
368
|
+
return 1
|
|
369
|
+
},
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
const client = await createTestClient<typeof services>()
|
|
373
|
+
|
|
374
|
+
const sub = () => {}
|
|
375
|
+
client.item.subscribe(sub)
|
|
376
|
+
|
|
377
|
+
await adelay(10)
|
|
378
|
+
|
|
379
|
+
client.item.unsubscribe(sub)
|
|
380
|
+
|
|
381
|
+
await adelay(20)
|
|
382
|
+
|
|
383
|
+
assert.equal(0, testClient!._allSubscriptions().length)
|
|
384
|
+
assert.equal(0, testServer!._allSubscriptions().length)
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it("skip unchanged data", async () => {
|
|
388
|
+
const item = {r: "1"}
|
|
389
|
+
|
|
390
|
+
const services = await startTestServer({
|
|
391
|
+
test: {
|
|
392
|
+
item: async () => item,
|
|
393
|
+
},
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
services.test.item.throttle({
|
|
397
|
+
timeout: 0,
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
const client = await createTestClient<typeof services>()
|
|
401
|
+
|
|
402
|
+
let receivedItem
|
|
403
|
+
|
|
404
|
+
await client.test.item.subscribe(() => {
|
|
405
|
+
receivedItem = item
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
await adelay(20)
|
|
409
|
+
assert.deepEqual(receivedItem, item)
|
|
410
|
+
receivedItem = null
|
|
411
|
+
|
|
412
|
+
services.test.item.trigger()
|
|
413
|
+
await adelay(20)
|
|
414
|
+
assert.isNotOk(receivedItem)
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
it("per-subscribe timeout", async () => {
|
|
418
|
+
const callTimeout = 200
|
|
419
|
+
|
|
420
|
+
const services = await startTestServer({
|
|
421
|
+
test: {
|
|
422
|
+
async longOp() {
|
|
423
|
+
await adelay(2 * callTimeout)
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
const client = await createTestClient<typeof services>({
|
|
429
|
+
callTimeout: 4 * callTimeout,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
await client.test.longOp.subscribe(() => {}, new CallOptions({timeout: callTimeout}))
|
|
434
|
+
assert.fail()
|
|
435
|
+
} catch (e: any) {
|
|
436
|
+
assert.equal(e.code, RpcErrors.Timeout)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
assert.equal(0, testClient!._allSubscriptions().length)
|
|
440
|
+
assert.equal(0, testServer!._allSubscriptions().length)
|
|
441
|
+
}).timeout(5000)
|
|
442
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeServices,
|
|
3
|
+
ConsumeServicesOptions,
|
|
4
|
+
publishServices,
|
|
5
|
+
PublishServicesOptions,
|
|
6
|
+
RpcClient,
|
|
7
|
+
RpcContext,
|
|
8
|
+
RpcServer,
|
|
9
|
+
Services,
|
|
10
|
+
ServicesWithSubscriptions,
|
|
11
|
+
ServicesWithTriggers,
|
|
12
|
+
} from "../src/index.js"
|
|
13
|
+
|
|
14
|
+
export const TEST_PORT = 5555
|
|
15
|
+
|
|
16
|
+
export let testServer: RpcServer | null = null
|
|
17
|
+
|
|
18
|
+
export async function startTestServer<S extends Services<S>, C extends RpcContext>(
|
|
19
|
+
local: S,
|
|
20
|
+
options: Partial<PublishServicesOptions<C>> = {}
|
|
21
|
+
): Promise<ServicesWithTriggers<S>> {
|
|
22
|
+
const r = await publishServices<S, C>(local, {
|
|
23
|
+
port: TEST_PORT,
|
|
24
|
+
path: "/rpc",
|
|
25
|
+
...options,
|
|
26
|
+
})
|
|
27
|
+
testServer = r.server
|
|
28
|
+
return r.services
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export let testClient: RpcClient | null = null
|
|
32
|
+
|
|
33
|
+
export async function createTestClient<S extends Services<S>>(
|
|
34
|
+
options?: Partial<ConsumeServicesOptions>
|
|
35
|
+
): Promise<ServicesWithSubscriptions<S>> {
|
|
36
|
+
const r = await consumeServices<S>(`http://127.0.0.1:${TEST_PORT}/rpc`, options)
|
|
37
|
+
testClient = r.client
|
|
38
|
+
return r.remote
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
afterEach(async function () {
|
|
42
|
+
this.timeout(4000)
|
|
43
|
+
|
|
44
|
+
if (testClient) {
|
|
45
|
+
await testClient.close()
|
|
46
|
+
testClient = null
|
|
47
|
+
}
|
|
48
|
+
if (testServer) {
|
|
49
|
+
await testServer.close()
|
|
50
|
+
testServer = null
|
|
51
|
+
}
|
|
52
|
+
})
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {createTestClient, startTestServer} from "./testUtils.js"
|
|
2
|
+
import {adelay} from "../src/utils/promises.js"
|
|
3
|
+
import {assert} from "chai"
|
|
4
|
+
import {groupReducer} from "../src/utils/throttle.js"
|
|
5
|
+
|
|
6
|
+
describe("Subscription triggers", () => {
|
|
7
|
+
it("trigger filter", async () => {
|
|
8
|
+
interface Item {
|
|
9
|
+
key: string
|
|
10
|
+
updated: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const services = await startTestServer({
|
|
14
|
+
test: {
|
|
15
|
+
async item({key}: {key: string}): Promise<{key: string; updated: number}> {
|
|
16
|
+
return {key, updated: Date.now()}
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
services.test.item.throttle({
|
|
22
|
+
timeout: 0,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const remote = await createTestClient<typeof services>()
|
|
26
|
+
|
|
27
|
+
let item1
|
|
28
|
+
let item2
|
|
29
|
+
|
|
30
|
+
const sub1 = (item: Item) => (item1 = item)
|
|
31
|
+
const sub2 = (item: Item) => (item2 = item)
|
|
32
|
+
|
|
33
|
+
await remote.test.item.subscribe(sub1, {key: "1"})
|
|
34
|
+
await remote.test.item.subscribe(sub2, {key: "2"})
|
|
35
|
+
|
|
36
|
+
// first notificaiton right after subscription, clear items
|
|
37
|
+
await adelay(20)
|
|
38
|
+
|
|
39
|
+
// trigger sends 1st item, but not second
|
|
40
|
+
item1 = null
|
|
41
|
+
item2 = null
|
|
42
|
+
|
|
43
|
+
services.test.item.trigger({key: "1"})
|
|
44
|
+
await adelay(20)
|
|
45
|
+
assert.equal(item1!.key, "1")
|
|
46
|
+
assert.isNull(item2)
|
|
47
|
+
|
|
48
|
+
// null trigger sends all items
|
|
49
|
+
item1 = null
|
|
50
|
+
item2 = null
|
|
51
|
+
|
|
52
|
+
services.test.item.trigger()
|
|
53
|
+
await adelay(20)
|
|
54
|
+
assert.deepEqual(item1!.key, "1")
|
|
55
|
+
assert.deepEqual(item2!.key, "2")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("trigger throttling", async () => {
|
|
59
|
+
const throttleTimeout = 400
|
|
60
|
+
|
|
61
|
+
const services = await startTestServer({
|
|
62
|
+
test: {
|
|
63
|
+
item: async () => "result",
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
services.test.item.throttle({
|
|
68
|
+
timeout: throttleTimeout,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const remote = await createTestClient<typeof services>()
|
|
72
|
+
|
|
73
|
+
let count = 0
|
|
74
|
+
let item = null
|
|
75
|
+
|
|
76
|
+
await remote.test.item.subscribe((i) => {
|
|
77
|
+
count++
|
|
78
|
+
item = i
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await adelay(50)
|
|
82
|
+
assert.equal(count, 1)
|
|
83
|
+
assert.equal(item, "result")
|
|
84
|
+
|
|
85
|
+
services.test.item.trigger(undefined, "1st")
|
|
86
|
+
services.test.item.trigger(undefined, "2nd") // throttled
|
|
87
|
+
await adelay(50)
|
|
88
|
+
assert.equal(count, 2)
|
|
89
|
+
assert.equal(item, "1st")
|
|
90
|
+
|
|
91
|
+
services.test.item.trigger(undefined, "3rd") // throttled
|
|
92
|
+
services.test.item.trigger(undefined, "4th") // delivered on trailing edge
|
|
93
|
+
|
|
94
|
+
await adelay(50)
|
|
95
|
+
assert.equal(count, 2)
|
|
96
|
+
|
|
97
|
+
await adelay(throttleTimeout + 50)
|
|
98
|
+
assert.equal(count, 3)
|
|
99
|
+
assert.equal(item, "4th")
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("throttling reducer", async () => {
|
|
103
|
+
const throttleTimeout = 400
|
|
104
|
+
|
|
105
|
+
const services = await startTestServer({
|
|
106
|
+
test: {
|
|
107
|
+
item: async () => [] as number[],
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
services.test.item.throttle({
|
|
112
|
+
timeout: throttleTimeout,
|
|
113
|
+
reducer: groupReducer,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const remote = await createTestClient<typeof services>()
|
|
117
|
+
|
|
118
|
+
let item = null
|
|
119
|
+
|
|
120
|
+
await remote.test.item.subscribe((i) => {
|
|
121
|
+
console.log(i)
|
|
122
|
+
item = i
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
126
|
+
await adelay(50)
|
|
127
|
+
assert.deepEqual(item, [])
|
|
128
|
+
|
|
129
|
+
services.test.item.trigger(undefined, [1])
|
|
130
|
+
services.test.item.trigger(undefined, [2]) // throttled
|
|
131
|
+
services.test.item.trigger(undefined, [3]) // throttled
|
|
132
|
+
await adelay(50)
|
|
133
|
+
assert.deepEqual(item, [1])
|
|
134
|
+
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, throttleTimeout))
|
|
136
|
+
assert.deepEqual(item, [2, 3]) // trailing edge
|
|
137
|
+
})
|
|
138
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"lib": [
|
|
6
|
+
"es2020"
|
|
7
|
+
],
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"outDir": "dist"
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/**/*"
|
|
19
|
+
]
|
|
20
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"lib": [
|
|
6
|
+
"es2020"
|
|
7
|
+
],
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"outDir": "dist"
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/**/*"
|
|
19
|
+
],
|
|
20
|
+
"ts-node": {
|
|
21
|
+
"esm": true,
|
|
22
|
+
"moduleTypes": {
|
|
23
|
+
"*": "esm"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|