@nxtedition/deepstream.io-client-js 31.2.16 → 31.2.18

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": "@nxtedition/deepstream.io-client-js",
3
- "version": "31.2.16",
3
+ "version": "31.2.18",
4
4
  "description": "the javascript client for deepstream.io",
5
5
  "homepage": "http://deepstream.io",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  import * as C from '../constants/constants.js'
2
2
  import * as messageBuilder from '../message/message-builder.js'
3
3
  import * as messageParser from '../message/message-parser.js'
4
- import MulticastListener from '../utils/multicast-listener.js'
4
+ import LegacyListener from '../utils/legacy-listener.js'
5
5
  import UnicastListener from '../utils/unicast-listener.js'
6
6
  import EventEmitter from 'component-emitter2'
7
7
  import * as rxjs from 'rxjs'
@@ -126,7 +126,7 @@ EventHandler.prototype.provide = function (pattern, callback, options) {
126
126
  const listener =
127
127
  options.mode?.toLowerCase() === 'unicast'
128
128
  ? new UnicastListener(C.TOPIC.EVENT, pattern, callback, this, options)
129
- : new MulticastListener(C.TOPIC.EVENT, pattern, callback, this, options)
129
+ : new LegacyListener(C.TOPIC.EVENT, pattern, callback, this, options)
130
130
 
131
131
  this._listeners.set(pattern, listener)
132
132
  return () => {
@@ -1,5 +1,5 @@
1
1
  import Record from './record.js'
2
- import MulticastListener from '../utils/multicast-listener.js'
2
+ import LegacyListener from '../utils/legacy-listener.js'
3
3
  import UnicastListener from '../utils/unicast-listener.js'
4
4
  import * as C from '../constants/constants.js'
5
5
  import * as rxjs from 'rxjs'
@@ -84,9 +84,18 @@ function onTimeout(subscription) {
84
84
 
85
85
  subscription.subscriber.error(
86
86
  Object.assign(
87
- new Error(`timeout state: ${subscription.record.name} [${current}<${expected}]`),
87
+ new Error(
88
+ !subscription.synced
89
+ ? `timeout sync: ${subscription.record.name}`
90
+ : `timeout state: ${subscription.record.name} [${current}<${expected}]`,
91
+ ),
88
92
  {
89
93
  code: 'ETIMEDOUT',
94
+ timeout: subscription.timeoutValue,
95
+ expected,
96
+ current,
97
+ synced: subscription.synced,
98
+ name: subscription.record.name,
90
99
  },
91
100
  ),
92
101
  )
@@ -119,7 +128,7 @@ class RecordHandler {
119
128
  }
120
129
 
121
130
  this._syncQueue = []
122
- this._syncMap = {}
131
+ this._syncMap = new Map()
123
132
 
124
133
  this.set = this.set.bind(this)
125
134
  this.get = this.get.bind(this)
@@ -219,13 +228,18 @@ class RecordHandler {
219
228
  * @returns {Record}
220
229
  */
221
230
  getRecord(name) {
222
- invariant(
223
- typeof name === 'string' &&
224
- name.length > 0 &&
225
- name !== '[object Object]' &&
226
- name.length <= 4096,
227
- `invalid name ${name}`,
228
- )
231
+ if (typeof name !== 'string' || name.length === 0) {
232
+ throw new Error('invalid argument: name')
233
+ }
234
+
235
+ if (name.startsWith('null') || name.startsWith('undefined') || name === '[object Object]') {
236
+ this._client._$onError(
237
+ C.TOPIC.RECORD,
238
+ C.EVENT.USER_ERROR,
239
+ 'name should not start with null or undefined',
240
+ name,
241
+ )
242
+ }
229
243
 
230
244
  let record = this._records.get(name)
231
245
 
@@ -261,7 +275,7 @@ class RecordHandler {
261
275
  const listener =
262
276
  options.mode?.toLowerCase() === 'unicast'
263
277
  ? new UnicastListener(C.TOPIC.RECORD, pattern, callback, this, options)
264
- : new MulticastListener(C.TOPIC.RECORD, pattern, callback, this, options)
278
+ : new LegacyListener(C.TOPIC.RECORD, pattern, callback, this, options)
265
279
 
266
280
  this._stats.listeners += 1
267
281
  this._listeners.set(pattern, listener)
@@ -552,78 +566,78 @@ class RecordHandler {
552
566
  * @returns {rxjs.Observable}
553
567
  */
554
568
  _observe(defaults, name, ...args) {
555
- return new rxjs.Observable((subscriber) => {
556
- let path
557
- let state = defaults?.state ?? C.RECORD_STATE.CLIENT
558
- let signal = null
559
- let timeout = defaults?.timeout ?? 0
560
- let dataOnly = defaults?.dataOnly ?? false
561
- let sync = defaults?.sync ?? false
562
-
563
- let idx = 0
564
-
565
- if (
566
- idx < args.length &&
567
- (args[idx] == null ||
568
- typeof args[idx] === 'string' ||
569
- Array.isArray(args[idx]) ||
570
- typeof args[idx] === 'function')
571
- ) {
572
- path = args[idx++]
573
- }
574
-
575
- if (idx < args.length && (args[idx] == null || typeof args[idx] === 'number')) {
576
- state = args[idx++]
577
- }
578
-
579
- if (idx < args.length && (args[idx] == null || typeof args[idx] === 'object')) {
580
- const options = args[idx++] || {}
581
-
582
- if (options.signal !== undefined) {
583
- signal = options.signal
584
- }
569
+ let path
570
+ let state = defaults?.state ?? C.RECORD_STATE.CLIENT
571
+ let signal = null
572
+ let timeout = defaults?.timeout ?? 0
573
+ let dataOnly = defaults?.dataOnly ?? false
574
+ let sync = defaults?.sync ?? false
585
575
 
586
- if (options.timeout !== undefined) {
587
- timeout = options.timeout
588
- }
576
+ let idx = 0
589
577
 
590
- if (options.path !== undefined) {
591
- path = options.path
592
- }
578
+ if (
579
+ idx < args.length &&
580
+ (args[idx] == null ||
581
+ typeof args[idx] === 'string' ||
582
+ Array.isArray(args[idx]) ||
583
+ typeof args[idx] === 'function')
584
+ ) {
585
+ path = args[idx++]
586
+ }
593
587
 
594
- if (options.state !== undefined) {
595
- state = options.state
596
- }
588
+ if (idx < args.length && (args[idx] == null || typeof args[idx] === 'number')) {
589
+ state = args[idx++]
590
+ }
597
591
 
598
- if (options.dataOnly !== undefined) {
599
- dataOnly = options.dataOnly
600
- }
592
+ if (idx < args.length && (args[idx] == null || typeof args[idx] === 'object')) {
593
+ const options = args[idx++] || {}
601
594
 
602
- if (options.sync !== undefined) {
603
- sync = options.sync
604
- }
595
+ if (options.signal !== undefined) {
596
+ signal = options.signal
605
597
  }
606
598
 
607
- if (typeof state === 'string') {
608
- state = C.RECORD_STATE[state.toUpperCase()]
599
+ if (options.timeout !== undefined) {
600
+ timeout = options.timeout
609
601
  }
610
602
 
611
- if (!Number.isInteger(state) || state < 0) {
612
- throw new Error('invalid argument: state')
603
+ if (options.path !== undefined) {
604
+ path = options.path
613
605
  }
614
606
 
615
- if (!Number.isInteger(timeout) || timeout < 0) {
616
- throw new Error('invalid argument: timeout')
607
+ if (options.state !== undefined) {
608
+ state = options.state
617
609
  }
618
610
 
619
- if (typeof dataOnly !== 'boolean') {
620
- throw new Error('invalid argument: dataOnly')
611
+ if (options.dataOnly !== undefined) {
612
+ dataOnly = options.dataOnly
621
613
  }
622
614
 
623
- if (typeof sync !== 'boolean') {
624
- throw new Error('invalid argument: sync')
615
+ if (options.sync !== undefined) {
616
+ sync = options.sync
625
617
  }
618
+ }
619
+
620
+ if (typeof state === 'string') {
621
+ state = C.RECORD_STATE[state.toUpperCase()]
622
+ }
626
623
 
624
+ if (!Number.isInteger(state) || state < 0) {
625
+ throw new Error('invalid argument: state')
626
+ }
627
+
628
+ if (!Number.isInteger(timeout) || timeout < 0) {
629
+ throw new Error('invalid argument: timeout')
630
+ }
631
+
632
+ if (typeof dataOnly !== 'boolean') {
633
+ throw new Error('invalid argument: dataOnly')
634
+ }
635
+
636
+ if (typeof sync !== 'boolean') {
637
+ throw new Error('invalid argument: sync')
638
+ }
639
+
640
+ return new rxjs.Observable((subscriber) => {
627
641
  // TODO (perf): Make a class
628
642
  const subscription = {
629
643
  /** @readonly @type {unknown} */
@@ -649,6 +663,9 @@ class RecordHandler {
649
663
  data: kEmpty,
650
664
  /** @type {boolean} */
651
665
  synced: false,
666
+
667
+ index: -1,
668
+ onUpdate,
652
669
  }
653
670
 
654
671
  subscriber.add(() => {
@@ -664,7 +681,7 @@ class RecordHandler {
664
681
  }
665
682
 
666
683
  if (subscription.record) {
667
- subscription.record.unsubscribe(onUpdate, subscription)
684
+ subscription.record._unobserve(subscription)
668
685
  subscription.record.unref()
669
686
  subscription.record = null
670
687
  }
@@ -675,11 +692,11 @@ class RecordHandler {
675
692
  utils.addAbortListener(subscription.signal, subscription.abort)
676
693
  }
677
694
 
678
- subscription.record = this.getRecord(name).subscribe(onUpdate, subscription)
695
+ subscription.record = this.getRecord(name)
696
+ subscription.record._observe(subscription)
679
697
 
680
698
  if (sync) {
681
- // TODO (fix): What about sync timeout?
682
- this._sync(onSync, sync === true ? 'WEAK' : sync, subscription)
699
+ this._sync(onSync, sync, subscription)
683
700
  } else {
684
701
  onSync(subscription)
685
702
  }
@@ -700,8 +717,8 @@ class RecordHandler {
700
717
  return true
701
718
  }
702
719
 
703
- const sync = this._syncMap[token]
704
- delete this._syncMap[token]
720
+ const sync = this._syncMap.get(token)
721
+ this._syncMap.delete(token)
705
722
 
706
723
  if (!sync) {
707
724
  return true
@@ -744,10 +761,10 @@ class RecordHandler {
744
761
  this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.PUT, update)
745
762
  }
746
763
 
747
- const syncMap = {}
748
- for (const sync of Object.values(this._syncMap)) {
764
+ const syncMap = new Map()
765
+ for (const sync of this._syncMap.values()) {
749
766
  const token = xuid()
750
- syncMap[token] = sync
767
+ syncMap.set(token, sync)
751
768
  this._connection.sendMsg(
752
769
  C.TOPIC.RECORD,
753
770
  C.ACTIONS.SYNC,
@@ -782,7 +799,7 @@ class RecordHandler {
782
799
  const token = xuid()
783
800
  const queue = this._syncQueue.splice(0)
784
801
 
785
- this._syncMap[token] = { queue, type }
802
+ this._syncMap.set(token, { queue, type })
786
803
  this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, type ? [token, type] : [token])
787
804
  }, 1)
788
805
  }
@@ -20,7 +20,13 @@ class Record {
20
20
  this._state = C.RECORD_STATE.VOID
21
21
  this._refs = 0
22
22
  this._subscriptions = []
23
- this._emitting = false
23
+
24
+ /** @type {Array|null} */
25
+ this._emittingArr = null
26
+ /** @type {number} */
27
+ this._emittingIndex = -1
28
+
29
+ this._observers = []
24
30
 
25
31
  /** @type Map? */ this._updating = null
26
32
  /** @type Array? */ this._patching = null
@@ -88,9 +94,8 @@ class Record {
88
94
  * @returns {Record}
89
95
  */
90
96
  subscribe(fn, opaque = null) {
91
- if (this._emitting) {
97
+ if (this._emittingArr == this._subscriptions) {
92
98
  this._subscriptions = this._subscriptions.slice()
93
- this._emitting = false
94
99
  }
95
100
 
96
101
  this._subscriptions.push(fn, opaque)
@@ -105,9 +110,8 @@ class Record {
105
110
  * @returns {Record}
106
111
  */
107
112
  unsubscribe(fn, opaque = null) {
108
- if (this._emitting) {
113
+ if (this._emittingArr == this._subscriptions) {
109
114
  this._subscriptions = this._subscriptions.slice()
110
- this._emitting = false
111
115
  }
112
116
 
113
117
  let idx = -1
@@ -128,6 +132,41 @@ class Record {
128
132
  return this
129
133
  }
130
134
 
135
+ /**
136
+ * @note Subscribers are unordered.
137
+ * @param {{ index: number, onUpdate: (Record) => void}} subscription
138
+ */
139
+ _observe(subscription) {
140
+ if (subscription.index != null && subscription.index !== -1) {
141
+ throw new Error('already observing')
142
+ }
143
+
144
+ subscription.index = this._observers.push(subscription) - 1
145
+ }
146
+
147
+ /**
148
+ * @param {{ index: number, onUpdate: (Record) => void}} subscription
149
+ */
150
+ _unobserve(subscription) {
151
+ if (subscription.index == null || subscription.index === -1) {
152
+ throw new Error('not observing')
153
+ }
154
+
155
+ if (this._emittingArr === this._observers) {
156
+ // TODO (perf): Shift from start if emitting?
157
+ this._observers = this._observers.slice()
158
+ }
159
+
160
+ const idx = subscription.index
161
+ const tmp = this._observers.pop()
162
+ subscription.index = -1
163
+
164
+ if (tmp !== subscription) {
165
+ this._observers[idx] = tmp
166
+ this._observers[idx].index = idx
167
+ }
168
+ }
169
+
131
170
  get(path) {
132
171
  if (!path) {
133
172
  return this._data
@@ -515,18 +554,41 @@ class Record {
515
554
  }
516
555
 
517
556
  _emitUpdate() {
518
- this._emitting = true
557
+ if (this._emittingArr != null) {
558
+ throw new Error('cannot reenter emitUpdate')
559
+ }
519
560
 
520
- const arr = this._subscriptions
521
- const len = arr.length
561
+ try {
562
+ const arr = this._subscriptions
563
+ const len = arr.length
522
564
 
523
- for (let n = 0; n < len; n += 2) {
524
- arr[n + 0](this, arr[n + 1])
565
+ this._emittingArr = arr
566
+ for (let n = 0; n < len; n += 2) {
567
+ this._emittingIndex = n
568
+ // TODO (fix): What if this throws?
569
+ arr[n + 0](this, arr[n + 1])
570
+ }
571
+ } finally {
572
+ this._emittingArr = null
573
+ this._emittingIndex = -1
525
574
  }
526
575
 
527
- this._handler._client.emit('recordUpdated', this)
576
+ try {
577
+ const arr = this._observers
578
+ const len = arr.length
528
579
 
529
- this._emitting = false
580
+ this._emittingArr = arr
581
+ for (let n = 0; n < len; n++) {
582
+ this._emittingIndex = n
583
+ // TODO (fix): What if this throws?
584
+ arr[n].onUpdate(this)
585
+ }
586
+ } finally {
587
+ this._emittingArr = null
588
+ this._emittingIndex = -1
589
+ }
590
+
591
+ this._handler._client.emit('recordUpdated', this)
530
592
  }
531
593
  }
532
594
 
@@ -1,6 +1,6 @@
1
1
  import * as rxjs from 'rxjs'
2
2
  import * as C from '../constants/constants.js'
3
- import { h64ToString, findBigIntPaths } from '../utils/utils.js'
3
+ import { h64ToString, findBigIntPaths } from './utils.js'
4
4
 
5
5
  export default class Listener {
6
6
  constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {