@fluidframework/map 2.0.0-dev.4.4.0.162574 → 2.0.0-dev.5.3.2.178189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/directory.ts CHANGED
@@ -654,7 +654,10 @@ export class SharedDirectory
654
654
  if (!newSubDir) {
655
655
  const createInfo = subdirObject.ci;
656
656
  newSubDir = new SubDirectory(
657
- createInfo !== undefined ? createInfo.csn : 0,
657
+ // If csn is -1, then initialize it with 0, otherwise we will never process ops for this
658
+ // sub directory. This could be done at serialization time too, but we need to maintain
659
+ // back compat too and also we will actually know the state when it was serialized.
660
+ createInfo !== undefined && createInfo.csn > -1 ? createInfo.csn : 0,
658
661
  createInfo !== undefined
659
662
  ? new Set<string>(createInfo.ccIds)
660
663
  : new Set(),
@@ -1015,12 +1018,10 @@ interface IClearLocalOpMetadata {
1015
1018
 
1016
1019
  interface ICreateSubDirLocalOpMetadata {
1017
1020
  type: "createSubDir";
1018
- pendingMessageId: number;
1019
1021
  }
1020
1022
 
1021
1023
  interface IDeleteSubDirLocalOpMetadata {
1022
1024
  type: "deleteSubDir";
1023
- pendingMessageId: number;
1024
1025
  subDirectory: SubDirectory | undefined;
1025
1026
  }
1026
1027
 
@@ -1052,19 +1053,15 @@ function isClearLocalOpMetadata(metadata: any): metadata is IClearLocalOpMetadat
1052
1053
  function isSubDirLocalOpMetadata(metadata: any): metadata is SubDirLocalOpMetadata {
1053
1054
  return (
1054
1055
  metadata !== undefined &&
1055
- typeof metadata.pendingMessageId === "number" &&
1056
1056
  (metadata.type === "createSubDir" || metadata.type === "deleteSubDir")
1057
1057
  );
1058
1058
  }
1059
1059
 
1060
1060
  function isDirectoryLocalOpMetadata(metadata: any): metadata is DirectoryLocalOpMetadata {
1061
1061
  return (
1062
- metadata !== undefined &&
1063
- typeof metadata.pendingMessageId === "number" &&
1064
- (metadata.type === "edit" ||
1065
- metadata.type === "deleteSubDir" ||
1066
- (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
1067
- metadata.type === "createSubDir")
1062
+ isKeyEditLocalOpMetadata(metadata) ||
1063
+ isClearLocalOpMetadata(metadata) ||
1064
+ isSubDirLocalOpMetadata(metadata)
1068
1065
  );
1069
1066
  }
1070
1067
 
@@ -1100,20 +1097,26 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1100
1097
  private readonly _subdirectories: Map<string, SubDirectory> = new Map();
1101
1098
 
1102
1099
  /**
1103
- * Keys that have been modified locally but not yet ack'd from the server.
1100
+ * Keys that have been modified locally but not yet ack'd from the server. This is for operations on keys like
1101
+ * set/delete operations on keys. The value of this map is list of pendingMessageIds at which that key
1102
+ * was modified. We don't store the type of ops, and behaviour of key ops are different from behaviour of sub
1103
+ * directory ops, so we have separate map from subDirectories tracker.
1104
1104
  */
1105
1105
  private readonly pendingKeys: Map<string, number[]> = new Map();
1106
1106
 
1107
1107
  /**
1108
- * Subdirectories that have been created/deleted locally but not yet ack'd from the server.
1108
+ * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the record
1109
+ * of delete op that are pending or yet to be acked from server. This is maintained just to track the locally
1110
+ * deleted sub directory.
1109
1111
  */
1110
- private readonly pendingSubDirectories: Map<string, number[]> = new Map();
1112
+ private readonly pendingDeleteSubDirectoriesTracker: Map<string, number> = new Map();
1111
1113
 
1112
1114
  /**
1113
- * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
1114
- * of delete op that are pending or yet to be acked from server.
1115
+ * Subdirectories that have been created locally but not yet ack'd from the server. This maintains the record
1116
+ * of create op that are pending or yet to be acked from server. This is maintained just to track the locally
1117
+ * created sub directory.
1115
1118
  */
1116
- private readonly pendingDeleteSubDirectoriesCount: Map<string, number> = new Map();
1119
+ private readonly pendingCreateSubDirectoriesTracker: Map<string, number> = new Map();
1117
1120
 
1118
1121
  /**
1119
1122
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
@@ -1335,8 +1338,7 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1335
1338
  * @returns - true if there is pending delete.
1336
1339
  */
1337
1340
  public isSubDirectoryDeletePending(subDirName: string): boolean {
1338
- const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
1339
- if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
1341
+ if (this.pendingDeleteSubDirectoriesTracker.has(subDirName)) {
1340
1342
  return true;
1341
1343
  }
1342
1344
  return false;
@@ -1637,7 +1639,12 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1637
1639
  localOpMetadata: unknown,
1638
1640
  ): void {
1639
1641
  this.throwIfDisposed();
1640
- if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1642
+ if (
1643
+ !(
1644
+ this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1645
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)
1646
+ )
1647
+ ) {
1641
1648
  return;
1642
1649
  }
1643
1650
  assertNonNullClientId(msg.clientId);
@@ -1655,11 +1662,10 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1655
1662
  this.throwIfDisposed();
1656
1663
  // Create the sub directory locally first.
1657
1664
  this.createSubDirectoryCore(op.subdirName, true, -1, this.runtime.clientId ?? "detached");
1658
- const newMessageId = this.getSubDirMessageId(op);
1665
+ this.updatePendingSubDirMessageCount(op);
1659
1666
 
1660
1667
  const localOpMetadata: ICreateSubDirLocalOpMetadata = {
1661
1668
  type: "createSubDir",
1662
- pendingMessageId: newMessageId,
1663
1669
  };
1664
1670
  return localOpMetadata;
1665
1671
  }
@@ -1701,10 +1707,9 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1701
1707
  ): IDeleteSubDirLocalOpMetadata {
1702
1708
  this.throwIfDisposed();
1703
1709
  const subDir = this.deleteSubDirectoryCore(op.subdirName, true);
1704
- const newMessageId = this.getSubDirMessageId(op);
1710
+ this.updatePendingSubDirMessageCount(op);
1705
1711
  const metadata: IDeleteSubDirLocalOpMetadata = {
1706
1712
  type: "deleteSubDir",
1707
- pendingMessageId: newMessageId,
1708
1713
  subDirectory: subDir,
1709
1714
  };
1710
1715
  return metadata;
@@ -1741,11 +1746,11 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1741
1746
  );
1742
1747
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1743
1748
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
1744
- assert(
1745
- pendingClearMessageId === localOpMetadata.pendingMessageId,
1746
- 0x32c /* pendingMessageId does not match */,
1747
- );
1748
- this.submitClearMessage(op, localOpMetadata.previousStorage);
1749
+ // Only submit the op, if we have record for it, otherwise it is possible that the older instance
1750
+ // is already deleted, in which case we don't need to submit the op.
1751
+ if (pendingClearMessageId === localOpMetadata.pendingMessageId) {
1752
+ this.submitClearMessage(op, localOpMetadata.previousStorage);
1753
+ }
1749
1754
  }
1750
1755
 
1751
1756
  /**
@@ -1789,36 +1794,49 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1789
1794
 
1790
1795
  // clear the old pending message id
1791
1796
  const pendingMessageIds = this.pendingKeys.get(op.key);
1792
- assert(
1797
+ // Only submit the op, if we have record for it, otherwise it is possible that the older instance
1798
+ // is already deleted, in which case we don't need to submit the op.
1799
+ if (
1793
1800
  pendingMessageIds !== undefined &&
1794
- pendingMessageIds[0] === localOpMetadata.pendingMessageId,
1795
- 0x32e /* Unexpected pending message received */,
1796
- );
1797
- pendingMessageIds.shift();
1798
- if (pendingMessageIds.length === 0) {
1799
- this.pendingKeys.delete(op.key);
1801
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId
1802
+ ) {
1803
+ pendingMessageIds.shift();
1804
+ if (pendingMessageIds.length === 0) {
1805
+ this.pendingKeys.delete(op.key);
1806
+ }
1807
+ this.submitKeyMessage(op, localOpMetadata.previousValue);
1800
1808
  }
