@fluidframework/map 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229

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.
Files changed (72) hide show
  1. package/.eslintrc.js +12 -14
  2. package/.mocharc.js +2 -2
  3. package/README.md +3 -3
  4. package/api-extractor.json +2 -2
  5. package/dist/directory.d.ts +38 -5
  6. package/dist/directory.d.ts.map +1 -1
  7. package/dist/directory.js +285 -88
  8. package/dist/directory.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/interfaces.d.ts +27 -17
  13. package/dist/interfaces.d.ts.map +1 -1
  14. package/dist/interfaces.js.map +1 -1
  15. package/dist/internalInterfaces.d.ts +39 -0
  16. package/dist/internalInterfaces.d.ts.map +1 -1
  17. package/dist/internalInterfaces.js.map +1 -1
  18. package/dist/localValues.d.ts +12 -3
  19. package/dist/localValues.d.ts.map +1 -1
  20. package/dist/localValues.js +10 -0
  21. package/dist/localValues.js.map +1 -1
  22. package/dist/map.d.ts +5 -5
  23. package/dist/map.d.ts.map +1 -1
  24. package/dist/map.js +15 -2
  25. package/dist/map.js.map +1 -1
  26. package/dist/mapKernel.d.ts +5 -5
  27. package/dist/mapKernel.d.ts.map +1 -1
  28. package/dist/mapKernel.js +58 -33
  29. package/dist/mapKernel.js.map +1 -1
  30. package/dist/packageVersion.d.ts +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/lib/directory.d.ts +38 -5
  34. package/lib/directory.d.ts.map +1 -1
  35. package/lib/directory.js +287 -90
  36. package/lib/directory.js.map +1 -1
  37. package/lib/index.d.ts +1 -1
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js.map +1 -1
  40. package/lib/interfaces.d.ts +27 -17
  41. package/lib/interfaces.d.ts.map +1 -1
  42. package/lib/interfaces.js.map +1 -1
  43. package/lib/internalInterfaces.d.ts +39 -0
  44. package/lib/internalInterfaces.d.ts.map +1 -1
  45. package/lib/internalInterfaces.js.map +1 -1
  46. package/lib/localValues.d.ts +12 -3
  47. package/lib/localValues.d.ts.map +1 -1
  48. package/lib/localValues.js +10 -0
  49. package/lib/localValues.js.map +1 -1
  50. package/lib/map.d.ts +5 -5
  51. package/lib/map.d.ts.map +1 -1
  52. package/lib/map.js +16 -3
  53. package/lib/map.js.map +1 -1
  54. package/lib/mapKernel.d.ts +5 -5
  55. package/lib/mapKernel.d.ts.map +1 -1
  56. package/lib/mapKernel.js +59 -34
  57. package/lib/mapKernel.js.map +1 -1
  58. package/lib/packageVersion.d.ts +1 -1
  59. package/lib/packageVersion.js +1 -1
  60. package/lib/packageVersion.js.map +1 -1
  61. package/package.json +60 -59
  62. package/prettier.config.cjs +1 -1
  63. package/src/directory.ts +2207 -1848
  64. package/src/index.ts +1 -0
  65. package/src/interfaces.ts +309 -288
  66. package/src/internalInterfaces.ts +83 -38
  67. package/src/localValues.ts +95 -93
  68. package/src/map.ts +364 -345
  69. package/src/mapKernel.ts +729 -676
  70. package/src/packageVersion.ts +1 -1
  71. package/tsconfig.esnext.json +5 -5
  72. package/tsconfig.json +9 -15
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
  */
@@ -160,6 +160,8 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
160
160
  /**
161
161
  * {@inheritDoc IDirectory.get}
162
162
  */
163
+ // TODO: Use `unknown` instead (breaking change).
164
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
165
  get(key) {
164
166
  return this.root.get(key);
165
167
  }
@@ -208,13 +210,18 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
208
210
  * Issue a callback on each entry under this IDirectory.
