@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/lib/directory.js CHANGED
@@ -6,11 +6,11 @@ var _a, _b;
6
6
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
7
7
  import { UsageError } from "@fluidframework/container-utils";
8
8
  import { readAndParse } from "@fluidframework/driver-utils";
9
- import { MessageType, } from "@fluidframework/protocol-definitions";
9
+ import { MessageType } from "@fluidframework/protocol-definitions";
10
10
  import { SharedObject, ValueType } from "@fluidframework/shared-object-base";
11
11
  import { SummaryTreeBuilder } from "@fluidframework/runtime-utils";
12
12
  import * as path from "path-browserify";
13
- import { LocalValueMaker, makeSerializable, } from "./localValues";
13
+ import { LocalValueMaker, makeSerializable } from "./localValues";
14
14
  import { pkgVersion } from "./packageVersion";
15
15
  // We use path-browserify since this code can run safely on the server or the browser.
16
16
  // We standardize on using posix slashes everywhere.
@@ -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
  */
@@ -137,6 +137,8 @@ export class SharedDirectory extends SharedObject {
137
137
  /**
138
138
  * {@inheritDoc IDirectory.get}
139
139
  */
140
+ // TODO: Use `unknown` instead (breaking change).
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
142
  get(key) {
141
143
  return this.root.get(key);
142
144
  }
@@ -185,13 +187,18 @@ export class SharedDirectory extends SharedObject {
185
187
  * Issue a callback on each entry under this IDirectory.
186
188
  * @param callback - Callback to issue
187
189
  */
190
+ // TODO: Use `unknown` instead (breaking change).
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
192
  forEach(callback) {
193
+ // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference
189
194
  this.root.forEach(callback);
190
195
  }
191
196
  /**
192
197
  * Get an iterator over the entries under this IDirectory.
193
198
  * @returns The iterator
194
199
  */
200
+ // TODO: Use `unknown` instead (breaking change).
201
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
202
  [(_a = Symbol.toStringTag, Symbol.iterator)]() {
196
203
  return this.root[Symbol.iterator]();
197
204
  }
@@ -199,6 +206,8 @@ export class SharedDirectory extends SharedObject {
199
206
  * Get an iterator over the entries under this IDirectory.
200
207
  * @returns The iterator
201
208
  */
209
+ // TODO: Use `unknown` instead (breaking change).
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
211
  entries() {
203
212
  return this.root.entries();
204
213
  }
@@ -219,6 +228,8 @@ export class SharedDirectory extends SharedObject {
219
228
  * Get an iterator over the values under this IDirectory.
220
229
  * @returns The iterator
221
230
  */
231
+ // TODO: Use `unknown` instead (breaking change).
232
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
233
  values() {
223
234
  return this.root.values();
224
235
  }
@@ -261,7 +272,7 @@ export class SharedDirectory extends SharedObject {
261
272
  return this.root;
262
273
  }
263
274
  let currentSubDir = this.root;
264
- const subdirs = absolutePath.substr(1).split(posix.sep);
275
+ const subdirs = absolutePath.slice(1).split(posix.sep);
265
276
  for (const subdir of subdirs) {
266
277
  currentSubDir = currentSubDir.getSubDirectory(subdir);
267
278
  if (!currentSubDir) {
@@ -337,7 +348,10 @@ export class SharedDirectory extends SharedObject {
337
348
  for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
338
349
  let newSubDir = currentSubDir.getSubDirectory(subdirName);
339
350
  if (!newSubDir) {
340
- 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));
341
355
  currentSubDir.populateSubDirectory(subdirName, newSubDir);
342
356
  }
343
357
  stack.push([newSubDir, subdirObject]);
@@ -360,13 +374,13 @@ export class SharedDirectory extends SharedObject {
360
374
  const op = message.contents;
361
375
  const handler = this.messageHandlers.get(op.type);
362
376
  assert(handler !== undefined, 0x00e /* Missing message handler for message type */);
363
- handler.process(op, local, localOpMetadata);
377
+ handler.process(message, op, local, localOpMetadata);
364
378
  }
365
379
  }
366
380
  /**
367
381
  * {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
368
382
  * @internal
369
- */
383
+ */
370
384
  rollback(content, localOpMetadata) {
371
385
  const op = content;
372
386
  const subdir = this.getWorkingDirectory(op.path);
@@ -392,18 +406,45 @@ export class SharedDirectory extends SharedObject {
392
406
  * @returns The local value that was produced
393
407
  */
394
408
  makeLocal(key, absolutePath, serializable) {
395
- assert(serializable.type === ValueType[ValueType.Plain] || serializable.type === ValueType[ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
409
+ assert(serializable.type === ValueType[ValueType.Plain] ||
410
+ serializable.type === ValueType[ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
396
411
  return this.localValueMaker.fromSerializable(serializable);
397
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
+ }
398
437
  /**
399
438
  * Set the message handlers for the directory.
400
439
  */
401
440
  setMessageHandlers() {
402
441
  this.messageHandlers.set("clear", {
403
- process: (op, local, localOpMetadata) => {
442
+ process: (msg, op, local, localOpMetadata) => {
404
443
  const subdir = this.getWorkingDirectory(op.path);
405
- if (subdir) {
406
- 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);
407
448
  }
408
449
  },
409
450
  submit: (op, localOpMetadata) => {
@@ -420,10 +461,12 @@ export class SharedDirectory extends SharedObject {
420
461
  },
421
462
  });
422
463
  this.messageHandlers.set("delete", {
423
- process: (op, local, localOpMetadata) => {
464
+ process: (msg, op, local, localOpMetadata) => {
424
465
  const subdir = this.getWorkingDirectory(op.path);
425
- if (subdir) {
426
- 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);
427
470
  }
428
471
  },
429
472
  submit: (op, localOpMetadata) => {
@@ -440,11 +483,13 @@ export class SharedDirectory extends SharedObject {
440
483
  },
441
484
  });
442
485
  this.messageHandlers.set("set", {
443
- process: (op, local, localOpMetadata) => {
486
+ process: (msg, op, local, localOpMetadata) => {
444
487
  const subdir = this.getWorkingDirectory(op.path);
445
- 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)) {
446
491
  const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
447
- subdir.processSetMessage(op, context, local, localOpMetadata);
492
+ subdir.processSetMessage(msg, op, context, local, localOpMetadata);
448
493
  }
449
494
  },
450
495
  submit: (op, localOpMetadata) => {
@@ -462,10 +507,10 @@ export class SharedDirectory extends SharedObject {
462
507
  },
463
508
  });
464
509
  this.messageHandlers.set("createSubDirectory", {
465
- process: (op, local, localOpMetadata) => {
510
+ process: (msg, op, local, localOpMetadata) => {
466
511
  const parentSubdir = this.getWorkingDirectory(op.path);
467
512
  if (parentSubdir) {
468
- parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
513
+ parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
469
514
  }
470
515
  },
471
516
  submit: (op, localOpMetadata) => {
@@ -475,7 +520,6 @@ export class SharedDirectory extends SharedObject {
475
520
  parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
476
521
  }
477
522
  },
478
- // eslint-disable-next-line max-len
479
523
  applyStashedOp: (op) => {
480
524
  const parentSubdir = this.getWorkingDirectory(op.path);
481
525
  if (parentSubdir) {
@@ -484,10 +528,10 @@ export class SharedDirectory extends SharedObject {
484
528
  },
485
529
  });
486
530
  this.messageHandlers.set("deleteSubDirectory", {
487
- process: (op, local, localOpMetadata) => {
531
+ process: (msg, op, local, localOpMetadata) => {
488
532
  const parentSubdir = this.getWorkingDirectory(op.path);
489
533
  if (parentSubdir) {
490
- parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
534
+ parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
491
535
  }
492
536
  },
493
537
  submit: (op, localOpMetadata) => {
@@ -497,7 +541,6 @@ export class SharedDirectory extends SharedObject {
497
541
  parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
498
542
  }
499
543
  },
500
- // eslint-disable-next-line max-len
501
544
  applyStashedOp: (op) => {
502
545
  const parentSubdir = this.getWorkingDirectory(op.path);
503
546
  if (parentSubdir) {
@@ -507,6 +550,7 @@ export class SharedDirectory extends SharedObject {
507
550
  });
508
551
  }
509
552
  /**
553
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
510
554
  * @internal
511
555
  */
512
556
  applyStashedOp(op) {
@@ -527,20 +571,20 @@ export class SharedDirectory extends SharedObject {
527
571
  while (stack.length > 0) {
528
572
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
529
573
  const [currentSubDir, currentSubDirObject] = stack.pop();
574
+ currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
530
575
  for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
531
576
  if (!currentSubDirObject.storage) {
532
577
  currentSubDirObject.storage = {};
533
578
  }
534
579
  const result = {
535
580
  type: value.type,
536
- // eslint-disable-next-line @typescript-eslint/ban-types
537
581
  value: value.value && JSON.parse(value.value),
538
582
  };
539
583
  if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
540
584
  const extraContent = {};
541
585
  let largeContent = extraContent;
542
586
  if (currentSubDir.absolutePath !== posix.sep) {
543
- for (const dir of currentSubDir.absolutePath.substr(1).split(posix.sep)) {
587
+ for (const dir of currentSubDir.absolutePath.slice(1).split(posix.sep)) {
544
588
  const subDataObject = {};
545
589
  largeContent.subdirectories = { [dir]: subDataObject };
546
590
  largeContent = subDataObject;
@@ -573,24 +617,32 @@ export class SharedDirectory extends SharedObject {
573
617
  return builder.getSummaryTree();
574
618
  }
575
619
  }
620
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
576
621
  function isKeyEditLocalOpMetadata(metadata) {
577
- return metadata !== undefined && typeof metadata.pendingMessageId === "number" && metadata.type === "edit";
622
+ return (metadata !== undefined &&
623
+ typeof metadata.pendingMessageId === "number" &&
624
+ metadata.type === "edit");
578
625
  }
579
626
  function isClearLocalOpMetadata(metadata) {
580
- return metadata !== undefined && metadata.type === "clear" && typeof metadata.pendingMessageId === "number" &&
581
- typeof metadata.previousStorage === "object";
627
+ return (metadata !== undefined &&
628
+ metadata.type === "clear" &&
629
+ typeof metadata.pendingMessageId === "number" &&
630
+ typeof metadata.previousStorage === "object");
582
631
  }
583
632
  function isSubDirLocalOpMetadata(metadata) {
584
- return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
585
- ((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
586
- metadata.type === "deleteSubDir");
633
+ return (metadata !== undefined &&
634
+ typeof metadata.pendingMessageId === "number" &&
635
+ (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
587
636
  }
588
637
  function isDirectoryLocalOpMetadata(metadata) {
589
- return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
590
- (metadata.type === "edit" || metadata.type === "deleteSubDir" ||
638
+ return (metadata !== undefined &&
639
+ typeof metadata.pendingMessageId === "number" &&
640
+ (metadata.type === "edit" ||
641
+ metadata.type === "deleteSubDir" ||
591
642
  (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
592
- (metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean"));
643
+ metadata.type === "createSubDir"));
593
644
  }
645
+ /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
594
646
  /**
595
647
  * Node of the directory tree.
596
648
  * @sealed
@@ -598,13 +650,17 @@ function isDirectoryLocalOpMetadata(metadata) {
598
650
  class SubDirectory extends TypedEventEmitter {
599
651
  /**
600
652
  * Constructor.
653
+ * @param sequenceNumber - Message seq number at which this was created.
654
+ * @param clientIds - Ids of client which created this directory.
601
655
  * @param directory - Reference back to the SharedDirectory to perform operations
602
656
  * @param runtime - The data store runtime this directory is associated with
603
657
  * @param serializer - The serializer to serialize / parse handles
604
658
  * @param absolutePath - The absolute path of this IDirectory
605
659
  */
606
- constructor(directory, runtime, serializer, absolutePath) {
660
+ constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
607
661
  super();
662
+ this.sequenceNumber = sequenceNumber;
663
+ this.clientIds = clientIds;
608
664
  this.directory = directory;
609
665
  this.runtime = runtime;
610
666
  this.serializer = serializer;
@@ -630,9 +686,14 @@ class SubDirectory extends TypedEventEmitter {
630
686
  */
631
687
  this.pendingKeys = new Map();
632
688
  /**
633
- * 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.
634
690
  */
635
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();
636
697
  /**
637
698
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
638
699
  */
@@ -647,10 +708,11 @@ class SubDirectory extends TypedEventEmitter {
647
708
  this.emit("disposed", this);
648
709
  }
649
710
  /**
650
- * Unmark the deleted property when rolling back delete.
711
+ * Unmark the deleted property only when rolling back delete.
651
712
  */
652
713
  undispose() {
653
714
  this._deleted = false;
715
+ this.emit("undisposed", this);
654
716
  }
655
717
  get disposed() {
656
718
  return this._deleted;
@@ -714,6 +776,7 @@ class SubDirectory extends TypedEventEmitter {
714
776
  * {@inheritDoc IDirectory.createSubDirectory}
715
777
  */
716
778
  createSubDirectory(subdirName) {
779
+ var _c;
717
780
  this.throwIfDisposed();
718
781
  // Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
719
782
  if (subdirName === undefined || subdirName === null) {
@@ -723,19 +786,22 @@ class SubDirectory extends TypedEventEmitter {
723
786
  throw new Error(`SubDirectory name may not contain ${posix.sep}`);
724
787
  }
725
788
  // Create the sub directory locally first.
726
- const isNew = this.createSubDirectoryCore(subdirName, true);
727
- // 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");
728
790
  const subDir = this._subdirectories.get(subdirName);
791
+ assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
729
792
  // If we are not attached, don't submit the op.
730
793
  if (!this.directory.isAttached()) {
731
794
  return subDir;
732
795
  }
733
- const op = {
734
- path: this.absolutePath,
735
- subdirName,
736
- type: "createSubDirectory",
737
- };
738
- 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
+ }
739
805
  return subDir;
740
806
  }
741
807
  /**
@@ -763,12 +829,15 @@ class SubDirectory extends TypedEventEmitter {
763
829
  if (!this.directory.isAttached()) {
764
830
  return subDir !== undefined;
765
831
  }
766
- const op = {
767
- path: this.absolutePath,
768
- subdirName,
769
- type: "deleteSubDirectory",
770
- };
771
- 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
+ }
772
841
  return subDir !== undefined;
773
842
  }
774
843
  /**
@@ -785,6 +854,18 @@ class SubDirectory extends TypedEventEmitter {
785
854
  this.throwIfDisposed();
786
855
  return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
787
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
+ }
788
869
  /**
789
870
  * Deletes the given key from within this IDirectory.
790
871
  * @param key - The key to delete
@@ -830,6 +911,7 @@ class SubDirectory extends TypedEventEmitter {
830
911
  */
831
912
  forEach(callback) {
832
913
  this.throwIfDisposed();
914
+ // eslint-disable-next-line unicorn/no-array-for-each
833
915
  this._storage.forEach((localValue, key, map) => {
834
916
  callback(localValue.value, key, map);
835
917
  });
@@ -899,14 +981,18 @@ class SubDirectory extends TypedEventEmitter {
899
981
  }
900
982
  /**
901
983
  * Process a clear operation.
984
+ * @param msg - The message from the server to apply.
902
985
  * @param op - The op to process
903
986
  * @param local - Whether the message originated from the local client
904
987
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
905
988
  * For messages from a remote client, this will be undefined.
906
989
  * @internal
907
990
  */
908
- processClearMessage(op, local, localOpMetadata) {
991
+ processClearMessage(msg, op, local, localOpMetadata) {
909
992
  this.throwIfDisposed();
993
+ if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
994
+ return;
995
+ }
910
996
  if (local) {
911
997
  assert(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
912
998
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
@@ -935,15 +1021,17 @@ class SubDirectory extends TypedEventEmitter {
935
1021
  }
936
1022
  /**
937
1023
  * Process a delete operation.
1024
+ * @param msg - The message from the server to apply.
938
1025
  * @param op - The op to process
939
1026
  * @param local - Whether the message originated from the local client
940
1027
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
941
1028
  * For messages from a remote client, this will be undefined.
942
1029
  * @internal
943
1030
  */
944
- processDeleteMessage(op, local, localOpMetadata) {
1031
+ processDeleteMessage(msg, op, local, localOpMetadata) {
945
1032
  this.throwIfDisposed();
946
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1033
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1034
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
947
1035
  return;
948
1036
  }
949
1037
  this.deleteCore(op.key, local);
@@ -957,20 +1045,26 @@ class SubDirectory extends TypedEventEmitter {
957
1045
  this.throwIfDisposed();
958
1046
  const previousValue = this.deleteCore(op.key, true);
959
1047
  const pendingMessageId = this.getKeyMessageId(op);
960
- const localMetadata = { type: "edit", pendingMessageId, previousValue };
1048
+ const localMetadata = {
1049
+ type: "edit",
1050
+ pendingMessageId,
1051
+ previousValue,
1052
+ };
961
1053
  return localMetadata;
962
1054
  }
963
1055
  /**
964
1056
  * Process a set operation.
1057
+ * @param msg - The message from the server to apply.
965
1058
  * @param op - The op to process
966
1059
  * @param local - Whether the message originated from the local client
967
1060
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
968
1061
  * For messages from a remote client, this will be undefined.
969
1062
  * @internal
970
1063
  */
971
- processSetMessage(op, context, local, localOpMetadata) {
1064
+ processSetMessage(msg, op, context, local, localOpMetadata) {
972
1065
  this.throwIfDisposed();
973
- if (!this.needProcessStorageOperation(op, local, localOpMetadata)) {
1066
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1067
+ this.needProcessStorageOperation(op, local, localOpMetadata))) {
974
1068
  return;
975
1069
  }
976
1070
  // needProcessStorageOperation should have returned false if local is true
@@ -989,23 +1083,28 @@ class SubDirectory extends TypedEventEmitter {
989
1083
  const previousValue = this.setCore(op.key, context, true);
990
1084
  // Create metadata
991
1085
  const pendingMessageId = this.getKeyMessageId(op);
992
- const localMetadata = { type: "edit", pendingMessageId, previousValue };
1086
+ const localMetadata = {
1087
+ type: "edit",
1088
+ pendingMessageId,
1089
+ previousValue,
1090
+ };
993
1091
  return localMetadata;
994
1092
  }
995
1093
  /**
996
1094
  * Process a create subdirectory operation.
1095
+ * @param msg - The message from the server to apply.
997
1096
  * @param op - The op to process
998
1097
  * @param local - Whether the message originated from the local client
999
1098
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1000
1099
  * For messages from a remote client, this will be undefined.
1001
1100
  * @internal
1002
1101
  */
1003
- processCreateSubDirectoryMessage(op, local, localOpMetadata) {
1102
+ processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1004
1103
  this.throwIfDisposed();
1005
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1104
+ if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1006
1105
  return;
1007
1106
  }
1008
- this.createSubDirectoryCore(op.subdirName, local);
1107
+ this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1009
1108
  }
1010
1109
  /**
1011
1110
  * Apply createSubDirectory operation locally and generate metadata
@@ -1013,28 +1112,30 @@ class SubDirectory extends TypedEventEmitter {
1013
1112
  * @returns metadata generated for stahed op
1014
1113
  */
1015
1114
  applyStashedCreateSubDirMessage(op) {
1115
+ var _c;
1016
1116
  this.throwIfDisposed();
1017
1117
  // Create the sub directory locally first.
1018
- const isNew = this.createSubDirectoryCore(op.subdirName, true);
1118
+ this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1019
1119
  const newMessageId = this.getSubDirMessageId(op);
1020
1120
  const localOpMetadata = {
1021
1121
  type: "createSubDir",
1022
1122
  pendingMessageId: newMessageId,
1023
- previouslyExisted: !isNew,
1024
1123
  };
1025
1124
  return localOpMetadata;
1026
1125
  }
1027
1126
  /**
1028
1127
  * Process a delete subdirectory operation.
1128
+ * @param msg - The message from the server to apply.
1029
1129
  * @param op - The op to process
1030
1130
  * @param local - Whether the message originated from the local client
1031
1131
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
1032
1132
  * For messages from a remote client, this will be undefined.
1033
1133
  * @internal
1034
1134
  */
1035
- processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
1135
+ processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
1036
1136
  this.throwIfDisposed();
1037
- if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
1137
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1138
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1038
1139
  return;
1039
1140
  }
1040
1141
  this.deleteSubDirectoryCore(op.subdirName, local);
@@ -1118,7 +1219,8 @@ class SubDirectory extends TypedEventEmitter {
1118
1219
  assert(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
1119
1220
  // clear the old pending message id
1120
1221
  const pendingMessageIds = this.pendingKeys.get(op.key);
1121
- assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
1222
+ assert(pendingMessageIds !== undefined &&
1223
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
1122
1224
  pendingMessageIds.shift();
1123
1225
  if (pendingMessageIds.length === 0) {
1124
1226
  this.pendingKeys.delete(op.key);
@@ -1129,6 +1231,7 @@ class SubDirectory extends TypedEventEmitter {
1129
1231
  * Get a new pending message id for the op and cache it to track the pending op
1130
1232
  */
1131
1233
  getSubDirMessageId(op) {
1234
+ var _c;
1132
1235
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1133
1236
  const newMessageId = ++this.pendingMessageId;
1134
1237
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
@@ -1138,20 +1241,22 @@ class SubDirectory extends TypedEventEmitter {
1138
1241
  else {
1139
1242
  this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1140
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
+ }
1141
1248
  return newMessageId;
1142
1249
  }
1143
1250
  /**
1144
1251
  * Submit a create subdirectory operation.
1145
1252
  * @param op - The operation
1146
- * @param prevExisted - Whether the subdirectory existed before the op
1147
1253
  */
1148
- submitCreateSubDirectoryMessage(op, prevExisted) {
1254
+ submitCreateSubDirectoryMessage(op) {
1149
1255
  this.throwIfDisposed();
1150
1256
  const newMessageId = this.getSubDirMessageId(op);
1151
1257
  const localOpMetadata = {
1152
1258
  type: "createSubDir",
1153
1259
  pendingMessageId: newMessageId,
1154
- previouslyExisted: prevExisted,
1155
1260
  };
1156
1261
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1157
1262
  }
@@ -1180,13 +1285,14 @@ class SubDirectory extends TypedEventEmitter {
1180
1285
  assert(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
1181
1286
  // clear the old pending message id
1182
1287
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1183
- assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
1288
+ assert(pendingMessageIds !== undefined &&
1289
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
1184
1290
  pendingMessageIds.shift();
1185
1291
  if (pendingMessageIds.length === 0) {
1186
1292
  this.pendingSubDirectories.delete(op.subdirName);
1187
1293
  }
1188
1294
  if (localOpMetadata.type === "createSubDir") {
1189
- this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
1295
+ this.submitCreateSubDirectoryMessage(op);
1190
1296
  }
1191
1297
  else {
1192
1298
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
@@ -1206,6 +1312,14 @@ class SubDirectory extends TypedEventEmitter {
1206
1312
  yield res;
1207
1313
  }
1208
1314
  }
1315
+ getSerializableCreateInfo() {
1316
+ this.throwIfDisposed();
1317
+ const createInfo = {
1318
+ csn: this.sequenceNumber,
1319
+ ccIds: Array.from(this.clientIds),
1320
+ };
1321
+ return createInfo;
1322
+ }
1209
1323
  /**
1210
1324
  * Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
1211
1325
  * @param key - The key to populate
@@ -1252,21 +1366,24 @@ class SubDirectory extends TypedEventEmitter {
1252
1366
  map.delete(key);
1253
1367
  }
1254
1368
  }
1369
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1255
1370
  /**
1256
1371
  * Rollback a local op
1257
1372
  * @param op - The operation to rollback
1258
1373
  * @param localOpMetadata - The local metadata associated with the op.
1259
1374
  */
1375
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1260
1376
  rollback(op, localOpMetadata) {
1261
1377
  if (!isDirectoryLocalOpMetadata(localOpMetadata)) {
1262
1378
  throw new Error("Invalid localOpMetadata");
1263
1379
  }
1264
1380
  if (op.type === "clear" && localOpMetadata.type === "clear") {
1265
- localOpMetadata.previousStorage.forEach((localValue, key) => {
1381
+ for (const [key, localValue] of localOpMetadata.previousStorage.entries()) {
1266
1382
  this.setCore(key, localValue, true);
1267
- });
1383
+ }
1268
1384
  const lastPendingClearId = this.pendingClearMessageIds.pop();
1269
- if (lastPendingClearId === undefined || lastPendingClearId !== localOpMetadata.pendingMessageId) {
1385
+ if (lastPendingClearId === undefined ||
1386
+ lastPendingClearId !== localOpMetadata.pendingMessageId) {
1270
1387
  throw new Error("Rollback op does match last clear");
1271
1388
  }
1272
1389
  }
@@ -1280,9 +1397,7 @@ class SubDirectory extends TypedEventEmitter {
1280
1397
  this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
1281
1398
  }
1282
1399
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1283
- if (!localOpMetadata.previouslyExisted) {
1284
- this.deleteSubDirectoryCore(op.subdirName, true);
1285
- }
1400
+ this.deleteSubDirectoryCore(op.subdirName, true);
1286
1401
  this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1287
1402
  }
1288
1403
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
@@ -1293,11 +1408,18 @@ class SubDirectory extends TypedEventEmitter {
1293
1408
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1294
1409
  }
1295
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
+ }
1296
1417
  }
1297
1418
  else {
1298
1419
  throw new Error("Unsupported op for rollback");
1299
1420
  }
1300
1421
  }
1422
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
1301
1423
  /**
1302
1424
  * Converts the given relative path into an absolute path.
1303
1425
  * @param path - Relative path to convert
@@ -1318,8 +1440,25 @@ class SubDirectory extends TypedEventEmitter {
1318
1440
  needProcessStorageOperation(op, local, localOpMetadata) {
1319
1441
  if (this.pendingClearMessageIds.length > 0) {
1320
1442
  if (local) {
1321
- assert(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata) &&
1443
+ assert(localOpMetadata !== undefined &&
1444
+ isKeyEditLocalOpMetadata(localOpMetadata) &&
1322
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
+ }
1323
1462
  }
1324
1463
  // If I have a NACK clear, we can ignore all ops.
1325
1464
  return false;
@@ -1331,7 +1470,8 @@ class SubDirectory extends TypedEventEmitter {
1331
1470
  if (local) {
1332
1471
  assert(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata), 0x011 /* pendingMessageId is missing from the local client's operation */);
1333
1472
  const pendingMessageIds = this.pendingKeys.get(op.key);
1334
- assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
1473
+ assert(pendingMessageIds !== undefined &&
1474
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
1335
1475
  pendingMessageIds.shift();
1336
1476
  if (pendingMessageIds.length === 0) {
1337
1477
  this.pendingKeys.delete(op.key);
@@ -1342,6 +1482,19 @@ class SubDirectory extends TypedEventEmitter {
1342
1482
  // If we don't have a NACK op on the key, we need to process the remote ops.
1343
1483
  return !local;
1344
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
+ }
1345
1498
  /**
1346
1499
  * If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
1347
1500
  * not process the incoming operation.
@@ -1352,17 +1505,52 @@ class SubDirectory extends TypedEventEmitter {
1352
1505
  * For messages from a remote client, this will be undefined.
1353
1506
  * @returns True if the operation should be processed, false otherwise
1354
1507
  */
1355
- needProcessSubDirectoryOperation(op, local, localOpMetadata) {
1508
+ needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1356
1509
  const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1357
1510
  if (pendingSubDirectoryMessageId !== undefined) {
1358
1511
  if (local) {
1359
1512
  assert(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
1360
1513
  const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1361
- assert(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
1514
+ assert(pendingMessageIds !== undefined &&
1515
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
1362
1516
  pendingMessageIds.shift();
1363
1517
  if (pendingMessageIds.length === 0) {
1364
1518
  this.pendingSubDirectories.delete(op.subdirName);
1365
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
+ }
1366
1554
  }
1367
1555
  return false;
1368
1556
  }
@@ -1375,14 +1563,17 @@ class SubDirectory extends TypedEventEmitter {
1375
1563
  // Assuming the pendingKeys is small and the map is large
1376
1564
  // we will get the value for the pendingKeys and clear the map
1377
1565
  const temp = new Map();
1378
- this.pendingKeys.forEach((value, key, map) => {
1379
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1380
- temp.set(key, this._storage.get(key));
1381
- });
1566
+ for (const [key] of this.pendingKeys) {
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
+ }
1572
+ }
1382
1573
  this.clearCore(local);
1383
- temp.forEach((value, key, map) => {
1574
+ for (const [key, value] of temp.entries()) {
1384
1575
  this.setCore(key, value, true);
1385
- });
1576
+ }
1386
1577
  }
1387
1578
  /**
1388
1579
  * Clear implementation used for both locally sourced clears as well as incoming remote clears.
@@ -1431,17 +1622,23 @@ class SubDirectory extends TypedEventEmitter {
1431
1622
  * Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
1432
1623
  * @param subdirName - The name of the subdirectory being created
1433
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.
1434
1627
  * @returns - True if is newly created, false if it already existed.
1435
1628
  */
1436
- createSubDirectoryCore(subdirName, local) {
1437
- if (!this._subdirectories.has(subdirName)) {
1629
+ createSubDirectoryCore(subdirName, local, seq, clientId) {
1630
+ const subdir = this._subdirectories.get(subdirName);
1631
+ if (subdir === undefined) {
1438
1632
  const absolutePath = posix.join(this.absolutePath, subdirName);
1439
- 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);
1440
1634
  this.registerEventsOnSubDirectory(subDir, subdirName);
1441
1635
  this._subdirectories.set(subdirName, subDir);
1442
1636
  this.emit("subDirectoryCreated", subdirName, local, this);
1443
1637
  return true;
1444
1638
  }
1639
+ else {
1640
+ subdir.clientIds.add(clientId);
1641
+ }
1445
1642
  return false;
1446
1643
  }
1447
1644
  registerEventsOnSubDirectory(subDirectory, subDirName) {