@nxtedition/deepstream.io-client-js 32.0.22 → 32.0.24
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 +1 -1
- package/src/client.d.ts +59 -5
- package/src/client.test-d.ts +96 -2
- package/src/event/event-handler.d.ts +11 -2
- package/src/event/event-handler.js +5 -5
- package/src/message/connection.js +11 -3
- package/src/record/record-handler.d.ts +117 -21
- package/src/record/record-handler.js +8 -23
- package/src/record/record.d.ts +25 -36
- package/src/rpc/rpc-handler.d.ts +7 -4
- package/src/rpc/rpc-handler.test-d.ts +40 -0
- package/src/rpc/rpc-response.d.ts +1 -0
- package/src/utils/multicast-listener.js +252 -0
- package/src/utils/utils.js +0 -8
package/package.json
CHANGED
package/src/client.d.ts
CHANGED
|
@@ -3,14 +3,25 @@ import type { Paths, Get } from './record/record.js'
|
|
|
3
3
|
import type RecordHandler from './record/record-handler.js'
|
|
4
4
|
import type { RecordStats, ProvideOptions, SyncOptions } from './record/record-handler.js'
|
|
5
5
|
import type EventHandler from './event/event-handler.js'
|
|
6
|
-
import type { EventStats } from './event/event-handler.js'
|
|
6
|
+
import type { EventStats, EventProvideOptions } from './event/event-handler.js'
|
|
7
7
|
import type RpcHandler from './rpc/rpc-handler.js'
|
|
8
8
|
import type { RpcStats, RpcMethodDef } from './rpc/rpc-handler.js'
|
|
9
9
|
|
|
10
|
+
export interface DeepstreamClientOptions {
|
|
11
|
+
reconnectIntervalIncrement?: number
|
|
12
|
+
maxReconnectInterval?: number
|
|
13
|
+
maxReconnectAttempts?: number
|
|
14
|
+
maxPacketSize?: number
|
|
15
|
+
batchSize?: number
|
|
16
|
+
schedule?: ((fn: () => void) => void) | null
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
logger?: any
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
export default function <
|
|
11
22
|
Records extends Record<string, unknown> = Record<string, unknown>,
|
|
12
23
|
Methods extends Record<string, RpcMethodDef> = Record<string, RpcMethodDef>,
|
|
13
|
-
>(url: string, options?:
|
|
24
|
+
>(url: string, options?: DeepstreamClientOptions): DeepstreamClient<Records, Methods>
|
|
14
25
|
|
|
15
26
|
export type {
|
|
16
27
|
DsRecord,
|
|
@@ -19,9 +30,12 @@ export type {
|
|
|
19
30
|
RpcHandler,
|
|
20
31
|
RpcMethodDef,
|
|
21
32
|
ProvideOptions,
|
|
33
|
+
EventProvideOptions,
|
|
22
34
|
SyncOptions,
|
|
23
35
|
Paths,
|
|
24
36
|
Get,
|
|
37
|
+
ConnectionStateName,
|
|
38
|
+
DeepstreamErrorEventName,
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
type RecordStateConstants = Readonly<{
|
|
@@ -77,6 +91,36 @@ type EventConstants = Readonly<{
|
|
|
77
91
|
}>
|
|
78
92
|
type EventKey = keyof EventConstants
|
|
79
93
|
type EventName = EventConstants[EventKey]
|
|
94
|
+
type DeepstreamErrorEventName = Exclude<
|
|
95
|
+
EventName,
|
|
96
|
+
'connectionStateChanged' | 'connected' | 'MAX_RECONNECTION_ATTEMPTS_REACHED'
|
|
97
|
+
>
|
|
98
|
+
|
|
99
|
+
export interface DeepstreamError extends Error {
|
|
100
|
+
topic?: string
|
|
101
|
+
event?: EventName | null
|
|
102
|
+
data?: unknown
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface DeepstreamMessage {
|
|
106
|
+
raw: string | null
|
|
107
|
+
topic: string | null
|
|
108
|
+
action: string | null
|
|
109
|
+
data: string[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface DeepstreamClientEventMap {
|
|
113
|
+
connectionStateChanged: (state: ConnectionStateName) => void
|
|
114
|
+
connected: (connected: boolean) => void
|
|
115
|
+
MAX_RECONNECTION_ATTEMPTS_REACHED: (attempt: number) => void
|
|
116
|
+
error: (error: DeepstreamError) => void
|
|
117
|
+
recv: (message: DeepstreamMessage) => void
|
|
118
|
+
send: (message: DeepstreamMessage) => void
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type DeepstreamErrorEventMap = {
|
|
122
|
+
[K in DeepstreamErrorEventName]: (error: DeepstreamError) => void
|
|
123
|
+
}
|
|
80
124
|
|
|
81
125
|
export interface DeepstreamClient<
|
|
82
126
|
Records extends Record<string, unknown> = Record<string, unknown>,
|
|
@@ -87,11 +131,21 @@ export interface DeepstreamClient<
|
|
|
87
131
|
rpc: RpcHandler<Methods>
|
|
88
132
|
record: RecordHandler<Records>
|
|
89
133
|
user: string | null
|
|
90
|
-
on
|
|
91
|
-
|
|
134
|
+
on<K extends keyof (DeepstreamClientEventMap & DeepstreamErrorEventMap)>(
|
|
135
|
+
evt: K,
|
|
136
|
+
callback: (DeepstreamClientEventMap & DeepstreamErrorEventMap)[K],
|
|
137
|
+
): this
|
|
138
|
+
off<K extends keyof (DeepstreamClientEventMap & DeepstreamErrorEventMap)>(
|
|
139
|
+
evt: K,
|
|
140
|
+
callback: (DeepstreamClientEventMap & DeepstreamErrorEventMap)[K],
|
|
141
|
+
): this
|
|
92
142
|
getConnectionState: () => ConnectionStateName
|
|
93
143
|
close: () => void
|
|
94
|
-
login: unknown
|
|
144
|
+
login(callback: (success: boolean, authData: unknown) => void): this
|
|
145
|
+
login(
|
|
146
|
+
authParams: Record<string, unknown>,
|
|
147
|
+
callback: (success: boolean, authData: unknown) => void,
|
|
148
|
+
): this
|
|
95
149
|
stats: {
|
|
96
150
|
record: RecordStats
|
|
97
151
|
rpc: RpcStats
|
package/src/client.test-d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import make, { type DeepstreamClient } from './client.js'
|
|
2
|
+
import make, { type DeepstreamClient, type DeepstreamError } from './client.js'
|
|
3
3
|
import { expectAssignable, expectError, expectType } from 'tsd'
|
|
4
4
|
import type { Observable } from 'rxjs'
|
|
5
|
+
import type { EmptyObject } from 'type-fest'
|
|
5
6
|
|
|
6
7
|
interface Records extends Record<string, unknown> {
|
|
7
8
|
o: {
|
|
@@ -22,6 +23,11 @@ interface Records extends Record<string, unknown> {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
}
|
|
26
|
+
possiblyEmpty:
|
|
27
|
+
| {
|
|
28
|
+
pe1: string
|
|
29
|
+
}
|
|
30
|
+
| EmptyObject
|
|
25
31
|
c: Circular
|
|
26
32
|
m: {
|
|
27
33
|
m1: string
|
|
@@ -62,7 +68,7 @@ expectAssignable<{ n0?: { n1: { n2: { n3: string } } } } | undefined>(await ds.r
|
|
|
62
68
|
expectAssignable<{ n1: { n2: { n3: string } } } | undefined>(await ds.record.get('n', 'n0'))
|
|
63
69
|
|
|
64
70
|
// set withouth path
|
|
65
|
-
ds.record.set('
|
|
71
|
+
ds.record.set('possiblyEmpty', {}) // empty should always work
|
|
66
72
|
ds.record.set('n', { n0: { n1: { n2: { n3: 'test' } } } })
|
|
67
73
|
expectError(ds.record.set('n', { n0: {} })) // nested props are required
|
|
68
74
|
|
|
@@ -96,10 +102,14 @@ expectError(ds.record.set('n', 'n1.x2', {}))
|
|
|
96
102
|
expectError(ds.record.set('n', 'n1.n2.n3', { n4: 22 }))
|
|
97
103
|
|
|
98
104
|
expectAssignable<string>(await ds.record.get('p', 'p1'))
|
|
105
|
+
expectAssignable<{ name: string; version: string; state: number; data: string }>(
|
|
106
|
+
await ds.record.get2('p', 'p1'),
|
|
107
|
+
)
|
|
99
108
|
expectAssignable<string>(await ds.record.get('p', 'p1', { signal: new AbortController().signal }))
|
|
100
109
|
expectAssignable<string>(await ds.record.get('p', { path: 'p1' }))
|
|
101
110
|
expectAssignable<string | undefined>(await ds.record.get('p', 'p2'))
|
|
102
111
|
expectAssignable<unknown>(await ds.record.get('p', 'x1'))
|
|
112
|
+
expectAssignable<string | undefined>(await ds.record.get('possiblyEmpty', 'pe1'))
|
|
103
113
|
|
|
104
114
|
// observe with options
|
|
105
115
|
expectAssignable<Observable<{ p1: string; p2?: string; p3: { p4: string } }>>(
|
|
@@ -179,6 +189,16 @@ expectAssignable<Promise<void>>(
|
|
|
179
189
|
)
|
|
180
190
|
expectAssignable<Promise<void>>(ds.record.update('p', 'p1', (data) => data, { timeout: 5000 }))
|
|
181
191
|
|
|
192
|
+
// update: updater receives version as second argument
|
|
193
|
+
ds.record.update('p', (data, version) => {
|
|
194
|
+
expectType<string>(version)
|
|
195
|
+
return data
|
|
196
|
+
})
|
|
197
|
+
ds.record.update('p', 'p1', (data, version) => {
|
|
198
|
+
expectType<string>(version)
|
|
199
|
+
return data
|
|
200
|
+
})
|
|
201
|
+
|
|
182
202
|
// Circular
|
|
183
203
|
expectAssignable<string | undefined>(await ds.record.get('c', 'a.b1'))
|
|
184
204
|
|
|
@@ -210,6 +230,19 @@ expectAssignable<Promise<typeof rec>>(rec.when({ state: 2, timeout: 5000 }))
|
|
|
210
230
|
expectAssignable<Promise<typeof rec>>(rec.when(2, { timeout: 5000 }))
|
|
211
231
|
expectAssignable<Promise<typeof rec>>(rec.when(2, { signal: new AbortController().signal }))
|
|
212
232
|
|
|
233
|
+
// Record.subscribe: callback receives (record, opaque)
|
|
234
|
+
rec.subscribe((record, opaque) => {
|
|
235
|
+
expectType<typeof rec>(record)
|
|
236
|
+
expectType<unknown>(opaque)
|
|
237
|
+
})
|
|
238
|
+
rec.subscribe((record, opaque) => {}, 'my-opaque-token')
|
|
239
|
+
|
|
240
|
+
// Record.unsubscribe: same callback signature
|
|
241
|
+
rec.unsubscribe((record, opaque) => {
|
|
242
|
+
expectType<typeof rec>(record)
|
|
243
|
+
expectType<unknown>(opaque)
|
|
244
|
+
})
|
|
245
|
+
|
|
213
246
|
// Record.update with options
|
|
214
247
|
expectAssignable<Promise<void>>(rec.update((x) => x, { signal: new AbortController().signal }))
|
|
215
248
|
expectAssignable<Promise<void>>(rec.update((x) => x, { timeout: 5000 }))
|
|
@@ -220,7 +253,68 @@ expectAssignable<Promise<void>>(
|
|
|
220
253
|
expectAssignable<Promise<void>>(rec.update('o0', (x) => x, { timeout: 5000 }))
|
|
221
254
|
expectAssignable<Promise<void>>(rec.update('o0', (x) => x, { state: 2 }))
|
|
222
255
|
|
|
256
|
+
// Record.update: updater receives version as second argument
|
|
257
|
+
rec.update((x, version) => {
|
|
258
|
+
expectType<string>(version)
|
|
259
|
+
return x
|
|
260
|
+
})
|
|
261
|
+
rec.update('o0', (x, version) => {
|
|
262
|
+
expectType<string>(version)
|
|
263
|
+
return x
|
|
264
|
+
})
|
|
265
|
+
|
|
223
266
|
const state = 'VOID'
|
|
224
267
|
expectType<0>(ds.record.STATE[state])
|
|
225
268
|
const unknownState: string = 'VOID'
|
|
226
269
|
expectType<number>(ds.record.STATE[unknownState])
|
|
270
|
+
|
|
271
|
+
// record.getRecord: [Symbol.dispose] is present
|
|
272
|
+
const recDispose = ds.record.getRecord('o')
|
|
273
|
+
recDispose[Symbol.dispose]()
|
|
274
|
+
|
|
275
|
+
// record.provide: returns Disposer | void
|
|
276
|
+
expectAssignable<(() => void) | void>(ds.record.provide('pattern*', () => ({})))
|
|
277
|
+
const recordDisposer = ds.record.provide('pattern*', () => ({}))
|
|
278
|
+
if (recordDisposer) {
|
|
279
|
+
recordDisposer()
|
|
280
|
+
recordDisposer[Symbol.dispose]()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// event.provide: returns (() => void) | void
|
|
284
|
+
expectAssignable<(() => void) | void>(ds.event.provide('pattern*', () => {}, {}))
|
|
285
|
+
|
|
286
|
+
// client.on/off: 'error' is a valid event name
|
|
287
|
+
ds.on('error', (err) => {})
|
|
288
|
+
ds.off('error', (err) => {})
|
|
289
|
+
expectError(ds.on('unknownEvent', () => {}))
|
|
290
|
+
|
|
291
|
+
// client.on: callback arg types per event
|
|
292
|
+
ds.on('error', (err) => {
|
|
293
|
+
expectType<DeepstreamError>(err)
|
|
294
|
+
})
|
|
295
|
+
ds.on('connectionError', (err) => {
|
|
296
|
+
expectType<DeepstreamError>(err)
|
|
297
|
+
})
|
|
298
|
+
ds.on('connectionStateChanged', (state) => {
|
|
299
|
+
expectType<
|
|
300
|
+
| 'CLOSED'
|
|
301
|
+
| 'AWAITING_CONNECTION'
|
|
302
|
+
| 'CHALLENGING'
|
|
303
|
+
| 'AWAITING_AUTHENTICATION'
|
|
304
|
+
| 'AUTHENTICATING'
|
|
305
|
+
| 'OPEN'
|
|
306
|
+
| 'ERROR'
|
|
307
|
+
| 'RECONNECTING'
|
|
308
|
+
>(state)
|
|
309
|
+
})
|
|
310
|
+
ds.on('connected', (connected) => {
|
|
311
|
+
expectType<boolean>(connected)
|
|
312
|
+
})
|
|
313
|
+
ds.on('MAX_RECONNECTION_ATTEMPTS_REACHED', (attempt) => {
|
|
314
|
+
expectType<number>(attempt)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// client.on: wrong callback arg types are errors
|
|
318
|
+
expectError(ds.on('connectionStateChanged', (state: number) => {}))
|
|
319
|
+
expectError(ds.on('connected', (connected: string) => {}))
|
|
320
|
+
expectError(ds.on('MAX_RECONNECTION_ATTEMPTS_REACHED', (attempt: string) => {}))
|
|
@@ -4,13 +4,22 @@ export default class EventHandler {
|
|
|
4
4
|
connected: boolean
|
|
5
5
|
stats: EventStats
|
|
6
6
|
subscribe: (name: string, callback: (data: unknown) => void) => void
|
|
7
|
-
unsubscribe: (name: string, callback
|
|
7
|
+
unsubscribe: (name: string, callback?: (data: unknown) => void) => void
|
|
8
8
|
on: (name: string, callback: (data: unknown) => void) => this
|
|
9
9
|
once: (name: string, callback: (data: unknown) => void) => this
|
|
10
10
|
off: (name: string, callback: (data: unknown) => void) => this
|
|
11
11
|
observe: <Data>(name: string) => Observable<Data>
|
|
12
12
|
emit: <Data>(name: string, data?: Data) => void
|
|
13
|
-
provide: (
|
|
13
|
+
provide: (
|
|
14
|
+
pattern: string,
|
|
15
|
+
callback: (name: string) => void,
|
|
16
|
+
options: EventProvideOptions,
|
|
17
|
+
) => (() => void) | void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EventProvideOptions {
|
|
21
|
+
mode?: 'unicast' | (string & {})
|
|
22
|
+
stringify?: ((input: unknown) => string) | null
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
export interface EventStats {
|
|
@@ -71,21 +71,21 @@ EventHandler.prototype.unsubscribe = function (name, callback) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
EventHandler.on = function (name, callback) {
|
|
74
|
+
EventHandler.prototype.on = function (name, callback) {
|
|
75
75
|
this.subscribe(name, callback)
|
|
76
76
|
return this
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
EventHandler.once = function (name, callback) {
|
|
79
|
+
EventHandler.prototype.once = function (name, callback) {
|
|
80
80
|
const fn = (...args) => {
|
|
81
|
-
this.unsubscribe(fn)
|
|
82
|
-
callback(...args)
|
|
81
|
+
this.unsubscribe(name, fn)
|
|
82
|
+
callback(name, ...args)
|
|
83
83
|
}
|
|
84
84
|
this.subscribe(name, fn)
|
|
85
85
|
return this
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
EventHandler.off = function (name, callback) {
|
|
88
|
+
EventHandler.prototype.off = function (name, callback) {
|
|
89
89
|
this.unsubscribe(name, callback)
|
|
90
90
|
return this
|
|
91
91
|
}
|
|
@@ -28,7 +28,6 @@ export default function Connection(client, url, options) {
|
|
|
28
28
|
action: null,
|
|
29
29
|
data: null,
|
|
30
30
|
}
|
|
31
|
-
this._decoder = new globalThis.TextDecoder()
|
|
32
31
|
this._recvQueue = new FixedQueue()
|
|
33
32
|
this._reconnectTimeout = null
|
|
34
33
|
this._reconnectionAttempt = 0
|
|
@@ -96,9 +95,20 @@ Connection.prototype._createEndpoint = function () {
|
|
|
96
95
|
generateMask() {},
|
|
97
96
|
})
|
|
98
97
|
this._endpoint.binaryType = 'nodebuffer'
|
|
98
|
+
this._endpoint.onmessage = ({ data: buf }) => {
|
|
99
|
+
this._onMessage(buf.toString('utf8', buf[0] >= 128 ? buf[0] - 128 : 0, buf.length))
|
|
100
|
+
}
|
|
99
101
|
} else {
|
|
102
|
+
const decoder = new globalThis.TextDecoder()
|
|
100
103
|
this._endpoint = new BrowserWebSocket(this._url)
|
|
101
104
|
this._endpoint.binaryType = 'arraybuffer'
|
|
105
|
+
this._endpoint.onmessage = ({ data }) => {
|
|
106
|
+
let buf = new Uint8Array(data)
|
|
107
|
+
if (buf[0] >= 128) {
|
|
108
|
+
buf = buf.subarray(buf[0] - 128)
|
|
109
|
+
}
|
|
110
|
+
this._onMessage(decoder.decode(buf))
|
|
111
|
+
}
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
this._corked = false
|
|
@@ -106,8 +116,6 @@ Connection.prototype._createEndpoint = function () {
|
|
|
106
116
|
this._endpoint.onopen = this._onOpen.bind(this)
|
|
107
117
|
this._endpoint.onerror = this._onError.bind(this)
|
|
108
118
|
this._endpoint.onclose = this._onClose.bind(this)
|
|
109
|
-
this._endpoint.onmessage = ({ data }) =>
|
|
110
|
-
this._onMessage(typeof data === 'string' ? data : this._decoder.decode(data))
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
Connection.prototype.send = function (message) {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { Observable } from 'rxjs'
|
|
2
2
|
import type DsRecord from './record.js'
|
|
3
|
-
import type {
|
|
4
|
-
EmptyObject,
|
|
5
|
-
Get,
|
|
6
|
-
UpdateOptions,
|
|
7
|
-
ObserveOptions,
|
|
8
|
-
ObserveOptionsWithPath,
|
|
9
|
-
} from './record.js'
|
|
3
|
+
import type { Get, UpdateOptions, ObserveOptions, ObserveOptionsWithPath } from './record.js'
|
|
10
4
|
|
|
11
5
|
type Lookup<Table, Name> = Name extends keyof Table ? Table[Name] : unknown
|
|
12
6
|
|
|
@@ -32,8 +26,8 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
JSON: {
|
|
35
|
-
EMPTY:
|
|
36
|
-
EMPTY_OBJ:
|
|
29
|
+
EMPTY: Record<string, unknown>
|
|
30
|
+
EMPTY_OBJ: Record<string, unknown>
|
|
37
31
|
EMPTY_ARR: []
|
|
38
32
|
}
|
|
39
33
|
|
|
@@ -46,13 +40,44 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
46
40
|
pattern: string,
|
|
47
41
|
callback: (key: string) => unknown,
|
|
48
42
|
optionsOrRecursive?: ProvideOptions | boolean,
|
|
49
|
-
) => Disposer
|
|
43
|
+
) => Disposer | void
|
|
44
|
+
|
|
45
|
+
put: (
|
|
46
|
+
name: string,
|
|
47
|
+
version: string,
|
|
48
|
+
data: Record<string, unknown> | null,
|
|
49
|
+
parent?: string,
|
|
50
|
+
) => void
|
|
51
|
+
|
|
52
|
+
getAsync: {
|
|
53
|
+
<Name extends string>(
|
|
54
|
+
name: Name,
|
|
55
|
+
options: ObserveOptions,
|
|
56
|
+
):
|
|
57
|
+
| { value: Lookup<Records, Name>; async: false }
|
|
58
|
+
| { value: Promise<Lookup<Records, Name>>; async: true }
|
|
59
|
+
|
|
60
|
+
<Name extends string, Path extends string | string[]>(
|
|
61
|
+
name: Name,
|
|
62
|
+
path: Path,
|
|
63
|
+
options?: ObserveOptions,
|
|
64
|
+
):
|
|
65
|
+
| { value: Get<Lookup<Records, Name>, Path>; async: false }
|
|
66
|
+
| { value: Promise<Get<Lookup<Records, Name>, Path>>; async: true }
|
|
67
|
+
|
|
68
|
+
<Name extends string>(
|
|
69
|
+
name: Name,
|
|
70
|
+
state?: number,
|
|
71
|
+
):
|
|
72
|
+
| { value: Lookup<Records, Name>; async: false }
|
|
73
|
+
| { value: Promise<Lookup<Records, Name>>; async: true }
|
|
74
|
+
}
|
|
50
75
|
|
|
51
76
|
sync: (options?: SyncOptions) => Promise<void>
|
|
52
77
|
|
|
53
78
|
set: {
|
|
54
79
|
// without path:
|
|
55
|
-
<Name extends string>(name: Name, data: Lookup<Records, Name>
|
|
80
|
+
<Name extends string>(name: Name, data: Lookup<Records, Name>): void
|
|
56
81
|
|
|
57
82
|
// with path:
|
|
58
83
|
<Name extends string, Path extends string | string[]>(
|
|
@@ -67,14 +92,17 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
67
92
|
update: {
|
|
68
93
|
<Name extends string>(
|
|
69
94
|
name: Name,
|
|
70
|
-
updater: (data: Lookup<Records, Name
|
|
95
|
+
updater: (data: Lookup<Records, Name>, version: string) => Lookup<Records, Name>,
|
|
71
96
|
options?: UpdateOptions,
|
|
72
97
|
): Promise<void>
|
|
73
98
|
|
|
74
99
|
<Name extends string, Path extends string | string[]>(
|
|
75
100
|
name: Name,
|
|
76
101
|
path: Path,
|
|
77
|
-
updater: (
|
|
102
|
+
updater: (
|
|
103
|
+
data: Get<Lookup<Records, Name>, Path>,
|
|
104
|
+
version: string,
|
|
105
|
+
) => Get<Lookup<Records, Name>, Path>,
|
|
78
106
|
options?: UpdateOptions,
|
|
79
107
|
): Promise<void>
|
|
80
108
|
}
|
|
@@ -113,6 +141,73 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
113
141
|
): Observable<Get<Lookup<Records, Name>, Path>>
|
|
114
142
|
}
|
|
115
143
|
|
|
144
|
+
observe2: {
|
|
145
|
+
<Name extends string>(
|
|
146
|
+
name: Name,
|
|
147
|
+
options: ObserveOptions,
|
|
148
|
+
): Observable<{
|
|
149
|
+
name: string
|
|
150
|
+
version: string
|
|
151
|
+
state: number
|
|
152
|
+
data: Lookup<Records, Name>
|
|
153
|
+
}>
|
|
154
|
+
|
|
155
|
+
<Name extends string, Path extends string | string[]>(
|
|
156
|
+
name: Name,
|
|
157
|
+
options: ObserveOptionsWithPath<Path>,
|
|
158
|
+
): Observable<{
|
|
159
|
+
name: string
|
|
160
|
+
version: string
|
|
161
|
+
state: number
|
|
162
|
+
data: Get<Lookup<Records, Name>, Path>
|
|
163
|
+
}>
|
|
164
|
+
|
|
165
|
+
<Name extends string>(
|
|
166
|
+
name: Name,
|
|
167
|
+
state?: number,
|
|
168
|
+
options?: ObserveOptions,
|
|
169
|
+
): Observable<{
|
|
170
|
+
name: string
|
|
171
|
+
version: string
|
|
172
|
+
state: number
|
|
173
|
+
data: Lookup<Records, Name>
|
|
174
|
+
}>
|
|
175
|
+
|
|
176
|
+
<Name extends string, Path extends string | string[]>(
|
|
177
|
+
name: Name,
|
|
178
|
+
state?: number,
|
|
179
|
+
options?: ObserveOptionsWithPath<Path>,
|
|
180
|
+
): Observable<{
|
|
181
|
+
name: string
|
|
182
|
+
version: string
|
|
183
|
+
state: number
|
|
184
|
+
data: Get<Lookup<Records, Name>, Path>
|
|
185
|
+
}>
|
|
186
|
+
|
|
187
|
+
<Name extends string, Path extends string | string[]>(
|
|
188
|
+
name: Name,
|
|
189
|
+
path: Path,
|
|
190
|
+
options?: ObserveOptionsWithPath<Path>,
|
|
191
|
+
): Observable<{
|
|
192
|
+
name: string
|
|
193
|
+
version: string
|
|
194
|
+
state: number
|
|
195
|
+
data: Get<Lookup<Records, Name>, Path>
|
|
196
|
+
}>
|
|
197
|
+
|
|
198
|
+
<Name extends string, Path extends string | string[]>(
|
|
199
|
+
name: Name,
|
|
200
|
+
path: Path,
|
|
201
|
+
state?: number,
|
|
202
|
+
options?: ObserveOptionsWithPath<Path>,
|
|
203
|
+
): Observable<{
|
|
204
|
+
name: string
|
|
205
|
+
version: string
|
|
206
|
+
state: number
|
|
207
|
+
data: Get<Lookup<Records, Name>, Path>
|
|
208
|
+
}>
|
|
209
|
+
}
|
|
210
|
+
|
|
116
211
|
get: {
|
|
117
212
|
<Name extends string>(name: Name, options: ObserveOptions): Promise<Lookup<Records, Name>>
|
|
118
213
|
|
|
@@ -147,11 +242,11 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
147
242
|
): Promise<Get<Lookup<Records, Name>, Path>>
|
|
148
243
|
}
|
|
149
244
|
|
|
150
|
-
|
|
245
|
+
get2: {
|
|
151
246
|
<Name extends string>(
|
|
152
247
|
name: Name,
|
|
153
248
|
options: ObserveOptions,
|
|
154
|
-
):
|
|
249
|
+
): Promise<{
|
|
155
250
|
name: string
|
|
156
251
|
version: string
|
|
157
252
|
state: number
|
|
@@ -161,7 +256,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
161
256
|
<Name extends string, Path extends string | string[]>(
|
|
162
257
|
name: Name,
|
|
163
258
|
options: ObserveOptionsWithPath<Path>,
|
|
164
|
-
):
|
|
259
|
+
): Promise<{
|
|
165
260
|
name: string
|
|
166
261
|
version: string
|
|
167
262
|
state: number
|
|
@@ -172,7 +267,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
172
267
|
name: Name,
|
|
173
268
|
state?: number,
|
|
174
269
|
options?: ObserveOptions,
|
|
175
|
-
):
|
|
270
|
+
): Promise<{
|
|
176
271
|
name: string
|
|
177
272
|
version: string
|
|
178
273
|
state: number
|
|
@@ -183,7 +278,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
183
278
|
name: Name,
|
|
184
279
|
state?: number,
|
|
185
280
|
options?: ObserveOptionsWithPath<Path>,
|
|
186
|
-
):
|
|
281
|
+
): Promise<{
|
|
187
282
|
name: string
|
|
188
283
|
version: string
|
|
189
284
|
state: number
|
|
@@ -194,7 +289,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
194
289
|
name: Name,
|
|
195
290
|
path: Path,
|
|
196
291
|
options?: ObserveOptionsWithPath<Path>,
|
|
197
|
-
):
|
|
292
|
+
): Promise<{
|
|
198
293
|
name: string
|
|
199
294
|
version: string
|
|
200
295
|
state: number
|
|
@@ -206,7 +301,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
|
|
|
206
301
|
path: Path,
|
|
207
302
|
state?: number,
|
|
208
303
|
options?: ObserveOptionsWithPath<Path>,
|
|
209
|
-
):
|
|
304
|
+
): Promise<{
|
|
210
305
|
name: string
|
|
211
306
|
version: string
|
|
212
307
|
state: number
|
|
@@ -223,12 +318,13 @@ export interface RecordStats {
|
|
|
223
318
|
pruning: number
|
|
224
319
|
patching: number
|
|
225
320
|
subscriptions: number
|
|
321
|
+
listeners: number
|
|
226
322
|
}
|
|
227
323
|
|
|
228
324
|
export interface ProvideOptions {
|
|
229
325
|
recursive?: boolean
|
|
230
326
|
stringify?: ((input: unknown) => string) | null
|
|
231
|
-
mode
|
|
327
|
+
mode?: null | 'unicast' | (string & {})
|
|
232
328
|
}
|
|
233
329
|
|
|
234
330
|
export interface SyncOptions {
|
|
@@ -3,7 +3,6 @@ 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'
|
|
6
|
-
import invariant from 'invariant'
|
|
7
6
|
import jsonPath from '@nxtedition/json-path'
|
|
8
7
|
import * as utils from '../utils/utils.js'
|
|
9
8
|
import xuid from 'xuid'
|
|
@@ -121,10 +120,6 @@ class RecordHandler {
|
|
|
121
120
|
this._stats = {
|
|
122
121
|
updating: 0,
|
|
123
122
|
created: 0,
|
|
124
|
-
destroyed: 0,
|
|
125
|
-
records: 0,
|
|
126
|
-
pruning: 0,
|
|
127
|
-
patching: 0,
|
|
128
123
|
}
|
|
129
124
|
|
|
130
125
|
this._syncQueue = []
|
|
@@ -161,12 +156,6 @@ class RecordHandler {
|
|
|
161
156
|
}
|
|
162
157
|
|
|
163
158
|
_onPruning(rec, value) {
|
|
164
|
-
if (value) {
|
|
165
|
-
this._stats.pruning += 1
|
|
166
|
-
} else {
|
|
167
|
-
this._stats.pruning -= 1
|
|
168
|
-
}
|
|
169
|
-
|
|
170
159
|
if (value) {
|
|
171
160
|
this._pruning.add(rec)
|
|
172
161
|
} else {
|
|
@@ -175,16 +164,10 @@ class RecordHandler {
|
|
|
175
164
|
}
|
|
176
165
|
|
|
177
166
|
_onUpdating(rec, value) {
|
|
178
|
-
const callbacks = this._updating.get(rec)
|
|
179
|
-
|
|
180
167
|
if (value) {
|
|
181
|
-
invariant(!callbacks, 'updating callbacks must not exist')
|
|
182
|
-
this._stats.updating += 1
|
|
183
168
|
this._updating.set(rec, [])
|
|
184
169
|
} else {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this._stats.updating -= 1
|
|
170
|
+
const callbacks = this._updating.get(rec)
|
|
188
171
|
this._updating.delete(rec)
|
|
189
172
|
for (const callback of callbacks) {
|
|
190
173
|
callback()
|
|
@@ -194,11 +177,8 @@ class RecordHandler {
|
|
|
194
177
|
|
|
195
178
|
_onPatching(rec, value) {
|
|
196
179
|
if (value) {
|
|
197
|
-
this._stats.patching += 1
|
|
198
180
|
this._patching.set(rec, [])
|
|
199
181
|
} else {
|
|
200
|
-
this._stats.patching -= 1
|
|
201
|
-
|
|
202
182
|
const callbacks = this._patching.get(rec)
|
|
203
183
|
this._patching.delete(rec)
|
|
204
184
|
for (const callback of callbacks) {
|
|
@@ -213,13 +193,18 @@ class RecordHandler {
|
|
|
213
193
|
|
|
214
194
|
get stats() {
|
|
215
195
|
let subscriptions = 0
|
|
216
|
-
for (const
|
|
217
|
-
subscriptions +=
|
|
196
|
+
for (const { stats } of this._listeners.values()) {
|
|
197
|
+
subscriptions += stats.subscriptions ?? 0
|
|
218
198
|
}
|
|
219
199
|
|
|
220
200
|
return {
|
|
221
201
|
...this._stats,
|
|
222
202
|
subscriptions,
|
|
203
|
+
patching: this._patching.size,
|
|
204
|
+
updating: this._updating.size,
|
|
205
|
+
putting: this._putting.size,
|
|
206
|
+
records: this._records.size,
|
|
207
|
+
listeners: this._listeners.size,
|
|
223
208
|
}
|
|
224
209
|
}
|
|
225
210
|
|
package/src/record/record.d.ts
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
1
|
import type RecordHandler from './record-handler.js'
|
|
2
|
-
import type { Get
|
|
3
|
-
export type {
|
|
2
|
+
import type { Get as _Get, AllUnionFields } from 'type-fest'
|
|
3
|
+
export type { Paths } from 'type-fest'
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export type GettablePossibleEmpty<Data> = keyof Data extends never
|
|
14
|
-
? EmptyObject // If there are no keys at all
|
|
15
|
-
: Partial<Data> extends Data
|
|
16
|
-
? // All properties in Data are already optional, so we can safely return it
|
|
17
|
-
// as is. The user just need to check the properties themselves instead.
|
|
18
|
-
Data
|
|
19
|
-
: SingleKeyObject<Data> extends never
|
|
20
|
-
? // There are more than one property in Data, and some of them are
|
|
21
|
-
// required. That means that the user must always check for the empty
|
|
22
|
-
// object case.
|
|
23
|
-
Data | EmptyObject
|
|
24
|
-
: // There is exactly one property in Data, and it is required. In this
|
|
25
|
-
// particular case, we can safely use Data as the "empty" type, but
|
|
26
|
-
// with the single property turned optional.
|
|
27
|
-
{
|
|
28
|
-
[K in keyof Data]+?: Data[K]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// When setting the data must fully adhere to the Data type, or exactly an
|
|
32
|
-
// empty object.
|
|
33
|
-
export type SettablePossibleEmpty<Data> = Data | EmptyObject
|
|
5
|
+
// HACK: Wrap type-fest's Get to get rid of EmptyObject from union
|
|
6
|
+
type RemoveSymbolKeys<T> = {
|
|
7
|
+
[K in keyof T as K extends symbol ? never : K]: T[K]
|
|
8
|
+
}
|
|
9
|
+
export type Get<BaseType, Path extends string | readonly string[]> = _Get<
|
|
10
|
+
RemoveSymbolKeys<AllUnionFields<BaseType>>,
|
|
11
|
+
Path
|
|
12
|
+
>
|
|
34
13
|
|
|
35
14
|
export interface WhenOptions {
|
|
36
15
|
signal?: AbortSignal
|
|
@@ -45,6 +24,7 @@ export interface UpdateOptions {
|
|
|
45
24
|
}
|
|
46
25
|
|
|
47
26
|
export interface ObserveOptions {
|
|
27
|
+
key?: string
|
|
48
28
|
signal?: AbortSignal
|
|
49
29
|
timeout?: number
|
|
50
30
|
state?: number
|
|
@@ -67,12 +47,21 @@ export default class Record<Data = unknown> {
|
|
|
67
47
|
|
|
68
48
|
ref(): Record<Data>
|
|
69
49
|
unref(): Record<Data>
|
|
70
|
-
|
|
71
|
-
|
|
50
|
+
[Symbol.dispose](): void
|
|
51
|
+
subscribe(
|
|
52
|
+
callback: (record: Record<Data>, opaque: unknown) => void,
|
|
53
|
+
opaque?: unknown,
|
|
54
|
+
): Record<Data>
|
|
55
|
+
unsubscribe(
|
|
56
|
+
callback: (record: Record<Data>, opaque: unknown) => void,
|
|
57
|
+
opaque?: unknown,
|
|
58
|
+
): Record<Data>
|
|
72
59
|
|
|
73
60
|
get: {
|
|
74
61
|
// with path
|
|
75
62
|
<P extends string | string[]>(path: P): Get<Data, P>
|
|
63
|
+
// with function mapper
|
|
64
|
+
<R>(fn: (data: Data) => R): R
|
|
76
65
|
// without path
|
|
77
66
|
(): Data
|
|
78
67
|
(path: undefined | string | string[]): unknown
|
|
@@ -85,7 +74,7 @@ export default class Record<Data = unknown> {
|
|
|
85
74
|
dataAtPath: unknown extends Get<Data, P> ? never : Get<Data, P>,
|
|
86
75
|
): void
|
|
87
76
|
// without path
|
|
88
|
-
(data:
|
|
77
|
+
(data: Data): void
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
when: {
|
|
@@ -97,13 +86,13 @@ export default class Record<Data = unknown> {
|
|
|
97
86
|
update: {
|
|
98
87
|
// without path
|
|
99
88
|
(
|
|
100
|
-
updater: (data: Readonly<Data
|
|
89
|
+
updater: (data: Readonly<Data>, version: string) => Data,
|
|
101
90
|
options?: UpdateOptions,
|
|
102
91
|
): Promise<void>
|
|
103
92
|
// with path
|
|
104
93
|
<P extends string | string[]>(
|
|
105
94
|
path: P,
|
|
106
|
-
updater: (dataAtPath: Readonly<Get<Data, P
|
|
95
|
+
updater: (dataAtPath: Readonly<Get<Data, P>>, version: string) => Get<Data, P>,
|
|
107
96
|
options?: UpdateOptions,
|
|
108
97
|
): Promise<void>
|
|
109
98
|
}
|
package/src/rpc/rpc-handler.d.ts
CHANGED
|
@@ -10,8 +10,11 @@ export default class RpcHandler<
|
|
|
10
10
|
|
|
11
11
|
provide: <Name extends keyof Methods>(
|
|
12
12
|
name: Name,
|
|
13
|
-
callback: (
|
|
14
|
-
|
|
13
|
+
callback: (
|
|
14
|
+
args: Methods[Name][0],
|
|
15
|
+
response: RpcResponse<Methods[Name][1]>,
|
|
16
|
+
) => Methods[Name][1] | Promise<Methods[Name][1]> | Promise<void> | void,
|
|
17
|
+
) => UnprovideFn | void
|
|
15
18
|
|
|
16
19
|
unprovide: <Name extends keyof Methods>(name: Name) => void
|
|
17
20
|
|
|
@@ -22,7 +25,7 @@ export default class RpcHandler<
|
|
|
22
25
|
ReturnValue extends Name extends keyof Methods ? Methods[Name][1] : unknown,
|
|
23
26
|
>(
|
|
24
27
|
name: Name,
|
|
25
|
-
args
|
|
28
|
+
args?: Args,
|
|
26
29
|
): Promise<ReturnValue>
|
|
27
30
|
<
|
|
28
31
|
Name extends keyof Methods | string,
|
|
@@ -30,7 +33,7 @@ export default class RpcHandler<
|
|
|
30
33
|
ReturnValue extends Name extends keyof Methods ? Methods[Name][1] : unknown,
|
|
31
34
|
>(
|
|
32
35
|
name: Name,
|
|
33
|
-
args: Args,
|
|
36
|
+
args: Args | undefined,
|
|
34
37
|
callback: (error: unknown, response: ReturnValue) => void,
|
|
35
38
|
): void
|
|
36
39
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import make from '../client.js'
|
|
2
|
+
import { expectAssignable, expectError, expectType } from 'tsd'
|
|
3
|
+
|
|
4
|
+
interface Methods extends Record<string, [unknown, unknown]> {
|
|
5
|
+
greet: [{ name: string }, { message: string }]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const ds = make<Record<string, unknown>, Methods>('')
|
|
9
|
+
|
|
10
|
+
// provide: callback may return void, a value, or a Promise — all valid
|
|
11
|
+
ds.rpc.provide('greet', (_args, _response) => {})
|
|
12
|
+
ds.rpc.provide('greet', (_args, _response) => ({ message: 'hello' }))
|
|
13
|
+
ds.rpc.provide('greet', async (_args, _response) => ({ message: 'hello' }))
|
|
14
|
+
// async callback that uses response.send() directly — returns Promise<void>
|
|
15
|
+
ds.rpc.provide('greet', async (_args, response) => {
|
|
16
|
+
response.send({ message: 'hello' })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// provide: returning the wrong shape is an error
|
|
20
|
+
expectError(ds.rpc.provide('greet', (_args, _response) => ({ notMessage: 'hello' })))
|
|
21
|
+
|
|
22
|
+
// provide: response.completed is boolean
|
|
23
|
+
ds.rpc.provide('greet', (_args, response) => {
|
|
24
|
+
expectType<boolean>(response.completed)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// provide: return type is UnprovideFn | void
|
|
28
|
+
expectAssignable<(() => void) | void>(ds.rpc.provide('greet', () => {}))
|
|
29
|
+
|
|
30
|
+
// make: args is optional (no args)
|
|
31
|
+
expectAssignable<Promise<{ message: string }>>(ds.rpc.make('greet'))
|
|
32
|
+
// make: args provided
|
|
33
|
+
expectAssignable<Promise<{ message: string }>>(ds.rpc.make('greet', { name: 'world' }))
|
|
34
|
+
// make: args explicitly undefined
|
|
35
|
+
expectAssignable<Promise<{ message: string }>>(ds.rpc.make('greet', undefined))
|
|
36
|
+
// make: callback form — args required positionally but can be undefined
|
|
37
|
+
ds.rpc.make('greet', undefined, (err, res) => {
|
|
38
|
+
expectType<unknown>(err)
|
|
39
|
+
expectAssignable<{ message: string } | undefined>(res)
|
|
40
|
+
})
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import * as rxjs from 'rxjs'
|
|
2
|
+
import * as C from '../constants/constants.js'
|
|
3
|
+
import { h64ToString, findBigIntPaths } from '../utils/utils.js'
|
|
4
|
+
|
|
5
|
+
export default class Listener {
|
|
6
|
+
constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {
|
|
7
|
+
this._topic = topic
|
|
8
|
+
this._pattern = pattern
|
|
9
|
+
this._callback = callback
|
|
10
|
+
this._handler = handler
|
|
11
|
+
this._client = this._handler._client
|
|
12
|
+
this._connection = this._handler._connection
|
|
13
|
+
this._subscriptions = new Map()
|
|
14
|
+
this._recursive = recursive
|
|
15
|
+
this._stringify = stringify || JSON.stringify
|
|
16
|
+
|
|
17
|
+
this._$onConnectionStateChange()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get connected() {
|
|
21
|
+
return this._connection.connected
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get stats() {
|
|
25
|
+
return {
|
|
26
|
+
subscriptions: this._subscriptions.size,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_$destroy() {
|
|
31
|
+
this._reset()
|
|
32
|
+
|
|
33
|
+
if (this.connected) {
|
|
34
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.UNLISTEN, [this._pattern])
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_$onMessage(message) {
|
|
39
|
+
if (!this.connected) {
|
|
40
|
+
this._client._$onError(
|
|
41
|
+
C.TOPIC.RECORD,
|
|
42
|
+
C.EVENT.NOT_CONNECTED,
|
|
43
|
+
new Error('received message while not connected'),
|
|
44
|
+
message,
|
|
45
|
+
)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const name = message.data[1]
|
|
50
|
+
|
|
51
|
+
if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND) {
|
|
52
|
+
if (this._subscriptions.has(name)) {
|
|
53
|
+
this._error(name, 'invalid add: listener exists')
|
|
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
|
|
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)
|
|
201
|
+
} else if (message.action === C.ACTIONS.LISTEN_ACCEPT) {
|
|
202
|
+
const provider = this._subscriptions.get(name)
|
|
203
|
+
if (!provider?.value$) {
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (provider.valueSubscription) {
|
|
208
|
+
this._error(
|
|
209
|
+
name,
|
|
210
|
+
'invalid accept: listener started (pattern:' + this._pattern + ' name:' + name + ')',
|
|
211
|
+
)
|
|
212
|
+
} else {
|
|
213
|
+
// TODO (fix): provider.version = message.data[2]
|
|
214
|
+
provider.valueSubscription = provider.value$.subscribe(provider.observer)
|
|
215
|
+
}
|
|
216
|
+
} else if (message.action === C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED) {
|
|
217
|
+
const provider = this._subscriptions.get(name)
|
|
218
|
+
|
|
219
|
+
if (!provider) {
|
|
220
|
+
this._error(
|
|
221
|
+
name,
|
|
222
|
+
'invalid remove: listener missing (pattern:' + this._pattern + ' name:' + name + ')',
|
|
223
|
+
)
|
|
224
|
+
} else {
|
|
225
|
+
provider.stop()
|
|
226
|
+
this._subscriptions.delete(provider.name)
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_$onConnectionStateChange() {
|
|
235
|
+
if (this.connected) {
|
|
236
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN, [this._pattern])
|
|
237
|
+
} else {
|
|
238
|
+
this._reset()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
_error(name, err) {
|
|
243
|
+
this._client._$onError(this._topic, C.EVENT.LISTENER_ERROR, err, [this._pattern, name])
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
_reset() {
|
|
247
|
+
for (const provider of this._subscriptions.values()) {
|
|
248
|
+
provider.stop()
|
|
249
|
+
}
|
|
250
|
+
this._subscriptions.clear()
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/utils/utils.js
CHANGED
|
@@ -83,14 +83,6 @@ export function setTimeout(callback, timeoutDuration) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
export function setInterval(callback, intervalDuration) {
|
|
87
|
-
if (intervalDuration !== null) {
|
|
88
|
-
return setInterval(callback, intervalDuration)
|
|
89
|
-
} else {
|
|
90
|
-
return -1
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
86
|
export function compareRev(a, b) {
|
|
95
87
|
if (!a) {
|
|
96
88
|
return b ? -1 : 0
|