@nxtedition/deepstream.io-client-js 26.0.12 → 27.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/package.json
CHANGED
|
@@ -170,7 +170,7 @@ Connection.prototype._sendAuthParams = function () {
|
|
|
170
170
|
this._setState(C.CONNECTION_STATE.AUTHENTICATING)
|
|
171
171
|
const authMessage = messageBuilder.getMsg(C.TOPIC.AUTH, C.ACTIONS.REQUEST, [
|
|
172
172
|
this._authParams,
|
|
173
|
-
'
|
|
173
|
+
'27.0.0',
|
|
174
174
|
utils.isNode
|
|
175
175
|
? `Node/${process.version}`
|
|
176
176
|
: globalThis.navigator && globalThis.navigator.userAgent,
|
|
@@ -1,25 +1,100 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
|
+
import varint from 'varint'
|
|
3
|
+
import * as utils from '../utils/utils.js'
|
|
2
4
|
|
|
3
|
-
const
|
|
5
|
+
const poolEncoder = new globalThis.TextEncoder()
|
|
6
|
+
|
|
7
|
+
let poolSize
|
|
8
|
+
let poolBuffer
|
|
9
|
+
let poolView
|
|
10
|
+
let poolOffset
|
|
11
|
+
|
|
12
|
+
function allocPool(size) {
|
|
13
|
+
poolSize = size || poolSize || 1024 * 1024
|
|
14
|
+
poolBuffer = utils.isNode
|
|
15
|
+
? globalThis.Buffer.allocUnsafe(poolSize)
|
|
16
|
+
: new Uint8Array(new ArrayBuffer(poolSize))
|
|
17
|
+
poolView = new DataView(poolBuffer.buffer)
|
|
18
|
+
poolOffset = 0
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function alignPool() {
|
|
22
|
+
// Ensure aligned slices
|
|
23
|
+
if (poolOffset & 0x7) {
|
|
24
|
+
poolOffset |= 0x7
|
|
25
|
+
poolOffset++
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeString(dst, str, offset) {
|
|
30
|
+
if (utils.isNode) {
|
|
31
|
+
return dst.write(str, offset)
|
|
32
|
+
} else {
|
|
33
|
+
const res = poolEncoder.encodeInto(str, new Uint8Array(dst.buffer, offset))
|
|
34
|
+
return res.written
|
|
35
|
+
}
|
|
36
|
+
}
|
|
4
37
|
|
|
5
38
|
export function getMsg(topic, action, data) {
|
|
6
39
|
if (data && !(data instanceof Array)) {
|
|
7
40
|
throw new Error('data must be an array')
|
|
8
41
|
}
|
|
9
42
|
|
|
10
|
-
|
|
43
|
+
if (!poolSize || poolOffset + poolSize / 16 >= poolSize) {
|
|
44
|
+
allocPool()
|
|
45
|
+
} else {
|
|
46
|
+
alignPool()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const start = poolOffset
|
|
50
|
+
|
|
51
|
+
const headerSize = 8
|
|
52
|
+
poolBuffer[poolOffset++] = 128 + headerSize
|
|
53
|
+
let headerPos = poolOffset
|
|
54
|
+
poolOffset += headerSize - 1
|
|
55
|
+
|
|
56
|
+
poolBuffer[poolOffset++] = topic.charCodeAt(0)
|
|
57
|
+
poolBuffer[poolOffset++] = 31
|
|
58
|
+
for (let n = 0; n < action.length; n++) {
|
|
59
|
+
poolBuffer[poolOffset++] = action.charCodeAt(n)
|
|
60
|
+
}
|
|
11
61
|
|
|
12
62
|
if (data) {
|
|
13
63
|
for (let i = 0; i < data.length; i++) {
|
|
14
|
-
|
|
15
|
-
|
|
64
|
+
const type = typeof data[i]
|
|
65
|
+
let len
|
|
66
|
+
if (data[i] == null) {
|
|
67
|
+
poolBuffer[poolOffset++] = 31
|
|
68
|
+
len = 0
|
|
69
|
+
} else if (type === 'object') {
|
|
70
|
+
poolBuffer[poolOffset++] = 31
|
|
71
|
+
len = writeString(poolBuffer, JSON.stringify(data[i]), poolOffset)
|
|
72
|
+
} else if (type === 'bigint') {
|
|
73
|
+
poolBuffer[poolOffset++] = 31
|
|
74
|
+
poolView.setBigUint64(poolOffset, data[i], false)
|
|
75
|
+
len = 8
|
|
76
|
+
} else if (type === 'string') {
|
|
77
|
+
poolBuffer[poolOffset++] = 31
|
|
78
|
+
len = writeString(poolBuffer, data[i], poolOffset)
|
|
16
79
|
} else {
|
|
17
|
-
|
|
80
|
+
throw new Error('invalid data')
|
|
81
|
+
}
|
|
82
|
+
poolOffset += len
|
|
83
|
+
|
|
84
|
+
varint.encode(len + 1, poolBuffer, headerPos)
|
|
85
|
+
headerPos += varint.encode.bytes
|
|
86
|
+
if (headerPos - start >= headerSize) {
|
|
87
|
+
throw new Error(`header too large: ${headerPos - start} ${headerSize}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (poolOffset >= poolBuffer.length) {
|
|
91
|
+
allocPool(start === 0 ? poolSize * 2 : poolSize)
|
|
92
|
+
return getMsg(topic, action, data)
|
|
18
93
|
}
|
|
19
94
|
}
|
|
20
95
|
}
|
|
21
96
|
|
|
22
|
-
return
|
|
97
|
+
return new Uint8Array(poolBuffer.buffer, start, poolOffset - start)
|
|
23
98
|
}
|
|
24
99
|
|
|
25
100
|
export function typed(value) {
|
package/src/record/record.js
CHANGED
|
@@ -11,11 +11,16 @@ 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
|
+
|
|
14
18
|
const connection = handler._connection
|
|
15
19
|
|
|
16
20
|
this._handler = handler
|
|
17
21
|
|
|
18
22
|
this._name = name
|
|
23
|
+
this._key = utils.hashNameBigInt(name)
|
|
19
24
|
this._version = ''
|
|
20
25
|
this._data = jsonPath.EMPTY
|
|
21
26
|
this._state = C.RECORD_STATE.VOID
|
|
@@ -24,7 +29,10 @@ export default class Record {
|
|
|
24
29
|
this._emitting = false
|
|
25
30
|
/** @type Map? */ this._updating = null
|
|
26
31
|
/** @type Array? */ this._patching = null
|
|
27
|
-
this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [
|
|
32
|
+
this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [
|
|
33
|
+
this._name,
|
|
34
|
+
this._key,
|
|
35
|
+
])
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
/** @type {string} */
|
|
@@ -32,6 +40,11 @@ export default class Record {
|
|
|
32
40
|
return this._name
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
/** @type {bigint} */
|
|
44
|
+
get key() {
|
|
45
|
+
return this._key
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
/** @type {string} */
|
|
36
49
|
get version() {
|
|
37
50
|
return this._version
|
|
@@ -62,7 +75,8 @@ export default class Record {
|
|
|
62
75
|
if (this._refs === 1) {
|
|
63
76
|
this._handler._onPruning(this, false)
|
|
64
77
|
this._subscribed =
|
|
65
|
-
this._subscribed ||
|
|
78
|
+
this._subscribed ||
|
|
79
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name, this._key])
|
|
66
80
|
}
|
|
67
81
|
return this
|
|
68
82
|
}
|
|
@@ -324,7 +338,8 @@ export default class Record {
|
|
|
324
338
|
|
|
325
339
|
if (connected) {
|
|
326
340
|
this._subscribed =
|
|
327
|
-
this._refs > 0 &&
|
|
341
|
+
this._refs > 0 &&
|
|
342
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._key, this._name])
|
|
328
343
|
|
|
329
344
|
if (this._updating) {
|
|
330
345
|
for (const update of this._updating.values()) {
|
|
@@ -349,7 +364,7 @@ export default class Record {
|
|
|
349
364
|
invariant(!this._updating, 'must not have updates')
|
|
350
365
|
|
|
351
366
|
if (this._subscribed) {
|
|
352
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this.
|
|
367
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this._key])
|
|
353
368
|
this._subscribed = false
|
|
354
369
|
}
|
|
355
370
|
|
|
@@ -371,7 +386,7 @@ export default class Record {
|
|
|
371
386
|
const prevVersion = this._version
|
|
372
387
|
const nextVersion = this._makeVersion(parseInt(prevVersion) + 1)
|
|
373
388
|
|
|
374
|
-
const update = [this.
|
|
389
|
+
const update = [this._key, nextVersion, jsonPath.stringify(nextData), prevVersion]
|
|
375
390
|
|
|
376
391
|
if (!this._updating) {
|
|
377
392
|
this._onUpdating(true)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
import * as rxjs from 'rxjs'
|
|
3
|
+
import * as utils from '../utils/utils.js'
|
|
3
4
|
|
|
4
5
|
class Listener {
|
|
5
6
|
constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {
|
|
@@ -47,8 +48,15 @@ class Listener {
|
|
|
47
48
|
|
|
48
49
|
const name = message.data[1]
|
|
49
50
|
|
|
51
|
+
if (!name?.length) {
|
|
52
|
+
this._error(name, 'invalid message')
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const key = utils.hashNameBigInt(name)
|
|
57
|
+
|
|
50
58
|
if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND) {
|
|
51
|
-
if (this._subscriptions.has(
|
|
59
|
+
if (this._subscriptions.has(key)) {
|
|
52
60
|
this._error(name, 'invalid add: listener exists')
|
|
53
61
|
return
|
|
54
62
|
}
|
|
@@ -56,6 +64,7 @@ class Listener {
|
|
|
56
64
|
// TODO (refactor): Move to class
|
|
57
65
|
const provider = {
|
|
58
66
|
name,
|
|
67
|
+
key,
|
|
59
68
|
value$: null,
|
|
60
69
|
sending: false,
|
|
61
70
|
accepted: false,
|
|
@@ -68,7 +77,7 @@ class Listener {
|
|
|
68
77
|
if (this.connected && provider.accepted) {
|
|
69
78
|
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [
|
|
70
79
|
this._pattern,
|
|
71
|
-
provider.
|
|
80
|
+
provider.key,
|
|
72
81
|
])
|
|
73
82
|
}
|
|
74
83
|
|
|
@@ -101,7 +110,7 @@ class Listener {
|
|
|
101
110
|
this._connection.sendMsg(
|
|
102
111
|
this._topic,
|
|
103
112
|
accepted ? C.ACTIONS.LISTEN_ACCEPT : C.ACTIONS.LISTEN_REJECT,
|
|
104
|
-
[this._pattern, provider.
|
|
113
|
+
[this._pattern, provider.key],
|
|
105
114
|
)
|
|
106
115
|
|
|
107
116
|
provider.version = null
|
|
@@ -151,13 +160,13 @@ class Listener {
|
|
|
151
160
|
}
|
|
152
161
|
|
|
153
162
|
const body = typeof value !== 'string' ? this._stringify(value) : value
|
|
154
|
-
const hash =
|
|
163
|
+
const hash = utils.h64ToString(body)
|
|
155
164
|
const version = `INF-${hash}`
|
|
156
165
|
|
|
157
166
|
if (provider.version !== version) {
|
|
158
167
|
provider.version = version
|
|
159
168
|
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, [
|
|
160
|
-
provider.
|
|
169
|
+
provider.key,
|
|
161
170
|
version,
|
|
162
171
|
body,
|
|
163
172
|
])
|
|
@@ -181,9 +190,9 @@ class Listener {
|
|
|
181
190
|
|
|
182
191
|
provider.start()
|
|
183
192
|
|
|
184
|
-
this._subscriptions.set(provider.
|
|
193
|
+
this._subscriptions.set(provider.key, provider)
|
|
185
194
|
} else if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
|
|
186
|
-
const provider = this._subscriptions.get(
|
|
195
|
+
const provider = this._subscriptions.get(key)
|
|
187
196
|
if (!provider?.value$) {
|
|
188
197
|
return
|
|
189
198
|
}
|
|
@@ -195,13 +204,13 @@ class Listener {
|
|
|
195
204
|
provider.valueSubscription = provider.value$.subscribe(provider.observer)
|
|
196
205
|
}
|
|
197
206
|
} else if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED) {
|
|
198
|
-
const provider = this._subscriptions.get(
|
|
207
|
+
const provider = this._subscriptions.get(key)
|
|
199
208
|
|
|
200
209
|
if (!provider) {
|
|
201
210
|
this._error(name, 'invalid remove: listener missing')
|
|
202
211
|
} else {
|
|
203
212
|
provider.stop()
|
|
204
|
-
this._subscriptions.delete(provider.
|
|
213
|
+
this._subscriptions.delete(provider.key)
|
|
205
214
|
}
|
|
206
215
|
} else {
|
|
207
216
|
return false
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
import * as rxjs from 'rxjs'
|
|
3
|
+
import * as utils from '../utils/utils.js'
|
|
3
4
|
|
|
4
5
|
const PIPE = rxjs.pipe(
|
|
5
6
|
rxjs.map((value) => {
|
|
@@ -54,8 +55,15 @@ class Listener {
|
|
|
54
55
|
_$onMessage(message) {
|
|
55
56
|
const name = message.data[1]
|
|
56
57
|
|
|
58
|
+
if (!name?.length) {
|
|
59
|
+
this._error(name, 'invalid message')
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const key = utils.hashNameBigInt(name)
|
|
64
|
+
|
|
57
65
|
if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
|
|
58
|
-
if (this._subscriptions.has(
|
|
66
|
+
if (this._subscriptions.has(key)) {
|
|
59
67
|
this._error(name, 'invalid accept: listener exists')
|
|
60
68
|
return
|
|
61
69
|
}
|
|
@@ -71,29 +79,29 @@ class Listener {
|
|
|
71
79
|
const subscription = value$.pipe(PIPE).subscribe({
|
|
72
80
|
next: (data) => {
|
|
73
81
|
if (data == null) {
|
|
74
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern,
|
|
75
|
-
this._subscriptions.delete(
|
|
82
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
|
|
83
|
+
this._subscriptions.delete(key)
|
|
76
84
|
subscription.unsubscribe()
|
|
77
85
|
} else {
|
|
78
|
-
const version = `INF-${
|
|
79
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [
|
|
86
|
+
const version = `INF-${utils.h64ToString(data)}`
|
|
87
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [key, version, data])
|
|
80
88
|
}
|
|
81
89
|
},
|
|
82
90
|
error: (err) => {
|
|
83
91
|
this._error(name, err)
|
|
84
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern,
|
|
85
|
-
this._subscriptions.delete(
|
|
92
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
|
|
93
|
+
this._subscriptions.delete(key)
|
|
86
94
|
},
|
|
87
95
|
})
|
|
88
|
-
this._subscriptions.set(
|
|
96
|
+
this._subscriptions.set(key, subscription)
|
|
89
97
|
} else {
|
|
90
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern,
|
|
98
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, key])
|
|
91
99
|
}
|
|
92
100
|
} else if (message.action === C.ACTIONS.LISTEN_REJECT) {
|
|
93
|
-
const subscription = this._subscriptions.get(
|
|
101
|
+
const subscription = this._subscriptions.get(key)
|
|
94
102
|
|
|
95
103
|
if (subscription) {
|
|
96
|
-
this._subscriptions.delete(
|
|
104
|
+
this._subscriptions.delete(key)
|
|
97
105
|
subscription.unsubscribe()
|
|
98
106
|
} else {
|
|
99
107
|
this._error(name, 'invalid remove: listener missing')
|
package/src/utils/utils.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import xxhash from 'xxhash-wasm'
|
|
2
|
+
|
|
3
|
+
const HASHER = await xxhash()
|
|
4
|
+
|
|
1
5
|
const NODE_ENV = typeof process !== 'undefined' && process.env && process.env.NODE_ENV
|
|
2
6
|
export const isNode = typeof process !== 'undefined' && process.toString() === '[object process]'
|
|
3
7
|
export const isProduction = NODE_ENV === 'production'
|
|
@@ -174,3 +178,24 @@ export function removeAbortListener(signal, handler) {
|
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
180
|
}
|
|
181
|
+
|
|
182
|
+
export function h64(name) {
|
|
183
|
+
return HASHER.h64(name)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function h64ToString(name) {
|
|
187
|
+
return HASHER.h64ToString(name)
|
|
188
|
+
}
|
|
189
|
+
|
|
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
|
+
}
|
|
198
|
+
|
|
199
|
+
const { written } = encoder.encodeInto(name, buffer)
|
|
200
|
+
return written === 8 ? view.getBigUint64(0, false) : HASHER.h64Raw(buffer.subarray(0, written))
|
|
201
|
+
}
|