1809
+ }
1801
1810
 
1802
- this.submitKeyMessage(op, localOpMetadata.previousValue);
1811
+ private incrementPendingSubDirCount(map: Map<string, number>, subDirName: string) {
1812
+ const count = map.get(subDirName) ?? 0;
1813
+ map.set(subDirName, count + 1);
1814
+ }
1815
+
1816
+ private decrementPendingSubDirCount(map: Map<string, number>, subDirName: string) {
1817
+ const count = map.get(subDirName) ?? 0;
1818
+ map.set(subDirName, count - 1);
1819
+ if (count <= 1) {
1820
+ map.delete(subDirName);
1821
+ }
1803
1822
  }
1804
1823
 
1805
1824
  /**
1806
- * Get a new pending message id for the op and cache it to track the pending op
1825
+ * Update the count for pending create/delete of the sub directory so that it can be validated on receiving op
1826
+ * or while resubmitting the op.
1807
1827
  */
1808
- private getSubDirMessageId(op: IDirectorySubDirectoryOperation): number {
1809
- // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1810
- const newMessageId = ++this.pendingMessageId;
1811
- const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1812
- if (pendingMessageIds !== undefined) {
1813
- pendingMessageIds.push(newMessageId);
1814
- } else {
1815
- this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1816
- }
1828
+ private updatePendingSubDirMessageCount(op: IDirectorySubDirectoryOperation) {
1817
1829
  if (op.type === "deleteSubDirectory") {
1818
- const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName) ?? 0;
1819
- this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
1830
+ this.incrementPendingSubDirCount(
1831
+ this.pendingDeleteSubDirectoriesTracker,
1832
+ op.subdirName,
1833
+ );
1834
+ } else if (op.type === "createSubDirectory") {
1835
+ this.incrementPendingSubDirCount(
1836
+ this.pendingCreateSubDirectoriesTracker,
1837
+ op.subdirName,
1838
+ );
1820
1839
  }
1821
- return newMessageId;
1822
1840
  }