209
211
  * @param callback - Callback to issue
210
212
  */
213
+ // TODO: Use `unknown` instead (breaking change).
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
215
  forEach(callback) {
216
+ // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference
212
217
  this.root.forEach(callback);
213
218
  }
214
219
  /**
215
220
  * Get an iterator over the entries under this IDirectory.
216
221
  * @returns The iterator
217
222
  */
223
+ // TODO: Use `unknown` instead (breaking change).
224
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
218
225
  [(_a = Symbol.toStringTag, Symbol.iterator)]() {
219
226
  return this.root[Symbol.iterator]();
220
227
  }
@@ -222,6 +229,8 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
222
229
  * Get an iterator over the entries under this IDirectory.
223
230
  * @returns The iterator
224
231
  */
232
+ // TODO: Use `unknown` instead (breaking change).
233
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
234
  entries() {
226
235
  return this.root.entries();
227
236
  }
@@ -242,6 +251,8 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
242
251
  * Get an iterator over the values under this IDirectory.
243
252
  * @returns The iterator
244
253
  */
254
+ // TODO: Use `unknown` instead (breaking change).
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
256
  values() {
246
257
  return this.root.values();
247
258
  }
@@ -284,7 +295,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
284
295
  return this.root;
285
296
  }
286
297
  let currentSubDir = this.root;
287
- const subdirs = absolutePath.substr(1).split(posix.sep);
298
+ const subdirs = absolutePath.slice(1).split(posix.sep);
288
299
  for (const subdir of subdirs) {
289
300
  currentSubDir = currentSubDir.getSubDirectory(subdir);
290
301
  if (!currentSubDir) {
@@ -360,7 +371,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
360
371
  for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
361
372
  let newSubDir = currentSubDir.getSubDirectory(subdirName);
362
373
  if (!newSubDir) {
363
- 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));
364
378
  currentSubDir.populateSubDirectory(subdirName, newSubDir);
365
379
  }
366
380
  stack.push([newSubDir, subdirObject]);
@@ -383,13 +397,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
383
397
  const op = message.contents;
384
398
  const handler = this.messageHandlers.get(op.type);
385
399
  (0, common_utils_1.assert)(handler !== undefined, 0x00e /* Missing message handler for message type */);
386
- handler.process(op, local, localOpMetadata);
400
+ handler.process(message, op, local, localOpMetadata);
387
401
  }
388
402
  }
