@nxtedition/deepstream.io-client-js 27.0.0-beta.1 → 27.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/deepstream.io-client-js",
3
- "version": "27.0.0-beta.1",
3
+ "version": "27.0.1",
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",
package/src/client.js CHANGED
@@ -127,12 +127,10 @@ Client.prototype._getOptions = function (options) {
127
127
  return mergedOptions
128
128
  }
129
129
 
130
- function createDeepstream(url, options) {
130
+ export default function createDeepstream(url, options) {
131
131
  return new Client(url, options)
132
132
  }
133
133
 
134
134
  Client.prototype.isSameOrNewer = utils.isSameOrNewer
135
135
  Client.prototype.CONSTANTS = C
136
136
  createDeepstream.CONSTANTS = C
137
-
138
- export default createDeepstream
@@ -1,10 +1,10 @@
1
- import * as messageBuilder from '../message/message-builder.js'
2
- import messageParser from '../message/message-parser.js'
3
1
  import * as C from '../constants/constants.js'
2
+ import * as messageBuilder from '../message/message-builder.js'
3
+ import * as messageParser from '../message/message-parser.js'
4
4
  import MulticastListener from '../utils/multicast-listener.js'
5
5
  import UnicastListener from '../utils/unicast-listener.js'
6
6
  import EventEmitter from 'component-emitter2'
7
- import * as rxjs from 'rxjs'
7
+ import rxjs from 'rxjs'
8
8
 
9
9
  const EventHandler = function (options, connection, client) {
10
10
  this._options = options
@@ -1,16 +1,14 @@
1
1
  import * as utils from '../utils/utils.js'
2
- import messageParser from './message-parser.js'
2
+ import * as messageParser from './message-parser.js'
3
3
  import * as messageBuilder from './message-builder.js'
4
4
  import * as C from '../constants/constants.js'
5
- import xxhash from 'xxhash-wasm'
6
5
  import FixedQueue from '../utils/fixed-queue.js'
7
6
  import Emitter from 'component-emitter2'
8
7
 
9
- const HASHER = await xxhash()
10
8
  const NodeWebSocket = utils.isNode ? await import('ws').then((x) => x.default) : null
11
9
  const BrowserWebSocket = globalThis.WebSocket || globalThis.MozWebSocket
12
10
 
13
- const Connection = function (client, url, options) {
11
+ export default function Connection(client, url, options) {
14
12
  this._client = client
15
13
  this._options = options
16
14
  this._logger = options.logger
@@ -40,8 +38,6 @@ const Connection = function (client, url, options) {
40
38
 
41
39
  this._state = C.CONNECTION_STATE.CLOSED
42
40
 
43
- this.hasher = HASHER
44
-
45
41
  this._createEndpoint()
46
42
  }
47
43
 
@@ -86,30 +82,25 @@ Connection.prototype.close = function () {
86
82
  this._endpoint?.close()
87
83
 
88
84
  if (this._reconnectTimeout) {
89
- globalThis.clearTimeout(this._reconnectTimeout)
85
+ clearTimeout(this._reconnectTimeout)
90
86
  this._reconnectTimeout = null
91
87
  }
92
88
  }
93
89
 
94
90
  Connection.prototype._createEndpoint = function () {
95
- if (utils.isNode) {
96
- this._endpoint = new NodeWebSocket(this._url, {
97
- generateMask() {},
98
- })
99
- } else {
100
- this._endpoint = new BrowserWebSocket(this._url)
101
- this._endpoint.binaryType = 'arraybuffer'
102
- }
91
+ this._endpoint = NodeWebSocket
92
+ ? new NodeWebSocket(this._url, {
93
+ generateMask() {},
94
+ })
95
+ : new BrowserWebSocket(this._url)
103
96
  this._corked = false
104
97
 
105
98
  this._endpoint.onopen = this._onOpen.bind(this)
106
99
  this._endpoint.onerror = this._onError.bind(this)
107
100
  this._endpoint.onclose = this._onClose.bind(this)
108
-
109
- const decoder = new TextDecoder()
110
- this._endpoint.onmessage = ({ data }) => {
111
- this._onMessage(typeof data === 'string' ? data : decoder.decode(data))
112
- }
101
+ this._endpoint.onmessage = BrowserWebSocket
102
+ ? ({ data }) => this._onMessage(typeof data === 'string' ? data : Buffer.from(data).toString())
103
+ : ({ data }) => this._onMessage(typeof data === 'string' ? data : data.toString())
113
104
  }
114
105
 
115
106
  Connection.prototype.send = function (message) {
@@ -136,7 +127,7 @@ Connection.prototype.send = function (message) {
136
127
  if (this._endpoint._socket && !this._corked) {
137
128
  this._endpoint._socket.cork()
138
129
  this._corked = true
139
- globalThis.setTimeout(() => {
130
+ setTimeout(() => {
140
131
  this._endpoint._socket.uncork()
141
132
  this._corked = false
142
133
  }, 1)
@@ -245,6 +236,10 @@ Connection.prototype._recvMessages = function (deadline) {
245
236
  continue
246
237
  }
247
238
 
239
+ if (this._logger) {
240
+ this._logger.trace(message, 'receive')
241
+ }
242
+
248
243
  messageParser.parseMessage(message, this._client, this._message)
249
244
 
250
245
  this.emit('recv', this._message)
@@ -365,5 +360,3 @@ Connection.prototype._clearReconnect = function () {
365
360
  }
366
361
  this._reconnectionAttempt = 0
367
362
  }
368
-
369
- export default Connection
@@ -1,16 +1,18 @@
1
1
  import * as C from '../constants/constants.js'
2
- import varint from 'varint'
3
2
  import * as utils from '../utils/utils.js'
3
+ import varint from 'varint'
4
4
 
5
5
  const poolEncoder = new globalThis.TextEncoder()
6
6
 
7
- let poolSize
7
+ // TODO (fix): Don't assume maxMesageSize is 1MB
8
+ const maxMessageSize = 1024 * 1024
9
+ const poolSize = maxMessageSize * 4
10
+
8
11
  let poolBuffer
9
12
  let poolView
10
13
  let poolOffset
11
14
 
12
- function allocPool(size) {
13
- poolSize = size || poolSize || 1024 * 1024
15
+ function reallocPool() {
14
16
  poolBuffer = utils.isNode
15
17
  ? globalThis.Buffer.allocUnsafe(poolSize)
16
18
  : new Uint8Array(new ArrayBuffer(poolSize))
@@ -40,8 +42,8 @@ export function getMsg(topic, action, data) {
40
42
  throw new Error('data must be an array')
41
43
  }
42
44
 
43
- if (!poolSize || poolOffset + poolSize / 16 >= poolSize) {
44
- allocPool()
45
+ if (!poolBuffer || poolOffset + maxMessageSize > poolSize) {
46
+ reallocPool()
45
47
  } else {
46
48
  alignPool()
47
49
  }
@@ -84,12 +86,11 @@ export function getMsg(topic, action, data) {
84
86
  varint.encode(len + 1, poolBuffer, headerPos)
85
87
  headerPos += varint.encode.bytes
86
88
  if (headerPos - start >= headerSize) {
87
- throw new Error(`header too large: ${headerPos - start} ${headerSize}`)
89
+ throw new Error('header too large')
88
90
  }
89
91
 
90
- if (poolOffset >= poolBuffer.length) {
91
- allocPool(start === 0 ? poolSize * 2 : poolSize)
92
- return getMsg(topic, action, data)
92
+ if (poolOffset >= poolSize) {
93
+ throw new Error('message too large')
93
94
  }
94
95
  }
95
96
  }
@@ -1,10 +1,12 @@
1
1
  import * as C from '../constants/constants.js'
2
2
 
3
- const MessageParser = function () {
4
- this._actions = this._getActions()
3
+ const actions = {}
4
+
5
+ for (const key in C.ACTIONS) {
6
+ actions[C.ACTIONS[key]] = key
5
7
  }
6
8
 
7
- MessageParser.prototype.convertTyped = function (value, client) {
9
+ export function convertTyped(value, client) {
8
10
  const type = value.charAt(0)
9
11
 
10
12
  if (type === C.TYPES.STRING) {
@@ -45,17 +47,7 @@ MessageParser.prototype.convertTyped = function (value, client) {
45
47
  return undefined
46
48
  }
47
49
 
48
- MessageParser.prototype._getActions = function () {
49
- const actions = {}
50
-
51
- for (const key in C.ACTIONS) {
52
- actions[C.ACTIONS[key]] = key
53
- }
54
-
55
- return actions
56
- }
57
-
58
- MessageParser.prototype.parseMessage = function (message, client, result) {
50
+ export function parseMessage(message, client, result) {
59
51
  const parts = message.split(C.MESSAGE_PART_SEPERATOR)
60
52
 
61
53
  if (parts.length < 2) {
@@ -72,7 +64,7 @@ MessageParser.prototype.parseMessage = function (message, client, result) {
72
64
  return null
73
65
  }
74
66
 
75
- if (this._actions[parts[1]] === undefined) {
67
+ if (actions[parts[1]] === undefined) {
76
68
  client._$onError(
77
69
  C.TOPIC.ERROR,
78
70
  C.EVENT.MESSAGE_PARSE_ERROR,
@@ -87,5 +79,3 @@ MessageParser.prototype.parseMessage = function (message, client, result) {
87
79
  result.action = parts[1]
88
80
  result.data = parts.splice(2)
89
81
  }
90
-
91
- export default new MessageParser()
@@ -2,7 +2,7 @@ import Record from './record.js'
2
2
  import MulticastListener from '../utils/multicast-listener.js'
3
3
  import UnicastListener from '../utils/unicast-listener.js'
4
4
  import * as C from '../constants/constants.js'
5
- import * as rxjs from 'rxjs'
5
+ import rxjs from 'rxjs'
6
6
  import invariant from 'invariant'
7
7
  import EventEmitter from 'component-emitter2'
8
8
  import jsonPath from '@nxtedition/json-path'
@@ -97,11 +97,19 @@ class RecordHandler {
97
97
  this._connection = connection
98
98
  this._client = client
99
99
  this._records = new Map()
100
+ this._cache = new Map()
100
101
  this._listeners = new Map()
101
102
  this._pruning = new Set()
102
103
  this._patching = new Map()
103
104
  this._updating = new Map()
104
105
 
106
+ this._registry = new FinalizationRegistry((name) => {
107
+ const entry = this._cache.get(name)
108
+ if (entry && entry.deref && entry.deref() === undefined) {
109
+ this._cache.delete(name)
110
+ }
111
+ })
112
+
105
113
  this._connected = 0
106
114
  this._stats = {
107
115
  updating: 0,
@@ -134,6 +142,11 @@ class RecordHandler {
134
142
  for (const rec of pruning) {
135
143
  rec._$dispose()
136
144
  this._records.delete(rec.name)
145
+
146
+ if (!this._cache.has(rec.name)) {
147
+ this._cache.set(rec.name, new WeakRef(rec))
148
+ this._registry.register(rec, rec.name)
149
+ }
137
150
  }
138
151
 
139
152
  this._stats.pruning -= pruning.size
@@ -219,7 +232,7 @@ class RecordHandler {
219
232
  let record = this._records.get(name)
220
233
 
221
234
  if (!record) {
222
- record = new Record(name, this)
235
+ record = this._cache.get(name)?.deref() ?? new Record(name, this)
223
236
  this._stats.records += 1
224
237
  this._stats.created += 1
225
238
  this._records.set(name, record)
@@ -1,7 +1,7 @@
1
1
  import jsonPath from '@nxtedition/json-path'
2
2
  import * as utils from '../utils/utils.js'
3
3
  import * as C from '../constants/constants.js'
4
- import messageParser from '../message/message-parser.js'
4
+ import * as messageParser from '../message/message-parser.js'
5
5
  import xuid from 'xuid'
6
6
  import invariant from 'invariant'
7
7
  import cloneDeep from 'lodash.clonedeep'
@@ -11,40 +11,36 @@ export default class Record {
11
11
  static STATE = C.RECORD_STATE
12
12
 
13
13
  constructor(name, handler) {
14
- if (!name?.length || typeof name !== 'string') {
15
- throw new Error('invalid argument: name')
16
- }
17
-
18
14
  const connection = handler._connection
19
15
 
20
16
  this._handler = handler
21
-
22
17
  this._name = name
23
- this._key = utils.hashNameBigInt(name)
18
+ this._key = utils.h64(name)
24
19
  this._version = ''
25
20
  this._data = jsonPath.EMPTY
26
21
  this._state = C.RECORD_STATE.VOID
27
22
  this._refs = 0
28
23
  this._subscriptions = []
29
24
  this._emitting = false
25
+
30
26
  /** @type Map? */ this._updating = null
31
27
  /** @type Array? */ this._patching = null
32
28
  this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [
33
- this._name,
34
29
  this._key,
30
+ this._name,
35
31
  ])
36
32
  }
37
33
 
38
- /** @type {string} */
39
- get name() {
40
- return this._name
41
- }
42
-
43
34
  /** @type {bigint} */
44
35
  get key() {
45
36
  return this._key
46
37
  }
47
38
 
39
+ /** @type {string} */
40
+ get name() {
41
+ return this._name
42
+ }
43
+
48
44
  /** @type {string} */
49
45
  get version() {
50
46
  return this._version
@@ -76,7 +72,7 @@ export default class Record {
76
72
  this._handler._onPruning(this, false)
77
73
  this._subscribed =
78
74
  this._subscribed ||
79
- connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name, this._key])
75
+ connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._key, this._name])
80
76
  }
81
77
  return this
82
78
  }
@@ -437,7 +433,7 @@ export default class Record {
437
433
  this._onPatching(false)
438
434
  }
439
435
 
440
- if (this._state < C.RECORD_STATE.PROVIDER && hasProvider === 'T') {
436
+ if (hasProvider === 'T') {
441
437
  this._state = C.RECORD_STATE.PROVIDER
442
438
  } else if (this._state < C.RECORD_STATE.SERVER) {
443
439
  this._state = this._version.charAt(0) === 'I' ? C.RECORD_STATE.STALE : C.RECORD_STATE.SERVER
@@ -1,6 +1,6 @@
1
1
  import * as C from '../constants/constants.js'
2
2
  import RpcResponse from './rpc-response.js'
3
- import messageParser from '../message/message-parser.js'
3
+ import * as messageParser from '../message/message-parser.js'
4
4
  import * as messageBuilder from '../message/message-builder.js'
5
5
  import xuid from 'xuid'
6
6
 
@@ -1,8 +1,8 @@
1
- import * as C from '../constants/constants.js'
2
1
  import * as rxjs from 'rxjs'
3
- import * as utils from '../utils/utils.js'
2
+ import * as C from '../constants/constants.js'
3
+ import { h64, h64ToString } from '../utils/utils.js'
4
4
 
5
- class Listener {
5
+ export default class Listener {
6
6
  constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {
7
7
  this._topic = topic
8
8
  this._pattern = pattern
@@ -48,12 +48,9 @@ class Listener {
48
48
 
49
49
  const name = message.data[1]
50
50
 
51
- if (!name?.length) {
52
- this._error(name, 'invalid message')
53
- return
54
- }
51
+ // TOOD (fix): Validate name
55
52
 
56
- const key = utils.hashNameBigInt(name)
53
+ const key = h64(name)
57
54
 
58
55
  if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND) {
59
56
  if (this._subscriptions.has(key)) {
@@ -86,7 +83,7 @@ class Listener {
86
83
  provider.accepted = false
87
84
  provider.sending = false
88
85
 
89
- globalThis.clearTimeout(provider.timeout)
86
+ clearTimeout(provider.timeout)
90
87
  provider.timeout = null
91
88
 
92
89
  provider.patternSubscription?.unsubscribe()
@@ -160,7 +157,7 @@ class Listener {
160
157
  }
161
158
 
162
159
  const body = typeof value !== 'string' ? this._stringify(value) : value
163
- const hash = utils.h64ToString(body)
160
+ const hash = h64ToString(body)
164
161
  const version = `INF-${hash}`
165
162
 
166
163
  if (provider.version !== version) {
@@ -237,5 +234,3 @@ class Listener {
237
234
  this._subscriptions.clear()
238
235
  }
239
236
  }
240
-
241
- export default Listener
@@ -1,8 +1,8 @@
1
- import * as C from '../constants/constants.js'
2
1
  import * as rxjs from 'rxjs'
3
- import * as utils from '../utils/utils.js'
2
+ import * as C from '../constants/constants.js'
3
+ import { h64, h64ToString } from '../utils/utils.js'
4
4
 
5
- const PIPE = rxjs.pipe(
5
+ const valuePipe = rxjs.pipe(
6
6
  rxjs.map((value) => {
7
7
  let data
8
8
  if (value && typeof value === 'string') {
@@ -21,7 +21,7 @@ const PIPE = rxjs.pipe(
21
21
  rxjs.distinctUntilChanged(),
22
22
  )
23
23
 
24
- class Listener {
24
+ export default class Listener {
25
25
  constructor(topic, pattern, callback, handler, opts) {
26
26
  if (opts.recursive) {
27
27
  throw new Error('invalid argument: recursive')
@@ -55,12 +55,9 @@ class Listener {
55
55
  _$onMessage(message) {
56
56
  const name = message.data[1]
57
57
 
58
- if (!name?.length) {
59
- this._error(name, 'invalid message')
60
- return
61
- }
58
+ // TODO (fix): Validate name
62
59
 
63
- const key = utils.hashNameBigInt(name)
60
+ const key = h64(name)
64
61
 
65
62
  if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
66
63
  if (this._subscriptions.has(key)) {
@@ -76,14 +73,14 @@ class Listener {
76
73
  }
77
74
 
78
75
  if (value$) {
79
- const subscription = value$.pipe(PIPE).subscribe({
76
+ const subscription = value$.pipe(valuePipe).subscribe({
80
77
  next: (data) => {
81
78
  if (data == null) {
82
79
  this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
83
80
  this._subscriptions.delete(key)
84
81
  subscription.unsubscribe()
85
82
  } else {
86
- const version = `INF-${utils.h64ToString(data)}`
83
+ const version = `INF-${h64ToString(data)}`
87
84
  this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [key, version, data])
88
85
  }
89
86
  },
@@ -133,5 +130,3 @@ class Listener {
133
130
  this._connection.sendMsg(this._topic, C.ACTIONS.UNLISTEN, [this._pattern])
134
131
  }
135
132
  }
136
-
137
- export default Listener
@@ -1,7 +1,5 @@
1
1
  import xxhash from 'xxhash-wasm'
2
2
 
3
- const HASHER = await xxhash()
4
-
5
3
  const NODE_ENV = typeof process !== 'undefined' && process.env && process.env.NODE_ENV
6
4
  export const isNode = typeof process !== 'undefined' && process.toString() === '[object process]'
7
5
  export const isProduction = NODE_ENV === 'production'
@@ -83,7 +81,7 @@ export function setTimeout(callback, timeoutDuration) {
83
81
 
84
82
  export function setInterval(callback, intervalDuration) {
85
83
  if (intervalDuration !== null) {
86
- return globalThis.setInterval(callback, intervalDuration)
84
+ return setInterval(callback, intervalDuration)
87
85
  } else {
88
86
  return -1
89
87
  }
@@ -179,23 +177,16 @@ export function removeAbortListener(signal, handler) {
179
177
  }
180
178
  }
181
179
 
182
- export function h64(name) {
183
- return HASHER.h64(name)
184
- }
180
+ const HASHER = await xxhash()
185
181
 
186
- export function h64ToString(name) {
187
- return HASHER.h64ToString(name)
182
+ export function h64(str) {
183
+ return HASHER.h64(str)
188
184
  }
189
185
 
190
- const encoder = new globalThis.TextEncoder()
191
- const buffer = new Uint8Array(8 * 3)
192
- const view = new DataView(buffer.buffer)
193
-
194
- export function hashNameBigInt(name) {
195
- if (name.length >= 8) {
196
- return HASHER.h64(name)
197
- }
186
+ export function h64ToString(str) {
187
+ return HASHER.h64ToString(str)
188
+ }
198
189
 
199
- const { written } = encoder.encodeInto(name, buffer)
200
- return written === 8 ? view.getBigUint64(0, false) : HASHER.h64Raw(buffer.subarray(0, written))
190
+ export function h64Raw(str) {
191
+ return HASHER.h64Raw(str)
201
192
  }