@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/dist/directory.js CHANGED
@@ -115,7 +115,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
115
115
  /**
116
116
  * Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
117
117
  */
118
- this.root = new SubDirectory(this, this.runtime, this.serializer, posix.sep);
118
+ this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
119
119
  /**
120
120
  * Mapping of op types to message handlers.
121
121
  */
@@ -371,7 +371,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
371
371
  for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
372
372
  let newSubDir = currentSubDir.getSubDirectory(subdirName);
373
373
  if (!newSubDir) {
374
- newSubDir = new SubDirectory(this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
374
+ const createInfo = subdirObject.ci;
375
+ newSubDir = new SubDirectory(createInfo !== undefined ? createInfo.csn : 0, createInfo !== undefined
376
+ ? new Set(createInfo.ccIds)
377
+ : new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
375
378
  currentSubDir.populateSubDirectory(subdirName, newSubDir);
376
379
  }
377
380
  stack.push([newSubDir, subdirObject]);
@@ -394,7 +397,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
394
397
  const op = message.contents;
395
398
  const handler = this.messageHandlers.get(op.type);
396
399
  (0, common_utils_1.assert)(handler !== undefined, 0x00e /* Missing message handler for message type */);
397
- handler.process(op, local, localOpMetadata);
400
+ handler.process(message, op, local, localOpMetadata);
398
401
  }
399
402
  }
400
403
  /**
@@ -430,15 +433,43 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
430
433
  serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
431
434
  return this.localValueMaker.fromSerializable(serializable);
432
435
  }
436
+ /**
437
+ * This checks if there is pending delete op for local delete for a any subdir in the relative path.
438
+ * @param relativePath - path of sub directory.
439
+ * @returns - true if there is pending delete.
440
+ */
441
+ isSubDirectoryDeletePending(relativePath) {
442
+ const absolutePath = this.makeAbsolute(relativePath);
443
+ if (absolutePath === posix.sep) {
444
+ return false;
445
+ }
446
+ let currentParent = this.root;
447
+ const nodeList = absolutePath.split(posix.sep);
448
+ let start = 1;
449
+ while (start < nodeList.length) {
450
+ const subDirName = nodeList[start];
451
+ if (currentParent.isSubDirectoryDeletePending(subDirName)) {
452
+ return true;
453
+ }
454
+ currentParent = currentParent.getSubDirectory(subDirName);
455
+ if (currentParent === undefined) {
456
+ return true;
457
+ }
458
+ start += 1;
459
+ }
460
+ return false;
461
+ }
433
462
  /**
434
463
  * Set the message handlers for the directory.
435
464
  */
