@pluv/client 0.44.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,6 @@ import type { AbstractCrdtDoc, AbstractCrdtDocFactory, CrdtType } from "@pluv/cr
2
2
  import { noop } from "@pluv/crdt";
3
3
 
4
4
  export type CrdtManagerOptions<TStorage extends Record<string, CrdtType<any, any>> = {}> = {
5
- encodedState?: string | Uint8Array | null;
6
5
  initialStorage?: AbstractCrdtDocFactory<TStorage>;
7
6
  };
8
7
 
@@ -19,13 +18,10 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
19
18
  private readonly _docFactory: AbstractCrdtDocFactory<TStorage>;
20
19
 
21
20
  constructor(options: CrdtManagerOptions<TStorage>) {
22
- const { encodedState, initialStorage = noop.doc({}) as AbstractCrdtDocFactory<TStorage> } = options;
21
+ const { initialStorage = noop.doc({}) as AbstractCrdtDocFactory<TStorage> } = options;
23
22
 
24
23
  this._docFactory = initialStorage;
25
-
26
- this.doc = encodedState
27
- ? initialStorage.getFresh().applyEncodedState({ update: encodedState })
28
- : initialStorage.getFresh();
24
+ this.doc = initialStorage.getEmpty();
29
25
  }
30
26
 
31
27
  /**
@@ -52,35 +48,51 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
52
48
  return this.doc.get(key);
53
49
  }
54
50
 
51
+ public getInitialState(): string {
52
+ return this._docFactory.getInitialized().getEncodedState();
53
+ }
54
+
55
55
  public initialize(params: CrdtManagerInitializeParams): this {
56
56
  const { onInitialized, origin, update } = params;
57
57
 
58
- const updates = typeof update === "string" ? [update] : update;
59
-
60
- if (!updates.length) return this;
61
-
58
+ /**
59
+ * !HACK
60
+ * @description If the doc was already initialized, we don't want to re-initialize
61
+ * the doc and overwrite what's already there. Skip the initialization and log a
62
+ * warning.
63
+ * @date May 7, 2025
64
+ */
62
65
  if (this.initialized) {
63
- this.doc = this._applyDocUpdates(this.doc, updates, origin).track();
64
-
66
+ console.warn("Attempted to initialize storage multiple times");
65
67
  return this;
66
68
  }
67
69
 
68
- const onEmpty = this._applyDocUpdates(this._docFactory.getEmpty(), updates, origin);
69
-
70
- if (!onEmpty.isEmpty()) {
71
- onEmpty.destroy();
70
+ /**
71
+ * !HACK
72
+ * @description If the doc is already populated, somehow we're attempting to overwrite
73
+ * a doc that was presumably initialized but not identified as initialized. Skip the
74
+ * initialization and log a warning.
75
+ * @date May 7, 2025
76
+ */
77
+ if (!this.doc.isEmpty()) {
78
+ console.warn("Attempted to initialize over a non-empty storage");
79
+ return this;
80
+ }
72
81
 
73
- this.doc = this._applyDocUpdates(this._docFactory.getFresh(), updates, origin).track();
74
- this.initialized = true;
82
+ const updates = typeof update === "string" ? [update] : update;
75
83
 
76
- onInitialized?.(this.doc.getEncodedState());
84
+ if (!updates.length) return this;
77
85
 
78
- return this;
79
- }
86
+ const initialized = this._docFactory.getInitialized();
87
+ const reference = initialized.get();
80
88
 
81
- onEmpty.destroy();
82
- this.doc = this._docFactory.getInitialized().track();
89
+ this.doc = this._applyDocUpdates(
90
+ this._docFactory.getEmpty(),
91
+ updates,
92
+ origin,
93
+ ).rebuildStorage(reference);
83
94
  this.initialized = true;
95
+ initialized.destroy();
84
96
 
85
97
  onInitialized?.(this.doc.getEncodedState());
86
98
 
