@fluidframework/map 1.1.0 → 1.2.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/api-extractor.json +1 -1
- package/dist/directory.d.ts +14 -10
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +289 -80
- package/dist/directory.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +29 -14
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.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 +14 -10
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +289 -80
- package/lib/directory.js.map +1 -1
- package/lib/index.d.ts +5 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -4
- package/lib/index.js.map +1 -1
- package/lib/interfaces.d.ts +29 -14
- package/lib/interfaces.d.ts.map +1 -1
- package/lib/interfaces.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 +13 -13
- package/src/directory.ts +358 -94
- package/src/index.ts +5 -5
- package/src/interfaces.ts +29 -14
- package/src/packageVersion.ts +1 -1
package/dist/directory.js
CHANGED
|
@@ -91,7 +91,7 @@ DirectoryFactory.Attributes = {
|
|
|
91
91
|
* SubDirectories can be retrieved for use as working directories.
|
|
92
92
|
*
|
|
93
93
|
* @example
|
|
94
|
-
* ```
|
|
94
|
+
* ```typescript
|
|
95
95
|
* mySharedDirectory.createSubDirectory("a").createSubDirectory("b").createSubDirectory("c").set("foo", val1);
|
|
96
96
|
* const mySubDir = mySharedDirectory.getWorkingDirectory("/a/b/c");
|
|
97
97
|
* mySubDir.get("foo"); // returns val1
|
|
@@ -387,6 +387,17 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
387
387
|
handler.process(op, local, localOpMetadata);
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
394
|
+
rollback(content, localOpMetadata) {
|
|
395
|
+
const op = content;
|
|
396
|
+
const subdir = this.getWorkingDirectory(op.path);
|
|
397
|
+
if (subdir) {
|
|
398
|
+
subdir.rollback(op, localOpMetadata);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
390
401
|
/**
|
|
391
402
|
* Converts the given relative path to absolute against the root.
|
|
392
403
|
* @param relativePath - The path to convert
|
|
@@ -422,8 +433,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
422
433
|
submit: (op, localOpMetadata) => {
|
|
423
434
|
const subdir = this.getWorkingDirectory(op.path);
|
|
424
435
|
if (subdir) {
|
|
425
|
-
|
|
426
|
-
subdir.submitClearMessage(op);
|
|
436
|
+
subdir.resubmitClearMessage(op, localOpMetadata);
|
|
427
437
|
}
|
|
428
438
|
},
|
|
429
439
|
});
|
|
@@ -437,8 +447,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
437
447
|
submit: (op, localOpMetadata) => {
|
|
438
448
|
const subdir = this.getWorkingDirectory(op.path);
|
|
439
449
|
if (subdir) {
|
|
440
|
-
|
|
441
|
-
subdir.submitKeyMessage(op);
|
|
450
|
+
subdir.resubmitKeyMessage(op, localOpMetadata);
|
|
442
451
|
}
|
|
443
452
|
},
|
|
444
453
|
});
|
|
@@ -453,8 +462,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
453
462
|
submit: (op, localOpMetadata) => {
|
|
454
463
|
const subdir = this.getWorkingDirectory(op.path);
|
|
455
464
|
if (subdir) {
|
|
456
|
-
|
|
457
|
-
subdir.submitKeyMessage(op);
|
|
465
|
+
subdir.resubmitKeyMessage(op, localOpMetadata);
|
|
458
466
|
}
|
|
459
467
|
},
|
|
460
468
|
});
|
|
@@ -469,7 +477,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
469
477
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
470
478
|
if (parentSubdir) {
|
|
471
479
|
// We don't reuse the metadata but send a new one on each submit.
|
|
472
|
-
parentSubdir.
|
|
480
|
+
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
473
481
|
}
|
|
474
482
|
},
|
|
475
483
|
});
|
|
@@ -484,7 +492,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
484
492
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
485
493
|
if (parentSubdir) {
|
|
486
494
|
// We don't reuse the metadata but send a new one on each submit.
|
|
487
|
-
parentSubdir.
|
|
495
|
+
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
488
496
|
}
|
|
489
497
|
},
|
|
490
498
|
});
|
|
@@ -553,6 +561,24 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
553
561
|
}
|
|
554
562
|
}
|
|
555
563
|
exports.SharedDirectory = SharedDirectory;
|
|
564
|
+
function isKeyEditLocalOpMetadata(metadata) {
|
|
565
|
+
return metadata !== undefined && typeof metadata.pendingMessageId === "number" && metadata.type === "edit";
|
|
566
|
+
}
|
|
567
|
+
function isClearLocalOpMetadata(metadata) {
|
|
568
|
+
return metadata !== undefined && metadata.type === "clear" && typeof metadata.pendingMessageId === "number" &&
|
|
569
|
+
typeof metadata.previousStorage === "object";
|
|
570
|
+
}
|
|
571
|
+
function isSubDirLocalOpMetadata(metadata) {
|
|
572
|
+
return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
|
|
573
|
+
((metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean") ||
|
|
574
|
+
metadata.type === "deleteSubDir");
|
|
575
|
+
}
|
|
576
|
+
function isDirectoryLocalOpMetadata(metadata) {
|
|
577
|
+
return metadata !== undefined && typeof metadata.pendingMessageId === "number" &&
|
|
578
|
+
(metadata.type === "edit" || metadata.type === "deleteSubDir" ||
|
|
579
|
+
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
580
|
+
(metadata.type === "createSubDir" && typeof metadata.previouslyExisted === "boolean"));
|
|
581
|
+
}
|
|
556
582
|
/**
|
|
557
583
|
* Node of the directory tree.
|
|
558
584
|
* @sealed
|
|
@@ -572,9 +598,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
572
598
|
this.serializer = serializer;
|
|
573
599
|
this.absolutePath = absolutePath;
|
|
574
600
|
/**
|
|
575
|
-
* Tells if the sub directory is
|
|
601
|
+
* Tells if the sub directory is deleted or not.
|
|
576
602
|
*/
|
|
577
|
-
this.
|
|
603
|
+
this._deleted = false;
|
|
578
604
|
/**
|
|
579
605
|
* String representation for the class.
|
|
580
606
|
*/
|
|
@@ -600,20 +626,25 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
600
626
|
*/
|
|
601
627
|
this.pendingMessageId = -1;
|
|
602
628
|
/**
|
|
603
|
-
*
|
|
604
|
-
* of that clear operation. Otherwise, is -1.
|
|
629
|
+
* The pending ids of any clears that have been performed locally but not yet ack'd from the server
|
|
605
630
|
*/
|
|
606
|
-
this.
|
|
631
|
+
this.pendingClearMessageIds = [];
|
|
607
632
|
}
|
|
608
633
|
dispose(error) {
|
|
609
|
-
this.
|
|
634
|
+
this._deleted = true;
|
|
610
635
|
this.emit("disposed", this);
|
|
611
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* Unmark the deleted property when rolling back delete.
|
|
639
|
+
*/
|
|
640
|
+
undispose() {
|
|
641
|
+
this._deleted = false;
|
|
642
|
+
}
|
|
612
643
|
get disposed() {
|
|
613
|
-
return this.
|
|
644
|
+
return this._deleted;
|
|
614
645
|
}
|
|
615
646
|
throwIfDisposed() {
|
|
616
|
-
if (this.
|
|
647
|
+
if (this._deleted) {
|
|
617
648
|
throw new container_utils_1.UsageError("Cannot access Disposed subDirectory");
|
|
618
649
|
}
|
|
619
650
|
}
|
|
@@ -647,7 +678,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
647
678
|
const localValue = this.directory.localValueMaker.fromInMemory(value);
|
|
648
679
|
const serializableValue = (0, localValues_1.makeSerializable)(localValue, this.serializer, this.directory.handle);
|
|
649
680
|
// Set the value locally.
|
|
650
|
-
this.setCore(key, localValue, true);
|
|
681
|
+
const previousValue = this.setCore(key, localValue, true);
|
|
651
682
|
// If we are not attached, don't submit the op.
|
|
652
683
|
if (!this.directory.isAttached()) {
|
|
653
684
|
return this;
|
|
@@ -658,7 +689,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
658
689
|
type: "set",
|
|
659
690
|
value: serializableValue,
|
|
660
691
|
};
|
|
661
|
-
this.submitKeyMessage(op);
|
|
692
|
+
this.submitKeyMessage(op, previousValue);
|
|
662
693
|
return this;
|
|
663
694
|
}
|
|
664
695
|
/**
|
|
@@ -680,7 +711,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
680
711
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
681
712
|
}
|
|
682
713
|
// Create the sub directory locally first.
|
|
683
|
-
this.createSubDirectoryCore(subdirName, true);
|
|
714
|
+
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
684
715
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
685
716
|
const subDir = this._subdirectories.get(subdirName);
|
|
686
717
|
// If we are not attached, don't submit the op.
|
|
@@ -692,7 +723,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
692
723
|
subdirName,
|
|
693
724
|
type: "createSubDirectory",
|
|
694
725
|
};
|
|
695
|
-
this.
|
|
726
|
+
this.submitCreateSubDirectoryMessage(op, !isNew);
|
|
696
727
|
return subDir;
|
|
697
728
|
}
|
|
698
729
|
/**
|
|
@@ -715,18 +746,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
715
746
|
deleteSubDirectory(subdirName) {
|
|
716
747
|
this.throwIfDisposed();
|
|
717
748
|
// Delete the sub directory locally first.
|
|
718
|
-
const
|
|
749
|
+
const subDir = this.deleteSubDirectoryCore(subdirName, true);
|
|
719
750
|
// If we are not attached, don't submit the op.
|
|
720
751
|
if (!this.directory.isAttached()) {
|
|
721
|
-
return
|
|
752
|
+
return subDir !== undefined;
|
|
722
753
|
}
|
|
723
754
|
const op = {
|
|
724
755
|
path: this.absolutePath,
|
|
725
756
|
subdirName,
|
|
726
757
|
type: "deleteSubDirectory",
|
|
727
758
|
};
|
|
728
|
-
this.
|
|
729
|
-
return
|
|
759
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
760
|
+
return subDir !== undefined;
|
|
730
761
|
}
|
|
731
762
|
/**
|
|
732
763
|
* {@inheritDoc IDirectory.subdirectories}
|
|
@@ -750,35 +781,36 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
750
781
|
delete(key) {
|
|
751
782
|
this.throwIfDisposed();
|
|
752
783
|
// Delete the key locally first.
|
|
753
|
-
const
|
|
784
|
+
const previousValue = this.deleteCore(key, true);
|
|
754
785
|
// If we are not attached, don't submit the op.
|
|
755
786
|
if (!this.directory.isAttached()) {
|
|
756
|
-
return
|
|
787
|
+
return previousValue !== undefined;
|
|
757
788
|
}
|
|
758
789
|
const op = {
|
|
759
790
|
key,
|
|
760
791
|
path: this.absolutePath,
|
|
761
792
|
type: "delete",
|
|
762
793
|
};
|
|
763
|
-
this.submitKeyMessage(op);
|
|
764
|
-
return
|
|
794
|
+
this.submitKeyMessage(op, previousValue);
|
|
795
|
+
return previousValue !== undefined;
|
|
765
796
|
}
|
|
766
797
|
/**
|
|
767
798
|
* Deletes all keys from within this IDirectory.
|
|
768
799
|
*/
|
|
769
800
|
clear() {
|
|
770
801
|
this.throwIfDisposed();
|
|
771
|
-
// Clear the data locally first.
|
|
772
|
-
this.clearCore(true);
|
|
773
802
|
// If we are not attached, don't submit the op.
|
|
774
803
|
if (!this.directory.isAttached()) {
|
|
804
|
+
this.clearCore(true);
|
|
775
805
|
return;
|
|
776
806
|
}
|
|
807
|
+
const copy = new Map(this._storage);
|
|
808
|
+
this.clearCore(true);
|
|
777
809
|
const op = {
|
|
778
810
|
path: this.absolutePath,
|
|
779
811
|
type: "clear",
|
|
780
812
|
};
|
|
781
|
-
this.submitClearMessage(op);
|
|
813
|
+
this.submitClearMessage(op, copy);
|
|
782
814
|
}
|
|
783
815
|
/**
|
|
784
816
|
* Issue a callback on each entry under this IDirectory.
|
|
@@ -873,15 +905,12 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
873
905
|
processClearMessage(op, local, localOpMetadata) {
|
|
874
906
|
this.throwIfDisposed();
|
|
875
907
|
if (local) {
|
|
876
|
-
(0, common_utils_1.assert)(localOpMetadata
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
this.pendingClearMessageId = -1;
|
|
880
|
-
}
|
|
908
|
+
(0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x00f /* `pendingMessageId is missing from the local client's ${op.type} operation` */);
|
|
909
|
+
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
910
|
+
(0, common_utils_1.assert)(pendingClearMessageId === localOpMetadata.pendingMessageId, 0x32a /* pendingMessageId does not match */);
|
|
881
911
|
return;
|
|
882
912
|
}
|
|
883
913
|
this.clearExceptPendingKeys();
|
|
884
|
-
this.directory.emit("clear", local, this.directory);
|
|
885
914
|
}
|
|
886
915
|
/**
|
|
887
916
|
* Process a delete operation.
|
|
@@ -929,7 +958,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
929
958
|
*/
|
|
930
959
|
processCreateSubDirectoryMessage(op, local, localOpMetadata) {
|
|
931
960
|
this.throwIfDisposed();
|
|
932
|
-
if (!this.
|
|
961
|
+
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
933
962
|
return;
|
|
934
963
|
}
|
|
935
964
|
this.createSubDirectoryCore(op.subdirName, local);
|
|
@@ -945,7 +974,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
945
974
|
*/
|
|
946
975
|
processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
|
|
947
976
|
this.throwIfDisposed();
|
|
948
|
-
if (!this.
|
|
977
|
+
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
949
978
|
return;
|
|
950
979
|
}
|
|
951
980
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -953,35 +982,139 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
953
982
|
/**
|
|
954
983
|
* Submit a clear operation.
|
|
955
984
|
* @param op - The operation
|
|
956
|
-
* @internal
|
|
957
985
|
*/
|
|
958
|
-
submitClearMessage(op) {
|
|
986
|
+
submitClearMessage(op, previousValue) {
|
|
959
987
|
this.throwIfDisposed();
|
|
988
|
+
const pendingMsgId = ++this.pendingMessageId;
|
|
989
|
+
this.pendingClearMessageIds.push(pendingMsgId);
|
|
990
|
+
const metadata = {
|
|
991
|
+
type: "clear",
|
|
992
|
+
pendingMessageId: pendingMsgId,
|
|
993
|
+
previousStorage: previousValue,
|
|
994
|
+
};
|
|
995
|
+
this.directory.submitDirectoryMessage(op, metadata);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Resubmit a clear operation.
|
|
999
|
+
* @param op - The operation
|
|
1000
|
+
* @internal
|
|
1001
|
+
*/
|
|
1002
|
+
resubmitClearMessage(op, localOpMetadata) {
|
|
1003
|
+
(0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x32b /* Invalid localOpMetadata for clear */);
|
|
1004
|
+
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1005
|
+
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
1006
|
+
(0, common_utils_1.assert)(pendingClearMessageId === localOpMetadata.pendingMessageId, 0x32c /* pendingMessageId does not match */);
|
|
1007
|
+
this.submitClearMessage(op, localOpMetadata.previousStorage);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Get a new pending message id for the op and cache it to track the pending op
|
|
1011
|
+
*/
|
|
1012
|
+
getKeyMessageId(op) {
|
|
1013
|
+
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
960
1014
|
const pendingMessageId = ++this.pendingMessageId;
|
|
961
|
-
this.
|
|
962
|
-
|
|
1015
|
+
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1016
|
+
if (pendingMessageIds !== undefined) {
|
|
1017
|
+
pendingMessageIds.push(pendingMessageId);
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
this.pendingKeys.set(op.key, [pendingMessageId]);
|
|
1021
|
+
}
|
|
1022
|
+
return pendingMessageId;
|
|
963
1023
|
}
|
|
964
1024
|
/**
|
|
965
1025
|
* Submit a key operation.
|
|
966
1026
|
* @param op - The operation
|
|
1027
|
+
* @param previousValue - The value of the key before this op
|
|
1028
|
+
*/
|
|
1029
|
+
submitKeyMessage(op, previousValue) {
|
|
1030
|
+
this.throwIfDisposed();
|
|
1031
|
+
const pendingMessageId = this.getKeyMessageId(op);
|
|
1032
|
+
const localMetadata = { type: "edit", pendingMessageId, previousValue };
|
|
1033
|
+
this.directory.submitDirectoryMessage(op, localMetadata);
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Submit a key message to remote clients based on a previous submit.
|
|
1037
|
+
* @param op - The map key message
|
|
1038
|
+
* @param localOpMetadata - Metadata from the previous submit
|
|
967
1039
|
* @internal
|
|
968
1040
|
*/
|
|
969
|
-
|
|
1041
|
+
resubmitKeyMessage(op, localOpMetadata) {
|
|
1042
|
+
(0, common_utils_1.assert)(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
|
|
1043
|
+
// clear the old pending message id
|
|
1044
|
+
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1045
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
|
|
1046
|
+
pendingMessageIds.shift();
|
|
1047
|
+
if (pendingMessageIds.length === 0) {
|
|
1048
|
+
this.pendingKeys.delete(op.key);
|
|
1049
|
+
}
|
|
1050
|
+
this.submitKeyMessage(op, localOpMetadata.previousValue);
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Get a new pending message id for the op and cache it to track the pending op
|
|
1054
|
+
*/
|
|
1055
|
+
getSubDirMessageId(op) {
|
|
1056
|
+
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1057
|
+
const newMessageId = ++this.pendingMessageId;
|
|
1058
|
+
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1059
|
+
if (pendingMessageIds !== undefined) {
|
|
1060
|
+
pendingMessageIds.push(newMessageId);
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1064
|
+
}
|
|
1065
|
+
return newMessageId;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Submit a create subdirectory operation.
|
|
1069
|
+
* @param op - The operation
|
|
1070
|
+
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1071
|
+
*/
|
|
1072
|
+
submitCreateSubDirectoryMessage(op, prevExisted) {
|
|
970
1073
|
this.throwIfDisposed();
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
1074
|
+
const newMessageId = this.getSubDirMessageId(op);
|
|
1075
|
+
const localOpMetadata = {
|
|
1076
|
+
type: "createSubDir",
|
|
1077
|
+
pendingMessageId: newMessageId,
|
|
1078
|
+
previouslyExisted: prevExisted,
|
|
1079
|
+
};
|
|
1080
|
+
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
974
1081
|
}
|
|
975
1082
|
/**
|
|
976
|
-
* Submit a subdirectory operation.
|
|
1083
|
+
* Submit a delete subdirectory operation.
|
|
977
1084
|
* @param op - The operation
|
|
978
|
-
* @
|
|
1085
|
+
* @param subDir - Any subdirectory deleted by the op
|
|
979
1086
|
*/
|
|
980
|
-
|
|
1087
|
+
submitDeleteSubDirectoryMessage(op, subDir) {
|
|
981
1088
|
this.throwIfDisposed();
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
1089
|
+
const newMessageId = this.getSubDirMessageId(op);
|
|
1090
|
+
const localOpMetadata = {
|
|
1091
|
+
type: "deleteSubDir",
|
|
1092
|
+
pendingMessageId: newMessageId,
|
|
1093
|
+
subDirectory: subDir,
|
|
1094
|
+
};
|
|
1095
|
+
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Submit a subdirectory operation again
|
|
1099
|
+
* @param op - The operation
|
|
1100
|
+
* @param localOpMetadata - metadata submitted with the op originally
|
|
1101
|
+
* @internal
|
|
1102
|
+
*/
|
|
1103
|
+
resubmitSubDirectoryMessage(op, localOpMetadata) {
|
|
1104
|
+
(0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
|
|
1105
|
+
// clear the old pending message id
|
|
1106
|
+
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1107
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
|
|
1108
|
+
pendingMessageIds.shift();
|
|
1109
|
+
if (pendingMessageIds.length === 0) {
|
|
1110
|
+
this.pendingSubDirectories.delete(op.subdirName);
|
|
1111
|
+
}
|
|
1112
|
+
if (localOpMetadata.type === "createSubDir") {
|
|
1113
|
+
this.submitCreateSubDirectoryMessage(op, localOpMetadata.previouslyExisted);
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
1116
|
+
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
1117
|
+
}
|
|
985
1118
|
}
|
|
986
1119
|
/**
|
|
987
1120
|
* Get the storage of this subdirectory in a serializable format, to be used in snapshotting.
|
|
@@ -1028,6 +1161,67 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1028
1161
|
this.throwIfDisposed();
|
|
1029
1162
|
return this._storage.get(key);
|
|
1030
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Remove the pendingMessageId from the map tracking it on rollback
|
|
1166
|
+
* @param map - map tracking the pending messages
|
|
1167
|
+
* @param key - key of the edit in the op
|
|
1168
|
+
*/
|
|
1169
|
+
rollbackPendingMessageId(map, key, pendingMessageId) {
|
|
1170
|
+
const pendingMessageIds = map.get(key);
|
|
1171
|
+
const lastPendingMessageId = pendingMessageIds === null || pendingMessageIds === void 0 ? void 0 : pendingMessageIds.pop();
|
|
1172
|
+
if (!pendingMessageIds || lastPendingMessageId !== pendingMessageId) {
|
|
1173
|
+
throw new Error("Rollback op does not match last pending");
|
|
1174
|
+
}
|
|
1175
|
+
if (pendingMessageIds.length === 0) {
|
|
1176
|
+
map.delete(key);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Rollback a local op
|
|
1181
|
+
* @param op - The operation to rollback
|
|
1182
|
+
* @param localOpMetadata - The local metadata associated with the op.
|
|
1183
|
+
*/
|
|
1184
|
+
rollback(op, localOpMetadata) {
|
|
1185
|
+
if (!isDirectoryLocalOpMetadata(localOpMetadata)) {
|
|
1186
|
+
throw new Error("Invalid localOpMetadata");
|
|
1187
|
+
}
|
|
1188
|
+
if (op.type === "clear" && localOpMetadata.type === "clear") {
|
|
1189
|
+
localOpMetadata.previousStorage.forEach((localValue, key) => {
|
|
1190
|
+
this.setCore(key, localValue, true);
|
|
1191
|
+
});
|
|
1192
|
+
const lastPendingClearId = this.pendingClearMessageIds.pop();
|
|
1193
|
+
if (lastPendingClearId === undefined || lastPendingClearId !== localOpMetadata.pendingMessageId) {
|
|
1194
|
+
throw new Error("Rollback op does match last clear");
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
else if ((op.type === "delete" || op.type === "set") && localOpMetadata.type === "edit") {
|
|
1198
|
+
if (localOpMetadata.previousValue === undefined) {
|
|
1199
|
+
this.deleteCore(op.key, true);
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
this.setCore(op.key, localOpMetadata.previousValue, true);
|
|
1203
|
+
}
|
|
1204
|
+
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1205
|
+
}
|
|
1206
|
+
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1207
|
+
if (!localOpMetadata.previouslyExisted) {
|
|
1208
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1209
|
+
}
|
|
1210
|
+
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1211
|
+
}
|
|
1212
|
+
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
1213
|
+
if (localOpMetadata.subDirectory !== undefined) {
|
|
1214
|
+
this.undeleteSubDirectoryTree(localOpMetadata.subDirectory);
|
|
1215
|
+
// don't need to register events because deleting never unregistered
|
|
1216
|
+
this._subdirectories.set(op.subdirName, localOpMetadata.subDirectory);
|
|
1217
|
+
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1218
|
+
}
|
|
1219
|
+
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1220
|
+
}
|
|
1221
|
+
else {
|
|
1222
|
+
throw new Error("Unsupported op for rollback");
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1031
1225
|
/**
|
|
1032
1226
|
* Converts the given relative path into an absolute path.
|
|
1033
1227
|
* @param path - Relative path to convert
|
|
@@ -1040,16 +1234,16 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1040
1234
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1041
1235
|
* not process the incoming operation.
|
|
1042
1236
|
* @param op - Operation to check
|
|
1043
|
-
* @param local - Whether the
|
|
1044
|
-
* @param
|
|
1045
|
-
*
|
|
1046
|
-
* For messages from a remote client, this will be undefined.
|
|
1237
|
+
* @param local - Whether the operation originated from the local client
|
|
1238
|
+
* @param localOpMetadata - For local client ops, this is the metadata that was submitted with the op.
|
|
1239
|
+
* For ops from a remote client, this will be undefined.
|
|
1047
1240
|
* @returns True if the operation should be processed, false otherwise
|
|
1048
1241
|
*/
|
|
1049
1242
|
needProcessStorageOperation(op, local, localOpMetadata) {
|
|
1050
|
-
if (this.
|
|
1243
|
+
if (this.pendingClearMessageIds.length > 0) {
|
|
1051
1244
|
if (local) {
|
|
1052
|
-
(0, common_utils_1.assert)(localOpMetadata !== undefined && localOpMetadata
|
|
1245
|
+
(0, common_utils_1.assert)(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1246
|
+
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1053
1247
|
}
|
|
1054
1248
|
// If I have a NACK clear, we can ignore all ops.
|
|
1055
1249
|
return false;
|
|
@@ -1059,9 +1253,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1059
1253
|
// Found an NACK op, clear it from the directory if the latest sequence number in the directory
|
|
1060
1254
|
// match the message's and don't process the op.
|
|
1061
1255
|
if (local) {
|
|
1062
|
-
(0, common_utils_1.assert)(localOpMetadata !== undefined, 0x011 /* pendingMessageId is missing from the local client's operation */);
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1256
|
+
(0, common_utils_1.assert)(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata), 0x011 /* pendingMessageId is missing from the local client's operation */);
|
|
1257
|
+
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1258
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
|
|
1259
|
+
pendingMessageIds.shift();
|
|
1260
|
+
if (pendingMessageIds.length === 0) {
|
|
1065
1261
|
this.pendingKeys.delete(op.key);
|
|
1066
1262
|
}
|
|
1067
1263
|
}
|
|
@@ -1080,13 +1276,15 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1080
1276
|
* For messages from a remote client, this will be undefined.
|
|
1081
1277
|
* @returns True if the operation should be processed, false otherwise
|
|
1082
1278
|
*/
|
|
1083
|
-
|
|
1279
|
+
needProcessSubDirectoryOperation(op, local, localOpMetadata) {
|
|
1084
1280
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1085
1281
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1086
1282
|
if (local) {
|
|
1087
|
-
(0, common_utils_1.assert)(localOpMetadata
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1283
|
+
(0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
|
|
1284
|
+
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1285
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
|
|
1286
|
+
pendingMessageIds.shift();
|
|
1287
|
+
if (pendingMessageIds.length === 0) {
|
|
1090
1288
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1091
1289
|
}
|
|
1092
1290
|
}
|
|
@@ -1105,9 +1303,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1105
1303
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1106
1304
|
temp.set(key, this._storage.get(key));
|
|
1107
1305
|
});
|
|
1108
|
-
this.
|
|
1306
|
+
this.clearCore(false);
|
|
1109
1307
|
temp.forEach((value, key, map) => {
|
|
1110
|
-
this.
|
|
1308
|
+
this.setCore(key, value, true);
|
|
1111
1309
|
});
|
|
1112
1310
|
}
|
|
1113
1311
|
/**
|
|
@@ -1123,11 +1321,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1123
1321
|
* Delete implementation used for both locally sourced deletes as well as incoming remote deletes.
|
|
1124
1322
|
* @param key - The key being deleted
|
|
1125
1323
|
* @param local - Whether the message originated from the local client
|
|
1126
|
-
* @
|
|
1127
|
-
* @returns True if the key existed and was deleted, false if it did not exist
|
|
1324
|
+
* @returns Previous local value of the key if it existed, undefined if it did not exist
|
|
1128
1325
|
*/
|
|
1129
1326
|
deleteCore(key, local) {
|
|
1130
|
-
const
|
|
1327
|
+
const previousLocalValue = this._storage.get(key);
|
|
1328
|
+
const previousValue = previousLocalValue === null || previousLocalValue === void 0 ? void 0 : previousLocalValue.value;
|
|
1131
1329
|
const successfullyRemoved = this._storage.delete(key);
|
|
1132
1330
|
if (successfullyRemoved) {
|
|
1133
1331
|
const event = { key, path: this.absolutePath, previousValue };
|
|
@@ -1135,27 +1333,30 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1135
1333
|
const containedEvent = { key, previousValue };
|
|
1136
1334
|
this.emit("containedValueChanged", containedEvent, local, this);
|
|
1137
1335
|
}
|
|
1138
|
-
return
|
|
1336
|
+
return previousLocalValue;
|
|
1139
1337
|
}
|
|
1140
1338
|
/**
|
|
1141
1339
|
* Set implementation used for both locally sourced sets as well as incoming remote sets.
|
|
1142
1340
|
* @param key - The key being set
|
|
1143
1341
|
* @param value - The value being set
|
|
1144
1342
|
* @param local - Whether the message originated from the local client
|
|
1145
|
-
* @
|
|
1343
|
+
* @returns Previous local value of the key, if any
|
|
1146
1344
|
*/
|
|
1147
1345
|
setCore(key, value, local) {
|
|
1148
|
-
const
|
|
1346
|
+
const previousLocalValue = this._storage.get(key);
|
|
1347
|
+
const previousValue = previousLocalValue === null || previousLocalValue === void 0 ? void 0 : previousLocalValue.value;
|
|
1149
1348
|
this._storage.set(key, value);
|
|
1150
1349
|
const event = { key, path: this.absolutePath, previousValue };
|
|
1151
1350
|
this.directory.emit("valueChanged", event, local, this.directory);
|
|
1152
1351
|
const containedEvent = { key, previousValue };
|
|
1153
1352
|
this.emit("containedValueChanged", containedEvent, local, this);
|
|
1353
|
+
return previousLocalValue;
|
|
1154
1354
|
}
|
|
1155
1355
|
/**
|
|
1156
1356
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1157
1357
|
* @param subdirName - The name of the subdirectory being created
|
|
1158
1358
|
* @param local - Whether the message originated from the local client
|
|
1359
|
+
* @returns - True if is newly created, false if it already existed.
|
|
1159
1360
|
*/
|
|
1160
1361
|
createSubDirectoryCore(subdirName, local) {
|
|
1161
1362
|
if (!this._subdirectories.has(subdirName)) {
|
|
@@ -1164,7 +1365,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1164
1365
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1165
1366
|
this._subdirectories.set(subdirName, subDir);
|
|
1166
1367
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1368
|
+
return true;
|
|
1167
1369
|
}
|
|
1370
|
+
return false;
|
|
1168
1371
|
}
|
|
1169
1372
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|
|
1170
1373
|
subDirectory.on("subDirectoryCreated", (relativePath, local) => {
|
|
@@ -1178,18 +1381,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1178
1381
|
* Delete subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1179
1382
|
* @param subdirName - The name of the subdirectory being deleted
|
|
1180
1383
|
* @param local - Whether the message originated from the local client
|
|
1181
|
-
* @param op - The message if from a remote delete, or null if from a local delete
|
|
1182
1384
|
*/
|
|
1183
1385
|
deleteSubDirectoryCore(subdirName, local) {
|
|
1184
|
-
const previousValue = this.
|
|
1386
|
+
const previousValue = this._subdirectories.get(subdirName);
|
|
1185
1387
|
// This should make the subdirectory structure unreachable so it can be GC'd and won't appear in snapshots
|
|
1186
|
-
// Might want to consider cleaning out the structure more exhaustively though?
|
|
1187
|
-
const successfullyRemoved = this._subdirectories.delete(subdirName);
|
|
1388
|
+
// Might want to consider cleaning out the structure more exhaustively though? But not when rollback.
|
|
1188
1389
|
if (previousValue !== undefined) {
|
|
1390
|
+
this._subdirectories.delete(subdirName);
|
|
1189
1391
|
this.disposeSubDirectoryTree(previousValue);
|
|
1190
1392
|
this.emit("subDirectoryDeleted", subdirName, local, this);
|
|
1191
1393
|
}
|
|
1192
|
-
return
|
|
1394
|
+
return previousValue;
|
|
1193
1395
|
}
|
|
1194
1396
|
disposeSubDirectoryTree(directory) {
|
|
1195
1397
|
if (!directory) {
|
|
@@ -1204,5 +1406,12 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1204
1406
|
directory.dispose();
|
|
1205
1407
|
}
|
|
1206
1408
|
}
|
|
1409
|
+
undeleteSubDirectoryTree(directory) {
|
|
1410
|
+
// Restore deleted subdirectory tree. This will unmark "deleted" from the subdirectories from bottom to top.
|
|
1411
|
+
for (const [_, subDirectory] of this._subdirectories.entries()) {
|
|
1412
|
+
this.undeleteSubDirectoryTree(subDirectory);
|
|
1413
|
+
}
|
|
1414
|
+
directory.undispose();
|
|
1415
|
+
}
|
|
1207
1416
|
}
|
|
1208
1417
|
//# sourceMappingURL=directory.js.map
|