@pluv/client 1.0.1 → 1.0.2

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,5 +1,5 @@
1
1
 
2
- > @pluv/client@1.0.1 build /home/runner/work/pluv/pluv/packages/client
2
+ > @pluv/client@1.0.2 build /home/runner/work/pluv/pluv/packages/client
3
3
  > tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- CJS dist/index.js 58.07 KB
12
- CJS ⚡️ Build success in 125ms
13
- ESM dist/index.mjs 56.75 KB
14
- ESM ⚡️ Build success in 127ms
11
+ ESM dist/index.mjs 57.31 KB
12
+ ESM ⚡️ Build success in 126ms
13
+ CJS dist/index.js 58.63 KB
14
+ CJS ⚡️ Build success in 126ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 3647ms
17
- DTS dist/index.d.mts 25.34 KB
18
- DTS dist/index.d.ts 25.34 KB
16
+ DTS ⚡️ Build success in 3638ms
17
+ DTS dist/index.d.mts 25.31 KB
18
+ DTS dist/index.d.ts 25.31 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @pluv/client
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d87159c: Restored `@pluv/addon-indexeddb` functionality, and fixed `@pluv/addon-indexeddb` sometimes duplicating storage operations based on `initialStorage` values.
8
+ - @pluv/crdt@1.0.2
9
+ - @pluv/types@1.0.2
10
+
3
11
  ## 1.0.1
4
12
 
5
13
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -188,7 +188,6 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
188
188
  undo: () => void;
189
189
  updateMyPresence: (presence: UpdateMyPresenceAction<TPresence>) => void;
190
190
  private _addToStorageStore;
191
- private _applyStorageStore;
192
191
  private _attachWindowListeners;
193
192
  private _attachWsListeners;
194
193
  private _clearInterval;
package/dist/index.d.ts CHANGED
@@ -188,7 +188,6 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
188
188
  undo: () => void;
189
189
  updateMyPresence: (presence: UpdateMyPresenceAction<TPresence>) => void;
190
190
  private _addToStorageStore;
191
- private _applyStorageStore;
192
191
  private _attachWindowListeners;
193
192
  private _attachWsListeners;
194
193
  private _clearInterval;