@@ -7,7 +7,10 @@ export class CrdtNotifier<TStorage extends Record<string, CrdtType<any, any>>> {
7
7
  [P in keyof TStorage]: InferCrdtJson<TStorage[P]>;
8
8
  }>();
9
9
 
10
- public subjects = new Map<keyof TStorage, Subject<InferCrdtJson<TStorage[keyof TStorage]>> | null>();
10
+ public subjects = new Map<
11
+ keyof TStorage,
12
+ Subject<InferCrdtJson<TStorage[keyof TStorage]>> | null
13
+ >();
11
14
 
12
15
  public subject<TKey extends keyof TStorage>(key: TKey): Subject<InferCrdtJson<TStorage[TKey]>> {
13
16
  const subject = this.subjects.get(key);
@@ -2,14 +2,20 @@ import type { Id, InferIOOutput, IOEventMessage, IOLike } from "@pluv/types";
2
2
  import type { Subject } from "wonka";
3
3
  import { makeSubject, subscribe } from "wonka";
4
4
 
5
- export type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (
6
- value: Id<IOEventMessage<TIO, TEvent>>,
7
- ) => void;
5
+ export type EventNotifierSubscriptionCallback<
6
+ TIO extends IOLike<any, any>,
7
+ TEvent extends keyof InferIOOutput<TIO>,
8
+ > = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
8
9
 
9
10
  export class EventNotifier<TIO extends IOLike> {
10
- public subjects = new Map<keyof InferIOOutput<TIO>, Subject<Id<IOEventMessage<TIO, keyof InferIOOutput<TIO>>>>>();
11
+ public subjects = new Map<
12
+ keyof InferIOOutput<TIO>,
13
+ Subject<Id<IOEventMessage<TIO, keyof InferIOOutput<TIO>>>>
14
+ >();
11
15
 
12
- public subject<TEvent extends keyof InferIOOutput<TIO>>(name: TEvent): Subject<Id<IOEventMessage<TIO, TEvent>>> {
16
+ public subject<TEvent extends keyof InferIOOutput<TIO>>(
17
+ name: TEvent,
18
+ ): Subject<Id<IOEventMessage<TIO, TEvent>>> {
13
19
  const subject = this.subjects.get(name);
14
20
 
15
21
  if (subject) return subject;
package/src/MockedRoom.ts CHANGED
@@ -86,8 +86,13 @@ export class MockedRoom<
86
86
  presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
87
87
  ...limits,
88
88
  };
89
- this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
90
- this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, limits: this._limits, presence });
89
+ this._router =
90
+ router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
91
+ this._usersManager = new UsersManager<TIO, TPresence>({
92
+ initialPresence,
93
+ limits: this._limits,
94
+ presence,
95
+ });
91
96
 
92
97
  this._crdtManager = new CrdtManager<TStorage>({
93
98
  initialStorage,
@@ -136,10 +141,9 @@ export class MockedRoom<
136
141
  user: myself,
137
142
  };
138
143
 
139
- const output = await (procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>)(
140
- parsed,
141
- context,
142
- );
144
+ const output = await (
145
+ procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>
146
+ )(parsed, context);
143
147
 
144
148
  Object.entries(output).forEach(([_type, _data]) => {
145
149
  this._simulateEvent(_type as TEvent, _data as any);
@@ -147,7 +151,9 @@ export class MockedRoom<
147
151
  },
148
152
  {
149
153
  get(fn, prop) {
150
- return async (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>): Promise<void> => {
154
+ return async (
155
+ data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>,
156
+ ): Promise<void> => {
151
157
  return await fn(prop, data);
152
158
  };
153
159
  },
@@ -174,8 +180,9 @@ export class MockedRoom<
174
180
  ): (() => void) => this._eventNotifier.subscribe(event, callback),
175
181
  {
176
182
  get(fn, prop) {
177
- return (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>): (() => void) =>
178
- fn(prop as any, callback);
183
+ return (
184
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
185
+ ): (() => void) => fn(prop as any, callback);
179
186
  },
180
187
  },
181
188
  ) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
@@ -221,7 +228,9 @@ export class MockedRoom<
221
228
  };
222
229
 
223
230
  public getStorageJson(): InferCrdtJson<TStorage> | null;
224
- public getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
231
+ public getStorageJson<TKey extends keyof TStorage>(
232
+ type: TKey,
233
+ ): InferCrdtJson<TStorage[TKey]> | null;
225
234
  public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
226
235
  if (this._state.connection.id === null) return null;
227
236
 
@@ -293,7 +302,8 @@ export class MockedRoom<
293
302
  };
294
303
 
295
304
  public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
296
- const newPresence = typeof presence === "function" ? presence(this.getMyPresence()) : presence;
305
+ const newPresence =
306
+ typeof presence === "function" ? presence(this.getMyPresence()) : presence;
297
307
 
298
308
  this._usersManager.updateMyPresence(newPresence);
299
309
 
@@ -8,7 +8,10 @@ export type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence exte
8
8
  ) => void;
