@fluidframework/map 2.20.0 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/directory.ts CHANGED
@@ -805,7 +805,10 @@ export class SharedDirectory
805
805
  if (message.type === MessageType.Operation) {
806
806
  const op: IDirectoryOperation = message.contents as IDirectoryOperation;
807
807
  const handler = this.messageHandlers.get(op.type);
808
- assert(handler !== undefined, 0x00e /* Missing message handler for message type */);
808
+ assert(
809
+ handler !== undefined,
810
+ 0x00e /* "Missing message handler for message type: op may be from a newer version */,
811
+ );
809
812
  handler.process(message, op, local, localOpMetadata);
810
813
  }
811
814
  }
@@ -864,18 +867,15 @@ export class SharedDirectory
864
867
  return false;
865
868
  }
866
869
  let currentParent = this.root;
867
- const nodeList = absolutePath.split(posix.sep);
868
- let start = 1;
869
- while (start < nodeList.length) {
870
- const subDirName = nodeList[start];
871
- if (currentParent.isSubDirectoryDeletePending(subDirName)) {
870
+ const pathParts = absolutePath.split(posix.sep).slice(1);
871
+ for (const dirName of pathParts) {
872
+ if (currentParent.isSubDirectoryDeletePending(dirName)) {
872
873
  return true;
873
874
  }
874
- currentParent = currentParent.getSubDirectory(subDirName) as SubDirectory;
875
+ currentParent = currentParent.getSubDirectory(dirName) as SubDirectory;
875
876
  if (currentParent === undefined) {
876
877
  return true;
877
878
  }
878
- start += 1;
879
879
  }
880
880
  return false;
881
881
  }
@@ -1492,7 +1492,9 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
1492
1492
  dirs: this._subdirectories,
1493
1493
  next(): IteratorResult<[string, IDirectory]> {
1494
1494
  if (this.index < subdirNames.length) {
1495
- const subdirName = subdirNames[this.index++];
1495
+ // Bounds check above guarantees non-null (at least at compile time, assuming all types are respected)
1496
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1497
+ const subdirName = subdirNames[this.index++]!;
1496
1498
  const subdir = this.dirs.get(subdirName);
1497
1499
  assert(subdir !== undefined, 0x8ac /* Could not find expected sub-directory. */);
1498
1500
  return { value: [subdirName, subdir], done: false };
@@ -2181,21 +2183,27 @@ class SubDirectory extends TypedEventEmitter<IDirectoryEvents> implements IDirec
2181
2183
  local: boolean,
2182
2184
  localOpMetadata: unknown,
2183
2185
  ): boolean {
2184
- if (this.pendingClearMessageIds.length > 0) {
2186
+ const firstPendingClearMessageId = this.pendingClearMessageIds[0];
2187
+ if (firstPendingClearMessageId !== undefined) {
2185
2188
  if (local) {
2186
2189
  assert(
2187
2190
  localOpMetadata !== undefined &&
2188
2191
  isKeyEditLocalOpMetadata(localOpMetadata) &&
2189
- localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0],
2192
+ localOpMetadata.pendingMessageId < firstPendingClearMessageId,
2190
2193
  0x010 /* "Received out of order storage op when there is an unackd clear message" */,
2191
2194
  );
2192
2195
  // Remove all pendingMessageIds lower than first pendingClearMessageId.
2193
- const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
2196
+ const lowestPendingClearMessageId = firstPendingClearMessageId;
2194
2197
  const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
2195
2198
  if (pendingKeyMessageIdArray !== undefined) {
2196
2199
  let index = 0;
2197
- while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
2200
+ let pendingKeyMessageId = pendingKeyMessageIdArray[index];
2201
+ while (
2202
+ pendingKeyMessageId !== undefined &&
2203
+ pendingKeyMessageId < lowestPendingClearMessageId
2204
+ ) {
2198
2205
  index += 1;
2206
+ pendingKeyMessageId = pendingKeyMessageIdArray[index];
2199
2207
  }
2200
2208
  const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
2201
2209
  if (newPendingKeyMessageId.length === 0) {
package/src/map.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { assert } from "@fluidframework/core-utils/internal";
6
7
  import type {
7
8
  IChannelAttributes,
8
9
  IFluidDataStoreRuntime,
@@ -202,8 +203,7 @@ export class SharedMap extends SharedObject<ISharedMapEvents> implements IShared
202
203
  // and result in non-incremental snapshot.
203
204
  // This can be improved in the future, without being format breaking change, as loading sequence
204
205
  // loads all blobs at once and partitioning schema has no impact on that process.
205
- for (const key of Object.keys(data)) {
206
- const value = data[key];
206
+ for (const [key, value] of Object.entries(data)) {
207
207
  if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
208
208
  const blobName = `blob${counter}`;
209
209
  counter++;
@@ -293,7 +293,14 @@ export class SharedMap extends SharedObject<ISharedMapEvents> implements IShared
293
293
  ): void {
294
294
  // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
295
295
  if (message.type === MessageType.Operation) {
296
- this.kernel.tryProcessMessage(message.contents as IMapOperation, local, localOpMetadata);
296
+ assert(
297
+ this.kernel.tryProcessMessage(
298
+ message.contents as IMapOperation,
299
+ local,
300
+ localOpMetadata,
301
+ ),
302
+ 0xab2 /* Map received an unrecognized op, possibly from a newer version */,
303
+ );
297
304
  }
298
305
  }
299
306
 
package/src/mapKernel.ts CHANGED
@@ -437,11 +437,18 @@ export class MapKernel {
437
437
 
438
438
  /**
439
439
  * Process the given op if a handler is registered.
440
- * @param op - The message to process
440
+ * @param message - The message to process
441
441
  * @param local - Whether the message originated from the local client
442
442
  * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
443
443
  * For messages from a remote client, this will be undefined.
444
- * @returns True if the operation was processed, false otherwise.
444
+ * @returns True if the operation was recognized and thus processed, false otherwise.
445
+ *
446
+ * @remarks
447
+ * When this returns false and the caller doesn't handle the op itself, then the op could be from a different version of this code.
448
+ * In such a case, not applying the op would result in this client becoming out of sync with clients that do handle the op
449
+ * and could result in data corruption or data loss as well.
450
+ * Therefore, in such cases the caller should typically throw an error, ensuring that this client treats the situation as data corruption
451
+ * (since its data no longer matches what other clients think the data should be) and will avoid overriding document content or misleading the users into thinking their current state is accurate.
445
452
  */
446
453
  public tryProcessMessage(
447
454
  op: IMapOperation,
@@ -607,7 +614,7 @@ export class MapKernel {
607
614
  local: boolean,
608
615
  localOpMetadata: MapLocalOpMetadata,
609
616
  ): boolean {
610
- if (this.pendingClearMessageIds.length > 0) {
617
+ if (this.pendingClearMessageIds[0] !== undefined) {
611
618
  if (local) {
612
619
  assert(
613
620
  localOpMetadata !== undefined &&
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/map";
9
- export const pkgVersion = "2.20.0";
9
+ export const pkgVersion = "2.22.0";
package/tsconfig.json CHANGED
@@ -5,7 +5,6 @@
5
5
  "compilerOptions": {
6
6
  "rootDir": "./src",
7
7
  "outDir": "./lib",
8
- "noUncheckedIndexedAccess": false,
9
8
  "exactOptionalPropertyTypes": false,
10
9
  },
11
10
  }