@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/lib/directory.js CHANGED
@@ -92,7 +92,7 @@ export class SharedDirectory extends SharedObject {
92
92
  /**
93
93
  * Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
94
94
  */
95
- this.root = new SubDirectory(this, this.runtime, this.serializer, posix.sep);
95
+ this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
96
96
  /**
97
97
  * Mapping of op types to message handlers.
98
98
  */
@@ -348,7 +348,10 @@ export class SharedDirectory extends SharedObject {
348
348
  for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
349
349
  let newSubDir = currentSubDir.getSubDirectory(subdirName);
350
350
  if (!newSubDir) {
351
- newSubDir = new SubDirectory(this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
351
+ const createInfo = subdirObject.ci;
352
+ newSubDir = new SubDirectory(createInfo !== undefined ? createInfo.csn : 0, createInfo !== undefined
353
+ ? new Set(createInfo.ccIds)
354
+ : new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
352
355
  currentSubDir.populateSubDirectory(subdirName, newSubDir);
353
356
  }
354
357
  stack.push([newSubDir, subdirObject]);
@@ -371,7 +374,7 @@ export class SharedDirectory extends SharedObject {
371
374
  const op = message.contents;
372
375
  const handler = this.messageHandlers.get(op.type);
373
376
  assert(handler !== undefined, 0x00e /* Missing message handler for message type */);
374
- handler.process(op, local, localOpMetadata);
377
+ handler.process(message, op, local, localOpMetadata);
375
378
  }
376
379
  }
377
380
  /**
@@ -407,15 +410,41 @@ export class SharedDirectory extends SharedObject {
407
410
  serializable.type === ValueType[ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
408
411
  return this.localValueMaker.fromSerializable(serializable);
409
412
  }
413
+ /**
414
+ * This checks if there is pending delete op for local delete for a subdirectory.
415
+ * @param relativePath - path of sub directory.
416
+ * @returns - true if there is pending delete.
417
+ */
418
+ isSubDirectoryDeletePending(relativePath) {
419
+ const parentSubDir = this.getParentDirectory(relativePath);
420
+ const index = relativePath.lastIndexOf(posix.sep);
421
+ const dirName = relativePath.substring(index + 1);
422
+ return !!(parentSubDir === null || parentSubDir === void 0 ? void 0 : parentSubDir.isSubDirectoryDeletePending(dirName));
423
+ }
424
+ /**
425
+ * Gets the parent directory of a sub directory.
426
+ * @param relativePath - path of sub directory of which parent needs to be find out.
427
+ */
428
+ getParentDirectory(relativePath) {
429
+ const absolutePath = this.makeAbsolute(relativePath);
430
+ if (absolutePath === posix.sep) {
431
+ return undefined;
432
+ }
433
+ const index = absolutePath.lastIndexOf(posix.sep);
434
+ const parentAbsPath = absolutePath.substring(0, index);
435
+ return this.getWorkingDirectory(parentAbsPath);
436
+ }
410
437
  /**
411
438
  * Set the message handlers for the directory.
412
439
  */
413
440
  setMessageHandlers() {
414
441
  this.messageHandlers.set("clear", {
415
- process: (op, local, localOpMetadata) => {
442
+ process: (msg, op, local, localOpMetadata) => {
416
443
  const subdir = this.getWorkingDirectory(op.path);
417
- if (subdir) {
418
- subdir.processClearMessage(op, local, localOpMetadata);
444
+ // If there is pending delete op for this subDirectory, then don't apply the this op as we are going
445
+ // to delete this subDirectory.
446
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
447
+ subdir.processClearMessage(msg, op, local, localOpMetadata);
419
448
  }
420
449
  },
421
450
  submit: (op, localOpMetadata) => {
@@ -432,10 +461,12 @@ export class SharedDirectory extends SharedObject {
432
461
  },
433
462
  });
434
463
  this.messageHandlers.set("delete", {
435
- process: (op, local, localOpMetadata) => {
464
+ process: (msg, op, local, localOpMetadata) => {
436
465
  const subdir = this.getWorkingDirectory(op.path);
437
- if (subdir) {
438
- subdir.processDeleteMessage(op, local, localOpMetadata);
466
+ // If there is pending delete op for this subDirectory, then don't apply the this op as we are going
467
+ // to delete this subDirectory.
468
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
469
+ subdir.processDeleteMessage(msg, op, local, localOpMetadata);
439
470
  }
440
471
  },
441
472
  submit: (op, localOpMetadata) => {
@@ -452,11 +483,13 @@ export class SharedDirectory extends SharedObject {
452
483
  },
453
484
  });
454
485
  this.messageHandlers.set("set", {
455
- process: (op, local, localOpMetadata) => {
486
+ process: (msg, op, local, localOpMetadata) => {
456
487
  const subdir = this.getWorkingDirectory(op.path);
457
- if (subdir) {
488
+ // If there is pending delete op for this subDirectory, then don't apply the this op as we are going
489
+ // to delete this subDirectory.
490
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
458
491
  const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
459
- subdir.processSetMessage(op, context, local, localOpMetadata);
492
+ subdir.processSetMessage(msg, op, context, local, localOpMetadata);
460
493
  }
461
494
  },
462
495
  submit: (op, localOpMetadata) => {
@@ -474,10 +507,10 @@ export class SharedDirectory extends SharedObject {
474
507
  },
475
508
  });
476
509
  this.messageHandlers.set("createSubDirectory", {
477
- process: (op, local, localOpMetadata) => {
510
+ process: (msg, op, local, localOpMetadata) => {
478
511
  const parentSubdir = this.getWorkingDirectory(op.path);
479
512
  if (parentSubdir) {
480
- parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
513
+ parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
481
514
  }
482
515
  },
483
516
  submit: (op, localOpMetadata) => {
@@ -495,10 +528,10 @@ export class SharedDirectory extends SharedObject {
495
528
  },
496
529
  });
497
530
  this.messageHandlers.set("deleteSubDirectory", {
498
- process: (op, local, localOpMetadata) => {
531
+ process: (msg, op, local, localOpMetadata) => {
499
532
  const parentSubdir = this.getWorkingDirectory(op.path);
500
533
  if (parentSubdir) {
501
- parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
534
+ parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
502
535
  }
503
536
  },
504
537
  submit: (op, localOpMetadata) => {
@@ -538,6 +571,7 @@ export class SharedDirectory extends SharedObject {
538
571
  while (stack.length > 0) {
539
572
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
540
573
  const [currentSubDir, currentSubDirObject] = stack.pop();
574
+ currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
541
575
  for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
542
576
  if (!currentSubDirObject.storage) {
543
577
  currentSubDirObject.storage = {};
@@ -598,8 +632,7 @@ function isClearLocalOpMetadata(metadata) {
598
632
  function isSubDirLocalOpMetadata(metadata) {
599
633
  return (metadata !== undefined &&
600
634
  typeof metadata.pendingMessageId === "number" &&
601
- ((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
602
- metadata.type === "deleteSubDir"));
635
+ (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
603
636
  }
604
637
  function isDirectoryLocalOpMetadata(metadata) {
605
638
  return (metadata !== undefined &&
@@ -607,7 +640,7 @@ function isDirectoryLocalOpMetadata(metadata) {
607
640
  (metadata.type === "edit" ||
608
641
  metadata.type === "deleteSubDir" ||
609
642
  (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
610
- (metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean")));
643
+ metadata.type === "createSubDir"));
611
644
  }
612
645
  /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
613
646
  /**
@@ -617,13 +650,17 @@ function isDirectoryLocalOpMetadata(metadata) {
617
650
  class SubDirectory extends TypedEventEmitter {
618
651
  /**
619
652
  * Constructor.
653
+ * @param sequenceNumber - Message seq number at which this was created.
654
+ * @param clientIds - Ids of client which created this directory.
620
655
  * @param directory - Reference back to the SharedDirectory to perform operations
621
656
  * @param runtime - The data store runtime this directory is associated with
622
657
  * @param serializer - The serializer to serialize / parse handles
623
658
  * @param absolutePath - The absolute path of this IDirectory
624
659
  */
625
- constructor(directory, runtime, serializer, absolutePath) {
660
+ constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
626
661
  super();
662
+ this.sequenceNumber = sequenceNumber;
663
+ this.clientIds = clientIds;
627
664
  this.directory = directory;
628
665
  this.runtime = runtime;
629
666
  this.serializer = serializer;
@@ -649,9 +686,14 @@ class SubDirectory extends TypedEventEmitter {
649
686
  */
650
687
  this.pendingKeys = new Map();
651
688
  /**
652
- * Subdirectories that have been modified locally but not yet ack'd from the server.
689
+ * Subdirectories that have been created/deleted locally but not yet ack'd from the server.
653
690
  */
654
691
  this.pendingSubDirectories = new Map();
692
+ /**
693
+ * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
694
+ * of delete op that are pending or yet to be acked from server.
695
+ */
696
+ this.pendingDeleteSubDirectoriesCount = new Map();
655
697
  /**
656
698
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
657
699
  */
@@ -734,6 +776,7 @@ class SubDirectory extends TypedEventEmitter {
734
776
  * {@inheritDoc IDirectory.createSubDirectory}
735
777
  */
736
778
  createSubDirectory(subdirName) {
779
+ var _c;
737
780
  this.throwIfDisposed();
738
781
  // Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
739
782
  if (subdirName === undefined || subdirName === null) {
@@ -743,19 +786,22 @@ class SubDirectory extends TypedEventEmitter {
743
786
  throw new Error(`SubDirectory name may not contain ${posix.sep}`);
744
787
  }
745
788
  // Create the sub directory locally first.
746
- const isNew = this.createSubDirectoryCore(subdirName, true);
747
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
789
+ const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
748
790
  const subDir = this._subdirectories.get(subdirName);
791
+ assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
749
792
  // If we are not attached, don't submit the op.
750
793
  if (!this.directory.isAttached()) {
751
794
  return subDir;
752
795
  }
753
- const op = {
754
- path: this.absolutePath,
755
- subdirName,
756
- type: "createSubDirectory",
757
- };
758
- this.submitCreateSubDirectoryMessage(op, !isNew);
796
+ // Only submit the op, if it is newly created.
797
+ if (isNew) {
798
+ const op = {
799
+ path: this.absolutePath,
800
+ subdirName,
801
+ type: "createSubDirectory",
802
+ };
803
+ this.submitCreateSubDirectoryMessage(op);
804
+ }
759
805
  return subDir;
760
806
  }
761
807
  /**
@@ -783,12 +829,15 @@ class SubDirectory extends TypedEventEmitter {
783
829
  if (!this.directory.isAttached()) {
784
830
  return subDir !== undefined;
785
831
  }
786
- const op = {
787
- path: this.absolutePath,
788
- subdirName,
789
- type: "deleteSubDirectory",
790
- };
791
- this.submitDeleteSubDirectoryMessage(op, subDir);
832
+ // Only submit the op, if the directory existed and we deleted it.
833
+ if (subDir !== undefined) {
834
+ const op = {
835
+ path: this.absolutePath,
836
+ subdirName,
837
+ type: "deleteSubDirectory",
838
+ };
839
+ this.submitDeleteSubDirectoryMessage(op, subDir);
840
+ }
792
841
  return subDir !== undefined;
793
842
  }
794
843
  /**
@@ -805,6 +854,18 @@ class SubDirectory extends TypedEventEmitter {
805
854
  this.throwIfDisposed();
806
855
  return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
807
856
  }
857
+ /**
858
+ * This checks if there is pending delete op for local delete for a given child subdirectory.
859
+ * @param subDirName - directory name.
860
+ * @returns - true if there is pending delete.
861
+ */
862
+ isSubDirectoryDeletePending(subDirName) {
863
+ const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
864
+ if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
865
+ return true;
866
+ }
867
+ return false;
868
+ }
808
869
  /**
809
870
  * Deletes the given key from within this IDirectory.
810
871
  * @param key - The key to delete
@@ -920,14 +981,18 @@ class SubDirectory extends TypedEventEmitter {
920
981
  }
921
982
  /**
922
983
  * Process a clear operation.
984
+ * @param msg - The message from the server to apply.
923
985
  * @param op - The op to process
924
986
  * @param local - Whether the message originated from the local client
925
987
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
926
988
  * For messages from a remote client, this will be undefined.
927
989
  * @internal
928
990
  */
929
- processClearMessage(op, local, localOpMetadata) {
991
+ processClearMessage(msg, op, local, localOpMetadata) {
930
992
  this.throwIfDisposed();
993
+ if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
994
+ return;
995
+ }
931
996
  if (local) {
932
997
  assert(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
933
998
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
@@ -956,15 +1021,17 @@ class SubDirectory extends TypedEventEmitter {
956
1021
  }
957
1022
  /**
958
1023
  * Process a delete operation.
1024
+ * @param msg - The message from the server to apply.
959
1025
  * @param op - The op to process
960
1026
  * @param local - Whether the message originated from the local client
961
1027
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
962
1028
  * For messages from a remote client, this will be undefined.
963
1029
  * @internal
964
1030
  */
965
- processDeleteMessage(op, local, localOpMetadata) {
1031
+ processDeleteMessage(msg, op, local, localOpMetadata) {
966
1032
  this.throwIfDisposed();
967
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1033
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1034
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
968
1035
  return;
969
1036
  }
970
1037
  this.deleteCore(op.key, local);
@@ -987,15 +1054,17 @@ class SubDirectory extends TypedEventEmitter {
987
1054
  }
988
1055
  /**
989
1056
  * Process a set operation.
1057
+ * @param msg - The message from the server to apply.
990
1058
  * @param op - The op to process
991
1059
  * @param local - Whether the message originated from the local client
992
1060
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
993
1061
  * For messages from a remote client, this will be undefined.
994
1062
  * @internal
995
1063
  */
996
- processSetMessage(op, context, local, localOpMetadata) {
1064
+ processSetMessage(msg, op, context, local, localOpMetadata) {
997
1065
  this.throwIfDisposed();
998
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1066
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1067
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
999
1068
  return;
1000
1069
  }
1001
1070
  // needProcessStorageOperation should have returned false if local is true
@@ -1023,18 +1092,19 @@ class SubDirectory extends TypedEventEmitter {
1023
1092
  }
1024
1093
  /**
1025
1094
  * Process a create subdirectory operation.
1095
+ * @param msg - The message from the server to apply.
1026
1096
  * @param op - The op to process
1027
1097
  * @param local - Whether the message originated from the local client
1028
1098
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1029
1099
  * For messages from a remote client, this will be undefined.
1030
1100
  * @internal
1031
1101
  */
1032
- processCreateSubDirectoryMessage(op, local, localOpMetadata) {
1102
+ processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1033
1103
  this.throwIfDisposed();
1034
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1104
+ if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1035
1105
  return;
1036
1106
  }
1037
- this.createSubDirectoryCore(op.subdirName, local);
1107
+ this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1038
1108
  }
1039
1109
  /**
1040
1110
  * Apply createSubDirectory operation locally and generate metadata
@@ -1042,28 +1112,30 @@ class SubDirectory extends TypedEventEmitter {
1042
1112
  * @returns metadata generated for stahed op
1043
1113
  */
1044
1114
  applyStashedCreateSubDirMessage(op) {
1115
+ var _c;
1045
1116
  this.throwIfDisposed();
1046
1117
  // Create the sub directory locally first.
1047
- const isNew = this.createSubDirectoryCore(op.subdirName, true);
1118
+ this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1048
1119
  const newMessageId = this.getSubDirMessageId(op);
1049
1120
  const localOpMetadata = {
1050
1121
  type: "createSubDir",
1051
1122
  pendingMessageId: newMessageId,
1052
- previouslyExisted: !isNew,
1053
1123
  };
1054
1124
  return localOpMetadata;
1055
1125
  }
1056
1126
  /**
1057
1127
  * Process a delete subdirectory operation.
1128
+ * @param msg - The message from the server to apply.
1058
1129
  * @param op - The op to process
1059
1130
  * @param local - Whether the message originated from the local client
1060
1131
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1061
1132
  * For messages from a remote client, this will be undefined.
1062
1133
  * @internal
1063
1134
  */
1064
- processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
1135
+ processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
1065
1136
  this.throwIfDisposed();
1066
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1137
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1138
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1067
1139
  return;
1068
1140
  }
1069
1141
  this.deleteSubDirectoryCore(op.subdirName, local);
@@ -1159,6 +1231,7 @@ class SubDirectory extends TypedEventEmitter {
1159
1231
  * Get a new pending message id for the op and cache it to track the pending op
1160
1232
  */
1161
1233
  getSubDirMessageId(op) {
1234
+ var _c;
1162
1235
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1163
1236
  const newMessageId = ++this.pendingMessageId;
1164
1237
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
@@ -1168,20 +1241,22 @@ class SubDirectory extends TypedEventEmitter {
1168
1241
  else {
1169
1242
  this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1170
1243
  }
1244
+ if (op.type === "deleteSubDirectory") {
1245
+ const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
1246
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
1247
+ }
1171
1248
  return newMessageId;
1172
1249
  }
1173
1250
  /**
1174
1251
  * Submit a create subdirectory operation.
1175
1252
  * @param op - The operation
1176
- * @param prevExisted - Whether the subdirectory existed before the op
1177
1253
  */
1178
- submitCreateSubDirectoryMessage(op, prevExisted) {
1254
+ submitCreateSubDirectoryMessage(op) {
1179
1255
  this.throwIfDisposed();
1180
1256
  const newMessageId = this.getSubDirMessageId(op);
1181
1257
  const localOpMetadata = {
1182
1258
  type: "createSubDir",
1183
1259
  pendingMessageId: newMessageId,
1184
- previouslyExisted: prevExisted,
1185
1260
  };
1186
1261
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1187
1262
  }
@@ -1217,7 +1292,7 @@ class SubDirectory extends TypedEventEmitter {
1217
1292
  this.pendingSubDirectories.delete(op.subdirName);
1218
1293
  }
1219
1294
  if (localOpMetadata.type === "createSubDir") {
1220
- this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
1295
+ this.submitCreateSubDirectoryMessage(op);
1221
1296
  }
1222
1297
  else {
1223
1298
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
@@ -1237,6 +1312,14 @@ class SubDirectory extends TypedEventEmitter {
1237
1312
  yield res;
1238
1313
  }
1239
1314
  }
1315
+ getSerializableCreateInfo() {
1316
+ this.throwIfDisposed();
1317
+ const createInfo = {
1318
+ csn: this.sequenceNumber,
1319
+ ccIds: Array.from(this.clientIds),
1320
+ };
1321
+ return createInfo;
1322
+ }
1240
1323
  /**
1241
1324
  * Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
1242
1325
  * @param key - The key to populate
@@ -1314,9 +1397,7 @@ class SubDirectory extends TypedEventEmitter {
1314
1397
  this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
1315
1398
  }
1316
1399
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1317
- if (!localOpMetadata.previouslyExisted) {
1318
- this.deleteSubDirectoryCore(op.subdirName, true);
1319
- }
1400
+ this.deleteSubDirectoryCore(op.subdirName, true);
1320
1401
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1321
1402
  }
1322
1403
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
@@ -1327,6 +1408,12 @@ class SubDirectory extends TypedEventEmitter {
1327
1408
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1328
1409
  }
1329
1410
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1411
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1412
+ assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
1413
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1414
+ if (count === 1) {
1415
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1416
+ }
1330
1417
  }
1331
1418
  else {
1332
1419
  throw new Error("Unsupported op for rollback");
@@ -1356,6 +1443,22 @@ class SubDirectory extends TypedEventEmitter {
1356
1443
  assert(localOpMetadata !== undefined &&
1357
1444
  isKeyEditLocalOpMetadata(localOpMetadata) &&
1358
1445
  localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
1446
+ // Remove all pendingMessageIds lower than first pendingClearMessageId.
1447
+ const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
1448
+ const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
1449
+ if (pendingKeyMessageIdArray !== undefined) {
1450
+ let index = 0;
1451
+ while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
1452
+ index += 1;
1453
+ }
1454
+ const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
1455
+ if (newPendingKeyMessageId.length === 0) {
1456
+ this.pendingKeys.delete(op.key);
1457
+ }
1458
+ else {
1459
+ this.pendingKeys.set(op.key, newPendingKeyMessageId);
1460
+ }
1461
+ }
1359
1462
  }
1360
1463
  // If I have a NACK clear, we can ignore all ops.
1361
1464
  return false;
@@ -1379,6 +1482,19 @@ class SubDirectory extends TypedEventEmitter {
1379
1482
  // If we don't have a NACK op on the key, we need to process the remote ops.
1380
1483
  return !local;
1381
1484
  }
1485
+ /**
1486
+ * This return true if the message is for the current instance of this sub directory. As the sub directory
1487
+ * can be deleted and created again, then this finds if the message is for current instance of directory or not.
1488
+ * @param msg - message for the directory
1489
+ */
1490
+ isMessageForCurrentInstanceOfSubDirectory(msg) {
1491
+ // If the message is either from the creator of directory or this directory was created when
1492
+ // container was detached or in case this directory is already live(known to other clients)
1493
+ // and the op was created after the directory was created then apply this op.
1494
+ return (this.clientIds.has(msg.clientId) ||
1495
+ this.clientIds.has("detached") ||
1496
+ (this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
1497
+ }
1382
1498
  /**
1383
1499
  * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
1384
1500
  * not process the incoming operation.
@@ -1389,7 +1505,7 @@ class SubDirectory extends TypedEventEmitter {
1389
1505
  * For messages from a remote client, this will be undefined.
1390
1506
  * @returns True if the operation should be processed, false otherwise
1391
1507
  */
1392
- needProcessSubDirectoryOperation(op, local, localOpMetadata) {
1508
+ needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1393
1509
  const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1394
1510
  if (pendingSubDirectoryMessageId !== undefined) {
1395
1511
  if (local) {
@@ -1401,6 +1517,40 @@ class SubDirectory extends TypedEventEmitter {
1401
1517
  if (pendingMessageIds.length === 0) {
1402
1518
  this.pendingSubDirectories.delete(op.subdirName);
1403
1519
  }
1520
+ if (op.type === "deleteSubDirectory") {
1521
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1522
+ assert(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
1523
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1524
+ if (count === 1) {
1525
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1526
+ }
1527
+ }
1528
+ }
1529
+ else if (op.type === "deleteSubDirectory") {
1530
+ // If this is remote delete op and we have keys in this subDirectory, then we need to delete these
1531
+ // keys except the pending ones as they will be sequenced after this delete.
1532
+ const subDirectory = this._subdirectories.get(op.subdirName);
1533
+ if (subDirectory) {
1534
+ subDirectory.clearExceptPendingKeys(local);
1535
+ // In case of remote delete op, we need to reset the creation seq number and client ids of
1536
+ // creators as the previous directory is getting deleted and we will initialize again when
1537
+ // we will receive op for the create again.
1538
+ subDirectory.sequenceNumber = -1;
1539
+ subDirectory.clientIds.clear();
1540
+ }
1541
+ }
1542
+ if (op.type === "createSubDirectory") {
1543
+ const dir = this._subdirectories.get(op.subdirName);
1544
+ if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
1545
+ // Only set the seq on the first message, could be more
1546
+ dir.sequenceNumber = msg.sequenceNumber;
1547
+ }
1548
+ // The client created the dir at or after the dirs seq, so list its client id as a creator.
1549
+ if (dir !== undefined &&
1550
+ !dir.clientIds.has(msg.clientId) &&
1551
+ dir.sequenceNumber <= msg.sequenceNumber) {
1552
+ dir.clientIds.add(msg.clientId);
1553
+ }
1404
1554
  }
1405
1555
  return false;
1406
1556
  }
@@ -1414,8 +1564,11 @@ class SubDirectory extends TypedEventEmitter {
1414
1564
  // we will get the value for the pendingKeys and clear the map
1415
1565
  const temp = new Map();
1416
1566
  for (const [key] of this.pendingKeys) {
1417
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1418
- temp.set(key, this._storage.get(key));
1567
+ const value = this._storage.get(key);
1568
+ // If this key is already deleted, then we don't need to add it again.
1569
+ if (value !== undefined) {
1570
+ temp.set(key, value);
1571
+ }
1419
1572
  }
1420
1573
  this.clearCore(local);
1421
1574
  for (const [key, value] of temp.entries()) {
@@ -1469,17 +1622,23 @@ class SubDirectory extends TypedEventEmitter {
1469
1622
  * Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
1470
1623
  * @param subdirName - The name of the subdirectory being created
1471
1624
  * @param local - Whether the message originated from the local client
1625
+ * @param seq - Sequence number at which this directory is created
1626
+ * @param clientId - Id of client which created this directory.
1472
1627
  * @returns - True if is newly created, false if it already existed.
1473
1628
  */
1474
- createSubDirectoryCore(subdirName, local) {
1475
- if (!this._subdirectories.has(subdirName)) {
1629
+ createSubDirectoryCore(subdirName, local, seq, clientId) {
1630
+ const subdir = this._subdirectories.get(subdirName);
1631
+ if (subdir === undefined) {
1476
1632
  const absolutePath = posix.join(this.absolutePath, subdirName);
1477
- const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
1633
+ const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
1478
1634
  this.registerEventsOnSubDirectory(subDir, subdirName);
1479
1635
  this._subdirectories.set(subdirName, subDir);
1480
1636
  this.emit("subDirectoryCreated", subdirName, local, this);
1481
1637
  return true;
1482
1638
  }
1639
+ else {
1640
+ subdir.clientIds.add(clientId);
1641
+ }
1483
1642
  return false;
1484
1643
  }
1485
1644
  registerEventsOnSubDirectory(subDirectory, subDirName) {