@pluv/client 0.11.1 → 0.12.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.
package/dist/index.mjs CHANGED
@@ -61,14 +61,21 @@ import { doc } from "@pluv/crdt-yjs";
61
61
  var CrdtManager = class {
62
62
  constructor(options = {}) {
63
63
  this.initialized = false;
64
- const { encodedState, initialStorage } = options;
64
+ const {
65
+ captureTimeout,
66
+ encodedState,
67
+ initialStorage,
68
+ trackedOrigins = []
69
+ } = options;
65
70
  const _doc = encodedState ? doc().applyUpdate(encodedState) : null;
66
71
  if (_doc && !!Object.keys(_doc.toJSON()).length) {
67
72
  this.doc = _doc;
73
+ this.trackOrigins({ captureTimeout, trackedOrigins });
68
74
  return;
69
75
  }
70
76
  _doc == null ? void 0 : _doc.destroy();
71
77
  this.doc = doc(initialStorage == null ? void 0 : initialStorage());
78
+ this.trackOrigins({ captureTimeout, trackedOrigins });
72
79
  }
73
80
  /**
74
81
  * !HACK
@@ -90,8 +97,15 @@ var CrdtManager = class {
90
97
  return this.doc.get(key);
91
98
  }
92
99
  initialize(params) {
93
- const { onInitialized, origin, update } = params;
100
+ const {
101
+ captureTimeout,
102
+ onInitialized,
103
+ origin,
104
+ trackedOrigins,
105
+ update
106
+ } = params;
94
107
  const updates = typeof update === "string" ? [update] : update;
108
+ this.trackOrigins({ captureTimeout, trackedOrigins });
95
109
  if (!updates.length)
96
110
  return this;
97
111
  if (this.initialized) {
@@ -106,8 +120,13 @@ var CrdtManager = class {
106
120
  _doc.destroy();
107
121
  }
108
122
  onInitialized == null ? void 0 : onInitialized();
123
+ this.trackOrigins({ captureTimeout, trackedOrigins });
109
124
  return this;
110
125
  }
126
+ trackOrigins(options) {
127
+ const { captureTimeout, trackedOrigins } = options;
128
+ this.doc.trackOrigins({ captureTimeout, trackedOrigins });
129
+ }
111
130
  _applyDocUpdates(yjsDoc, updates, origin) {
112
131
  yjsDoc.transact(() => {
113
132
  updates.reduce((acc, _update) => acc.applyUpdate(_update), yjsDoc);
@@ -120,6 +139,7 @@ var CrdtManager = class {
120
139
  import { makeSubject, subscribe } from "wonka";
121
140
  var CrdtNotifier = class {
122
141
  constructor() {
142
+ this.rootSubject = makeSubject();
123
143
  this.subjects = /* @__PURE__ */ new Map();
124
144
  }
