@pluv/client 2.1.0 → 2.2.0

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.
@@ -1,29 +1,34 @@
1
- import type { CrdtType } from "@pluv/crdt";
1
+ import type { AbstractCrdtDocFactory, InferDocLike } from "@pluv/crdt";
2
2
  import type { EventRecord, IOLike, InputZodLike, JsonObject, ProcedureLike } from "@pluv/types";
3
3
  import type { EventResolver, MergeEventRecords } from "./types";
4
4
 
5
5
  export interface PluvProcedureConfig<
6
- TIO extends IOLike = IOLike,
7
- TInput extends JsonObject = {},
8
- TOutput extends EventRecord<string, any> = {},
9
- TPresence extends JsonObject = {},
10
- TStorage extends Record<string, CrdtType<any, any>> = {},
6
+ TIO extends IOLike,
7
+ TInput extends JsonObject,
8
+ TOutput extends EventRecord<string, any>,
9
+ TPresence extends Record<string, any>,
10
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
11
11
  > {
12
- broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, TStorage> | null;
12
+ broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, InferDocLike<TCrdt>> | null;
13
13
  input?: InputZodLike<TInput>;
14
14
  }
15
15
 
16
16
  export class PluvProcedure<
17
- TIO extends IOLike = IOLike,
18
- TInput extends JsonObject = {},
19
- TOutput extends EventRecord<string, any> = {},
20
- TPresence extends JsonObject = {},
21
- TStorage extends Record<string, CrdtType<any, any>> = {},
22
- TFilled extends "input" | "broadcast" | "" = "",
17
+ TIO extends IOLike,
18
+ TInput extends JsonObject,
19
+ TOutput extends EventRecord<string, any>,
20
+ TPresence extends Record<string, any>,
21
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
22
+ TFilled extends "input" | "broadcast" | "",
23
23
  > implements ProcedureLike<TInput, TOutput>
24
24
  {
25
- private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null =
26
- null;
25
+ private _broadcast: EventResolver<
26
+ TIO,
27
+ TInput,
28
+ Partial<TOutput>,
29
+ TPresence,
30
+ InferDocLike<TCrdt>
31
+ > | null = null;
27
32
  private _input: InputZodLike<TInput> | null = null;
28
33
 
29
34
  public get config(): ProcedureLike<TInput, TOutput>["config"] {
@@ -34,7 +39,7 @@ export class PluvProcedure<
34
39
  } as ProcedureLike<TInput, TOutput>["config"];
35
40
  }
36
41
 
