@pluv/client 2.0.2 → 2.1.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.
package/dist/index.mjs CHANGED
@@ -200,35 +200,6 @@ var EventNotifier = class {
200
200
  }
201
201
  };
202
202
 
203
- // src/OtherNotifier.ts
204
- import { makeSubject as makeSubject3, subscribe as subscribe3 } from "wonka";
205
- var OtherNotifier = class {
206
- constructor() {
207
- this._subjects = /* @__PURE__ */ new Map();
208
- }
209
- clear() {
210
- this._subjects.forEach((subject) => {
211
- subject.next(null);
212
- });
213
- this._subjects.clear();
214
- }
215
- delete(clientId) {
216
- this.subject(clientId).next(null);
217
- this._subjects.delete(clientId);
218
- }
219
- subject(clientId) {
220
- const subject = this._subjects.get(clientId);
221
- if (subject) return subject;
222
- const newSubject = makeSubject3();
223
- this._subjects.set(clientId, newSubject);
224
- return newSubject;
225
- }
226
- subscribe(clientId, callback) {
227
- const source = this.subject(clientId).source;
228
- return subscribe3(callback)(source).unsubscribe;
229
- }
230
- };
231
-
232
203
  // src/PluvRouter.ts
233
204
  var PluvRouter = class _PluvRouter {
234
205
  constructor(events) {
@@ -257,22 +228,22 @@ var PluvRouter = class _PluvRouter {
257
228
  };
258
229
 
259
230
  // src/StateNotifier.ts
260
- import { makeSubject as makeSubject4, subscribe as subscribe4 } from "wonka";
231
+ import { makeSubject as makeSubject3, subscribe as subscribe3 } from "wonka";
261
232
  var StateNotifier = class {
262
233
  constructor() {
263
234
  this.subjects = {
264
- connection: makeSubject4(),
265
- "my-presence": makeSubject4(),
266
- myself: makeSubject4(),
267
- others: makeSubject4(),
268
- "storage-loaded": makeSubject4()
235
+ connection: makeSubject3(),
236
+ "my-presence": makeSubject3(),
237
+ myself: makeSubject3(),
238
+ others: makeSubject3(),
239
+ "storage-loaded": makeSubject3()
269
240
  };
270
241
  }
271
242
  subscribe(name, callback) {
272
243
  var _a;
273
244
  const source = (_a = this.subjects[name]) == null ? void 0 : _a.source;
274
245
  if (!source) throw new Error(`Could not subscribe to source: ${name}`);
275
- return subscribe4(callback)(source).unsubscribe;
246
+ return subscribe3(callback)(source).unsubscribe;
276
247
  }
277
248
  };
278
249
 
@@ -522,6 +493,43 @@ var UsersManager = class {
522
493
  }
523
494
  };
524
495
 
496
+ // src/UsersNotifier.ts
497
+ import { makeSubject as makeSubject4, subscribe as subscribe4 } from "wonka";
498
+ var UsersNotifier = class {
499
+ constructor() {
500
+ this.others = makeSubject4();
501
+ this._otherSubjects = /* @__PURE__ */ new Map();
502
+ }
503
+ clear() {
504
+ this._otherSubjects.forEach((subject) => {
505
+ subject.next(null);
506
+ });
507
+ this._otherSubjects.clear();
508
+ }
509
+ delete(clientId) {
510
+ this.other(clientId).next(null);
511
+ this._otherSubjects.delete(clientId);
512
+ }
513
+ other(clientId) {
514
+ const subject = this._otherSubjects.get(clientId);
515
+ if (subject) return subject;
516
+ const newSubject = makeSubject4();
517
+ this._otherSubjects.set(clientId, newSubject);
518
+ return newSubject;
519
+ }
520
+ subscribeOther(clientId, callback) {
521
+ const source = this.other(clientId).source;
522
+ const subscription = subscribe4(callback)(source);
523
+ return subscription.unsubscribe;
524
+ }
525
+ subscribeOthers(callback) {
526
+ const subscription = subscribe4(
527
+ ({ others, event }) => callback(others, event)
528
+ )(this.others.source);
529
+ return subscription.unsubscribe;
530
+ }
531
+ };
532
+
525
533
  // src/PluvRoom.ts
526
534
  var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
527
535
  var HEARTBEAT_INTERVAL_MS = 1e4;
@@ -541,7 +549,6 @@ var PluvRoom = class {
541
549
  this._intervals = {
542
550
  heartbeat: null
543
551
  };
544
- this._otherNotifier = new OtherNotifier();
545
552
  this._publicKey = null;
546
553
  this._stateNotifier = new StateNotifier();
547
554
  this._subscriptions = {
@@ -551,6 +558,7 @@ var PluvRoom = class {
551
558
  pong: null,
552
559
  reconnect: null
553
560
  };
561
+ this._usersNotifier = new UsersNotifier();
554
562
  this._lastMetadata = null;
555
563
  this._state = {
556
564
  authorization: {
@@ -605,6 +613,9 @@ var PluvRoom = class {
605
613
  this.canUndo = () => {
606
614
  return !!this._crdtManager.doc.canUndo();
607
615
  };
616
+ /**
617
+ * @deprecated To be removed in v3. Use PluvRoom.subscribe.event instead
618
+ */
608
619
  this.event = new Proxy(
609
620
  (event, callback) => this._eventNotifier.subscribe(event, callback),
610
621
  {
@@ -636,23 +647,77 @@ var PluvRoom = class {
636
647
  if (typeof sharedType === "undefined") return null;
637
648
  return sharedType;
638
649
  };
650
+ /**
651
+ * @deprecated To be removed in v3. Use PluvRoom.subscribe.other instead
652
+ */
639
653
  this.other = (connectionId, callback) => {
640
654
  const clientId = this._usersManager.getClientId(connectionId);
641
655
  if (!clientId) return () => void 0;
642
- return this._otherNotifier.subscribe(clientId, callback);
656
+ return this._usersNotifier.subscribeOther(clientId, callback);
643
657
  };
644
658
  this.redo = () => {
645
659
  this._crdtManager.doc.redo();
646
660
  };
647
- this.storage = (key, fn) => {
648
- return this._crdtNotifier.subscribe(key, fn);
649
- };
650
- this.storageRoot = (fn) => {
651
- return this._crdtNotifier.subcribeRoot(fn);
652
- };
653
- this.subscribe = (name, callback) => {
654
- return this._stateNotifier.subscribe(name, callback);
661
+ /**
662
+ * @deprecated To be removed in v3. To be removed in v3. Use PluvRoom.subscribe.storage instead
663
+ */
664
+ this.storage = new Proxy(
665
+ (key, callback) => this._crdtNotifier.subscribe(key, callback),
666
+ {
667
+ get: (fn, prop) => {
668
+ if (!!prop) {
669
+ return (callback) => {
670
+ return this._crdtNotifier.subcribeRoot(callback);
671
+ };
672
+ }
673
+ return (callback) => {
674
+ return fn(prop, callback);
675
+ };
676
+ }
677
+ }
678
+ );
679
+ /**
680
+ * @deprecated To be removed in v3. Use PluvRoom.subscribe.storage instead
681
+ * @description Subscribes to the root storage object (serialized)
682
+ */
683
+ this.storageRoot = (callback) => {
684
+ return this._crdtNotifier.subcribeRoot(callback);
655
685
  };
686
+ this.subscribe = new Proxy(
687
+ (name, callback) => this._stateNotifier.subscribe(name, callback),
688
+ {
689
+ get: (fn, prop) => {
690
+ if (prop === "connection") {
691
+ return (callback) => {
692
+ return fn("connection", callback);
693
+ };
694
+ }
695
+ if (prop === "myPresence") {
696
+ return (callback) => {
697
+ return fn("my-presence", callback);
698
+ };
699
+ }
700
+ if (prop === "myself") {
701
+ return (callback) => {
702
+ return fn("myself", callback);
703
+ };
704
+ }
705
+ if (prop === "others") {
706
+ return (callback) => {
707
+ return this._usersNotifier.subscribeOthers(callback);
708
+ };
709
+ }
710
+ if (prop === "storageLoaded") {
711
+ return (callback) => {
712
+ return fn("storage-loaded", callback);
713
+ };
714
+ }
715
+ if (prop === "event") return this.event;
716
+ if (prop === "other") return this.other;
717
+ if (prop === "storage") return this.storage;
718
+ }
719
+ }
720
+ );
656
721
  this.transact = (fn, origin) => {
657
722
  const _origin = origin != null ? origin : this._state.connection.id;
658
723
  if (typeof _origin !== "string") return;
@@ -863,9 +928,10 @@ var PluvRoom = class {
863
928
  this._detachWindowListeners();
864
929
  this._usersManager.removeMyself();
865
930
  this._usersManager.clearConnections();
866
- this._otherNotifier.clear();
931
+ this._usersNotifier.clear();
867
932
  this._stateNotifier.subjects.myself.next(null);
868
933
  this._stateNotifier.subjects.others.next([]);
934
+ this._usersNotifier.others.next({ others: [], event: { kind: "clear" } });
869
935
  (_c = this._state.webSocket) == null ? void 0 : _c.close();
870
936
  this._detachWsListeners();
871
937
  }
@@ -956,7 +1022,16 @@ var PluvRoom = class {
956
1022
  const deleted = this._usersManager.deleteConnection(connectionId);
957
1023
  const others = this._usersManager.getOthers();
958
1024
  this._stateNotifier.subjects.others.next(others);
959
- if (!(deleted == null ? void 0 : deleted.remaining) && !!clientId) this._otherNotifier.delete(clientId);
1025
+ if (!deleted) {
1026
+ console.warn("Could not identify exited connection");
1027
+ return;
1028
+ }
1029
+ const { data: user, remaining } = deleted;
1030
+ this._usersNotifier.others.next({
1031
+ others,
1032
+ event: { kind: "leave", user }
1033
+ });
1034
+ if (!remaining && !!clientId) this._usersNotifier.delete(clientId);
960
1035
  }
961
1036
  _handlePresenceUpdatedMessage(message) {
962
1037
  var _a;
@@ -975,19 +1050,31 @@ var PluvRoom = class {
975
1050
  return;
976
1051
  }
977
1052
  if (!!clientId) {
978
- const other = this._usersManager.getOther(connectionId);
979
- this._otherNotifier.subject(clientId).next(other);
980
- } else {
981
- const added = this._usersManager.addConnection({
982
- connectionId,
983
- presence: data.presence,
984
- user: message.user
985
- });
986
- const other = this._usersManager.getOther(connectionId);
987
- this._otherNotifier.subject(added.clientId).next(other);
1053
+ const other2 = this._usersManager.getOther(connectionId);
1054
+ const others2 = this._usersManager.getOthers();
1055
+ this._usersNotifier.other(clientId).next(other2);
1056
+ this._stateNotifier.subjects.others.next(others2);
1057
+ if (!!other2) {
1058
+ this._usersNotifier.others.next({
1059
+ others: others2,
1060
+ event: { kind: "update", user: other2 }
1061
+ });
1062
+ }
1063
+ return;
988
1064
  }
1065
+ const added = this._usersManager.addConnection({
1066
+ connectionId,
1067
+ presence: data.presence,
1068
+ user: message.user
1069
+ });
1070
+ const other = this._usersManager.getOther(connectionId);
989
1071
  const others = this._usersManager.getOthers();
1072
+ this._usersNotifier.other(added.clientId).next(other);
990
1073
  this._stateNotifier.subjects.others.next(others);
1074
+ this._usersNotifier.others.next({
1075
+ others,
1076
+ event: { kind: "update", user: added.data }
1077
+ });
991
1078
  }
992
1079
  _handleReceiveOthers(message) {
993
1080
  if (!message.connectionId) return;
@@ -1004,10 +1091,14 @@ var PluvRoom = class {
1004
1091
  if (result.isMyself) return;
1005
1092
  const clientId = result.clientId;
1006
1093
  const other = this._usersManager.getOther(connectionId);
1007
- this._otherNotifier.subject(clientId).next(other);
1094
+ this._usersNotifier.other(clientId).next(other);
1008
1095
  });
1009
1096
  const others = this._usersManager.getOthers();
1010
1097
  this._stateNotifier.subjects.others.next(others);
1098
+ this._usersNotifier.others.next({
1099
+ others,
1100
+ event: { kind: "sync", users: others }
1101
+ });
1011
1102
  }
1012
1103
  _handleRegisteredMessage(message) {
1013
1104
  return __async(this, null, function* () {
@@ -1109,6 +1200,11 @@ var PluvRoom = class {
1109
1200
  const quitters = this._usersManager.getOthers().filter((other) => !active.has(other.connectionId));
1110
1201
  quitters.forEach((quitter) => {
1111
1202
  this._usersManager.deleteConnection(quitter.connectionId);
1203
+ const remaining2 = this._usersManager.getOthers();
1204
+ this._usersNotifier.others.next({
1205
+ others: remaining2,
1206
+ event: { kind: "leave", user: quitter }
1207
+ });
1112
1208
  });
1113
1209
  const remaining = this._usersManager.getOthers();
1114
1210
  this._stateNotifier.subjects.others.next(remaining);
@@ -1131,8 +1227,12 @@ var PluvRoom = class {
1131
1227
  });
1132
1228
  const other = this._usersManager.getOther(connectionId);
1133
1229
  const others = this._usersManager.getOthers();
1134
- this._otherNotifier.subject(added.clientId).next(other);
1230
+ this._usersNotifier.other(added.clientId).next(other);
1135
1231
  this._stateNotifier.subjects.others.next(others);
1232
+ this._usersNotifier.others.next({
1233
+ others,
1234
+ event: { kind: "enter", user: added.data }
1235
+ });
1136
1236
  }
1137
1237
  _heartbeat() {
1138
1238
  this._clearTimeout(this._timeouts.pong);
@@ -1507,7 +1607,7 @@ var MockedRoom = class {
1507
1607
  constructor(room, options) {
1508
1608
  this._crdtNotifier = new CrdtNotifier();
1509
1609
  this._eventNotifier = new EventNotifier();
1510
- this._otherNotifier = new OtherNotifier();
1610
+ this._usersNotifier = new UsersNotifier();
1511
1611
  this._state = {
1512
1612
  authorization: {
1513
1613
  token: null,
@@ -1594,20 +1694,66 @@ var MockedRoom = class {
1594
1694
  this.other = (connectionId, callback) => {
1595
1695
  const clientId = this._usersManager.getClientId(connectionId);
1596
1696
  if (!clientId) return () => void 0;
1597
- return this._otherNotifier.subscribe(clientId, callback);
1697
+ return this._usersNotifier.subscribeOther(clientId, callback);
1598
1698
  };
1599
1699
  this.redo = () => {
1600
1700
  this._crdtManager.doc.redo();
1601
1701
  };
1602
- this.storage = (key, fn) => {
1603
- return this._crdtNotifier.subscribe(key, fn);
1604
- };
1702
+ this.storage = new Proxy(
1703
+ (key, callback) => this._crdtNotifier.subscribe(key, callback),
1704
+ {
1705
+ get: (fn, prop) => {
1706
+ if (!!prop) {
1707
+ return (callback) => {
1708
+ return this._crdtNotifier.subcribeRoot(callback);
1709
+ };
1710
+ }
1711
+ return (callback) => {
1712
+ return fn(prop, callback);
1713
+ };
1714
+ }
1715
+ }
1716
+ );
1605
1717
  this.storageRoot = (fn) => {
1606
1718
  return this._crdtNotifier.subcribeRoot(fn);
1607
1719
  };
1608
- this.subscribe = (name, callback) => {
1609
- return this._stateNotifier.subscribe(name, callback);
1610
- };
1720
+ this.subscribe = new Proxy(
1721
+ (name, callback) => {
1722
+ return this._stateNotifier.subscribe(name, callback);
1723
+ },
1724
+ {
1725
+ get: (fn, prop) => {
1726
+ if (prop === "connection") {
1727
+ return (callback) => {
1728
+ return fn("connection", callback);
1729
+ };
1730
+ }
1731
+ if (prop === "myPresence") {
1732
+ return (callback) => {
1733
+ return fn("my-presence", callback);
1734
+ };
1735
+ }
1736
+ if (prop === "myself") {
1737
+ return (callback) => {
1738
+ return fn("myself", callback);
1739
+ };
1740
+ }
1741
+ if (prop === "others") {
1742
+ return (callback) => {
1743
+ return this._usersNotifier.subscribeOthers(callback);
1744
+ };
1745
+ }
1746
+ if (prop === "storageLoaded") {
1747
+ return (callback) => {
1748
+ return fn("storage-loaded", callback);
1749
+ };
1750
+ }
1751
+ if (prop === "event") return this.event;
1752
+ if (prop === "other") return this.other;
1753
+ if (prop === "storage") return this.storage;
1754
+ }
1755
+ }
1756
+ );
1611
1757
  this.transact = (fn, origin) => {
1612
1758
  const _origin = origin != null ? origin : this._state.connection.id;
1613
1759
  const crdtManager = this._crdtManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "@pluv/io frontend client",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -18,15 +18,15 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "wonka": "^6.3.5",
21
- "@pluv/crdt": "^2.0.2",
22
- "@pluv/types": "^2.0.2"
21
+ "@pluv/crdt": "^2.1.0",
22
+ "@pluv/types": "^2.1.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "eslint": "^9.26.0",
26
26
  "tsup": "^8.4.0",
27
27
  "typescript": "^5.8.3",
28
- "@pluv/tsconfig": "^2.0.2",
29
- "eslint-config-pluv": "^2.0.2"
28
+ "@pluv/tsconfig": "^2.1.0",
29
+ "eslint-config-pluv": "^2.1.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -1,4 +1,5 @@
1
1
  import type { CrdtType, InferCrdtJson } from "@pluv/crdt";
2
+ import type { StorageSubscriptionCallback } from "@pluv/types";
2
3
  import type { Subject } from "wonka";
3
4
  import { makeSubject, subscribe } from "wonka";
4
5
 
@@ -36,7 +37,7 @@ export class CrdtNotifier<TStorage extends Record<string, CrdtType<any, any>>> {
36
37
 
37
38
  public subscribe<TKey extends keyof TStorage>(
38
39
  key: TKey,
39
- callback: (value: InferCrdtJson<TStorage[TKey]>) => void,
40
+ callback: StorageSubscriptionCallback<TStorage, TKey>,
40
41
  ): () => void {
41
42
  const subject = this.subject(key);
42
43
 
package/src/MockedRoom.ts CHANGED
@@ -1,15 +1,24 @@
1
- import type { AbstractCrdtDocFactory, CrdtType, InferCrdtJson, InferStorage } from "@pluv/crdt";
1
+ import type { AbstractCrdtDocFactory, InferCrdtJson, InferStorage } from "@pluv/crdt";
2
2
  import type {
3
+ BroadcastProxy,
3
4
  CrdtDocLike,
4
5
  EventNotifierSubscriptionCallback,
6
+ EventProxy,
5
7
  IOLike,
6
8
  Id,
7
9
  InferIOInput,
8
10
  InferIOOutput,
9
11
  JsonObject,
10
12
  MergeEvents,
11
- OtherNotifierSubscriptionCallback,
13
+ OtherSubscriptionCallback,
14
+ OthersSubscriptionCallback,
12
15
  RoomLike,
16
+ StateNotifierSubjects,
17
+ StorageProxy,
18
+ StorageRootSubscriptionCallback,
19
+ StorageSubscriptionCallback,
20
+ SubscribeProxy,
21
+ SubscriptionCallback,
13
22
  UpdateMyPresenceAction,
14
23
  UserInfo,
15
24
  WebSocketState,
@@ -19,14 +28,13 @@ import type { CrdtManagerOptions } from "./CrdtManager";
19
28
  import { CrdtManager } from "./CrdtManager";
20
29
  import { CrdtNotifier } from "./CrdtNotifier";
21
30
  import { EventNotifier } from "./EventNotifier";
22
- import { OtherNotifier } from "./OtherNotifier";
23
31
  import { PluvProcedure } from "./PluvProcedure";
24
32
  import type { PluvRouterEventConfig } from "./PluvRouter";
25
33
  import { PluvRouter } from "./PluvRouter";
26
- import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
27
34
  import { StateNotifier } from "./StateNotifier";
28
35
  import type { UsersManagerConfig } from "./UsersManager";
29
36
  import { UsersManager } from "./UsersManager";
37
+ import { UsersNotifier } from "./UsersNotifier";
30
38
  import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
31
39
  import type {
32
40
  EventResolver,
@@ -66,7 +74,7 @@ export class MockedRoom<
66
74
  private readonly _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
67
75
  private readonly _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
68
76
  private readonly _limits: PluvClientLimits;
69
- private readonly _otherNotifier = new OtherNotifier<TIO, TPresence>();
77
+ private readonly _usersNotifier = new UsersNotifier<TIO, TPresence>();
70
78
  private readonly _router: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
71
79
  private _state: WebSocketState<TIO> = {
72
80
  authorization: {
@@ -176,12 +184,7 @@ export class MockedRoom<
176
184
  };
177
185
  },
178
186
  },
179
- ) as (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
180
- event: TEvent,
181
- data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
182
- ) => Promise<void>) & {
183
- [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>;
184
- };
187
+ ) as BroadcastProxy<TIO, TEvents>;
185
188
 
186
189
  public canRedo = (): boolean => {
187
190
  return !!this._crdtManager.doc.canRedo();
@@ -203,14 +206,7 @@ export class MockedRoom<
203
206
  ): (() => void) => fn(prop as any, callback);
204
207
  },
205
208
  },
206
- ) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
207
- event: TEvent,
208
- callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
209
- ) => () => void) & {
210
- [event in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (
211
- callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
212
- ) => () => void;
213
- };
209
+ ) as EventProxy<TIO, TEvents>;
214
210
 
215
211
  public getConnection = (): WebSocketConnection => {
216
212
  // Create a read-only clone of the connection state
@@ -261,25 +257,40 @@ export class MockedRoom<
261
257
 
262
258
  public other = (
263
259
  connectionId: string,
264
- callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
260
+ callback: OtherSubscriptionCallback<TIO, TPresence>,
265
261
  ): (() => void) => {
266
262
  const clientId = this._usersManager.getClientId(connectionId);
267
263
 
268
264
  if (!clientId) return () => undefined;
269
265
 
270
- return this._otherNotifier.subscribe(clientId, callback);
266
+ return this._usersNotifier.subscribeOther(clientId, callback);
271
267
  };
272
268
 
273
269
  public redo = (): void => {
274
270
  this._crdtManager.doc.redo();
275
271
  };
276
272
 
277
- public storage = <TKey extends keyof InferStorage<TCrdt>>(
278
- key: TKey,
279
- fn: (value: InferCrdtJson<InferStorage<TCrdt>[TKey]>) => void,
280
- ): (() => void) => {
281
- return this._crdtNotifier.subscribe(key, fn);
282
- };
273
+ public storage = new Proxy(
274
+ <TKey extends keyof InferStorage<TCrdt>>(
275
+ key: TKey,
276
+ callback: StorageSubscriptionCallback<InferStorage<TCrdt>, TKey>,
277
+ ): (() => void) => this._crdtNotifier.subscribe(key, callback),
278
+ {
279
+ get: (fn, prop) => {
280
+ type _Storage = InferStorage<TCrdt>;
281
+
282
+ if (!!prop) {
283
+ return (callback: StorageRootSubscriptionCallback<_Storage>) => {
284
+ return this._crdtNotifier.subcribeRoot(callback);
285
+ };
286
+ }
287
+
288
+ return (callback: StorageSubscriptionCallback<_Storage, keyof _Storage>) => {
289
+ return fn(prop as keyof InferStorage<TCrdt>, callback);
290
+ };
291
+ },
292
+ },
293
+ ) as StorageProxy<InferStorage<TCrdt>>;
283
294
 
284
295
  public storageRoot = (
285
296
  fn: (value: {
@@ -289,12 +300,51 @@ export class MockedRoom<
289
300
  return this._crdtNotifier.subcribeRoot(fn);
290
301
  };
291
302
 
292
- public subscribe = <TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
293
- name: TSubject,
294
- callback: SubscriptionCallback<TIO, TPresence, TSubject>,
295
- ): (() => void) => {
296
- return this._stateNotifier.subscribe(name, callback);
297
- };
303
+ public subscribe = new Proxy(
304
+ <TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
305
+ name: TSubject,
306
+ callback: SubscriptionCallback<TIO, TPresence, TSubject>,
307
+ ): (() => void) => {
308
+ return this._stateNotifier.subscribe(name, callback);
309
+ },
310
+ {
311
+ get: (fn, prop) => {
312
+ if (prop === "connection") {
313
+ return (callback: SubscriptionCallback<TIO, TPresence, "connection">) => {
314
+ return fn("connection", callback);
315
+ };
316
+ }
317
+
318
+ if (prop === "myPresence") {
319
+ return (callback: SubscriptionCallback<TIO, TPresence, "my-presence">) => {
320
+ return fn("my-presence", callback);
321
+ };
322
+ }
323
+
324
+ if (prop === "myself") {
325
+ return (callback: SubscriptionCallback<TIO, TPresence, "myself">) => {
326
+ return fn("myself", callback);
327
+ };
328
+ }
329
+
330
+ if (prop === "others") {
331
+ return (callback: OthersSubscriptionCallback<TIO, TPresence>) => {
332
+ return this._usersNotifier.subscribeOthers(callback);
333
+ };
334
+ }
335
+
336
+ if (prop === "storageLoaded") {
337
+ return (callback: SubscriptionCallback<TIO, TPresence, "storage-loaded">) => {
338
+ return fn("storage-loaded", callback);
339
+ };
340
+ }
341
+
342
+ if (prop === "event") return this.event;
343
+ if (prop === "other") return this.other;
344
+ if (prop === "storage") return this.storage;
345
+ },
346
+ },
347
+ ) as SubscribeProxy<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
298
348
 
299
349
  public transact = (fn: (storage: InferStorage<TCrdt>) => void, origin?: string): void => {
300
350
  const _origin = origin ?? this._state.connection.id;