389
403
  /**
390
404
  * {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
391
405
  * @internal
392
- */
406
+ */
393
407
  rollback(content, localOpMetadata) {
394
408
  const op = content;
395
409
  const subdir = this.getWorkingDirectory(op.path);
@@ -415,18 +429,45 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
415
429
  * @returns The local value that was produced
416
430
  */
417
431
  makeLocal(key, absolutePath, serializable) {
418
- (0, common_utils_1.assert)(serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Plain] || serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
432
+ (0, common_utils_1.assert)(serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Plain] ||
433
+ serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
419
434
  return this.localValueMaker.fromSerializable(serializable);
420
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
+ }
421
460
  /**
422
461
  * Set the message handlers for the directory.
423
462
  */
424
463
  setMessageHandlers() {
425
464
  this.messageHandlers.set("clear", {
426
- process: (op, local, localOpMetadata) => {
465
+ process: (msg, op, local, localOpMetadata) => {
427
466
  const subdir = this.getWorkingDirectory(op.path);
428
- if (subdir) {
429
- 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);
430
471
  }
431
472
  },
432
473
  submit: (op, localOpMetadata) => {
@@ -443,10 +484,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
443
484
  },
444
485
  });
445
486
  this.messageHandlers.set("delete", {
446
- process: (op, local, localOpMetadata) => {
487
+ process: (msg, op, local, localOpMetadata) => {
447
488
  const subdir = this.getWorkingDirectory(op.path);
448
- if (subdir) {
449
- 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);
450
493
  }
451
494
  },
452
495
  submit: (op, localOpMetadata) => {
@@ -463,11 +506,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
463
506
  },
464
507
  });
465
508
  this.messageHandlers.set("set", {
466
- process: (op, local, localOpMetadata) => {
509
+ process: (msg, op, local, localOpMetadata) => {
467
510
  const subdir = this.getWorkingDirectory(op.path);
468
- 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)) {
469
514
  const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
470
- subdir.processSetMessage(op, context, local, localOpMetadata);
515
+ subdir.processSetMessage(msg, op, context, local, localOpMetadata);
471
516
  }
472
517
  },
473
518
  submit: (op, localOpMetadata) => {
@@ -485,10 +530,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
485
530
  },
486
531
  });
487
532
  this.messageHandlers.set("createSubDirectory", {
488
- process: (op, local, localOpMetadata) => {
533
+ process: (msg, op, local, localOpMetadata) => {
489
534
  const parentSubdir = this.getWorkingDirectory(op.path);
490
535
  if (parentSubdir) {
491
- parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
536
+ parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
492
537
  }
493
538
  },
494
539
  submit: (op, localOpMetadata) => {
@@ -498,7 +543,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
498
543
  parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
499
544
  }
500
545
  },
501
- // eslint-disable-next-line max-len
502
546
  applyStashedOp: (op) => {
503
547
  const parentSubdir = this.getWorkingDirectory(op.path);
504
548
  if (parentSubdir) {
@@ -507,10 +551,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
507
551
  },
508
552
  });
509
553
  this.messageHandlers.set("deleteSubDirectory", {
510
- process: (op, local, localOpMetadata) => {
554
+ process: (msg, op, local, localOpMetadata) => {
511
555
  const parentSubdir = this.getWorkingDirectory(op.path);
512
556
  if (parentSubdir) {
513
- parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
557
+ parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
514
558
  }
515
559
  },
516
560
  submit: (op, localOpMetadata) => {
@@ -520,7 +564,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
520
564
  parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
521
565
  }
522
566
  },
523
- // eslint-disable-next-line max-len
524
567
  applyStashedOp: (op) => {
525
568
  const parentSubdir = this.getWorkingDirectory(op.path);
526
569
  if (parentSubdir) {
@@ -530,6 +573,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
530
573
  });
531
574
  }
532
575
  /**
576
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
533
577
  * @internal
534
578
  */
535
579
  applyStashedOp(op) {
@@ -550,20 +594,20 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
550
594
  while (stack.length > 0) {
551
595
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
552
596
  const [currentSubDir, currentSubDirObject] = stack.pop();
597
+ currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
553
598
  for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
554
599
  if (!currentSubDirObject.storage) {
555
600
  currentSubDirObject.storage = {};
556
601
  }
557
602
  const result = {
558
603
  type: value.type,
559
- // eslint-disable-next-line @typescript-eslint/ban-types
560
604
  value: value.value && JSON.parse(value.value),
561
605
  };
562
606
  if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
563
607
  const extraContent = {};
564
608
  let largeContent = extraContent;
565
609
  if (currentSubDir.absolutePath !== posix.sep) {
566
- for (const dir of currentSubDir.absolutePath.substr(1).split(posix.sep)) {
610
+ for (const dir of currentSubDir.absolutePath.slice(1).split(posix.sep)) {
567
611
  const subDataObject = {};
568
612
  largeContent.subdirectories = { [dir]: subDataObject };
569
613
  largeContent = subDataObject;
@@ -597,24 +641,32 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
597
641
  }
598
642
  }
599
643
  exports.SharedDirectory = SharedDirectory;
644
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
600
645
  function isKeyEditLocalOpMetadata(metadata) {
601
- return metadata !== undefined && typeof metadata.pendingMessageId === "number" && metadata.type === "edit";
646
+ return (metadata !== undefined &&
647
+ typeof metadata.pendingMessageId === "number" &&
648
+ metadata.type === "edit");
602
649
  }
603
650
  function isClearLocalOpMetadata(metadata) {
604
- return metadata !== undefined && metadata.type === "clear" && typeof metadata.pendingMessageId === "number" &&
605
- typeof metadata.previousStorage === "object";
651
+ return (metadata !== undefined &&
652
+ metadata.type === "clear" &&
653
+ typeof metadata.pendingMessageId === "number" &&
654
+ typeof metadata.previousStorage === "object");
606
655
  }
607
656
  function isSubDirLocalOpMetadata(metadata) {
608
- return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
609
- ((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
610
- metadata.type === "deleteSubDir");
657
+ return (metadata !== undefined &&
658
+ typeof metadata.pendingMessageId === "number" &&
659
+ (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
611
660
  }
612
661
  function isDirectoryLocalOpMetadata(metadata) {
613
- return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
614
- (metadata.type === "edit" || metadata.type === "deleteSubDir" ||
662
+ return (metadata !== undefined &&
663
+ typeof metadata.pendingMessageId === "number" &&
664
+ (metadata.type === "edit" ||
665
+ metadata.type === "deleteSubDir" ||
615
666
  (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
616
- (metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean"));
667
+ metadata.type === "createSubDir"));
617
668
  }
669
+ /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
618
670
  /**
619
671
  * Node of the directory tree.
620
672
  * @sealed
@@ -622,13 +674,17 @@ function isDirectoryLocalOpMetadata(metadata) {
622
674
  class SubDirectory extends common_utils_1.TypedEventEmitter {
623
675
  /**
624
676
  * Constructor.
677
+ * @param sequenceNumber - Message seq number at which this was created.
678
+ * @param clientIds - Ids of client which created this directory.
625
679
  * @param directory - Reference back to the SharedDirectory to perform operations
626
680
  * @param runtime - The data store runtime this directory is associated with
627
681
  * @param serializer - The serializer to serialize / parse handles
628
682
  * @param absolutePath - The absolute path of this IDirectory
629
683
  */
630
- constructor(directory, runtime, serializer, absolutePath) {
684
+ constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
631
685
  super();
686
+ this.sequenceNumber = sequenceNumber;
687
+ this.clientIds = clientIds;
632
688
  this.directory = directory;
633
689
  this.runtime = runtime;
634
690
  this.serializer = serializer;
@@ -654,9 +710,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
654
710
  */
655
711
  this.pendingKeys = new Map();
656
712
  /**
657
- * 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.
658
714
  */
659
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();
660
721
  /**
661
722
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
662
723
  */
@@ -671,10 +732,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
671
732
  this.emit("disposed", this);
672
733
  }
673
734
  /**
674
- * Unmark the deleted property when rolling back delete.
735
+ * Unmark the deleted property only when rolling back delete.
675
736
  */
676
737
  undispose() {
677
738
  this._deleted = false;
739
+ this.emit("undisposed", this);
678
740
  }
679
741
  get disposed() {
680
742
  return this._deleted;
@@ -738,6 +800,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
738
800
  * {@inheritDoc IDirectory.createSubDirectory}
739
801
  */
740
802
  createSubDirectory(subdirName) {
803
+ var _c;
741
804
  this.throwIfDisposed();
742
805
  // Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
743
806
  if (subdirName === undefined || subdirName === null) {
@@ -747,19 +810,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
747
810
  throw new Error(`SubDirectory name may not contain ${posix.sep}`);
748
811
  }
749
812
  // Create the sub directory locally first.
750
- const isNew = this.createSubDirectoryCore(subdirName, true);
751
- // 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");
752
814
  const subDir = this._subdirectories.get(subdirName);
815
+ (0, common_utils_1.assert)(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
753
816
  // If we are not attached, don't submit the op.
754
817
  if (!this.directory.isAttached()) {
755
818
  return subDir;
756
819
  }
757
- const op = {
758
- path: this.absolutePath,
759
- subdirName,
760
- type: "createSubDirectory",
761
- };
762
- 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
+ }
763
829
  return subDir;
764
830
  }
765
831
  /**
@@ -787,12 +853,15 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
787
853
  if (!this.directory.isAttached()) {
788
854
  return subDir !== undefined;
789
855
  }
790
- const op = {
791
- path: this.absolutePath,
792
- subdirName,
793
- type: "deleteSubDirectory",
794
- };
795
- 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
+ }
796
865
  return subDir !== undefined;
797
866
  }
798
867
  /**
@@ -809,6 +878,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
809
878
  this.throwIfDisposed();
810
879
  return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
811
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
+ }
812
893
  /**
813
894
  * Deletes the given key from within this IDirectory.
814
895
  * @param key - The key to delete
@@ -854,6 +935,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
854
935
  */
855
936
  forEach(callback) {
856
937
  this.throwIfDisposed();
938
+ // eslint-disable-next-line unicorn/no-array-for-each
857
939
  this._storage.forEach((localValue, key, map) => {
858
940
  callback(localValue.value, key, map);
859
941
  });
@@ -923,14 +1005,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
923
1005
  }
924
1006
  /**
925
1007
  * Process a clear operation.
1008
+ * @param msg - The message from the server to apply.
926
1009
  * @param op - The op to process
927
1010
  * @param local - Whether the message originated from the local client
928
1011
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
929
1012
  * For messages from a remote client, this will be undefined.
930
1013
  * @internal
931
1014
  */
932
- processClearMessage(op, local, localOpMetadata) {
1015
+ processClearMessage(msg, op, local, localOpMetadata) {
933
1016
  this.throwIfDisposed();
1017
+ if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
1018
+ return;
1019
+ }
934
1020
  if (local) {
935
1021
  (0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
936
1022
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
@@ -959,15 +1045,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
959
1045
  }
960
1046
  /**
961
1047
  * Process a delete operation.
1048
+ * @param msg - The message from the server to apply.
962
1049
  * @param op - The op to process
963
1050
  * @param local - Whether the message originated from the local client
964
1051
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
965
1052
  * For messages from a remote client, this will be undefined.
966
1053
  * @internal
967
1054
  */
968
- processDeleteMessage(op, local, localOpMetadata) {
1055
+ processDeleteMessage(msg, op, local, localOpMetadata) {
969
1056
  this.throwIfDisposed();
970
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1057
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1058
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
971
1059
  return;
972
1060
  }
973
1061
  this.deleteCore(op.key, local);
@@ -981,20 +1069,26 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
981
1069
  this.throwIfDisposed();
982
1070
  const previousValue = this.deleteCore(op.key, true);
983
1071
  const pendingMessageId = this.getKeyMessageId(op);
984
- const localMetadata = { type: "edit", pendingMessageId, previousValue };
1072
+ const localMetadata = {
1073
+ type: "edit",
1074
+ pendingMessageId,
1075
+ previousValue,
1076
+ };
985
1077
  return localMetadata;
986
1078
  }
987
1079
  /**
988
1080
  * Process a set operation.
1081
+ * @param msg - The message from the server to apply.
989
1082
  * @param op - The op to process
990
1083
  * @param local - Whether the message originated from the local client
991
1084
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
992
1085
  * For messages from a remote client, this will be undefined.
993
1086
  * @internal
994
1087
  */
995
- processSetMessage(op, context, local, localOpMetadata) {
1088
+ processSetMessage(msg, op, context, local, localOpMetadata) {
996
1089
  this.throwIfDisposed();
997
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1090
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1091
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
998
1092
  return;
999
1093
  }
1000
1094
  // needProcessStorageOperation should have returned false if local is true
@@ -1013,23 +1107,28 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1013
1107
  const previousValue = this.setCore(op.key, context, true);
1014
1108
  // Create metadata
1015
1109
  const pendingMessageId = this.getKeyMessageId(op);
1016
- const localMetadata = { type: "edit", pendingMessageId, previousValue };
1110
+ const localMetadata = {
1111
+ type: "edit",
1112
+ pendingMessageId,
1113
+ previousValue,
1114
+ };
1017
1115
  return localMetadata;
1018
1116
  }
1019
1117
  /**
1020
1118
  * Process a create subdirectory operation.
1119
+ * @param msg - The message from the server to apply.
1021
1120
  * @param op - The op to process
1022
1121
  * @param local - Whether the message originated from the local client
1023
1122
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1024
1123
  * For messages from a remote client, this will be undefined.
1025
1124
  * @internal
1026
1125
  */
1027
- processCreateSubDirectoryMessage(op, local, localOpMetadata) {
1126
+ processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1028
1127
  this.throwIfDisposed();
1029
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1128
+ if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1030
1129
  return;
1031
1130
  }
1032
- this.createSubDirectoryCore(op.subdirName, local);
1131
+ this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1033
1132
  }
1034
1133
  /**
1035
1134
  * Apply createSubDirectory operation locally and generate metadata
@@ -1037,28 +1136,30 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1037
1136
  * @returns metadata generated for stahed op
1038
1137
  */
1039
1138
  applyStashedCreateSubDirMessage(op) {
1139
+ var _c;
1040
1140
  this.throwIfDisposed();
1041
1141
  // Create the sub directory locally first.
1042
- const isNew = this.createSubDirectoryCore(op.subdirName, true);
1142
+ this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1043
1143
  const newMessageId = this.getSubDirMessageId(op);
1044
1144
  const localOpMetadata = {
1045
1145
  type: "createSubDir",
1046
1146
  pendingMessageId: newMessageId,
1047
- previouslyExisted: !isNew,
1048
1147
  };
1049
1148
  return localOpMetadata;
1050
1149
  }
1051
1150
  /**
1052
1151
  * Process a delete subdirectory operation.
1152
+ * @param msg - The message from the server to apply.
1053
1153
  * @param op - The op to process
1054
1154
  * @param local - Whether the message originated from the local client
1055
1155
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1056
1156
  * For messages from a remote client, this will be undefined.
1057
1157
  * @internal
1058
1158
  */
1059
- processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
1159
+ processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
1060
1160
  this.throwIfDisposed();
1061
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1161
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1162
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1062
1163
  return;
1063
1164
  }
1064
1165
  this.deleteSubDirectoryCore(op.subdirName, local);
@@ -1142,7 +1243,8 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1142
1243
  (0, common_utils_1.assert)(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
1143
1244
  // clear the old pending message id
1144
1245
  const pendingMessageIds = this.pendingKeys.get(op.key);
1145
- (0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
1246
+ (0, common_utils_1.assert)(pendingMessageIds !== undefined &&
1247
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
1146
1248
  pendingMessageIds.shift();
1147
1249
  if (pendingMessageIds.length === 0) {
1148
1250
  this.pendingKeys.delete(op.key);
@@ -1153,6 +1255,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1153
1255
  * Get a new pending message id for the op and cache it to track the pending op
1154
1256
  */
1155
1257
  getSubDirMessageId(op) {
1258
+ var _c;
1156
1259
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1157
1260
  const newMessageId = ++this.pendingMessageId;
1158
1261
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
@@ -1162,20 +1265,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1162
1265
  else {
1163
1266
  this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1164
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
+ }
1165
1272
  return newMessageId;
1166
1273
  }
1167
1274
  /**
1168
1275
  * Submit a create subdirectory operation.
1169
1276
  * @param op - The operation
1170
- * @param prevExisted - Whether the subdirectory existed before the op
1171
1277
  */
1172
- submitCreateSubDirectoryMessage(op, prevExisted) {
1278
+ submitCreateSubDirectoryMessage(op) {
1173
1279
  this.throwIfDisposed();
1174
1280
  const newMessageId = this.getSubDirMessageId(op);
1175
1281
  const localOpMetadata = {
1176
1282
  type: "createSubDir",
1177
1283
  pendingMessageId: newMessageId,
1178
- previouslyExisted: prevExisted,
1179
1284
  };
1180
1285
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1181
1286
  }
@@ -1204,13 +1309,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1204
1309
  (0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
1205
1310
  // clear the old pending message id
1206
1311
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1207
- (0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
1312
+ (0, common_utils_1.assert)(pendingMessageIds !== undefined &&
1313
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
1208
1314
  pendingMessageIds.shift();
1209
1315
  if (pendingMessageIds.length === 0) {
1210
1316
  this.pendingSubDirectories.delete(op.subdirName);
1211
1317
  }
1212
1318
  if (localOpMetadata.type === "createSubDir") {
1213
- this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
1319
+ this.submitCreateSubDirectoryMessage(op);
1214
1320
  }
1215
1321
  else {
1216
1322
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
@@ -1230,6 +1336,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1230
1336
  yield res;
1231
1337
  }
1232
1338
  }
1339
+ getSerializableCreateInfo() {
1340
+ this.throwIfDisposed();
1341
+ const createInfo = {
1342
+ csn: this.sequenceNumber,
1343
+ ccIds: Array.from(this.clientIds),
1344
+ };
1345
+ return createInfo;
1346
+ }
1233
1347
  /**
1234
1348
  * Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
1235
1349
  * @param key - The key to populate
@@ -1276,21 +1390,24 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1276
1390
  map.delete(key);
1277
1391
  }
1278
1392
  }
1393
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1279
1394
  /**
1280
1395
  * Rollback a local op
1281
1396
  * @param op - The operation to rollback
1282
1397
  * @param localOpMetadata - The local metadata associated with the op.
1283
1398
  */
1399
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1284
1400
  rollback(op, localOpMetadata) {
1285
1401
  if (!isDirectoryLocalOpMetadata(localOpMetadata)) {
1286
1402
  throw new Error("Invalid localOpMetadata");
1287
1403
  }
1288
1404
  if (op.type === "clear" && localOpMetadata.type === "clear") {
1289
- localOpMetadata.previousStorage.forEach((localValue, key) => {
1405
+ for (const [key, localValue] of localOpMetadata.previousStorage.entries()) {
1290
1406
  this.setCore(key, localValue, true);
1291
- });
1407
+ }
1292
1408
  const lastPendingClearId = this.pendingClearMessageIds.pop();
1293
- if (lastPendingClearId === undefined || lastPendingClearId !== localOpMetadata.pendingMessageId) {
1409
+ if (lastPendingClearId === undefined ||
1410
+ lastPendingClearId !== localOpMetadata.pendingMessageId) {
1294
1411
  throw new Error("Rollback op does match last clear");
1295
1412
  }
1296
1413
  }
@@ -1304,9 +1421,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1304
1421
  this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
1305
1422
  }
1306
1423
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1307
- if (!localOpMetadata.previouslyExisted) {
1308
- this.deleteSubDirectoryCore(op.subdirName, true);
1309
- }
1424
+ this.deleteSubDirectoryCore(op.subdirName, true);
1310
1425
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1311
1426
  }
1312
1427
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
@@ -1317,11 +1432,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1317
1432
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1318
1433
  }
1319
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
+ }
1320
1441
  }
1321
1442
  else {
1322
1443
  throw new Error("Unsupported op for rollback");
1323
1444
  }
1324
1445
  }
1446
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
1325
1447
  /**
1326
1448
  * Converts the given relative path into an absolute path.
1327
1449
  * @param path - Relative path to convert
@@ -1342,8 +1464,25 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1342
1464
  needProcessStorageOperation(op, local, localOpMetadata) {
1343
1465
  if (this.pendingClearMessageIds.length > 0) {
1344
1466
  if (local) {
1345
- (0, common_utils_1.assert)(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata) &&
1467
+ (0, common_utils_1.assert)(localOpMetadata !== undefined &&
1468
+ isKeyEditLocalOpMetadata(localOpMetadata) &&
1346
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
+ }
1347
1486
  }
1348
1487
  // If I have a NACK clear, we can ignore all ops.
1349
1488
  return false;
@@ -1355,7 +1494,8 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1355
1494
  if (local) {
1356
1495
  (0, common_utils_1.assert)(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata), 0x011 /* pendingMessageId is missing from the local client's operation */);
1357
1496
  const pendingMessageIds = this.pendingKeys.get(op.key);
1358
- (0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
1497
+ (0, common_utils_1.assert)(pendingMessageIds !== undefined &&
1498
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
1359
1499
  pendingMessageIds.shift();
1360
1500
  if (pendingMessageIds.length === 0) {
1361
1501
  this.pendingKeys.delete(op.key);
@@ -1366,6 +1506,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1366
1506
  // If we don't have a NACK op on the key, we need to process the remote ops.
1367
1507
  return !local;
1368
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
+ }
1369
1522
  /**
1370
1523
  * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
1371
1524
  * not process the incoming operation.
@@ -1376,17 +1529,52 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1376
1529
  * For messages from a remote client, this will be undefined.
1377
1530
  * @returns True if the operation should be processed, false otherwise
1378
1531
  */
1379
- needProcessSubDirectoryOperation(op, local, localOpMetadata) {
1532
+ needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1380
1533
  const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1381
1534
  if (pendingSubDirectoryMessageId !== undefined) {
1382
1535
  if (local) {
1383
1536
  (0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
1384
1537
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1385
- (0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
1538
+ (0, common_utils_1.assert)(pendingMessageIds !== undefined &&
1539
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
1386
1540
  pendingMessageIds.shift();
1387
1541
  if (pendingMessageIds.length === 0) {
1388
1542
  this.pendingSubDirectories.delete(op.subdirName);
1389
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
+ }
1390
1578
  }
1391
1579
  return false;
1392
1580
  }
@@ -1399,14 +1587,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1399
1587
  // Assuming the pendingKeys is small and the map is large
1400
1588
  // we will get the value for the pendingKeys and clear the map
1401
1589
  const temp = new Map();
1402
- this.pendingKeys.forEach((value, key, map) => {
1403
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1404
- temp.set(key, this._storage.get(key));
1405
- });
1590
+ for (const [key] of this.pendingKeys) {
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
+ }
1596
+ }
1406
1597
  this.clearCore(local);
1407
- temp.forEach((value, key, map) => {
1598
+ for (const [key, value] of temp.entries()) {
1408
1599
  this.setCore(key, value, true);
1409
- });
1600
+ }
1410
1601
  }
1411
1602
  /**
1412
1603
  * Clear implementation used for both locally sourced clears as well as incoming remote clears.
@@ -1455,17 +1646,23 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
1455
1646
  * Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
1456
1647
  * @param subdirName - The name of the subdirectory being created
1457
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.
1458
1651
  * @returns - True if is newly created, false if it already existed.
1459
1652
  */
1460
- createSubDirectoryCore(subdirName, local) {
1461
- if (!this._subdirectories.has(subdirName)) {
1653
+ createSubDirectoryCore(subdirName, local, seq, clientId) {
1654
+ const subdir = this._subdirectories.get(subdirName);
1655
+ if (subdir === undefined) {
1462
1656
  const absolutePath = posix.join(this.absolutePath, subdirName);
1463
- 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);
1464
1658
  this.registerEventsOnSubDirectory(subDir, subdirName);
1465
1659
  this._subdirectories.set(subdirName, subDir);
1466
1660
  this.emit("subDirectoryCreated", subdirName, local, this);
1467
1661
  return true;
1468
1662
  }
1663
+ else {
1664
+ subdir.clientIds.add(clientId);
1665
+ }
1469
1666
  return false;
1470
1667
  }
1471
1668
  registerEventsOnSubDirectory(subDirectory, subDirName) {