@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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type RecordHandler from './record-handler.js'
|
|
2
|
+
import type { EmptyObject, SingleKeyObject } from 'type-fest'
|
|
3
|
+
|
|
4
|
+
type Paths<T> = keyof T
|
|
5
|
+
type Get<Data, Path extends string> = Path extends keyof Data ? Data[Path] : unknown
|
|
6
|
+
|
|
7
|
+
export type { EmptyObject } from 'type-fest'
|
|
8
|
+
|
|
9
|
+
// When getting, for convenience, we say the data might be partial under some
|
|
10
|
+
// circumstances.
|
|
11
|
+
//
|
|
12
|
+
// When you e.g. do record.get or record.update, there is always a possibility
|
|
13
|
+
// that the data object is empty. The naive correct type for that would be
|
|
14
|
+
// `Data | EmptyObject`. However, that forces the user to always type guard
|
|
15
|
+
// against the empty object case. This type tries to allow the user to skip
|
|
16
|
+
// that check in some cases, where it should be safe to do so.
|
|
17
|
+
export type GettablePossibleEmpty<Data> = keyof Data extends never
|
|
18
|
+
? EmptyObject // If there are no keys at all
|
|
19
|
+
: Partial<Data> extends Data
|
|
20
|
+
? // All properties in Data are already optional, so we can safely return it
|
|
21
|
+
// as is. The user just need to check the properties themselves instead.
|
|
22
|
+
Data
|
|
23
|
+
: SingleKeyObject<Data> extends never
|
|
24
|
+
? // There are more than one property in Data, and some of them are
|
|
25
|
+
// required. That means that the user must always check for the empty
|
|
26
|
+
// object case.
|
|
27
|
+
Data | EmptyObject
|
|
28
|
+
: // There is exactly one property in Data, and it is required. In this
|
|
29
|
+
// particular case, we can safely use Data as the "empty" type, but
|
|
30
|
+
// with the single property turned optional.
|
|
31
|
+
{
|
|
32
|
+
[K in keyof Data]+?: Data[K]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// When setting the data must fully adhere to the Data type, or exactly an
|
|
36
|
+
// empty object.
|
|
37
|
+
export type SettablePossibleEmpty<Data> = Data | EmptyObject
|
|
38
|
+
|
|
39
|
+
export interface WhenOptions {
|
|
40
|
+
timeout?: number
|
|
41
|
+
signal?: AbortSignal
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface UpdateOptions {
|
|
45
|
+
signal?: AbortSignal
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default class Record<Data> {
|
|
49
|
+
constructor(name: string, handler: RecordHandler)
|
|
50
|
+
|
|
51
|
+
readonly name: string
|
|
52
|
+
readonly version: string
|
|
53
|
+
readonly data: GettablePossibleEmpty<Data>
|
|
54
|
+
readonly state: number
|
|
55
|
+
readonly refs: number
|
|
56
|
+
|
|
57
|
+
ref(): Record<Data>
|
|
58
|
+
unref(): Record<Data>
|
|
59
|
+
subscribe(callback: (record: Record<Data>) => void, opaque?: unknown): Record<Data>
|
|
60
|
+
unsubscribe(callback: (record: Record<Data>) => void, opaque?: unknown): Record<Data>
|
|
61
|
+
|
|
62
|
+
get: {
|
|
63
|
+
// with path
|
|
64
|
+
<Path extends Paths<Data>, DataAtPath extends Get<Data, Path> = Get<Data, Path>>(
|
|
65
|
+
path: Path,
|
|
66
|
+
): DataAtPath | undefined
|
|
67
|
+
// without path
|
|
68
|
+
(): GettablePossibleEmpty<Data>
|
|
69
|
+
// implementation
|
|
70
|
+
<Path extends Paths<Data>, DataAtPath extends Get<Data, Path> = Get<Data, Path>>(
|
|
71
|
+
path?: Path,
|
|
72
|
+
): Path extends undefined ? GettablePossibleEmpty<Data> : DataAtPath | undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
set: {
|
|
76
|
+
// with path
|
|
77
|
+
<Path extends Paths<Data>, DataAtPath extends Get<Data, Path>>(
|
|
78
|
+
path: Path,
|
|
79
|
+
dataAtPath: DataAtPath,
|
|
80
|
+
): void
|
|
81
|
+
// without path
|
|
82
|
+
(data: SettablePossibleEmpty<Data>): void
|
|
83
|
+
// implementation
|
|
84
|
+
<Path extends Paths<Data>, DataAtPath extends Get<Data, Path>>(
|
|
85
|
+
...args: [pathOrData: Path | SettablePossibleEmpty<Data>, value?: DataAtPath]
|
|
86
|
+
): void
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
when: {
|
|
90
|
+
(): Promise<Record<Data>>
|
|
91
|
+
(state: number): Promise<Record<Data>>
|
|
92
|
+
(options: WhenOptions): Promise<Record<Data>>
|
|
93
|
+
(state: number, options: WhenOptions): Promise<Record<Data>>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
update<
|
|
97
|
+
Path extends Paths<Data>,
|
|
98
|
+
PathOrUpdater extends
|
|
99
|
+
| Path
|
|
100
|
+
| ((data: Readonly<GettablePossibleEmpty<Data>>) => SettablePossibleEmpty<Data>),
|
|
101
|
+
>(
|
|
102
|
+
...args: PathOrUpdater extends Path
|
|
103
|
+
? [
|
|
104
|
+
path: Path,
|
|
105
|
+
updater: (dataAtPath: Readonly<Get<Data, Path>> | undefined) => Get<Data, Path>,
|
|
106
|
+
options?: UpdateOptions,
|
|
107
|
+
]
|
|
108
|
+
: [updater: PathOrUpdater, options?: UpdateOptions]
|
|
109
|
+
): Promise<void>
|
|
110
|
+
}
|
package/src/record/record.js
CHANGED
|
@@ -15,7 +15,6 @@ class Record {
|
|
|
15
15
|
|
|
16
16
|
this._handler = handler
|
|
17
17
|
this._name = name
|
|
18
|
-
this._key = utils.h64ToString(name)
|
|
19
18
|
this._version = ''
|
|
20
19
|
this._data = jsonPath.EMPTY
|
|
21
20
|
this._state = C.RECORD_STATE.VOID
|
|
@@ -25,15 +24,7 @@ class Record {
|
|
|
25
24
|
|
|
26
25
|
/** @type Map? */ this._updating = null
|
|
27
26
|
/** @type Array? */ this._patching = null
|
|
28
|
-
this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [
|
|
29
|
-
this._key,
|
|
30
|
-
this._name,
|
|
31
|
-
])
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** @type { string} */
|
|
35
|
-
get key() {
|
|
36
|
-
return this._key
|
|
27
|
+
this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
/** @type {string} */
|
|
@@ -71,8 +62,7 @@ class Record {
|
|
|
71
62
|
if (this._refs === 1) {
|
|
72
63
|
this._handler._onPruning(this, false)
|
|
73
64
|
this._subscribed =
|
|
74
|
-
this._subscribed ||
|
|
75
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._key, this._name])
|
|
65
|
+
this._subscribed || connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
76
66
|
}
|
|
77
67
|
return this
|
|
78
68
|
}
|
|
@@ -88,6 +78,10 @@ class Record {
|
|
|
88
78
|
return this
|
|
89
79
|
}
|
|
90
80
|
|
|
81
|
+
[Symbol.dispose]() {
|
|
82
|
+
this.unref()
|
|
83
|
+
}
|
|
84
|
+
|
|
91
85
|
/**
|
|
92
86
|
* @param {*} fn
|
|
93
87
|
* @param {*} opaque
|
|
@@ -308,7 +302,7 @@ class Record {
|
|
|
308
302
|
.then(() => {
|
|
309
303
|
const prev = this.get(path)
|
|
310
304
|
const next = updater(prev, this._version)
|
|
311
|
-
if (prev !== next) {
|
|
305
|
+
if (prev !== next && (path || next != null)) {
|
|
312
306
|
this.set(path, next)
|
|
313
307
|
}
|
|
314
308
|
})
|
|
@@ -334,8 +328,7 @@ class Record {
|
|
|
334
328
|
|
|
335
329
|
if (connected) {
|
|
336
330
|
this._subscribed =
|
|
337
|
-
this._refs > 0 &&
|
|
338
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._key, this._name])
|
|
331
|
+
this._refs > 0 && connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
339
332
|
|
|
340
333
|
if (this._updating) {
|
|
341
334
|
for (const update of this._updating.values()) {
|
|
@@ -360,7 +353,7 @@ class Record {
|
|
|
360
353
|
invariant(!this._updating, 'must not have updates')
|
|
361
354
|
|
|
362
355
|
if (this._subscribed) {
|
|
363
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this.
|
|
356
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this._name])
|
|
364
357
|
this._subscribed = false
|
|
365
358
|
}
|
|
366
359
|
|
|
@@ -382,7 +375,9 @@ class Record {
|
|
|
382
375
|
const prevVersion = this._version
|
|
383
376
|
const nextVersion = this._makeVersion(parseInt(prevVersion) + 1)
|
|
384
377
|
|
|
385
|
-
const update = [this.
|
|
378
|
+
const update = [this._name, nextVersion, jsonPath.stringify(nextData), prevVersion]
|
|
379
|
+
|
|
380
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, update)
|
|
386
381
|
|
|
387
382
|
if (!this._updating) {
|
|
388
383
|
this._onUpdating(true)
|
|
@@ -394,8 +389,6 @@ class Record {
|
|
|
394
389
|
throw new Error('invalid state')
|
|
395
390
|
}
|
|
396
391
|
|
|
397
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, update)
|
|
398
|
-
|
|
399
392
|
this._data = nextData
|
|
400
393
|
this._version = nextVersion
|
|
401
394
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import RpcResponse from './rpc-response.js'
|
|
2
|
+
|
|
3
|
+
export type RpcMethodDef = [arguments: unknown, response: unknown]
|
|
4
|
+
|
|
5
|
+
export default class RpcHandler<Methods extends Record<string, RpcMethodDef>> {
|
|
6
|
+
connected: boolean
|
|
7
|
+
stats: RpcStats
|
|
8
|
+
|
|
9
|
+
provide: <Name extends keyof Methods>(
|
|
10
|
+
name: Name,
|
|
11
|
+
callback: (args: Methods[Name][0], response: RpcResponse<Methods[Name][1]>) => void,
|
|
12
|
+
) => UnprovideFn
|
|
13
|
+
|
|
14
|
+
unprovide: <Name extends keyof Methods>(name: Name) => void
|
|
15
|
+
|
|
16
|
+
make: {
|
|
17
|
+
<
|
|
18
|
+
Name extends keyof Methods | string,
|
|
19
|
+
Args extends Name extends keyof Methods ? Methods[Name][0] : unknown,
|
|
20
|
+
ReturnValue extends Name extends keyof Methods ? Methods[Name][1] : unknown,
|
|
21
|
+
>(
|
|
22
|
+
name: Name,
|
|
23
|
+
args: Args,
|
|
24
|
+
): Promise<ReturnValue>
|
|
25
|
+
<
|
|
26
|
+
Name extends keyof Methods | string,
|
|
27
|
+
Args extends Name extends keyof Methods ? Methods[Name][0] : unknown,
|
|
28
|
+
ReturnValue extends Name extends keyof Methods ? Methods[Name][1] : unknown,
|
|
29
|
+
>(
|
|
30
|
+
name: Name,
|
|
31
|
+
args: Args,
|
|
32
|
+
callback: (error: unknown, response: ReturnValue) => void,
|
|
33
|
+
): void
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type UnprovideFn = () => void
|
|
38
|
+
|
|
39
|
+
export interface RpcStats {
|
|
40
|
+
listeners: number
|
|
41
|
+
rpcs: number
|
|
42
|
+
}
|
|
@@ -1,176 +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'
|
|
4
|
-
import * as timers from '../utils/timers.js'
|
|
5
|
-
|
|
6
|
-
class Provider {
|
|
7
|
-
#sending = false
|
|
8
|
-
#accepted = false
|
|
9
|
-
#value$ = null
|
|
10
|
-
#name
|
|
11
|
-
#key
|
|
12
|
-
#version
|
|
13
|
-
#timeout
|
|
14
|
-
#valueSubscription
|
|
15
|
-
#patternSubscription
|
|
16
|
-
#observer
|
|
17
|
-
#listener
|
|
18
|
-
|
|
19
|
-
constructor(name, listener) {
|
|
20
|
-
this.#name = name
|
|
21
|
-
this.#key = h64ToString(name)
|
|
22
|
-
this.#listener = listener
|
|
23
|
-
this.#observer = {
|
|
24
|
-
next: (value) => {
|
|
25
|
-
if (value == null) {
|
|
26
|
-
this.next(null) // TODO (fix): This is weird...
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (this.#listener._topic === C.TOPIC.EVENT) {
|
|
31
|
-
this.#listener._handler.emit(this.#name, value)
|
|
32
|
-
} else if (this.#listener._topic === C.TOPIC.RECORD) {
|
|
33
|
-
if (typeof value !== 'object' && typeof value !== 'string') {
|
|
34
|
-
this.#listener._error(this.#name, 'invalid value')
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const body = typeof value !== 'string' ? this.#listener._stringify(value) : value
|
|
39
|
-
const hash = h64ToString(body)
|
|
40
|
-
const version = `INF-${hash}`
|
|
41
|
-
|
|
42
|
-
if (this.#version !== version) {
|
|
43
|
-
this.#version = version
|
|
44
|
-
this.#listener._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, [
|
|
45
|
-
this.#key,
|
|
46
|
-
version,
|
|
47
|
-
body,
|
|
48
|
-
])
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
error: (err) => {
|
|
53
|
-
this.error(err)
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
this.#start()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
dispose() {
|
|
61
|
-
this.#stop()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
accept() {
|
|
65
|
-
if (!this.#value$) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (this.#valueSubscription) {
|
|
70
|
-
this.#listener._error(this.#name, 'invalid accept: listener started')
|
|
71
|
-
} else {
|
|
72
|
-
// TODO (fix): provider.version = message.data[2]
|
|
73
|
-
this.#valueSubscription = this.#value$.subscribe(this.#observer)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
reject() {
|
|
78
|
-
this.#listener._error(this.#name, 'invalid reject: not implemented')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
next(value$) {
|
|
82
|
-
if (!value$) {
|
|
83
|
-
value$ = null
|
|
84
|
-
} else if (typeof value$.subscribe !== 'function') {
|
|
85
|
-
value$ = rxjs.of(value$) // Compat for recursive with value
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (Boolean(this.#value$) !== Boolean(value$) && !this.#sending) {
|
|
89
|
-
this.#sending = true
|
|
90
|
-
// TODO (fix): Why async?
|
|
91
|
-
queueMicrotask(() => {
|
|
92
|
-
this.#sending = false
|
|
93
|
-
|
|
94
|
-
if (!this.#patternSubscription) {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const accepted = Boolean(this.#value$)
|
|
99
|
-
if (this.#accepted === accepted) {
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.#listener._connection.sendMsg(
|
|
104
|
-
this.#listener._topic,
|
|
105
|
-
accepted ? C.ACTIONS.LISTEN_ACCEPT : C.ACTIONS.LISTEN_REJECT,
|
|
106
|
-
[this.#listener._pattern, this.#key],
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
this.#version = null
|
|
110
|
-
this.#accepted = accepted
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this.#value$ = value$
|
|
115
|
-
|
|
116
|
-
if (this.#valueSubscription) {
|
|
117
|
-
this.#valueSubscription.unsubscribe()
|
|
118
|
-
this.#valueSubscription = this.#value$?.subscribe(this.#observer)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
error(err) {
|
|
123
|
-
this.#stop()
|
|
124
|
-
// TODO (feat): backoff retryCount * delay?
|
|
125
|
-
// TODO (feat): backoff option?
|
|
126
|
-
this.#timeout = timers.setTimeout(
|
|
127
|
-
(provider) => {
|
|
128
|
-
provider.start()
|
|
129
|
-
},
|
|
130
|
-
10e3,
|
|
131
|
-
this,
|
|
132
|
-
)
|
|
133
|
-
this.#listener._error(this.#name, err)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
#start() {
|
|
137
|
-
try {
|
|
138
|
-
const ret$ = this.#listener._callback(this.#name)
|
|
139
|
-
if (this.#listener._recursive && typeof ret$?.subscribe === 'function') {
|
|
140
|
-
this.patternSubscription = ret$.subscribe(this)
|
|
141
|
-
} else {
|
|
142
|
-
this.patternSubscription = rxjs.of(ret$).subscribe(this)
|
|
143
|
-
}
|
|
144
|
-
} catch (err) {
|
|
145
|
-
this.#listener._error(this.#name, err)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
#stop() {
|
|
150
|
-
if (this.#listener.connected && this.#accepted) {
|
|
151
|
-
this.#listener._connection.sendMsg(this.#listener._topic, C.ACTIONS.LISTEN_REJECT, [
|
|
152
|
-
this.#listener._pattern,
|
|
153
|
-
this.#key,
|
|
154
|
-
])
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
this.#value$ = null
|
|
158
|
-
this.#version = null
|
|
159
|
-
this.#accepted = false
|
|
160
|
-
this.#sending = false
|
|
161
|
-
|
|
162
|
-
if (this.#timeout) {
|
|
163
|
-
timers.clearTimeout(this.#timeout)
|
|
164
|
-
this.#timeout = null
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
this.#patternSubscription?.unsubscribe()
|
|
168
|
-
this.#patternSubscription = null
|
|
169
|
-
|
|
170
|
-
this.#valueSubscription?.unsubscribe()
|
|
171
|
-
this.#valueSubscription = null
|
|
172
|
-
}
|
|
173
|
-
}
|
|
3
|
+
import { h64ToString, findBigIntPaths } from '../utils/utils.js'
|
|
174
4
|
|
|
175
5
|
export default class Listener {
|
|
176
6
|
constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {
|
|
@@ -221,20 +51,173 @@ export default class Listener {
|
|
|
221
51
|
if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND) {
|
|
222
52
|
if (this._subscriptions.has(name)) {
|
|
223
53
|
this._error(name, 'invalid add: listener exists')
|
|
224
|
-
|
|
225
|
-
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// TODO (refactor): Move to class
|
|
58
|
+
const provider = {
|
|
59
|
+
name,
|
|
60
|
+
value$: null,
|
|
61
|
+
sending: false,
|
|
62
|
+
accepted: false,
|
|
63
|
+
version: null,
|
|
64
|
+
timeout: null,
|
|
65
|
+
patternSubscription: null,
|
|
66
|
+
valueSubscription: null,
|
|
67
|
+
}
|
|
68
|
+
provider.stop = () => {
|
|
69
|
+
if (this.connected && provider.accepted) {
|
|
70
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [
|
|
71
|
+
this._pattern,
|
|
72
|
+
provider.name,
|
|
73
|
+
])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
provider.value$ = null
|
|
77
|
+
provider.version = null
|
|
78
|
+
provider.accepted = false
|
|
79
|
+
provider.sending = false
|
|
80
|
+
|
|
81
|
+
clearTimeout(provider.timeout)
|
|
82
|
+
provider.timeout = null
|
|
83
|
+
|
|
84
|
+
provider.patternSubscription?.unsubscribe()
|
|
85
|
+
provider.patternSubscription = null
|
|
86
|
+
|
|
87
|
+
provider.valueSubscription?.unsubscribe()
|
|
88
|
+
provider.valueSubscription = null
|
|
226
89
|
}
|
|
90
|
+
provider.send = () => {
|
|
91
|
+
provider.sending = false
|
|
92
|
+
|
|
93
|
+
if (!provider.patternSubscription) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const accepted = Boolean(provider.value$)
|
|
98
|
+
if (provider.accepted === accepted) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this._connection.sendMsg(
|
|
103
|
+
this._topic,
|
|
104
|
+
accepted ? C.ACTIONS.LISTEN_ACCEPT : C.ACTIONS.LISTEN_REJECT,
|
|
105
|
+
[this._pattern, provider.name],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
provider.version = null
|
|
109
|
+
provider.accepted = accepted
|
|
110
|
+
}
|
|
111
|
+
provider.next = (value$) => {
|
|
112
|
+
if (!value$) {
|
|
113
|
+
value$ = null
|
|
114
|
+
} else if (typeof value$.subscribe !== 'function') {
|
|
115
|
+
value$ = rxjs.of(value$) // Compat for recursive with value
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (Boolean(provider.value$) !== Boolean(value$) && !provider.sending) {
|
|
119
|
+
provider.sending = true
|
|
120
|
+
queueMicrotask(provider.send)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
provider.value$ = value$
|
|
124
|
+
|
|
125
|
+
if (provider.valueSubscription) {
|
|
126
|
+
provider.valueSubscription.unsubscribe()
|
|
127
|
+
provider.valueSubscription = provider.value$?.subscribe(provider.observer)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
provider.error = (err) => {
|
|
131
|
+
provider.stop()
|
|
132
|
+
// TODO (feat): backoff retryCount * delay?
|
|
133
|
+
// TODO (feat): backoff option?
|
|
134
|
+
provider.timeout = setTimeout(() => {
|
|
135
|
+
provider.start()
|
|
136
|
+
}, 10e3)
|
|
137
|
+
this._error(provider.name, err)
|
|
138
|
+
}
|
|
139
|
+
provider.observer = {
|
|
140
|
+
next: (value) => {
|
|
141
|
+
if (value == null) {
|
|
142
|
+
provider.next(null) // TODO (fix): This is weird...
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (this._topic === C.TOPIC.EVENT) {
|
|
147
|
+
this._handler.emit(provider.name, value)
|
|
148
|
+
} else if (this._topic === C.TOPIC.RECORD) {
|
|
149
|
+
if (typeof value !== 'object' && typeof value !== 'string') {
|
|
150
|
+
this._error(provider.name, 'invalid value')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (typeof value !== 'string') {
|
|
155
|
+
try {
|
|
156
|
+
value = this._stringify(value)
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const bigIntPaths = /BigInt/.test(err.message) ? findBigIntPaths(value) : undefined
|
|
159
|
+
this._error(
|
|
160
|
+
Object.assign(new Error(`invalid value: ${value}`), {
|
|
161
|
+
cause: err,
|
|
162
|
+
data: { name: provider.name, bigIntPaths },
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const body = value
|
|
170
|
+
const hash = h64ToString(body)
|
|
171
|
+
const version = `INF-${hash}`
|
|
172
|
+
|
|
173
|
+
if (provider.version !== version) {
|
|
174
|
+
provider.version = version
|
|
175
|
+
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, [
|
|
176
|
+
provider.name,
|
|
177
|
+
version,
|
|
178
|
+
body,
|
|
179
|
+
])
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
error: provider.error,
|
|
184
|
+
}
|
|
185
|
+
provider.start = () => {
|
|
186
|
+
try {
|
|
187
|
+
const ret$ = this._callback(name)
|
|
188
|
+
if (this._recursive && typeof ret$?.subscribe === 'function') {
|
|
189
|
+
provider.patternSubscription = ret$.subscribe(provider)
|
|
190
|
+
} else {
|
|
191
|
+
provider.patternSubscription = rxjs.of(ret$).subscribe(provider)
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
this._error(provider.name, err)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
provider.start()
|
|
199
|
+
|
|
200
|
+
this._subscriptions.set(provider.name, provider)
|
|
227
201
|
} else if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
|
|
228
|
-
this._subscriptions.get(name)?.accept()
|
|
229
|
-
} else if (message.action === C.ACTIONS.LISTEN_REJECT) {
|
|
230
|
-
this._subscriptions.get(name)?.reject()
|
|
231
|
-
} else if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED) {
|
|
232
202
|
const provider = this._subscriptions.get(name)
|
|
233
|
-
if (provider) {
|
|
234
|
-
|
|
235
|
-
|
|
203
|
+
if (!provider?.value$) {
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (provider.valueSubscription) {
|
|
208
|
+
this._error(name, 'invalid accept: listener started')
|
|
236
209
|
} else {
|
|
210
|
+
// TODO (fix): provider.version = message.data[2]
|
|
211
|
+
provider.valueSubscription = provider.value$.subscribe(provider.observer)
|
|
212
|
+
}
|
|
213
|
+
} else if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED) {
|
|
214
|
+
const provider = this._subscriptions.get(name)
|
|
215
|
+
|
|
216
|
+
if (!provider) {
|
|
237
217
|
this._error(name, 'invalid remove: listener missing')
|
|
218
|
+
} else {
|
|
219
|
+
provider.stop()
|
|
220
|
+
this._subscriptions.delete(provider.name)
|
|
238
221
|
}
|
|
239
222
|
} else {
|
|
240
223
|
return false
|
|
@@ -256,7 +239,7 @@ export default class Listener {
|
|
|
256
239
|
|
|
257
240
|
_reset() {
|
|
258
241
|
for (const provider of this._subscriptions.values()) {
|
|
259
|
-
provider.
|
|
242
|
+
provider.stop()
|
|
260
243
|
}
|
|
261
244
|
this._subscriptions.clear()
|
|
262
245
|
}
|