@signe/room 3.0.0 → 3.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.
package/dist/index.d.ts CHANGED
@@ -42,6 +42,21 @@ declare function RoomGuard(guards: RoomGuardFn[]): (target: any) => void;
42
42
  */
43
43
  declare function Guard(guards: GuardFn[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
44
44
 
45
+ type StorageRestoreContext<TSnapshot = any> = {
46
+ snapshot: TSnapshot;
47
+ room: Room$1;
48
+ server: Server;
49
+ legacy: boolean;
50
+ };
51
+ type UserStorageRestoreContext<TUser = any, TSnapshot = any> = {
52
+ userSnapshot: TSnapshot;
53
+ user: TUser | undefined;
54
+ publicId: string;
55
+ usersPropName: string;
56
+ room: Room$1;
57
+ server: Server;
58
+ legacy: boolean;
59
+ };
45
60
  type SessionData = {
46
61
  publicId: string;
47
62
  state?: any;
@@ -109,6 +124,11 @@ declare class Server implements Server$1 {
109
124
  private putStorageEntries;
110
125
  private deleteStorageKeys;
111
126
  private deleteStatePath;
127
+ private containsDeleteToken;
128
+ private compactStateStorage;
129
+ private createUserFromClassType;
130
+ private restoreUsersStorageSnapshot;
131
+ private restoreStorageSnapshot;
112
132
  private createStorageMetrics;
113
133
  private getTransferExpiryTime;
114
134
  private getPrivateId;
@@ -806,4 +826,4 @@ declare function createRequireSessionGuard(storage: Storage$1): (sender: Connect
806
826
  */
807
827
  declare const requireSession: (sender: Connection, value: any, room: Room$1) => Promise<boolean>;
808
828
 
809
- export { Action, ClientIo, Guard, MockConnection, Request, type RequestOptions, Room, RoomGuard, type RoomInterceptorPacket, type RoomMethods, type RoomOnJoin, type RoomOnLeave, type RoomOptions, Server, ServerIo, ServerResponse, Shard, type ShardOptions, UnhandledAction, WorldRoom, createRequireSessionGuard, request, requireSession, testRoom, tick };
829
+ export { Action, ClientIo, Guard, MockConnection, Request, type RequestOptions, Room, RoomGuard, type RoomInterceptorPacket, type RoomMethods, type RoomOnJoin, type RoomOnLeave, type RoomOptions, Server, ServerIo, ServerResponse, Shard, type ShardOptions, type StorageRestoreContext, UnhandledAction, type UserStorageRestoreContext, WorldRoom, createRequireSessionGuard, request, requireSession, testRoom, tick };
package/dist/index.js CHANGED
@@ -544,15 +544,114 @@ var Server = class {
544
544
  const descendantEntries = await this.listStorage(`${stateKey}.`);
545
545
  await this.deleteStorageKeys([
546
546
  ...Array.from(descendantEntries.keys()),
547
- path
547
+ stateKey
548
548
  ]);
549
549
  await this.saveStatePath(path, DELETE_TOKEN);
550
550
  }
551
+ containsDeleteToken(value) {
552
+ if (value === DELETE_TOKEN) {
553
+ return true;
554
+ }
555
+ if (!value || typeof value !== "object") {
556
+ return false;
557
+ }
558
+ if (Array.isArray(value)) {
559
+ return value.some((item) => this.containsDeleteToken(item));
560
+ }
561
+ return Object.values(value).some((item) => this.containsDeleteToken(item));
562
+ }
563
+ async compactStateStorage(instance) {
564
+ const entries = await this.listStorage(STATE_PREFIX);
565
+ const keys = Array.from(entries.keys());
566
+ if (keys.length === 0) {
567
+ return;
568
+ }
569
+ const snapshot = createStatesSnapshotDeep(instance);
570
+ await this.deleteStorageKeys(keys);
571
+ await this.saveStatePath(".", snapshot);
572
+ const metrics = instance.$storageMetrics;
573
+ if (metrics) {
574
+ metrics.stateCompactions += 1;
575
+ metrics.stateCompactionDeletes += keys.length;
576
+ }
577
+ }
578
+ createUserFromClassType(classType, ...args) {
579
+ if (!classType) {
580
+ return void 0;
581
+ }
582
+ return isClass(classType) ? new classType() : classType(...args);
583
+ }
584
+ async restoreUsersStorageSnapshot(instance, snapshot, options) {
585
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) {
586
+ return snapshot;
587
+ }
588
+ const restoreUser = instance["onUserStorageRestore"];
589
+ if (typeof restoreUser !== "function") {
590
+ return snapshot;
591
+ }
592
+ const signal2 = this.getUsersProperty(instance);
593
+ const usersPropName = this.getUsersPropName(instance);
594
+ if (!signal2 || !usersPropName) {
595
+ return snapshot;
596
+ }
597
+ const usersSnapshot = snapshot[usersPropName];
598
+ if (!usersSnapshot || typeof usersSnapshot !== "object" || Array.isArray(usersSnapshot)) {
599
+ return snapshot;
600
+ }
601
+ const { classType } = signal2.options || {};
602
+ let nextSnapshot = snapshot;
603
+ let nextUsersSnapshot = usersSnapshot;
604
+ for (const [publicId, userSnapshot] of Object.entries(usersSnapshot)) {
605
+ const user = this.createUserFromClassType(classType, publicId);
606
+ const restoredUserSnapshot = await awaitReturn(
607
+ restoreUser.call(instance, {
608
+ userSnapshot,
609
+ user,
610
+ publicId,
611
+ usersPropName,
612
+ room: this.room,
613
+ server: this,
614
+ legacy: options.legacy
615
+ })
616
+ );
617
+ if (restoredUserSnapshot !== void 0 && restoredUserSnapshot !== userSnapshot) {
618
+ if (nextSnapshot === snapshot) {
619
+ nextSnapshot = { ...snapshot };
620
+ }
621
+ if (nextUsersSnapshot === usersSnapshot) {
622
+ nextUsersSnapshot = { ...usersSnapshot };
623
+ nextSnapshot[usersPropName] = nextUsersSnapshot;
624
+ }
625
+ nextUsersSnapshot[publicId] = restoredUserSnapshot;
626
+ }
627
+ }
628
+ return nextSnapshot;
629
+ }
630
+ async restoreStorageSnapshot(instance, snapshot, options) {
631
+ const restoreSnapshot = instance["onStorageRestore"];
632
+ let restoredSnapshot = snapshot;
633
+ if (typeof restoreSnapshot === "function") {
634
+ const result = await awaitReturn(
635
+ restoreSnapshot.call(instance, {
636
+ snapshot,
637
+ room: this.room,
638
+ server: this,
639
+ legacy: options.legacy
640
+ })
641
+ );
642
+ if (result !== void 0) {
643
+ restoredSnapshot = result;
644
+ }
645
+ }
646
+ return this.restoreUsersStorageSnapshot(instance, restoredSnapshot, options);
647
+ }
551
648
  createStorageMetrics() {
552
649
  return {
553
650
  loadMs: 0,
554
651
  loadStateKeys: 0,
555
652
  loadLegacyKeys: 0,
653
+ stateCompactions: 0,
654
+ stateCompactionDeletes: 0,
556
655
  persistFlushes: 0,
557
656
  persistWrites: 0,
558
657
  persistDeletes: 0,
@@ -868,14 +967,26 @@ var Server = class {
868
967
  migratedEntries.map(([path, value]) => [this.stateKey(path), value])
869
968
  )
870
969
  );
871
- if (legacyRoot !== void 0) {
970
+ if (legacyDeleteKeys.length > 0) {
872
971
  await this.deleteStorageKeys(legacyDeleteKeys);
873
972
  }
874
- load(instance, legacyObject, true);
973
+ const restoredLegacyObject = await this.restoreStorageSnapshot(instance, legacyObject, {
974
+ legacy: true
975
+ });
976
+ load(instance, restoredLegacyObject, true);
977
+ if (this.containsDeleteToken(restoredLegacyObject)) {
978
+ await this.compactStateStorage(instance);
979
+ }
875
980
  metrics.loadMs = Date.now() - startedAt;
876
981
  return;
877
982
  }
878
- load(instance, tmpObject, true);
983
+ const restoredObject = await this.restoreStorageSnapshot(instance, tmpObject, {
984
+ legacy: false
985
+ });
986
+ load(instance, restoredObject, true);
987
+ if (this.containsDeleteToken(restoredObject)) {
988
+ await this.compactStateStorage(instance);
989
+ }
879
990
  metrics.loadMs = Date.now() - startedAt;
880
991
  };
881
992
  instance.$memoryAll = {};
@@ -996,17 +1107,15 @@ var Server = class {
996
1107
  );
997
1108
  values.clear();
998
1109
  };
999
- const persistCb = async (values) => {
1000
- if (initPersist) {
1001
- values.clear();
1002
- return;
1003
- }
1110
+ const flushPersist = async (values) => {
1004
1111
  const startedAt = Date.now();
1005
1112
  const stateWrites = {};
1006
1113
  const deleteTasks = [];
1114
+ let hasDeletes = false;
1007
1115
  for (let [path, value] of values) {
1008
1116
  const _instance = path == "." ? instance : getByPath(instance, path);
1009
1117
  if (value == DELETE_TOKEN) {
1118
+ hasDeletes = true;
1010
1119
  deleteTasks.push(this.deleteStatePath(path));
1011
1120
  } else {
1012
1121
  const itemValue = _instance?.$snapshot ? createStatesSnapshot(_instance) : value;
@@ -1017,6 +1126,9 @@ var Server = class {
1017
1126
  this.putStorageEntries(stateWrites),
1018
1127
  ...deleteTasks
1019
1128
  ]);
1129
+ if (hasDeletes) {
1130
+ await this.compactStateStorage(instance);
1131
+ }
1020
1132
  const metrics = instance.$storageMetrics;
1021
1133
  if (metrics) {
1022
1134
  metrics.persistFlushes += 1;
@@ -1024,7 +1136,20 @@ var Server = class {
1024
1136
  metrics.persistDeletes += deleteTasks.length;
1025
1137
  metrics.persistLastFlushMs = Date.now() - startedAt;
1026
1138
  }
1139
+ };
1140
+ let persistQueue = Promise.resolve();
1141
+ const persistCb = async (values) => {
1142
+ if (initPersist) {
1143
+ values.clear();
1144
+ return;
1145
+ }
1146
+ const valuesSnapshot = new Map(values);
1027
1147
  values.clear();
1148
+ persistQueue = persistQueue.then(
1149
+ () => flushPersist(valuesSnapshot),
1150
+ () => flushPersist(valuesSnapshot)
1151
+ );
1152
+ await persistQueue;
1028
1153
  };
1029
1154
  const debouncePersist = (wait) => {
1030
1155
  let timeout = null;
@@ -1343,7 +1468,7 @@ var Server = class {
1343
1468
  if (transferData?.restored && signal2()[publicId]) {
1344
1469
  user = signal2()[publicId];
1345
1470
  } else {
1346
- user = isClass(classType) ? new classType() : classType(conn, ctx);
1471
+ user = this.createUserFromClassType(classType, conn, ctx);
1347
1472
  signal2()[publicId] = user;
1348
1473
  const snapshot = createStatesSnapshotDeep(user);
1349
1474
  await this.saveStatePath(`${usersPropName}.${publicId}`, snapshot);
@@ -1775,7 +1900,7 @@ var Server = class {
1775
1900
  const usersPropName = this.getUsersPropName(subRoom);
1776
1901
  if (signal2 && usersPropName) {
1777
1902
  const { classType } = signal2.options;
1778
- const user = isClass(classType) ? new classType() : classType();
1903
+ const user = this.createUserFromClassType(classType);
1779
1904
  const hydratedSnapshot = await awaitReturn(
1780
1905
  subRoom["onSessionRestore"]?.({
1781
1906
  userSnapshot,