1823
1841
 
1824
1842
  /**
@@ -1827,11 +1845,10 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1827
1845
  */
1828
1846
  private submitCreateSubDirectoryMessage(op: IDirectorySubDirectoryOperation): void {
1829
1847
  this.throwIfDisposed();
1830
- const newMessageId = this.getSubDirMessageId(op);
1848
+ this.updatePendingSubDirMessageCount(op);
1831
1849
 
1832
1850
  const localOpMetadata: ICreateSubDirLocalOpMetadata = {
1833
1851
  type: "createSubDir",
1834
- pendingMessageId: newMessageId,
1835
1852
  };
1836
1853
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1837
1854
  }
@@ -1846,11 +1863,10 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1846
1863
  subDir: SubDirectory | undefined,
1847
1864
  ): void {
1848
1865
  this.throwIfDisposed();
1849
- const newMessageId = this.getSubDirMessageId(op);
1866
+ this.updatePendingSubDirMessageCount(op);
1850
1867
 
1851
1868
  const localOpMetadata: IDeleteSubDirLocalOpMetadata = {
1852
1869
  type: "deleteSubDir",
1853
- pendingMessageId: newMessageId,
1854
1870
  subDirectory: subDir,
1855
1871
  };
1856
1872
  this.directory.submitDirectoryMessage(op, localOpMetadata);
@@ -1871,21 +1887,31 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1871
1887
  0x32f /* Invalid localOpMetadata for sub directory op */,
1872
1888
  );
1873
1889
 