9
9
 
10
10
  export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
11
- private _subjects = new Map<[clientId: string][0], Subject<Id<UserInfo<TIO, TPresence>> | null>>();
11
+ private _subjects = new Map<
12
+ [clientId: string][0],
13
+ Subject<Id<UserInfo<TIO, TPresence>> | null>
14
+ >();
12
15
 
13
16
  public clear(): void {
14
17
  this._subjects.forEach((subject) => {
@@ -35,7 +38,10 @@ export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}
35
38
  return newSubject;
36
39
  }
37
40
 
38
- public subscribe(clientId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>): () => void {
41
+ public subscribe(
42
+ clientId: string,
43
+ callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
44
+ ): () => void {
39
45
  const source = this.subject(clientId).source;
40
46
 
41
47
  return subscribe(callback)(source).unsubscribe;
package/src/PluvClient.ts CHANGED
@@ -158,7 +158,9 @@ export class PluvClient<
158
158
  };
159
159
 
160
160
  public getRoom = (room: string): PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null => {
161
- const found = this._rooms.get(room) as PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | undefined;
161
+ const found = this._rooms.get(room) as
162
+ | PluvRoom<TIO, TMetadata, TPresence, TStorage, any>
163
+ | undefined;
162
164
 
163
165
  return found ?? null;
164
166
  };
@@ -21,7 +21,8 @@ export class PluvProcedure<
21
21
  TStorage extends Record<string, CrdtType<any, any>> = {},
22
22
  TFilled extends "input" | "broadcast" | "" = "",
23
23
  > {
24
- private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null = null;
24
+ private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null =
25
+ null;
25
26
  private _input: InputZodLike<TInput> | null = null;
26
27
 
27
28
  public get config(): ProcedureLike<TInput, TOutput>["config"] {
@@ -71,7 +72,10 @@ export class PluvProcedure<
71
72
 
72
73
  public input<TData extends JsonObject>(
73
74
  input: InputZodLike<TData>,
74
- ): Omit<PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">, TFilled | "input"> {
75
+ ): Omit<
76
+ PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">,
77
+ TFilled | "input"
78
+ > {
75
79
  return new PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">({ input });
76
80
  }
77
81
 
package/src/PluvRoom.ts CHANGED
@@ -109,7 +109,9 @@ export type AuthEndpoint<TMetadata extends JsonObject> =
109
109
  | string
110
110
  | ((params: EndpointParams<TMetadata>) => string | FetchOptions)
111
111
  | true;
112
- export type WsEndpoint<TMetadata extends JsonObject> = string | ((params: EndpointParams<TMetadata>) => string);
112
+ export type WsEndpoint<TMetadata extends JsonObject> =
113
+ | string
114
+ | ((params: EndpointParams<TMetadata>) => string);
113
115
 
114
116
  type FetchOptions = { url: string; options?: RequestInit };
115
117
 
@@ -128,7 +130,9 @@ export type PluvRoomAddon<
128
130
  TMetadata extends JsonObject = {},
129
131
  TPresence extends JsonObject = {},
130
132
  TStorage extends Record<string, CrdtType<any, any>> = {},
131
- > = (input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TStorage>) => Partial<PluvRoomAddonResult>;
133
+ > = (
134
+ input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TStorage>,
135
+ ) => Partial<PluvRoomAddonResult>;
132
136
 
133
137
  export interface PluvRoomAddonInput<
134
138
  TIO extends IOLike = IOLike,
@@ -276,8 +280,13 @@ export class PluvRoom<
276
280
  },
277
281
  };
278
282
 
279
- this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
280
- this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, limits: this._limits, presence });
283
+ this._router =
284
+ router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
285
+ this._usersManager = new UsersManager<TIO, TPresence>({
286
+ initialPresence,
287
+ limits: this._limits,
288
+ presence,
289
+ });
281
290
  this._crdtManager = new CrdtManager<TStorage>({ initialStorage });