436
465
  setMessageHandlers() {
437
466
  this.messageHandlers.set("clear", {
438
- process: (op, local, localOpMetadata) => {
467
+ process: (msg, op, local, localOpMetadata) => {
439
468
  const subdir = this.getWorkingDirectory(op.path);
440
- if (subdir) {
441
- subdir.processClearMessage(op, local, localOpMetadata);
469
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
470
+ // as we are going to delete this subDirectory.
471
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
472
+ subdir.processClearMessage(msg, op, local, localOpMetadata);
442
473
  }
443
474
  },
444
475
  submit: (op, localOpMetadata) => {
@@ -455,10 +486,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
455
486
  },
456
487
  });
457
488
  this.messageHandlers.set("delete", {
458
- process: (op, local, localOpMetadata) => {
489
+ process: (msg, op, local, localOpMetadata) => {
459
490
  const subdir = this.getWorkingDirectory(op.path);
460
- if (subdir) {
461
- subdir.processDeleteMessage(op, local, localOpMetadata);
491
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
492
+ // as we are going to delete this subDirectory.
493
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
494
+ subdir.processDeleteMessage(msg, op, local, localOpMetadata);
462
495
  }
463
496
  },
464
497
  submit: (op, localOpMetadata) => {
@@ -475,11 +508,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
475
508
  },
476
509
  });
477
510
  this.messageHandlers.set("set", {
478
- process: (op, local, localOpMetadata) => {
511
+ process: (msg, op, local, localOpMetadata) => {
479
512
  const subdir = this.getWorkingDirectory(op.path);
480
- if (subdir) {
513
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
514
+ // as we are going to delete this subDirectory.
515
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
481
516
  const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
482
- subdir.processSetMessage(op, context, local, localOpMetadata);
517
+ subdir.processSetMessage(msg, op, context, local, localOpMetadata);
483
518
  }
484
519
  },
485
520
  submit: (op, localOpMetadata) => {
@@ -497,10 +532,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
497
532
  },
498
533
  });
499
534
  this.messageHandlers.set("createSubDirectory", {
500
- process: (op, local, localOpMetadata) => {
535
+ process: (msg, op, local, localOpMetadata) => {
501
536
  const parentSubdir = this.getWorkingDirectory(op.path);
502
- if (parentSubdir) {
503
- parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
537
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
538
+ // as we are going to delete this subDirectory.
539
+ if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
540
+ parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
504
541
  }
505
542
  },
506
543
  submit: (op, localOpMetadata) => {
@@ -518,10 +555,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
518
555
  },
519
556
  });
520
557
  this.messageHandlers.set("deleteSubDirectory", {
521
- process: (op, local, localOpMetadata) => {
558
+ process: (msg, op, local, localOpMetadata) => {
522
559
  const parentSubdir = this.getWorkingDirectory(op.path);
523
- if (parentSubdir) {
524
- parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
560
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
561
+ // as we are going to delete this subDirectory.
562
+ if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
563
+ parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
525
564
  }
526
565
  },
527
566
  submit: (op, localOpMetadata) => {
@@ -561,6 +600,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
561
600
  while (stack.length > 0) {
562
601
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563
602
  const [currentSubDir, currentSubDirObject] = stack.pop();
603
+ currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
564
604
  for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
565
605
  if (!currentSubDirObject.storage) {
566
606
  currentSubDirObject.storage = {};
@@ -622,8 +662,7 @@ function isClearLocalOpMetadata(metadata) {
622
662
  function isSubDirLocalOpMetadata(metadata) {
623
663
  return (metadata !== undefined &&
624
664
  typeof metadata.pendingMessageId === "number" &&
625
- ((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
626
- metadata.type === "deleteSubDir"));
665
+ (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
627
666
  }
628
667
  function isDirectoryLocalOpMetadata(metadata) {
629
668
  return (metadata !== undefined &&
@@ -631,7 +670,7 @@ function isDirectoryLocalOpMetadata(metadata) {
631
670
  (metadata.type === "edit" ||
632
671
  metadata.type === "deleteSubDir" ||
633
672
  (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
634
- (metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean")));
673
+ metadata.type === "createSubDir"));
635
674
  }
636
675
  /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
637
676
  /**
@@ -641,13 +680,17 @@ function isDirectoryLocalOpMetadata(metadata) {
641
680
  class SubDirectory extends common_utils_1.TypedEventEmitter {
642
681
  /**
643
682
  * Constructor.
683
+ * @param sequenceNumber - Message seq number at which this was created.
684
+ * @param clientIds - Ids of client which created this directory.
644
685
  * @param directory - Reference back to the SharedDirectory to perform operations
645
686
  * @param runtime - The data store runtime this directory is associated with
646
687
  * @param serializer - The serializer to serialize / parse handles
647
688
  * @param absolutePath - The absolute path of this IDirectory
648
689
  */
649
- constructor(directory, runtime, serializer, absolutePath) {
690
+ constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
650
691
  super();
692
+ this.sequenceNumber = sequenceNumber;
693
+ this.clientIds = clientIds;
651
694
  this.directory = directory;
652
695
  this.runtime = runtime;
653
696
  this.serializer = serializer;
@@ -673,9 +716,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
673
716
  */
674
717
  this.pendingKeys = new Map();
675
718
  /**
676
- * Subdirectories that have been modified locally but not yet ack'd from the server.
719
+ * Subdirectories that have been created/deleted locally but not yet ack'd from the server.
677
720
  */
678
721
  this.pendingSubDirectories = new Map();
722
+ /**
723
+ * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
724
+ * of delete op that are pending or yet to be acked from server.
725
+ */
726
+ this.pendingDeleteSubDirectoriesCount = new Map();
679
727
  /**
680
728
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
681
729
  */
@@ -758,6 +806,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
758
806
  * {@inheritDoc IDirectory.createSubDirectory}
759
807
  */
760
808
  createSubDirectory(subdirName) {
809
+ var _c;
761
810
  this.throwIfDisposed();
762
811
  // Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
763
812
  if (subdirName === undefined || subdirName === null) {
@@ -767,19 +816,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
767
816
  throw new Error(`SubDirectory name may not contain ${posix.sep}`);
768
817
  }
769
818
  // Create the sub directory locally first.
770
- const isNew = this.createSubDirectoryCore(subdirName, true);
771
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
819
+ const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
772
820
  const subDir = this._subdirectories.get(subdirName);
821
+ (0, common_utils_1.assert)(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
773
822
  // If we are not attached, don't submit the op.
774
823
  if (!this.directory.isAttached()) {
775
824
  return subDir;
776
825
  }
777
- const op = {
778
- path: this.absolutePath,
779
- subdirName,
780
- type: "createSubDirectory",
781
- };
782
- this.submitCreateSubDirectoryMessage(op, !isNew);
826
+ // Only submit the op, if it is newly created.
827
+ if (isNew) {
828
+ const op = {
829
+ path: this.absolutePath,
830
+ subdirName,
831
+ type: "createSubDirectory",
832
+ };
833
+ this.submitCreateSubDirectoryMessage(op);
834
+ }
783
835
  return subDir;
784
836
  }
785
837
  /**
@@ -807,12 +859,15 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
807
859
  if (!this.directory.isAttached()) {
808
860
  return subDir !== undefined;
809
861
  }
810
- const op = {
811
- path: this.absolutePath,
812
- subdirName,
813
- type: "deleteSubDirectory",
814
- };
815
- this.submitDeleteSubDirectoryMessage(op, subDir);
862
+ // Only submit the op, if the directory existed and we deleted it.
863
+ if (subDir !== undefined) {
864
+ const op = {
865
+ path: this.absolutePath,
866
+ subdirName,
867
+ type: "deleteSubDirectory",
868
+ };
869
+ this.submitDeleteSubDirectoryMessage(op, subDir);
870
+ }
816
871
  return subDir !== undefined;
817
872
  }
818
873
  /**
@@ -829,6 +884,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
829
884
  this.throwIfDisposed();
830
885
  return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
831
886
  }
887
+ /**
888
+ * This checks if there is pending delete op for local delete for a given child subdirectory.
889
+ * @param subDirName - directory name.
890
+ * @returns - true if there is pending delete.
891
+ */
892
+ isSubDirectoryDeletePending(subDirName) {
893
+ const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
894
+ if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
895
+ return true;
896
+ }
897
+ return false;
898
+ }
832
899
  /**
833
900
  * Deletes the given key from within this IDirectory.
834
901
  * @param key - The key to delete
@@ -944,14 +1011,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
944
1011
  }
945
1012
  /**
946
1013
  * Process a clear operation.
1014
+ * @param msg - The message from the server to apply.
947
1015
  * @param op - The op to process
948
1016
  * @param local - Whether the message originated from the local client
949
1017
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
950
1018
  * For messages from a remote client, this will be undefined.
951
1019
  * @internal
952
1020
  */
953
- processClearMessage(op, local, localOpMetadata) {
1021
+ processClearMessage(msg, op, local, localOpMetadata) {
954
1022
  this.throwIfDisposed();
1023
+ if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
1024
+ return;
1025
+ }
955
1026
  if (local) {
956
1027
  (0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
957
1028
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
@@ -980,15 +1051,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
980
1051
  }
981
1052
  /**
982
1053
  * Process a delete operation.
1054
+ * @param msg - The message from the server to apply.
983
1055
  * @param op - The op to process
984
1056
  * @param local - Whether the message originated from the local client
985
1057
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
986
1058
  * For messages from a remote client, this will be undefined.
987
1059
  * @internal
988
1060
  */
989
- processDeleteMessage(op, local, localOpMetadata) {
1061
+ processDeleteMessage(msg, op, local, localOpMetadata) {
990
1062
  this.throwIfDisposed();
991
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1063
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1064
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
992
1065
  return;
993
1066
  }
994
1067
  this.deleteCore(op.key, local);
@@ -1011,15 +1084,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1011
1084
  }
1012
1085
  /**
1013
1086
  * Process a set operation.
1087
+ * @param msg - The message from the server to apply.
1014
1088
  * @param op - The op to process
1015
1089
  * @param local - Whether the message originated from the local client
1016
1090
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1017
1091
  * For messages from a remote client, this will be undefined.
1018
1092
  * @internal
1019
1093
  */
1020
- processSetMessage(op, context, local, localOpMetadata) {
1094
+ processSetMessage(msg, op, context, local, localOpMetadata) {
1021
1095
  this.throwIfDisposed();
1022
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1096
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1097
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
1023
1098
  return;
1024
1099
  }
1025
1100
  // needProcessStorageOperation should have returned false if local is true
@@ -1047,18 +1122,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1047
1122
  }
1048
1123
  /**
1049
1124
  * Process a create subdirectory operation.
1125
+ * @param msg - The message from the server to apply.
1050
1126
  * @param op - The op to process
1051
1127
  * @param local - Whether the message originated from the local client
1052
1128
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1053
1129
  * For messages from a remote client, this will be undefined.
1054
1130
  * @internal
1055
1131
  */
1056
- processCreateSubDirectoryMessage(op, local, localOpMetadata) {
1132
+ processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1057
1133
  this.throwIfDisposed();
1058
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1134
+ if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1059
1135
  return;
1060
1136
  }
1061
- this.createSubDirectoryCore(op.subdirName, local);
1137
+ this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1062
1138
  }
1063
1139
  /**
1064
1140
  * Apply createSubDirectory operation locally and generate metadata
@@ -1066,28 +1142,30 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1066
1142
  * @returns metadata generated for stahed op
1067
1143
  */
1068
1144
  applyStashedCreateSubDirMessage(op) {
1145
+ var _c;
1069
1146
  this.throwIfDisposed();
1070
1147
  // Create the sub directory locally first.
1071
- const isNew = this.createSubDirectoryCore(op.subdirName, true);
1148
+ this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1072
1149
  const newMessageId = this.getSubDirMessageId(op);
1073
1150
  const localOpMetadata = {
1074
1151
  type: "createSubDir",
1075
1152
  pendingMessageId: newMessageId,
1076
- previouslyExisted: !isNew,
1077
1153
  };
1078
1154
  return localOpMetadata;
1079
1155
  }
1080
1156
  /**
1081
1157
  * Process a delete subdirectory operation.
1158
+ * @param msg - The message from the server to apply.
1082
1159
  * @param op - The op to process
1083
1160
  * @param local - Whether the message originated from the local client
1084
1161
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1085
1162
  * For messages from a remote client, this will be undefined.
1086
1163
  * @internal
1087
1164
  */
1088
- processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
1165
+ processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
1089
1166
  this.throwIfDisposed();
1090
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1167
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1168
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1091
1169
  return;
1092
1170
  }
1093
1171
  this.deleteSubDirectoryCore(op.subdirName, local);
@@ -1183,6 +1261,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1183
1261
  * Get a new pending message id for the op and cache it to track the pending op
1184
1262
  */
1185
1263
  getSubDirMessageId(op) {
1264
+ var _c;
1186
1265
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1187
1266
  const newMessageId = ++this.pendingMessageId;
1188
1267
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
@@ -1192,20 +1271,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1192
1271
  else {
1193
1272
  this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1194
1273
  }
1274
+ if (op.type === "deleteSubDirectory") {
1275
+ const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
1276
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
1277
+ }
1195
1278
  return newMessageId;
1196
1279
  }
1197
1280
  /**
1198
1281
  * Submit a create subdirectory operation.
1199
1282
  * @param op - The operation
1200
- * @param prevExisted - Whether the subdirectory existed before the op
1201
1283
  */
1202
- submitCreateSubDirectoryMessage(op, prevExisted) {
1284
+ submitCreateSubDirectoryMessage(op) {
1203
1285
  this.throwIfDisposed();
1204
1286
  const newMessageId = this.getSubDirMessageId(op);
1205
1287
  const localOpMetadata = {
1206
1288
  type: "createSubDir",
1207
1289
  pendingMessageId: newMessageId,
1208
- previouslyExisted: prevExisted,
1209
1290
  };
1210
1291
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1211
1292
  }
@@ -1241,7 +1322,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1241
1322
  this.pendingSubDirectories.delete(op.subdirName);
1242
1323
  }
1243
1324
  if (localOpMetadata.type === "createSubDir") {
1244
- this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
1325
+ this.submitCreateSubDirectoryMessage(op);
1245
1326
  }
1246
1327
  else {
1247
1328
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
@@ -1261,6 +1342,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1261
1342
  yield res;
1262
1343
  }
1263
1344
  }
1345
+ getSerializableCreateInfo() {
1346
+ this.throwIfDisposed();
1347
+ const createInfo = {
1348
+ csn: this.sequenceNumber,
1349
+ ccIds: Array.from(this.clientIds),
1350
+ };
1351
+ return createInfo;
1352
+ }
1264
1353
  /**
1265
1354
  * Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
1266
1355
  * @param key - The key to populate
@@ -1338,9 +1427,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1338
1427
  this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
1339
1428
  }
1340
1429
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1341
- if (!localOpMetadata.previouslyExisted) {
1342
- this.deleteSubDirectoryCore(op.subdirName, true);
1343
- }
1430
+ this.deleteSubDirectoryCore(op.subdirName, true);
1344
1431
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1345
1432
  }
1346
1433
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
@@ -1351,6 +1438,12 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1351
1438
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1352
1439
  }
1353
1440
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1441
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1442
+ (0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
1443
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1444
+ if (count === 1) {
1445
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1446
+ }
1354
1447
  }
1355
1448
  else {
1356
1449
  throw new Error("Unsupported op for rollback");
@@ -1380,6 +1473,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1380
1473
  (0, common_utils_1.assert)(localOpMetadata !== undefined &&
1381
1474
  isKeyEditLocalOpMetadata(localOpMetadata) &&
1382
1475
  localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
1476
+ // Remove all pendingMessageIds lower than first pendingClearMessageId.
1477
+ const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
1478
+ const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
1479
+ if (pendingKeyMessageIdArray !== undefined) {
1480
+ let index = 0;
1481
+ while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
1482
+ index += 1;
1483
+ }
1484
+ const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
1485
+ if (newPendingKeyMessageId.length === 0) {
1486
+ this.pendingKeys.delete(op.key);
1487
+ }
1488
+ else {
1489
+ this.pendingKeys.set(op.key, newPendingKeyMessageId);
1490
+ }
1491
+ }
1383
1492
  }
1384
1493
  // If I have a NACK clear, we can ignore all ops.
1385
1494
  return false;
@@ -1403,6 +1512,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1403
1512
  // If we don't have a NACK op on the key, we need to process the remote ops.
1404
1513
  return !local;
1405
1514
  }
1515
+ /**
1516
+ * This return true if the message is for the current instance of this sub directory. As the sub directory
1517
+ * can be deleted and created again, then this finds if the message is for current instance of directory or not.
1518
+ * @param msg - message for the directory
1519
+ */
1520
+ isMessageForCurrentInstanceOfSubDirectory(msg) {
1521
+ // If the message is either from the creator of directory or this directory was created when
1522
+ // container was detached or in case this directory is already live(known to other clients)
1523
+ // and the op was created after the directory was created then apply this op.
1524
+ return (this.clientIds.has(msg.clientId) ||
1525
+ this.clientIds.has("detached") ||
1526
+ (this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
1527
+ }
1406
1528
  /**
1407
1529
  * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
1408
1530
  * not process the incoming operation.
@@ -1413,7 +1535,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1413
1535
  * For messages from a remote client, this will be undefined.
1414
1536
  * @returns True if the operation should be processed, false otherwise
1415
1537
  */
1416
- needProcessSubDirectoryOperation(op, local, localOpMetadata) {
1538
+ needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1417
1539
  const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1418
1540
  if (pendingSubDirectoryMessageId !== undefined) {
1419
1541
  if (local) {
@@ -1425,6 +1547,40 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1425
1547
  if (pendingMessageIds.length === 0) {
1426
1548
  this.pendingSubDirectories.delete(op.subdirName);
1427
1549
  }
1550
+ if (op.type === "deleteSubDirectory") {
1551
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1552
+ (0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
1553
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1554
+ if (count === 1) {
1555
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1556
+ }
1557
+ }
1558
+ }
1559
+ else if (op.type === "deleteSubDirectory") {
1560
+ // If this is remote delete op and we have keys in this subDirectory, then we need to delete these
1561
+ // keys except the pending ones as they will be sequenced after this delete.
1562
+ const subDirectory = this._subdirectories.get(op.subdirName);
1563
+ if (subDirectory) {
1564
+ subDirectory.clearExceptPendingKeys(local);
1565
+ // In case of remote delete op, we need to reset the creation seq number and client ids of
1566
+ // creators as the previous directory is getting deleted and we will initialize again when
1567
+ // we will receive op for the create again.
1568
+ subDirectory.sequenceNumber = -1;
1569
+ subDirectory.clientIds.clear();
1570
+ }
1571
+ }
1572
+ if (op.type === "createSubDirectory") {
1573
+ const dir = this._subdirectories.get(op.subdirName);
1574
+ if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
1575
+ // Only set the seq on the first message, could be more
1576
+ dir.sequenceNumber = msg.sequenceNumber;
1577
+ }
1578
+ // The client created the dir at or after the dirs seq, so list its client id as a creator.
1579
+ if (dir !== undefined &&
1580
+ !dir.clientIds.has(msg.clientId) &&
1581
+ dir.sequenceNumber <= msg.sequenceNumber) {
1582
+ dir.clientIds.add(msg.clientId);
1583
+ }
1428
1584
  }
1429
1585
  return false;
1430
1586
  }
@@ -1438,8 +1594,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1438
1594
  // we will get the value for the pendingKeys and clear the map
1439
1595
  const temp = new Map();
1440
1596
  for (const [key] of this.pendingKeys) {
1441
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1442
- temp.set(key, this._storage.get(key));
1597
+ const value = this._storage.get(key);
1598
+ // If this key is already deleted, then we don't need to add it again.
1599
+ if (value !== undefined) {
1600
+ temp.set(key, value);
1601
+ }
1443
1602
  }
1444
1603
  this.clearCore(local);
1445
1604
  for (const [key, value] of temp.entries()) {
@@ -1493,17 +1652,23 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1493
1652
  * Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
1494
1653
  * @param subdirName - The name of the subdirectory being created
1495
1654
  * @param local - Whether the message originated from the local client
1655
+ * @param seq - Sequence number at which this directory is created
1656
+ * @param clientId - Id of client which created this directory.
1496
1657
  * @returns - True if is newly created, false if it already existed.
1497
1658
  */
1498
- createSubDirectoryCore(subdirName, local) {
1499
- if (!this._subdirectories.has(subdirName)) {
1659
+ createSubDirectoryCore(subdirName, local, seq, clientId) {
1660
+ const subdir = this._subdirectories.get(subdirName);
1661
+ if (subdir === undefined) {
1500
1662
  const absolutePath = posix.join(this.absolutePath, subdirName);
1501
- const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
1663
+ const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
1502
1664
  this.registerEventsOnSubDirectory(subDir, subdirName);
1503
1665
  this._subdirectories.set(subdirName, subDir);
1504
1666
  this.emit("subDirectoryCreated", subdirName, local, this);
1505
1667
  return true;
1506
1668
  }
1669
+ else {
1670
+ subdir.clientIds.add(clientId);
1671
+ }
1507
1672
  return false;
1508
1673
  }
1509
1674
  registerEventsOnSubDirectory(subDirectory, subDirName) {