1874
- // clear the old pending message id
1875
- const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1876
- assert(
1877
- pendingMessageIds !== undefined &&
1878
- pendingMessageIds[0] === localOpMetadata.pendingMessageId,
1879
- 0x330 /* Unexpected pending message received */,
1880
- );
1881
- pendingMessageIds.shift();
1882
- if (pendingMessageIds.length === 0) {
1883
- this.pendingSubDirectories.delete(op.subdirName);
1890
+ // Only submit the op, if we have record for it, otherwise it is possible that the older instance
1891
+ // is already deleted, in which case we don't need to submit the op.
1892
+ if (
1893
+ localOpMetadata.type === "createSubDir" &&
1894
+ !this.pendingCreateSubDirectoriesTracker.has(op.subdirName)
1895
+ ) {
1896
+ return;
1897
+ } else if (
1898
+ localOpMetadata.type === "deleteSubDir" &&
1899
+ !this.pendingDeleteSubDirectoriesTracker.has(op.subdirName)
1900
+ ) {
1901
+ return;
1884
1902
  }
1885
1903
 
1886
1904
  if (localOpMetadata.type === "createSubDir") {
1905
+ this.decrementPendingSubDirCount(
1906
+ this.pendingCreateSubDirectoriesTracker,
1907
+ op.subdirName,
1908
+ );
1887
1909
  this.submitCreateSubDirectoryMessage(op);
1888
1910
  } else {
1911
+ this.decrementPendingSubDirCount(
1912
+ this.pendingDeleteSubDirectoriesTracker,
1913
+ op.subdirName,
1914
+ );
1889
1915
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
1890
1916
  }
1891
1917
  }
@@ -2010,10 +2036,9 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
2010
2036
  } else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
2011
2037
  this.deleteSubDirectoryCore(op.subdirName as string, true);
2012
2038
 
2013
- this.rollbackPendingMessageId(
2014
- this.pendingSubDirectories,
2039
+ this.decrementPendingSubDirCount(
2040
+ this.pendingCreateSubDirectoriesTracker,
2015
2041
  op.subdirName as string,
2016
- localOpMetadata.pendingMessageId,
2017
2042
  );
2018
2043
  } else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
2019
2044
  if (localOpMetadata.subDirectory !== undefined) {
@@ -2023,17 +2048,10 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
2023
2048
  this.emit("subDirectoryCreated", op.subdirName, true, this);
2024
2049
  }
2025
2050
 
2026
- this.rollbackPendingMessageId(
2027
- this.pendingSubDirectories,
2028
- op.subdirName as string,
2029
- localOpMetadata.pendingMessageId,
2051
+ this.decrementPendingSubDirCount(
2052
+ this.pendingDeleteSubDirectoriesTracker,
2053
+ op.subDirName as string,
2030
2054
  );
2031
- const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
2032
- assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
2033
- this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
2034
- if (count === 1) {
2035
- this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
2036
- }
2037
2055
  } else {
2038
2056
  throw new Error("Unsupported op for rollback");
2039
2057
  }
@@ -2152,61 +2170,81 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
2152
2170
  local: boolean,
2153
2171
  localOpMetadata: unknown,
