@nxtedition/deepstream.io-client-js 30.0.1 → 31.0.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 +13 -10
- package/src/client.d.ts +104 -0
- package/src/constants/constants.js +1 -0
- package/src/event/event-handler.d.ts +20 -0
- package/src/message/connection.js +1 -1
- package/src/record/record-handler.d.ts +158 -0
- package/src/record/record-handler.js +316 -260
- package/src/record/record.d.ts +110 -0
- package/src/record/record.js +12 -19
- package/src/rpc/rpc-handler.d.ts +42 -0
- package/src/rpc/rpc-response.d.ts +5 -0
- package/src/utils/multicast-listener.js +164 -181
- package/src/utils/timers.js +4 -0
- package/src/utils/unicast-listener.js +35 -52
- package/src/utils/utils.js +20 -1
|
@@ -10,10 +10,44 @@ import * as utils from '../utils/utils.js'
|
|
|
10
10
|
import xuid from 'xuid'
|
|
11
11
|
import * as timers from '../utils/timers.js'
|
|
12
12
|
|
|
13
|
+
function noop() {}
|
|
14
|
+
|
|
13
15
|
const kEmpty = Symbol('kEmpty')
|
|
14
16
|
|
|
17
|
+
const OBSERVE_DEFAULTS = {
|
|
18
|
+
timeout: 2 * 60e3,
|
|
19
|
+
state: C.RECORD_STATE.SERVER,
|
|
20
|
+
dataOnly: true,
|
|
21
|
+
}
|
|
22
|
+
const OBSERVE2_DEFAULTS = {
|
|
23
|
+
timeout: 2 * 60e3,
|
|
24
|
+
}
|
|
25
|
+
const GET_DEFAULTS = {
|
|
26
|
+
timeout: 2 * 60e3,
|
|
27
|
+
first: true,
|
|
28
|
+
sync: true,
|
|
29
|
+
dataOnly: true,
|
|
30
|
+
}
|
|
31
|
+
const GET2_DEFAULTS = {
|
|
32
|
+
timeout: 2 * 60e3,
|
|
33
|
+
first: true,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onSync(subscription) {
|
|
37
|
+
subscription.synced = true
|
|
38
|
+
onUpdate(null, subscription)
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
function onUpdate(record, subscription) {
|
|
16
|
-
if (subscription.
|
|
42
|
+
if (!subscription.record) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!subscription.synced) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (subscription.state && subscription.record.state < subscription.state) {
|
|
17
51
|
return
|
|
18
52
|
}
|
|
19
53
|
|
|
@@ -22,7 +56,9 @@ function onUpdate(record, subscription) {
|
|
|
22
56
|
subscription.timeout = null
|
|
23
57
|
}
|
|
24
58
|
|
|
25
|
-
const data = subscription.path
|
|
59
|
+
const data = subscription.path
|
|
60
|
+
? subscription.record.get(subscription.path)
|
|
61
|
+
: subscription.record.data
|
|
26
62
|
|
|
27
63
|
if (subscription.dataOnly) {
|
|
28
64
|
if (data !== subscription.data) {
|
|
@@ -31,12 +67,17 @@ function onUpdate(record, subscription) {
|
|
|
31
67
|
}
|
|
32
68
|
} else {
|
|
33
69
|
subscription.subscriber.next({
|
|
34
|
-
name: record.name,
|
|
35
|
-
version: record.version,
|
|
36
|
-
state: record.state,
|
|
70
|
+
name: subscription.record.name,
|
|
71
|
+
version: subscription.record.version,
|
|
72
|
+
state: subscription.record.state,
|
|
37
73
|
data,
|
|
38
74
|
})
|
|
39
75
|
}
|
|
76
|
+
|
|
77
|
+
if (subscription.first) {
|
|
78
|
+
subscription.subscriber.complete?.()
|
|
79
|
+
subscription.unsubscribe()
|
|
80
|
+
}
|
|
40
81
|
}
|
|
41
82
|
|
|
42
83
|
function onTimeout(subscription) {
|
|
@@ -53,39 +94,6 @@ function onTimeout(subscription) {
|
|
|
53
94
|
)
|
|
54
95
|
}
|
|
55
96
|
|
|
56
|
-
function onUpdateFast(rec, opaque) {
|
|
57
|
-
const { timeout, resolve, synced, state } = opaque
|
|
58
|
-
|
|
59
|
-
if (rec.state >= state && synced) {
|
|
60
|
-
timers.clearTimeout(timeout)
|
|
61
|
-
rec.unsubscribe(onUpdateFast, opaque)
|
|
62
|
-
rec.unref()
|
|
63
|
-
resolve(rec.data)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function onSyncFast(opaque) {
|
|
68
|
-
opaque.synced = true
|
|
69
|
-
onUpdateFast(opaque.rec, opaque)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function onTimeoutFast(opaque) {
|
|
73
|
-
const { rec, synced, resolve } = opaque
|
|
74
|
-
rec.unsubscribe(onUpdateFast, opaque)
|
|
75
|
-
rec.unref()
|
|
76
|
-
|
|
77
|
-
let err
|
|
78
|
-
if (rec.state < opaque.state) {
|
|
79
|
-
err = new Error(`timeout state: ${opaque.rec.name} [${opaque.rec.state}<${opaque.state}]`)
|
|
80
|
-
} else if (!synced) {
|
|
81
|
-
err = new Error(`timeout sync: ${opaque.rec.name} `)
|
|
82
|
-
} else {
|
|
83
|
-
err = new Error('timeout')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
resolve(Promise.reject(Object.assign(err, { code: 'ETIMEDOUT' })))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
97
|
class RecordHandler {
|
|
90
98
|
constructor(options, connection, client) {
|
|
91
99
|
this.JSON = jsonPath
|
|
@@ -95,12 +103,12 @@ class RecordHandler {
|
|
|
95
103
|
this._options = options
|
|
96
104
|
this._connection = connection
|
|
97
105
|
this._client = client
|
|
98
|
-
this.
|
|
99
|
-
this._recordsByKey = new Map()
|
|
106
|
+
this._records = new Map()
|
|
100
107
|
this._listeners = new Map()
|
|
101
108
|
this._pruning = new Set()
|
|
102
109
|
this._patching = new Map()
|
|
103
110
|
this._updating = new Map()
|
|
111
|
+
this._putting = new Map()
|
|
104
112
|
|
|
105
113
|
this._connected = 0
|
|
106
114
|
this._stats = {
|
|
@@ -133,7 +141,7 @@ class RecordHandler {
|
|
|
133
141
|
|
|
134
142
|
for (const rec of pruning) {
|
|
135
143
|
rec._$dispose()
|
|
136
|
-
this.
|
|
144
|
+
this._records.delete(rec.name)
|
|
137
145
|
}
|
|
138
146
|
|
|
139
147
|
this._stats.pruning -= pruning.size
|
|
@@ -161,13 +169,16 @@ class RecordHandler {
|
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
_onUpdating(rec, value) {
|
|
172
|
+
const callbacks = this._updating.get(rec)
|
|
173
|
+
|
|
164
174
|
if (value) {
|
|
175
|
+
invariant(!callbacks, 'updating callbacks must not exist')
|
|
165
176
|
this._stats.updating += 1
|
|
166
177
|
this._updating.set(rec, [])
|
|
167
178
|
} else {
|
|
168
|
-
|
|
179
|
+
invariant(callbacks, 'updating callbacks must exist')
|
|
169
180
|
|
|
170
|
-
|
|
181
|
+
this._stats.updating -= 1
|
|
171
182
|
this._updating.delete(rec)
|
|
172
183
|
for (const callback of callbacks) {
|
|
173
184
|
callback()
|
|
@@ -216,14 +227,13 @@ class RecordHandler {
|
|
|
216
227
|
`invalid name ${name}`,
|
|
217
228
|
)
|
|
218
229
|
|
|
219
|
-
let record = this.
|
|
230
|
+
let record = this._records.get(name)
|
|
220
231
|
|
|
221
232
|
if (!record) {
|
|
222
233
|
record = new Record(name, this)
|
|
223
234
|
this._stats.records += 1
|
|
224
235
|
this._stats.created += 1
|
|
225
|
-
this.
|
|
226
|
-
this._recordsByKey.set(record.name, record)
|
|
236
|
+
this._records.set(name, record)
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
return record.ref()
|
|
@@ -256,139 +266,148 @@ class RecordHandler {
|
|
|
256
266
|
this._stats.listeners += 1
|
|
257
267
|
this._listeners.set(pattern, listener)
|
|
258
268
|
|
|
259
|
-
|
|
269
|
+
const disposer = () => {
|
|
260
270
|
listener._$destroy()
|
|
261
271
|
|
|
262
272
|
this._stats.listeners -= 1
|
|
263
273
|
this._listeners.delete(pattern)
|
|
264
274
|
}
|
|
275
|
+
disposer[Symbol.dispose] = disposer
|
|
276
|
+
|
|
277
|
+
return disposer
|
|
265
278
|
}
|
|
266
279
|
|
|
267
280
|
async sync(opts) {
|
|
268
281
|
// TODO (fix): Sync pending? What about VOID state?
|
|
269
|
-
|
|
270
|
-
let onAbort
|
|
282
|
+
// TODO (perf): Slow implementation...
|
|
271
283
|
|
|
272
284
|
const signal = opts?.signal
|
|
273
285
|
const timeout = opts?.timeout
|
|
274
286
|
|
|
275
|
-
|
|
276
|
-
? new Promise((resolve, reject) => {
|
|
277
|
-
onAbort = () => {
|
|
278
|
-
reject(signal.reason ?? new utils.AbortError())
|
|
279
|
-
}
|
|
280
|
-
signal.addEventListener('abort', onAbort)
|
|
281
|
-
})
|
|
282
|
-
: null
|
|
283
|
-
signalPromise?.catch(() => {})
|
|
284
|
-
|
|
287
|
+
let disposers
|
|
285
288
|
try {
|
|
289
|
+
const signalPromise = signal
|
|
290
|
+
? new Promise((resolve, reject) => {
|
|
291
|
+
const onAbort = () => reject(signal.reason ?? new utils.AbortError())
|
|
292
|
+
signal.addEventListener('abort', onAbort)
|
|
293
|
+
disposers ??= []
|
|
294
|
+
disposers.push(() => signal.removeEventListener('abort', onAbort))
|
|
295
|
+
})
|
|
296
|
+
: null
|
|
297
|
+
|
|
298
|
+
signalPromise?.catch(noop)
|
|
299
|
+
|
|
286
300
|
if (this._patching.size) {
|
|
287
|
-
let
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
let promises
|
|
302
|
+
|
|
303
|
+
{
|
|
304
|
+
const patchingPromises = []
|
|
305
|
+
for (const callbacks of this._patching.values()) {
|
|
306
|
+
patchingPromises.push(new Promise((resolve) => callbacks.push(resolve)))
|
|
307
|
+
}
|
|
308
|
+
promises ??= []
|
|
309
|
+
promises.push(Promise.all(patchingPromises))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (timeout) {
|
|
313
|
+
promises.push(
|
|
314
|
+
new Promise((resolve) => {
|
|
315
|
+
const patchingTimeout = timers.setTimeout(() => {
|
|
296
316
|
this._client._$onError(
|
|
297
317
|
C.TOPIC.RECORD,
|
|
298
318
|
C.EVENT.TIMEOUT,
|
|
299
|
-
|
|
300
|
-
data: { patching, timeout },
|
|
301
|
-
}),
|
|
319
|
+
new Error('sync patching timeout'),
|
|
302
320
|
)
|
|
303
321
|
resolve(null)
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
}, timeout)
|
|
323
|
+
disposers ??= []
|
|
324
|
+
disposers.push(() => timers.clearTimeout(patchingTimeout))
|
|
325
|
+
}),
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (signalPromise) {
|
|
330
|
+
promises ??= []
|
|
331
|
+
promises.push(signalPromise)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (promises) {
|
|
335
|
+
await Promise.race(promises)
|
|
336
|
+
}
|
|
312
337
|
}
|
|
313
338
|
|
|
314
339
|
if (this._updating.size) {
|
|
315
|
-
let
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
340
|
+
let promises
|
|
341
|
+
|
|
342
|
+
{
|
|
343
|
+
const updatingPromises = []
|
|
344
|
+
for (const callbacks of this._updating.values()) {
|
|
345
|
+
updatingPromises.push(new Promise((resolve) => callbacks.push(resolve)))
|
|
346
|
+
}
|
|
347
|
+
promises ??= []
|
|
348
|
+
promises.push(Promise.all(updatingPromises))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (timeout) {
|
|
352
|
+
promises ??= []
|
|
353
|
+
promises.push(
|
|
354
|
+
new Promise((resolve) => {
|
|
355
|
+
const updatingTimeout = timers.setTimeout(() => {
|
|
324
356
|
this._client._$onError(
|
|
325
357
|
C.TOPIC.RECORD,
|
|
326
358
|
C.EVENT.TIMEOUT,
|
|
327
|
-
|
|
328
|
-
data: { updating, timeout },
|
|
329
|
-
}),
|
|
359
|
+
new Error('sync updating timeout'),
|
|
330
360
|
)
|
|
331
361
|
resolve(null)
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
signalPromise,
|
|
337
|
-
]).finally(() => {
|
|
338
|
-
timers.clearTimeout(updatingTimeout)
|
|
339
|
-
})
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
let serverTimeout
|
|
343
|
-
const token = xuid()
|
|
344
|
-
return await Promise.race([
|
|
345
|
-
await new Promise((resolve) => {
|
|
346
|
-
this._syncEmitter.once(token, resolve)
|
|
347
|
-
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token])
|
|
348
|
-
}),
|
|
349
|
-
new Promise((resolve) => {
|
|
350
|
-
serverTimeout = timers.setTimeout(
|
|
351
|
-
() => {
|
|
352
|
-
this._client._$onError(
|
|
353
|
-
C.TOPIC.RECORD,
|
|
354
|
-
C.EVENT.TIMEOUT,
|
|
355
|
-
Object.assign(new Error('sync server timeout'), { data: { token, timeout } }),
|
|
356
|
-
)
|
|
357
|
-
resolve(null)
|
|
358
|
-
},
|
|
359
|
-
timeout ?? 2 * 60e3,
|
|
362
|
+
}, timeout)
|
|
363
|
+
disposers ??= []
|
|
364
|
+
disposers.push(() => timers.clearTimeout(updatingTimeout))
|
|
365
|
+
}),
|
|
360
366
|
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
} finally {
|
|
367
|
-
if (onAbort) {
|
|
368
|
-
signal?.removeEventListener('abort', onAbort)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (promises) {
|
|
370
|
+
await Promise.race(promises)
|
|
371
|
+
}
|
|
369
372
|
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
373
|
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
{
|
|
375
|
+
const syncPromise = new Promise((resolve) => this._sync(resolve))
|
|
376
|
+
|
|
377
|
+
let promises
|
|
378
|
+
|
|
379
|
+
if (timeout) {
|
|
380
|
+
promises ??= []
|
|
381
|
+
promises.push(
|
|
382
|
+
new Promise((resolve, reject) => {
|
|
383
|
+
const serverTimeout = timers.setTimeout(() => {
|
|
384
|
+
reject(new Error('sync server timeout'))
|
|
385
|
+
}, timeout)
|
|
386
|
+
disposers ??= []
|
|
387
|
+
disposers.push(() => timers.clearTimeout(serverTimeout))
|
|
388
|
+
}),
|
|
389
|
+
)
|
|
390
|
+
}
|
|
375
391
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
392
|
+
if (signalPromise) {
|
|
393
|
+
promises ??= []
|
|
394
|
+
promises.push(signalPromise)
|
|
395
|
+
}
|
|
379
396
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this._syncEmitter.once(token, () => {
|
|
386
|
-
for (let n = 0; n < queue.length; n += 2) {
|
|
387
|
-
queue[n](queue[n + 1])
|
|
397
|
+
if (promises) {
|
|
398
|
+
promises.push(syncPromise)
|
|
399
|
+
await Promise.race(promises)
|
|
400
|
+
} else {
|
|
401
|
+
await syncPromise
|
|
388
402
|
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
403
|
+
}
|
|
404
|
+
} finally {
|
|
405
|
+
if (disposers) {
|
|
406
|
+
for (const disposer of disposers) {
|
|
407
|
+
disposer()
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
392
411
|
}
|
|
393
412
|
|
|
394
413
|
set(name, ...args) {
|
|
@@ -400,6 +419,35 @@ class RecordHandler {
|
|
|
400
419
|
}
|
|
401
420
|
}
|
|
402
421
|
|
|
422
|
+
put(name, version, data, parent) {
|
|
423
|
+
if (typeof name !== 'string' || name.startsWith('_')) {
|
|
424
|
+
throw new Error('invalid argument: name')
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (typeof version !== 'string' || !/^\d+-/.test(version)) {
|
|
428
|
+
throw new Error('invalid argument: verison')
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (typeof data !== 'object' && data != null) {
|
|
432
|
+
throw new Error('invalid argument: data')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (parent != null && (typeof version !== 'string' || !/^\d+-/.test(version))) {
|
|
436
|
+
throw new Error('invalid argument: parent')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const update = [name, version, jsonPath.stringify(data)]
|
|
440
|
+
|
|
441
|
+
if (parent) {
|
|
442
|
+
update.push(parent)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.PUT, update)
|
|
446
|
+
|
|
447
|
+
this._putting.set(update, [])
|
|
448
|
+
this._sync((update) => this._putting.delete(update), 'WEAK', update)
|
|
449
|
+
}
|
|
450
|
+
|
|
403
451
|
/**
|
|
404
452
|
*
|
|
405
453
|
* @param {*} name
|
|
@@ -424,14 +472,7 @@ class RecordHandler {
|
|
|
424
472
|
* @returns {rxjs.Observable}
|
|
425
473
|
*/
|
|
426
474
|
observe(...args) {
|
|
427
|
-
return this._observe(
|
|
428
|
-
{
|
|
429
|
-
state: C.RECORD_STATE.SERVER,
|
|
430
|
-
timeout: 2 * 60e3,
|
|
431
|
-
dataOnly: true,
|
|
432
|
-
},
|
|
433
|
-
...args,
|
|
434
|
-
)
|
|
475
|
+
return this._observe(OBSERVE_DEFAULTS, ...args)
|
|
435
476
|
}
|
|
436
477
|
|
|
437
478
|
/**
|
|
@@ -439,47 +480,9 @@ class RecordHandler {
|
|
|
439
480
|
* @returns {Promise}
|
|
440
481
|
*/
|
|
441
482
|
get(...args) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const state = args.length === 2 ? args[1] : C.RECORD_STATE.SERVER
|
|
446
|
-
// TODO (perf): We could also skip sync if state is less than SERVER. However,
|
|
447
|
-
// there is a potential race where we receive an UPDATE for a previous SUBSCRIBE.
|
|
448
|
-
// Unsure how to avoid that. Keep it simple for now and always sync regardless of
|
|
449
|
-
// current state.
|
|
450
|
-
const synced = state < C.RECORD_STATE.SERVER
|
|
451
|
-
|
|
452
|
-
if (rec.state >= state && synced) {
|
|
453
|
-
rec.unref()
|
|
454
|
-
resolve(rec.data)
|
|
455
|
-
} else {
|
|
456
|
-
const opaque = {
|
|
457
|
-
rec,
|
|
458
|
-
state,
|
|
459
|
-
resolve,
|
|
460
|
-
timeout: null,
|
|
461
|
-
synced,
|
|
462
|
-
}
|
|
463
|
-
opaque.timeout = timers.setTimeout(onTimeoutFast, 2 * 60e3, opaque)
|
|
464
|
-
rec.subscribe(onUpdateFast, opaque)
|
|
465
|
-
|
|
466
|
-
if (!opaque.synced) {
|
|
467
|
-
this._sync(onSyncFast, opaque)
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
})
|
|
471
|
-
} else {
|
|
472
|
-
// Slow path...
|
|
473
|
-
// TODO (fix): Missing sync..
|
|
474
|
-
return new Promise((resolve, reject) => {
|
|
475
|
-
this.observe(...args)
|
|
476
|
-
.pipe(rxjs.first())
|
|
477
|
-
.subscribe({
|
|
478
|
-
next: resolve,
|
|
479
|
-
error: reject,
|
|
480
|
-
})
|
|
481
|
-
})
|
|
482
|
-
}
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
this._subscribe({ next: resolve, error: reject }, GET_DEFAULTS, ...args)
|
|
485
|
+
})
|
|
483
486
|
}
|
|
484
487
|
|
|
485
488
|
/**
|
|
@@ -488,12 +491,7 @@ class RecordHandler {
|
|
|
488
491
|
*/
|
|
489
492
|
get2(...args) {
|
|
490
493
|
return new Promise((resolve, reject) => {
|
|
491
|
-
this.
|
|
492
|
-
.pipe(rxjs.first())
|
|
493
|
-
.subscribe({
|
|
494
|
-
next: resolve,
|
|
495
|
-
error: reject,
|
|
496
|
-
})
|
|
494
|
+
this._subscribe({ next: resolve, error: reject }, GET2_DEFAULTS, ...args)
|
|
497
495
|
})
|
|
498
496
|
}
|
|
499
497
|
|
|
@@ -502,24 +500,29 @@ class RecordHandler {
|
|
|
502
500
|
* @returns {rxjs.Observable<{ name: string, version: string, state: Number, data: any}>}
|
|
503
501
|
*/
|
|
504
502
|
observe2(...args) {
|
|
505
|
-
return this._observe(
|
|
506
|
-
{
|
|
507
|
-
timeout: 2 * 60e3,
|
|
508
|
-
},
|
|
509
|
-
...args,
|
|
510
|
-
)
|
|
503
|
+
return this._observe(OBSERVE2_DEFAULTS, ...args)
|
|
511
504
|
}
|
|
512
505
|
|
|
513
506
|
/**
|
|
514
507
|
* @returns {rxjs.Observable}
|
|
515
508
|
*/
|
|
516
|
-
// TODO (perf): Avoid rest parameters.
|
|
517
509
|
_observe(defaults, name, ...args) {
|
|
510
|
+
return new rxjs.Observable((subscriber) => {
|
|
511
|
+
this._subscribe(subscriber, defaults, name, ...args)
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @returns {{ unsubscribe: Function }}
|
|
517
|
+
*/
|
|
518
|
+
_subscribe(subscriber, defaults, name, ...args) {
|
|
518
519
|
let path
|
|
519
|
-
let state = defaults
|
|
520
|
+
let state = defaults?.state
|
|
520
521
|
let signal
|
|
521
|
-
let timeout = defaults
|
|
522
|
-
let dataOnly = defaults
|
|
522
|
+
let timeout = defaults?.timeout
|
|
523
|
+
let dataOnly = defaults?.dataOnly
|
|
524
|
+
let sync = defaults?.sync
|
|
525
|
+
let first = defaults?.first
|
|
523
526
|
|
|
524
527
|
let idx = 0
|
|
525
528
|
|
|
@@ -559,71 +562,90 @@ class RecordHandler {
|
|
|
559
562
|
if (options.dataOnly !== undefined) {
|
|
560
563
|
dataOnly = options.dataOnly
|
|
561
564
|
}
|
|
565
|
+
|
|
566
|
+
if (options.sync !== undefined) {
|
|
567
|
+
sync = options.sync
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (options.first !== undefined) {
|
|
571
|
+
first = options.first
|
|
572
|
+
}
|
|
562
573
|
}
|
|
563
574
|
|
|
564
575
|
if (typeof state === 'string') {
|
|
565
576
|
state = C.RECORD_STATE[state.toUpperCase()]
|
|
566
577
|
}
|
|
567
578
|
|
|
568
|
-
// TODO (perf):
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
579
|
+
// TODO (perf): Make a class
|
|
580
|
+
const subscription = {
|
|
581
|
+
subscriber,
|
|
582
|
+
first,
|
|
583
|
+
path,
|
|
584
|
+
state,
|
|
585
|
+
synced: false,
|
|
586
|
+
signal,
|
|
587
|
+
dataOnly,
|
|
588
|
+
data: kEmpty,
|
|
589
|
+
/** @type {NodeJS.Timeout|Timeout|null} */
|
|
590
|
+
timeout: null,
|
|
591
|
+
/** @type {Record?} */
|
|
592
|
+
record: null,
|
|
593
|
+
/** @type {Function?} */
|
|
594
|
+
abort: null,
|
|
595
|
+
unsubscribe() {
|
|
596
|
+
if (this.timeout) {
|
|
597
|
+
timers.clearTimeout(this.timeout)
|
|
598
|
+
this.timeout = null
|
|
599
|
+
}
|
|
585
600
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
601
|
+
if (this.signal) {
|
|
602
|
+
utils.removeAbortListener(this.signal, this.abort)
|
|
603
|
+
this.signal = null
|
|
604
|
+
this.abort = null
|
|
605
|
+
}
|
|
591
606
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
607
|
+
if (this.record) {
|
|
608
|
+
this.record.unsubscribe(onUpdate, this)
|
|
609
|
+
this.record.unref()
|
|
610
|
+
this.record = null
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
}
|
|
599
614
|
|
|
600
|
-
|
|
615
|
+
subscription.record = this.getRecord(name).subscribe(onUpdate, subscription)
|
|
601
616
|
|
|
602
|
-
|
|
603
|
-
// TODO (perf): Avoid Timer allocation.
|
|
604
|
-
subscription.timeout = timers.setTimeout(onTimeout, timeout, subscription)
|
|
605
|
-
}
|
|
617
|
+
const record = subscription.record
|
|
606
618
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
619
|
+
if (sync && record.state >= C.RECORD_STATE.SERVER) {
|
|
620
|
+
this._sync(onSync, sync === true ? 'WEAK' : sync, subscription)
|
|
621
|
+
} else {
|
|
622
|
+
subscription.synced = true
|
|
623
|
+
}
|
|
610
624
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
625
|
+
if (timeout > 0 && state && record.state < state) {
|
|
626
|
+
// TODO (perf): Avoid Timer allocation.
|
|
627
|
+
subscription.timeout = timers.setTimeout(onTimeout, timeout, subscription)
|
|
628
|
+
}
|
|
616
629
|
|
|
617
|
-
|
|
618
|
-
|
|
630
|
+
if (signal) {
|
|
631
|
+
// TODO (perf): Avoid abort closure allocation.
|
|
632
|
+
subscription.abort = () => subscriber.error(new utils.AbortError())
|
|
633
|
+
utils.addAbortListener(signal, subscription.abort)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (record.version) {
|
|
637
|
+
onUpdate(null, subscription)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return subscription
|
|
619
641
|
}
|
|
620
642
|
|
|
621
643
|
_$handle(message) {
|
|
622
|
-
let
|
|
644
|
+
let name
|
|
623
645
|
if (message.action === C.ACTIONS.ERROR) {
|
|
624
|
-
|
|
646
|
+
name = message.data[1]
|
|
625
647
|
} else {
|
|
626
|
-
|
|
648
|
+
name = message.data[0]
|
|
627
649
|
}
|
|
628
650
|
|
|
629
651
|
if (message.action === C.ACTIONS.SYNC) {
|
|
@@ -631,12 +653,12 @@ class RecordHandler {
|
|
|
631
653
|
return true
|
|
632
654
|
}
|
|
633
655
|
|
|
634
|
-
const listener = this._listeners.get(
|
|
656
|
+
const listener = this._listeners.get(name)
|
|
635
657
|
if (listener && listener._$onMessage(message)) {
|
|
636
658
|
return true
|
|
637
659
|
}
|
|
638
660
|
|
|
639
|
-
const record = this.
|
|
661
|
+
const record = this._records.get(name)
|
|
640
662
|
if (record && record._$onMessage(message)) {
|
|
641
663
|
return true
|
|
642
664
|
}
|
|
@@ -649,12 +671,17 @@ class RecordHandler {
|
|
|
649
671
|
listener._$onConnectionStateChange(connected)
|
|
650
672
|
}
|
|
651
673
|
|
|
652
|
-
for (const record of this.
|
|
674
|
+
for (const record of this._records.values()) {
|
|
653
675
|
record._$onConnectionStateChange(connected)
|
|
654
676
|
}
|
|
655
677
|
|
|
656
678
|
if (connected) {
|
|
657
679
|
this._connected = Date.now()
|
|
680
|
+
|
|
681
|
+
for (const update of this._putting.keys()) {
|
|
682
|
+
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.PUT, update)
|
|
683
|
+
}
|
|
684
|
+
|
|
658
685
|
for (const token of this._syncEmitter.eventNames()) {
|
|
659
686
|
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token])
|
|
660
687
|
}
|
|
@@ -662,6 +689,35 @@ class RecordHandler {
|
|
|
662
689
|
this._connected = 0
|
|
663
690
|
}
|
|
664
691
|
}
|
|
692
|
+
|
|
693
|
+
_sync(callback, type, opaque) {
|
|
694
|
+
this._syncQueue.push(callback, opaque)
|
|
695
|
+
|
|
696
|
+
if (this._syncQueue.length > 2) {
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (type == null) {
|
|
701
|
+
type = null
|
|
702
|
+
} else if (type === true) {
|
|
703
|
+
type = 'WEAK'
|
|
704
|
+
} else if (type !== 'WEAK' && type !== 'STRONG') {
|
|
705
|
+
throw new Error(`invalid sync type: ${type}`)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
setTimeout(() => {
|
|
709
|
+
// Token must be universally unique until deepstream properly separates
|
|
710
|
+
// sync requests from different sockets.
|
|
711
|
+
const token = xuid()
|
|
712
|
+
const queue = this._syncQueue.splice(0)
|
|
713
|
+
this._syncEmitter.once(token, () => {
|
|
714
|
+
for (let n = 0; n < queue.length; n += 2) {
|
|
715
|
+
queue[n](queue[n + 1])
|
|
716
|
+
}
|
|
717
|
+
})
|
|
718
|
+
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, type ? [token, type] : [token])
|
|
719
|
+
}, 1)
|
|
720
|
+
}
|
|
665
721
|
}
|
|
666
722
|
|
|
667
723
|
export default RecordHandler
|