@nxtedition/deepstream.io-client-js 26.0.24 → 27.0.0-beta.3

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": "26.0.24",
3
+ "version": "27.0.0-beta.3",
4
4
  "description": "the javascript client for deepstream.io",
5
5
  "homepage": "http://deepstream.io",
6
6
  "type": "module",
@@ -71,6 +71,7 @@
71
71
  "xxhash-wasm": "^1.0.2"
72
72
  },
73
73
  "devDependencies": {
74
+ "@types/node": "^22.0.0",
74
75
  "eslint": "^8.0.0",
75
76
  "eslint-config-prettier": "^9.1.0",
76
77
  "eslint-config-standard": "^17.1.0",
@@ -37,6 +37,8 @@ export default function Connection(client, url, options) {
37
37
  this._url = new URL(url)
38
38
 
39
39
  this._state = C.CONNECTION_STATE.CLOSED
40
+
41
+ this._createEndpoint()
40
42
  }
41
43
 
42
44
  Emitter(Connection.prototype)
@@ -159,7 +161,7 @@ Connection.prototype._sendAuthParams = function () {
159
161
  this._setState(C.CONNECTION_STATE.AUTHENTICATING)
160
162
  const authMessage = messageBuilder.getMsg(C.TOPIC.AUTH, C.ACTIONS.REQUEST, [
161
163
  this._authParams,
162
- '26.0.22',
164
+ '27.0.0',
163
165
  utils.isNode
164
166
  ? `Node/${process.version}`
165
167
  : globalThis.navigator && globalThis.navigator.userAgent,
@@ -1,25 +1,101 @@
1
1
  import * as C from '../constants/constants.js'
2
+ import * as utils from '../utils/utils.js'
3
+ import varint from 'varint'
2
4
 
3
- const SEP = C.MESSAGE_PART_SEPERATOR
5
+ const poolEncoder = new globalThis.TextEncoder()
6
+
7
+ // TODO (fix): Don't assume maxMesageSize is 1MB
8
+ const maxMessageSize = 1024 * 1024
9
+ const poolSize = maxMessageSize * 4
10
+
11
+ let poolBuffer
12
+ let poolView
13
+ let poolOffset
14
+
15
+ function reallocPool() {
16
+ poolBuffer = utils.isNode
17
+ ? globalThis.Buffer.allocUnsafe(poolSize)
18
+ : new Uint8Array(new ArrayBuffer(poolSize))
19
+ poolView = new DataView(poolBuffer.buffer)
20
+ poolOffset = 0
21
+ }
22
+
23
+ function alignPool() {
24
+ // Ensure aligned slices
25
+ if (poolOffset & 0x7) {
26
+ poolOffset |= 0x7
27
+ poolOffset++
28
+ }
29
+ }
30
+
31
+ function writeString(dst, str, offset) {
32
+ if (utils.isNode) {
33
+ return dst.write(str, offset)
34
+ } else {
35
+ const res = poolEncoder.encodeInto(str, new Uint8Array(dst.buffer, offset))
36
+ return res.written
37
+ }
38
+ }
4
39
 
5
40
  export function getMsg(topic, action, data) {
6
41
  if (data && !(data instanceof Array)) {
7
42
  throw new Error('data must be an array')
8
43
  }
9
44
 
10
- const sendData = [topic, action]
45
+ if (!poolBuffer || poolOffset + maxMessageSize > poolSize) {
46
+ reallocPool()
47
+ } else {
48
+ alignPool()
49
+ }
50
+
51
+ const start = poolOffset
52
+
53
+ const headerSize = 8
54
+ poolBuffer[poolOffset++] = 128 + headerSize
55
+ let headerPos = poolOffset
56
+ poolOffset += headerSize - 1
57
+
58
+ poolBuffer[poolOffset++] = topic.charCodeAt(0)
59
+ poolBuffer[poolOffset++] = 31
60
+ for (let n = 0; n < action.length; n++) {
61
+ poolBuffer[poolOffset++] = action.charCodeAt(n)
62
+ }
11
63
 
12
64
  if (data) {
13
65
  for (let i = 0; i < data.length; i++) {
14
- if (typeof data[i] === 'object') {
15
- sendData.push(JSON.stringify(data[i]))
66
+ const type = typeof data[i]
67
+ let len
68
+ if (data[i] == null) {
69
+ poolBuffer[poolOffset++] = 31
70
+ len = 0
71
+ } else if (type === 'object') {
72
+ poolBuffer[poolOffset++] = 31
73
+ len = writeString(poolBuffer, JSON.stringify(data[i]), poolOffset)
74
+ } else if (type === 'bigint') {
75
+ poolBuffer[poolOffset++] = 31
76
+ poolView.setBigUint64(poolOffset, data[i], false)
77
+ len = 8
78
+ } else if (type === 'string') {
79
+ poolBuffer[poolOffset++] = 31
80
+ len = writeString(poolBuffer, data[i], poolOffset)
16
81
  } else {
17
- sendData.push(data[i])
82
+ throw new Error('invalid data')
83
+ }
84
+ poolOffset += len
85
+
86
+ varint.encode(len + 1, poolBuffer, headerPos)
87
+ headerPos += varint.encode.bytes
88
+ if (headerPos - start >= headerSize) {
89
+ throw new Error('header too large')
90
+ }
91
+
92
+ if (poolOffset >= poolSize) {
93
+ throw new Error('message too large')
18
94
  }
19
95
  }
20
96
  }
21
97
 
22
- return sendData.join(SEP)
98
+ return new Uint8Array(poolBuffer.buffer, start, poolOffset - start)
23
99
  }
24
100
 
25
101
  export function typed(value) {
@@ -7,7 +7,7 @@ import invariant from 'invariant'
7
7
  import cloneDeep from 'lodash.clonedeep'
8
8
  import * as timers from '../utils/timers.js'
9
9
 
10
- class Record {
10
+ export default class Record {
11
11
  static STATE = C.RECORD_STATE
12
12
 
13
13
  constructor(name, handler) {
@@ -15,6 +15,7 @@ class Record {
15
15
 
16
16
  this._handler = handler
17
17
  this._name = name
18
+ this._key = utils.h64(name)
18
19
  this._version = ''
19
20
  this._data = jsonPath.EMPTY
20
21
  this._state = C.RECORD_STATE.VOID
@@ -24,7 +25,15 @@ class Record {
24
25
 
25
26
  /** @type Map? */ this._updating = null
26
27
  /** @type Array? */ this._patching = null
27
- this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
28
+ this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [
29
+ this._key,
30
+ this._name,
31
+ ])
32
+ }
33
+
34
+ /** @type {bigint} */
35
+ get key() {
36
+ return this._key
28
37
  }
29
38
 
30
39
  /** @type {string} */
@@ -62,7 +71,8 @@ class Record {
62
71
  if (this._refs === 1) {
63
72
  this._handler._onPruning(this, false)
64
73
  this._subscribed =
65
- this._subscribed || connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
74
+ this._subscribed ||
75
+ connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._key, this._name])
66
76
  }
67
77
  return this
68
78
  }
@@ -324,7 +334,8 @@ class Record {
324
334
 
325
335
  if (connected) {
326
336
  this._subscribed =
327
- this._refs > 0 && connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
337
+ this._refs > 0 &&
338
+ connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._key, this._name])
328
339
 
329
340
  if (this._updating) {
330
341
  for (const update of this._updating.values()) {
@@ -349,7 +360,7 @@ class Record {
349
360
  invariant(!this._updating, 'must not have updates')
350
361
 
351
362
  if (this._subscribed) {
352
- connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this._name])
363
+ connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this._key])
353
364
  this._subscribed = false
354
365
  }
355
366
 
@@ -371,7 +382,7 @@ class Record {
371
382
  const prevVersion = this._version
372
383
  const nextVersion = this._makeVersion(parseInt(prevVersion) + 1)
373
384
 
374
- const update = [this._name, nextVersion, jsonPath.stringify(nextData), prevVersion]
385
+ const update = [this._key, nextVersion, jsonPath.stringify(nextData), prevVersion]
375
386
 
376
387
  if (!this._updating) {
377
388
  this._onUpdating(true)
@@ -389,7 +400,7 @@ class Record {
389
400
  this._version = nextVersion
390
401
  }
391
402
 
392
- _onUpdate([, version, data]) {
403
+ _onUpdate([, version, data, hasProvider]) {
393
404
  const prevData = this._data
394
405
  const prevVersion = this._version
395
406
  const prevState = this._state
@@ -422,7 +433,9 @@ class Record {
422
433
  this._onPatching(false)
423
434
  }
424
435
 
425
- if (this._state < C.RECORD_STATE.SERVER) {
436
+ if (hasProvider === 'T') {
437
+ this._state = C.RECORD_STATE.PROVIDER
438
+ } else if (this._state < C.RECORD_STATE.SERVER) {
426
439
  this._state = this._version.charAt(0) === 'I' ? C.RECORD_STATE.STALE : C.RECORD_STATE.SERVER
427
440
  }
428
441
 
@@ -571,5 +584,3 @@ Object.defineProperty(Record.prototype, 'hasProvider', {
571
584
  return this.state >= C.RECORD_STATE.PROVIDER
572
585
  },
573
586
  })
574
-
575
- export default Record
@@ -1,6 +1,6 @@
1
1
  import * as rxjs from 'rxjs'
2
2
  import * as C from '../constants/constants.js'
3
- import { h64ToString } from '../utils/utils.js'
3
+ import { h64, h64ToString } from '../utils/utils.js'
4
4
 
5
5
  export default class Listener {
6
6
  constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {
@@ -48,8 +48,12 @@ export default class Listener {
48
48
 
49
49
  const name = message.data[1]
50
50
 
51
+ // TOOD (fix): Validate name
52
+
53
+ const key = h64(name)
54
+
51
55
  if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND) {
52
- if (this._subscriptions.has(name)) {
56
+ if (this._subscriptions.has(key)) {
53
57
  this._error(name, 'invalid add: listener exists')
54
58
  return
55
59
  }
@@ -57,6 +61,7 @@ export default class Listener {
57
61
  // TODO (refactor): Move to class
58
62
  const provider = {
59
63
  name,
64
+ key,
60
65
  value$: null,
61
66
  sending: false,
62
67
  accepted: false,
@@ -69,7 +74,7 @@ export default class Listener {
69
74
  if (this.connected && provider.accepted) {
70
75
  this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [
71
76
  this._pattern,
72
- provider.name,
77
+ provider.key,
73
78
  ])
74
79
  }
75
80
 
@@ -102,7 +107,7 @@ export default class Listener {
102
107
  this._connection.sendMsg(
103
108
  this._topic,
104
109
  accepted ? C.ACTIONS.LISTEN_ACCEPT : C.ACTIONS.LISTEN_REJECT,
105
- [this._pattern, provider.name],
110
+ [this._pattern, provider.key],
106
111
  )
107
112
 
108
113
  provider.version = null
@@ -158,7 +163,7 @@ export default class Listener {
158
163
  if (provider.version !== version) {
159
164
  provider.version = version
160
165
  this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, [
161
- provider.name,
166
+ provider.key,
162
167
  version,
163
168
  body,
164
169
  ])
@@ -182,9 +187,9 @@ export default class Listener {
182
187
 
183
188
  provider.start()
184
189
 
185
- this._subscriptions.set(provider.name, provider)
190
+ this._subscriptions.set(provider.key, provider)
186
191
  } else if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
187
- const provider = this._subscriptions.get(name)
192
+ const provider = this._subscriptions.get(key)
188
193
  if (!provider?.value$) {
189
194
  return
190
195
  }
@@ -196,13 +201,13 @@ export default class Listener {
196
201
  provider.valueSubscription = provider.value$.subscribe(provider.observer)
197
202
  }
198
203
  } else if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED) {
199
- const provider = this._subscriptions.get(name)
204
+ const provider = this._subscriptions.get(key)
200
205
 
201
206
  if (!provider) {
202
207
  this._error(name, 'invalid remove: listener missing')
203
208
  } else {
204
209
  provider.stop()
205
- this._subscriptions.delete(provider.name)
210
+ this._subscriptions.delete(provider.key)
206
211
  }
207
212
  } else {
208
213
  return false
@@ -1,6 +1,6 @@
1
1
  import * as rxjs from 'rxjs'
2
2
  import * as C from '../constants/constants.js'
3
- import { h64ToString } from '../utils/utils.js'
3
+ import { h64, h64ToString } from '../utils/utils.js'
4
4
 
5
5
  const valuePipe = rxjs.pipe(
6
6
  rxjs.map((value) => {
@@ -55,8 +55,12 @@ export default class Listener {
55
55
  _$onMessage(message) {
56
56
  const name = message.data[1]
57
57
 
58
+ // TODO (fix): Validate name
59
+
60
+ const key = h64(name)
61
+
58
62
  if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
59
- if (this._subscriptions.has(name)) {
63
+ if (this._subscriptions.has(key)) {
60
64
  this._error(name, 'invalid accept: listener exists')
61
65
  return
62
66
  }
@@ -72,29 +76,29 @@ export default class Listener {
72
76
  const subscription = value$.pipe(valuePipe).subscribe({
73
77
  next: (data) => {
74
78
  if (data == null) {
75
- this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, name])
76
- this._subscriptions.delete(name)
79
+ this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
80
+ this._subscriptions.delete(key)
77
81
  subscription.unsubscribe()
78
82
  } else {
79
83
  const version = `INF-${h64ToString(data)}`
80
- this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [name, version, data])
84
+ this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [key, version, data])
81
85
  }
82
86
  },
83
87
  error: (err) => {
84
88
  this._error(name, err)
85
- this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, name])
86
- this._subscriptions.delete(name)
89
+ this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
90
+ this._subscriptions.delete(key)
87
91
  },
88
92
  })
89
- this._subscriptions.set(name, subscription)
93
+ this._subscriptions.set(key, subscription)
90
94
  } else {
91
- this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, name])
95
+ this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
92
96
  }
93
97
  } else if (message.action === C.ACTIONS.LISTEN_REJECT) {
94
- const subscription = this._subscriptions.get(name)
98
+ const subscription = this._subscriptions.get(key)
95
99
 
96
100
  if (subscription) {
97
- this._subscriptions.delete(name)
101
+ this._subscriptions.delete(key)
98
102
  subscription.unsubscribe()
99
103
  } else {
100
104
  this._error(name, 'invalid remove: listener missing')