@nxtedition/deepstream.io-client-js 28.1.22 → 28.1.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/.claude/settings.local.json +9 -0
- package/package.json +4 -2
- package/src/client.d.ts +29 -25
- package/src/client.test-d.ts +105 -0
- package/src/record/record-handler.d.ts +48 -46
- package/src/record/record-handler.js +47 -64
- package/src/record/record.d.ts +20 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/deepstream.io-client-js",
|
|
3
|
-
"version": "28.1.
|
|
3
|
+
"version": "28.1.24",
|
|
4
4
|
"description": "the javascript client for deepstream.io",
|
|
5
5
|
"homepage": "http://deepstream.io",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"prepublishOnly": "pinst --disable",
|
|
23
23
|
"postpublish": "pinst --enable",
|
|
24
|
-
"prepare": "husky"
|
|
24
|
+
"prepare": "husky",
|
|
25
|
+
"test:types": "tsd"
|
|
25
26
|
},
|
|
26
27
|
"lint-staged": {
|
|
27
28
|
"*.{js,jsx,md,ts}": [
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"pinst": "^3.0.0",
|
|
61
62
|
"prettier": "^3.3.3",
|
|
62
63
|
"rxjs": "^7.8.1",
|
|
64
|
+
"tsd": "^0.33.0",
|
|
63
65
|
"type-fest": "^4.33.0",
|
|
64
66
|
"typescript": "^5.6.3",
|
|
65
67
|
"typescript-eslint": "^8.12.2"
|
package/src/client.d.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
3
|
-
import type
|
|
4
|
-
import type
|
|
1
|
+
import type DsRecord from './record/record.js'
|
|
2
|
+
import type { Paths, Get } from './record/record.js'
|
|
3
|
+
import type RecordHandler from './record/record-handler.js'
|
|
4
|
+
import type { RecordStats, ProvideOptions, SyncOptions } from './record/record-handler.js'
|
|
5
|
+
import type EventHandler from './event/event-handler.js'
|
|
6
|
+
import type { EventStats } from './event/event-handler.js'
|
|
7
|
+
import type RpcHandler from './rpc/rpc-handler.js'
|
|
8
|
+
import type { RpcStats, RpcMethodDef } from './rpc/rpc-handler.js'
|
|
5
9
|
|
|
6
|
-
export default function <
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
): DeepstreamClient<Records, Methods>
|
|
10
|
+
export default function <
|
|
11
|
+
Records extends Record<string, unknown> = Record<string, unknown>,
|
|
12
|
+
Methods extends Record<string, RpcMethodDef> = Record<string, RpcMethodDef>,
|
|
13
|
+
>(url: string, options?: unknown): DeepstreamClient<Records, Methods>
|
|
10
14
|
|
|
11
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
DsRecord,
|
|
17
|
+
RecordHandler,
|
|
18
|
+
EventHandler,
|
|
19
|
+
RpcHandler,
|
|
20
|
+
RpcMethodDef,
|
|
21
|
+
ProvideOptions,
|
|
22
|
+
SyncOptions,
|
|
23
|
+
Paths,
|
|
24
|
+
Get,
|
|
25
|
+
}
|
|
12
26
|
|
|
13
27
|
type RecordStateConstants = Readonly<{
|
|
14
28
|
VOID: 0
|
|
@@ -28,8 +42,8 @@ type ConnectionStateConstants = Readonly<{
|
|
|
28
42
|
ERROR: 'ERROR'
|
|
29
43
|
RECONNECTING: 'RECONNECTING'
|
|
30
44
|
}>
|
|
31
|
-
type ConnectionStateKey = keyof
|
|
32
|
-
type ConnectionStateName =
|
|
45
|
+
type ConnectionStateKey = keyof ConnectionStateConstants
|
|
46
|
+
type ConnectionStateName = ConnectionStateConstants[ConnectionStateKey]
|
|
33
47
|
|
|
34
48
|
type EventConstants = Readonly<{
|
|
35
49
|
CONNECTION_ERROR: 'connectionError'
|
|
@@ -61,12 +75,12 @@ type EventConstants = Readonly<{
|
|
|
61
75
|
RECORD_NOT_FOUND: 'RECORD_NOT_FOUND'
|
|
62
76
|
NOT_SUBSCRIBED: 'NOT_SUBSCRIBED'
|
|
63
77
|
}>
|
|
64
|
-
type EventKey = keyof
|
|
65
|
-
type EventName =
|
|
78
|
+
type EventKey = keyof EventConstants
|
|
79
|
+
type EventName = EventConstants[EventKey]
|
|
66
80
|
|
|
67
81
|
export interface DeepstreamClient<
|
|
68
|
-
Records = Record<string, unknown>,
|
|
69
|
-
Methods = Record<string, RpcMethodDef>,
|
|
82
|
+
Records extends Record<string, unknown> = Record<string, unknown>,
|
|
83
|
+
Methods extends Record<string, RpcMethodDef> = Record<string, RpcMethodDef>,
|
|
70
84
|
> {
|
|
71
85
|
nuid: () => string
|
|
72
86
|
event: EventHandler
|
|
@@ -92,13 +106,3 @@ export interface DeepstreamClient<
|
|
|
92
106
|
EVENT: EventConstants
|
|
93
107
|
}
|
|
94
108
|
}
|
|
95
|
-
|
|
96
|
-
export interface ProvideOptions {
|
|
97
|
-
recursive?: boolean
|
|
98
|
-
stringify?: ((input: unknown) => string) | null
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export interface SyncOptions {
|
|
102
|
-
signal?: AbortSignal
|
|
103
|
-
timeout?: number
|
|
104
|
-
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import make from './client.js'
|
|
2
|
+
import { expectAssignable, expectError } from 'tsd'
|
|
3
|
+
|
|
4
|
+
interface Records extends Record<string, unknown> {
|
|
5
|
+
o: {
|
|
6
|
+
o0?: {
|
|
7
|
+
o1?: {
|
|
8
|
+
o2?: {
|
|
9
|
+
o3?: string
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
n: {
|
|
15
|
+
n0: {
|
|
16
|
+
n1: {
|
|
17
|
+
n2: {
|
|
18
|
+
n3: string
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
c: Circular
|
|
24
|
+
m: {
|
|
25
|
+
m1: string
|
|
26
|
+
m2: string
|
|
27
|
+
m3?: string
|
|
28
|
+
}
|
|
29
|
+
p: {
|
|
30
|
+
p1: string
|
|
31
|
+
p2?: string
|
|
32
|
+
p3: { p4: string }
|
|
33
|
+
}
|
|
34
|
+
[x: `${string}:domain`]: {
|
|
35
|
+
d1: string
|
|
36
|
+
d2: {
|
|
37
|
+
d3: string
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Circular {
|
|
43
|
+
a: {
|
|
44
|
+
b0: Circular
|
|
45
|
+
b1: string
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ds = make<Records>('')
|
|
50
|
+
|
|
51
|
+
expectAssignable<{ n0?: { n1: { n2: { n3: string } } } } | undefined>(await ds.record.get('n'))
|
|
52
|
+
expectAssignable<{ n1: { n2: { n3: string } } } | undefined>(await ds.record.get('n', 'n0'))
|
|
53
|
+
|
|
54
|
+
// set withouth path
|
|
55
|
+
ds.record.set('n', {}) // empty should always work
|
|
56
|
+
ds.record.set('n', { n0: { n1: { n2: { n3: 'test' } } } })
|
|
57
|
+
expectError(ds.record.set('n', { n0: {} })) // nested props are required
|
|
58
|
+
|
|
59
|
+
// set with path
|
|
60
|
+
ds.record.set('n', 'n0.n1', { n2: { n3: 'test' } })
|
|
61
|
+
ds.record.set('n', 'n0', { n1: { n2: { n3: 'test' } } })
|
|
62
|
+
ds.record.set('n', 'n0.n1', { n2: { n3: 'test' } })
|
|
63
|
+
ds.record.set('n', 'n0.n1.n2', { n3: 'test' })
|
|
64
|
+
ds.record.set('n', 'n0.n1.n2.n3', 'test')
|
|
65
|
+
ds.record.set('o', 'o0.o1.o2.o3', 'test')
|
|
66
|
+
ds.record.set('o', 'o0', {})
|
|
67
|
+
ds.record.set('o', 'o0.o1', {})
|
|
68
|
+
ds.record.set('o', 'o0.o1.o2', {})
|
|
69
|
+
ds.record.set('o', 'o0.o1', { o2: {} })
|
|
70
|
+
ds.record.set('o', 'o0.o1', { o2: { o3: 'test' } })
|
|
71
|
+
ds.record.set('c', 'a.b1', 'test')
|
|
72
|
+
ds.record.set('x:domain', 'd1', 'test')
|
|
73
|
+
const id = 'id'
|
|
74
|
+
ds.record.set(`${id}:domain`, 'd2.d3', 'test')
|
|
75
|
+
|
|
76
|
+
expectAssignable<string>(await ds.record.get(`${id}:domain`, 'd2.d3'))
|
|
77
|
+
|
|
78
|
+
// errors
|
|
79
|
+
expectError(ds.record.set('o', 'o0.o1', { o2: { o3: 0 } }))
|
|
80
|
+
expectError(ds.record.set('o', 'o0.o1', { o3: 0 }))
|
|
81
|
+
expectError(ds.record.set('n', 'x1', {}))
|
|
82
|
+
expectError(ds.record.set('n', 'n0.x2', 22))
|
|
83
|
+
expectError(ds.record.set('n', 'n1.x2', {}))
|
|
84
|
+
expectError(ds.record.set('n', 'n1.n2.n3', { n4: 22 }))
|
|
85
|
+
|
|
86
|
+
expectAssignable<string>(await ds.record.get('p', 'p1'))
|
|
87
|
+
expectAssignable<string | undefined>(await ds.record.get('p', 'p2'))
|
|
88
|
+
expectAssignable<unknown>(await ds.record.get('p', 'x1'))
|
|
89
|
+
|
|
90
|
+
// Circular
|
|
91
|
+
expectAssignable<string | undefined>(await ds.record.get('c', 'a.b1'))
|
|
92
|
+
|
|
93
|
+
// ============
|
|
94
|
+
//
|
|
95
|
+
|
|
96
|
+
// getRecord
|
|
97
|
+
const daRec = ds.record.getRecord('o')
|
|
98
|
+
daRec.set({ o0: {} })
|
|
99
|
+
|
|
100
|
+
daRec.update('o0', (x) => ({ ...x, o1: {} }))
|
|
101
|
+
expectError(daRec.update((x) => 'x'))
|
|
102
|
+
expectError(daRec.update('o0', (x) => ({ ...x, o1: '22' })))
|
|
103
|
+
|
|
104
|
+
ds.record.set('foo', { num: [22, true] })
|
|
105
|
+
ds.record.set('foo', { num: ['22'] })
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Observable } from 'rxjs'
|
|
2
|
-
import type
|
|
2
|
+
import type DsRecord from './record.js'
|
|
3
|
+
import type { EmptyObject, Get, Paths } from './record.js'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export default class RecordHandler<Records> {
|
|
5
|
+
export default class RecordHandler<
|
|
6
|
+
Lookup extends Record<string, unknown> = Record<string, unknown>,
|
|
7
|
+
> {
|
|
8
8
|
VOID: 0
|
|
9
9
|
CLIENT: 1
|
|
10
10
|
SERVER: 2
|
|
@@ -20,13 +20,13 @@ export default class RecordHandler<Records> {
|
|
|
20
20
|
connected: boolean
|
|
21
21
|
stats: RecordStats
|
|
22
22
|
|
|
23
|
-
getRecord
|
|
23
|
+
getRecord<Name extends string, Data = Name extends keyof Lookup ? Lookup[Name] : unknown>(
|
|
24
24
|
name: Name,
|
|
25
|
-
)
|
|
25
|
+
): DsRecord<Data>
|
|
26
26
|
|
|
27
|
-
provide:
|
|
27
|
+
provide: (
|
|
28
28
|
pattern: string,
|
|
29
|
-
callback: (key: string) =>
|
|
29
|
+
callback: (key: string) => unknown,
|
|
30
30
|
optionsOrRecursive?: ProvideOptions | boolean,
|
|
31
31
|
) => void | (() => void)
|
|
32
32
|
|
|
@@ -34,115 +34,107 @@ export default class RecordHandler<Records> {
|
|
|
34
34
|
|
|
35
35
|
set: {
|
|
36
36
|
// without path:
|
|
37
|
-
<Name extends
|
|
37
|
+
<Name extends string>(name: Name, data: Lookup[Name] | EmptyObject): void
|
|
38
38
|
|
|
39
39
|
// with path:
|
|
40
|
-
<Name extends
|
|
40
|
+
<Name extends string, Path extends string | string[]>(
|
|
41
41
|
name: Name,
|
|
42
42
|
path: Path,
|
|
43
|
-
data: Get<
|
|
43
|
+
data: Path extends Paths<Lookup[Name]> ? Get<Lookup[Name], Path> : never,
|
|
44
44
|
): void
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
update: {
|
|
48
48
|
// without path:
|
|
49
|
-
<Name extends
|
|
49
|
+
<Name extends string>(
|
|
50
50
|
name: Name,
|
|
51
|
-
updater: (data:
|
|
51
|
+
updater: (data: Lookup[Name]) => Lookup[Name] | EmptyObject,
|
|
52
52
|
): Promise<void>
|
|
53
53
|
|
|
54
54
|
// with path:
|
|
55
|
-
<Name extends
|
|
55
|
+
<Name extends string, Path extends string | string[]>(
|
|
56
56
|
name: Name,
|
|
57
57
|
path: Path,
|
|
58
|
-
updater: (data:
|
|
58
|
+
updater: (data: Get<Lookup[Name], Path>) => Get<Lookup[Name], Path>,
|
|
59
59
|
): Promise<void>
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
observe: {
|
|
63
63
|
// without path:
|
|
64
|
-
<Name extends
|
|
65
|
-
name: Name,
|
|
66
|
-
): Observable<GettablePossibleEmpty<Data>>
|
|
64
|
+
<Name extends string>(name: Name): Observable<Lookup[Name]>
|
|
67
65
|
|
|
68
66
|
// with path:
|
|
69
|
-
<Name extends
|
|
67
|
+
<Name extends string, Path extends string | string[]>(
|
|
70
68
|
name: Name,
|
|
71
69
|
path: Path,
|
|
72
|
-
): Observable<Get<
|
|
70
|
+
): Observable<Get<Lookup[Name], Path>>
|
|
73
71
|
|
|
74
72
|
// with state:
|
|
75
|
-
<Name extends
|
|
76
|
-
name: Name,
|
|
77
|
-
state: number,
|
|
78
|
-
): Observable<GettablePossibleEmpty<Data>>
|
|
73
|
+
<Name extends string>(name: Name, state: number): Observable<Lookup[Name]>
|
|
79
74
|
|
|
80
75
|
// with path and state:
|
|
81
|
-
<Name extends
|
|
76
|
+
<Name extends string, Path extends string | string[]>(
|
|
82
77
|
name: Name,
|
|
83
78
|
path: Path,
|
|
84
79
|
state: number,
|
|
85
|
-
): Observable<Get<
|
|
80
|
+
): Observable<Get<Lookup[Name], Path>>
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
get: {
|
|
89
84
|
// without path:
|
|
90
|
-
<Name extends
|
|
91
|
-
name: Name,
|
|
92
|
-
state?: number,
|
|
93
|
-
): Promise<GettablePossibleEmpty<Data>>
|
|
85
|
+
<Name extends string>(name: Name, state?: number): Promise<Lookup[Name]>
|
|
94
86
|
|
|
95
87
|
// with path:
|
|
96
|
-
<Name extends
|
|
88
|
+
<Name extends string, Path extends string | string[]>(
|
|
97
89
|
name: Name,
|
|
98
|
-
path
|
|
90
|
+
path: Path,
|
|
99
91
|
state?: number,
|
|
100
|
-
): Promise<Get<
|
|
92
|
+
): Promise<Get<Lookup[Name], Path>>
|
|
101
93
|
}
|
|
102
94
|
|
|
103
95
|
observe2: {
|
|
104
96
|
// without path:
|
|
105
|
-
<Name extends
|
|
97
|
+
<Name extends string>(
|
|
106
98
|
name: Name,
|
|
107
99
|
): Observable<{
|
|
108
|
-
name:
|
|
100
|
+
name: string
|
|
109
101
|
version: string
|
|
110
102
|
state: number
|
|
111
|
-
data:
|
|
103
|
+
data: Lookup[Name]
|
|
112
104
|
}>
|
|
113
105
|
|
|
114
106
|
// with path:
|
|
115
|
-
<Name extends
|
|
107
|
+
<Name extends string, Path extends string | string[]>(
|
|
116
108
|
name: Name,
|
|
117
109
|
path: Path,
|
|
118
110
|
): Observable<{
|
|
119
|
-
name:
|
|
111
|
+
name: string
|
|
120
112
|
version: string
|
|
121
113
|
state: number
|
|
122
|
-
data: Get<
|
|
114
|
+
data: Get<Lookup[Name], Path>
|
|
123
115
|
}>
|
|
124
116
|
|
|
125
117
|
// with state:
|
|
126
|
-
<Name extends
|
|
118
|
+
<Name extends string>(
|
|
127
119
|
name: Name,
|
|
128
120
|
state: number,
|
|
129
121
|
): Observable<{
|
|
130
|
-
name:
|
|
122
|
+
name: string
|
|
131
123
|
version: string
|
|
132
124
|
state: number
|
|
133
|
-
data:
|
|
125
|
+
data: Lookup[Name]
|
|
134
126
|
}>
|
|
135
127
|
|
|
136
128
|
// with path and state:
|
|
137
|
-
<Name extends
|
|
129
|
+
<Name extends string, Path extends string | string[]>(
|
|
138
130
|
name: Name,
|
|
139
131
|
path: Path,
|
|
140
132
|
state: number,
|
|
141
133
|
): Observable<{
|
|
142
|
-
name:
|
|
134
|
+
name: string
|
|
143
135
|
version: string
|
|
144
136
|
state: number
|
|
145
|
-
data: Get<
|
|
137
|
+
data: Get<Lookup[Name], Path>
|
|
146
138
|
}>
|
|
147
139
|
}
|
|
148
140
|
}
|
|
@@ -156,3 +148,13 @@ export interface RecordStats {
|
|
|
156
148
|
patching: number
|
|
157
149
|
subscriptions: number
|
|
158
150
|
}
|
|
151
|
+
|
|
152
|
+
export interface ProvideOptions {
|
|
153
|
+
recursive?: boolean
|
|
154
|
+
stringify?: ((input: unknown) => string) | null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface SyncOptions {
|
|
158
|
+
signal?: AbortSignal
|
|
159
|
+
timeout?: number
|
|
160
|
+
}
|
|
@@ -163,13 +163,16 @@ class RecordHandler {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
_onUpdating(rec, value) {
|
|
166
|
+
const callbacks = this._updating.get(rec)
|
|
167
|
+
|
|
166
168
|
if (value) {
|
|
169
|
+
invariant(!callbacks, 'updating callbacks must not exist')
|
|
167
170
|
this._stats.updating += 1
|
|
168
171
|
this._updating.set(rec, [])
|
|
169
172
|
} else {
|
|
170
|
-
|
|
173
|
+
invariant(callbacks, 'updating callbacks must exist')
|
|
171
174
|
|
|
172
|
-
|
|
175
|
+
this._stats.updating -= 1
|
|
173
176
|
this._updating.delete(rec)
|
|
174
177
|
for (const callback of callbacks) {
|
|
175
178
|
callback()
|
|
@@ -273,63 +276,31 @@ class RecordHandler {
|
|
|
273
276
|
// TODO (perf): Slow implementation...
|
|
274
277
|
|
|
275
278
|
const signal = opts?.signal
|
|
276
|
-
const timeout = opts?.timeout
|
|
279
|
+
const timeout = opts?.timeout
|
|
277
280
|
|
|
278
|
-
|
|
281
|
+
let disposers
|
|
279
282
|
try {
|
|
280
283
|
const signalPromise = signal
|
|
281
284
|
? new Promise((resolve, reject) => {
|
|
282
285
|
const onAbort = () => reject(signal.reason ?? new utils.AbortError())
|
|
283
286
|
signal.addEventListener('abort', onAbort)
|
|
287
|
+
disposers ??= []
|
|
284
288
|
disposers.push(() => signal.removeEventListener('abort', onAbort))
|
|
285
289
|
})
|
|
286
290
|
: null
|
|
287
291
|
|
|
288
292
|
signalPromise?.catch(noop)
|
|
289
293
|
|
|
290
|
-
if (this._putting.size) {
|
|
291
|
-
const promises = []
|
|
292
|
-
|
|
293
|
-
{
|
|
294
|
-
const puttingPromises = []
|
|
295
|
-
for (const callbacks of this._putting.values()) {
|
|
296
|
-
puttingPromises.push(new Promise((resolve) => callbacks.push(resolve)))
|
|
297
|
-
}
|
|
298
|
-
promises.push(puttingPromises)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (timeout) {
|
|
302
|
-
promises.push(
|
|
303
|
-
new Promise((resolve) => {
|
|
304
|
-
const patchingTimeout = timers.setTimeout(() => {
|
|
305
|
-
this._client._$onError(
|
|
306
|
-
C.TOPIC.RECORD,
|
|
307
|
-
C.EVENT.TIMEOUT,
|
|
308
|
-
new Error('sync putting timeout'),
|
|
309
|
-
)
|
|
310
|
-
resolve(null)
|
|
311
|
-
}, timeout)
|
|
312
|
-
disposers.push(() => timers.clearTimeout(patchingTimeout))
|
|
313
|
-
}),
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (signalPromise) {
|
|
318
|
-
promises.push(signalPromise)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
await Promise.race(promises)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
294
|
if (this._patching.size) {
|
|
325
|
-
|
|
295
|
+
let promises
|
|
326
296
|
|
|
327
297
|
{
|
|
328
298
|
const patchingPromises = []
|
|
329
299
|
for (const callbacks of this._patching.values()) {
|
|
330
300
|
patchingPromises.push(new Promise((resolve) => callbacks.push(resolve)))
|
|
331
301
|
}
|
|
332
|
-
promises
|
|
302
|
+
promises ??= []
|
|
303
|
+
promises.push(Promise.all(patchingPromises))
|
|
333
304
|
}
|
|
334
305
|
|
|
335
306
|
if (timeout) {
|
|
@@ -343,30 +314,36 @@ class RecordHandler {
|
|
|
343
314
|
)
|
|
344
315
|
resolve(null)
|
|
345
316
|
}, timeout)
|
|
317
|
+
disposers ??= []
|
|
346
318
|
disposers.push(() => timers.clearTimeout(patchingTimeout))
|
|
347
319
|
}),
|
|
348
320
|
)
|
|
349
321
|
}
|
|
350
322
|
|
|
351
323
|
if (signalPromise) {
|
|
324
|
+
promises ??= []
|
|
352
325
|
promises.push(signalPromise)
|
|
353
326
|
}
|
|
354
327
|
|
|
355
|
-
|
|
328
|
+
if (promises) {
|
|
329
|
+
await Promise.race(promises)
|
|
330
|
+
}
|
|
356
331
|
}
|
|
357
332
|
|
|
358
333
|
if (this._updating.size) {
|
|
359
|
-
|
|
334
|
+
let promises
|
|
360
335
|
|
|
361
336
|
{
|
|
362
337
|
const updatingPromises = []
|
|
363
338
|
for (const callbacks of this._updating.values()) {
|
|
364
339
|
updatingPromises.push(new Promise((resolve) => callbacks.push(resolve)))
|
|
365
340
|
}
|
|
366
|
-
promises
|
|
341
|
+
promises ??= []
|
|
342
|
+
promises.push(Promise.all(updatingPromises))
|
|
367
343
|
}
|
|
368
344
|
|
|
369
345
|
if (timeout) {
|
|
346
|
+
promises ??= []
|
|
370
347
|
promises.push(
|
|
371
348
|
new Promise((resolve) => {
|
|
372
349
|
const updatingTimeout = timers.setTimeout(() => {
|
|
@@ -377,50 +354,57 @@ class RecordHandler {
|
|
|
377
354
|
)
|
|
378
355
|
resolve(null)
|
|
379
356
|
}, timeout)
|
|
357
|
+
disposers ??= []
|
|
380
358
|
disposers.push(() => timers.clearTimeout(updatingTimeout))
|
|
381
359
|
}),
|
|
382
360
|
)
|
|
383
361
|
}
|
|
384
362
|
|
|
385
|
-
|
|
363
|
+
if (promises) {
|
|
364
|
+
await Promise.race(promises)
|
|
365
|
+
}
|
|
386
366
|
}
|
|
387
367
|
|
|
388
368
|
{
|
|
389
|
-
const
|
|
369
|
+
const syncPromise = new Promise((resolve) => this._sync(resolve))
|
|
390
370
|
|
|
391
|
-
promises
|
|
392
|
-
new Promise((resolve) => {
|
|
393
|
-
const token = xuid()
|
|
394
|
-
this._syncEmitter.once(token, resolve)
|
|
395
|
-
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token])
|
|
396
|
-
}),
|
|
397
|
-
)
|
|
371
|
+
let promises
|
|
398
372
|
|
|
399
373
|
if (timeout) {
|
|
374
|
+
promises ??= []
|
|
400
375
|
promises.push(
|
|
401
376
|
new Promise((resolve, reject) => {
|
|
402
377
|
const serverTimeout = timers.setTimeout(() => {
|
|
403
378
|
reject(new Error('sync server timeout'))
|
|
404
379
|
}, timeout)
|
|
380
|
+
disposers ??= []
|
|
405
381
|
disposers.push(() => timers.clearTimeout(serverTimeout))
|
|
406
382
|
}),
|
|
407
383
|
)
|
|
408
384
|
}
|
|
409
385
|
|
|
410
386
|
if (signalPromise) {
|
|
387
|
+
promises ??= []
|
|
411
388
|
promises.push(signalPromise)
|
|
412
389
|
}
|
|
413
390
|
|
|
414
|
-
|
|
391
|
+
if (promises) {
|
|
392
|
+
promises.push(syncPromise)
|
|
393
|
+
await Promise.race(promises)
|
|
394
|
+
} else {
|
|
395
|
+
await syncPromise
|
|
396
|
+
}
|
|
415
397
|
}
|
|
416
398
|
} finally {
|
|
417
|
-
|
|
418
|
-
disposer
|
|
399
|
+
if (disposers) {
|
|
400
|
+
for (const disposer of disposers) {
|
|
401
|
+
disposer()
|
|
402
|
+
}
|
|
419
403
|
}
|
|
420
404
|
}
|
|
421
405
|
}
|
|
422
406
|
|
|
423
|
-
_sync(callback, opaque) {
|
|
407
|
+
_sync(callback, type, opaque) {
|
|
424
408
|
this._syncQueue.push(callback, opaque)
|
|
425
409
|
|
|
426
410
|
if (this._syncQueue.length > 2) {
|
|
@@ -437,7 +421,7 @@ class RecordHandler {
|
|
|
437
421
|
queue[n](queue[n + 1])
|
|
438
422
|
}
|
|
439
423
|
})
|
|
440
|
-
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token,
|
|
424
|
+
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, type ? [token, type] : [token])
|
|
441
425
|
}, 1)
|
|
442
426
|
}
|
|
443
427
|
|
|
@@ -476,9 +460,7 @@ class RecordHandler {
|
|
|
476
460
|
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.PUT, update)
|
|
477
461
|
|
|
478
462
|
this._putting.set(update, [])
|
|
479
|
-
this._sync((update) =>
|
|
480
|
-
this._putting.delete(update)
|
|
481
|
-
}, update)
|
|
463
|
+
this._sync((update) => this._putting.delete(update), 'WEAK', update)
|
|
482
464
|
}
|
|
483
465
|
|
|
484
466
|
/**
|
|
@@ -545,7 +527,7 @@ class RecordHandler {
|
|
|
545
527
|
rec.subscribe(onUpdateFast, opaque)
|
|
546
528
|
|
|
547
529
|
if (!opaque.synced) {
|
|
548
|
-
this._sync(onSyncFast, opaque)
|
|
530
|
+
this._sync(onSyncFast, 'WEAK', opaque)
|
|
549
531
|
}
|
|
550
532
|
}
|
|
551
533
|
})
|
|
@@ -736,13 +718,14 @@ class RecordHandler {
|
|
|
736
718
|
|
|
737
719
|
if (connected) {
|
|
738
720
|
this._connected = Date.now()
|
|
739
|
-
for (const token of this._syncEmitter.eventNames()) {
|
|
740
|
-
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token])
|
|
741
|
-
}
|
|
742
721
|
|
|
743
722
|
for (const update of this._putting.keys()) {
|
|
744
723
|
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.PUT, update)
|
|
745
724
|
}
|
|
725
|
+
|
|
726
|
+
for (const token of this._syncEmitter.eventNames()) {
|
|
727
|
+
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token])
|
|
728
|
+
}
|
|
746
729
|
} else {
|
|
747
730
|
this._connected = 0
|
|
748
731
|
}
|
package/src/record/record.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
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'
|
|
2
|
+
import type { Get, EmptyObject, SingleKeyObject } from 'type-fest'
|
|
3
|
+
export type { Get, Paths, EmptyObject } from 'type-fest'
|
|
8
4
|
|
|
9
5
|
// When getting, for convenience, we say the data might be partial under some
|
|
10
6
|
// circumstances.
|
|
@@ -45,12 +41,12 @@ export interface UpdateOptions {
|
|
|
45
41
|
signal?: AbortSignal
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
export default class Record<Data> {
|
|
44
|
+
export default class Record<Data = unknown> {
|
|
49
45
|
constructor(name: string, handler: RecordHandler)
|
|
50
46
|
|
|
51
47
|
readonly name: string
|
|
52
48
|
readonly version: string
|
|
53
|
-
readonly data:
|
|
49
|
+
readonly data: Data
|
|
54
50
|
readonly state: number
|
|
55
51
|
readonly refs: number
|
|
56
52
|
|
|
@@ -61,29 +57,16 @@ export default class Record<Data> {
|
|
|
61
57
|
|
|
62
58
|
get: {
|
|
63
59
|
// with path
|
|
64
|
-
<
|
|
65
|
-
path: Path,
|
|
66
|
-
): DataAtPath | undefined
|
|
60
|
+
<P extends string | string[]>(path: P): Get<Data, P>
|
|
67
61
|
// without path
|
|
68
|
-
():
|
|
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
|
|
62
|
+
(): Data
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
set: {
|
|
76
66
|
// with path
|
|
77
|
-
<
|
|
78
|
-
path: Path,
|
|
79
|
-
dataAtPath: DataAtPath,
|
|
80
|
-
): void
|
|
67
|
+
<P extends string | string[]>(path: P, dataAtPath: Get<Data, P>): void
|
|
81
68
|
// without path
|
|
82
69
|
(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
70
|
}
|
|
88
71
|
|
|
89
72
|
when: {
|
|
@@ -93,18 +76,17 @@ export default class Record<Data> {
|
|
|
93
76
|
(state: number, options: WhenOptions): Promise<Record<Data>>
|
|
94
77
|
}
|
|
95
78
|
|
|
96
|
-
update
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
): Promise<void>
|
|
79
|
+
update: {
|
|
80
|
+
// without path
|
|
81
|
+
(
|
|
82
|
+
updater: (data: Readonly<Data>) => SettablePossibleEmpty<Data>,
|
|
83
|
+
options?: UpdateOptions,
|
|
84
|
+
): Promise<void>
|
|
85
|
+
// with path
|
|
86
|
+
<P extends string | string[]>(
|
|
87
|
+
path: P,
|
|
88
|
+
updater: (dataAtPath: Readonly<Get<Data, P>>) => Get<Data, P>,
|
|
89
|
+
options?: UpdateOptions,
|
|
90
|
+
): Promise<void>
|
|
91
|
+
}
|
|
110
92
|
}
|