282
291
  }
283
292
 
@@ -325,10 +334,9 @@ export class PluvRoom<
325
334
  user: myself,
326
335
  };
327
336
 
328
- const output = await (procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>)(
329
- parsed,
330
- context,
331
- );
337
+ const output = await (
338
+ procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>
339
+ )(parsed, context);
332
340
 
333
341
  Object.entries(output).forEach(([_type, _data]) => {
334
342
  this._sendMessage({ data: _data, type: _type });
@@ -336,7 +344,9 @@ export class PluvRoom<
336
344
  },
337
345
  {
338
346
  get(fn, prop) {
339
- return async (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>): Promise<void> => {
347
+ return async (
348
+ data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>,
349
+ ): Promise<void> => {
340
350
  return await fn(prop, data);
341
351
  };
342
352
  },
@@ -364,9 +374,11 @@ export class PluvRoom<
364
374
 
365
375
  this._setMetadata(metadata);
366
376
 
367
- const canConnect = [ConnectionState.Closed, ConnectionState.Unavailable, ConnectionState.Untouched].some(
368
- (state) => this._state.connection.state === state,
369
- );
377
+ const canConnect = [
378
+ ConnectionState.Closed,
379
+ ConnectionState.Unavailable,
380
+ ConnectionState.Untouched,
381
+ ].some((state) => this._state.connection.state === state);
370
382
 
371
383
  if (!canConnect) return;
372
384
 
@@ -377,8 +389,8 @@ export class PluvRoom<
377
389
  return oldState;
378
390
  });
379
391
 
380
- await this._storageStore.initialize();
381
- await this._applyStorageStore();
392
+ // await this._storageStore.initialize();
393
+ // await this._applyStorageStore();
382
394
 
383
395
  const wsEndpoint = this._getWsEndpoint(this.id, params);
384
396
  const url = new URL(wsEndpoint);
@@ -453,8 +465,9 @@ export class PluvRoom<
453
465
  ): (() => void) => this._eventNotifier.subscribe(event, callback),
454
466
  {
455
467
  get(fn, prop) {
456
- return (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>): (() => void) =>
457
- fn(prop as any, callback);
468
+ return (
469
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
470
+ ): (() => void) => fn(prop as any, callback);
458
471
  },
459
472
  },
460
473
  ) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
@@ -500,7 +513,9 @@ export class PluvRoom<
500
513
  };
501
514
 
502
515
  public getStorageJson(): InferCrdtJson<TStorage> | null;
503
- public getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
516
+ public getStorageJson<TKey extends keyof TStorage>(
517
+ type: TKey,
518
+ ): InferCrdtJson<TStorage[TKey]> | null;
504
519
  public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
505
520
  if (this._state.connection.id === null) return null;
506
521
 
@@ -574,7 +589,8 @@ export class PluvRoom<
574
589
  };
575
590
 
576
591
  public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
577
- const newPresence = typeof presence === "function" ? presence(this.getMyPresence()) : presence;
592
+ const newPresence =
593
+ typeof presence === "function" ? presence(this.getMyPresence()) : presence;
578
594
 
