@fluidframework/map 1.1.0 → 1.2.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/api-extractor.json +1 -1
- package/dist/directory.d.ts +14 -10
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +289 -80
- package/dist/directory.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +29 -14
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/directory.d.ts +14 -10
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +289 -80
- package/lib/directory.js.map +1 -1
- package/lib/index.d.ts +5 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -4
- package/lib/index.js.map +1 -1
- package/lib/interfaces.d.ts +29 -14
- package/lib/interfaces.d.ts.map +1 -1
- package/lib/interfaces.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +13 -13
- package/src/directory.ts +358 -94
- package/src/index.ts +5 -5
- package/src/interfaces.ts +29 -14
- package/src/packageVersion.ts +1 -1
package/src/directory.ts
CHANGED
|
@@ -72,7 +72,7 @@ interface IDirectoryMessageHandler {
|
|
|
72
72
|
/**
|
|
73
73
|
* Operation indicating a value should be set for a key.
|
|
74
74
|
*/
|
|
75
|
-
interface IDirectorySetOperation {
|
|
75
|
+
export interface IDirectorySetOperation {
|
|
76
76
|
/**
|
|
77
77
|
* String identifier of the operation type.
|
|
78
78
|
*/
|
|
@@ -97,7 +97,7 @@ interface IDirectorySetOperation {
|
|
|
97
97
|
/**
|
|
98
98
|
* Operation indicating a key should be deleted from the directory.
|
|
99
99
|
*/
|
|
100
|
-
interface IDirectoryDeleteOperation {
|
|
100
|
+
export interface IDirectoryDeleteOperation {
|
|
101
101
|
/**
|
|
102
102
|
* String identifier of the operation type.
|
|
103
103
|
*/
|
|
@@ -117,12 +117,12 @@ interface IDirectoryDeleteOperation {
|
|
|
117
117
|
/**
|
|
118
118
|
* An operation on a specific key within a directory
|
|
119
119
|
*/
|
|
120
|
-
type IDirectoryKeyOperation = IDirectorySetOperation | IDirectoryDeleteOperation;
|
|
120
|
+
export type IDirectoryKeyOperation = IDirectorySetOperation | IDirectoryDeleteOperation;
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Operation indicating the directory should be cleared.
|
|
124
124
|
*/
|
|
125
|
-
interface IDirectoryClearOperation {
|
|
125
|
+
export interface IDirectoryClearOperation {
|
|
126
126
|
/**
|
|
127
127
|
* String identifier of the operation type.
|
|
128
128
|
*/
|
|
@@ -137,12 +137,12 @@ interface IDirectoryClearOperation {
|
|
|
137
137
|
/**
|
|
138
138
|
* An operation on one or more of the keys within a directory
|
|
139
139
|
*/
|
|
140
|
-
type IDirectoryStorageOperation = IDirectoryKeyOperation | IDirectoryClearOperation;
|
|
140
|
+
export type IDirectoryStorageOperation = IDirectoryKeyOperation | IDirectoryClearOperation;
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
143
|
* Operation indicating a subdirectory should be created.
|
|
144
144
|
*/
|
|
145
|
-
interface IDirectoryCreateSubDirectoryOperation {
|
|
145
|
+
export interface IDirectoryCreateSubDirectoryOperation {
|
|
146
146
|
/**
|
|
147
147
|
* String identifier of the operation type.
|
|
148
148
|
*/
|
|
@@ -162,7 +162,7 @@ interface IDirectoryCreateSubDirectoryOperation {
|
|
|
162
162
|
/**
|
|
163
163
|
* Operation indicating a subdirectory should be deleted.
|
|
164
164
|
*/
|
|
165
|
-
interface IDirectoryDeleteSubDirectoryOperation {
|
|
165
|
+
export interface IDirectoryDeleteSubDirectoryOperation {
|
|
166
166
|
/**
|
|
167
167
|
* String identifier of the operation type.
|
|
168
168
|
*/
|
|
@@ -182,7 +182,8 @@ interface IDirectoryDeleteSubDirectoryOperation {
|
|
|
182
182
|
/**
|
|
183
183
|
* An operation on the subdirectories within a directory
|
|
184
184
|
*/
|
|
185
|
-
type IDirectorySubDirectoryOperation = IDirectoryCreateSubDirectoryOperation
|
|
185
|
+
export type IDirectorySubDirectoryOperation = IDirectoryCreateSubDirectoryOperation
|
|
186
|
+
| IDirectoryDeleteSubDirectoryOperation;
|
|
186
187
|
|
|
187
188
|
/**
|
|
188
189
|
* Any operation on a directory
|
|
@@ -268,7 +269,7 @@ export class DirectoryFactory {
|
|
|
268
269
|
* SubDirectories can be retrieved for use as working directories.
|
|
269
270
|
*
|
|
270
271
|
* @example
|
|
271
|
-
* ```
|
|
272
|
+
* ```typescript
|
|
272
273
|
* mySharedDirectory.createSubDirectory("a").createSubDirectory("b").createSubDirectory("c").set("foo", val1);
|
|
273
274
|
* const mySubDir = mySharedDirectory.getWorkingDirectory("/a/b/c");
|
|
274
275
|
* mySubDir.get("foo"); // returns val1
|
|
@@ -629,6 +630,18 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
629
630
|
}
|
|
630
631
|
}
|
|
631
632
|
|
|
633
|
+
/**
|
|
634
|
+
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
|
|
635
|
+
* @internal
|
|
636
|
+
*/
|
|
637
|
+
protected rollback(content: any, localOpMetadata: unknown) {
|
|
638
|
+
const op: IDirectoryOperation = content as IDirectoryOperation;
|
|
639
|
+
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
640
|
+
if (subdir) {
|
|
641
|
+
subdir.rollback(op, localOpMetadata);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
632
645
|
/**
|
|
633
646
|
* Converts the given relative path to absolute against the root.
|
|
634
647
|
* @param relativePath - The path to convert
|
|
@@ -675,8 +688,7 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
675
688
|
submit: (op: IDirectoryClearOperation, localOpMetadata: unknown) => {
|
|
676
689
|
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
677
690
|
if (subdir) {
|
|
678
|
-
|
|
679
|
-
subdir.submitClearMessage(op);
|
|
691
|
+
subdir.resubmitClearMessage(op, localOpMetadata);
|
|
680
692
|
}
|
|
681
693
|
},
|
|
682
694
|
},
|
|
@@ -693,8 +705,7 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
693
705
|
submit: (op: IDirectoryDeleteOperation, localOpMetadata: unknown) => {
|
|
694
706
|
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
695
707
|
if (subdir) {
|
|
696
|
-
|
|
697
|
-
subdir.submitKeyMessage(op);
|
|
708
|
+
subdir.resubmitKeyMessage(op, localOpMetadata);
|
|
698
709
|
}
|
|
699
710
|
},
|
|
700
711
|
},
|
|
@@ -712,8 +723,7 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
712
723
|
submit: (op: IDirectorySetOperation, localOpMetadata: unknown) => {
|
|
713
724
|
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
714
725
|
if (subdir) {
|
|
715
|
-
|
|
716
|
-
subdir.submitKeyMessage(op);
|
|
726
|
+
subdir.resubmitKeyMessage(op, localOpMetadata);
|
|
717
727
|
}
|
|
718
728
|
},
|
|
719
729
|
},
|
|
@@ -732,7 +742,7 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
732
742
|
const parentSubdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
733
743
|
if (parentSubdir) {
|
|
734
744
|
// We don't reuse the metadata but send a new one on each submit.
|
|
735
|
-
parentSubdir.
|
|
745
|
+
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
736
746
|
}
|
|
737
747
|
},
|
|
738
748
|
},
|
|
@@ -751,7 +761,7 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
751
761
|
const parentSubdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
752
762
|
if (parentSubdir) {
|
|
753
763
|
// We don't reuse the metadata but send a new one on each submit.
|
|
754
|
-
parentSubdir.
|
|
764
|
+
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
755
765
|
}
|
|
756
766
|
},
|
|
757
767
|
},
|
|
@@ -832,15 +842,64 @@ export class SharedDirectory extends SharedObject<ISharedDirectoryEvents> implem
|
|
|
832
842
|
}
|
|
833
843
|
}
|
|
834
844
|
|
|
845
|
+
interface IKeyEditLocalOpMetadata {
|
|
846
|
+
type: "edit";
|
|
847
|
+
pendingMessageId: number;
|
|
848
|
+
previousValue: ILocalValue | undefined;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
interface IClearLocalOpMetadata {
|
|
852
|
+
type: "clear";
|
|
853
|
+
pendingMessageId: number;
|
|
854
|
+
previousStorage: Map<string, ILocalValue>;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
interface ICreateSubDirLocalOpMetadata {
|
|
858
|
+
type: "createSubDir";
|
|
859
|
+
pendingMessageId: number;
|
|
860
|
+
previouslyExisted: boolean;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
interface IDeleteSubDirLocalOpMetadata {
|
|
864
|
+
type: "deleteSubDir";
|
|
865
|
+
pendingMessageId: number;
|
|
866
|
+
subDirectory: SubDirectory | undefined;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
type SubDirLocalOpMetadata = ICreateSubDirLocalOpMetadata | IDeleteSubDirLocalOpMetadata;
|
|
870
|
+
type DirectoryLocalOpMetadata = IClearLocalOpMetadata | IKeyEditLocalOpMetadata | SubDirLocalOpMetadata;
|
|
871
|
+
|
|
872
|
+
function isKeyEditLocalOpMetadata(metadata: any): metadata is IKeyEditLocalOpMetadata {
|
|
873
|
+
return metadata !== undefined && typeof metadata.pendingMessageId === "number" && metadata.type === "edit";
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function isClearLocalOpMetadata(metadata: any): metadata is IClearLocalOpMetadata {
|
|
877
|
+
return metadata !== undefined && metadata.type === "clear" && typeof metadata.pendingMessageId === "number" &&
|
|
878
|
+
typeof metadata.previousStorage === "object";
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function isSubDirLocalOpMetadata(metadata: any): metadata is SubDirLocalOpMetadata {
|
|
882
|
+
return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
|
|
883
|
+
((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
|
|
884
|
+
metadata.type === "deleteSubDir");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function isDirectoryLocalOpMetadata(metadata: any): metadata is DirectoryLocalOpMetadata {
|
|
888
|
+
return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
|
|
889
|
+
(metadata.type === "edit" || metadata.type === "deleteSubDir" ||
|
|
890
|
+
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
891
|
+
(metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean"));
|
|
892
|
+
}
|
|
893
|
+
|
|
835
894
|
/**
|
|
836
895
|
* Node of the directory tree.
|
|
837
896
|
* @sealed
|
|
838
897
|
*/
|
|
839
898
|
class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirectory {
|
|
840
899
|
/**
|
|
841
|
-
* Tells if the sub directory is
|
|
900
|
+
* Tells if the sub directory is deleted or not.
|
|
842
901
|
*/
|
|
843
|
-
private
|
|
902
|
+
private _deleted = false;
|
|
844
903
|
|
|
845
904
|
/**
|
|
846
905
|
* String representation for the class.
|
|
@@ -860,12 +919,12 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
860
919
|
/**
|
|
861
920
|
* Keys that have been modified locally but not yet ack'd from the server.
|
|
862
921
|
*/
|
|
863
|
-
private readonly pendingKeys: Map<string, number> = new Map();
|
|
922
|
+
private readonly pendingKeys: Map<string, number[]> = new Map();
|
|
864
923
|
|
|
865
924
|
/**
|
|
866
925
|
* Subdirectories that have been modified locally but not yet ack'd from the server.
|
|
867
926
|
*/
|
|
868
|
-
private readonly pendingSubDirectories: Map<string, number> = new Map();
|
|
927
|
+
private readonly pendingSubDirectories: Map<string, number[]> = new Map();
|
|
869
928
|
|
|
870
929
|
/**
|
|
871
930
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
@@ -873,10 +932,9 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
873
932
|
private pendingMessageId: number = -1;
|
|
874
933
|
|
|
875
934
|
/**
|
|
876
|
-
*
|
|
877
|
-
* of that clear operation. Otherwise, is -1.
|
|
935
|
+
* The pending ids of any clears that have been performed locally but not yet ack'd from the server
|
|
878
936
|
*/
|
|
879
|
-
private
|
|
937
|
+
private readonly pendingClearMessageIds: number[] = [];
|
|
880
938
|
|
|
881
939
|
/**
|
|
882
940
|
* Constructor.
|
|
@@ -895,16 +953,23 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
895
953
|
}
|
|
896
954
|
|
|
897
955
|
public dispose(error?: Error): void {
|
|
898
|
-
this.
|
|
956
|
+
this._deleted = true;
|
|
899
957
|
this.emit("disposed", this);
|
|
900
958
|
}
|
|
901
959
|
|
|
960
|
+
/**
|
|
961
|
+
* Unmark the deleted property when rolling back delete.
|
|
962
|
+
*/
|
|
963
|
+
private undispose(): void {
|
|
964
|
+
this._deleted = false;
|
|
965
|
+
}
|
|
966
|
+
|
|
902
967
|
public get disposed(): boolean {
|
|
903
|
-
return this.
|
|
968
|
+
return this._deleted;
|
|
904
969
|
}
|
|
905
970
|
|
|
906
971
|
private throwIfDisposed() {
|
|
907
|
-
if (this.
|
|
972
|
+
if (this._deleted) {
|
|
908
973
|
throw new UsageError("Cannot access Disposed subDirectory");
|
|
909
974
|
}
|
|
910
975
|
}
|
|
@@ -945,7 +1010,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
945
1010
|
this.directory.handle);
|
|
946
1011
|
|
|
947
1012
|
// Set the value locally.
|
|
948
|
-
this.setCore(
|
|
1013
|
+
const previousValue = this.setCore(
|
|
949
1014
|
key,
|
|
950
1015
|
localValue,
|
|
951
1016
|
true,
|
|
@@ -962,7 +1027,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
962
1027
|
type: "set",
|
|
963
1028
|
value: serializableValue,
|
|
964
1029
|
};
|
|
965
|
-
this.submitKeyMessage(op);
|
|
1030
|
+
this.submitKeyMessage(op, previousValue);
|
|
966
1031
|
return this;
|
|
967
1032
|
}
|
|
968
1033
|
|
|
@@ -988,7 +1053,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
988
1053
|
}
|
|
989
1054
|
|
|
990
1055
|
// Create the sub directory locally first.
|
|
991
|
-
this.createSubDirectoryCore(subdirName, true);
|
|
1056
|
+
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
992
1057
|
|
|
993
1058
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
994
1059
|
const subDir: IDirectory = this._subdirectories.get(subdirName)!;
|
|
@@ -1003,7 +1068,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1003
1068
|
subdirName,
|
|
1004
1069
|
type: "createSubDirectory",
|
|
1005
1070
|
};
|
|
1006
|
-
this.
|
|
1071
|
+
this.submitCreateSubDirectoryMessage(op, !isNew);
|
|
1007
1072
|
|
|
1008
1073
|
return subDir;
|
|
1009
1074
|
}
|
|
@@ -1030,11 +1095,11 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1030
1095
|
public deleteSubDirectory(subdirName: string): boolean {
|
|
1031
1096
|
this.throwIfDisposed();
|
|
1032
1097
|
// Delete the sub directory locally first.
|
|
1033
|
-
const
|
|
1098
|
+
const subDir = this.deleteSubDirectoryCore(subdirName, true);
|
|
1034
1099
|
|
|
1035
1100
|
// If we are not attached, don't submit the op.
|
|
1036
1101
|
if (!this.directory.isAttached()) {
|
|
1037
|
-
return
|
|
1102
|
+
return subDir !== undefined;
|
|
1038
1103
|
}
|
|
1039
1104
|
|
|
1040
1105
|
const op: IDirectoryDeleteSubDirectoryOperation = {
|
|
@@ -1043,8 +1108,8 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1043
1108
|
type: "deleteSubDirectory",
|
|
1044
1109
|
};
|
|
1045
1110
|
|
|
1046
|
-
this.
|
|
1047
|
-
return
|
|
1111
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
1112
|
+
return subDir !== undefined;
|
|
1048
1113
|
}
|
|
1049
1114
|
|
|
1050
1115
|
/**
|
|
@@ -1071,11 +1136,11 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1071
1136
|
public delete(key: string): boolean {
|
|
1072
1137
|
this.throwIfDisposed();
|
|
1073
1138
|
// Delete the key locally first.
|
|
1074
|
-
const
|
|
1139
|
+
const previousValue = this.deleteCore(key, true);
|
|
1075
1140
|
|
|
1076
1141
|
// If we are not attached, don't submit the op.
|
|
1077
1142
|
if (!this.directory.isAttached()) {
|
|
1078
|
-
return
|
|
1143
|
+
return previousValue !== undefined;
|
|
1079
1144
|
}
|
|
1080
1145
|
|
|
1081
1146
|
const op: IDirectoryDeleteOperation = {
|
|
@@ -1084,8 +1149,8 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1084
1149
|
type: "delete",
|
|
1085
1150
|
};
|
|
1086
1151
|
|
|
1087
|
-
this.submitKeyMessage(op);
|
|
1088
|
-
return
|
|
1152
|
+
this.submitKeyMessage(op, previousValue);
|
|
1153
|
+
return previousValue !== undefined;
|
|
1089
1154
|
}
|
|
1090
1155
|
|
|
1091
1156
|
/**
|
|
@@ -1093,19 +1158,20 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1093
1158
|
*/
|
|
1094
1159
|
public clear(): void {
|
|
1095
1160
|
this.throwIfDisposed();
|
|
1096
|
-
// Clear the data locally first.
|
|
1097
|
-
this.clearCore(true);
|
|
1098
1161
|
|
|
1099
1162
|
// If we are not attached, don't submit the op.
|
|
1100
1163
|
if (!this.directory.isAttached()) {
|
|
1164
|
+
this.clearCore(true);
|
|
1101
1165
|
return;
|
|
1102
1166
|
}
|
|
1103
1167
|
|
|
1168
|
+
const copy = new Map<string, ILocalValue>(this._storage);
|
|
1169
|
+
this.clearCore(true);
|
|
1104
1170
|
const op: IDirectoryClearOperation = {
|
|
1105
1171
|
path: this.absolutePath,
|
|
1106
1172
|
type: "clear",
|
|
1107
1173
|
};
|
|
1108
|
-
this.submitClearMessage(op);
|
|
1174
|
+
this.submitClearMessage(op, copy);
|
|
1109
1175
|
}
|
|
1110
1176
|
|
|
1111
1177
|
/**
|
|
@@ -1209,16 +1275,14 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1209
1275
|
): void {
|
|
1210
1276
|
this.throwIfDisposed();
|
|
1211
1277
|
if (local) {
|
|
1212
|
-
assert(localOpMetadata
|
|
1213
|
-
0x00f /* pendingMessageId is missing from the local client's operation */);
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1278
|
+
assert(isClearLocalOpMetadata(localOpMetadata),
|
|
1279
|
+
0x00f /* `pendingMessageId is missing from the local client's ${op.type} operation` */);
|
|
1280
|
+
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
1281
|
+
assert(pendingClearMessageId === localOpMetadata.pendingMessageId,
|
|
1282
|
+
0x32a /* pendingMessageId does not match */);
|
|
1218
1283
|
return;
|
|
1219
1284
|
}
|
|
1220
1285
|
this.clearExceptPendingKeys();
|
|
1221
|
-
this.directory.emit("clear", local, this.directory);
|
|
1222
1286
|
}
|
|
1223
1287
|
|
|
1224
1288
|
/**
|
|
@@ -1284,7 +1348,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1284
1348
|
localOpMetadata: unknown,
|
|
1285
1349
|
): void {
|
|
1286
1350
|
this.throwIfDisposed();
|
|
1287
|
-
if (!this.
|
|
1351
|
+
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1288
1352
|
return;
|
|
1289
1353
|
}
|
|
1290
1354
|
this.createSubDirectoryCore(op.subdirName, local);
|
|
@@ -1305,7 +1369,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1305
1369
|
localOpMetadata: unknown,
|
|
1306
1370
|
): void {
|
|
1307
1371
|
this.throwIfDisposed();
|
|
1308
|
-
if (!this.
|
|
1372
|
+
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1309
1373
|
return;
|
|
1310
1374
|
}
|
|
1311
1375
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1314,37 +1378,156 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1314
1378
|
/**
|
|
1315
1379
|
* Submit a clear operation.
|
|
1316
1380
|
* @param op - The operation
|
|
1317
|
-
* @internal
|
|
1318
1381
|
*/
|
|
1319
|
-
|
|
1382
|
+
private submitClearMessage(op: IDirectoryClearOperation,
|
|
1383
|
+
previousValue: Map<string, ILocalValue>): void {
|
|
1320
1384
|
this.throwIfDisposed();
|
|
1385
|
+
const pendingMsgId = ++this.pendingMessageId;
|
|
1386
|
+
this.pendingClearMessageIds.push(pendingMsgId);
|
|
1387
|
+
const metadata: IClearLocalOpMetadata = {
|
|
1388
|
+
type: "clear",
|
|
1389
|
+
pendingMessageId: pendingMsgId,
|
|
1390
|
+
previousStorage: previousValue,
|
|
1391
|
+
};
|
|
1392
|
+
this.directory.submitDirectoryMessage(op, metadata);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Resubmit a clear operation.
|
|
1397
|
+
* @param op - The operation
|
|
1398
|
+
* @internal
|
|
1399
|
+
*/
|
|
1400
|
+
public resubmitClearMessage(op: IDirectoryClearOperation, localOpMetadata: unknown): void {
|
|
1401
|
+
assert(isClearLocalOpMetadata(localOpMetadata), 0x32b /* Invalid localOpMetadata for clear */);
|
|
1402
|
+
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1403
|
+
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
1404
|
+
assert(pendingClearMessageId === localOpMetadata.pendingMessageId,
|
|
1405
|
+
0x32c /* pendingMessageId does not match */);
|
|
1406
|
+
this.submitClearMessage(op, localOpMetadata.previousStorage);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Get a new pending message id for the op and cache it to track the pending op
|
|
1411
|
+
*/
|
|
1412
|
+
private getKeyMessageId(op: IDirectoryKeyOperation): number {
|
|
1413
|
+
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1321
1414
|
const pendingMessageId = ++this.pendingMessageId;
|
|
1322
|
-
this.
|
|
1323
|
-
|
|
1415
|
+
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1416
|
+
if (pendingMessageIds !== undefined) {
|
|
1417
|
+
pendingMessageIds.push(pendingMessageId);
|
|
1418
|
+
} else {
|
|
1419
|
+
this.pendingKeys.set(op.key, [pendingMessageId]);
|
|
1420
|
+
}
|
|
1421
|
+
return pendingMessageId;
|
|
1324
1422
|
}
|
|
1325
1423
|
|
|
1326
1424
|
/**
|
|
1327
1425
|
* Submit a key operation.
|
|
1328
1426
|
* @param op - The operation
|
|
1427
|
+
* @param previousValue - The value of the key before this op
|
|
1428
|
+
*/
|
|
1429
|
+
private submitKeyMessage(op: IDirectoryKeyOperation, previousValue?: ILocalValue): void {
|
|
1430
|
+
this.throwIfDisposed();
|
|
1431
|
+
const pendingMessageId = this.getKeyMessageId(op);
|
|
1432
|
+
const localMetadata = { type: "edit", pendingMessageId, previousValue };
|
|
1433
|
+
this.directory.submitDirectoryMessage(op, localMetadata);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* Submit a key message to remote clients based on a previous submit.
|
|
1438
|
+
* @param op - The map key message
|
|
1439
|
+
* @param localOpMetadata - Metadata from the previous submit
|
|
1329
1440
|
* @internal
|
|
1330
1441
|
*/
|
|
1331
|
-
public
|
|
1442
|
+
public resubmitKeyMessage(op: IDirectoryKeyOperation, localOpMetadata: unknown): void {
|
|
1443
|
+
assert(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
|
|
1444
|
+
|
|
1445
|
+
// clear the old pending message id
|
|
1446
|
+
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1447
|
+
assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId,
|
|
1448
|
+
0x32e /* Unexpected pending message received */);
|
|
1449
|
+
pendingMessageIds.shift();
|
|
1450
|
+
if (pendingMessageIds.length === 0) {
|
|
1451
|
+
this.pendingKeys.delete(op.key);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
this.submitKeyMessage(op, localOpMetadata.previousValue);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* Get a new pending message id for the op and cache it to track the pending op
|
|
1459
|
+
*/
|
|
1460
|
+
private getSubDirMessageId(op: IDirectorySubDirectoryOperation): number {
|
|
1461
|
+
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1462
|
+
const newMessageId = ++this.pendingMessageId;
|
|
1463
|
+
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1464
|
+
if (pendingMessageIds !== undefined) {
|
|
1465
|
+
pendingMessageIds.push(newMessageId);
|
|
1466
|
+
} else {
|
|
1467
|
+
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1468
|
+
}
|
|
1469
|
+
return newMessageId;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Submit a create subdirectory operation.
|
|
1474
|
+
* @param op - The operation
|
|
1475
|
+
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1476
|
+
*/
|
|
1477
|
+
private submitCreateSubDirectoryMessage(op: IDirectorySubDirectoryOperation,
|
|
1478
|
+
prevExisted: boolean): void {
|
|
1332
1479
|
this.throwIfDisposed();
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
|
|
1480
|
+
const newMessageId = this.getSubDirMessageId(op);
|
|
1481
|
+
|
|
1482
|
+
const localOpMetadata: ICreateSubDirLocalOpMetadata = {
|
|
1483
|
+
type: "createSubDir",
|
|
1484
|
+
pendingMessageId: newMessageId,
|
|
1485
|
+
previouslyExisted: prevExisted,
|
|
1486
|
+
};
|
|
1487
|
+
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1336
1488
|
}
|
|
1337
1489
|
|
|
1338
1490
|
/**
|
|
1339
|
-
* Submit a subdirectory operation.
|
|
1491
|
+
* Submit a delete subdirectory operation.
|
|
1340
1492
|
* @param op - The operation
|
|
1341
|
-
* @
|
|
1493
|
+
* @param subDir - Any subdirectory deleted by the op
|
|
1342
1494
|
*/
|
|
1343
|
-
|
|
1495
|
+
private submitDeleteSubDirectoryMessage(op: IDirectorySubDirectoryOperation,
|
|
1496
|
+
subDir: SubDirectory | undefined): void {
|
|
1344
1497
|
this.throwIfDisposed();
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1498
|
+
const newMessageId = this.getSubDirMessageId(op);
|
|
1499
|
+
|
|
1500
|
+
const localOpMetadata: IDeleteSubDirLocalOpMetadata = {
|
|
1501
|
+
type: "deleteSubDir",
|
|
1502
|
+
pendingMessageId: newMessageId,
|
|
1503
|
+
subDirectory: subDir,
|
|
1504
|
+
};
|
|
1505
|
+
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* Submit a subdirectory operation again
|
|
1510
|
+
* @param op - The operation
|
|
1511
|
+
* @param localOpMetadata - metadata submitted with the op originally
|
|
1512
|
+
* @internal
|
|
1513
|
+
*/
|
|
1514
|
+
public resubmitSubDirectoryMessage(op: IDirectorySubDirectoryOperation, localOpMetadata: unknown): void {
|
|
1515
|
+
assert(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
|
|
1516
|
+
|
|
1517
|
+
// clear the old pending message id
|
|
1518
|
+
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1519
|
+
assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId,
|
|
1520
|
+
0x330 /* Unexpected pending message received */);
|
|
1521
|
+
pendingMessageIds.shift();
|
|
1522
|
+
if (pendingMessageIds.length === 0) {
|
|
1523
|
+
this.pendingSubDirectories.delete(op.subdirName);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (localOpMetadata.type === "createSubDir") {
|
|
1527
|
+
this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
|
|
1528
|
+
} else {
|
|
1529
|
+
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
1530
|
+
}
|
|
1348
1531
|
}
|
|
1349
1532
|
|
|
1350
1533
|
/**
|
|
@@ -1396,6 +1579,69 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1396
1579
|
return this._storage.get(key) as T;
|
|
1397
1580
|
}
|
|
1398
1581
|
|
|
1582
|
+
/**
|
|
1583
|
+
* Remove the pendingMessageId from the map tracking it on rollback
|
|
1584
|
+
* @param map - map tracking the pending messages
|
|
1585
|
+
* @param key - key of the edit in the op
|
|
1586
|
+
*/
|
|
1587
|
+
private rollbackPendingMessageId(map: Map<string, number[]>, key: string, pendingMessageId) {
|
|
1588
|
+
const pendingMessageIds = map.get(key);
|
|
1589
|
+
const lastPendingMessageId = pendingMessageIds?.pop();
|
|
1590
|
+
if (!pendingMessageIds || lastPendingMessageId !== pendingMessageId) {
|
|
1591
|
+
throw new Error("Rollback op does not match last pending");
|
|
1592
|
+
}
|
|
1593
|
+
if (pendingMessageIds.length === 0) {
|
|
1594
|
+
map.delete(key);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* Rollback a local op
|
|
1600
|
+
* @param op - The operation to rollback
|
|
1601
|
+
* @param localOpMetadata - The local metadata associated with the op.
|
|
1602
|
+
*/
|
|
1603
|
+
public rollback(op: any, localOpMetadata: unknown) {
|
|
1604
|
+
if (!isDirectoryLocalOpMetadata(localOpMetadata)) {
|
|
1605
|
+
throw new Error("Invalid localOpMetadata");
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
if (op.type === "clear" && localOpMetadata.type === "clear") {
|
|
1609
|
+
localOpMetadata.previousStorage.forEach((localValue, key) => {
|
|
1610
|
+
this.setCore(key, localValue, true);
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
const lastPendingClearId = this.pendingClearMessageIds.pop();
|
|
1614
|
+
if (lastPendingClearId === undefined || lastPendingClearId !== localOpMetadata.pendingMessageId) {
|
|
1615
|
+
throw new Error("Rollback op does match last clear");
|
|
1616
|
+
}
|
|
1617
|
+
} else if ((op.type === "delete" || op.type === "set") && localOpMetadata.type === "edit") {
|
|
1618
|
+
if (localOpMetadata.previousValue === undefined) {
|
|
1619
|
+
this.deleteCore(op.key, true);
|
|
1620
|
+
} else {
|
|
1621
|
+
this.setCore(op.key, localOpMetadata.previousValue, true);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1625
|
+
} else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1626
|
+
if (!localOpMetadata.previouslyExisted) {
|
|
1627
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1631
|
+
} else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
1632
|
+
if (localOpMetadata.subDirectory !== undefined) {
|
|
1633
|
+
this.undeleteSubDirectoryTree(localOpMetadata.subDirectory);
|
|
1634
|
+
// don't need to register events because deleting never unregistered
|
|
1635
|
+
this._subdirectories.set(op.subdirName, localOpMetadata.subDirectory);
|
|
1636
|
+
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1640
|
+
} else {
|
|
1641
|
+
throw new Error("Unsupported op for rollback");
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1399
1645
|
/**
|
|
1400
1646
|
* Converts the given relative path into an absolute path.
|
|
1401
1647
|
* @param path - Relative path to convert
|
|
@@ -1409,10 +1655,9 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1409
1655
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1410
1656
|
* not process the incoming operation.
|
|
1411
1657
|
* @param op - Operation to check
|
|
1412
|
-
* @param local - Whether the
|
|
1413
|
-
* @param
|
|
1414
|
-
*
|
|
1415
|
-
* For messages from a remote client, this will be undefined.
|
|
1658
|
+
* @param local - Whether the operation originated from the local client
|
|
1659
|
+
* @param localOpMetadata - For local client ops, this is the metadata that was submitted with the op.
|
|
1660
|
+
* For ops from a remote client, this will be undefined.
|
|
1416
1661
|
* @returns True if the operation should be processed, false otherwise
|
|
1417
1662
|
*/
|
|
1418
1663
|
private needProcessStorageOperation(
|
|
@@ -1420,9 +1665,10 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1420
1665
|
local: boolean,
|
|
1421
1666
|
localOpMetadata: unknown,
|
|
1422
1667
|
): boolean {
|
|
1423
|
-
if (this.
|
|
1668
|
+
if (this.pendingClearMessageIds.length > 0) {
|
|
1424
1669
|
if (local) {
|
|
1425
|
-
assert(localOpMetadata !== undefined && localOpMetadata
|
|
1670
|
+
assert(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1671
|
+
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0],
|
|
1426
1672
|
0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1427
1673
|
}
|
|
1428
1674
|
// If I have a NACK clear, we can ignore all ops.
|
|
@@ -1434,10 +1680,13 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1434
1680
|
// Found an NACK op, clear it from the directory if the latest sequence number in the directory
|
|
1435
1681
|
// match the message's and don't process the op.
|
|
1436
1682
|
if (local) {
|
|
1437
|
-
assert(localOpMetadata !== undefined,
|
|
1683
|
+
assert(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata),
|
|
1438
1684
|
0x011 /* pendingMessageId is missing from the local client's operation */);
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1685
|
+
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1686
|
+
assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId,
|
|
1687
|
+
0x331 /* Unexpected pending message received */);
|
|
1688
|
+
pendingMessageIds.shift();
|
|
1689
|
+
if (pendingMessageIds.length === 0) {
|
|
1441
1690
|
this.pendingKeys.delete(op.key);
|
|
1442
1691
|
}
|
|
1443
1692
|
}
|
|
@@ -1458,7 +1707,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1458
1707
|
* For messages from a remote client, this will be undefined.
|
|
1459
1708
|
* @returns True if the operation should be processed, false otherwise
|
|
1460
1709
|
*/
|
|
1461
|
-
private
|
|
1710
|
+
private needProcessSubDirectoryOperation(
|
|
1462
1711
|
op: IDirectorySubDirectoryOperation,
|
|
1463
1712
|
local: boolean,
|
|
1464
1713
|
localOpMetadata: unknown,
|
|
@@ -1466,10 +1715,13 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1466
1715
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1467
1716
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1468
1717
|
if (local) {
|
|
1469
|
-
assert(localOpMetadata
|
|
1718
|
+
assert(isSubDirLocalOpMetadata(localOpMetadata),
|
|
1470
1719
|
0x012 /* pendingMessageId is missing from the local client's operation */);
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1720
|
+
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1721
|
+
assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId,
|
|
1722
|
+
0x332 /* Unexpected pending message received */);
|
|
1723
|
+
pendingMessageIds.shift();
|
|
1724
|
+
if (pendingMessageIds.length === 0) {
|
|
1473
1725
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1474
1726
|
}
|
|
1475
1727
|
}
|
|
@@ -1490,9 +1742,9 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1490
1742
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1491
1743
|
temp.set(key, this._storage.get(key)!);
|
|
1492
1744
|
});
|
|
1493
|
-
this.
|
|
1745
|
+
this.clearCore(false);
|
|
1494
1746
|
temp.forEach((value, key, map) => {
|
|
1495
|
-
this.
|
|
1747
|
+
this.setCore(key, value, true);
|
|
1496
1748
|
});
|
|
1497
1749
|
}
|
|
1498
1750
|
|
|
@@ -1510,11 +1762,11 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1510
1762
|
* Delete implementation used for both locally sourced deletes as well as incoming remote deletes.
|
|
1511
1763
|
* @param key - The key being deleted
|
|
1512
1764
|
* @param local - Whether the message originated from the local client
|
|
1513
|
-
* @
|
|
1514
|
-
* @returns True if the key existed and was deleted, false if it did not exist
|
|
1765
|
+
* @returns Previous local value of the key if it existed, undefined if it did not exist
|
|
1515
1766
|
*/
|
|
1516
|
-
private deleteCore(key: string, local: boolean) {
|
|
1517
|
-
const
|
|
1767
|
+
private deleteCore(key: string, local: boolean): ILocalValue | undefined {
|
|
1768
|
+
const previousLocalValue = this._storage.get(key);
|
|
1769
|
+
const previousValue = previousLocalValue?.value;
|
|
1518
1770
|
const successfullyRemoved = this._storage.delete(key);
|
|
1519
1771
|
if (successfullyRemoved) {
|
|
1520
1772
|
const event: IDirectoryValueChanged = { key, path: this.absolutePath, previousValue };
|
|
@@ -1522,7 +1774,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1522
1774
|
const containedEvent: IValueChanged = { key, previousValue };
|
|
1523
1775
|
this.emit("containedValueChanged", containedEvent, local, this);
|
|
1524
1776
|
}
|
|
1525
|
-
return
|
|
1777
|
+
return previousLocalValue;
|
|
1526
1778
|
}
|
|
1527
1779
|
|
|
1528
1780
|
/**
|
|
@@ -1530,30 +1782,35 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1530
1782
|
* @param key - The key being set
|
|
1531
1783
|
* @param value - The value being set
|
|
1532
1784
|
* @param local - Whether the message originated from the local client
|
|
1533
|
-
* @
|
|
1785
|
+
* @returns Previous local value of the key, if any
|
|
1534
1786
|
*/
|
|
1535
|
-
private setCore(key: string, value: ILocalValue, local: boolean) {
|
|
1536
|
-
const
|
|
1787
|
+
private setCore(key: string, value: ILocalValue, local: boolean): ILocalValue | undefined {
|
|
1788
|
+
const previousLocalValue = this._storage.get(key);
|
|
1789
|
+
const previousValue = previousLocalValue?.value;
|
|
1537
1790
|
this._storage.set(key, value);
|
|
1538
1791
|
const event: IDirectoryValueChanged = { key, path: this.absolutePath, previousValue };
|
|
1539
1792
|
this.directory.emit("valueChanged", event, local, this.directory);
|
|
1540
1793
|
const containedEvent: IValueChanged = { key, previousValue };
|
|
1541
1794
|
this.emit("containedValueChanged", containedEvent, local, this);
|
|
1795
|
+
return previousLocalValue;
|
|
1542
1796
|
}
|
|
1543
1797
|
|
|
1544
1798
|
/**
|
|
1545
1799
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1546
1800
|
* @param subdirName - The name of the subdirectory being created
|
|
1547
1801
|
* @param local - Whether the message originated from the local client
|
|
1802
|
+
* @returns - True if is newly created, false if it already existed.
|
|
1548
1803
|
*/
|
|
1549
|
-
private createSubDirectoryCore(subdirName: string, local: boolean) {
|
|
1804
|
+
private createSubDirectoryCore(subdirName: string, local: boolean): boolean {
|
|
1550
1805
|
if (!this._subdirectories.has(subdirName)) {
|
|
1551
1806
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1552
1807
|
const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
|
|
1553
1808
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1554
1809
|
this._subdirectories.set(subdirName, subDir);
|
|
1555
1810
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1811
|
+
return true;
|
|
1556
1812
|
}
|
|
1813
|
+
return false;
|
|
1557
1814
|
}
|
|
1558
1815
|
|
|
1559
1816
|
private registerEventsOnSubDirectory(subDirectory: SubDirectory, subDirName: string) {
|
|
@@ -1569,18 +1826,17 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1569
1826
|
* Delete subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1570
1827
|
* @param subdirName - The name of the subdirectory being deleted
|
|
1571
1828
|
* @param local - Whether the message originated from the local client
|
|
1572
|
-
* @param op - The message if from a remote delete, or null if from a local delete
|
|
1573
1829
|
*/
|
|
1574
1830
|
private deleteSubDirectoryCore(subdirName: string, local: boolean) {
|
|
1575
|
-
const previousValue = this.
|
|
1831
|
+
const previousValue = this._subdirectories.get(subdirName);
|
|
1576
1832
|
// This should make the subdirectory structure unreachable so it can be GC'd and won't appear in snapshots
|
|
1577
|
-
// Might want to consider cleaning out the structure more exhaustively though?
|
|
1578
|
-
const successfullyRemoved = this._subdirectories.delete(subdirName);
|
|
1833
|
+
// Might want to consider cleaning out the structure more exhaustively though? But not when rollback.
|
|
1579
1834
|
if (previousValue !== undefined) {
|
|
1835
|
+
this._subdirectories.delete(subdirName);
|
|
1580
1836
|
this.disposeSubDirectoryTree(previousValue);
|
|
1581
1837
|
this.emit("subDirectoryDeleted", subdirName, local, this);
|
|
1582
1838
|
}
|
|
1583
|
-
return
|
|
1839
|
+
return previousValue;
|
|
1584
1840
|
}
|
|
1585
1841
|
|
|
1586
1842
|
private disposeSubDirectoryTree(directory: IDirectory | undefined) {
|
|
@@ -1596,4 +1852,12 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1596
1852
|
directory.dispose();
|
|
1597
1853
|
}
|
|
1598
1854
|
}
|
|
1855
|
+
|
|
1856
|
+
private undeleteSubDirectoryTree(directory: SubDirectory) {
|
|
1857
|
+
// Restore deleted subdirectory tree. This will unmark "deleted" from the subdirectories from bottom to top.
|
|
1858
|
+
for (const [_, subDirectory] of this._subdirectories.entries()) {
|
|
1859
|
+
this.undeleteSubDirectoryTree(subDirectory);
|
|
1860
|
+
}
|
|
1861
|
+
directory.undispose();
|
|
1862
|
+
}
|
|
1599
1863
|
}
|