@nxtedition/deepstream.io-client-js 32.0.20 → 32.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/deepstream.io-client-js",
3
- "version": "32.0.20",
3
+ "version": "32.0.22",
4
4
  "description": "the javascript client for deepstream.io",
5
5
  "homepage": "http://deepstream.io",
6
6
  "type": "module",
@@ -36,16 +36,16 @@
36
36
  "singleQuote": true
37
37
  },
38
38
  "dependencies": {
39
- "@nxtedition/json-path": "1.0.9",
40
- "bufferutil": "4.0.8",
41
- "component-emitter2": "1.3.5",
42
- "invariant": "2.2.4",
43
- "lodash.clonedeep": "4.5.0",
44
- "utf-8-validate": "6.0.6",
45
- "varint": "6.0.0",
46
- "ws": "8.20.0",
47
- "xuid": "4.1.5",
48
- "xxhash-wasm": "^1.1.0"
39
+ "@nxtedition/json-path": "^1.0.8",
40
+ "bufferutil": "^4.0.8",
41
+ "component-emitter2": "^1.3.5",
42
+ "invariant": "^2.2.4",
43
+ "lodash.clonedeep": "^4.5.0",
44
+ "utf-8-validate": "^6.0.5",
45
+ "varint": "^6.0.0",
46
+ "ws": "^8.18.0",
47
+ "xuid": "^4.1.3",
48
+ "xxhash-wasm": "^1.0.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.10.3",
package/src/client.d.ts CHANGED
@@ -7,21 +7,10 @@ import type { EventStats } 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
-
21
10
  export default function <
22
11
  Records extends Record<string, unknown> = Record<string, unknown>,
23
12
  Methods extends Record<string, RpcMethodDef> = Record<string, RpcMethodDef>,
24
- >(url: string, options?: DeepstreamClientOptions): DeepstreamClient<Records, Methods>
13
+ >(url: string, options?: unknown): DeepstreamClient<Records, Methods>
25
14
 
26
15
  export type {
27
16
  DsRecord,
@@ -33,8 +22,6 @@ export type {
33
22
  SyncOptions,
34
23
  Paths,
35
24
  Get,
36
- ConnectionStateName,
37
- DeepstreamErrorEventName,
38
25
  }
39
26
 
40
27
  type RecordStateConstants = Readonly<{
@@ -90,36 +77,6 @@ type EventConstants = Readonly<{
90
77
  }>
91
78
  type EventKey = keyof EventConstants
92
79
  type EventName = EventConstants[EventKey]
93
- type DeepstreamErrorEventName = Exclude<
94
- EventName,
95
- 'connectionStateChanged' | 'connected' | 'MAX_RECONNECTION_ATTEMPTS_REACHED'
96
- >
97
-
98
- export interface DeepstreamError extends Error {
99
- topic?: string
100
- event?: EventName | null
101
- data?: unknown
102
- }
103
-
104
- export interface DeepstreamMessage {
105
- raw: string | null
106
- topic: string | null
107
- action: string | null
108
- data: string[]
109
- }
110
-
111
- export interface DeepstreamClientEventMap {
112
- connectionStateChanged: (state: ConnectionStateName) => void
113
- connected: (connected: boolean) => void
114
- MAX_RECONNECTION_ATTEMPTS_REACHED: (attempt: number) => void
115
- error: (error: DeepstreamError) => void
116
- recv: (message: DeepstreamMessage) => void
117
- send: (message: DeepstreamMessage) => void
118
- }
119
-
120
- type DeepstreamErrorEventMap = {
121
- [K in DeepstreamErrorEventName]: (error: DeepstreamError) => void
122
- }
123
80
 
124
81
  export interface DeepstreamClient<
125
82
  Records extends Record<string, unknown> = Record<string, unknown>,
@@ -130,21 +87,11 @@ export interface DeepstreamClient<
130
87
  rpc: RpcHandler<Methods>
131
88
  record: RecordHandler<Records>
132
89
  user: string | null
133
- on<K extends keyof (DeepstreamClientEventMap & DeepstreamErrorEventMap)>(
134
- evt: K,
135
- callback: (DeepstreamClientEventMap & DeepstreamErrorEventMap)[K],
136
- ): this
137
- off<K extends keyof (DeepstreamClientEventMap & DeepstreamErrorEventMap)>(
138
- evt: K,
139
- callback: (DeepstreamClientEventMap & DeepstreamErrorEventMap)[K],
140
- ): this
90
+ on: (evt: EventName, callback: (...args: unknown[]) => void) => void
91
+ off: (evt: EventName, callback: (...args: unknown[]) => void) => void
141
92
  getConnectionState: () => ConnectionStateName
142
93
  close: () => void
143
- login(callback: (success: boolean, authData: unknown) => void): this
144
- login(
145
- authParams: Record<string, unknown>,
146
- callback: (success: boolean, authData: unknown) => void,
147
- ): this
94
+ login: unknown
148
95
  stats: {
149
96
  record: RecordStats
150
97
  rpc: RpcStats
@@ -1,8 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import make, { type DeepstreamClient, type DeepstreamError } from './client.js'
2
+ import make, { type DeepstreamClient } 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'
6
5
 
7
6
  interface Records extends Record<string, unknown> {
8
7
  o: {
@@ -23,11 +22,6 @@ interface Records extends Record<string, unknown> {
23
22
  }
24
23
  }
25
24
  }
26
- possiblyEmpty:
27
- | {
28
- pe1: string
29
- }
30
- | EmptyObject
31
25
  c: Circular
32
26
  m: {
33
27
  m1: string
@@ -68,7 +62,7 @@ expectAssignable<{ n0?: { n1: { n2: { n3: string } } } } | undefined>(await ds.r
68
62
  expectAssignable<{ n1: { n2: { n3: string } } } | undefined>(await ds.record.get('n', 'n0'))
69
63
 
70
64
  // set withouth path
71
- ds.record.set('possiblyEmpty', {}) // empty should always work
65
+ ds.record.set('n', {}) // empty should always work
72
66
  ds.record.set('n', { n0: { n1: { n2: { n3: 'test' } } } })
73
67
  expectError(ds.record.set('n', { n0: {} })) // nested props are required
74
68
 
@@ -102,14 +96,10 @@ expectError(ds.record.set('n', 'n1.x2', {}))
102
96
  expectError(ds.record.set('n', 'n1.n2.n3', { n4: 22 }))
103
97
 
104
98
  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
- )
108
99
  expectAssignable<string>(await ds.record.get('p', 'p1', { signal: new AbortController().signal }))
109
100
  expectAssignable<string>(await ds.record.get('p', { path: 'p1' }))
110
101
  expectAssignable<string | undefined>(await ds.record.get('p', 'p2'))
111
102
  expectAssignable<unknown>(await ds.record.get('p', 'x1'))
112
- expectAssignable<string | undefined>(await ds.record.get('possiblyEmpty', 'pe1'))
113
103
 
114
104
  // observe with options
115
105
  expectAssignable<Observable<{ p1: string; p2?: string; p3: { p4: string } }>>(
@@ -189,16 +179,6 @@ expectAssignable<Promise<void>>(
189
179
  )
190
180
  expectAssignable<Promise<void>>(ds.record.update('p', 'p1', (data) => data, { timeout: 5000 }))
191
181
 
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
-
202
182
  // Circular
203
183
  expectAssignable<string | undefined>(await ds.record.get('c', 'a.b1'))
204
184
 
@@ -230,19 +210,6 @@ expectAssignable<Promise<typeof rec>>(rec.when({ state: 2, timeout: 5000 }))
230
210
  expectAssignable<Promise<typeof rec>>(rec.when(2, { timeout: 5000 }))
231
211
  expectAssignable<Promise<typeof rec>>(rec.when(2, { signal: new AbortController().signal }))
232
212
 
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
-
246
213
  // Record.update with options
247
214
  expectAssignable<Promise<void>>(rec.update((x) => x, { signal: new AbortController().signal }))
248
215
  expectAssignable<Promise<void>>(rec.update((x) => x, { timeout: 5000 }))
@@ -253,68 +220,7 @@ expectAssignable<Promise<void>>(
253
220
  expectAssignable<Promise<void>>(rec.update('o0', (x) => x, { timeout: 5000 }))
254
221
  expectAssignable<Promise<void>>(rec.update('o0', (x) => x, { state: 2 }))
255
222
 
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
-
266
223
  const state = 'VOID'
267
224
  expectType<0>(ds.record.STATE[state])
268
225
  const unknownState: string = 'VOID'
269
226
  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) => {}))
@@ -10,11 +10,7 @@ export default class EventHandler {
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: (
14
- pattern: string,
15
- callback: (name: string) => void,
16
- options: unknown,
17
- ) => (() => void) | void
13
+ provide: (pattern: string, callback: (name: string) => void, options: unknown) => () => void
18
14
  }
19
15
 
20
16
  export interface EventStats {
@@ -1,6 +1,12 @@
1
1
  import type { Observable } from 'rxjs'
2
2
  import type DsRecord from './record.js'
3
- import type { Get, UpdateOptions, ObserveOptions, ObserveOptionsWithPath } from './record.js'
3
+ import type {
4
+ EmptyObject,
5
+ Get,
6
+ UpdateOptions,
7
+ ObserveOptions,
8
+ ObserveOptionsWithPath,
9
+ } from './record.js'
4
10
 
5
11
  type Lookup<Table, Name> = Name extends keyof Table ? Table[Name] : unknown
6
12
 
@@ -26,8 +32,8 @@ export default class RecordHandler<Records = Record<string, unknown>> {
26
32
  }
27
33
 
28
34
  JSON: {
29
- EMPTY: Record<string, unknown>
30
- EMPTY_OBJ: Record<string, unknown>
35
+ EMPTY: EmptyObject
36
+ EMPTY_OBJ: EmptyObject
31
37
  EMPTY_ARR: []
32
38
  }
33
39
 
@@ -40,13 +46,13 @@ export default class RecordHandler<Records = Record<string, unknown>> {
40
46
  pattern: string,
41
47
  callback: (key: string) => unknown,
42
48
  optionsOrRecursive?: ProvideOptions | boolean,
43
- ) => Disposer | void
49
+ ) => Disposer
44
50
 
45
51
  sync: (options?: SyncOptions) => Promise<void>
46
52
 
47
53
  set: {
48
54
  // without path:
49
- <Name extends string>(name: Name, data: Lookup<Records, Name>): void
55
+ <Name extends string>(name: Name, data: Lookup<Records, Name> | EmptyObject): void
50
56
 
51
57
  // with path:
52
58
  <Name extends string, Path extends string | string[]>(
@@ -61,17 +67,14 @@ export default class RecordHandler<Records = Record<string, unknown>> {
61
67
  update: {
62
68
  <Name extends string>(
63
69
  name: Name,
64
- updater: (data: Lookup<Records, Name>, version: string) => Lookup<Records, Name>,
70
+ updater: (data: Lookup<Records, Name>) => Lookup<Records, Name> | EmptyObject,
65
71
  options?: UpdateOptions,
66
72
  ): Promise<void>
67
73
 
68
74
  <Name extends string, Path extends string | string[]>(
69
75
  name: Name,
70
76
  path: Path,
71
- updater: (
72
- data: Get<Lookup<Records, Name>, Path>,
73
- version: string,
74
- ) => Get<Lookup<Records, Name>, Path>,
77
+ updater: (data: Get<Lookup<Records, Name>, Path>) => Get<Lookup<Records, Name>, Path>,
75
78
  options?: UpdateOptions,
76
79
  ): Promise<void>
77
80
  }
@@ -110,73 +113,6 @@ export default class RecordHandler<Records = Record<string, unknown>> {
110
113
  ): Observable<Get<Lookup<Records, Name>, Path>>
111
114
  }
112
115
 
113
- observe2: {
114
- <Name extends string>(
115
- name: Name,
116
- options: ObserveOptions,
117
- ): Observable<{
118
- name: string
119
- version: string
120
- state: number
121
- data: Lookup<Records, Name>
122
- }>
123
-
124
- <Name extends string, Path extends string | string[]>(
125
- name: Name,
126
- options: ObserveOptionsWithPath<Path>,
127
- ): Observable<{
128
- name: string
129
- version: string
130
- state: number
131
- data: Get<Lookup<Records, Name>, Path>
132
- }>
133
-
134
- <Name extends string>(
135
- name: Name,
136
- state?: number,
137
- options?: ObserveOptions,
138
- ): Observable<{
139
- name: string
140
- version: string
141
- state: number
142
- data: Lookup<Records, Name>
143
- }>
144
-
145
- <Name extends string, Path extends string | string[]>(
146
- name: Name,
147
- state?: number,
148
- options?: ObserveOptionsWithPath<Path>,
149
- ): Observable<{
150
- name: string
151
- version: string
152
- state: number
153
- data: Get<Lookup<Records, Name>, Path>
154
- }>
155
-
156
- <Name extends string, Path extends string | string[]>(
157
- name: Name,
158
- path: Path,
159
- options?: ObserveOptionsWithPath<Path>,
160
- ): Observable<{
161
- name: string
162
- version: string
163
- state: number
164
- data: Get<Lookup<Records, Name>, Path>
165
- }>
166
-
167
- <Name extends string, Path extends string | string[]>(
168
- name: Name,
169
- path: Path,
170
- state?: number,
171
- options?: ObserveOptionsWithPath<Path>,
172
- ): Observable<{
173
- name: string
174
- version: string
175
- state: number
176
- data: Get<Lookup<Records, Name>, Path>
177
- }>
178
- }
179
-
180
116
  get: {
181
117
  <Name extends string>(name: Name, options: ObserveOptions): Promise<Lookup<Records, Name>>
182
118
 
@@ -211,11 +147,11 @@ export default class RecordHandler<Records = Record<string, unknown>> {
211
147
  ): Promise<Get<Lookup<Records, Name>, Path>>
212
148
  }
213
149
 
214
- get2: {
150
+ observe2: {
215
151
  <Name extends string>(
216
152
  name: Name,
217
153
  options: ObserveOptions,
218
- ): Promise<{
154
+ ): Observable<{
219
155
  name: string
220
156
  version: string
221
157
  state: number
@@ -225,7 +161,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
225
161
  <Name extends string, Path extends string | string[]>(
226
162
  name: Name,
227
163
  options: ObserveOptionsWithPath<Path>,
228
- ): Promise<{
164
+ ): Observable<{
229
165
  name: string
230
166
  version: string
231
167
  state: number
@@ -236,7 +172,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
236
172
  name: Name,
237
173
  state?: number,
238
174
  options?: ObserveOptions,
239
- ): Promise<{
175
+ ): Observable<{
240
176
  name: string
241
177
  version: string
242
178
  state: number
@@ -247,7 +183,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
247
183
  name: Name,
248
184
  state?: number,
249
185
  options?: ObserveOptionsWithPath<Path>,
250
- ): Promise<{
186
+ ): Observable<{
251
187
  name: string
252
188
  version: string
253
189
  state: number
@@ -258,7 +194,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
258
194
  name: Name,
259
195
  path: Path,
260
196
  options?: ObserveOptionsWithPath<Path>,
261
- ): Promise<{
197
+ ): Observable<{
262
198
  name: string
263
199
  version: string
264
200
  state: number
@@ -270,7 +206,7 @@ export default class RecordHandler<Records = Record<string, unknown>> {
270
206
  path: Path,
271
207
  state?: number,
272
208
  options?: ObserveOptionsWithPath<Path>,
273
- ): Promise<{
209
+ ): Observable<{
274
210
  name: string
275
211
  version: string
276
212
  state: number
@@ -1,15 +1,36 @@
1
1
  import type RecordHandler from './record-handler.js'
2
- import type { Get as _Get, AllUnionFields } from 'type-fest'
3
- export type { Paths } from 'type-fest'
2
+ import type { Get, EmptyObject, SingleKeyObject } from 'type-fest'
3
+ export type { Get, Paths, EmptyObject } from 'type-fest'
4
4
 
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
- >
5
+ // When getting, for convenience, we say the data might be partial under some
6
+ // circumstances.
7
+ //
8
+ // When you e.g. do record.get or record.update, there is always a possibility
9
+ // that the data object is empty. The naive correct type for that would be
10
+ // `Data | EmptyObject`. However, that forces the user to always type guard
11
+ // against the empty object case. This type tries to allow the user to skip
12
+ // that check in some cases, where it should be safe to do so.
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
13
34
 
14
35
  export interface WhenOptions {
15
36
  signal?: AbortSignal
@@ -24,7 +45,6 @@ export interface UpdateOptions {
24
45
  }
25
46
 
26
47
  export interface ObserveOptions {
27
- key?: string
28
48
  signal?: AbortSignal
29
49
  timeout?: number
30
50
  state?: number
@@ -47,15 +67,8 @@ export default class Record<Data = unknown> {
47
67
 
48
68
  ref(): Record<Data>
49
69
  unref(): Record<Data>
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>
70
+ subscribe(callback: (record: Record<Data>) => void, opaque?: unknown): Record<Data>
71
+ unsubscribe(callback: (record: Record<Data>) => void, opaque?: unknown): Record<Data>
59
72
 
60
73
  get: {
61
74
  // with path
@@ -72,7 +85,7 @@ export default class Record<Data = unknown> {
72
85
  dataAtPath: unknown extends Get<Data, P> ? never : Get<Data, P>,
73
86
  ): void
74
87
  // without path
75
- (data: Data): void
88
+ (data: SettablePossibleEmpty<Data>): void
76
89
  }
77
90
 
78
91
  when: {
@@ -84,13 +97,13 @@ export default class Record<Data = unknown> {
84
97
  update: {
85
98
  // without path
86
99
  (
87
- updater: (data: Readonly<Data>, version: string) => Data,
100
+ updater: (data: Readonly<Data>) => SettablePossibleEmpty<Data>,
88
101
  options?: UpdateOptions,
89
102
  ): Promise<void>
90
103
  // with path
91
104
  <P extends string | string[]>(
92
105
  path: P,
93
- updater: (dataAtPath: Readonly<Get<Data, P>>, version: string) => Get<Data, P>,
106
+ updater: (dataAtPath: Readonly<Get<Data, P>>) => Get<Data, P>,
94
107
  options?: UpdateOptions,
95
108
  ): Promise<void>
96
109
  }
@@ -10,11 +10,8 @@ export default class RpcHandler<
10
10
 
11
11
  provide: <Name extends keyof Methods>(
12
12
  name: Name,
13
- callback: (
14
- args: Methods[Name][0],
15
- response: RpcResponse<Methods[Name][1]>,
16
- ) => Methods[Name][1] | Promise<Methods[Name][1]> | void,
17
- ) => UnprovideFn | void
13
+ callback: (args: Methods[Name][0], response: RpcResponse<Methods[Name][1]>) => void,
14
+ ) => UnprovideFn
18
15
 
19
16
  unprovide: <Name extends keyof Methods>(name: Name) => void
20
17
 
@@ -25,7 +22,7 @@ export default class RpcHandler<
25
22
  ReturnValue extends Name extends keyof Methods ? Methods[Name][1] : unknown,
26
23
  >(
27
24
  name: Name,
28
- args?: Args,
25
+ args: Args,
29
26
  ): Promise<ReturnValue>
30
27
  <
31
28
  Name extends keyof Methods | string,
@@ -33,7 +30,7 @@ export default class RpcHandler<
33
30
  ReturnValue extends Name extends keyof Methods ? Methods[Name][1] : unknown,
34
31
  >(
35
32
  name: Name,
36
- args: Args | undefined,
33
+ args: Args,
37
34
  callback: (error: unknown, response: ReturnValue) => void,
38
35
  ): void
39
36
  }
@@ -1,5 +1,4 @@
1
1
  export default class RpcResponse<Data> {
2
- completed: boolean
3
2
  reject: () => void
4
3
  error: (error: Error | string) => void
5
4
  send: (data: Data) => void
@@ -1,40 +0,0 @@
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
- })
@@ -1,252 +0,0 @@
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
- }