579
595
  this._usersManager.updateMyPresence(newPresence);
580
596
 
@@ -743,7 +759,10 @@ export class PluvRoom<
743
759
  );
744
760
  };
745
761
 
746
- private _getAuthFetchOptions(room: string, params: GetAuthEndpointParams<TMetadata>): FetchOptions | null {
762
+ private _getAuthFetchOptions(
763
+ room: string,
764
+ params: GetAuthEndpointParams<TMetadata>,
765
+ ): FetchOptions | null {
747
766
  const metadata = params.metadata as TMetadata;
748
767
 
749
768
  if (typeof this._endpoints.authEndpoint === "undefined") return null;
@@ -767,7 +786,10 @@ export class PluvRoom<
767
786
  return typeof result === "string" ? { url: result, options: {} } : result;
768
787
  }
769
788
 
770
- private async _getAuthorization(room: string, params: GetAuthEndpointParams<TMetadata>): Promise<string | null> {
789
+ private async _getAuthorization(
790
+ room: string,
791
+ params: GetAuthEndpointParams<TMetadata>,
792
+ ): Promise<string | null> {
771
793
  const fetchOptions = this._getAuthFetchOptions(room, params);
772
794
 
773
795
  if (!fetchOptions) return null;
@@ -798,7 +820,9 @@ export class PluvRoom<
798
820
 
799
821
  switch (typeof this._endpoints.wsEndpoint) {
800
822
  case "undefined":
801
- return !!this._getPublicKey(params) ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
823
+ return !!this._getPublicKey(params)
824
+ ? `wss://rooms.pluv.io/api/room/${room}`
825
+ : `/api/pluv/room/${room}`;
802
826
  case "string":
803
827
  return this._endpoints.wsEndpoint;
804
828
  default:
@@ -926,6 +950,12 @@ export class PluvRoom<
926
950
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
927
951
 
928
952
  const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$registered"];
953
+ /**
954
+ * TODO
955
+ * @description Maybe we can use this to resolve storage initialization more efficiently
956
+ * @date May 7, 2025
957
+ */
958
+ const state = data.state;
929
959
 
930
960
  this._updateState((oldState) => {
931
961
  oldState.connection.count += 1;
@@ -948,7 +978,7 @@ export class PluvRoom<
948
978
  this._stateNotifier.subjects["my-presence"].next(presence);
949
979
  this._stateNotifier.subjects.myself.next(myself);
950
980
 
951
- const update = this._state.connection.count > 1 ? (this._crdtManager.doc.getEncodedState() ?? null) : null;
981
+ const update = this._crdtManager.getInitialState();
952
982
 
953
983
  this._sendMessage({
954
984
  type: "$initializeSession",
@@ -964,17 +994,17 @@ export class PluvRoom<
964
994
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
965
995
 
966
996
  const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$storageReceived"];
997
+ const state = data.state;
967
998
 
968
999
  this._crdtManager.initialize({
969
1000
  onInitialized: async (update) => {
970
- this._addToStorageStore(update);
1001
+ await this._addToStorageStore(update);
971
1002
  this._emitSharedTypes();
972
-
973
1003
  this._observeCrdt();
974
1004
  this._stateNotifier.subjects["storage-loaded"].next(true);
975
1005
  },
976
1006
  origin: ORIGIN_INITIALIZED,
977
- update: data.state,
1007
+ update: state,
978
1008
  });
979
1009
 
980
1010
  const encodedState = this._crdtManager.doc.getEncodedState();
@@ -998,9 +1028,9 @@ export class PluvRoom<
998
1028
  // Should not reach here
999
1029
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1000
1030
 
1001
- const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>[typeof ORIGIN_STORAGE_UPDATED];
1002
-
1003
- if (!this._crdtManager) return;
1031
+ const data = message.data as BaseIOEventRecord<
1032
+ InferIOAuthorize<TIO>
1033
+ >[typeof ORIGIN_STORAGE_UPDATED];
1004
1034
 
1005
1035
  this._crdtManager.doc.applyEncodedState({
1006
1036
  origin: ORIGIN_STORAGE_UPDATED,
@@ -1011,8 +1041,6 @@ export class PluvRoom<
1011
1041
 
1012
1042
  const storageRoot = Object.keys(sharedTypes).reduce(
1013
1043
  (acc, prop) => {
1014
- if (!this._crdtManager) return acc;
1015
-
1016
1044
  const serialized = this._crdtManager.doc.toJson(prop);
1017
1045
 
1018
1046
  this._crdtNotifier.subject(prop).next(serialized);
@@ -1038,7 +1066,9 @@ export class PluvRoom<
1038
1066
  const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$syncStateReceived"];
1039
1067
  const active = new Set(data?.connectionIds ?? []);
1040
1068
 
1041
- const quitters = this._usersManager.getOthers().filter((other) => !active.has(other.connectionId));
1069
+ const quitters = this._usersManager
1070
+ .getOthers()
1071
+ .filter((other) => !active.has(other.connectionId));
1042
1072
 
1043
1073
  quitters.forEach((quitter) => {
1044
1074
  this._usersManager.deleteConnection(quitter.connectionId);
@@ -1080,7 +1110,10 @@ export class PluvRoom<
1080
1110
 
1081
1111
  private _heartbeat(): void {
1082
1112
  this._clearTimeout(this._timeouts.pong);
1083
- this._timeouts.pong = setTimeout(this._reconnect.bind(this), PONG_TIMEOUT_MS) as unknown as number;
1113
+ this._timeouts.pong = setTimeout(
1114
+ this._reconnect.bind(this),
1115
+ PONG_TIMEOUT_MS,
1116
+ ) as unknown as number;
1084
1117
 
1085
1118
  /**
1086
1119
  * !HACK
@@ -1099,8 +1132,6 @@ export class PluvRoom<
1099
1132
  private _observeCrdt(): void {
1100
1133
  this._subscriptions.observeCrdt?.();
1101
1134
 
1102
- if (!this._crdtManager) return;
1103
-
1104
1135
  const unsubscribe = this._crdtManager.doc.subscribe((event) => {
1105
1136
  const origin = event.origin ?? null;
1106
1137
 
@@ -1193,7 +1224,9 @@ export class PluvRoom<
1193
1224
  if (!message) return;
1194
1225
 
1195
1226
  const shouldLog =
1196
- typeof this._debug === "boolean" ? this._debug : this._debug.output.find((value) => value === message.type);
1227
+ typeof this._debug === "boolean"
1228
+ ? this._debug
1229
+ : this._debug.output.find((value) => value === message.type);
1197
1230
 
1198
1231
  if (shouldLog) {
1199
1232
  this._logDebug("WebSocket event received: ", message.type, message);
@@ -1246,7 +1279,6 @@ export class PluvRoom<
1246
1279
  if (this._state.connection.state !== ConnectionState.Connecting) return;
1247
1280
 
1248
1281
  this._logDebug("WebSocket connected");
1249
-
1250
1282
  this._updateState((oldState) => {
1251
1283
  oldState.connection.state = ConnectionState.Open;
1252
1284
 
@@ -1254,7 +1286,10 @@ export class PluvRoom<
1254
1286
  });
1255
1287
 
1256
1288
  this._clearInterval(this._intervals.heartbeat);
1257
- this._intervals.heartbeat = setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS) as unknown as number;
1289
+ this._intervals.heartbeat = setInterval(
1290
+ this._heartbeat.bind(this),
1291
+ HEARTBEAT_INTERVAL_MS,
1292
+ ) as unknown as number;
1258
1293
  }
1259
1294
 
1260
1295
  private async _onNavigatorOffline(): Promise<void> {
@@ -1325,9 +1360,15 @@ export class PluvRoom<
1325
1360
  typeof this._reconnectTimeoutMs === "number"
1326
1361
  ? this._reconnectTimeoutMs
1327
1362
  : this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
1328
- const clampedMs = Math.max(Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs), MAX_RECONNECT_TIMEOUT_MS);
1363
+ const clampedMs = Math.max(
1364
+ Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs),
1365
+ MAX_RECONNECT_TIMEOUT_MS,
1366
+ );
1329
1367
 
1330
- this._timeouts.reconnect = setTimeout(this._reconnect.bind(this), clampedMs) as unknown as number;
1368
+ this._timeouts.reconnect = setTimeout(
1369
+ this._reconnect.bind(this),
1370
+ clampedMs,
1371
+ ) as unknown as number;
1331
1372
  }
1332
1373
 
1333
1374
  private async _reconnect(): Promise<void> {
@@ -1342,13 +1383,17 @@ export class PluvRoom<
1342
1383
  if (this._state.connection.state === ConnectionState.Closed) return;
1343
1384
 
1344
1385
  const metadata = this._lastMetadata;
1345
- const params = (typeof metadata === "undefined" ? [] : [{ metadata }]) as RoomConnectParams<TMetadata>;
1386
+ const params = (
1387
+ typeof metadata === "undefined" ? [] : [{ metadata }]
1388
+ ) as RoomConnectParams<TMetadata>;
1346
1389
 
1347
1390
  await this.connect(...params);
1348
1391
  }
1349
1392
 
1350
1393
  private _sendMessage(data: string): void;
1351
- private _sendMessage<TMessage extends EventMessage<string, any> = EventMessage<string, any>>(data: TMessage): void;
1394
+ private _sendMessage<TMessage extends EventMessage<string, any> = EventMessage<string, any>>(
1395
+ data: TMessage,
1396
+ ): void;
1352
1397
  private _sendMessage(data: any): void {
1353
1398
  const webSocket = this._state.webSocket;
1354
1399
 
@@ -1387,7 +1432,9 @@ export class PluvRoom<
1387
1432
  return parsed;
1388
1433
  }
1389
1434
 
1390
- private _updateState(updater: (oldState: WebSocketState<TIO>) => WebSocketState<TIO>): WebSocketState<TIO> {
1435
+ private _updateState(
1436
+ updater: (oldState: WebSocketState<TIO>) => WebSocketState<TIO>,
1437
+ ): WebSocketState<TIO> {
1391
1438
  let authorization: AuthorizationState<TIO>;
1392
1439
 
1393
1440
  try {
@@ -1404,11 +1451,9 @@ export class PluvRoom<
1404
1451
  };
1405
1452
 
1406
1453
  const newState = updater(oldState);
1407
-
1408
- this._logDebug("WebSocket new state: ", newState);
1409
-
1410
1454
  this._state = newState;
1411
1455
 
1456
+ this._logDebug("WebSocket new state: ", newState);
1412
1457
  this._stateNotifier.subjects.connection.next(newState);
1413
1458
 
1414
1459
  return newState;
package/src/PluvRouter.ts CHANGED
@@ -67,7 +67,10 @@ export class PluvRouter<
67
67
  public static merge<TRouters extends PluvRouter<any, any, any, any>[]>(
68
68
  ...routers: TRouters
69
69
  ): MergedRouter<TRouters> {
70
- const events = Object.assign(Object.create(null), ...routers.map((router) => router._defs.events));
70
+ const events = Object.assign(
71
+ Object.create(null),
72
+ ...routers.map((router) => router._defs.events),
73
+ );
71
74
 
72
75
  if (Object.keys(events).some((key) => key.startsWith("$"))) {
73
76
  throw new Error('Procedures may not start with "$"');
@@ -15,7 +15,10 @@ type InferSubjectValue<
15
15
  TIO extends IOLike,
16
16
  TPresence extends JsonObject,
17
17
  TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
18
- > = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
18
+ > =
19
+ StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
20
+ ? Id<IValue>
21
+ : never;
19
22
 
20
23
  export type SubscriptionCallback<
21
24
  TIO extends IOLike,