@fluidframework/map 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.2.0.153917
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/CHANGELOG.md +5 -0
- package/README.md +20 -0
- package/dist/directory.d.ts +27 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +227 -62
- package/dist/directory.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.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 +27 -0
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +227 -62
- package/lib/directory.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.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 +58 -60
- package/src/directory.ts +315 -60
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +1 -1
package/src/directory.ts
CHANGED
|
@@ -43,12 +43,18 @@ const snapshotFileName = "header";
|
|
|
43
43
|
interface IDirectoryMessageHandler {
|
|
44
44
|
/**
|
|
45
45
|
* Apply the given operation.
|
|
46
|
+
* @param msg - The message from the server to apply.
|
|
46
47
|
* @param op - The directory operation to apply
|
|
47
48
|
* @param local - Whether the message originated from the local client
|
|
48
49
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
49
50
|
* For messages from a remote client, this will be undefined.
|
|
50
51
|
*/
|
|
51
|
-
process(
|
|
52
|
+
process(
|
|
53
|
+
msg: ISequencedDocumentMessage,
|
|
54
|
+
op: IDirectoryOperation,
|
|
55
|
+
local: boolean,
|
|
56
|
+
localOpMetadata: unknown,
|
|
57
|
+
): void;
|
|
52
58
|
|
|
53
59
|
/**
|
|
54
60
|
* Communicate the operation to remote clients.
|
|
@@ -182,6 +188,21 @@ export type IDirectorySubDirectoryOperation =
|
|
|
182
188
|
*/
|
|
183
189
|
export type IDirectoryOperation = IDirectoryStorageOperation | IDirectorySubDirectoryOperation;
|
|
184
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Create info for the subdirectory.
|
|
193
|
+
*/
|
|
194
|
+
export interface ICreateInfo {
|
|
195
|
+
/**
|
|
196
|
+
* Sequence number at which this subdirectory was created.
|
|
197
|
+
*/
|
|
198
|
+
csn: number;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* clientids of the clients which created this sub directory.
|
|
202
|
+
*/
|
|
203
|
+
ccIds: string[];
|
|
204
|
+
}
|
|
205
|
+
|
|
185
206
|
/**
|
|
186
207
|
* Defines the in-memory object structure to be used for the conversion to/from serialized.
|
|
187
208
|
*
|
|
@@ -200,6 +221,15 @@ export interface IDirectoryDataObject {
|
|
|
200
221
|
* Recursive sub-directories {@link IDirectoryDataObject | objects}.
|
|
201
222
|
*/
|
|
202
223
|
subdirectories?: { [subdirName: string]: IDirectoryDataObject };
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create info for the sub directory. Since directories with same name can get deleted/created by multiple clients
|
|
227
|
+
* asynchronously, this info helps us to determine whether the ops where for the current instance of sub directory
|
|
228
|
+
* or not and whether to process them or not based on that. Summaries which were not produced which this change
|
|
229
|
+
* will not have this info and in that case we can still run in eventual consistency issues but that is no worse
|
|
230
|
+
* than the state before this change.
|
|
231
|
+
*/
|
|
232
|
+
ci?: ICreateInfo;
|
|
203
233
|
}
|
|
204
234
|
|
|
205
235
|
/**
|
|
@@ -336,6 +366,8 @@ export class SharedDirectory
|
|
|
336
366
|
* Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
|
|
337
367
|
*/
|
|
338
368
|
private readonly root: SubDirectory = new SubDirectory(
|
|
369
|
+
0,
|
|
370
|
+
new Set(),
|
|
339
371
|
this,
|
|
340
372
|
this.runtime,
|
|
341
373
|
this.serializer,
|
|
@@ -620,7 +652,12 @@ export class SharedDirectory
|
|
|
620
652
|
)) {
|
|
621
653
|
let newSubDir = currentSubDir.getSubDirectory(subdirName) as SubDirectory;
|
|
622
654
|
if (!newSubDir) {
|
|
655
|
+
const createInfo = subdirObject.ci;
|
|
623
656
|
newSubDir = new SubDirectory(
|
|
657
|
+
createInfo !== undefined ? createInfo.csn : 0,
|
|
658
|
+
createInfo !== undefined
|
|
659
|
+
? new Set<string>(createInfo.ccIds)
|
|
660
|
+
: new Set(),
|
|
624
661
|
this,
|
|
625
662
|
this.runtime,
|
|
626
663
|
this.serializer,
|
|
@@ -658,7 +695,7 @@ export class SharedDirectory
|
|
|
658
695
|
const op: IDirectoryOperation = message.contents as IDirectoryOperation;
|
|
659
696
|
const handler = this.messageHandlers.get(op.type);
|
|
660
697
|
assert(handler !== undefined, 0x00e /* Missing message handler for message type */);
|
|
661
|
-
handler.process(op, local, localOpMetadata);
|
|
698
|
+
handler.process(message, op, local, localOpMetadata);
|
|
662
699
|
}
|
|
663
700
|
}
|
|
664
701
|
|
|
@@ -705,15 +742,49 @@ export class SharedDirectory
|
|
|
705
742
|
return this.localValueMaker.fromSerializable(serializable);
|
|
706
743
|
}
|
|
707
744
|
|
|
745
|
+
/**
|
|
746
|
+
* This checks if there is pending delete op for local delete for a any subdir in the relative path.
|
|
747
|
+
* @param relativePath - path of sub directory.
|
|
748
|
+
* @returns - true if there is pending delete.
|
|
749
|
+
*/
|
|
750
|
+
private isSubDirectoryDeletePending(relativePath: string): boolean {
|
|
751
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
752
|
+
if (absolutePath === posix.sep) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
let currentParent = this.root;
|
|
756
|
+
const nodeList = absolutePath.split(posix.sep);
|
|
757
|
+
let start = 1;
|
|
758
|
+
while (start < nodeList.length) {
|
|
759
|
+
const subDirName = nodeList[start];
|
|
760
|
+
if (currentParent.isSubDirectoryDeletePending(subDirName)) {
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
currentParent = currentParent.getSubDirectory(subDirName) as SubDirectory;
|
|
764
|
+
if (currentParent === undefined) {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
start += 1;
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
|
|
708
772
|
/**
|
|
709
773
|
* Set the message handlers for the directory.
|
|
710
774
|
*/
|
|
711
775
|
private setMessageHandlers(): void {
|
|
712
776
|
this.messageHandlers.set("clear", {
|
|
713
|
-
process: (
|
|
777
|
+
process: (
|
|
778
|
+
msg: ISequencedDocumentMessage,
|
|
779
|
+
op: IDirectoryClearOperation,
|
|
780
|
+
local,
|
|
781
|
+
localOpMetadata,
|
|
782
|
+
) => {
|
|
714
783
|
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
715
|
-
|
|
716
|
-
|
|
784
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
785
|
+
// as we are going to delete this subDirectory.
|
|
786
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
787
|
+
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
717
788
|
}
|
|
718
789
|
},
|
|
719
790
|
submit: (op: IDirectoryClearOperation, localOpMetadata: unknown) => {
|
|
@@ -730,10 +801,17 @@ export class SharedDirectory
|
|
|
730
801
|
},
|
|
731
802
|
});
|
|
732
803
|
this.messageHandlers.set("delete", {
|
|
733
|
-
process: (
|
|
804
|
+
process: (
|
|
805
|
+
msg: ISequencedDocumentMessage,
|
|
806
|
+
op: IDirectoryDeleteOperation,
|
|
807
|
+
local,
|
|
808
|
+
localOpMetadata,
|
|
809
|
+
) => {
|
|
734
810
|
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
735
|
-
|
|
736
|
-
|
|
811
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
812
|
+
// as we are going to delete this subDirectory.
|
|
813
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
814
|
+
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
737
815
|
}
|
|
738
816
|
},
|
|
739
817
|
submit: (op: IDirectoryDeleteOperation, localOpMetadata: unknown) => {
|
|
@@ -752,11 +830,18 @@ export class SharedDirectory
|
|
|
752
830
|
},
|
|
753
831
|
});
|
|
754
832
|
this.messageHandlers.set("set", {
|
|
755
|
-
process: (
|
|
833
|
+
process: (
|
|
834
|
+
msg: ISequencedDocumentMessage,
|
|
835
|
+
op: IDirectorySetOperation,
|
|
836
|
+
local,
|
|
837
|
+
localOpMetadata,
|
|
838
|
+
) => {
|
|
756
839
|
const subdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
757
|
-
|
|
840
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
841
|
+
// as we are going to delete this subDirectory.
|
|
842
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
758
843
|
const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
|
|
759
|
-
subdir.processSetMessage(op, context, local, localOpMetadata);
|
|
844
|
+
subdir.processSetMessage(msg, op, context, local, localOpMetadata);
|
|
760
845
|
}
|
|
761
846
|
},
|
|
762
847
|
submit: (op: IDirectorySetOperation, localOpMetadata: unknown) => {
|
|
@@ -775,10 +860,17 @@ export class SharedDirectory
|
|
|
775
860
|
});
|
|
776
861
|
|
|
777
862
|
this.messageHandlers.set("createSubDirectory", {
|
|
778
|
-
process: (
|
|
863
|
+
process: (
|
|
864
|
+
msg: ISequencedDocumentMessage,
|
|
865
|
+
op: IDirectoryCreateSubDirectoryOperation,
|
|
866
|
+
local,
|
|
867
|
+
localOpMetadata,
|
|
868
|
+
) => {
|
|
779
869
|
const parentSubdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
780
|
-
|
|
781
|
-
|
|
870
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
871
|
+
// as we are going to delete this subDirectory.
|
|
872
|
+
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
873
|
+
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
782
874
|
}
|
|
783
875
|
},
|
|
784
876
|
submit: (op: IDirectoryCreateSubDirectoryOperation, localOpMetadata: unknown) => {
|
|
@@ -799,10 +891,17 @@ export class SharedDirectory
|
|
|
799
891
|
});
|
|
800
892
|
|
|
801
893
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
802
|
-
process: (
|
|
894
|
+
process: (
|
|
895
|
+
msg: ISequencedDocumentMessage,
|
|
896
|
+
op: IDirectoryDeleteSubDirectoryOperation,
|
|
897
|
+
local,
|
|
898
|
+
localOpMetadata,
|
|
899
|
+
) => {
|
|
803
900
|
const parentSubdir = this.getWorkingDirectory(op.path) as SubDirectory | undefined;
|
|
804
|
-
|
|
805
|
-
|
|
901
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
902
|
+
// as we are going to delete this subDirectory.
|
|
903
|
+
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
904
|
+
parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
806
905
|
}
|
|
807
906
|
},
|
|
808
907
|
submit: (op: IDirectoryDeleteSubDirectoryOperation, localOpMetadata: unknown) => {
|
|
@@ -853,6 +952,7 @@ export class SharedDirectory
|
|
|
853
952
|
while (stack.length > 0) {
|
|
854
953
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
855
954
|
const [currentSubDir, currentSubDirObject] = stack.pop()!;
|
|
955
|
+
currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
|
|
856
956
|
for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
|
|
857
957
|
if (!currentSubDirObject.storage) {
|
|
858
958
|
currentSubDirObject.storage = {};
|
|
@@ -916,7 +1016,6 @@ interface IClearLocalOpMetadata {
|
|
|
916
1016
|
interface ICreateSubDirLocalOpMetadata {
|
|
917
1017
|
type: "createSubDir";
|
|
918
1018
|
pendingMessageId: number;
|
|
919
|
-
previouslyExisted: boolean;
|
|
920
1019
|
}
|
|
921
1020
|
|
|
922
1021
|
interface IDeleteSubDirLocalOpMetadata {
|
|
@@ -954,8 +1053,7 @@ function isSubDirLocalOpMetadata(metadata: any): metadata is SubDirLocalOpMetada
|
|
|
954
1053
|
return (
|
|
955
1054
|
metadata !== undefined &&
|
|
956
1055
|
typeof metadata.pendingMessageId === "number" &&
|
|
957
|
-
(
|
|
958
|
-
metadata.type === "deleteSubDir")
|
|
1056
|
+
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir")
|
|
959
1057
|
);
|
|
960
1058
|
}
|
|
961
1059
|
|
|
@@ -966,7 +1064,7 @@ function isDirectoryLocalOpMetadata(metadata: any): metadata is DirectoryLocalOp
|
|
|
966
1064
|
(metadata.type === "edit" ||
|
|
967
1065
|
metadata.type === "deleteSubDir" ||
|
|
968
1066
|
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
969
|
-
|
|
1067
|
+
metadata.type === "createSubDir")
|
|
970
1068
|
);
|
|
971
1069
|
}
|
|
972
1070
|
|
|
@@ -1003,10 +1101,16 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1003
1101
|
private readonly pendingKeys: Map<string, number[]> = new Map();
|
|
1004
1102
|
|
|
1005
1103
|
/**
|
|
1006
|
-
* Subdirectories that have been
|
|
1104
|
+
* Subdirectories that have been created/deleted locally but not yet ack'd from the server.
|
|
1007
1105
|
*/
|
|
1008
1106
|
private readonly pendingSubDirectories: Map<string, number[]> = new Map();
|
|
1009
1107
|
|
|
1108
|
+
/**
|
|
1109
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
|
|
1110
|
+
* of delete op that are pending or yet to be acked from server.
|
|
1111
|
+
*/
|
|
1112
|
+
private readonly pendingDeleteSubDirectoriesCount: Map<string, number> = new Map();
|
|
1113
|
+
|
|
1010
1114
|
/**
|
|
1011
1115
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
1012
1116
|
*/
|
|
@@ -1019,12 +1123,16 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1019
1123
|
|
|
1020
1124
|
/**
|
|
1021
1125
|
* Constructor.
|
|
1126
|
+
* @param sequenceNumber - Message seq number at which this was created.
|
|
1127
|
+
* @param clientIds - Ids of client which created this directory.
|
|
1022
1128
|
* @param directory - Reference back to the SharedDirectory to perform operations
|
|
1023
1129
|
* @param runtime - The data store runtime this directory is associated with
|
|
1024
1130
|
* @param serializer - The serializer to serialize / parse handles
|
|
1025
1131
|
* @param absolutePath - The absolute path of this IDirectory
|
|
1026
1132
|
*/
|
|
1027
1133
|
public constructor(
|
|
1134
|
+
private sequenceNumber: number,
|
|
1135
|
+
private readonly clientIds: Set<string>,
|
|
1028
1136
|
private readonly directory: SharedDirectory,
|
|
1029
1137
|
private readonly runtime: IFluidDataStoreRuntime,
|
|
1030
1138
|
private readonly serializer: IFluidSerializer,
|
|
@@ -1132,22 +1240,29 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1132
1240
|
}
|
|
1133
1241
|
|
|
1134
1242
|
// Create the sub directory locally first.
|
|
1135
|
-
const isNew = this.createSubDirectoryCore(
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1243
|
+
const isNew = this.createSubDirectoryCore(
|
|
1244
|
+
subdirName,
|
|
1245
|
+
true,
|
|
1246
|
+
-1,
|
|
1247
|
+
this.runtime.clientId ?? "detached",
|
|
1248
|
+
);
|
|
1249
|
+
const subDir = this._subdirectories.get(subdirName);
|
|
1250
|
+
assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
|
|
1139
1251
|
|
|
1140
1252
|
// If we are not attached, don't submit the op.
|
|
1141
1253
|
if (!this.directory.isAttached()) {
|
|
1142
1254
|
return subDir;
|
|
1143
1255
|
}
|
|
1144
1256
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1257
|
+
// Only submit the op, if it is newly created.
|
|
1258
|
+
if (isNew) {
|
|
1259
|
+
const op: IDirectoryCreateSubDirectoryOperation = {
|
|
1260
|
+
path: this.absolutePath,
|
|
1261
|
+
subdirName,
|
|
1262
|
+
type: "createSubDirectory",
|
|
1263
|
+
};
|
|
1264
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1265
|
+
}
|
|
1151
1266
|
|
|
1152
1267
|
return subDir;
|
|
1153
1268
|
}
|
|
@@ -1181,13 +1296,16 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1181
1296
|
return subDir !== undefined;
|
|
1182
1297
|
}
|
|
1183
1298
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1299
|
+
// Only submit the op, if the directory existed and we deleted it.
|
|
1300
|
+
if (subDir !== undefined) {
|
|
1301
|
+
const op: IDirectoryDeleteSubDirectoryOperation = {
|
|
1302
|
+
path: this.absolutePath,
|
|
1303
|
+
subdirName,
|
|
1304
|
+
type: "deleteSubDirectory",
|
|
1305
|
+
};
|
|
1189
1306
|
|
|
1190
|
-
|
|
1307
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
1308
|
+
}
|
|
1191
1309
|
return subDir !== undefined;
|
|
1192
1310
|
}
|
|
1193
1311
|
|
|
@@ -1207,6 +1325,19 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1207
1325
|
return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
|
|
1208
1326
|
}
|
|
1209
1327
|
|
|
1328
|
+
/**
|
|
1329
|
+
* This checks if there is pending delete op for local delete for a given child subdirectory.
|
|
1330
|
+
* @param subDirName - directory name.
|
|
1331
|
+
* @returns - true if there is pending delete.
|
|
1332
|
+
*/
|
|
1333
|
+
public isSubDirectoryDeletePending(subDirName: string): boolean {
|
|
1334
|
+
const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
|
|
1335
|
+
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1210
1341
|
/**
|
|
1211
1342
|
* Deletes the given key from within this IDirectory.
|
|
1212
1343
|
* @param key - The key to delete
|
|
@@ -1337,6 +1468,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1337
1468
|
|
|
1338
1469
|
/**
|
|
1339
1470
|
* Process a clear operation.
|
|
1471
|
+
* @param msg - The message from the server to apply.
|
|
1340
1472
|
* @param op - The op to process
|
|
1341
1473
|
* @param local - Whether the message originated from the local client
|
|
1342
1474
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
@@ -1344,11 +1476,15 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1344
1476
|
* @internal
|
|
1345
1477
|
*/
|
|
1346
1478
|
public processClearMessage(
|
|
1479
|
+
msg: ISequencedDocumentMessage,
|
|
1347
1480
|
op: IDirectoryClearOperation,
|
|
1348
1481
|
local: boolean,
|
|
1349
1482
|
localOpMetadata: unknown,
|
|
1350
1483
|
): void {
|
|
1351
1484
|
this.throwIfDisposed();
|
|
1485
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1352
1488
|
if (local) {
|
|
1353
1489
|
assert(
|
|
1354
1490
|
isClearLocalOpMetadata(localOpMetadata),
|
|
@@ -1385,6 +1521,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1385
1521
|
|
|
1386
1522
|
/**
|
|
1387
1523
|
* Process a delete operation.
|
|
1524
|
+
* @param msg - The message from the server to apply.
|
|
1388
1525
|
* @param op - The op to process
|
|
1389
1526
|
* @param local - Whether the message originated from the local client
|
|
1390
1527
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
@@ -1392,12 +1529,18 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1392
1529
|
* @internal
|
|
1393
1530
|
*/
|
|
1394
1531
|
public processDeleteMessage(
|
|
1532
|
+
msg: ISequencedDocumentMessage,
|
|
1395
1533
|
op: IDirectoryDeleteOperation,
|
|
1396
1534
|
local: boolean,
|
|
1397
1535
|
localOpMetadata: unknown,
|
|
1398
1536
|
): void {
|
|
1399
1537
|
this.throwIfDisposed();
|
|
1400
|
-
if (
|
|
1538
|
+
if (
|
|
1539
|
+
!(
|
|
1540
|
+
this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1541
|
+
this.needProcessStorageOperation(op, local, localOpMetadata)
|
|
1542
|
+
)
|
|
1543
|
+
) {
|
|
1401
1544
|
return;
|
|
1402
1545
|
}
|
|
1403
1546
|
this.deleteCore(op.key, local);
|
|
@@ -1422,6 +1565,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1422
1565
|
|
|
1423
1566
|
/**
|
|
1424
1567
|
* Process a set operation.
|
|
1568
|
+
* @param msg - The message from the server to apply.
|
|
1425
1569
|
* @param op - The op to process
|
|
1426
1570
|
* @param local - Whether the message originated from the local client
|
|
1427
1571
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
@@ -1429,19 +1573,24 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1429
1573
|
* @internal
|
|
1430
1574
|
*/
|
|
1431
1575
|
public processSetMessage(
|
|
1576
|
+
msg: ISequencedDocumentMessage,
|
|
1432
1577
|
op: IDirectorySetOperation,
|
|
1433
1578
|
context: ILocalValue | undefined,
|
|
1434
1579
|
local: boolean,
|
|
1435
1580
|
localOpMetadata: unknown,
|
|
1436
1581
|
): void {
|
|
1437
1582
|
this.throwIfDisposed();
|
|
1438
|
-
if (
|
|
1583
|
+
if (
|
|
1584
|
+
!(
|
|
1585
|
+
this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1586
|
+
this.needProcessStorageOperation(op, local, localOpMetadata)
|
|
1587
|
+
)
|
|
1588
|
+
) {
|
|
1439
1589
|
return;
|
|
1440
1590
|
}
|
|
1441
1591
|
|
|
1442
1592
|
// needProcessStorageOperation should have returned false if local is true
|
|
1443
1593
|
// so we can assume context is not undefined
|
|
1444
|
-
|
|
1445
1594
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1446
1595
|
this.setCore(op.key, context!, local);
|
|
1447
1596
|
}
|
|
@@ -1470,6 +1619,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1470
1619
|
}
|
|
1471
1620
|
/**
|
|
1472
1621
|
* Process a create subdirectory operation.
|
|
1622
|
+
* @param msg - The message from the server to apply.
|
|
1473
1623
|
* @param op - The op to process
|
|
1474
1624
|
* @param local - Whether the message originated from the local client
|
|
1475
1625
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
@@ -1477,15 +1627,16 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1477
1627
|
* @internal
|
|
1478
1628
|
*/
|
|
1479
1629
|
public processCreateSubDirectoryMessage(
|
|
1630
|
+
msg: ISequencedDocumentMessage,
|
|
1480
1631
|
op: IDirectoryCreateSubDirectoryOperation,
|
|
1481
1632
|
local: boolean,
|
|
1482
1633
|
localOpMetadata: unknown,
|
|
1483
1634
|
): void {
|
|
1484
1635
|
this.throwIfDisposed();
|
|
1485
|
-
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1636
|
+
if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
|
|
1486
1637
|
return;
|
|
1487
1638
|
}
|
|
1488
|
-
this.createSubDirectoryCore(op.subdirName, local);
|
|
1639
|
+
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1489
1640
|
}
|
|
1490
1641
|
|
|
1491
1642
|
/**
|
|
@@ -1498,19 +1649,19 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1498
1649
|
): ICreateSubDirLocalOpMetadata {
|
|
1499
1650
|
this.throwIfDisposed();
|
|
1500
1651
|
// Create the sub directory locally first.
|
|
1501
|
-
|
|
1652
|
+
this.createSubDirectoryCore(op.subdirName, true, -1, this.runtime.clientId ?? "detached");
|
|
1502
1653
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1503
1654
|
|
|
1504
1655
|
const localOpMetadata: ICreateSubDirLocalOpMetadata = {
|
|
1505
1656
|
type: "createSubDir",
|
|
1506
1657
|
pendingMessageId: newMessageId,
|
|
1507
|
-
previouslyExisted: !isNew,
|
|
1508
1658
|
};
|
|
1509
1659
|
return localOpMetadata;
|
|
1510
1660
|
}
|
|
1511
1661
|
|
|
1512
1662
|
/**
|
|
1513
1663
|
* Process a delete subdirectory operation.
|
|
1664
|
+
* @param msg - The message from the server to apply.
|
|
1514
1665
|
* @param op - The op to process
|
|
1515
1666
|
* @param local - Whether the message originated from the local client
|
|
1516
1667
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
@@ -1518,12 +1669,18 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1518
1669
|
* @internal
|
|
1519
1670
|
*/
|
|
1520
1671
|
public processDeleteSubDirectoryMessage(
|
|
1672
|
+
msg: ISequencedDocumentMessage,
|
|
1521
1673
|
op: IDirectoryDeleteSubDirectoryOperation,
|
|
1522
1674
|
local: boolean,
|
|
1523
1675
|
localOpMetadata: unknown,
|
|
1524
1676
|
): void {
|
|
1525
1677
|
this.throwIfDisposed();
|
|
1526
|
-
if (
|
|
1678
|
+
if (
|
|
1679
|
+
!(
|
|
1680
|
+
this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1681
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)
|
|
1682
|
+
)
|
|
1683
|
+
) {
|
|
1527
1684
|
return;
|
|
1528
1685
|
}
|
|
1529
1686
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1652,25 +1809,24 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1652
1809
|
} else {
|
|
1653
1810
|
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1654
1811
|
}
|
|
1812
|
+
if (op.type === "deleteSubDirectory") {
|
|
1813
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName) ?? 0;
|
|
1814
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
|
|
1815
|
+
}
|
|
1655
1816
|
return newMessageId;
|
|
1656
1817
|
}
|
|
1657
1818
|
|
|
1658
1819
|
/**
|
|
1659
1820
|
* Submit a create subdirectory operation.
|
|
1660
1821
|
* @param op - The operation
|
|
1661
|
-
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1662
1822
|
*/
|
|
1663
|
-
private submitCreateSubDirectoryMessage(
|
|
1664
|
-
op: IDirectorySubDirectoryOperation,
|
|
1665
|
-
prevExisted: boolean,
|
|
1666
|
-
): void {
|
|
1823
|
+
private submitCreateSubDirectoryMessage(op: IDirectorySubDirectoryOperation): void {
|
|
1667
1824
|
this.throwIfDisposed();
|
|
1668
1825
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1669
1826
|
|
|
1670
1827
|
const localOpMetadata: ICreateSubDirLocalOpMetadata = {
|
|
1671
1828
|
type: "createSubDir",
|
|
1672
1829
|
pendingMessageId: newMessageId,
|
|
1673
|
-
previouslyExisted: prevExisted,
|
|
1674
1830
|
};
|
|
1675
1831
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1676
1832
|
}
|
|
@@ -1723,7 +1879,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1723
1879
|
}
|
|
1724
1880
|
|
|
1725
1881
|
if (localOpMetadata.type === "createSubDir") {
|
|
1726
|
-
this.submitCreateSubDirectoryMessage(op
|
|
1882
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1727
1883
|
} else {
|
|
1728
1884
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
1729
1885
|
}
|
|
@@ -1746,6 +1902,15 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1746
1902
|
}
|
|
1747
1903
|
}
|
|
1748
1904
|
|
|
1905
|
+
public getSerializableCreateInfo() {
|
|
1906
|
+
this.throwIfDisposed();
|
|
1907
|
+
const createInfo: ICreateInfo = {
|
|
1908
|
+
csn: this.sequenceNumber,
|
|
1909
|
+
ccIds: Array.from(this.clientIds),
|
|
1910
|
+
};
|
|
1911
|
+
return createInfo;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1749
1914
|
/**
|
|
1750
1915
|
* Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
|
|
1751
1916
|
* @param key - The key to populate
|
|
@@ -1838,9 +2003,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1838
2003
|
localOpMetadata.pendingMessageId,
|
|
1839
2004
|
);
|
|
1840
2005
|
} else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1841
|
-
|
|
1842
|
-
this.deleteSubDirectoryCore(op.subdirName as string, true);
|
|
1843
|
-
}
|
|
2006
|
+
this.deleteSubDirectoryCore(op.subdirName as string, true);
|
|
1844
2007
|
|
|
1845
2008
|
this.rollbackPendingMessageId(
|
|
1846
2009
|
this.pendingSubDirectories,
|
|
@@ -1860,6 +2023,12 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1860
2023
|
op.subdirName as string,
|
|
1861
2024
|
localOpMetadata.pendingMessageId,
|
|
1862
2025
|
);
|
|
2026
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
2027
|
+
assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
2028
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
2029
|
+
if (count === 1) {
|
|
2030
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
2031
|
+
}
|
|
1863
2032
|
} else {
|
|
1864
2033
|
throw new Error("Unsupported op for rollback");
|
|
1865
2034
|
}
|
|
@@ -1898,7 +2067,23 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1898
2067
|
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0],
|
|
1899
2068
|
0x010 /* "Received out of order storage op when there is an unackd clear message" */,
|
|
1900
2069
|
);
|
|
2070
|
+
// Remove all pendingMessageIds lower than first pendingClearMessageId.
|
|
2071
|
+
const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
|
|
2072
|
+
const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
|
|
2073
|
+
if (pendingKeyMessageIdArray !== undefined) {
|
|
2074
|
+
let index = 0;
|
|
2075
|
+
while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
|
|
2076
|
+
index += 1;
|
|
2077
|
+
}
|
|
2078
|
+
const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
|
|
2079
|
+
if (newPendingKeyMessageId.length === 0) {
|
|
2080
|
+
this.pendingKeys.delete(op.key);
|
|
2081
|
+
} else {
|
|
2082
|
+
this.pendingKeys.set(op.key, newPendingKeyMessageId);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
1901
2085
|
}
|
|
2086
|
+
|
|
1902
2087
|
// If I have a NACK clear, we can ignore all ops.
|
|
1903
2088
|
return false;
|
|
1904
2089
|
}
|
|
@@ -1930,6 +2115,22 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1930
2115
|
return !local;
|
|
1931
2116
|
}
|
|
1932
2117
|
|
|
2118
|
+
/**
|
|
2119
|
+
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
2120
|
+
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
2121
|
+
* @param msg - message for the directory
|
|
2122
|
+
*/
|
|
2123
|
+
private isMessageForCurrentInstanceOfSubDirectory(msg: ISequencedDocumentMessage) {
|
|
2124
|
+
// If the message is either from the creator of directory or this directory was created when
|
|
2125
|
+
// container was detached or in case this directory is already live(known to other clients)
|
|
2126
|
+
// and the op was created after the directory was created then apply this op.
|
|
2127
|
+
return (
|
|
2128
|
+
this.clientIds.has(msg.clientId) ||
|
|
2129
|
+
this.clientIds.has("detached") ||
|
|
2130
|
+
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber)
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1933
2134
|
/**
|
|
1934
2135
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1935
2136
|
* not process the incoming operation.
|
|
@@ -1941,6 +2142,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1941
2142
|
* @returns True if the operation should be processed, false otherwise
|
|
1942
2143
|
*/
|
|
1943
2144
|
private needProcessSubDirectoryOperation(
|
|
2145
|
+
msg: ISequencedDocumentMessage,
|
|
1944
2146
|
op: IDirectorySubDirectoryOperation,
|
|
1945
2147
|
local: boolean,
|
|
1946
2148
|
localOpMetadata: unknown,
|
|
@@ -1962,6 +2164,44 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1962
2164
|
if (pendingMessageIds.length === 0) {
|
|
1963
2165
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1964
2166
|
}
|
|
2167
|
+
if (op.type === "deleteSubDirectory") {
|
|
2168
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
2169
|
+
assert(
|
|
2170
|
+
count !== undefined && count > 0,
|
|
2171
|
+
0x5ac /* should have record for delete op */,
|
|
2172
|
+
);
|
|
2173
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
2174
|
+
if (count === 1) {
|
|
2175
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
} else if (op.type === "deleteSubDirectory") {
|
|
2179
|
+
// If this is remote delete op and we have keys in this subDirectory, then we need to delete these
|
|
2180
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
2181
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
2182
|
+
if (subDirectory) {
|
|
2183
|
+
subDirectory.clearExceptPendingKeys(local);
|
|
2184
|
+
// In case of remote delete op, we need to reset the creation seq number and client ids of
|
|
2185
|
+
// creators as the previous directory is getting deleted and we will initialize again when
|
|
2186
|
+
// we will receive op for the create again.
|
|
2187
|
+
subDirectory.sequenceNumber = -1;
|
|
2188
|
+
subDirectory.clientIds.clear();
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (op.type === "createSubDirectory") {
|
|
2192
|
+
const dir = this._subdirectories.get(op.subdirName);
|
|
2193
|
+
if (dir?.sequenceNumber === -1) {
|
|
2194
|
+
// Only set the seq on the first message, could be more
|
|
2195
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
2196
|
+
}
|
|
2197
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
2198
|
+
if (
|
|
2199
|
+
dir !== undefined &&
|
|
2200
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
2201
|
+
dir.sequenceNumber <= msg.sequenceNumber
|
|
2202
|
+
) {
|
|
2203
|
+
dir.clientIds.add(msg.clientId);
|
|
2204
|
+
}
|
|
1965
2205
|
}
|
|
1966
2206
|
return false;
|
|
1967
2207
|
}
|
|
@@ -1978,8 +2218,11 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
1978
2218
|
const temp = new Map<string, ILocalValue>();
|
|
1979
2219
|
|
|
1980
2220
|
for (const [key] of this.pendingKeys) {
|
|
1981
|
-
|
|
1982
|
-
|
|
2221
|
+
const value = this._storage.get(key);
|
|
2222
|
+
// If this key is already deleted, then we don't need to add it again.
|
|
2223
|
+
if (value !== undefined) {
|
|
2224
|
+
temp.set(key, value);
|
|
2225
|
+
}
|
|
1983
2226
|
}
|
|
1984
2227
|
|
|
1985
2228
|
this.clearCore(local);
|
|
@@ -2039,12 +2282,22 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
2039
2282
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
2040
2283
|
* @param subdirName - The name of the subdirectory being created
|
|
2041
2284
|
* @param local - Whether the message originated from the local client
|
|
2285
|
+
* @param seq - Sequence number at which this directory is created
|
|
2286
|
+
* @param clientId - Id of client which created this directory.
|
|
2042
2287
|
* @returns - True if is newly created, false if it already existed.
|
|
2043
2288
|
*/
|
|
2044
|
-
private createSubDirectoryCore(
|
|
2045
|
-
|
|
2289
|
+
private createSubDirectoryCore(
|
|
2290
|
+
subdirName: string,
|
|
2291
|
+
local: boolean,
|
|
2292
|
+
seq: number,
|
|
2293
|
+
clientId: string,
|
|
2294
|
+
): boolean {
|
|
2295
|
+
const subdir = this._subdirectories.get(subdirName);
|
|
2296
|
+
if (subdir === undefined) {
|
|
2046
2297
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
2047
2298
|
const subDir = new SubDirectory(
|
|
2299
|
+
seq,
|
|
2300
|
+
new Set([clientId]),
|
|
2048
2301
|
this.directory,
|
|
2049
2302
|
this.runtime,
|
|
2050
2303
|
this.serializer,
|
|
@@ -2054,6 +2307,8 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
|
|
|
2054
2307
|
this._subdirectories.set(subdirName, subDir);
|
|
2055
2308
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
2056
2309
|
return true;
|
|
2310
|
+
} else {
|
|
2311
|
+
subdir.clientIds.add(clientId);
|
|
2057
2312
|
}
|
|
2058
2313
|
return false;
|
|
2059
2314
|
}
|