@fluidframework/map 2.0.0-internal.3.3.2 → 2.0.0-internal.3.4.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/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,41 @@ 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 subdirectory.
438
+ * @param relativePath - path of sub directory.
439
+ * @returns - true if there is pending delete.
440
+ */
441
+ isSubDirectoryDeletePending(relativePath) {
442
+ const parentSubDir = this.getParentDirectory(relativePath);
443
+ const index = relativePath.lastIndexOf(posix.sep);
444
+ const dirName = relativePath.substring(index + 1);
445
+ return !!(parentSubDir === null || parentSubDir === void 0 ? void 0 : parentSubDir.isSubDirectoryDeletePending(dirName));
446
+ }
447
+ /**
448
+ * Gets the parent directory of a sub directory.
449
+ * @param relativePath - path of sub directory of which parent needs to be find out.
450
+ */
451
+ getParentDirectory(relativePath) {
452
+ const absolutePath = this.makeAbsolute(relativePath);
453
+ if (absolutePath === posix.sep) {
454
+ return undefined;
455
+ }
456
+ const index = absolutePath.lastIndexOf(posix.sep);
457
+ const parentAbsPath = absolutePath.substring(0, index);
458
+ return this.getWorkingDirectory(parentAbsPath);
459
+ }
433
460
  /**
434
461
  * Set the message handlers for the directory.
435
462
  */
