@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 +21 -1
- package/dist/index.js +136 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +47 -0
- package/src/index.ts +2 -2
- package/src/server.ts +184 -12
- package/src/types/party.ts +26 -0
- package/tests/storage-restore.spec.ts +122 -0
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
|
-
|
|
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 (
|
|
970
|
+
if (legacyDeleteKeys.length > 0) {
|
|
872
971
|
await this.deleteStorageKeys(legacyDeleteKeys);
|
|
873
972
|
}
|
|
874
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
1903
|
+
const user = this.createUserFromClassType(classType);
|
|
1779
1904
|
const hydratedSnapshot = await awaitReturn(
|
|
1780
1905
|
subRoom["onSessionRestore"]?.({
|
|
1781
1906
|
userSnapshot,
|