@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/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,43 @@ 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 any subdir in the relative path.
415
+ * @param relativePath - path of sub directory.
416
+ * @returns - true if there is pending delete.
417
+ */
418
+ isSubDirectoryDeletePending(relativePath) {
419
+ const absolutePath = this.makeAbsolute(relativePath);
420
+ if (absolutePath === posix.sep) {
421
+ return false;
422
+ }
423
+ let currentParent = this.root;
424
+ const nodeList = absolutePath.split(posix.sep);
425
+ let start = 1;
426
+ while (start < nodeList.length) {
427
+ const subDirName = nodeList[start];
428
+ if (currentParent.isSubDirectoryDeletePending(subDirName)) {
429
+ return true;
430
+ }
431
+ currentParent = currentParent.getSubDirectory(subDirName);
432
+ if (currentParent === undefined) {
433
+ return true;
434
+ }
435
+ start += 1;
436
+ }
437
+ return false;
438
+ }
410
439
  /**
411
440
  * Set the message handlers for the directory.
412
441
  */
413
442
  setMessageHandlers() {
414
443
  this.messageHandlers.set("clear", {
415
- process: (op, local, localOpMetadata) => {
444
+ process: (msg, op, local, localOpMetadata) => {
416
445
  const subdir = this.getWorkingDirectory(op.path);
417
- if (subdir) {
418
- subdir.processClearMessage(op, local, localOpMetadata);
446
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
447
+ // as we are going to delete this subDirectory.
448
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
449
+ subdir.processClearMessage(msg, op, local, localOpMetadata);
419
450
  }
420
451
  },
421
452
  submit: (op, localOpMetadata) => {
@@ -432,10 +463,12 @@ export class SharedDirectory extends SharedObject {
432
463
  },
433
464
  });
434
465
  this.messageHandlers.set("delete", {
435
- process: (op, local, localOpMetadata) => {
466
+ process: (msg, op, local, localOpMetadata) => {
436
467
  const subdir = this.getWorkingDirectory(op.path);
437
- if (subdir) {
438
- subdir.processDeleteMessage(op, local, localOpMetadata);
468
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
469
+ // as we are going to delete this subDirectory.
470
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
471
+ subdir.processDeleteMessage(msg, op, local, localOpMetadata);
439
472
  }
440
473
  },
441
474
  submit: (op, localOpMetadata) => {
@@ -452,11 +485,13 @@ export class SharedDirectory extends SharedObject {
452
485
  },
453
486
  });
454
487
  this.messageHandlers.set("set", {
455
- process: (op, local, localOpMetadata) => {
488
+ process: (msg, op, local, localOpMetadata) => {
456
489
  const subdir = this.getWorkingDirectory(op.path);
457
- if (subdir) {
490
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
491
+ // as we are going to delete this subDirectory.
492
+ if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
458
493
  const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
459
- subdir.processSetMessage(op, context, local, localOpMetadata);
494
+ subdir.processSetMessage(msg, op, context, local, localOpMetadata);
460
495
  }
461
496
  },
462
497
  submit: (op, localOpMetadata) => {
@@ -474,10 +509,12 @@ export class SharedDirectory extends SharedObject {
474
509
  },
475
510
  });
476
511
  this.messageHandlers.set("createSubDirectory", {
477
- process: (op, local, localOpMetadata) => {
512
+ process: (msg, op, local, localOpMetadata) => {
478
513
  const parentSubdir = this.getWorkingDirectory(op.path);
479
- if (parentSubdir) {
480
- parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
514
+ // If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
515
+ // as we are going to delete this subDirectory.
516
+ if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
517
+ parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
481
518
  }
482
519
  },
483
520
  submit: (op, localOpMetadata) => {
@@ -495,10 +532,12 @@ export class SharedDirectory extends SharedObject {
495
532
  },
496
533
  });
497
534
  this.messageHandlers.set("deleteSubDirectory", {
498
- process: (op, local, localOpMetadata) => {
535
+ process: (msg, op, local, localOpMetadata) => {
499
536
  const parentSubdir = this.getWorkingDirectory(op.path);
500
- if (parentSubdir) {
501
- parentSubdir.processDeleteSubDirectoryMessage(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.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
502
541
  }
503
542
  },
504
543
  submit: (op, localOpMetadata) => {
@@ -538,6 +577,7 @@ export class SharedDirectory extends SharedObject {
538
577
  while (stack.length > 0) {
539
578
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
540
579
  const [currentSubDir, currentSubDirObject] = stack.pop();
580
+ currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
541
581
  for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
542
582
  if (!currentSubDirObject.storage) {
543
583
  currentSubDirObject.storage = {};
@@ -598,8 +638,7 @@ function isClearLocalOpMetadata(metadata) {
598
638
  function isSubDirLocalOpMetadata(metadata) {
599
639
  return (metadata !== undefined &&
600
640
  typeof metadata.pendingMessageId === "number" &&
601
- ((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
602
- metadata.type === "deleteSubDir"));
641
+ (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
603
642
  }
604
643
  function isDirectoryLocalOpMetadata(metadata) {
605
644
  return (metadata !== undefined &&
@@ -607,7 +646,7 @@ function isDirectoryLocalOpMetadata(metadata) {
607
646
  (metadata.type === "edit" ||
608
647
  metadata.type === "deleteSubDir" ||
609
648
  (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
610
- (metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean")));
649
+ metadata.type === "createSubDir"));
611
650
  }
612
651
  /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
613
652
  /**
@@ -617,13 +656,17 @@ function isDirectoryLocalOpMetadata(metadata) {
617
656
  class SubDirectory extends TypedEventEmitter {
618
657
  /**
619
658
  * Constructor.
659
+ * @param sequenceNumber - Message seq number at which this was created.
660
+ * @param clientIds - Ids of client which created this directory.
620
661
  * @param directory - Reference back to the SharedDirectory to perform operations
621
662
  * @param runtime - The data store runtime this directory is associated with
622
663
  * @param serializer - The serializer to serialize / parse handles
623
664
  * @param absolutePath - The absolute path of this IDirectory
624
665
  */
625
- constructor(directory, runtime, serializer, absolutePath) {
666
+ constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
626
667
  super();
668
+ this.sequenceNumber = sequenceNumber;
669
+ this.clientIds = clientIds;
627
670
  this.directory = directory;
628
671
  this.runtime = runtime;
629
672
  this.serializer = serializer;
@@ -649,9 +692,14 @@ class SubDirectory extends TypedEventEmitter {
649
692
  */
650
693
  this.pendingKeys = new Map();
651
694
  /**
652
- * Subdirectories that have been modified locally but not yet ack'd from the server.
695
+ * Subdirectories that have been created/deleted locally but not yet ack'd from the server.
653
696
  */
654
697
  this.pendingSubDirectories = new Map();
698
+ /**
699
+ * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
700
+ * of delete op that are pending or yet to be acked from server.
701
+ */
702
+ this.pendingDeleteSubDirectoriesCount = new Map();
655
703
  /**
656
704
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
657
705
  */
@@ -734,6 +782,7 @@ class SubDirectory extends TypedEventEmitter {
734
782
  * {@inheritDoc IDirectory.createSubDirectory}
735
783
  */
736
784
  createSubDirectory(subdirName) {
785
+ var _c;
737
786
  this.throwIfDisposed();
738
787
  // Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
739
788
  if (subdirName === undefined || subdirName === null) {
@@ -743,19 +792,22 @@ class SubDirectory extends TypedEventEmitter {
743
792
  throw new Error(`SubDirectory name may not contain ${posix.sep}`);
744
793
  }
745
794
  // Create the sub directory locally first.
746
- const isNew = this.createSubDirectoryCore(subdirName, true);
747
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
795
+ const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
748
796
  const subDir = this._subdirectories.get(subdirName);
797
+ assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
749
798
  // If we are not attached, don't submit the op.
750
799
  if (!this.directory.isAttached()) {
751
800
  return subDir;
752
801
  }
753
- const op = {
754
- path: this.absolutePath,
755
- subdirName,
756
- type: "createSubDirectory",
757
- };
758
- this.submitCreateSubDirectoryMessage(op, !isNew);
802
+ // Only submit the op, if it is newly created.
803
+ if (isNew) {
804
+ const op = {
805
+ path: this.absolutePath,
806
+ subdirName,
807
+ type: "createSubDirectory",
808
+ };
809
+ this.submitCreateSubDirectoryMessage(op);
810
+ }
759
811
  return subDir;
760
812
  }
761
813
  /**
@@ -783,12 +835,15 @@ class SubDirectory extends TypedEventEmitter {
783
835
  if (!this.directory.isAttached()) {
784
836
  return subDir !== undefined;
785
837
  }
786
- const op = {
787
- path: this.absolutePath,
788
- subdirName,
789
- type: "deleteSubDirectory",
790
- };
791
- this.submitDeleteSubDirectoryMessage(op, subDir);
838
+ // Only submit the op, if the directory existed and we deleted it.
839
+ if (subDir !== undefined) {
840
+ const op = {
841
+ path: this.absolutePath,
842
+ subdirName,
843
+ type: "deleteSubDirectory",
844
+ };
845
+ this.submitDeleteSubDirectoryMessage(op, subDir);
846
+ }
792
847
  return subDir !== undefined;
793
848
  }
794
849
  /**
@@ -805,6 +860,18 @@ class SubDirectory extends TypedEventEmitter {
805
860
  this.throwIfDisposed();
806
861
  return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
807
862
  }
863
+ /**
864
+ * This checks if there is pending delete op for local delete for a given child subdirectory.
865
+ * @param subDirName - directory name.
866
+ * @returns - true if there is pending delete.
867
+ */
868
+ isSubDirectoryDeletePending(subDirName) {
869
+ const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
870
+ if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
871
+ return true;
872
+ }
873
+ return false;
874
+ }
808
875
  /**
809
876
  * Deletes the given key from within this IDirectory.
810
877
  * @param key - The key to delete
@@ -920,14 +987,18 @@ class SubDirectory extends TypedEventEmitter {
920
987
  }
921
988
  /**
922
989
  * Process a clear operation.
990
+ * @param msg - The message from the server to apply.
923
991
  * @param op - The op to process
924
992
  * @param local - Whether the message originated from the local client
925
993
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
926
994
  * For messages from a remote client, this will be undefined.
927
995
  * @internal
928
996
  */
929
- processClearMessage(op, local, localOpMetadata) {
997
+ processClearMessage(msg, op, local, localOpMetadata) {
930
998
  this.throwIfDisposed();
999
+ if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
1000
+ return;
1001
+ }
931
1002
  if (local) {
932
1003
  assert(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
933
1004
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
@@ -956,15 +1027,17 @@ class SubDirectory extends TypedEventEmitter {
956
1027
  }
957
1028
  /**
958
1029
  * Process a delete operation.
1030
+ * @param msg - The message from the server to apply.
959
1031
  * @param op - The op to process
960
1032
  * @param local - Whether the message originated from the local client
961
1033
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
962
1034
  * For messages from a remote client, this will be undefined.
963
1035
  * @internal
964
1036
  */
965
- processDeleteMessage(op, local, localOpMetadata) {
1037
+ processDeleteMessage(msg, op, local, localOpMetadata) {
966
1038
  this.throwIfDisposed();
967
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1039
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1040
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
968
1041
  return;
969
1042
  }
970
1043
  this.deleteCore(op.key, local);
@@ -987,15 +1060,17 @@ class SubDirectory extends TypedEventEmitter {
987
1060
  }
988
1061
  /**
989
1062
  * Process a set operation.
1063
+ * @param msg - The message from the server to apply.
990
1064
  * @param op - The op to process
991
1065
  * @param local - Whether the message originated from the local client
992
1066
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
993
1067
  * For messages from a remote client, this will be undefined.
994
1068
  * @internal
995
1069
  */
996
- processSetMessage(op, context, local, localOpMetadata) {
1070
+ processSetMessage(msg, op, context, local, localOpMetadata) {
997
1071
  this.throwIfDisposed();
998
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1072
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1073
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
999
1074
  return;
1000
1075
  }
1001
1076
  // needProcessStorageOperation should have returned false if local is true
@@ -1023,18 +1098,19 @@ class SubDirectory extends TypedEventEmitter {
1023
1098
  }
1024
1099
  /**
1025
1100
  * Process a create subdirectory operation.
1101
+ * @param msg - The message from the server to apply.
1026
1102
  * @param op - The op to process
1027
1103
  * @param local - Whether the message originated from the local client
1028
1104
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1029
1105
  * For messages from a remote client, this will be undefined.
1030
1106
  * @internal
1031
1107
  */
1032
- processCreateSubDirectoryMessage(op, local, localOpMetadata) {
1108
+ processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1033
1109
  this.throwIfDisposed();
1034
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1110
+ if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1035
1111
  return;
1036
1112
  }
1037
- this.createSubDirectoryCore(op.subdirName, local);
1113
+ this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1038
1114
  }
1039
1115
  /**
1040
1116
  * Apply createSubDirectory operation locally and generate metadata
@@ -1042,28 +1118,30 @@ class SubDirectory extends TypedEventEmitter {
1042
1118
  * @returns metadata generated for stahed op
1043
1119
  */
1044
1120
  applyStashedCreateSubDirMessage(op) {
1121
+ var _c;
1045
1122
  this.throwIfDisposed();
1046
1123
  // Create the sub directory locally first.
1047
- const isNew = this.createSubDirectoryCore(op.subdirName, true);
1124
+ this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1048
1125
  const newMessageId = this.getSubDirMessageId(op);
1049
1126
  const localOpMetadata = {
1050
1127
  type: "createSubDir",
1051
1128
  pendingMessageId: newMessageId,
1052
- previouslyExisted: !isNew,
1053
1129
  };
1054
1130
  return localOpMetadata;
1055
1131
  }
1056
1132
  /**
1057
1133
  * Process a delete subdirectory operation.
1134
+ * @param msg - The message from the server to apply.
1058
1135
  * @param op - The op to process
1059
1136
  * @param local - Whether the message originated from the local client
1060
1137
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1061
1138
  * For messages from a remote client, this will be undefined.
1062
1139
  * @internal
1063
1140
  */
1064
- processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
1141
+ processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
1065
1142
  this.throwIfDisposed();
1066
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1143
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1144
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1067
1145
  return;
1068
1146
  }
1069
1147
  this.deleteSubDirectoryCore(op.subdirName, local);
@@ -1159,6 +1237,7 @@ class SubDirectory extends TypedEventEmitter {
1159
1237
  * Get a new pending message id for the op and cache it to track the pending op
1160
1238
  */
1161
1239
  getSubDirMessageId(op) {
1240
+ var _c;
1162
1241
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1163
1242
  const newMessageId = ++this.pendingMessageId;
1164
1243
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
@@ -1168,20 +1247,22 @@ class SubDirectory extends TypedEventEmitter {
1168
1247
  else {
1169
1248
  this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1170
1249
  }
1250
+ if (op.type === "deleteSubDirectory") {
1251
+ const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
1252
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
1253
+ }
1171
1254
  return newMessageId;
1172
1255
  }
1173
1256
  /**
1174
1257
  * Submit a create subdirectory operation.
1175
1258
  * @param op - The operation
1176
- * @param prevExisted - Whether the subdirectory existed before the op
1177
1259
  */
1178
- submitCreateSubDirectoryMessage(op, prevExisted) {
1260
+ submitCreateSubDirectoryMessage(op) {
1179
1261
  this.throwIfDisposed();
1180
1262
  const newMessageId = this.getSubDirMessageId(op);
1181
1263
  const localOpMetadata = {
1182
1264
  type: "createSubDir",
1183
1265
  pendingMessageId: newMessageId,
1184
- previouslyExisted: prevExisted,
1185
1266
  };
1186
1267
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1187
1268
  }
@@ -1217,7 +1298,7 @@ class SubDirectory extends TypedEventEmitter {
1217
1298
  this.pendingSubDirectories.delete(op.subdirName);
1218
1299
  }
1219
1300
  if (localOpMetadata.type === "createSubDir") {
1220
- this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
1301
+ this.submitCreateSubDirectoryMessage(op);
1221
1302
  }
1222
1303
  else {
1223
1304
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
@@ -1237,6 +1318,14 @@ class SubDirectory extends TypedEventEmitter {
1237
1318
  yield res;
1238
1319
  }
1239
1320
  }
1321
+ getSerializableCreateInfo() {
1322
+ this.throwIfDisposed();
1323
+ const createInfo = {
1324
+ csn: this.sequenceNumber,
1325
+ ccIds: Array.from(this.clientIds),
1326
+ };
1327
+ return createInfo;
1328
+ }
1240
1329
  /**
1241
1330
  * Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
1242
1331
  * @param key - The key to populate
@@ -1314,9 +1403,7 @@ class SubDirectory extends TypedEventEmitter {
1314
1403
  this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
1315
1404
  }
1316
1405
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1317
- if (!localOpMetadata.previouslyExisted) {
1318
- this.deleteSubDirectoryCore(op.subdirName, true);
1319
- }
1406
+ this.deleteSubDirectoryCore(op.subdirName, true);
1320
1407
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1321
1408
  }
1322
1409
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
@@ -1327,6 +1414,12 @@ class SubDirectory extends TypedEventEmitter {
1327
1414
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1328
1415
  }
1329
1416
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1417
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1418
+ assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
1419
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1420
+ if (count === 1) {
1421
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1422
+ }
1330
1423
  }
1331
1424
  else {
1332
1425
  throw new Error("Unsupported op for rollback");
@@ -1356,6 +1449,22 @@ class SubDirectory extends TypedEventEmitter {
1356
1449
  assert(localOpMetadata !== undefined &&
1357
1450
  isKeyEditLocalOpMetadata(localOpMetadata) &&
1358
1451
  localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
1452
+ // Remove all pendingMessageIds lower than first pendingClearMessageId.
1453
+ const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
1454
+ const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
1455
+ if (pendingKeyMessageIdArray !== undefined) {
1456
+ let index = 0;
1457
+ while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
1458
+ index += 1;
1459
+ }
1460
+ const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
1461
+ if (newPendingKeyMessageId.length === 0) {
1462
+ this.pendingKeys.delete(op.key);
1463
+ }
1464
+ else {
1465
+ this.pendingKeys.set(op.key, newPendingKeyMessageId);
1466
+ }
1467
+ }
1359
1468
  }
1360
1469
  // If I have a NACK clear, we can ignore all ops.
1361
1470
  return false;
@@ -1379,6 +1488,19 @@ class SubDirectory extends TypedEventEmitter {
1379
1488
  // If we don't have a NACK op on the key, we need to process the remote ops.
1380
1489
  return !local;
1381
1490
  }
1491
+ /**
1492
+ * This return true if the message is for the current instance of this sub directory. As the sub directory
1493
+ * can be deleted and created again, then this finds if the message is for current instance of directory or not.
1494
+ * @param msg - message for the directory
1495
+ */
1496
+ isMessageForCurrentInstanceOfSubDirectory(msg) {
1497
+ // If the message is either from the creator of directory or this directory was created when
1498
+ // container was detached or in case this directory is already live(known to other clients)
1499
+ // and the op was created after the directory was created then apply this op.
1500
+ return (this.clientIds.has(msg.clientId) ||
1501
+ this.clientIds.has("detached") ||
1502
+ (this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
1503
+ }
1382
1504
  /**
1383
1505
  * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
1384
1506
  * not process the incoming operation.
@@ -1389,7 +1511,7 @@ class SubDirectory extends TypedEventEmitter {
1389
1511
  * For messages from a remote client, this will be undefined.
1390
1512
  * @returns True if the operation should be processed, false otherwise
1391
1513
  */
1392
- needProcessSubDirectoryOperation(op, local, localOpMetadata) {
1514
+ needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1393
1515
  const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1394
1516
  if (pendingSubDirectoryMessageId !== undefined) {
1395
1517
  if (local) {
@@ -1401,6 +1523,40 @@ class SubDirectory extends TypedEventEmitter {
1401
1523
  if (pendingMessageIds.length === 0) {
1402
1524
  this.pendingSubDirectories.delete(op.subdirName);
1403
1525
  }
1526
+ if (op.type === "deleteSubDirectory") {
1527
+ const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1528
+ assert(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
1529
+ this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1530
+ if (count === 1) {
1531
+ this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1532
+ }
1533
+ }
1534
+ }
1535
+ else if (op.type === "deleteSubDirectory") {
1536
+ // If this is remote delete op and we have keys in this subDirectory, then we need to delete these
1537
+ // keys except the pending ones as they will be sequenced after this delete.
1538
+ const subDirectory = this._subdirectories.get(op.subdirName);
1539
+ if (subDirectory) {
1540
+ subDirectory.clearExceptPendingKeys(local);
1541
+ // In case of remote delete op, we need to reset the creation seq number and client ids of
1542
+ // creators as the previous directory is getting deleted and we will initialize again when
1543
+ // we will receive op for the create again.
1544
+ subDirectory.sequenceNumber = -1;
1545
+ subDirectory.clientIds.clear();
1546
+ }
1547
+ }
1548
+ if (op.type === "createSubDirectory") {
1549
+ const dir = this._subdirectories.get(op.subdirName);
1550
+ if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
1551
+ // Only set the seq on the first message, could be more
1552
+ dir.sequenceNumber = msg.sequenceNumber;
1553
+ }
1554
+ // The client created the dir at or after the dirs seq, so list its client id as a creator.
1555
+ if (dir !== undefined &&
1556
+ !dir.clientIds.has(msg.clientId) &&
1557
+ dir.sequenceNumber <= msg.sequenceNumber) {
1558
+ dir.clientIds.add(msg.clientId);
1559
+ }
1404
1560
  }
1405
1561
  return false;
1406
1562
  }
@@ -1414,8 +1570,11 @@ class SubDirectory extends TypedEventEmitter {
1414
1570
  // we will get the value for the pendingKeys and clear the map
1415
1571
  const temp = new Map();
1416
1572
  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));
1573
+ const value = this._storage.get(key);
1574
+ // If this key is already deleted, then we don't need to add it again.
1575
+ if (value !== undefined) {
1576
+ temp.set(key, value);
1577
+ }
1419
1578
  }
1420
1579
  this.clearCore(local);
1421
1580
  for (const [key, value] of temp.entries()) {
@@ -1469,17 +1628,23 @@ class SubDirectory extends TypedEventEmitter {
1469
1628
  * Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
1470
1629
  * @param subdirName - The name of the subdirectory being created
1471
1630
  * @param local - Whether the message originated from the local client
1631
+ * @param seq - Sequence number at which this directory is created
1632
+ * @param clientId - Id of client which created this directory.
1472
1633
  * @returns - True if is newly created, false if it already existed.
1473
1634
  */
1474
- createSubDirectoryCore(subdirName, local) {
1475
- if (!this._subdirectories.has(subdirName)) {
1635
+ createSubDirectoryCore(subdirName, local, seq, clientId) {
1636
+ const subdir = this._subdirectories.get(subdirName);
1637
+ if (subdir === undefined) {
1476
1638
  const absolutePath = posix.join(this.absolutePath, subdirName);
1477
- const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
1639
+ const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
1478
1640
  this.registerEventsOnSubDirectory(subDir, subdirName);
1479
1641
  this._subdirectories.set(subdirName, subDir);
1480
1642
  this.emit("subDirectoryCreated", subdirName, local, this);
1481
1643
  return true;
1482
1644
  }
1645
+ else {
1646
+ subdir.clientIds.add(clientId);
1647
+ }
1483
1648
  return false;
1484
1649
  }
1485
1650
  registerEventsOnSubDirectory(subDirectory, subDirName) {