436
463
  setMessageHandlers() {
437
464
  this.messageHandlers.set("clear", {
438
- process: (op, local, localOpMetadata) => {
465
+ process: (msg, op, local, localOpMetadata) => {
439
466
  const subdir = this.getWorkingDirectory(op.path);
440
- if (subdir) {
441
- subdir.processClearMessage(op, local, localOpMetadata);
467
+ // If there is pending delete op for this subDirectory, then don't apply the this op as we are going
468
+ // to delete this subDirectory.
469
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
470
+ subdir.processClearMessage(msg, op, local, localOpMetadata);
442
471
  }
443
472
  },
444
473
  submit: (op, localOpMetadata) => {
@@ -455,10 +484,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
455
484
  },
456
485
  });
457
486
  this.messageHandlers.set("delete", {
458
- process: (op, local, localOpMetadata) => {
487
+ process: (msg, op, local, localOpMetadata) => {
459
488
  const subdir = this.getWorkingDirectory(op.path);
460
- if (subdir) {
461
- subdir.processDeleteMessage(op, local, localOpMetadata);
489
+ // If there is pending delete op for this subDirectory, then don't apply the this op as we are going
490
+ // to delete this subDirectory.
491
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
492
+ subdir.processDeleteMessage(msg, op, local, localOpMetadata);
462
493
  }
463
494
  },
464
495
  submit: (op, localOpMetadata) => {
@@ -475,11 +506,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
475
506
  },
476
507
  });
477
508
  this.messageHandlers.set("set", {
478
- process: (op, local, localOpMetadata) => {
509
+ process: (msg, op, local, localOpMetadata) => {
479
510
  const subdir = this.getWorkingDirectory(op.path);
480
- if (subdir) {
511
+ // If there is pending delete op for this subDirectory, then don't apply the this op as we are going
512
+ // to delete this subDirectory.
513
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
481
514
  const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
482
- subdir.processSetMessage(op, context, local, localOpMetadata);
515
+ subdir.processSetMessage(msg, op, context, local, localOpMetadata);
483
516
  }
484
517
  },
485
518
  submit: (op, localOpMetadata) => {
@@ -497,10 +530,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
497
530
  },
498
531
  });
499
532
  this.messageHandlers.set("createSubDirectory", {
500
- process: (op, local, localOpMetadata) => {
533
+ process: (msg, op, local, localOpMetadata) => {
501
534
  const parentSubdir = this.getWorkingDirectory(op.path);
502
535
  if (parentSubdir) {
503
- parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
536
+ parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
504
537
  }
505
538
  },
506
539
  submit: (op, localOpMetadata) => {
@@ -518,10 +551,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
518
551
  },
519
552
  });
520
553
  this.messageHandlers.set("deleteSubDirectory", {
521
- process: (op, local, localOpMetadata) => {
554
+ process: (msg, op, local, localOpMetadata) => {
522
555
  const parentSubdir = this.getWorkingDirectory(op.path);
523
556
  if (parentSubdir) {
524
- parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
557
+ parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
525
558
  }
526
559
  },
527
560
  submit: (op, localOpMetadata) => {
@@ -561,6 +594,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
561
594
  while (stack.length > 0) {
562
595
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563
596
  const [currentSubDir, currentSubDirObject] = stack.pop();
597
+ currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
564
598
  for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
565
599
  if (!currentSubDirObject.storage) {
566
600
  currentSubDirObject.storage = {};
@@ -622,8 +656,7 @@ function isClearLocalOpMetadata(metadata) {
622
656
  function isSubDirLocalOpMetadata(metadata) {
623
657
  return (metadata !== undefined &&
624
658
  typeof metadata.pendingMessageId === "number" &&
625
- ((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
626
- metadata.type === "deleteSubDir"));
659
+ (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
627
660
  }
628
661
  function isDirectoryLocalOpMetadata(metadata) {
629
662
  return (metadata !== undefined &&
@@ -631,7 +664,7 @@ function isDirectoryLocalOpMetadata(metadata) {
631
664
  (metadata.type === "edit" ||
632
665
  metadata.type === "deleteSubDir" ||
633
666
  (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
634
- (metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean")));
667
+ metadata.type === "createSubDir"));
635
668
  }
636
669
  /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
637
670
  /**
@@ -641,13 +674,17 @@ function isDirectoryLocalOpMetadata(metadata) {
641
674
  class SubDirectory extends common_utils_1.TypedEventEmitter {
642
675
  /**
643
676
  * Constructor.
677
+ * @param sequenceNumber - Message seq number at which this was created.
678
+ * @param clientIds - Ids of client which created this directory.
644
679
  * @param directory - Reference back to the SharedDirectory to perform operations
645
680
  * @param runtime - The data store runtime this directory is associated with
646
681
  * @param serializer - The serializer to serialize / parse handles
647
682
  * @param absolutePath - The absolute path of this IDirectory
648
683
  */
649
- constructor(directory, runtime, serializer, absolutePath) {
684
+ constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
650
685
  super();
686
+ this.sequenceNumber = sequenceNumber;
687
+ this.clientIds = clientIds;
651
688
  this.directory = directory;
652
689
  this.runtime = runtime;
653
690
  this.serializer = serializer;
@@ -673,9 +710,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
673
710
  */
674
711
  this.pendingKeys = new Map();
675
712
  /**
676
- * Subdirectories that have been modified locally but not yet ack'd from the server.
713
+ * Subdirectories that have been created/deleted locally but not yet ack'd from the server.
677
714
  */
678
715
  this.pendingSubDirectories = new Map();
716
+ /**
717
+ * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
718
+ * of delete op that are pending or yet to be acked from server.
719
+ */
720
+ this.pendingDeleteSubDirectoriesCount = new Map();
679
721
  /**
680
722
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
681
723
  */
@@ -758,6 +800,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
758
800
  * {@inheritDoc IDirectory.createSubDirectory}
759
801
  */
760
802
  createSubDirectory(subdirName) {
803
+ var _c;
761
804
  this.throwIfDisposed();
762
805
  // Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
763
806
  if (subdirName === undefined || subdirName === null) {
@@ -767,19 +810,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
767
810
  throw new Error(`SubDirectory name may not contain ${posix.sep}`);
768
811
  }
769
812
  // Create the sub directory locally first.
770
- const isNew = this.createSubDirectoryCore(subdirName, true);
771
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
813
+ const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
772
814
  const subDir = this._subdirectories.get(subdirName);
815
+ (0, common_utils_1.assert)(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
773
816
  // If we are not attached, don't submit the op.
774
817
  if (!this.directory.isAttached()) {
775
818
  return subDir;
776
819
  }
777
- const op = {
778
- path: this.absolutePath,
779
- subdirName,
780
- type: "createSubDirectory",
781
- };
782
- this.submitCreateSubDirectoryMessage(op, !isNew);
820
+ // Only submit the op, if it is newly created.
821
+ if (isNew) {
822
+ const op = {
823
+ path: this.absolutePath,
824
+ subdirName,
825
+ type: "createSubDirectory",
826
+ };
827
+ this.submitCreateSubDirectoryMessage(op);
828
+ }
783
829
  return subDir;
784
830
  }
785
831
  /**
@@ -807,12 +853,15 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
807
853
  if (!this.directory.isAttached()) {
808
854
  return subDir !== undefined;
809
855
  }
810
- const op = {
811
- path: this.absolutePath,
812
- subdirName,
813
- type: "deleteSubDirectory",
814
- };
815
- this.submitDeleteSubDirectoryMessage(op, subDir);
856
+ // Only submit the op, if the directory existed and we deleted it.
857
+ if (subDir !== undefined) {
858
+ const op = {
859
+ path: this.absolutePath,
860
+ subdirName,
861
+ type: "deleteSubDirectory",
862
+ };
863
+ this.submitDeleteSubDirectoryMessage(op, subDir);
864
+ }
816
865
  return subDir !== undefined;
817
866
  }
818
867
  /**
@@ -829,6 +878,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
829
878
  this.throwIfDisposed();
830
879
  return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
831
880
  }
881
+ /**
882
+ * This checks if there is pending delete op for local delete for a given child subdirectory.
883
+ * @param subDirName - directory name.
884
+ * @returns - true if there is pending delete.
885
+ */
886
+ isSubDirectoryDeletePending(subDirName) {
887
+ const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
888
+ if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
889
+ return true;
890
+ }
891
+ return false;
892
+ }
832
893
  /**
833
894
  * Deletes the given key from within this IDirectory.
834
895
  * @param key - The key to delete
@@ -944,14 +1005,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
944
1005
  }
945
1006
  /**
946
1007
  * Process a clear operation.
1008
+ * @param msg - The message from the server to apply.
947
1009
  * @param op - The op to process
948
1010
  * @param local - Whether the message originated from the local client
949
1011
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
950
1012
  * For messages from a remote client, this will be undefined.
951
1013
  * @internal
952
1014
  */
953
- processClearMessage(op, local, localOpMetadata) {
1015
+ processClearMessage(msg, op, local, localOpMetadata) {
954
1016
  this.throwIfDisposed();
1017
+ if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
1018
+ return;
1019
+ }
955
1020
  if (local) {
956
1021
  (0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
957
1022
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
@@ -980,15 +1045,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
980
1045
  }
981
1046
  /**
982
1047
  * Process a delete operation.
1048
+ * @param msg - The message from the server to apply.
983
1049
  * @param op - The op to process
984
1050
  * @param local - Whether the message originated from the local client
985
1051
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
986
1052
  * For messages from a remote client, this will be undefined.
987
1053
  * @internal
988
1054
  */
989
- processDeleteMessage(op, local, localOpMetadata) {
1055
+ processDeleteMessage(msg, op, local, localOpMetadata) {
990
1056
  this.throwIfDisposed();
991
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1057
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1058
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
992
1059
  return;
993
1060
  }
994
1061
  this.deleteCore(op.key, local);
@@ -1011,15 +1078,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1011
1078
  }
1012
1079
  /**
1013
1080
  * Process a set operation.
1081
+ * @param msg - The message from the server to apply.
1014
1082
  * @param op - The op to process
1015
1083
  * @param local - Whether the message originated from the local client
1016
1084
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1017
1085
  * For messages from a remote client, this will be undefined.
1018
1086
  * @internal
1019
1087
  */
1020
- processSetMessage(op, context, local, localOpMetadata) {
1088
+ processSetMessage(msg, op, context, local, localOpMetadata) {
1021
1089
  this.throwIfDisposed();
1022
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1090
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1091
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
1023
1092
  return;
1024
1093
  }
1025
1094
  // needProcessStorageOperation should have returned false if local is true
@@ -1047,18 +1116,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1047
1116
  }
1048
1117
  /**
1049
1118
  * Process a create subdirectory operation.
1119
+ * @param msg - The message from the server to apply.
1050
1120
  * @param op - The op to process
1051
1121
  * @param local - Whether the message originated from the local client
1052
1122
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1053
1123
  * For messages from a remote client, this will be undefined.
1054
1124
  * @internal
1055
1125
  */
1056
- processCreateSubDirectoryMessage(op, local, localOpMetadata) {
1126
+ processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1057
1127
  this.throwIfDisposed();
1058
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1128
+ if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1059
1129
  return;
1060
1130
  }
1061
- this.createSubDirectoryCore(op.subdirName, local);
1131
+ this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1062
1132
  }
1063
1133
  /**
1064
1134
  * Apply createSubDirectory operation locally and generate metadata
@@ -1066,28 +1136,30 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1066
1136
  * @returns metadata generated for stahed op
1067
1137
  */
1068
1138
  applyStashedCreateSubDirMessage(op) {
1139
+ var _c;
1069
1140
  this.throwIfDisposed();
1070
1141
  // Create the sub directory locally first.
1071
- const isNew = this.createSubDirectoryCore(op.subdirName, true);
1142
+ this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1072
1143
  const newMessageId = this.getSubDirMessageId(op);
1073
1144
  const localOpMetadata = {
1074
1145
  type: "createSubDir",
1075
1146
  pendingMessageId: newMessageId,
1076
- previouslyExisted: !isNew,
1077
1147
  };
1078
1148
  return localOpMetadata;
1079
1149
  }
1080
1150
  /**
1081
1151
  * Process a delete subdirectory operation.
1152
+ * @param msg - The message from the server to apply.
1082
1153
  * @param op - The op to process
1083
1154
  * @param local - Whether the message originated from the local client
1084
1155
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1085
1156
  * For messages from a remote client, this will be undefined.
1086
1157
  * @internal
1087
1158
  */
1088
- processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
1159
+ processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
1089
1160
  this.throwIfDisposed();
1090
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1161
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1162
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1091
1163
  return;
1092
1164
  }
1093
1165
  this.deleteSubDirectoryCore(op.subdirName, local);
@@ -1183,6 +1255,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1183
1255
  * Get a new pending message id for the op and cache it to track the pending op
1184
1256
  */
1185
1257
  getSubDirMessageId(op) {
1258
+ var _c;
1186
1259
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1187
1260
  const newMessageId = ++this.pendingMessageId;
1188
1261
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
@@ -1192,20 +1265,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1192
1265
  else {
1193
1266
  this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1194
1267
  }
1268
+ if (op.type === "deleteSubDirectory") {
1269
+ const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
1270
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
1271
+ }
1195
1272
  return newMessageId;
1196
1273
  }
1197
1274
  /**
1198
1275
  * Submit a create subdirectory operation.
1199
1276
  * @param op - The operation
1200
- * @param prevExisted - Whether the subdirectory existed before the op
1201
1277
  */
1202
- submitCreateSubDirectoryMessage(op, prevExisted) {
1278
+ submitCreateSubDirectoryMessage(op) {
1203
1279
  this.throwIfDisposed();
1204
1280
  const newMessageId = this.getSubDirMessageId(op);
1205
1281
  const localOpMetadata = {
1206
1282
  type: "createSubDir",
1207
1283
  pendingMessageId: newMessageId,
1208
- previouslyExisted: prevExisted,
1209
1284
  };
1210
1285
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1211
1286
  }
@@ -1241,7 +1316,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1241
1316
  this.pendingSubDirectories.delete(op.subdirName);
1242
1317
  }
1243
1318
  if (localOpMetadata.type === "createSubDir") {
1244
- this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
1319
+ this.submitCreateSubDirectoryMessage(op);
1245
1320
  }
1246
1321
  else {
1247
1322
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
@@ -1261,6 +1336,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1261
1336
  yield res;
1262
1337
  }
1263
1338
  }
1339
+ getSerializableCreateInfo() {
1340
+ this.throwIfDisposed();
1341
+ const createInfo = {
1342
+ csn: this.sequenceNumber,
1343
+ ccIds: Array.from(this.clientIds),
1344
+ };
1345
+ return createInfo;
1346
+ }
1264
1347
  /**
1265
1348
  * Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
1266
1349
  * @param key - The key to populate
@@ -1338,9 +1421,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1338
1421
  this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
1339
1422
  }
1340
1423
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1341
- if (!localOpMetadata.previouslyExisted) {
1342
- this.deleteSubDirectoryCore(op.subdirName, true);
1343
- }
1424
+ this.deleteSubDirectoryCore(op.subdirName, true);
1344
1425
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1345
1426
  }
1346
1427
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
@@ -1351,6 +1432,12 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1351
1432
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1352
1433
  }
1353
1434
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1435
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1436
+ (0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
1437
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1438
+ if (count === 1) {
1439
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1440
+ }
1354
1441
  }
1355
1442
  else {
1356
1443
  throw new Error("Unsupported op for rollback");
@@ -1380,6 +1467,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1380
1467
  (0, common_utils_1.assert)(localOpMetadata !== undefined &&
1381
1468
  isKeyEditLocalOpMetadata(localOpMetadata) &&
1382
1469
  localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
1470
+ // Remove all pendingMessageIds lower than first pendingClearMessageId.
1471
+ const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
1472
+ const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
1473
+ if (pendingKeyMessageIdArray !== undefined) {
1474
+ let index = 0;
1475
+ while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
1476
+ index += 1;
1477
+ }
1478
+ const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
1479
+ if (newPendingKeyMessageId.length === 0) {
1480
+ this.pendingKeys.delete(op.key);
1481
+ }
1482
+ else {
1483
+ this.pendingKeys.set(op.key, newPendingKeyMessageId);
1484
+ }
1485
+ }
1383
1486
  }
1384
1487
  // If I have a NACK clear, we can ignore all ops.
1385
1488
  return false;
@@ -1403,6 +1506,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1403
1506
  // If we don't have a NACK op on the key, we need to process the remote ops.
1404
1507
  return !local;
1405
1508
  }
1509
+ /**
1510
+ * This return true if the message is for the current instance of this sub directory. As the sub directory
1511
+ * can be deleted and created again, then this finds if the message is for current instance of directory or not.
1512
+ * @param msg - message for the directory
1513
+ */
1514
+ isMessageForCurrentInstanceOfSubDirectory(msg) {
1515
+ // If the message is either from the creator of directory or this directory was created when
1516
+ // container was detached or in case this directory is already live(known to other clients)
1517
+ // and the op was created after the directory was created then apply this op.
1518
+ return (this.clientIds.has(msg.clientId) ||
1519
+ this.clientIds.has("detached") ||
1520
+ (this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
1521
+ }
1406
1522
  /**
1407
1523
  * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
1408
1524
  * not process the incoming operation.
@@ -1413,7 +1529,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1413
1529
  * For messages from a remote client, this will be undefined.
1414
1530
  * @returns True if the operation should be processed, false otherwise
1415
1531
  */
1416
- needProcessSubDirectoryOperation(op, local, localOpMetadata) {
1532
+ needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1417
1533
  const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1418
1534
  if (pendingSubDirectoryMessageId !== undefined) {
1419
1535
  if (local) {
@@ -1425,6 +1541,40 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1425
1541
  if (pendingMessageIds.length === 0) {
1426
1542
  this.pendingSubDirectories.delete(op.subdirName);
1427
1543
  }
1544
+ if (op.type === "deleteSubDirectory") {
1545
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1546
+ (0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
1547
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1548
+ if (count === 1) {
1549
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1550
+ }
1551
+ }
1552
+ }
1553
+ else if (op.type === "deleteSubDirectory") {
1554
+ // If this is remote delete op and we have keys in this subDirectory, then we need to delete these
1555
+ // keys except the pending ones as they will be sequenced after this delete.
1556
+ const subDirectory = this._subdirectories.get(op.subdirName);
1557
+ if (subDirectory) {
1558
+ subDirectory.clearExceptPendingKeys(local);
1559
+ // In case of remote delete op, we need to reset the creation seq number and client ids of
1560
+ // creators as the previous directory is getting deleted and we will initialize again when
1561
+ // we will receive op for the create again.
1562
+ subDirectory.sequenceNumber = -1;
1563
+ subDirectory.clientIds.clear();
1564
+ }
1565
+ }
1566
+ if (op.type === "createSubDirectory") {
1567
+ const dir = this._subdirectories.get(op.subdirName);
1568
+ if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
1569
+ // Only set the seq on the first message, could be more
1570
+ dir.sequenceNumber = msg.sequenceNumber;
1571
+ }
1572
+ // The client created the dir at or after the dirs seq, so list its client id as a creator.
1573
+ if (dir !== undefined &&
1574
+ !dir.clientIds.has(msg.clientId) &&
1575
+ dir.sequenceNumber <= msg.sequenceNumber) {
1576
+ dir.clientIds.add(msg.clientId);
1577
+ }
1428
1578
  }
1429
1579
  return false;
1430
1580
  }
@@ -1438,8 +1588,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1438
1588
  // we will get the value for the pendingKeys and clear the map
1439
1589
  const temp = new Map();
1440
1590
  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));
1591
+ const value = this._storage.get(key);
1592
+ // If this key is already deleted, then we don't need to add it again.
1593
+ if (value !== undefined) {
1594
+ temp.set(key, value);
1595
+ }
1443
1596
  }
1444
1597
  this.clearCore(local);
1445
1598
  for (const [key, value] of temp.entries()) {
@@ -1493,17 +1646,23 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1493
1646
  * Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
1494
1647
  * @param subdirName - The name of the subdirectory being created
1495
1648
  * @param local - Whether the message originated from the local client
1649
+ * @param seq - Sequence number at which this directory is created
1650
+ * @param clientId - Id of client which created this directory.
1496
1651
  * @returns - True if is newly created, false if it already existed.
1497
1652
  */
1498
- createSubDirectoryCore(subdirName, local) {
1499
- if (!this._subdirectories.has(subdirName)) {
1653
+ createSubDirectoryCore(subdirName, local, seq, clientId) {
1654
+ const subdir = this._subdirectories.get(subdirName);
1655
+ if (subdir === undefined) {
1500
1656
  const absolutePath = posix.join(this.absolutePath, subdirName);
1501
- const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
1657
+ const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
1502
1658
  this.registerEventsOnSubDirectory(subDir, subdirName);
1503
1659
  this._subdirectories.set(subdirName, subDir);
1504
1660
  this.emit("subDirectoryCreated", subdirName, local, this);
1505
1661
  return true;
1506
1662
  }
1663
+ else {
1664
+ subdir.clientIds.add(clientId);
1665
+ }
1507
1666
  return false;
1508
1667
  }
1509
1668
  registerEventsOnSubDirectory(subDirectory, subDirName) {