2154
2172
  ): boolean {
2155
- const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
2156
2173
  assertNonNullClientId(msg.clientId);
2157
- if (pendingSubDirectoryMessageId !== undefined) {
2174
+ const pendingDeleteCount = this.pendingDeleteSubDirectoriesTracker.get(op.subdirName);
2175
+ const pendingCreateCount = this.pendingCreateSubDirectoriesTracker.get(op.subdirName);
2176
+ if (
2177
+ (pendingDeleteCount !== undefined && pendingDeleteCount > 0) ||
2178
+ (pendingCreateCount !== undefined && pendingCreateCount > 0)
2179
+ ) {
2158
2180
  if (local) {
2159
2181
  assert(
2160
2182
  isSubDirLocalOpMetadata(localOpMetadata),
2161
2183
  0x012 /* pendingMessageId is missing from the local client's operation */,
2162
2184
  );
2163
- const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
2164
- assert(
2165
- pendingMessageIds !== undefined &&
2166
- pendingMessageIds[0] === localOpMetadata.pendingMessageId,
2167
- 0x332 /* Unexpected pending message received */,
2168
- );
2169
- pendingMessageIds.shift();
2170
- if (pendingMessageIds.length === 0) {
2171
- this.pendingSubDirectories.delete(op.subdirName);
2172
- }
2173
- if (op.type === "deleteSubDirectory") {
2174
- const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
2185
+ if (localOpMetadata.type === "deleteSubDir") {
2175
2186
  assert(
2176
- count !== undefined && count > 0,
2177
- 0x5ac /* should have record for delete op */,
2187
+ pendingDeleteCount !== undefined && pendingDeleteCount > 0,
2188
+ 0x6c2 /* pendingDeleteCount should exist */,
2189
+ );
2190
+ this.decrementPendingSubDirCount(
2191
+ this.pendingDeleteSubDirectoriesTracker,
2192
+ op.subdirName,
2193
+ );
2194
+ } else if (localOpMetadata.type === "createSubDir") {
2195
+ assert(
2196
+ pendingCreateCount !== undefined && pendingCreateCount > 0,
2197
+ 0x6c3 /* pendingCreateCount should exist */,
2198
+ );
2199
+ this.decrementPendingSubDirCount(
2200
+ this.pendingCreateSubDirectoriesTracker,
2201
+ op.subdirName,
2178
2202
  );
2179
- this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
2180
- if (count === 1) {
2181
- this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
2182
- }
2183
2203
  }
2184
- } else if (op.type === "deleteSubDirectory") {
2185
- // If this is remote delete op and we have keys in this subDirectory, then we need to delete these
2186
- // keys except the pending ones as they will be sequenced after this delete.
2187
- const subDirectory = this._subdirectories.get(op.subdirName);
2188
- if (subDirectory) {
2189
- subDirectory.clearExceptPendingKeys(local);
2190
- // In case of remote delete op, we need to reset the creation seq number and client ids of
2204
+ }
2205
+ if (op.type === "deleteSubDirectory") {
2206
+ const resetSubDirectoryTree = (directory: SubDirectory | undefined): void => {
2207
+ if (!directory) {
2208
+ return;
2209
+ }
2210
+ // If this is delete op and we have keys in this subDirectory, then we need to delete these
2211
+ // keys except the pending ones as they will be sequenced after this delete.
2212
+ directory.clearExceptPendingKeys(local);
2213
+ // In case of delete op, we need to reset the creation seq number and client ids of
2191
2214
  // creators as the previous directory is getting deleted and we will initialize again when
2192
2215
  // we will receive op for the create again.
2193
- subDirectory.sequenceNumber = -1;
2194
- subDirectory.clientIds.clear();
2195
- }
2216
+ directory.sequenceNumber = -1;
2217
+ directory.clientIds.clear();
2218
+ // Do the same thing for the subtree of the directory. If create is not pending for a child, then just
2219
+ // delete it.
2220
+ const subDirectories = directory.subdirectories();
2221
+ for (const [subDirName, subDir] of subDirectories) {
2222
+ if (directory.pendingCreateSubDirectoriesTracker.has(subDirName)) {
2223
+ resetSubDirectoryTree(subDir as SubDirectory);
2224
+ continue;
2225
+ }
2226
+ directory.deleteSubDirectoryCore(subDirName, false);
2227
+ }
2228
+ };
2229
+ const subDirectory = this._subdirectories.get(op.subdirName);
2230
+ resetSubDirectoryTree(subDirectory);
2196
2231
  }
2197
2232
  if (op.type === "createSubDirectory") {
2198
2233
  const dir = this._subdirectories.get(op.subdirName);
2199
- if (dir?.sequenceNumber === -1) {
2200
- // Only set the seq on the first message, could be more
2201
- dir.sequenceNumber = msg.sequenceNumber;
2202
- }
2203
- // The client created the dir at or after the dirs seq, so list its client id as a creator.
2204
- if (
2205
- dir !== undefined &&
2206
- !dir.clientIds.has(msg.clientId) &&
2207
- dir.sequenceNumber <= msg.sequenceNumber
2208
- ) {
2209
- dir.clientIds.add(msg.clientId);
2234
+ // Child sub directory create seq number can't be lower than the parent subdirectory.
2235
+ if (this.sequenceNumber !== -1 && this.sequenceNumber < msg.sequenceNumber) {
2236
+ if (dir?.sequenceNumber === -1) {
2237
+ // Only set the seq on the first message, could be more
2238
+ dir.sequenceNumber = msg.sequenceNumber;
2239
+ }
2240
+ // The client created the dir at or after the dirs seq, so list its client id as a creator.
2241
+ if (
2242
+ dir !== undefined &&
2243
+ !dir.clientIds.has(msg.clientId) &&
2244
+ dir.sequenceNumber <= msg.sequenceNumber
2245
+ ) {
2246
+ dir.clientIds.add(msg.clientId);
2247
+ }
2210
2248
  }
2211
2249
  }
2212
2250
  return false;
package/src/interfaces.ts CHANGED
@@ -4,12 +4,8 @@
4
4
  */
5
5
 
6
6
  import { ISharedObject, ISharedObjectEvents } from "@fluidframework/shared-object-base";
7
- import {
8
- IDisposable,
9
- IEvent,
10
- IEventProvider,
11
- IEventThisPlaceHolder,
12
- } from "@fluidframework/common-definitions";
7
+ import { IEvent, IEventProvider, IEventThisPlaceHolder } from "@fluidframework/common-definitions";
8
+ import { IDisposable } from "@fluidframework/core-interfaces";
13
9
 
14
10
  /**
15
11
  * Type of "valueChanged" event parameter.
package/src/mapKernel.ts CHANGED
@@ -342,6 +342,9 @@ export class MapKernel {
342
342
  // Clear the data locally first.
343
343
  this.clearCore(true);
344
344
 
345
+ // Clear the pendingKeys immediately, the local unack'd operations are aborted
346
+ this.pendingKeys.clear();
347
+
345
348
  // If we are not attached, don't submit the op.
346
349
  if (!this.isAttached()) {
347
350
  return;
@@ -547,8 +550,11 @@ export class MapKernel {
547
550
  // we will get the value for the pendingKeys and clear the map
548
551
  const temp = new Map<string, ILocalValue>();
549
552
  for (const key of this.pendingKeys.keys()) {
550
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
551
- temp.set(key, this.data.get(key)!);
553
+ // Verify if the most recent pending operation is a delete op, no need to retain it if so.
554
+ // This ensures the map size remains consistent.
555
+ if (this.data.has(key)) {
556
+ temp.set(key, this.data.get(key) as ILocalValue);
557
+ }
552
558
  }
553
559
  this.clearCore(false);
554
560
  for (const [key, value] of temp.entries()) {
@@ -771,13 +777,16 @@ export class MapKernel {
771
777
  0x2fe /* Invalid localOpMetadata in submit */,
772
778
  );
773
779
 
774
- // clear the old pending message id
780
+ // no need to submit messages for op's that have been aborted
775
781
  const pendingMessageIds = this.pendingKeys.get(op.key);
776
- assert(
777
- pendingMessageIds !== undefined &&
778
- pendingMessageIds[0] === localOpMetadata.pendingMessageId,
779
- 0x2ff /* Unexpected pending message received */,
780
- );
782
+ if (
783
+ pendingMessageIds === undefined ||
784
+ pendingMessageIds[0] !== localOpMetadata.pendingMessageId
785
+ ) {
786
+ return;
787
+ }
788
+
789
+ // clear the old pending message id
781
790
  pendingMessageIds.shift();
782
791
  if (pendingMessageIds.length === 0) {
783
792
  this.pendingKeys.delete(op.key);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/map";
9
- export const pkgVersion = "2.0.0-dev.4.4.0.162574";
9
+ export const pkgVersion = "2.0.0-dev.5.3.2.178189";