@fluidframework/map 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.2.0.153917
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/CHANGELOG.md +5 -0
- package/README.md +20 -0
- package/dist/directory.d.ts +27 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +227 -62
- package/dist/directory.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/directory.d.ts +27 -0
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +227 -62
- package/lib/directory.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +58 -60
- package/src/directory.ts +315 -60
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +1 -1
package/dist/directory.js
CHANGED
|
@@ -115,7 +115,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
115
115
|
/**
|
|
116
116
|
* Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
|
|
117
117
|
*/
|
|
118
|
-
this.root = new SubDirectory(this, this.runtime, this.serializer, posix.sep);
|
|
118
|
+
this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
|
|
119
119
|
/**
|
|
120
120
|
* Mapping of op types to message handlers.
|
|
121
121
|
*/
|
|
@@ -371,7 +371,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
371
371
|
for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
|
|
372
372
|
let newSubDir = currentSubDir.getSubDirectory(subdirName);
|
|
373
373
|
if (!newSubDir) {
|
|
374
|
-
|
|
374
|
+
const createInfo = subdirObject.ci;
|
|
375
|
+
newSubDir = new SubDirectory(createInfo !== undefined ? createInfo.csn : 0, createInfo !== undefined
|
|
376
|
+
? new Set(createInfo.ccIds)
|
|
377
|
+
: new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
|
|
375
378
|
currentSubDir.populateSubDirectory(subdirName, newSubDir);
|
|
376
379
|
}
|
|
377
380
|
stack.push([newSubDir, subdirObject]);
|
|
@@ -394,7 +397,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
394
397
|
const op = message.contents;
|
|
395
398
|
const handler = this.messageHandlers.get(op.type);
|
|
396
399
|
(0, common_utils_1.assert)(handler !== undefined, 0x00e /* Missing message handler for message type */);
|
|
397
|
-
handler.process(op, local, localOpMetadata);
|
|
400
|
+
handler.process(message, op, local, localOpMetadata);
|
|
398
401
|
}
|
|
399
402
|
}
|
|
400
403
|
/**
|
|
@@ -430,15 +433,43 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
430
433
|
serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
|
|
431
434
|
return this.localValueMaker.fromSerializable(serializable);
|
|
432
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* This checks if there is pending delete op for local delete for a any subdir in the relative path.
|
|
438
|
+
* @param relativePath - path of sub directory.
|
|
439
|
+
* @returns - true if there is pending delete.
|
|
440
|
+
*/
|
|
441
|
+
isSubDirectoryDeletePending(relativePath) {
|
|
442
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
443
|
+
if (absolutePath === posix.sep) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
let currentParent = this.root;
|
|
447
|
+
const nodeList = absolutePath.split(posix.sep);
|
|
448
|
+
let start = 1;
|
|
449
|
+
while (start < nodeList.length) {
|
|
450
|
+
const subDirName = nodeList[start];
|
|
451
|
+
if (currentParent.isSubDirectoryDeletePending(subDirName)) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
currentParent = currentParent.getSubDirectory(subDirName);
|
|
455
|
+
if (currentParent === undefined) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
start += 1;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
433
462
|
/**
|
|
434
463
|
* Set the message handlers for the directory.
|
|
435
464
|
*/
|
|
436
465
|
setMessageHandlers() {
|
|
437
466
|
this.messageHandlers.set("clear", {
|
|
438
|
-
process: (op, local, localOpMetadata) => {
|
|
467
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
439
468
|
const subdir = this.getWorkingDirectory(op.path);
|
|
440
|
-
|
|
441
|
-
|
|
469
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
470
|
+
// as we are going to delete this subDirectory.
|
|
471
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
472
|
+
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
442
473
|
}
|
|
443
474
|
},
|
|
444
475
|
submit: (op, localOpMetadata) => {
|
|
@@ -455,10 +486,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
455
486
|
},
|
|
456
487
|
});
|
|
457
488
|
this.messageHandlers.set("delete", {
|
|
458
|
-
process: (op, local, localOpMetadata) => {
|
|
489
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
459
490
|
const subdir = this.getWorkingDirectory(op.path);
|
|
460
|
-
|
|
461
|
-
|
|
491
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
492
|
+
// as we are going to delete this subDirectory.
|
|
493
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
494
|
+
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
462
495
|
}
|
|
463
496
|
},
|
|
464
497
|
submit: (op, localOpMetadata) => {
|
|
@@ -475,11 +508,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
475
508
|
},
|
|
476
509
|
});
|
|
477
510
|
this.messageHandlers.set("set", {
|
|
478
|
-
process: (op, local, localOpMetadata) => {
|
|
511
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
479
512
|
const subdir = this.getWorkingDirectory(op.path);
|
|
480
|
-
|
|
513
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
514
|
+
// as we are going to delete this subDirectory.
|
|
515
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
481
516
|
const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
|
|
482
|
-
subdir.processSetMessage(op, context, local, localOpMetadata);
|
|
517
|
+
subdir.processSetMessage(msg, op, context, local, localOpMetadata);
|
|
483
518
|
}
|
|
484
519
|
},
|
|
485
520
|
submit: (op, localOpMetadata) => {
|
|
@@ -497,10 +532,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
497
532
|
},
|
|
498
533
|
});
|
|
499
534
|
this.messageHandlers.set("createSubDirectory", {
|
|
500
|
-
process: (op, local, localOpMetadata) => {
|
|
535
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
501
536
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
502
|
-
|
|
503
|
-
|
|
537
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
538
|
+
// as we are going to delete this subDirectory.
|
|
539
|
+
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
540
|
+
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
504
541
|
}
|
|
505
542
|
},
|
|
506
543
|
submit: (op, localOpMetadata) => {
|
|
@@ -518,10 +555,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
518
555
|
},
|
|
519
556
|
});
|
|
520
557
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
521
|
-
process: (op, local, localOpMetadata) => {
|
|
558
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
522
559
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
523
|
-
|
|
524
|
-
|
|
560
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
561
|
+
// as we are going to delete this subDirectory.
|
|
562
|
+
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
563
|
+
parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
525
564
|
}
|
|
526
565
|
},
|
|
527
566
|
submit: (op, localOpMetadata) => {
|
|
@@ -561,6 +600,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
561
600
|
while (stack.length > 0) {
|
|
562
601
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
563
602
|
const [currentSubDir, currentSubDirObject] = stack.pop();
|
|
603
|
+
currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
|
|
564
604
|
for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
|
|
565
605
|
if (!currentSubDirObject.storage) {
|
|
566
606
|
currentSubDirObject.storage = {};
|
|
@@ -622,8 +662,7 @@ function isClearLocalOpMetadata(metadata) {
|
|
|
622
662
|
function isSubDirLocalOpMetadata(metadata) {
|
|
623
663
|
return (metadata !== undefined &&
|
|
624
664
|
typeof metadata.pendingMessageId === "number" &&
|
|
625
|
-
(
|
|
626
|
-
metadata.type === "deleteSubDir"));
|
|
665
|
+
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
|
|
627
666
|
}
|
|
628
667
|
function isDirectoryLocalOpMetadata(metadata) {
|
|
629
668
|
return (metadata !== undefined &&
|
|
@@ -631,7 +670,7 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
631
670
|
(metadata.type === "edit" ||
|
|
632
671
|
metadata.type === "deleteSubDir" ||
|
|
633
672
|
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
634
|
-
|
|
673
|
+
metadata.type === "createSubDir"));
|
|
635
674
|
}
|
|
636
675
|
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
637
676
|
/**
|
|
@@ -641,13 +680,17 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
641
680
|
class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
642
681
|
/**
|
|
643
682
|
* Constructor.
|
|
683
|
+
* @param sequenceNumber - Message seq number at which this was created.
|
|
684
|
+
* @param clientIds - Ids of client which created this directory.
|
|
644
685
|
* @param directory - Reference back to the SharedDirectory to perform operations
|
|
645
686
|
* @param runtime - The data store runtime this directory is associated with
|
|
646
687
|
* @param serializer - The serializer to serialize / parse handles
|
|
647
688
|
* @param absolutePath - The absolute path of this IDirectory
|
|
648
689
|
*/
|
|
649
|
-
constructor(directory, runtime, serializer, absolutePath) {
|
|
690
|
+
constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
|
|
650
691
|
super();
|
|
692
|
+
this.sequenceNumber = sequenceNumber;
|
|
693
|
+
this.clientIds = clientIds;
|
|
651
694
|
this.directory = directory;
|
|
652
695
|
this.runtime = runtime;
|
|
653
696
|
this.serializer = serializer;
|
|
@@ -673,9 +716,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
673
716
|
*/
|
|
674
717
|
this.pendingKeys = new Map();
|
|
675
718
|
/**
|
|
676
|
-
* Subdirectories that have been
|
|
719
|
+
* Subdirectories that have been created/deleted locally but not yet ack'd from the server.
|
|
677
720
|
*/
|
|
678
721
|
this.pendingSubDirectories = new Map();
|
|
722
|
+
/**
|
|
723
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
|
|
724
|
+
* of delete op that are pending or yet to be acked from server.
|
|
725
|
+
*/
|
|
726
|
+
this.pendingDeleteSubDirectoriesCount = new Map();
|
|
679
727
|
/**
|
|
680
728
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
681
729
|
*/
|
|
@@ -758,6 +806,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
758
806
|
* {@inheritDoc IDirectory.createSubDirectory}
|
|
759
807
|
*/
|
|
760
808
|
createSubDirectory(subdirName) {
|
|
809
|
+
var _c;
|
|
761
810
|
this.throwIfDisposed();
|
|
762
811
|
// Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
|
|
763
812
|
if (subdirName === undefined || subdirName === null) {
|
|
@@ -767,19 +816,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
767
816
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
768
817
|
}
|
|
769
818
|
// Create the sub directory locally first.
|
|
770
|
-
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
771
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
819
|
+
const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
772
820
|
const subDir = this._subdirectories.get(subdirName);
|
|
821
|
+
(0, common_utils_1.assert)(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
|
|
773
822
|
// If we are not attached, don't submit the op.
|
|
774
823
|
if (!this.directory.isAttached()) {
|
|
775
824
|
return subDir;
|
|
776
825
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
826
|
+
// Only submit the op, if it is newly created.
|
|
827
|
+
if (isNew) {
|
|
828
|
+
const op = {
|
|
829
|
+
path: this.absolutePath,
|
|
830
|
+
subdirName,
|
|
831
|
+
type: "createSubDirectory",
|
|
832
|
+
};
|
|
833
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
834
|
+
}
|
|
783
835
|
return subDir;
|
|
784
836
|
}
|
|
785
837
|
/**
|
|
@@ -807,12 +859,15 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
807
859
|
if (!this.directory.isAttached()) {
|
|
808
860
|
return subDir !== undefined;
|
|
809
861
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
862
|
+
// Only submit the op, if the directory existed and we deleted it.
|
|
863
|
+
if (subDir !== undefined) {
|
|
864
|
+
const op = {
|
|
865
|
+
path: this.absolutePath,
|
|
866
|
+
subdirName,
|
|
867
|
+
type: "deleteSubDirectory",
|
|
868
|
+
};
|
|
869
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
870
|
+
}
|
|
816
871
|
return subDir !== undefined;
|
|
817
872
|
}
|
|
818
873
|
/**
|
|
@@ -829,6 +884,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
829
884
|
this.throwIfDisposed();
|
|
830
885
|
return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
|
|
831
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* This checks if there is pending delete op for local delete for a given child subdirectory.
|
|
889
|
+
* @param subDirName - directory name.
|
|
890
|
+
* @returns - true if there is pending delete.
|
|
891
|
+
*/
|
|
892
|
+
isSubDirectoryDeletePending(subDirName) {
|
|
893
|
+
const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
|
|
894
|
+
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
832
899
|
/**
|
|
833
900
|
* Deletes the given key from within this IDirectory.
|
|
834
901
|
* @param key - The key to delete
|
|
@@ -944,14 +1011,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
944
1011
|
}
|
|
945
1012
|
/**
|
|
946
1013
|
* Process a clear operation.
|
|
1014
|
+
* @param msg - The message from the server to apply.
|
|
947
1015
|
* @param op - The op to process
|
|
948
1016
|
* @param local - Whether the message originated from the local client
|
|
949
1017
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
950
1018
|
* For messages from a remote client, this will be undefined.
|
|
951
1019
|
* @internal
|
|
952
1020
|
*/
|
|
953
|
-
processClearMessage(op, local, localOpMetadata) {
|
|
1021
|
+
processClearMessage(msg, op, local, localOpMetadata) {
|
|
954
1022
|
this.throwIfDisposed();
|
|
1023
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
955
1026
|
if (local) {
|
|
956
1027
|
(0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
|
|
957
1028
|
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
@@ -980,15 +1051,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
980
1051
|
}
|
|
981
1052
|
/**
|
|
982
1053
|
* Process a delete operation.
|
|
1054
|
+
* @param msg - The message from the server to apply.
|
|
983
1055
|
* @param op - The op to process
|
|
984
1056
|
* @param local - Whether the message originated from the local client
|
|
985
1057
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
986
1058
|
* For messages from a remote client, this will be undefined.
|
|
987
1059
|
* @internal
|
|
988
1060
|
*/
|
|
989
|
-
processDeleteMessage(op, local, localOpMetadata) {
|
|
1061
|
+
processDeleteMessage(msg, op, local, localOpMetadata) {
|
|
990
1062
|
this.throwIfDisposed();
|
|
991
|
-
if (!this.
|
|
1063
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1064
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
992
1065
|
return;
|
|
993
1066
|
}
|
|
994
1067
|
this.deleteCore(op.key, local);
|
|
@@ -1011,15 +1084,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1011
1084
|
}
|
|
1012
1085
|
/**
|
|
1013
1086
|
* Process a set operation.
|
|
1087
|
+
* @param msg - The message from the server to apply.
|
|
1014
1088
|
* @param op - The op to process
|
|
1015
1089
|
* @param local - Whether the message originated from the local client
|
|
1016
1090
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1017
1091
|
* For messages from a remote client, this will be undefined.
|
|
1018
1092
|
* @internal
|
|
1019
1093
|
*/
|
|
1020
|
-
processSetMessage(op, context, local, localOpMetadata) {
|
|
1094
|
+
processSetMessage(msg, op, context, local, localOpMetadata) {
|
|
1021
1095
|
this.throwIfDisposed();
|
|
1022
|
-
if (!this.
|
|
1096
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1097
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
1023
1098
|
return;
|
|
1024
1099
|
}
|
|
1025
1100
|
// needProcessStorageOperation should have returned false if local is true
|
|
@@ -1047,18 +1122,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1047
1122
|
}
|
|
1048
1123
|
/**
|
|
1049
1124
|
* Process a create subdirectory operation.
|
|
1125
|
+
* @param msg - The message from the server to apply.
|
|
1050
1126
|
* @param op - The op to process
|
|
1051
1127
|
* @param local - Whether the message originated from the local client
|
|
1052
1128
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1053
1129
|
* For messages from a remote client, this will be undefined.
|
|
1054
1130
|
* @internal
|
|
1055
1131
|
*/
|
|
1056
|
-
processCreateSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1132
|
+
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1057
1133
|
this.throwIfDisposed();
|
|
1058
|
-
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1134
|
+
if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
|
|
1059
1135
|
return;
|
|
1060
1136
|
}
|
|
1061
|
-
this.createSubDirectoryCore(op.subdirName, local);
|
|
1137
|
+
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1062
1138
|
}
|
|
1063
1139
|
/**
|
|
1064
1140
|
* Apply createSubDirectory operation locally and generate metadata
|
|
@@ -1066,28 +1142,30 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1066
1142
|
* @returns metadata generated for stahed op
|
|
1067
1143
|
*/
|
|
1068
1144
|
applyStashedCreateSubDirMessage(op) {
|
|
1145
|
+
var _c;
|
|
1069
1146
|
this.throwIfDisposed();
|
|
1070
1147
|
// Create the sub directory locally first.
|
|
1071
|
-
|
|
1148
|
+
this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
1072
1149
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1073
1150
|
const localOpMetadata = {
|
|
1074
1151
|
type: "createSubDir",
|
|
1075
1152
|
pendingMessageId: newMessageId,
|
|
1076
|
-
previouslyExisted: !isNew,
|
|
1077
1153
|
};
|
|
1078
1154
|
return localOpMetadata;
|
|
1079
1155
|
}
|
|
1080
1156
|
/**
|
|
1081
1157
|
* Process a delete subdirectory operation.
|
|
1158
|
+
* @param msg - The message from the server to apply.
|
|
1082
1159
|
* @param op - The op to process
|
|
1083
1160
|
* @param local - Whether the message originated from the local client
|
|
1084
1161
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1085
1162
|
* For messages from a remote client, this will be undefined.
|
|
1086
1163
|
* @internal
|
|
1087
1164
|
*/
|
|
1088
|
-
processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1165
|
+
processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1089
1166
|
this.throwIfDisposed();
|
|
1090
|
-
if (!this.
|
|
1167
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1168
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1091
1169
|
return;
|
|
1092
1170
|
}
|
|
1093
1171
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1183,6 +1261,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1183
1261
|
* Get a new pending message id for the op and cache it to track the pending op
|
|
1184
1262
|
*/
|
|
1185
1263
|
getSubDirMessageId(op) {
|
|
1264
|
+
var _c;
|
|
1186
1265
|
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1187
1266
|
const newMessageId = ++this.pendingMessageId;
|
|
1188
1267
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
@@ -1192,20 +1271,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1192
1271
|
else {
|
|
1193
1272
|
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1194
1273
|
}
|
|
1274
|
+
if (op.type === "deleteSubDirectory") {
|
|
1275
|
+
const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1276
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
|
|
1277
|
+
}
|
|
1195
1278
|
return newMessageId;
|
|
1196
1279
|
}
|
|
1197
1280
|
/**
|
|
1198
1281
|
* Submit a create subdirectory operation.
|
|
1199
1282
|
* @param op - The operation
|
|
1200
|
-
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1201
1283
|
*/
|
|
1202
|
-
submitCreateSubDirectoryMessage(op
|
|
1284
|
+
submitCreateSubDirectoryMessage(op) {
|
|
1203
1285
|
this.throwIfDisposed();
|
|
1204
1286
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1205
1287
|
const localOpMetadata = {
|
|
1206
1288
|
type: "createSubDir",
|
|
1207
1289
|
pendingMessageId: newMessageId,
|
|
1208
|
-
previouslyExisted: prevExisted,
|
|
1209
1290
|
};
|
|
1210
1291
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1211
1292
|
}
|
|
@@ -1241,7 +1322,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1241
1322
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1242
1323
|
}
|
|
1243
1324
|
if (localOpMetadata.type === "createSubDir") {
|
|
1244
|
-
this.submitCreateSubDirectoryMessage(op
|
|
1325
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1245
1326
|
}
|
|
1246
1327
|
else {
|
|
1247
1328
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
@@ -1261,6 +1342,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1261
1342
|
yield res;
|
|
1262
1343
|
}
|
|
1263
1344
|
}
|
|
1345
|
+
getSerializableCreateInfo() {
|
|
1346
|
+
this.throwIfDisposed();
|
|
1347
|
+
const createInfo = {
|
|
1348
|
+
csn: this.sequenceNumber,
|
|
1349
|
+
ccIds: Array.from(this.clientIds),
|
|
1350
|
+
};
|
|
1351
|
+
return createInfo;
|
|
1352
|
+
}
|
|
1264
1353
|
/**
|
|
1265
1354
|
* Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
|
|
1266
1355
|
* @param key - The key to populate
|
|
@@ -1338,9 +1427,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1338
1427
|
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1339
1428
|
}
|
|
1340
1429
|
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1341
|
-
|
|
1342
|
-
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1343
|
-
}
|
|
1430
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1344
1431
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1345
1432
|
}
|
|
1346
1433
|
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
@@ -1351,6 +1438,12 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1351
1438
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1352
1439
|
}
|
|
1353
1440
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1441
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1442
|
+
(0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
1443
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1444
|
+
if (count === 1) {
|
|
1445
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1446
|
+
}
|
|
1354
1447
|
}
|
|
1355
1448
|
else {
|
|
1356
1449
|
throw new Error("Unsupported op for rollback");
|
|
@@ -1380,6 +1473,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1380
1473
|
(0, common_utils_1.assert)(localOpMetadata !== undefined &&
|
|
1381
1474
|
isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1382
1475
|
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1476
|
+
// Remove all pendingMessageIds lower than first pendingClearMessageId.
|
|
1477
|
+
const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
|
|
1478
|
+
const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
|
|
1479
|
+
if (pendingKeyMessageIdArray !== undefined) {
|
|
1480
|
+
let index = 0;
|
|
1481
|
+
while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
|
|
1482
|
+
index += 1;
|
|
1483
|
+
}
|
|
1484
|
+
const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
|
|
1485
|
+
if (newPendingKeyMessageId.length === 0) {
|
|
1486
|
+
this.pendingKeys.delete(op.key);
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
this.pendingKeys.set(op.key, newPendingKeyMessageId);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1383
1492
|
}
|
|
1384
1493
|
// If I have a NACK clear, we can ignore all ops.
|
|
1385
1494
|
return false;
|
|
@@ -1403,6 +1512,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1403
1512
|
// If we don't have a NACK op on the key, we need to process the remote ops.
|
|
1404
1513
|
return !local;
|
|
1405
1514
|
}
|
|
1515
|
+
/**
|
|
1516
|
+
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
1517
|
+
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
1518
|
+
* @param msg - message for the directory
|
|
1519
|
+
*/
|
|
1520
|
+
isMessageForCurrentInstanceOfSubDirectory(msg) {
|
|
1521
|
+
// If the message is either from the creator of directory or this directory was created when
|
|
1522
|
+
// container was detached or in case this directory is already live(known to other clients)
|
|
1523
|
+
// and the op was created after the directory was created then apply this op.
|
|
1524
|
+
return (this.clientIds.has(msg.clientId) ||
|
|
1525
|
+
this.clientIds.has("detached") ||
|
|
1526
|
+
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
|
|
1527
|
+
}
|
|
1406
1528
|
/**
|
|
1407
1529
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1408
1530
|
* not process the incoming operation.
|
|
@@ -1413,7 +1535,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1413
1535
|
* For messages from a remote client, this will be undefined.
|
|
1414
1536
|
* @returns True if the operation should be processed, false otherwise
|
|
1415
1537
|
*/
|
|
1416
|
-
needProcessSubDirectoryOperation(op, local, localOpMetadata) {
|
|
1538
|
+
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1417
1539
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1418
1540
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1419
1541
|
if (local) {
|
|
@@ -1425,6 +1547,40 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1425
1547
|
if (pendingMessageIds.length === 0) {
|
|
1426
1548
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1427
1549
|
}
|
|
1550
|
+
if (op.type === "deleteSubDirectory") {
|
|
1551
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1552
|
+
(0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
|
|
1553
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1554
|
+
if (count === 1) {
|
|
1555
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
else if (op.type === "deleteSubDirectory") {
|
|
1560
|
+
// If this is remote delete op and we have keys in this subDirectory, then we need to delete these
|
|
1561
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
1562
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1563
|
+
if (subDirectory) {
|
|
1564
|
+
subDirectory.clearExceptPendingKeys(local);
|
|
1565
|
+
// In case of remote delete op, we need to reset the creation seq number and client ids of
|
|
1566
|
+
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1567
|
+
// we will receive op for the create again.
|
|
1568
|
+
subDirectory.sequenceNumber = -1;
|
|
1569
|
+
subDirectory.clientIds.clear();
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (op.type === "createSubDirectory") {
|
|
1573
|
+
const dir = this._subdirectories.get(op.subdirName);
|
|
1574
|
+
if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
|
|
1575
|
+
// Only set the seq on the first message, could be more
|
|
1576
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
1577
|
+
}
|
|
1578
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1579
|
+
if (dir !== undefined &&
|
|
1580
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
1581
|
+
dir.sequenceNumber <= msg.sequenceNumber) {
|
|
1582
|
+
dir.clientIds.add(msg.clientId);
|
|
1583
|
+
}
|
|
1428
1584
|
}
|
|
1429
1585
|
return false;
|
|
1430
1586
|
}
|
|
@@ -1438,8 +1594,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1438
1594
|
// we will get the value for the pendingKeys and clear the map
|
|
1439
1595
|
const temp = new Map();
|
|
1440
1596
|
for (const [key] of this.pendingKeys) {
|
|
1441
|
-
|
|
1442
|
-
|
|
1597
|
+
const value = this._storage.get(key);
|
|
1598
|
+
// If this key is already deleted, then we don't need to add it again.
|
|
1599
|
+
if (value !== undefined) {
|
|
1600
|
+
temp.set(key, value);
|
|
1601
|
+
}
|
|
1443
1602
|
}
|
|
1444
1603
|
this.clearCore(local);
|
|
1445
1604
|
for (const [key, value] of temp.entries()) {
|
|
@@ -1493,17 +1652,23 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1493
1652
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1494
1653
|
* @param subdirName - The name of the subdirectory being created
|
|
1495
1654
|
* @param local - Whether the message originated from the local client
|
|
1655
|
+
* @param seq - Sequence number at which this directory is created
|
|
1656
|
+
* @param clientId - Id of client which created this directory.
|
|
1496
1657
|
* @returns - True if is newly created, false if it already existed.
|
|
1497
1658
|
*/
|
|
1498
|
-
createSubDirectoryCore(subdirName, local) {
|
|
1499
|
-
|
|
1659
|
+
createSubDirectoryCore(subdirName, local, seq, clientId) {
|
|
1660
|
+
const subdir = this._subdirectories.get(subdirName);
|
|
1661
|
+
if (subdir === undefined) {
|
|
1500
1662
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1501
|
-
const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
|
|
1663
|
+
const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
|
|
1502
1664
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1503
1665
|
this._subdirectories.set(subdirName, subDir);
|
|
1504
1666
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1505
1667
|
return true;
|
|
1506
1668
|
}
|
|
1669
|
+
else {
|
|
1670
|
+
subdir.clientIds.add(clientId);
|
|
1671
|
+
}
|
|
1507
1672
|
return false;
|
|
1508
1673
|
}
|
|
1509
1674
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|