125
145
  subject(key) {
@@ -131,6 +151,9 @@ var CrdtNotifier = class {
131
151
  this.subjects.set(key, newSubject);
132
152
  return newSubject;
133
153
  }
154
+ subcribeRoot(callback) {
155
+ return subscribe(callback)(this.rootSubject.source).unsubscribe;
156
+ }
134
157
  subscribe(key, callback) {
135
158
  const subject = this.subject(key);
136
159
  return subscribe(callback)(subject.source).unsubscribe;
@@ -353,7 +376,8 @@ var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
353
376
  var HEARTBEAT_INTERVAL_MS = 1e4;
354
377
  var PONG_TIMEOUT_MS = 2e3;
355
378
  var RECONNECT_TIMEOUT_MS = 3e4;
356
- var Y_ORIGIN_INITIALIZED = "$INITIALIZED";
379
+ var ORIGIN_INITIALIZED = "$INITIALIZED";
380
+ var ORIGIN_STORAGE_UPDATED = "$STORAGE_UPDATED";
357
381
  var DEFAULT_PLUV_CLIENT_ADDON = (input) => ({
358
382
  storage: new StorageStore(input.room.id)
359
383
  });
@@ -362,14 +386,17 @@ var PluvRoom = class extends AbstractRoom {
362
386
  const {
363
387
  addons = [],
364
388
  authEndpoint,
389
+ captureTimeout,
365
390
  debug = false,
366
391
  initialPresence,
367
392
  initialStorage,
368
393
  onAuthorizationFail,
369
394
  presence,
395
+ trackedOrigins,
370
396
  wsEndpoint
371
397
  } = options;
372
398
  super(room);
399
+ this._captureTimeout = null;
373
400
  this._crdtNotifier = new CrdtNotifier();
374
401
  this._eventNotifier = new EventNotifier();
375
402
  this._intervals = {
@@ -396,8 +423,15 @@ var PluvRoom = class extends AbstractRoom {
396
423
  pong: null,
397
424
  reconnect: null
398
425
  };
426
+ this._trackedOrigins = null;
399
427
  this._windowListeners = null;
400
428
  this._wsListeners = null;
429
+ this.canRedo = () => {
430
+ return !!this._crdtManager.doc.canRedo();
431
+ };
432
+ this.canUndo = () => {
433
+ return !!this._crdtManager.doc.canUndo();
434
+ };
401
435
  this.event = (event, callback) => {
402
436
  return this._eventNotifier.subscribe(event, callback);
403
437
  };
@@ -414,18 +448,38 @@ var PluvRoom = class extends AbstractRoom {
414
448
  return this._usersManager.getOthers();
415
449
  };
416
450
  this.getStorage = (key) => {
417
- var _a, _b;
418
- return (_b = (_a = this._crdtManager) == null ? void 0 : _a.get(key)) != null ? _b : null;
451
+ const sharedType = this._crdtManager.get(key);
452
+ if (typeof sharedType === "undefined") {
453
+ throw new Error(`Could not find storege: ${key.toString()}`);
454
+ }
455
+ return sharedType;
419
456
  };
420
457
  this.other = (connectionId, callback) => {
421
458
  return this._otherNotifier.subscribe(connectionId, callback);
422
459
  };
460
+ this.redo = () => {
461
+ this._crdtManager.doc.redo();
462
+ };
423
463
  this.storage = (key, fn) => {
424
464
  return this._crdtNotifier.subscribe(key, fn);
425
465
  };
466
+ this.storageRoot = (fn) => {
467
+ return this._crdtNotifier.subcribeRoot(fn);
468
+ };
426
469
  this.subscribe = (name, callback) => {
427
470
  return this._stateNotifier.subscribe(name, callback);
428
471
  };
472
+ this.transact = (fn, origin) => {
473
+ const _origin = origin != null ? origin : this._state.connection.id;
474
+ if (typeof _origin !== "string")
475
+ return;
476
+ this._crdtManager.doc.transact(() => {
477
+ fn(this._crdtManager.doc.storage);
478
+ }, _origin);
479
+ };
480
+ this.undo = () => {
481
+ this._crdtManager.doc.undo();
482
+ };
429
483
  this.updateMyPresence = (presence) => {
430
484
  var _a;
431
485
  this._usersManager.updateMyPresence(presence);
@@ -475,7 +529,13 @@ var PluvRoom = class extends AbstractRoom {
475
529
  initialPresence,
476
530
  presence
477
531
  });
478
- this._crdtManager = new CrdtManager({ initialStorage });
532
+ this._crdtManager = new CrdtManager({
533
+ captureTimeout,
534
+ initialStorage,
535
+ trackedOrigins
536
+ });
537
+ this._captureTimeout = captureTimeout != null ? captureTimeout : null;
538
+ this._trackedOrigins = trackedOrigins != null ? trackedOrigins : null;
479
539
  }
480
540
  get webSocket() {
481
541
  return this._state.webSocket;
@@ -506,7 +566,6 @@ var PluvRoom = class extends AbstractRoom {
506
566
  });
507
567
  yield this._storageStore.initialize();
508
568
  yield this._applyStorageStore();
509
- console.log(this._getWsEndpoint(this.id));
510
569
  const url = new URL(this._getWsEndpoint(this.id));
511
570
  let authToken = null;
512
571
  let webSocket;
@@ -530,7 +589,6 @@ var PluvRoom = class extends AbstractRoom {
530
589
  }
531
590
  disconnect() {
532
591
  return __async(this, null, function* () {
533
- var _a;
534
592
  if (!this._endpoints.wsEndpoint) {
535
593
  throw new Error("Must provide an wsEndpoint.");
536
594
  }
@@ -545,7 +603,7 @@ var PluvRoom = class extends AbstractRoom {
545
603
  this._clearInterval(this._intervals.heartbeat);
546
604
  this._closeWs();
547
605
  yield this._storageStore.destroy();
548
- (_a = this._crdtManager) == null ? void 0 : _a.destroy();
606
+ this._crdtManager.destroy();
549
607
  this._stateNotifier.subjects["my-presence"].next(null);
550
608
  });
551
609
  }
@@ -562,12 +620,16 @@ var PluvRoom = class extends AbstractRoom {
562
620
  }
563
621
  _applyStorageStore() {
564
622
  return __async(this, null, function* () {
623
+ var _a;
565
624
  const updates = yield this._storageStore.getUpdates();
625
+ const trackedOrigins = this._getTrackedOrigins();
566
626
  this._crdtManager.initialize({
627
+ captureTimeout: (_a = this._captureTimeout) != null ? _a : void 0,
567
628
  onInitialized: () => {
568
629
  this._observeCrdt();
569
630
  },
570
- origin: Y_ORIGIN_INITIALIZED,
631
+ origin: ORIGIN_INITIALIZED,
632
+ trackedOrigins: trackedOrigins != null ? trackedOrigins : void 0,
571
633
  update: updates
572
634
  });
573
635
  this._emitSharedTypes();
@@ -686,10 +748,15 @@ var PluvRoom = class extends AbstractRoom {
686
748
  }
687
749
  _emitSharedTypes() {
688
750
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
689
- Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
690
- const serialized = sharedType.toJSON();
691
- this._crdtNotifier.subject(prop).next(serialized);
692
- });
751
+ const storageRoot = Object.entries(sharedTypes).reduce(
752
+ (acc, [prop, sharedType]) => {
753
+ const serialized = sharedType.toJSON();
754
+ this._crdtNotifier.subject(prop).next(serialized);
755
+ return __spreadProps(__spreadValues({}, acc), { [prop]: serialized });
756
+ },
757
+ {}
758
+ );
759
+ this._crdtNotifier.rootSubject.next(storageRoot);
693
760
  }
694
761
  _getAuthFetchOptions(room) {
695
762
  if (typeof this._endpoints.authEndpoint === "undefined")
@@ -728,6 +795,11 @@ var PluvRoom = class extends AbstractRoom {
728
795
  }
729
796
  });
730
797
  }
798
+ _getTrackedOrigins() {
799
+ var _a;
800
+ const connectionId = this._state.connection.id;
801
+ return !connectionId ? this._trackedOrigins : [connectionId, ...(_a = this._trackedOrigins) != null ? _a : []];
802
+ }
731
803
  _getWsEndpoint(room) {
732
804
  switch (typeof this._endpoints.wsEndpoint) {
733
805
  case "undefined":
@@ -796,7 +868,7 @@ var PluvRoom = class extends AbstractRoom {
796
868
  this._stateNotifier.subjects["others"].next(others);
797
869
  }
798
870
  _handleRegisteredMessage(message) {
799
- var _a, _b, _c;
871
+ var _a, _b;
800
872
  const { connectionId, user } = message;
801
873
  if (!connectionId)
802
874
  return;
@@ -816,7 +888,7 @@ var PluvRoom = class extends AbstractRoom {
816
888
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
817
889
  this._stateNotifier.subjects["my-presence"].next(presence);
818
890
  this._stateNotifier.subjects["myself"].next(myself);
819
- const update = this._state.connection.count > 1 ? (_c = (_b = this._crdtManager) == null ? void 0 : _b.doc.encodeStateAsUpdate()) != null ? _c : null : null;
891
+ const update = this._state.connection.count > 1 ? (_b = this._crdtManager.doc.encodeStateAsUpdate()) != null ? _b : null : null;
820
892
  this._sendMessage({
821
893
  type: "$INITIALIZE_SESSION",
822
894
  data: { presence, update }
@@ -824,17 +896,21 @@ var PluvRoom = class extends AbstractRoom {
824
896
  }
825
897
  _handleStorageReceivedMessage(message) {
826
898
  return __async(this, null, function* () {
899
+ var _a;
827
900
  const { connectionId } = message;
828
901
  if (!connectionId)
829
902
  return;
830
903
  if (!this._state.webSocket)
831
904
  throw new Error("Could not find WebSocket");
832
905
  const data = message.data;
906
+ const trackedOrigins = this._getTrackedOrigins();
833
907
  this._crdtManager.initialize({
908
+ captureTimeout: (_a = this._captureTimeout) != null ? _a : void 0,
834
909
  onInitialized: () => __async(this, null, function* () {
835
910
  this._observeCrdt();
836
911
  }),
837
- origin: Y_ORIGIN_INITIALIZED,
912
+ origin: ORIGIN_INITIALIZED,
913
+ trackedOrigins: trackedOrigins != null ? trackedOrigins : void 0,
838
914
  update: data.state
839
915
  });
840
916
  const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
@@ -843,7 +919,7 @@ var PluvRoom = class extends AbstractRoom {
843
919
  const update = this._crdtManager.doc.encodeStateAsUpdate();
844
920
  this._sendMessage({
845
921
  type: "$UPDATE_STORAGE",
846
- data: { origin: Y_ORIGIN_INITIALIZED, update }
922
+ data: { origin: ORIGIN_INITIALIZED, update }
847
923
  });
848
924
  });
849
925
  }
@@ -856,14 +932,19 @@ var PluvRoom = class extends AbstractRoom {
856
932
  const data = message.data;
857
933
  if (!this._crdtManager)
858
934
  return;
859
- this._crdtManager.doc.applyUpdate(data.state, "$STORAGE_UPDATED");
935
+ this._crdtManager.doc.applyUpdate(data.state, ORIGIN_STORAGE_UPDATED);
860
936
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
861
- Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
862
- if (!this._crdtManager)
863
- return;
864
- const serialized = sharedType.toJSON();
865
- this._crdtNotifier.subject(prop).next(serialized);
866
- });
937
+ const storageRoot = Object.entries(sharedTypes).reduce(
938
+ (acc, [prop, sharedType]) => {
939
+ if (!this._crdtManager)
940
+ return acc;
941
+ const serialized = sharedType.toJSON();
942
+ this._crdtNotifier.subject(prop).next(serialized);
943
+ return __spreadProps(__spreadValues({}, acc), { [prop]: serialized });
944
+ },
945
+ {}
946
+ );
947
+ this._crdtNotifier.rootSubject.next(storageRoot);
867
948
  }
868
949
  _handleUserJoinedMessage(message) {
869
950
  const { connectionId } = message;
@@ -912,7 +993,7 @@ var PluvRoom = class extends AbstractRoom {
912
993
  const origin = _origin != null ? _origin : null;
913
994
  this._addToStorageStore(update);
914
995
  this._emitSharedTypes();
915
- if (origin === Y_ORIGIN_INITIALIZED || origin === this)
996
+ if (origin === ORIGIN_INITIALIZED || origin === this)
916
997
  return;
917
998
  if (!this._state.webSocket)
918
999
  return;
@@ -920,7 +1001,7 @@ var PluvRoom = class extends AbstractRoom {
920
1001
  return;
921
1002
  if (this._state.webSocket.readyState !== WebSocket.OPEN)
922
1003
  return;
923
- if (origin === "$STORAGE_UPDATED")
1004
+ if (origin === ORIGIN_STORAGE_UPDATED)
924
1005
  return;
925
1006
  this._sendMessage({
926
1007
  type: "$UPDATE_STORAGE",
@@ -1190,7 +1271,6 @@ var MockedRoom = class extends AbstractRoom {
1190
1271
  constructor(room, options) {
1191
1272
  const { events, initialPresence, initialStorage, presence } = options;
1192
1273
  super(room);
1193
- this._crdtManager = null;
1194
1274
  this._crdtNotifier = new CrdtNotifier();
1195
1275
  this._eventNotifier = new EventNotifier();
1196
1276
  this._otherNotifier = new OtherNotifier();
@@ -1210,6 +1290,12 @@ var MockedRoom = class extends AbstractRoom {
1210
1290
  this._subscriptions = {
1211
1291
  observeCrdt: null
1212
1292
  };
1293
+ this.canRedo = () => {
1294
+ return !!this._crdtManager.doc.canRedo();
1295
+ };
1296
+ this.canUndo = () => {
1297
+ return !!this._crdtManager.doc.canUndo();
1298
+ };
1213
1299
  this.event = (event, callback) => {
1214
1300
  return this._eventNotifier.subscribe(event, callback);
1215
1301
  };
@@ -1226,18 +1312,41 @@ var MockedRoom = class extends AbstractRoom {
1226
1312
  return this._usersManager.getOthers();
1227
1313
  };
1228
1314
  this.getStorage = (key) => {
1229
- var _a, _b;
1230
- return (_b = (_a = this._crdtManager) == null ? void 0 : _a.get(key)) != null ? _b : null;
1315
+ const sharedType = this._crdtManager.get(key);
1316
+ if (typeof sharedType === "undefined") {
1317
+ throw new Error(`Could not find storege: ${key.toString()}`);
1318
+ }
1319
+ return sharedType;
1231
1320
  };
1232
1321
  this.other = (connectionId, callback) => {
1233
1322
  return this._otherNotifier.subscribe(connectionId, callback);
1234
1323
  };
1324
+ this.redo = () => {
1325
+ this._crdtManager.doc.redo();
1326
+ };
1235
1327
  this.storage = (key, fn) => {
1236
1328
  return this._crdtNotifier.subscribe(key, fn);
1237
1329
  };
1330
+ this.storageRoot = (fn) => {
1331
+ return this._crdtNotifier.subcribeRoot(fn);
1332
+ };
1238
1333
  this.subscribe = (name, callback) => {
1239
1334
  return this._stateNotifier.subscribe(name, callback);
1240
1335
  };
1336
+ this.transact = (fn, origin) => {
1337
+ const _origin = origin != null ? origin : this._state.connection.id;
1338
+ const crdtManager = this._crdtManager;
1339
+ if (typeof _origin !== "string")
1340
+ return;
1341
+ if (!crdtManager)
1342
+ return;
1343
+ crdtManager.doc.transact(() => {
1344
+ fn(crdtManager.doc.storage);
1345
+ }, _origin);
1346
+ };
1347
+ this.undo = () => {
1348
+ this._crdtManager.doc.undo();
1349
+ };
1241
1350
  this.updateMyPresence = (presence) => {
1242
1351
  var _a;
1243
1352
  this._usersManager.updateMyPresence(presence);
@@ -1289,12 +1398,17 @@ var MockedRoom = class extends AbstractRoom {
1289
1398
  if (origin === "$STORAGE_UPDATED")
1290
1399
  return;
1291
1400
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
1292
- Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
1293
- if (!this._crdtManager)
1294
- return;
1295
- const serialized = sharedType.toJSON();
1296
- this._crdtNotifier.subject(prop).next(serialized);
1297
- });
1401
+ const storageRoot = Object.entries(sharedTypes).reduce(
1402
+ (acc, [prop, sharedType]) => {
1403
+ if (!this._crdtManager)
1404
+ return acc;
1405
+ const serialized = sharedType.toJSON();
1406
+ this._crdtNotifier.subject(prop).next(serialized);
1407
+ return __spreadProps(__spreadValues({}, acc), { [prop]: serialized });
1408
+ },
1409
+ {}
1410
+ );
1411
+ this._crdtNotifier.rootSubject.next(storageRoot);
1298
1412
  }
1299
1413
  );
1300
1414
  this._subscriptions.observeCrdt = unsubscribe;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "description": "@pluv/io frontend client",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -18,19 +18,19 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "wonka": "^6.3.4",
21
- "@pluv/crdt-yjs": "^0.11.1",
22
- "@pluv/types": "^0.11.1"
21
+ "@pluv/crdt-yjs": "^0.12.1",
22
+ "@pluv/types": "^0.12.1"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "yjs": "^13.0.0"
26
26
  },
27
27
  "devDependencies": {
28
- "eslint": "^8.49.0",
28
+ "eslint": "^8.50.0",
29
29
  "tsup": "^7.2.0",
30
30
  "typescript": "^5.2.2",
31
- "yjs": "^13.6.7",
32
- "@pluv/tsconfig": "^0.11.1",
33
- "eslint-config-pluv": "^0.11.1"
31
+ "yjs": "^13.6.8",
32
+ "@pluv/tsconfig": "^0.12.1",
33
+ "eslint-config-pluv": "^0.12.1"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -18,7 +18,7 @@ import type { UserInfo, WebSocketConnection } from "./types";
18
18
  export abstract class AbstractRoom<
19
19
  TIO extends IOLike,
20
20
  TPresence extends JsonObject = {},
21
- TStorage extends Record<string, AbstractType<any>> = {}
21
+ TStorage extends Record<string, AbstractType<any>> = {},
22
22
  > {
23
23
  public readonly id: string;
24
24
 
@@ -28,12 +28,16 @@ export abstract class AbstractRoom<
28
28
 
29
29
  public abstract broadcast<TEvent extends keyof InferIOInput<TIO>>(
30
30
  event: TEvent,
31
- data: Id<InferIOInput<TIO>[TEvent]>
31
+ data: Id<InferIOInput<TIO>[TEvent]>,
32
32
  ): void;
33
33
 
34
+ public abstract canRedo(): boolean;
35
+
36
+ public abstract canUndo(): boolean;
37
+
34
38
  public abstract event<TEvent extends keyof InferIOOutput<TIO>>(
35
39
  event: TEvent,
36
- callback: EventNotifierSubscriptionCallback<TIO, TEvent>
40
+ callback: EventNotifierSubscriptionCallback<TIO, TEvent>,
37
41
  ): () => void;
38
42
 
39
43
  public abstract getConnection(): WebSocketConnection;
@@ -43,31 +47,46 @@ export abstract class AbstractRoom<
43
47
  public abstract getMyself(): Id<UserInfo<TIO, TPresence>> | null;
44
48
 
45
49
  public abstract getOther(
46
- connectionId: string
50
+ connectionId: string,
47
51
  ): Id<UserInfo<TIO, TPresence>> | null;
48
52
 
49
53
  public abstract getOthers(): readonly Id<UserInfo<TIO, TPresence>>[];
50
54
 
51
55
  public abstract getStorage<TKey extends keyof TStorage>(
52
- key: TKey
53
- ): TStorage[TKey] | null;
56
+ key: TKey,
57
+ ): TStorage[TKey];
54
58
 
55
59
  public abstract other(
56
60
  connectionId: string,
57
- callback: OtherNotifierSubscriptionCallback<TIO>
61
+ callback: OtherNotifierSubscriptionCallback<TIO>,
58
62
  ): () => void;
59
63
 
64
+ public abstract redo(): void;
65
+
60
66
  public abstract storage<TKey extends keyof TStorage>(
61
67
  key: TKey,
62
- fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void
68
+ fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void,
69
+ ): () => void;
70
+
71
+ public abstract storageRoot(
72
+ fn: (value: {
73
+ [P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
74
+ }) => void,
63
75
  ): () => void;
64
76
 
65
77
  public abstract subscribe<
66
- TSubject extends keyof StateNotifierSubjects<TIO, TPresence>
78
+ TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
67
79
  >(
68
80
  name: TSubject,
69
- callback: SubscriptionCallback<TIO, TPresence, TSubject>
81
+ callback: SubscriptionCallback<TIO, TPresence, TSubject>,
70
82
  ): () => void;
71
83
 
84
+ public abstract transact(
85
+ fn: (storage: TStorage) => void,
86
+ origin?: string,
87
+ ): void;
88
+
89
+ public abstract undo(): void;
90
+
72
91
  public abstract updateMyPresence(presence: Partial<TPresence>): void;
73
92
  }
@@ -1,5 +1,3 @@
1
- import { MaybePromise } from "@pluv/types";
2
-
3
1
  export abstract class AbstractStorageStore {
4
2
  public room: string;
5
3
 
@@ -1,26 +1,31 @@
1
- import type { YjsDoc } from "@pluv/crdt-yjs";
1
+ import type { TrackOriginOptions, YjsDoc } from "@pluv/crdt-yjs";
2
2
  import { doc } from "@pluv/crdt-yjs";
3
3
  import type { AbstractType } from "yjs";
4
4
 
5
- export interface CrdtManagerOptions<
6
- TStorage extends Record<string, AbstractType<any>> = {}
7
- > {
5
+ export type CrdtManagerOptions<
6
+ TStorage extends Record<string, AbstractType<any>> = {},
7
+ > = {
8
8
  encodedState?: string | Uint8Array | null;
9
9
  initialStorage?: () => TStorage;
10
- }
10
+ } & TrackOriginOptions;
11
11
 
12
- interface CrdtManagerInitializeParams {
12
+ type CrdtManagerInitializeParams = {
13
13
  onInitialized?: () => void;
14
14
  origin?: any;
15
15
  update: string | readonly string[];
16
- }
16
+ } & TrackOriginOptions;
17
17
 
18
18
  export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
19
19
  public doc: YjsDoc<TStorage>;
20
20
  public initialized: boolean = false;
21
21
 
22
22
  constructor(options: CrdtManagerOptions<TStorage> = {}) {
23
- const { encodedState, initialStorage } = options;
23
+ const {
24
+ captureTimeout,
25
+ encodedState,
26
+ initialStorage,
27
+ trackedOrigins = [],
28
+ } = options;
24
29
 
25
30
  const _doc = encodedState
26
31
  ? doc<TStorage>().applyUpdate(encodedState)
@@ -28,6 +33,7 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
28
33
 
29
34
  if (_doc && !!Object.keys(_doc.toJSON()).length) {
30
35
  this.doc = _doc;
36
+ this.trackOrigins({ captureTimeout, trackedOrigins });
31
37
 
32
38
  return;
33
39
  }
@@ -35,6 +41,7 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
35
41
  _doc?.destroy();
36
42
 
37
43
  this.doc = doc<TStorage>(initialStorage?.());
44
+ this.trackOrigins({ captureTimeout, trackedOrigins });
38
45
  }
39
46
 
40
47
  /**
@@ -63,10 +70,18 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
63
70
  }
64
71
 
65
72
  public initialize(params: CrdtManagerInitializeParams): this {
66
- const { onInitialized, origin, update } = params;
73
+ const {
74
+ captureTimeout,
75
+ onInitialized,
76
+ origin,
77
+ trackedOrigins,
78
+ update,
79
+ } = params;
67
80
 
68
81
  const updates = typeof update === "string" ? [update] : update;
69
82
 
83
+ this.trackOrigins({ captureTimeout, trackedOrigins });
84
+
70
85
  if (!updates.length) return this;
71
86
 
72
87
  if (this.initialized) {
@@ -87,13 +102,21 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
87
102
 
88
103
  onInitialized?.();
89
104
 
105
+ this.trackOrigins({ captureTimeout, trackedOrigins });
106
+
90
107
  return this;
91
108
  }
92
109
 
110
+ public trackOrigins(options: TrackOriginOptions): void {
111
+ const { captureTimeout, trackedOrigins } = options;
112
+
113
+ this.doc.trackOrigins({ captureTimeout, trackedOrigins });
114
+ }
115
+
93
116
  private _applyDocUpdates(
94
117
  yjsDoc: YjsDoc<TStorage>,
95
118
  updates: readonly string[],
96
- origin?: string
119
+ origin?: string,
97
120
  ): YjsDoc<TStorage> {
98
121
  yjsDoc.transact(() => {
99
122
  updates.reduce((acc, _update) => acc.applyUpdate(_update), yjsDoc);
@@ -3,13 +3,17 @@ import { makeSubject, Subject, subscribe } from "wonka";
3
3
  import type { AbstractType } from "yjs";
4
4
 
5
5
  export class CrdtNotifier<TStorage extends Record<string, AbstractType<any>>> {
6
+ public rootSubject = makeSubject<{
7
+ [P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
8
+ }>();
9
+
6
10
  public subjects = new Map<
7
11
  keyof TStorage,
8
12
  Subject<InferYjsSharedTypeJson<TStorage[keyof TStorage]>> | null
9
13
  >();
10
14
 
11
15
  public subject<TKey extends keyof TStorage>(
12
- key: TKey
16
+ key: TKey,
13
17
  ): Subject<InferYjsSharedTypeJson<TStorage[TKey]>> {
14
18
  const subject = this.subjects.get(key);
15
19
 
@@ -25,9 +29,17 @@ export class CrdtNotifier<TStorage extends Record<string, AbstractType<any>>> {
25
29
  return newSubject as Subject<InferYjsSharedTypeJson<TStorage[TKey]>>;
26
30
  }
27
31
 
32
+ public subcribeRoot(
33
+ callback: (value: {
34
+ [P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
35
+ }) => void,
36
+ ): () => void {
37
+ return subscribe(callback)(this.rootSubject.source).unsubscribe;
38
+ }
39
+
28
40
  public subscribe<TKey extends keyof TStorage>(
29
41
  key: TKey,
30
- callback: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void
42
+ callback: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void,
31
43
  ): () => void {
32
44
  const subject = this.subject(key);
33
45