37
- constructor(config: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TStorage> = {}) {
42
+ constructor(config: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TCrdt> = {}) {
38
43
  const { broadcast, input } = config;
39
44
 
40
45
  this._broadcast = broadcast ?? null;
@@ -42,14 +47,14 @@ export class PluvProcedure<
42
47
  }
43
48
 
44
49
  public broadcast<TResult extends EventRecord<string, any> = {}>(
45
- resolver: EventResolver<TIO, TInput, TResult, TPresence, TStorage>,
50
+ resolver: EventResolver<TIO, TInput, TResult, TPresence, InferDocLike<TCrdt>>,
46
51
  ): Omit<
47
52
  PluvProcedure<
48
53
  TIO,
49
54
  TInput,
50
55
  MergeEventRecords<[TOutput, TResult]>,
51
56
  TPresence,
52
- TStorage,
57
+ TCrdt,
53
58
  TFilled | "input" | "broadcast"
54
59
  >,
55
60
  TFilled | "input" | "broadcast"
@@ -63,7 +68,7 @@ export class PluvProcedure<
63
68
  TInput,
64
69
  MergeEventRecords<[TOutput, TResult]>,
65
70
  TPresence,
66
- TStorage,
71
+ TCrdt,
67
72
  TFilled | "input" | "broadcast"
68
73
  >({
69
74
  ...(this.config as any),
@@ -73,14 +78,11 @@ export class PluvProcedure<
73
78
 
74
79
  public input<TData extends JsonObject>(
75
80
  input: InputZodLike<TData>,
76
- ): Omit<
77
- PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">,
78
- TFilled | "input"
79
- > {
80
- return new PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">({ input });
81
+ ): Omit<PluvProcedure<TIO, TData, {}, TPresence, TCrdt, TFilled | "input">, TFilled | "input"> {
82
+ return new PluvProcedure<TIO, TData, {}, TPresence, TCrdt, TFilled | "input">({ input });
81
83
  }
82
84
 
83
- private _resolver(): EventResolver<TIO, TInput, TOutput, TPresence, TStorage> {
85
+ private _resolver(): EventResolver<TIO, TInput, TOutput, TPresence, InferDocLike<TCrdt>> {
84
86
  return (data, context) => this._broadcast?.(data, context) as TOutput;
85
87
  }
86
88
  }
package/src/PluvRoom.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type {
2
2
  AbstractCrdtDocFactory,
3
3
  InferCrdtJson,
4
+ InferDoc,
5
+ InferDocLike,
4
6
  InferStorage,
5
7
  NoopCrdtDocFactory,
6
8
  } from "@pluv/crdt";
@@ -25,6 +27,7 @@ import type {
25
27
  OptionalProps,
26
28
  OtherSubscriptionCallback,
27
29
  OthersSubscriptionCallback,
30
+ RoomEventListenerMap,
28
31
  RoomLike,
29
32
  StateNotifierSubjects,
30
33
  StorageProxy,
@@ -34,14 +37,16 @@ import type {
34
37
  SubscriptionCallback,
35
38
  UpdateMyPresenceAction,
36
39
  UserInfo,
40
+ WebSocketConnection,
37
41
  WebSocketState,
38
42
  } from "@pluv/types";
39
- import { ConnectionState } from "@pluv/types";
43
+ import { ConnectionState, StorageState } from "@pluv/types";
40
44
  import type { AbstractStorageStore } from "./AbstractStorageStore";
41
45
  import type { CrdtManagerOptions } from "./CrdtManager";
42
46
  import { CrdtManager } from "./CrdtManager";
43
47
  import { CrdtNotifier } from "./CrdtNotifier";
44
48
  import { EventNotifier } from "./EventNotifier";
49
+ import { ListenerManager } from "./ListenerManager";
45
50
  import { PluvProcedure } from "./PluvProcedure";
46
51
  import type { PluvRouterEventConfig } from "./PluvRouter";
47
52
  import { PluvRouter } from "./PluvRouter";
@@ -54,7 +59,6 @@ import type {
54
59
  InternalSubscriptions,
55
60
  PluvClientLimits,
56
61
  PublicKey,
57
- WebSocketConnection,
58
62
  WithMetadata,
59
63
  } from "./types";
60
64
  import type { UsersManagerConfig } from "./UsersManager";
@@ -85,8 +89,8 @@ declare global {
85
89
  export const DEFAULT_PLUV_CLIENT_ADDON = <
86
90
  TIO extends IOLike,
87
91
  TMetadata extends JsonObject,
88
- TPresence extends JsonObject,
89
- TCrdt extends AbstractCrdtDocFactory<any>,
92
+ TPresence extends Record<string, any>,
93
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
90
94
  >(
91
95
  input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TCrdt>,
92
96
  ): PluvRoomAddonResult => ({
@@ -102,9 +106,9 @@ interface WindowListeners {
102
106
 
103
107
  interface WebSocketListeners {
104
108
  onClose: (event: CloseEvent) => void;
105
- onError: () => void;
109
+ onError: (event: Event) => void;
106
110
  onMessage: (message: MessageEvent<string>) => void;
107
- onOpen: () => void;
111
+ onOpen: (event: Event) => void;
108
112
  }
109
113
 
110
114
  interface IntervalIds {
@@ -143,15 +147,15 @@ interface InternalListeners {
143
147
  export type PluvRoomAddon<
144
148
  TIO extends IOLike,
145
149
  TMetadata extends JsonObject,
146
- TPresence extends JsonObject,
147
- TCrdt extends AbstractCrdtDocFactory<any>,
150
+ TPresence extends Record<string, any>,
151
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
148
152
  > = (input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TCrdt>) => Partial<PluvRoomAddonResult>;
149
153
 
150
154
  export interface PluvRoomAddonInput<
151
155
  TIO extends IOLike,
152
156
  TMetadata extends JsonObject,
153
- TPresence extends JsonObject,
154
- TCrdt extends AbstractCrdtDocFactory<any>,
157
+ TPresence extends Record<string, any>,
158
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
155
159
  > {
156
160
  room: PluvRoom<TIO, TMetadata, TPresence, TCrdt>;
157
161
  }
@@ -181,8 +185,8 @@ export type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) =>
181
185
  export type RoomConfig<
182
186
  TIO extends IOLike,
183
187
  TMetadata extends JsonObject,
184
- TPresence extends JsonObject,
185
- TCrdt extends AbstractCrdtDocFactory<any>,
188
+ TPresence extends Record<string, any>,
189
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
186
190
  TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>>,
187
191
  > = Id<
188
192
  {
@@ -202,10 +206,10 @@ export type RoomConfig<
202
206
  export class PluvRoom<
203
207
  TIO extends IOLike<any, any>,
204
208
  TMetadata extends JsonObject = {},
205
- TPresence extends JsonObject = {},
206
- TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory,
209
+ TPresence extends Record<string, any> = {},
210
+ TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory,
207
211
  TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
208
- > implements RoomLike<TIO, TPresence, InferStorage<TCrdt>>
212
+ > implements RoomLike<TIO, InferDoc<TCrdt>, TPresence, InferStorage<TCrdt>>
209
213
  {
210
214
  readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
211
215
 
@@ -220,6 +224,7 @@ export class PluvRoom<
220
224
  heartbeat: null,
221
225
  };
222
226
  private readonly _limits: PluvClientLimits;
227
+ private readonly _listenerManager = new ListenerManager();
223
228
  private readonly _listeners: InternalListeners;
224
229
  private readonly _publicKey: PublicKey<TMetadata> | null = null;
225
230
  private readonly _reconnectTimeoutMs: ReconnectTimeoutMs;
@@ -244,10 +249,12 @@ export class PluvRoom<
244
249
  },
245
250
  connection: {
246
251
  attempts: 0,
247
- count: 0,
248
252
  id: null,
249
253
  state: ConnectionState.Untouched,
250
254
  },
255
+ storage: {
256
+ state: StorageState.Unavailable,
257
+ },
251
258
  webSocket: null,
252
259
  };
253
260
  private _windowListeners: WindowListeners | null = null;
@@ -304,14 +311,24 @@ export class PluvRoom<
304
311
  this._crdtManager = new CrdtManager<TCrdt>({ initialStorage });
305
312
  }
306
313
 
314
+ /**
315
+ * @deprecated To be removed in v3. Use `getStorageLoaded` instead.
316
+ */
307
317
  public get storageLoaded(): boolean {
308
- return this._crdtManager.initialized;
318
+ return this.getStorageLoaded();
309
319
  }
310
320
 
311
321
  public get webSocket(): WebSocket | null {
312
322
  return this._state.webSocket;
313
323
  }
314
324
 
325
+ public addEventListener<TKind extends keyof RoomEventListenerMap>(
326
+ kind: TKind,
327
+ handler: RoomEventListenerMap[TKind],
328
+ ): () => void {
329
+ return this._listenerManager.subscribe(kind, handler as any);
330
+ }
331
+
315
332
  public broadcast = new Proxy(
316
333
  async <TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
317
334
  event: TEvent,
@@ -327,7 +344,8 @@ export class PluvRoom<
327
344
  any,
328
345
  any,
329
346
  TPresence,
330
- InferStorage<TCrdt>
347
+ TCrdt,
348
+ ""
331
349
  > | null;
332
350
 
333
351
  if (!procedure?.config.broadcast) {
@@ -341,7 +359,7 @@ export class PluvRoom<
341
359
  if (!myself) return;
342
360
 
343
361
  const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
344
- const context: EventResolverContext<TIO, TPresence, InferStorage<TCrdt>> = {
362
+ const context: EventResolverContext<TIO, TPresence, InferDocLike<TCrdt>> = {
345
363
  doc: this._crdtManager.doc,
346
364
  others: this._usersManager.getOthers(),
347
365
  room: this.id,
@@ -436,6 +454,7 @@ export class PluvRoom<
436
454
  this._updateState((oldState) => {
437
455
  oldState.connection.attempts = 0;
438
456
  oldState.authorization.token = authToken;
457
+ oldState.storage.state = StorageState.Loading;
439
458
  oldState.webSocket = webSocket;
440
459
 
441
460
  return oldState;
@@ -460,6 +479,10 @@ export class PluvRoom<
460
479
  this._updateState((oldState) => {
461
480
  oldState.authorization.token = null;
462
481
  oldState.connection.state = ConnectionState.Closed;
482
+ oldState.storage.state =
483
+ oldState.storage.state === StorageState.Synced
484
+ ? StorageState.Offline
485
+ : StorageState.Unavailable;
463
486
  oldState.webSocket = null;
464
487
 
465
488
  return oldState;
@@ -492,7 +515,7 @@ export class PluvRoom<
492
515
  return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
493
516
  };
494
517
 
495
- public getDoc = (): CrdtDocLike<InferStorage<TCrdt>> => {
518
+ public getDoc = (): CrdtDocLike<InferDoc<TCrdt>, InferStorage<TCrdt>> => {
496
519
  return this._crdtManager.doc;
497
520
  };
498
521
 
@@ -528,7 +551,6 @@ export class PluvRoom<
528
551
  ): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null;
529
552
  public getStorageJson<TKey extends keyof InferStorage<TCrdt>>(type?: TKey) {
530
553
  if (this._state.connection.id === null) return null;
531
-
532
554
  if (typeof type === "undefined") return this._crdtManager.doc.toJson();
533
555
 
534
556
  const sharedType = this._crdtManager.get(type);
@@ -538,6 +560,13 @@ export class PluvRoom<
538
560
  return this._crdtManager.doc.toJson(type);
539
561
  }
540
562
 
563
+ public getStorageLoaded = (): boolean => {
564
+ return (
565
+ this._state.storage.state === StorageState.Offline ||
566
+ this._state.storage.state === StorageState.Synced
567
+ );
568
+ };
569
+
541
570
  /**
542
571
  * @deprecated To be removed in v3. Use PluvRoom.subscribe.other instead
543
572
  */
@@ -557,7 +586,7 @@ export class PluvRoom<
557
586
  };
558
587
 
559
588
  /**
560
- * @deprecated To be removed in v3. To be removed in v3. Use PluvRoom.subscribe.storage instead
589
+ * @deprecated To be removed in v3. Use PluvRoom.subscribe.storage instead
561
590
  */
562
591
  public storage = new Proxy(
563
592
  <TKey extends keyof InferStorage<TCrdt>>(
@@ -1041,7 +1070,6 @@ export class PluvRoom<
1041
1070
  const state = data.state;
1042
1071
 
1043
1072
  this._updateState((oldState) => {
1044
- oldState.connection.count += 1;
1045
1073
  oldState.connection.id = connectionId;
1046
1074
  oldState.authorization.user = user ?? null;
1047
1075
 
@@ -1114,6 +1142,11 @@ export class PluvRoom<
1114
1142
 
1115
1143
  this._emitSharedTypes();
1116
1144
  this._observeCrdt();
1145
+ this._updateState((oldState) => {
1146
+ oldState.storage.state = StorageState.Synced;
1147
+
1148
+ return oldState;
1149
+ });
1117
1150
  this._stateNotifier.subjects["storage-loaded"].next(true);
1118
1151
 
1119
1152
  this._sendMessage({
@@ -1277,12 +1310,15 @@ export class PluvRoom<
1277
1310
 
1278
1311
  this._updateState((oldState) => {
1279
1312
  oldState.connection.state = ConnectionState.Unavailable;
1313
+ oldState.storage.state = StorageState.Unavailable;
1280
1314
 
1281
1315
  return oldState;
1282
1316
  });
1283
1317
  }
1284
1318
 
1285
1319
  private async _onClose(event: CloseEvent): Promise<void> {
1320
+ this._listenerManager.subjects.close.next(event);
1321
+
1286
1322
  this._logDebug("WebSocket closed");
1287
1323
  if (!!event.reason) this._logDebug(event.reason);
1288
1324
 
@@ -1312,6 +1348,10 @@ export class PluvRoom<
1312
1348
  * @date April 28, 2025
1313
1349
  */
1314
1350
  ConnectionState.Closed;
1351
+ oldState.storage.state =
1352
+ oldState.storage.state === StorageState.Synced
1353
+ ? StorageState.Offline
1354
+ : StorageState.Unavailable;
1315
1355
  oldState.webSocket = null;
1316
1356
 
1317
1357
  return oldState;
@@ -1331,9 +1371,13 @@ export class PluvRoom<
1331
1371
  * @description Handle websocket errors in a meaningful way, should they occur
1332
1372
  * @date August 19, 2022
1333
1373
  */
1334
- private _onError(): void {}
1374
+ private _onError(event: Event): void {
1375
+ this._listenerManager.subjects.error.next(event);
1376
+ }
1335
1377
 
1336
1378
  private _onMessage(event: MessageEvent<string>): void {
1379
+ this._listenerManager.subjects.message.next(event);
1380
+
1337
1381
  const message = this._parseMessage(event);
1338
1382
 
1339
1383
  if (!message) return;
@@ -1390,7 +1434,9 @@ export class PluvRoom<
1390
1434
  }
1391
1435
  }
1392
1436
 
1393
- private _onOpen(): void {
1437
+ private _onOpen(event: Event): void {
1438
+ this._listenerManager.subjects.open.next(event);
1439
+
1394
1440
  if (this._state.connection.state !== ConnectionState.Connecting) return;
1395
1441
 
1396
1442
  this._logDebug("WebSocket connected");
@@ -1412,6 +1458,10 @@ export class PluvRoom<
1412
1458
 
1413
1459
  this._updateState((oldState) => {
1414
1460
  oldState.connection.state = ConnectionState.Unavailable;
1461
+ oldState.storage.state =
1462
+ oldState.storage.state === StorageState.Synced
1463
+ ? StorageState.Offline
1464
+ : StorageState.Unavailable;
1415
1465
 
1416
1466
  return oldState;
1417
1467
  });
@@ -1562,6 +1612,7 @@ export class PluvRoom<
1562
1612
  ...this._state,
1563
1613
  authorization,
1564
1614
  connection: JSON.parse(JSON.stringify(this._state.connection)),
1615
+ storage: JSON.parse(JSON.stringify(this._state.storage)),
1565
1616
  webSocket: this._state.webSocket,
1566
1617
  };
1567
1618
 
package/src/PluvRouter.ts CHANGED
@@ -1,31 +1,31 @@
1
- import type { CrdtType } from "@pluv/crdt";
1
+ import type { AbstractCrdtDocFactory } from "@pluv/crdt";
2
2
  import type { IOLike, IORouterLike, JsonObject } from "@pluv/types";
3
3
  import type { PluvProcedure } from "./PluvProcedure";
4
4
 
5
5
  export type PluvRouterEventConfig<
6
- TIO extends IOLike = IOLike,
7
- TPresence extends JsonObject = {},
8
- TStorage extends Record<string, CrdtType<any, any>> = {},
9
- > = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TStorage>, "config"> };
6
+ TIO extends IOLike,
7
+ TPresence extends Record<string, any>,
8
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
9
+ > = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TCrdt, "">, "config"> };
10
10
 
11
11
  export type MergedRouter<
12
- TRouters extends PluvRouter<TIO, TPresence, TStorage, any>[] = [],
13
- TIO extends IOLike = IOLike,
14
- TPresence extends JsonObject = {},
15
- TStorage extends Record<string, CrdtType<any, any>> = {},
16
- TRoot extends TRouters[0]["_defs"]["events"] = {},
12
+ TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
13
+ TIO extends IOLike,
14
+ TPresence extends Record<string, any>,
15
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
16
+ TRoot extends TRouters[0]["_defs"]["events"],
17
17
  > = TRouters extends [
18
- infer IHead extends PluvRouter<TIO, TPresence, TStorage, any>,
19
- ...infer ITail extends PluvRouter<TIO, TPresence, TStorage, any>[],
18
+ infer IHead extends PluvRouter<TIO, TPresence, TCrdt, any>,
19
+ ...infer ITail extends PluvRouter<TIO, TPresence, TCrdt, any>[],
20
20
  ]
21
- ? MergedRouter<ITail, TIO, TPresence, TStorage, TRoot & IHead["_defs"]["events"]>
22
- : PluvRouter<TIO, TPresence, TStorage, TRoot>;
21
+ ? MergedRouter<ITail, TIO, TPresence, TCrdt, TRoot & IHead["_defs"]["events"]>
22
+ : PluvRouter<TIO, TPresence, TCrdt, TRoot>;
23
23
 
24
24
  export class PluvRouter<
25
- TIO extends IOLike = IOLike,
26
- TPresence extends JsonObject = {},
27
- TStorage extends Record<string, CrdtType<any, any>> = {},
28
- TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
25
+ TIO extends IOLike,
26
+ TPresence extends Record<string, any>,
27
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
28
+ TEvents extends PluvRouterEventConfig<TIO, TPresence, TCrdt> = {},
29
29
  > implements IORouterLike<TEvents>
30
30
  {
31
31
  readonly _defs: { events: TEvents } = { events: {} as TEvents };
@@ -42,9 +42,12 @@ export class PluvRouter<
42
42
  this._defs = { events };
43
43
  }
44
44
 
45
- public static merge<TRouters extends PluvRouter<any, any, any, any>[]>(
46
- ...routers: TRouters
47
- ): MergedRouter<TRouters> {
45
+ public static merge<
46
+ TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
47
+ TIO extends IOLike,
48
+ TPresence extends Record<string, any>,
49
+ TCrdt extends AbstractCrdtDocFactory<any, any>,
50
+ >(...routers: TRouters): MergedRouter<TRouters, TIO, TPresence, TCrdt, {}> {
48
51
  const events = Object.assign(
49
52
  Object.create(null),
50
53
  ...routers.map((router) => router._defs.events),
@@ -54,7 +57,13 @@ export class PluvRouter<
54
57
  throw new Error('Procedures may not start with "$"');
55
58
  }
56
59
 
57
- return new PluvRouter<any, any, any, any>(events) as MergedRouter<TRouters>;
60
+ return new PluvRouter<any, any, any, any>(events) as MergedRouter<
61
+ TRouters,
62
+ TIO,
63
+ TPresence,
64
+ TCrdt,
65
+ {}
66
+ >;
58
67
  }
59
68
 
60
69
  private _isValidEventName(name: string): boolean {
@@ -12,14 +12,14 @@ import { makeSubject, subscribe } from "wonka";
12
12
 
13
13
  type InferSubjectValue<
14
14
  TIO extends IOLike,
15
- TPresence extends JsonObject,
15
+ TPresence extends Record<string, any>,
16
16
  TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
17
17
  > =
18
18
  StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
19
19
  ? Id<IValue>
20
20
  : never;
21
21
 
22
- export class StateNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
22
+ export class StateNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
23
23
  public subjects: StateNotifierSubjects<TIO, TPresence> = {
24
24
  connection: makeSubject<Id<WebSocketState<TIO>>>(),
25
25
  "my-presence": makeSubject<TPresence>(),
@@ -1,29 +1,33 @@
1
1
  import type { InputZodLike, IOLike, JsonObject, OptionalProps, UserInfo } from "@pluv/types";
2
+ import { PLUV_PRESENCE_META_KEY } from "./constants";
2
3
  import type { PluvClientLimits } from "./types";
3
4
  import { pickBy } from "./utils";
4
5
 
5
6
  export type Presence = Record<string, unknown>;
6
7
 
7
- export type UsersManagerConfig<TPresence extends JsonObject = {}> = {
8
+ export type UsersManagerConfig<TPresence extends Record<string, any> = {}> = {
8
9
  initialPresence?: TPresence;
9
10
  limits: PluvClientLimits;
10
11
  presence?: InputZodLike<TPresence>;
11
12
  };
12
13
 
13
- export type AddConnectionResult<TIO extends IOLike, TPresence extends JsonObject = {}> = {
14
+ export type AddConnectionResult<TIO extends IOLike, TPresence extends Record<string, any> = {}> = {
14
15
  clientId: string;
15
16
  data: UserInfo<TIO, TPresence>;
16
17
  isMyself: boolean;
17
18
  remaining: number;
18
19
  };
19
20
 
20
- export type DeleteConnectionResult<TIO extends IOLike, TPresence extends JsonObject = {}> = {
21
+ export type DeleteConnectionResult<
22
+ TIO extends IOLike,
23
+ TPresence extends Record<string, any> = {},
24
+ > = {
21
25
  clientId: string;
22
26
  data: UserInfo<TIO, TPresence>;
23
27
  remaining: number;
24
28
  };
25
29
 
26
- export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}> {
30
+ export class UsersManager<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
27
31
  public readonly initialPresence: TPresence;
28
32
 
29
33
  private readonly _idMap = {
@@ -210,6 +214,16 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
210
214
  } as TPresence;
211
215
  const validated = this._presence ? this._presence.parse(presence) : presence;
212
216
 
217
+ /**
218
+ * !HACK
219
+ * @description We're patching internal metadata back into the presence field so it doesn't
220
+ * get stripped from validation
221
+ * @date May 13, 2025
222
+ */
223
+ if (!!presence[PLUV_PRESENCE_META_KEY]) {
224
+ (validated as any)[PLUV_PRESENCE_META_KEY] = presence[PLUV_PRESENCE_META_KEY];
225
+ }
226
+
213
227
  this._others.set(clientId, { ...other, presence: validated });
214
228
 
215
229
  return validated;
@@ -10,7 +10,7 @@ import type {
10
10
  import type { Subject } from "wonka";
11
11
  import { makeSubject, subscribe, TypeOfSource } from "wonka";
12
12
 
13
- export class UsersNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
13
+ export class UsersNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
14
14
  public readonly others = makeSubject<{
15
15
  others: readonly Id<UserInfo<TIO, TPresence>>[];
16
16
  event: OthersSubscriptionEvent<TIO, TPresence>;
package/src/constants.ts CHANGED
@@ -1 +1,2 @@
1
1
  export const MAX_PRESENCE_SIZE_BYTES = 512;
2
+ export const PLUV_PRESENCE_META_KEY = "__pluv";
@@ -5,8 +5,8 @@ import { PluvClient } from "./PluvClient";
5
5
 
6
6
  export const createClient = <
7
7
  TIO extends IOLike<any, any>,
8
- TPresence extends JsonObject = {},
9
- TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory,
8
+ TPresence extends Record<string, any> = {},
9
+ TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory,
10
10
  TMetadata extends JsonObject = {},
11
11
  >(
12
12
  options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>,
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export type {
9
9
  MergeEvents,
10
10
  RoomLike,
11
11
  UserInfo,
12
+ WebSocketConnection,
12
13
  WebSocketState,
13
14
  } from "@pluv/types";
14
15
  export { AbstractStorageStore } from "./AbstractStorageStore";
@@ -32,4 +33,4 @@ export type {
32
33
  export type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
33
34
  export { register } from "./register";
34
35
  export type { RegisterParams } from "./register";
35
- export type { InferMetadata, PublicKey, PublicKeyParams, WebSocketConnection } from "./types";
36
+ export type { InferMetadata, PublicKey, PublicKeyParams } from "./types";