package/dist/index.js CHANGED
@@ -136,14 +136,17 @@ var CrdtManager = class {
136
136
  * update. This unfortunately means updates can be dropped entirely
137
137
  * @date June 13, 2023
138
138
  */
139
- applyUpdate(update, origin) {
139
+ applyUpdate(params) {
140
+ const { origin, update } = params;
140
141
  if (!this.initialized) return this;
141
142
  const updates = typeof update === "string" ? [update] : update;
142
143
  this._applyDocUpdates(this.doc, updates, origin);
143
144
  return this;
144
145
  }
145
146
  destroy() {
147
+ this.initialized = false;
146
148
  this.doc.destroy();
149
+ this.doc = this._docFactory.getEmpty();
147
150
  }
148
151
  get(key) {
149
152
  return this.doc.get(key);
@@ -152,7 +155,7 @@ var CrdtManager = class {
152
155
  return this._docFactory.getInitialized().getEncodedState();
153
156
  }
154
157
  initialize(params) {
155
- const { onInitialized, origin, update } = params;
158
+ const { origin, update } = params;
156
159
  if (this.initialized) {
157
160
  console.warn("Attempted to initialize storage multiple times");
158
161
  return this;
@@ -172,9 +175,11 @@ var CrdtManager = class {
172
175
  ).rebuildStorage(reference);
173
176
  this.initialized = true;
174
177
  initialized.destroy();
175
- onInitialized == null ? void 0 : onInitialized(this.doc.getEncodedState());
176
178
  return this;
177
179
  }
180
+ resolveEncodedState(updates) {
181
+ return this._docFactory.resolveEncodedState(updates);
182
+ }
178
183
  _applyDocUpdates(doc, updates, origin) {
179
184
  doc.transact(() => {
180
185
  doc.batchApplyEncodedState({ origin, updates });
@@ -734,7 +739,7 @@ var PluvRoom = class extends AbstractRoom {
734
739
  const shouldFlatten = yield this._storageStore.getShouldFlatten();
735
740
  if (!shouldFlatten) return;
736
741
  const updates = yield this._storageStore.getUpdates();
737
- this._crdtManager.applyUpdate(updates, this);
742
+ this._crdtManager.applyUpdate({ update: updates, origin: this });
738
743
  const encodedState = this._crdtManager.doc.getEncodedState();
739
744
  yield this._storageStore.flatten(encodedState);
740
745
  }),
@@ -791,6 +796,7 @@ var PluvRoom = class extends AbstractRoom {
791
796
  oldState.connection.state = ConnectionState.Connecting;
792
797
  return oldState;
793
798
  });
799
+ yield this._storageStore.initialize();
794
800
  const wsEndpoint = this._getWsEndpoint(this.id, params);
795
801
  const url = new URL(wsEndpoint);
796
802
  let authToken = null;
@@ -855,20 +861,6 @@ var PluvRoom = class extends AbstractRoom {
855
861
  this._flattenStorageStore();
856
862
  });
857
863
  }
858
- _applyStorageStore() {
859
- return __async(this, null, function* () {
860
- const updates = yield this._storageStore.getUpdates();
861
- this._crdtManager.initialize({
862
- onInitialized: () => {
863
- this._stateNotifier.subjects["storage-loaded"].next(true);
864
- this._observeCrdt();
865
- },
866
- origin: ORIGIN_INITIALIZED,
867
- update: updates
868
- });
869
- this._emitSharedTypes();
870
- });
871
- }
872
864
  _attachWindowListeners() {
873
865
  if (typeof window === "undefined") return;
874
866
  if (typeof document === "undefined") return;
@@ -1059,33 +1051,35 @@ var PluvRoom = class extends AbstractRoom {
1059
1051
  this._stateNotifier.subjects.others.next(others);
1060
1052
  }
1061
1053
  _handleRegisteredMessage(message) {
1062
- var _a, _b;
1063
- const { connectionId } = message;
1064
- const user = message.user;
1065
- if (!connectionId) return;
1066
- if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1067
- const data = message.data;
1068
- const state = data.state;
1069
- this._updateState((oldState) => {
1070
- oldState.connection.count += 1;
1071
- oldState.connection.id = connectionId;
1072
- oldState.authorization.user = user != null ? user : null;
1073
- return oldState;
1074
- });
1075
- const userInfo = {
1076
- connectionId,
1077
- presence: (_a = data.presence) != null ? _a : void 0,
1078
- user
1079
- };
1080
- this._usersManager.setMyself(userInfo);
1081
- const presence = this._usersManager.myPresence;
1082
- const myself = (_b = this._usersManager.myself) != null ? _b : null;
1083
- this._stateNotifier.subjects["my-presence"].next(presence);
1084
- this._stateNotifier.subjects.myself.next(myself);
1085
- const update = this._crdtManager.getInitialState();
1086
- this._sendMessage({
1087
- type: "$initializeSession",
1088
- data: { presence, update }
1054
+ return __async(this, null, function* () {
1055
+ var _a, _b, _c;
1056
+ const { connectionId } = message;
1057
+ const user = message.user;
1058
+ if (!connectionId) return;
1059
+ if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1060
+ const data = message.data;
1061
+ const state = data.state;
1062
+ this._updateState((oldState) => {
1063
+ oldState.connection.count += 1;
1064
+ oldState.connection.id = connectionId;
1065
+ oldState.authorization.user = user != null ? user : null;
1066
+ return oldState;
1067
+ });
1068
+ const userInfo = {
1069
+ connectionId,
1070
+ presence: (_a = data.presence) != null ? _a : void 0,
1071
+ user
1072
+ };
1073
+ this._usersManager.setMyself(userInfo);
1074
+ const presence = this._usersManager.myPresence;
1075
+ const myself = (_b = this._usersManager.myself) != null ? _b : null;
1076
+ this._stateNotifier.subjects["my-presence"].next(presence);
1077
+ this._stateNotifier.subjects.myself.next(myself);
1078
+ const update = !state ? (_c = this._crdtManager.resolveEncodedState(yield this._storageStore.getUpdates())) != null ? _c : this._crdtManager.getInitialState() : this._crdtManager.getInitialState();
1079
+ this._sendMessage({
1080
+ type: "$initializeSession",
1081
+ data: { presence, update }
1082
+ });
1089
1083
  });
1090
1084
  }
1091
1085
  _handleStorageReceivedMessage(message) {
@@ -1094,24 +1088,34 @@ var PluvRoom = class extends AbstractRoom {
1094
1088
  if (!connectionId) return;
1095
1089
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1096
1090
  const data = message.data;
1091
+ const changeKind = data.changeKind;
1097
1092
  const state = data.state;
1098
- this._crdtManager.initialize({
1099
- onInitialized: (update2) => __async(this, null, function* () {
1100
- yield this._addToStorageStore(update2);
1101
- this._emitSharedTypes();
1102
- this._observeCrdt();
1103
- this._stateNotifier.subjects["storage-loaded"].next(true);
1104
- }),
1105
- origin: ORIGIN_INITIALIZED,
1106
- update: state
1107
- });
1093
+ const updates = yield this._storageStore.getUpdates();
1094
+ const origin = ORIGIN_INITIALIZED;
1095
+ const updateStorage = (update) => {
1096
+ return this._crdtManager.initialized ? this._crdtManager.applyUpdate({ update, origin }) : this._crdtManager.initialize({ update, origin });
1097
+ };
1098
+ if (changeKind === "empty") {
1099
+ this._crdtManager.destroy();
1100
+ this._crdtManager.initialize({ origin, update: updates });
1101
+ } else if (changeKind === "initialized") {
1102
+ if (!!updates.length) updateStorage(updates);
1103
+ else {
1104
+ const encodedState2 = updateStorage(state).doc.getEncodedState();
1105
+ this._addToStorageStore(encodedState2);
1106
+ }
1107
+ } else {
1108
+ const encodedState2 = updateStorage(state).doc.getEncodedState();
1109
+ if (!!updates.length) this._crdtManager.applyUpdate({ update: updates, origin });
1110
+ yield this._addToStorageStore(encodedState2);
1111
+ }
1108
1112
  const encodedState = this._crdtManager.doc.getEncodedState();
1109
- yield this._storageStore.addUpdate(encodedState);
1110
1113
  this._emitSharedTypes();
1111
- const update = this._crdtManager.doc.getEncodedState();
1114
+ this._observeCrdt();
1115
+ this._stateNotifier.subjects["storage-loaded"].next(true);
1112
1116
  this._sendMessage({
1113
1117
  type: "$updateStorage",
1114
- data: { origin: ORIGIN_INITIALIZED, update }
1118
+ data: { origin, update: encodedState }
1115
1119
  });
1116
1120
  });
1117
1121
  }
@@ -1190,11 +1194,11 @@ var PluvRoom = class extends AbstractRoom {
1190
1194
  const unsubscribe = this._crdtManager.doc.subscribe((event) => {
1191
1195
  var _a2;
1192
1196
  const origin = (_a2 = event.origin) != null ? _a2 : null;
1193
- this._addToStorageStore(event.update);
1194
1197
  this._emitSharedTypes();
1195
1198
  if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) {
1196
1199
  return;
1197
1200
  }
1201
+ this._addToStorageStore(event.update);
1198
1202
  if (!this._state.webSocket) return;
1199
1203
  if (!this._state.connection.id) return;
1200
1204
  if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
package/dist/index.mjs CHANGED
@@ -105,14 +105,17 @@ var CrdtManager = class {
105
105
  * update. This unfortunately means updates can be dropped entirely
106
106
  * @date June 13, 2023
107
107
  */
108
- applyUpdate(update, origin) {
108
+ applyUpdate(params) {
109
+ const { origin, update } = params;
109
110
  if (!this.initialized) return this;
110
111
  const updates = typeof update === "string" ? [update] : update;
111
112
  this._applyDocUpdates(this.doc, updates, origin);
112
113
  return this;
113
114
  }
114
115
  destroy() {
116
+ this.initialized = false;
115
117
  this.doc.destroy();
118
+ this.doc = this._docFactory.getEmpty();
116
119
  }
117
120
  get(key) {
118
121
  return this.doc.get(key);
@@ -121,7 +124,7 @@ var CrdtManager = class {
121
124
  return this._docFactory.getInitialized().getEncodedState();
122
125
  }
123
126
  initialize(params) {
124
- const { onInitialized, origin, update } = params;
127
+ const { origin, update } = params;
125
128
  if (this.initialized) {
126
129
  console.warn("Attempted to initialize storage multiple times");
127
130
  return this;
@@ -141,9 +144,11 @@ var CrdtManager = class {
141
144
  ).rebuildStorage(reference);
142
145
  this.initialized = true;
143
146
  initialized.destroy();
144
- onInitialized == null ? void 0 : onInitialized(this.doc.getEncodedState());
145
147
  return this;
146
148
  }
149
+ resolveEncodedState(updates) {
150
+ return this._docFactory.resolveEncodedState(updates);
151
+ }
147
152
  _applyDocUpdates(doc, updates, origin) {
148
153
  doc.transact(() => {
149
154
  doc.batchApplyEncodedState({ origin, updates });
@@ -703,7 +708,7 @@ var PluvRoom = class extends AbstractRoom {
703
708
  const shouldFlatten = yield this._storageStore.getShouldFlatten();
704
709
  if (!shouldFlatten) return;
705
710
  const updates = yield this._storageStore.getUpdates();
706
- this._crdtManager.applyUpdate(updates, this);
711
+ this._crdtManager.applyUpdate({ update: updates, origin: this });
707
712
  const encodedState = this._crdtManager.doc.getEncodedState();
708
713
  yield this._storageStore.flatten(encodedState);
709
714
  }),
@@ -760,6 +765,7 @@ var PluvRoom = class extends AbstractRoom {
760
765
  oldState.connection.state = ConnectionState.Connecting;
761
766
  return oldState;
762
767
  });
768
+ yield this._storageStore.initialize();
763
769
  const wsEndpoint = this._getWsEndpoint(this.id, params);
764
770
  const url = new URL(wsEndpoint);
765
771
  let authToken = null;
@@ -824,20 +830,6 @@ var PluvRoom = class extends AbstractRoom {
824
830
  this._flattenStorageStore();
825
831
  });
826
832
  }
827
- _applyStorageStore() {
828
- return __async(this, null, function* () {
829
- const updates = yield this._storageStore.getUpdates();
830
- this._crdtManager.initialize({
831
- onInitialized: () => {
832
- this._stateNotifier.subjects["storage-loaded"].next(true);
833
- this._observeCrdt();
834
- },
835
- origin: ORIGIN_INITIALIZED,
836
- update: updates
837
- });
838
- this._emitSharedTypes();
839
- });
840
- }
841
833
  _attachWindowListeners() {
842
834
  if (typeof window === "undefined") return;
843
835
  if (typeof document === "undefined") return;
@@ -1028,33 +1020,35 @@ var PluvRoom = class extends AbstractRoom {
1028
1020
  this._stateNotifier.subjects.others.next(others);
1029
1021
  }
1030
1022
  _handleRegisteredMessage(message) {
1031
- var _a, _b;
1032
- const { connectionId } = message;
1033
- const user = message.user;
1034
- if (!connectionId) return;
1035
- if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1036
- const data = message.data;
1037
- const state = data.state;
1038
- this._updateState((oldState) => {
1039
- oldState.connection.count += 1;
1040
- oldState.connection.id = connectionId;
1041
- oldState.authorization.user = user != null ? user : null;
1042
- return oldState;
1043
- });
1044
- const userInfo = {
1045
- connectionId,
1046
- presence: (_a = data.presence) != null ? _a : void 0,
1047
- user
1048
- };
1049
- this._usersManager.setMyself(userInfo);
1050
- const presence = this._usersManager.myPresence;
1051
- const myself = (_b = this._usersManager.myself) != null ? _b : null;
1052
- this._stateNotifier.subjects["my-presence"].next(presence);
1053
- this._stateNotifier.subjects.myself.next(myself);
1054
- const update = this._crdtManager.getInitialState();
1055
- this._sendMessage({
1056
- type: "$initializeSession",
1057
- data: { presence, update }
1023
+ return __async(this, null, function* () {
1024
+ var _a, _b, _c;
1025
+ const { connectionId } = message;
1026
+ const user = message.user;
1027
+ if (!connectionId) return;
1028
+ if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1029
+ const data = message.data;
1030
+ const state = data.state;
1031
+ this._updateState((oldState) => {
1032
+ oldState.connection.count += 1;
1033
+ oldState.connection.id = connectionId;
1034
+ oldState.authorization.user = user != null ? user : null;
1035
+ return oldState;
1036
+ });
1037
+ const userInfo = {
1038
+ connectionId,
1039
+ presence: (_a = data.presence) != null ? _a : void 0,
1040
+ user
1041
+ };
1042
+ this._usersManager.setMyself(userInfo);
1043
+ const presence = this._usersManager.myPresence;
1044
+ const myself = (_b = this._usersManager.myself) != null ? _b : null;
1045
+ this._stateNotifier.subjects["my-presence"].next(presence);
1046
+ this._stateNotifier.subjects.myself.next(myself);
1047
+ const update = !state ? (_c = this._crdtManager.resolveEncodedState(yield this._storageStore.getUpdates())) != null ? _c : this._crdtManager.getInitialState() : this._crdtManager.getInitialState();
1048
+ this._sendMessage({
1049
+ type: "$initializeSession",
1050
+ data: { presence, update }
1051
+ });
1058
1052
  });
1059
1053
  }
1060
1054
  _handleStorageReceivedMessage(message) {
@@ -1063,24 +1057,34 @@ var PluvRoom = class extends AbstractRoom {
1063
1057
  if (!connectionId) return;
1064
1058
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1065
1059
  const data = message.data;
1060
+ const changeKind = data.changeKind;
1066
1061
  const state = data.state;
1067
- this._crdtManager.initialize({
1068
- onInitialized: (update2) => __async(this, null, function* () {
1069
- yield this._addToStorageStore(update2);
1070
- this._emitSharedTypes();
1071
- this._observeCrdt();
1072
- this._stateNotifier.subjects["storage-loaded"].next(true);
1073
- }),
1074
- origin: ORIGIN_INITIALIZED,
1075
- update: state
1076
- });
1062
+ const updates = yield this._storageStore.getUpdates();
1063
+ const origin = ORIGIN_INITIALIZED;
1064
+ const updateStorage = (update) => {
1065
+ return this._crdtManager.initialized ? this._crdtManager.applyUpdate({ update, origin }) : this._crdtManager.initialize({ update, origin });
1066
+ };
1067
+ if (changeKind === "empty") {
1068
+ this._crdtManager.destroy();
1069
+ this._crdtManager.initialize({ origin, update: updates });
1070
+ } else if (changeKind === "initialized") {
1071
+ if (!!updates.length) updateStorage(updates);
1072
+ else {
1073
+ const encodedState2 = updateStorage(state).doc.getEncodedState();
1074
+ this._addToStorageStore(encodedState2);
1075
+ }
1076
+ } else {
1077
+ const encodedState2 = updateStorage(state).doc.getEncodedState();
1078
+ if (!!updates.length) this._crdtManager.applyUpdate({ update: updates, origin });
1079
+ yield this._addToStorageStore(encodedState2);
1080
+ }
1077
1081
  const encodedState = this._crdtManager.doc.getEncodedState();
1078
- yield this._storageStore.addUpdate(encodedState);
1079
1082
  this._emitSharedTypes();
1080
- const update = this._crdtManager.doc.getEncodedState();
1083
+ this._observeCrdt();
1084
+ this._stateNotifier.subjects["storage-loaded"].next(true);
1081
1085
  this._sendMessage({
1082
1086
  type: "$updateStorage",
1083
- data: { origin: ORIGIN_INITIALIZED, update }
1087
+ data: { origin, update: encodedState }
1084
1088
  });
1085
1089
  });
1086
1090
  }
@@ -1159,11 +1163,11 @@ var PluvRoom = class extends AbstractRoom {
1159
1163
  const unsubscribe = this._crdtManager.doc.subscribe((event) => {
1160
1164
  var _a2;
1161
1165
  const origin = (_a2 = event.origin) != null ? _a2 : null;
1162
- this._addToStorageStore(event.update);
1163
1166
  this._emitSharedTypes();
1164
1167
  if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) {
1165
1168
  return;
1166
1169
  }
1170
+ this._addToStorageStore(event.update);
1167
1171
  if (!this._state.webSocket) return;
1168
1172
  if (!this._state.connection.id) return;
1169
1173
  if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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": "^1.0.1",
22
- "@pluv/types": "^1.0.1"
21
+ "@pluv/crdt": "^1.0.2",
22
+ "@pluv/types": "^1.0.2"
23
23
  },
24
24
  "devDependencies": {
25
25
  "eslint": "^9.25.1",
26
26
  "tsup": "^8.4.0",
27
27
  "typescript": "^5.8.3",
28
- "@pluv/tsconfig": "^1.0.1",
29
- "eslint-config-pluv": "^1.0.1"
28
+ "@pluv/tsconfig": "^1.0.2",
29
+ "eslint-config-pluv": "^1.0.2"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -5,11 +5,15 @@ export type CrdtManagerOptions<TStorage extends Record<string, CrdtType<any, any
5
5
  initialStorage?: AbstractCrdtDocFactory<TStorage>;
6
6
  };
7
7
 
8
- type CrdtManagerInitializeParams = {
9
- onInitialized?: (state: string) => void;
8
+ interface CrdtManagerInitializeParams {
10
9
  origin?: any;
11
10
  update: string | readonly string[];
12
- };
11
+ }
12
+
13
+ interface ApplyUpdateParams {
14
+ origin?: any;
15
+ update: string | readonly string[];
16
+ }
13
17
 
14
18
  export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
15
19
  public doc: AbstractCrdtDoc<TStorage>;
@@ -30,7 +34,9 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
30
34
  * update. This unfortunately means updates can be dropped entirely
31
35
  * @date June 13, 2023
32
36
  */
33
- public applyUpdate(update: string | readonly string[], origin?: any): this {
37
+ public applyUpdate(params: ApplyUpdateParams): this {
38
+ const { origin, update } = params;
39
+
34
40
  if (!this.initialized) return this;
35
41
 
36
42
  const updates: readonly string[] = typeof update === "string" ? [update] : update;
@@ -41,7 +47,9 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
41
47
  }
42
48
 
43
49
  public destroy(): void {
50
+ this.initialized = false;
44
51
  this.doc.destroy();
52
+ this.doc = this._docFactory.getEmpty();
45
53
  }
46
54
 
47
55
  public get<TKey extends keyof TStorage>(key: TKey): TStorage[TKey] {
@@ -53,7 +61,7 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
53
61
  }
54
62
 
55
63
  public initialize(params: CrdtManagerInitializeParams): this {
56
- const { onInitialized, origin, update } = params;
64
+ const { origin, update } = params;
57
65
 
58
66
  /**
59
67
  * !HACK
@@ -94,11 +102,13 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
94
102
  this.initialized = true;
95
103
  initialized.destroy();
96
104
 
97
- onInitialized?.(this.doc.getEncodedState());
98
-
99
105
  return this;
100
106
  }
101
107
 
108
+ public resolveEncodedState(updates: string | string[] | readonly string[]): string | null {
109
+ return this._docFactory.resolveEncodedState(updates);
110
+ }
111
+
102
112
  private _applyDocUpdates(
103
113
  doc: AbstractCrdtDoc<TStorage>,
104
114
  updates: readonly string[],
package/src/PluvRoom.ts CHANGED
@@ -389,8 +389,7 @@ export class PluvRoom<
389
389
  return oldState;
390
390
  });
391
391
 
392
- // await this._storageStore.initialize();
393
- // await this._applyStorageStore();
392
+ await this._storageStore.initialize();
394
393
 
395
394
  const wsEndpoint = this._getWsEndpoint(this.id, params);
396
395
  const url = new URL(wsEndpoint);
@@ -612,22 +611,6 @@ export class PluvRoom<
612
611
  this._flattenStorageStore();
613
612
  }
614
613
 
615
- private async _applyStorageStore(): Promise<void> {
616
- const updates = await this._storageStore.getUpdates();
617
-
618
- this._crdtManager.initialize({
619
- onInitialized: () => {
620
- this._stateNotifier.subjects["storage-loaded"].next(true);
621
-
622
- this._observeCrdt();
623
- },
624
- origin: ORIGIN_INITIALIZED,
625
- update: updates,
626
- });
627
-
628
- this._emitSharedTypes();
629
- }
630
-
631
614
  private _attachWindowListeners(): void {
632
615
  if (typeof window === "undefined") return;
633
616
  if (typeof document === "undefined") return;
@@ -738,7 +721,7 @@ export class PluvRoom<
738
721
 
739
722
  const updates = await this._storageStore.getUpdates();
740
723
 
741
- this._crdtManager.applyUpdate(updates, this);
724
+ this._crdtManager.applyUpdate({ update: updates, origin: this });
742
725
 
743
726
  const encodedState = this._crdtManager.doc.getEncodedState();
744
727
 
@@ -941,7 +924,7 @@ export class PluvRoom<
941
924
  this._stateNotifier.subjects.others.next(others);
942
925
  }
943
926
 
944
- private _handleRegisteredMessage(message: IOEventMessage<TIO>): void {
927
+ private async _handleRegisteredMessage(message: IOEventMessage<TIO>): Promise<void> {
945
928
  const { connectionId } = message;
946
929
  const user = message.user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
947
930
 
@@ -950,11 +933,6 @@ export class PluvRoom<
950
933
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
951
934
 
952
935
  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
936
  const state = data.state;
959
937
 
960
938
  this._updateState((oldState) => {
@@ -978,7 +956,10 @@ export class PluvRoom<
978
956
  this._stateNotifier.subjects["my-presence"].next(presence);
979
957
  this._stateNotifier.subjects.myself.next(myself);
980
958
 
981
- const update = this._crdtManager.getInitialState();
959
+ const update = !state
960
+ ? (this._crdtManager.resolveEncodedState(await this._storageStore.getUpdates()) ??
961
+ this._crdtManager.getInitialState())
962
+ : this._crdtManager.getInitialState();
982
963
 
983
964
  this._sendMessage({
984
965
  type: "$initializeSession",
@@ -994,30 +975,45 @@ export class PluvRoom<
994
975
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
995
976
 
996
977
  const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$storageReceived"];
978
+ const changeKind = data.changeKind;
997
979
  const state = data.state;
998
980
 
999
- this._crdtManager.initialize({
1000
- onInitialized: async (update) => {
1001
- await this._addToStorageStore(update);
1002
- this._emitSharedTypes();
1003
- this._observeCrdt();
1004
- this._stateNotifier.subjects["storage-loaded"].next(true);
1005
- },
1006
- origin: ORIGIN_INITIALIZED,
1007
- update: state,
1008
- });
981
+ const updates = await this._storageStore.getUpdates();
982
+ const origin = ORIGIN_INITIALIZED;
1009
983
 
1010
- const encodedState = this._crdtManager.doc.getEncodedState();
984
+ const updateStorage = (update: string | readonly string[]) => {
985
+ return this._crdtManager.initialized
986
+ ? this._crdtManager.applyUpdate({ update, origin })
987
+ : this._crdtManager.initialize({ update, origin });
988
+ };
1011
989
 
1012
- await this._storageStore.addUpdate(encodedState);
990
+ if (changeKind === "empty") {
991
+ this._crdtManager.destroy();
992
+ this._crdtManager.initialize({ origin, update: updates });
993
+ } else if (changeKind === "initialized") {
994
+ if (!!updates.length) updateStorage(updates);
995
+ else {
996
+ const encodedState = updateStorage(state).doc.getEncodedState();
1013
997
 
1014
- this._emitSharedTypes();
998
+ this._addToStorageStore(encodedState);
999
+ }
1000
+ } else {
1001
+ const encodedState = updateStorage(state).doc.getEncodedState();
1002
+
1003
+ if (!!updates.length) this._crdtManager.applyUpdate({ update: updates, origin });
1004
+
1005
+ await this._addToStorageStore(encodedState);
1006
+ }
1007
+
1008
+ const encodedState = this._crdtManager.doc.getEncodedState();
1015
1009
 
1016
- const update = this._crdtManager.doc.getEncodedState();
1010
+ this._emitSharedTypes();
1011
+ this._observeCrdt();
1012
+ this._stateNotifier.subjects["storage-loaded"].next(true);
1017
1013
 
1018
1014
  this._sendMessage({
1019
1015
  type: "$updateStorage",
1020
- data: { origin: ORIGIN_INITIALIZED, update },
1016
+ data: { origin, update: encodedState },
1021
1017
  });
1022
1018
  }
1023
1019
 
@@ -1135,13 +1131,14 @@ export class PluvRoom<
1135
1131
  const unsubscribe = this._crdtManager.doc.subscribe((event) => {
1136
1132
  const origin = event.origin ?? null;
1137
1133
 
1138
- this._addToStorageStore(event.update);
1139
1134
  this._emitSharedTypes();
1140
1135
 
1141
1136
  if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) {
1142
1137
  return;
1143
1138
  }
1144
1139
 
1140
+ this._addToStorageStore(event.update);
1141
+
1145
1142
  if (!this._state.webSocket) return;
1146
1143
  if (!this._state.connection.id) return;
1147
1144
  if (this._state.webSocket.readyState !== WebSocket.OPEN) return;