@fluidframework/map 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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.
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/map";
8
- export declare const pkgVersion = "2.0.0-internal.4.2.1";
8
+ export declare const pkgVersion = "2.0.0-internal.4.4.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/map";
11
- exports.pkgVersion = "2.0.0-internal.4.2.1";
11
+ exports.pkgVersion = "2.0.0-internal.4.4.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,qBAAqB,CAAC;AAChC,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/map\";\nexport const pkgVersion = \"2.0.0-internal.4.2.1\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,qBAAqB,CAAC;AAChC,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/map\";\nexport const pkgVersion = \"2.0.0-internal.4.4.0\";\n"]}
package/lib/directory.js CHANGED
@@ -637,18 +637,17 @@ function isClearLocalOpMetadata(metadata) {
637
637
  }
638
638
  function isSubDirLocalOpMetadata(metadata) {
639
639
  return (metadata !== undefined &&
640
- typeof metadata.pendingMessageId === "number" &&
641
640
  (metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
642
641
  }
643
642
  function isDirectoryLocalOpMetadata(metadata) {
644
- return (metadata !== undefined &&
645
- typeof metadata.pendingMessageId === "number" &&
646
- (metadata.type === "edit" ||
647
- metadata.type === "deleteSubDir" ||
648
- (metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
649
- metadata.type === "createSubDir"));
643
+ return (isKeyEditLocalOpMetadata(metadata) ||
644
+ isClearLocalOpMetadata(metadata) ||
645
+ isSubDirLocalOpMetadata(metadata));
650
646
  }
651
647
  /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
648
+ function assertNonNullClientId(clientId) {
649
+ assert(clientId !== null, 0x6af /* client id should never be null */);
650
+ }
652
651
  /**
653
652
  * Node of the directory tree.
654
653
  * @sealed
@@ -688,18 +687,24 @@ class SubDirectory extends TypedEventEmitter {
688
687
  */
689
688
  this._subdirectories = new Map();
690
689
  /**
691
- * Keys that have been modified locally but not yet ack'd from the server.
690
+ * Keys that have been modified locally but not yet ack'd from the server. This is for operations on keys like
691
+ * set/delete operations on keys. The value of this map is list of pendingMessageIds at which that key
692
+ * was modified. We don't store the type of ops, and behaviour of key ops are different from behaviour of sub
693
+ * directory ops, so we have separate map from subDirectories tracker.
692
694
  */
693
695
  this.pendingKeys = new Map();
694
696
  /**
695
- * Subdirectories that have been created/deleted locally but not yet ack'd from the server.
697
+ * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the record
698
+ * of delete op that are pending or yet to be acked from server. This is maintained just to track the locally
699
+ * deleted sub directory.
696
700
  */
697
- this.pendingSubDirectories = new Map();
701
+ this.pendingDeleteSubDirectoriesTracker = new Map();
698
702
  /**
699
- * Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
700
- * of delete op that are pending or yet to be acked from server.
703
+ * Subdirectories that have been created locally but not yet ack'd from the server. This maintains the record
704
+ * of create op that are pending or yet to be acked from server. This is maintained just to track the locally
705
+ * created sub directory.
701
706
  */
702
- this.pendingDeleteSubDirectoriesCount = new Map();
707
+ this.pendingCreateSubDirectoriesTracker = new Map();
703
708
  /**
704
709
  * This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
705
710
  */
@@ -866,8 +871,7 @@ class SubDirectory extends TypedEventEmitter {
866
871
  * @returns - true if there is pending delete.
867
872
  */
868
873
  isSubDirectoryDeletePending(subDirName) {
869
- const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
870
- if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
874
+ if (this.pendingDeleteSubDirectoriesTracker.has(subDirName)) {
871
875
  return true;
872
876
  }
873
877
  return false;
@@ -1107,9 +1111,11 @@ class SubDirectory extends TypedEventEmitter {
1107
1111
  */
1108
1112
  processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
1109
1113
  this.throwIfDisposed();
1110
- if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
1114
+ if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
1115
+ this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
1111
1116
  return;
1112
1117
  }
1118
+ assertNonNullClientId(msg.clientId);
1113
1119
  this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
1114
1120
  }
1115
1121
  /**
@@ -1122,10 +1128,9 @@ class SubDirectory extends TypedEventEmitter {
1122
1128
  this.throwIfDisposed();
1123
1129
  // Create the sub directory locally first.
1124
1130
  this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
1125
- const newMessageId = this.getSubDirMessageId(op);
1131
+ this.updatePendingSubDirMessageCount(op);
1126
1132
  const localOpMetadata = {
1127
1133
  type: "createSubDir",
1128
- pendingMessageId: newMessageId,
1129
1134
  };
1130
1135
  return localOpMetadata;
1131
1136
  }
@@ -1154,10 +1159,9 @@ class SubDirectory extends TypedEventEmitter {
1154
1159
  applyStashedDeleteSubDirMessage(op) {
1155
1160
  this.throwIfDisposed();
1156
1161
  const subDir = this.deleteSubDirectoryCore(op.subdirName, true);
1157
- const newMessageId = this.getSubDirMessageId(op);
1162
+ this.updatePendingSubDirMessageCount(op);
1158
1163
  const metadata = {
1159
1164
  type: "deleteSubDir",
1160
- pendingMessageId: newMessageId,
1161
1165
  subDirectory: subDir,
1162
1166
  };
1163
1167
  return metadata;
@@ -1186,8 +1190,11 @@ class SubDirectory extends TypedEventEmitter {
1186
1190
  assert(isClearLocalOpMetadata(localOpMetadata), 0x32b /* Invalid localOpMetadata for clear */);
1187
1191
  // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1188
1192
  const pendingClearMessageId = this.pendingClearMessageIds.shift();
1189
- assert(pendingClearMessageId === localOpMetadata.pendingMessageId, 0x32c /* pendingMessageId does not match */);
1190
- this.submitClearMessage(op, localOpMetadata.previousStorage);
1193
+ // Only submit the op, if we have record for it, otherwise it is possible that the older instance
1194
+ // is already deleted, in which case we don't need to submit the op.
1195
+ if (pendingClearMessageId === localOpMetadata.pendingMessageId) {
1196
+ this.submitClearMessage(op, localOpMetadata.previousStorage);
1197
+ }
1191
1198
  }
1192
1199
  /**
1193
1200
  * Get a new pending message id for the op and cache it to track the pending op
@@ -1225,33 +1232,41 @@ class SubDirectory extends TypedEventEmitter {
1225
1232
  assert(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
1226
1233
  // clear the old pending message id
1227
1234
  const pendingMessageIds = this.pendingKeys.get(op.key);
1228
- assert(pendingMessageIds !== undefined &&
1229
- pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
1230
- pendingMessageIds.shift();
1231
- if (pendingMessageIds.length === 0) {
1232
- this.pendingKeys.delete(op.key);
1235
+ // Only submit the op, if we have record for it, otherwise it is possible that the older instance
1236
+ // is already deleted, in which case we don't need to submit the op.
1237
+ if (pendingMessageIds !== undefined &&
1238
+ pendingMessageIds[0] === localOpMetadata.pendingMessageId) {
1239
+ pendingMessageIds.shift();
1240
+ if (pendingMessageIds.length === 0) {
1241
+ this.pendingKeys.delete(op.key);
1242
+ }
1243
+ this.submitKeyMessage(op, localOpMetadata.previousValue);
1233
1244
  }
1234
- this.submitKeyMessage(op, localOpMetadata.previousValue);
1235
1245
  }
1236
- /**
1237
- * Get a new pending message id for the op and cache it to track the pending op
1238
- */
1239
- getSubDirMessageId(op) {
1246
+ incrementPendingSubDirCount(map, subDirName) {
1240
1247
  var _c;
1241
- // We don't reuse the metadata pendingMessageId but send a new one on each submit.
1242
- const newMessageId = ++this.pendingMessageId;
1243
- const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1244
- if (pendingMessageIds !== undefined) {
1245
- pendingMessageIds.push(newMessageId);
1246
- }
1247
- else {
1248
- this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
1248
+ const count = (_c = map.get(subDirName)) !== null && _c !== void 0 ? _c : 0;
1249
+ map.set(subDirName, count + 1);
1250
+ }
1251
+ decrementPendingSubDirCount(map, subDirName) {
1252
+ var _c;
1253
+ const count = (_c = map.get(subDirName)) !== null && _c !== void 0 ? _c : 0;
1254
+ map.set(subDirName, count - 1);
1255
+ if (count <= 1) {
1256
+ map.delete(subDirName);
1249
1257
  }
1258
+ }
1259
+ /**
1260
+ * Update the count for pending create/delete of the sub directory so that it can be validated on receiving op
1261
+ * or while resubmitting the op.
1262
+ */
1263
+ updatePendingSubDirMessageCount(op) {
1250
1264
  if (op.type === "deleteSubDirectory") {
1251
- const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
1252
- this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
1265
+ this.incrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
1266
+ }
1267
+ else if (op.type === "createSubDirectory") {
1268
+ this.incrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
1253
1269
  }
1254
- return newMessageId;
1255
1270
  }
1256
1271
  /**
1257
1272
  * Submit a create subdirectory operation.
@@ -1259,10 +1274,9 @@ class SubDirectory extends TypedEventEmitter {
1259
1274
  */
1260
1275
  submitCreateSubDirectoryMessage(op) {
1261
1276
  this.throwIfDisposed();
1262
- const newMessageId = this.getSubDirMessageId(op);
1277
+ this.updatePendingSubDirMessageCount(op);
1263
1278
  const localOpMetadata = {
1264
1279
  type: "createSubDir",
1265
- pendingMessageId: newMessageId,
1266
1280
  };
1267
1281
  this.directory.submitDirectoryMessage(op, localOpMetadata);
1268
1282
  }
@@ -1273,10 +1287,9 @@ class SubDirectory extends TypedEventEmitter {
1273
1287
  */
1274
1288
  submitDeleteSubDirectoryMessage(op, subDir) {
1275
1289
  this.throwIfDisposed();
1276
- const newMessageId = this.getSubDirMessageId(op);
1290
+ this.updatePendingSubDirMessageCount(op);
1277
1291
  const localOpMetadata = {
1278
1292
  type: "deleteSubDir",
1279
- pendingMessageId: newMessageId,
1280
1293
  subDirectory: subDir,
1281
1294
  };
1282
1295
  this.directory.submitDirectoryMessage(op, localOpMetadata);
@@ -1289,18 +1302,22 @@ class SubDirectory extends TypedEventEmitter {
1289
1302
  */
1290
1303
  resubmitSubDirectoryMessage(op, localOpMetadata) {
1291
1304
  assert(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
1292
- // clear the old pending message id
1293
- const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1294
- assert(pendingMessageIds !== undefined &&
1295
- pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
1296
- pendingMessageIds.shift();
1297
- if (pendingMessageIds.length === 0) {
1298
- this.pendingSubDirectories.delete(op.subdirName);
1305
+ // Only submit the op, if we have record for it, otherwise it is possible that the older instance
1306
+ // is already deleted, in which case we don't need to submit the op.
1307
+ if (localOpMetadata.type === "createSubDir" &&
1308
+ !this.pendingCreateSubDirectoriesTracker.has(op.subdirName)) {
1309
+ return;
1310
+ }
1311
+ else if (localOpMetadata.type === "deleteSubDir" &&
1312
+ !this.pendingDeleteSubDirectoriesTracker.has(op.subdirName)) {
1313
+ return;
1299
1314
  }
1300
1315
  if (localOpMetadata.type === "createSubDir") {
1316
+ this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
1301
1317
  this.submitCreateSubDirectoryMessage(op);
1302
1318
  }
1303
1319
  else {
1320
+ this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
1304
1321
  this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
1305
1322
  }
1306
1323
  }
@@ -1404,7 +1421,7 @@ class SubDirectory extends TypedEventEmitter {
1404
1421
  }
1405
1422
  else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
1406
1423
  this.deleteSubDirectoryCore(op.subdirName, true);
1407
- this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1424
+ this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
1408
1425
  }
1409
1426
  else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
1410
1427
  if (localOpMetadata.subDirectory !== undefined) {
@@ -1413,13 +1430,7 @@ class SubDirectory extends TypedEventEmitter {
1413
1430
  this._subdirectories.set(op.subdirName, localOpMetadata.subDirectory);
1414
1431
  this.emit("subDirectoryCreated", op.subdirName, true, this);
1415
1432
  }
1416
- this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
1417
- const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1418
- assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
1419
- this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1420
- if (count === 1) {
1421
- this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1422
- }
1433
+ this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subDirName);
1423
1434
  }
1424
1435
  else {
1425
1436
  throw new Error("Unsupported op for rollback");
@@ -1497,7 +1508,7 @@ class SubDirectory extends TypedEventEmitter {
1497
1508
  // If the message is either from the creator of directory or this directory was created when
1498
1509
  // container was detached or in case this directory is already live(known to other clients)
1499
1510
  // and the op was created after the directory was created then apply this op.
1500
- return (this.clientIds.has(msg.clientId) ||
1511
+ return ((msg.clientId !== null && this.clientIds.has(msg.clientId)) ||
1501
1512
  this.clientIds.has("detached") ||
1502
1513
  (this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
1503
1514
  }
@@ -1512,50 +1523,63 @@ class SubDirectory extends TypedEventEmitter {
1512
1523
  * @returns True if the operation should be processed, false otherwise
1513
1524
  */
1514
1525
  needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
1515
- const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
1516
- if (pendingSubDirectoryMessageId !== undefined) {
1526
+ assertNonNullClientId(msg.clientId);
1527
+ const pendingDeleteCount = this.pendingDeleteSubDirectoriesTracker.get(op.subdirName);
1528
+ const pendingCreateCount = this.pendingCreateSubDirectoriesTracker.get(op.subdirName);
1529
+ if ((pendingDeleteCount !== undefined && pendingDeleteCount > 0) ||
1530
+ (pendingCreateCount !== undefined && pendingCreateCount > 0)) {
1517
1531
  if (local) {
1518
1532
  assert(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
1519
- const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
1520
- assert(pendingMessageIds !== undefined &&
1521
- pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
1522
- pendingMessageIds.shift();
1523
- if (pendingMessageIds.length === 0) {
1524
- this.pendingSubDirectories.delete(op.subdirName);
1533
+ if (localOpMetadata.type === "deleteSubDir") {
1534
+ assert(pendingDeleteCount !== undefined && pendingDeleteCount > 0, 0x6c2 /* pendingDeleteCount should exist */);
1535
+ this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
1525
1536
  }
1526
- if (op.type === "deleteSubDirectory") {
1527
- const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
1528
- assert(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
1529
- this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
1530
- if (count === 1) {
1531
- this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
1532
- }
1537
+ else if (localOpMetadata.type === "createSubDir") {
1538
+ assert(pendingCreateCount !== undefined && pendingCreateCount > 0, 0x6c3 /* pendingCreateCount should exist */);
1539
+ this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
1533
1540
  }
1534
1541
  }
1535
- else if (op.type === "deleteSubDirectory") {
1536
- // If this is remote delete op and we have keys in this subDirectory, then we need to delete these
1537
- // keys except the pending ones as they will be sequenced after this delete.
1538
- const subDirectory = this._subdirectories.get(op.subdirName);
1539
- if (subDirectory) {
1540
- subDirectory.clearExceptPendingKeys(local);
1541
- // In case of remote delete op, we need to reset the creation seq number and client ids of
1542
+ if (op.type === "deleteSubDirectory") {
1543
+ const resetSubDirectoryTree = (directory) => {
1544
+ if (!directory) {
1545
+ return;
1546
+ }
1547
+ // If this is delete op and we have keys in this subDirectory, then we need to delete these
1548
+ // keys except the pending ones as they will be sequenced after this delete.
1549
+ directory.clearExceptPendingKeys(local);
1550
+ // In case of delete op, we need to reset the creation seq number and client ids of
1542
1551
  // creators as the previous directory is getting deleted and we will initialize again when
1543
1552
  // we will receive op for the create again.
1544
- subDirectory.sequenceNumber = -1;
1545
- subDirectory.clientIds.clear();
1546
- }
1553
+ directory.sequenceNumber = -1;
1554
+ directory.clientIds.clear();
1555
+ // Do the same thing for the subtree of the directory. If create is not pending for a child, then just
1556
+ // delete it.
1557
+ const subDirectories = directory.subdirectories();
1558
+ for (const [subDirName, subDir] of subDirectories) {
1559
+ if (directory.pendingCreateSubDirectoriesTracker.has(subDirName)) {
1560
+ resetSubDirectoryTree(subDir);
1561
+ continue;
1562
+ }
1563
+ directory.deleteSubDirectoryCore(subDirName, false);
1564
+ }
1565
+ };
1566
+ const subDirectory = this._subdirectories.get(op.subdirName);
1567
+ resetSubDirectoryTree(subDirectory);
1547
1568
  }
1548
1569
  if (op.type === "createSubDirectory") {
1549
1570
  const dir = this._subdirectories.get(op.subdirName);
1550
- if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
1551
- // Only set the seq on the first message, could be more
1552
- dir.sequenceNumber = msg.sequenceNumber;
1553
- }
1554
- // The client created the dir at or after the dirs seq, so list its client id as a creator.
1555
- if (dir !== undefined &&
1556
- !dir.clientIds.has(msg.clientId) &&
1557
- dir.sequenceNumber <= msg.sequenceNumber) {
1558
- dir.clientIds.add(msg.clientId);
1571
+ // Child sub directory create seq number can't be lower than the parent subdirectory.
1572
+ if (this.sequenceNumber !== -1 && this.sequenceNumber < msg.sequenceNumber) {
1573
+ if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
1574
+ // Only set the seq on the first message, could be more
1575
+ dir.sequenceNumber = msg.sequenceNumber;
1576
+ }
1577
+ // The client created the dir at or after the dirs seq, so list its client id as a creator.
1578
+ if (dir !== undefined &&
1579
+ !dir.clientIds.has(msg.clientId) &&
1580
+ dir.sequenceNumber <= msg.sequenceNumber) {
1581
+ dir.clientIds.add(msg.clientId);
1582
+ }
1559
1583
  }
1560
1584
  }
1561
1585
  return false;