@fluidframework/map 2.0.0-internal.3.3.2 → 2.0.0-internal.3.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.
- package/dist/directory.d.ts +32 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +219 -60
- 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 +32 -0
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +219 -60
- 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 +21 -22
- package/src/directory.ts +308 -58
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +1 -1
package/lib/directory.js
CHANGED
|
@@ -92,7 +92,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
92
92
|
/**
|
|
93
93
|
* Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
|
|
94
94
|
*/
|
|
95
|
-
this.root = new SubDirectory(this, this.runtime, this.serializer, posix.sep);
|
|
95
|
+
this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
|
|
96
96
|
/**
|
|
97
97
|
* Mapping of op types to message handlers.
|
|
98
98
|
*/
|
|
@@ -348,7 +348,10 @@ export class SharedDirectory extends SharedObject {
|
|
|
348
348
|
for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
|
|
349
349
|
let newSubDir = currentSubDir.getSubDirectory(subdirName);
|
|
350
350
|
if (!newSubDir) {
|
|
351
|
-
|
|
351
|
+
const createInfo = subdirObject.ci;
|
|
352
|
+
newSubDir = new SubDirectory(createInfo !== undefined ? createInfo.csn : 0, createInfo !== undefined
|
|
353
|
+
? new Set(createInfo.ccIds)
|
|
354
|
+
: new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
|
|
352
355
|
currentSubDir.populateSubDirectory(subdirName, newSubDir);
|
|
353
356
|
}
|
|
354
357
|
stack.push([newSubDir, subdirObject]);
|
|
@@ -371,7 +374,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
371
374
|
const op = message.contents;
|
|
372
375
|
const handler = this.messageHandlers.get(op.type);
|
|
373
376
|
assert(handler !== undefined, 0x00e /* Missing message handler for message type */);
|
|
374
|
-
handler.process(op, local, localOpMetadata);
|
|
377
|
+
handler.process(message, op, local, localOpMetadata);
|
|
375
378
|
}
|
|
376
379
|
}
|
|
377
380
|
/**
|
|
@@ -407,15 +410,41 @@ export class SharedDirectory extends SharedObject {
|
|
|
407
410
|
serializable.type === ValueType[ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
|
|
408
411
|
return this.localValueMaker.fromSerializable(serializable);
|
|
409
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* This checks if there is pending delete op for local delete for a subdirectory.
|
|
415
|
+
* @param relativePath - path of sub directory.
|
|
416
|
+
* @returns - true if there is pending delete.
|
|
417
|
+
*/
|
|
418
|
+
isSubDirectoryDeletePending(relativePath) {
|
|
419
|
+
const parentSubDir = this.getParentDirectory(relativePath);
|
|
420
|
+
const index = relativePath.lastIndexOf(posix.sep);
|
|
421
|
+
const dirName = relativePath.substring(index + 1);
|
|
422
|
+
return !!(parentSubDir === null || parentSubDir === void 0 ? void 0 : parentSubDir.isSubDirectoryDeletePending(dirName));
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Gets the parent directory of a sub directory.
|
|
426
|
+
* @param relativePath - path of sub directory of which parent needs to be find out.
|
|
427
|
+
*/
|
|
428
|
+
getParentDirectory(relativePath) {
|
|
429
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
430
|
+
if (absolutePath === posix.sep) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
const index = absolutePath.lastIndexOf(posix.sep);
|
|
434
|
+
const parentAbsPath = absolutePath.substring(0, index);
|
|
435
|
+
return this.getWorkingDirectory(parentAbsPath);
|
|
436
|
+
}
|
|
410
437
|
/**
|
|
411
438
|
* Set the message handlers for the directory.
|
|
412
439
|
*/
|
|
413
440
|
setMessageHandlers() {
|
|
414
441
|
this.messageHandlers.set("clear", {
|
|
415
|
-
process: (op, local, localOpMetadata) => {
|
|
442
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
416
443
|
const subdir = this.getWorkingDirectory(op.path);
|
|
417
|
-
|
|
418
|
-
|
|
444
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
445
|
+
// to delete this subDirectory.
|
|
446
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
447
|
+
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
419
448
|
}
|
|
420
449
|
},
|
|
421
450
|
submit: (op, localOpMetadata) => {
|
|
@@ -432,10 +461,12 @@ export class SharedDirectory extends SharedObject {
|
|
|
432
461
|
},
|
|
433
462
|
});
|
|
434
463
|
this.messageHandlers.set("delete", {
|
|
435
|
-
process: (op, local, localOpMetadata) => {
|
|
464
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
436
465
|
const subdir = this.getWorkingDirectory(op.path);
|
|
437
|
-
|
|
438
|
-
|
|
466
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
467
|
+
// to delete this subDirectory.
|
|
468
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
469
|
+
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
439
470
|
}
|
|
440
471
|
},
|
|
441
472
|
submit: (op, localOpMetadata) => {
|
|
@@ -452,11 +483,13 @@ export class SharedDirectory extends SharedObject {
|
|
|
452
483
|
},
|
|
453
484
|
});
|
|
454
485
|
this.messageHandlers.set("set", {
|
|
455
|
-
process: (op, local, localOpMetadata) => {
|
|
486
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
456
487
|
const subdir = this.getWorkingDirectory(op.path);
|
|
457
|
-
|
|
488
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
489
|
+
// to delete this subDirectory.
|
|
490
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
458
491
|
const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
|
|
459
|
-
subdir.processSetMessage(op, context, local, localOpMetadata);
|
|
492
|
+
subdir.processSetMessage(msg, op, context, local, localOpMetadata);
|
|
460
493
|
}
|
|
461
494
|
},
|
|
462
495
|
submit: (op, localOpMetadata) => {
|
|
@@ -474,10 +507,10 @@ export class SharedDirectory extends SharedObject {
|
|
|
474
507
|
},
|
|
475
508
|
});
|
|
476
509
|
this.messageHandlers.set("createSubDirectory", {
|
|
477
|
-
process: (op, local, localOpMetadata) => {
|
|
510
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
478
511
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
479
512
|
if (parentSubdir) {
|
|
480
|
-
parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
|
|
513
|
+
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
481
514
|
}
|
|
482
515
|
},
|
|
483
516
|
submit: (op, localOpMetadata) => {
|
|
@@ -495,10 +528,10 @@ export class SharedDirectory extends SharedObject {
|
|
|
495
528
|
},
|
|
496
529
|
});
|
|
497
530
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
498
|
-
process: (op, local, localOpMetadata) => {
|
|
531
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
499
532
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
500
533
|
if (parentSubdir) {
|
|
501
|
-
parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
|
|
534
|
+
parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
502
535
|
}
|
|
503
536
|
},
|
|
504
537
|
submit: (op, localOpMetadata) => {
|
|
@@ -538,6 +571,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
538
571
|
while (stack.length > 0) {
|
|
539
572
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
540
573
|
const [currentSubDir, currentSubDirObject] = stack.pop();
|
|
574
|
+
currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
|
|
541
575
|
for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
|
|
542
576
|
if (!currentSubDirObject.storage) {
|
|
543
577
|
currentSubDirObject.storage = {};
|
|
@@ -598,8 +632,7 @@ function isClearLocalOpMetadata(metadata) {
|
|
|
598
632
|
function isSubDirLocalOpMetadata(metadata) {
|
|
599
633
|
return (metadata !== undefined &&
|
|
600
634
|
typeof metadata.pendingMessageId === "number" &&
|
|
601
|
-
(
|
|
602
|
-
metadata.type === "deleteSubDir"));
|
|
635
|
+
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
|
|
603
636
|
}
|
|
604
637
|
function isDirectoryLocalOpMetadata(metadata) {
|
|
605
638
|
return (metadata !== undefined &&
|
|
@@ -607,7 +640,7 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
607
640
|
(metadata.type === "edit" ||
|
|
608
641
|
metadata.type === "deleteSubDir" ||
|
|
609
642
|
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
610
|
-
|
|
643
|
+
metadata.type === "createSubDir"));
|
|
611
644
|
}
|
|
612
645
|
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
613
646
|
/**
|
|
@@ -617,13 +650,17 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
617
650
|
class SubDirectory extends TypedEventEmitter {
|
|
618
651
|
/**
|
|
619
652
|
* Constructor.
|
|
653
|
+
* @param sequenceNumber - Message seq number at which this was created.
|
|
654
|
+
* @param clientIds - Ids of client which created this directory.
|
|
620
655
|
* @param directory - Reference back to the SharedDirectory to perform operations
|
|
621
656
|
* @param runtime - The data store runtime this directory is associated with
|
|
622
657
|
* @param serializer - The serializer to serialize / parse handles
|
|
623
658
|
* @param absolutePath - The absolute path of this IDirectory
|
|
624
659
|
*/
|
|
625
|
-
constructor(directory, runtime, serializer, absolutePath) {
|
|
660
|
+
constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
|
|
626
661
|
super();
|
|
662
|
+
this.sequenceNumber = sequenceNumber;
|
|
663
|
+
this.clientIds = clientIds;
|
|
627
664
|
this.directory = directory;
|
|
628
665
|
this.runtime = runtime;
|
|
629
666
|
this.serializer = serializer;
|
|
@@ -649,9 +686,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
649
686
|
*/
|
|
650
687
|
this.pendingKeys = new Map();
|
|
651
688
|
/**
|
|
652
|
-
* Subdirectories that have been
|
|
689
|
+
* Subdirectories that have been created/deleted locally but not yet ack'd from the server.
|
|
653
690
|
*/
|
|
654
691
|
this.pendingSubDirectories = new Map();
|
|
692
|
+
/**
|
|
693
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
|
|
694
|
+
* of delete op that are pending or yet to be acked from server.
|
|
695
|
+
*/
|
|
696
|
+
this.pendingDeleteSubDirectoriesCount = new Map();
|
|
655
697
|
/**
|
|
656
698
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
657
699
|
*/
|
|
@@ -734,6 +776,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
734
776
|
* {@inheritDoc IDirectory.createSubDirectory}
|
|
735
777
|
*/
|
|
736
778
|
createSubDirectory(subdirName) {
|
|
779
|
+
var _c;
|
|
737
780
|
this.throwIfDisposed();
|
|
738
781
|
// Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
|
|
739
782
|
if (subdirName === undefined || subdirName === null) {
|
|
@@ -743,19 +786,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
743
786
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
744
787
|
}
|
|
745
788
|
// Create the sub directory locally first.
|
|
746
|
-
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
747
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
789
|
+
const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
748
790
|
const subDir = this._subdirectories.get(subdirName);
|
|
791
|
+
assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
|
|
749
792
|
// If we are not attached, don't submit the op.
|
|
750
793
|
if (!this.directory.isAttached()) {
|
|
751
794
|
return subDir;
|
|
752
795
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
796
|
+
// Only submit the op, if it is newly created.
|
|
797
|
+
if (isNew) {
|
|
798
|
+
const op = {
|
|
799
|
+
path: this.absolutePath,
|
|
800
|
+
subdirName,
|
|
801
|
+
type: "createSubDirectory",
|
|
802
|
+
};
|
|
803
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
804
|
+
}
|
|
759
805
|
return subDir;
|
|
760
806
|
}
|
|
761
807
|
/**
|
|
@@ -783,12 +829,15 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
783
829
|
if (!this.directory.isAttached()) {
|
|
784
830
|
return subDir !== undefined;
|
|
785
831
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
832
|
+
// Only submit the op, if the directory existed and we deleted it.
|
|
833
|
+
if (subDir !== undefined) {
|
|
834
|
+
const op = {
|
|
835
|
+
path: this.absolutePath,
|
|
836
|
+
subdirName,
|
|
837
|
+
type: "deleteSubDirectory",
|
|
838
|
+
};
|
|
839
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
840
|
+
}
|
|
792
841
|
return subDir !== undefined;
|
|
793
842
|
}
|
|
794
843
|
/**
|
|
@@ -805,6 +854,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
805
854
|
this.throwIfDisposed();
|
|
806
855
|
return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
|
|
807
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* This checks if there is pending delete op for local delete for a given child subdirectory.
|
|
859
|
+
* @param subDirName - directory name.
|
|
860
|
+
* @returns - true if there is pending delete.
|
|
861
|
+
*/
|
|
862
|
+
isSubDirectoryDeletePending(subDirName) {
|
|
863
|
+
const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
|
|
864
|
+
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
808
869
|
/**
|
|
809
870
|
* Deletes the given key from within this IDirectory.
|
|
810
871
|
* @param key - The key to delete
|
|
@@ -920,14 +981,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
920
981
|
}
|
|
921
982
|
/**
|
|
922
983
|
* Process a clear operation.
|
|
984
|
+
* @param msg - The message from the server to apply.
|
|
923
985
|
* @param op - The op to process
|
|
924
986
|
* @param local - Whether the message originated from the local client
|
|
925
987
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
926
988
|
* For messages from a remote client, this will be undefined.
|
|
927
989
|
* @internal
|
|
928
990
|
*/
|
|
929
|
-
processClearMessage(op, local, localOpMetadata) {
|
|
991
|
+
processClearMessage(msg, op, local, localOpMetadata) {
|
|
930
992
|
this.throwIfDisposed();
|
|
993
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
931
996
|
if (local) {
|
|
932
997
|
assert(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
|
|
933
998
|
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
@@ -956,15 +1021,17 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
956
1021
|
}
|
|
957
1022
|
/**
|
|
958
1023
|
* Process a delete operation.
|
|
1024
|
+
* @param msg - The message from the server to apply.
|
|
959
1025
|
* @param op - The op to process
|
|
960
1026
|
* @param local - Whether the message originated from the local client
|
|
961
1027
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
962
1028
|
* For messages from a remote client, this will be undefined.
|
|
963
1029
|
* @internal
|
|
964
1030
|
*/
|
|
965
|
-
processDeleteMessage(op, local, localOpMetadata) {
|
|
1031
|
+
processDeleteMessage(msg, op, local, localOpMetadata) {
|
|
966
1032
|
this.throwIfDisposed();
|
|
967
|
-
if (!this.
|
|
1033
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1034
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
968
1035
|
return;
|
|
969
1036
|
}
|
|
970
1037
|
this.deleteCore(op.key, local);
|
|
@@ -987,15 +1054,17 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
987
1054
|
}
|
|
988
1055
|
/**
|
|
989
1056
|
* Process a set operation.
|
|
1057
|
+
* @param msg - The message from the server to apply.
|
|
990
1058
|
* @param op - The op to process
|
|
991
1059
|
* @param local - Whether the message originated from the local client
|
|
992
1060
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
993
1061
|
* For messages from a remote client, this will be undefined.
|
|
994
1062
|
* @internal
|
|
995
1063
|
*/
|
|
996
|
-
processSetMessage(op, context, local, localOpMetadata) {
|
|
1064
|
+
processSetMessage(msg, op, context, local, localOpMetadata) {
|
|
997
1065
|
this.throwIfDisposed();
|
|
998
|
-
if (!this.
|
|
1066
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1067
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
999
1068
|
return;
|
|
1000
1069
|
}
|
|
1001
1070
|
// needProcessStorageOperation should have returned false if local is true
|
|
@@ -1023,18 +1092,19 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1023
1092
|
}
|
|
1024
1093
|
/**
|
|
1025
1094
|
* Process a create subdirectory operation.
|
|
1095
|
+
* @param msg - The message from the server to apply.
|
|
1026
1096
|
* @param op - The op to process
|
|
1027
1097
|
* @param local - Whether the message originated from the local client
|
|
1028
1098
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1029
1099
|
* For messages from a remote client, this will be undefined.
|
|
1030
1100
|
* @internal
|
|
1031
1101
|
*/
|
|
1032
|
-
processCreateSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1102
|
+
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1033
1103
|
this.throwIfDisposed();
|
|
1034
|
-
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1104
|
+
if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
|
|
1035
1105
|
return;
|
|
1036
1106
|
}
|
|
1037
|
-
this.createSubDirectoryCore(op.subdirName, local);
|
|
1107
|
+
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1038
1108
|
}
|
|
1039
1109
|
/**
|
|
1040
1110
|
* Apply createSubDirectory operation locally and generate metadata
|
|
@@ -1042,28 +1112,30 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1042
1112
|
* @returns metadata generated for stahed op
|
|
1043
1113
|
*/
|
|
1044
1114
|
applyStashedCreateSubDirMessage(op) {
|
|
1115
|
+
var _c;
|
|
1045
1116
|
this.throwIfDisposed();
|
|
1046
1117
|
// Create the sub directory locally first.
|
|
1047
|
-
|
|
1118
|
+
this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
1048
1119
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1049
1120
|
const localOpMetadata = {
|
|
1050
1121
|
type: "createSubDir",
|
|
1051
1122
|
pendingMessageId: newMessageId,
|
|
1052
|
-
previouslyExisted: !isNew,
|
|
1053
1123
|
};
|
|
1054
1124
|
return localOpMetadata;
|
|
1055
1125
|
}
|
|
1056
1126
|
/**
|
|
1057
1127
|
* Process a delete subdirectory operation.
|
|
1128
|
+
* @param msg - The message from the server to apply.
|
|
1058
1129
|
* @param op - The op to process
|
|
1059
1130
|
* @param local - Whether the message originated from the local client
|
|
1060
1131
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1061
1132
|
* For messages from a remote client, this will be undefined.
|
|
1062
1133
|
* @internal
|
|
1063
1134
|
*/
|
|
1064
|
-
processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1135
|
+
processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1065
1136
|
this.throwIfDisposed();
|
|
1066
|
-
if (!this.
|
|
1137
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1138
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1067
1139
|
return;
|
|
1068
1140
|
}
|
|
1069
1141
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1159,6 +1231,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1159
1231
|
* Get a new pending message id for the op and cache it to track the pending op
|
|
1160
1232
|
*/
|
|
1161
1233
|
getSubDirMessageId(op) {
|
|
1234
|
+
var _c;
|
|
1162
1235
|
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1163
1236
|
const newMessageId = ++this.pendingMessageId;
|
|
1164
1237
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
@@ -1168,20 +1241,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1168
1241
|
else {
|
|
1169
1242
|
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1170
1243
|
}
|
|
1244
|
+
if (op.type === "deleteSubDirectory") {
|
|
1245
|
+
const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1246
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
|
|
1247
|
+
}
|
|
1171
1248
|
return newMessageId;
|
|
1172
1249
|
}
|
|
1173
1250
|
/**
|
|
1174
1251
|
* Submit a create subdirectory operation.
|
|
1175
1252
|
* @param op - The operation
|
|
1176
|
-
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1177
1253
|
*/
|
|
1178
|
-
submitCreateSubDirectoryMessage(op
|
|
1254
|
+
submitCreateSubDirectoryMessage(op) {
|
|
1179
1255
|
this.throwIfDisposed();
|
|
1180
1256
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1181
1257
|
const localOpMetadata = {
|
|
1182
1258
|
type: "createSubDir",
|
|
1183
1259
|
pendingMessageId: newMessageId,
|
|
1184
|
-
previouslyExisted: prevExisted,
|
|
1185
1260
|
};
|
|
1186
1261
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1187
1262
|
}
|
|
@@ -1217,7 +1292,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1217
1292
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1218
1293
|
}
|
|
1219
1294
|
if (localOpMetadata.type === "createSubDir") {
|
|
1220
|
-
this.submitCreateSubDirectoryMessage(op
|
|
1295
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1221
1296
|
}
|
|
1222
1297
|
else {
|
|
1223
1298
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
@@ -1237,6 +1312,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1237
1312
|
yield res;
|
|
1238
1313
|
}
|
|
1239
1314
|
}
|
|
1315
|
+
getSerializableCreateInfo() {
|
|
1316
|
+
this.throwIfDisposed();
|
|
1317
|
+
const createInfo = {
|
|
1318
|
+
csn: this.sequenceNumber,
|
|
1319
|
+
ccIds: Array.from(this.clientIds),
|
|
1320
|
+
};
|
|
1321
|
+
return createInfo;
|
|
1322
|
+
}
|
|
1240
1323
|
/**
|
|
1241
1324
|
* Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
|
|
1242
1325
|
* @param key - The key to populate
|
|
@@ -1314,9 +1397,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1314
1397
|
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1315
1398
|
}
|
|
1316
1399
|
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1317
|
-
|
|
1318
|
-
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1319
|
-
}
|
|
1400
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1320
1401
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1321
1402
|
}
|
|
1322
1403
|
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
@@ -1327,6 +1408,12 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1327
1408
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1328
1409
|
}
|
|
1329
1410
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1411
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1412
|
+
assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
1413
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1414
|
+
if (count === 1) {
|
|
1415
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1416
|
+
}
|
|
1330
1417
|
}
|
|
1331
1418
|
else {
|
|
1332
1419
|
throw new Error("Unsupported op for rollback");
|
|
@@ -1356,6 +1443,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1356
1443
|
assert(localOpMetadata !== undefined &&
|
|
1357
1444
|
isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1358
1445
|
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1446
|
+
// Remove all pendingMessageIds lower than first pendingClearMessageId.
|
|
1447
|
+
const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
|
|
1448
|
+
const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
|
|
1449
|
+
if (pendingKeyMessageIdArray !== undefined) {
|
|
1450
|
+
let index = 0;
|
|
1451
|
+
while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
|
|
1452
|
+
index += 1;
|
|
1453
|
+
}
|
|
1454
|
+
const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
|
|
1455
|
+
if (newPendingKeyMessageId.length === 0) {
|
|
1456
|
+
this.pendingKeys.delete(op.key);
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.pendingKeys.set(op.key, newPendingKeyMessageId);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1359
1462
|
}
|
|
1360
1463
|
// If I have a NACK clear, we can ignore all ops.
|
|
1361
1464
|
return false;
|
|
@@ -1379,6 +1482,19 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1379
1482
|
// If we don't have a NACK op on the key, we need to process the remote ops.
|
|
1380
1483
|
return !local;
|
|
1381
1484
|
}
|
|
1485
|
+
/**
|
|
1486
|
+
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
1487
|
+
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
1488
|
+
* @param msg - message for the directory
|
|
1489
|
+
*/
|
|
1490
|
+
isMessageForCurrentInstanceOfSubDirectory(msg) {
|
|
1491
|
+
// If the message is either from the creator of directory or this directory was created when
|
|
1492
|
+
// container was detached or in case this directory is already live(known to other clients)
|
|
1493
|
+
// and the op was created after the directory was created then apply this op.
|
|
1494
|
+
return (this.clientIds.has(msg.clientId) ||
|
|
1495
|
+
this.clientIds.has("detached") ||
|
|
1496
|
+
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
|
|
1497
|
+
}
|
|
1382
1498
|
/**
|
|
1383
1499
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1384
1500
|
* not process the incoming operation.
|
|
@@ -1389,7 +1505,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1389
1505
|
* For messages from a remote client, this will be undefined.
|
|
1390
1506
|
* @returns True if the operation should be processed, false otherwise
|
|
1391
1507
|
*/
|
|
1392
|
-
needProcessSubDirectoryOperation(op, local, localOpMetadata) {
|
|
1508
|
+
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1393
1509
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1394
1510
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1395
1511
|
if (local) {
|
|
@@ -1401,6 +1517,40 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1401
1517
|
if (pendingMessageIds.length === 0) {
|
|
1402
1518
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1403
1519
|
}
|
|
1520
|
+
if (op.type === "deleteSubDirectory") {
|
|
1521
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1522
|
+
assert(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
|
|
1523
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1524
|
+
if (count === 1) {
|
|
1525
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
else if (op.type === "deleteSubDirectory") {
|
|
1530
|
+
// If this is remote delete op and we have keys in this subDirectory, then we need to delete these
|
|
1531
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
1532
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1533
|
+
if (subDirectory) {
|
|
1534
|
+
subDirectory.clearExceptPendingKeys(local);
|
|
1535
|
+
// In case of remote delete op, we need to reset the creation seq number and client ids of
|
|
1536
|
+
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1537
|
+
// we will receive op for the create again.
|
|
1538
|
+
subDirectory.sequenceNumber = -1;
|
|
1539
|
+
subDirectory.clientIds.clear();
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (op.type === "createSubDirectory") {
|
|
1543
|
+
const dir = this._subdirectories.get(op.subdirName);
|
|
1544
|
+
if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
|
|
1545
|
+
// Only set the seq on the first message, could be more
|
|
1546
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
1547
|
+
}
|
|
1548
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1549
|
+
if (dir !== undefined &&
|
|
1550
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
1551
|
+
dir.sequenceNumber <= msg.sequenceNumber) {
|
|
1552
|
+
dir.clientIds.add(msg.clientId);
|
|
1553
|
+
}
|
|
1404
1554
|
}
|
|
1405
1555
|
return false;
|
|
1406
1556
|
}
|
|
@@ -1414,8 +1564,11 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1414
1564
|
// we will get the value for the pendingKeys and clear the map
|
|
1415
1565
|
const temp = new Map();
|
|
1416
1566
|
for (const [key] of this.pendingKeys) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1567
|
+
const value = this._storage.get(key);
|
|
1568
|
+
// If this key is already deleted, then we don't need to add it again.
|
|
1569
|
+
if (value !== undefined) {
|
|
1570
|
+
temp.set(key, value);
|
|
1571
|
+
}
|
|
1419
1572
|
}
|
|
1420
1573
|
this.clearCore(local);
|
|
1421
1574
|
for (const [key, value] of temp.entries()) {
|
|
@@ -1469,17 +1622,23 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1469
1622
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1470
1623
|
* @param subdirName - The name of the subdirectory being created
|
|
1471
1624
|
* @param local - Whether the message originated from the local client
|
|
1625
|
+
* @param seq - Sequence number at which this directory is created
|
|
1626
|
+
* @param clientId - Id of client which created this directory.
|
|
1472
1627
|
* @returns - True if is newly created, false if it already existed.
|
|
1473
1628
|
*/
|
|
1474
|
-
createSubDirectoryCore(subdirName, local) {
|
|
1475
|
-
|
|
1629
|
+
createSubDirectoryCore(subdirName, local, seq, clientId) {
|
|
1630
|
+
const subdir = this._subdirectories.get(subdirName);
|
|
1631
|
+
if (subdir === undefined) {
|
|
1476
1632
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1477
|
-
const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
|
|
1633
|
+
const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
|
|
1478
1634
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1479
1635
|
this._subdirectories.set(subdirName, subDir);
|
|
1480
1636
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1481
1637
|
return true;
|
|
1482
1638
|
}
|
|
1639
|
+
else {
|
|
1640
|
+
subdir.clientIds.add(clientId);
|
|
1641
|
+
}
|
|
1483
1642
|
return false;
|
|
1484
1643
|
}
|
|
1485
1644
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|