@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/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,43 @@ 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 any subdir in the relative path.
|
|
415
|
+
* @param relativePath - path of sub directory.
|
|
416
|
+
* @returns - true if there is pending delete.
|
|
417
|
+
*/
|
|
418
|
+
isSubDirectoryDeletePending(relativePath) {
|
|
419
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
420
|
+
if (absolutePath === posix.sep) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
let currentParent = this.root;
|
|
424
|
+
const nodeList = absolutePath.split(posix.sep);
|
|
425
|
+
let start = 1;
|
|
426
|
+
while (start < nodeList.length) {
|
|
427
|
+
const subDirName = nodeList[start];
|
|
428
|
+
if (currentParent.isSubDirectoryDeletePending(subDirName)) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
currentParent = currentParent.getSubDirectory(subDirName);
|
|
432
|
+
if (currentParent === undefined) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
start += 1;
|
|
436
|
+
}
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
410
439
|
/**
|
|
411
440
|
* Set the message handlers for the directory.
|
|
412
441
|
*/
|
|
413
442
|
setMessageHandlers() {
|
|
414
443
|
this.messageHandlers.set("clear", {
|
|
415
|
-
process: (op, local, localOpMetadata) => {
|
|
444
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
416
445
|
const subdir = this.getWorkingDirectory(op.path);
|
|
417
|
-
|
|
418
|
-
|
|
446
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
447
|
+
// as we are going to delete this subDirectory.
|
|
448
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
449
|
+
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
419
450
|
}
|
|
420
451
|
},
|
|
421
452
|
submit: (op, localOpMetadata) => {
|
|
@@ -432,10 +463,12 @@ export class SharedDirectory extends SharedObject {
|
|
|
432
463
|
},
|
|
433
464
|
});
|
|
434
465
|
this.messageHandlers.set("delete", {
|
|
435
|
-
process: (op, local, localOpMetadata) => {
|
|
466
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
436
467
|
const subdir = this.getWorkingDirectory(op.path);
|
|
437
|
-
|
|
438
|
-
|
|
468
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
469
|
+
// as we are going to delete this subDirectory.
|
|
470
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
471
|
+
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
439
472
|
}
|
|
440
473
|
},
|
|
441
474
|
submit: (op, localOpMetadata) => {
|
|
@@ -452,11 +485,13 @@ export class SharedDirectory extends SharedObject {
|
|
|
452
485
|
},
|
|
453
486
|
});
|
|
454
487
|
this.messageHandlers.set("set", {
|
|
455
|
-
process: (op, local, localOpMetadata) => {
|
|
488
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
456
489
|
const subdir = this.getWorkingDirectory(op.path);
|
|
457
|
-
|
|
490
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
491
|
+
// as we are going to delete this subDirectory.
|
|
492
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
458
493
|
const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
|
|
459
|
-
subdir.processSetMessage(op, context, local, localOpMetadata);
|
|
494
|
+
subdir.processSetMessage(msg, op, context, local, localOpMetadata);
|
|
460
495
|
}
|
|
461
496
|
},
|
|
462
497
|
submit: (op, localOpMetadata) => {
|
|
@@ -474,10 +509,12 @@ export class SharedDirectory extends SharedObject {
|
|
|
474
509
|
},
|
|
475
510
|
});
|
|
476
511
|
this.messageHandlers.set("createSubDirectory", {
|
|
477
|
-
process: (op, local, localOpMetadata) => {
|
|
512
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
478
513
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
479
|
-
|
|
480
|
-
|
|
514
|
+
// If there is pending delete op for any subDirectory in the op.path, then don't apply the this op
|
|
515
|
+
// as we are going to delete this subDirectory.
|
|
516
|
+
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
517
|
+
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
481
518
|
}
|
|
482
519
|
},
|
|
483
520
|
submit: (op, localOpMetadata) => {
|
|
@@ -495,10 +532,12 @@ export class SharedDirectory extends SharedObject {
|
|
|
495
532
|
},
|
|
496
533
|
});
|
|
497
534
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
498
|
-
process: (op, local, localOpMetadata) => {
|
|
535
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
499
536
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
500
|
-
|
|
501
|
-
|
|
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.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
502
541
|
}
|
|
503
542
|
},
|
|
504
543
|
submit: (op, localOpMetadata) => {
|
|
@@ -538,6 +577,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
538
577
|
while (stack.length > 0) {
|
|
539
578
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
540
579
|
const [currentSubDir, currentSubDirObject] = stack.pop();
|
|
580
|
+
currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
|
|
541
581
|
for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
|
|
542
582
|
if (!currentSubDirObject.storage) {
|
|
543
583
|
currentSubDirObject.storage = {};
|
|
@@ -598,8 +638,7 @@ function isClearLocalOpMetadata(metadata) {
|
|
|
598
638
|
function isSubDirLocalOpMetadata(metadata) {
|
|
599
639
|
return (metadata !== undefined &&
|
|
600
640
|
typeof metadata.pendingMessageId === "number" &&
|
|
601
|
-
(
|
|
602
|
-
metadata.type === "deleteSubDir"));
|
|
641
|
+
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
|
|
603
642
|
}
|
|
604
643
|
function isDirectoryLocalOpMetadata(metadata) {
|
|
605
644
|
return (metadata !== undefined &&
|
|
@@ -607,7 +646,7 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
607
646
|
(metadata.type === "edit" ||
|
|
608
647
|
metadata.type === "deleteSubDir" ||
|
|
609
648
|
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
610
|
-
|
|
649
|
+
metadata.type === "createSubDir"));
|
|
611
650
|
}
|
|
612
651
|
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
613
652
|
/**
|
|
@@ -617,13 +656,17 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
617
656
|
class SubDirectory extends TypedEventEmitter {
|
|
618
657
|
/**
|
|
619
658
|
* Constructor.
|
|
659
|
+
* @param sequenceNumber - Message seq number at which this was created.
|
|
660
|
+
* @param clientIds - Ids of client which created this directory.
|
|
620
661
|
* @param directory - Reference back to the SharedDirectory to perform operations
|
|
621
662
|
* @param runtime - The data store runtime this directory is associated with
|
|
622
663
|
* @param serializer - The serializer to serialize / parse handles
|
|
623
664
|
* @param absolutePath - The absolute path of this IDirectory
|
|
624
665
|
*/
|
|
625
|
-
constructor(directory, runtime, serializer, absolutePath) {
|
|
666
|
+
constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
|
|
626
667
|
super();
|
|
668
|
+
this.sequenceNumber = sequenceNumber;
|
|
669
|
+
this.clientIds = clientIds;
|
|
627
670
|
this.directory = directory;
|
|
628
671
|
this.runtime = runtime;
|
|
629
672
|
this.serializer = serializer;
|
|
@@ -649,9 +692,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
649
692
|
*/
|
|
650
693
|
this.pendingKeys = new Map();
|
|
651
694
|
/**
|
|
652
|
-
* Subdirectories that have been
|
|
695
|
+
* Subdirectories that have been created/deleted locally but not yet ack'd from the server.
|
|
653
696
|
*/
|
|
654
697
|
this.pendingSubDirectories = new Map();
|
|
698
|
+
/**
|
|
699
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
|
|
700
|
+
* of delete op that are pending or yet to be acked from server.
|
|
701
|
+
*/
|
|
702
|
+
this.pendingDeleteSubDirectoriesCount = new Map();
|
|
655
703
|
/**
|
|
656
704
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
657
705
|
*/
|
|
@@ -734,6 +782,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
734
782
|
* {@inheritDoc IDirectory.createSubDirectory}
|
|
735
783
|
*/
|
|
736
784
|
createSubDirectory(subdirName) {
|
|
785
|
+
var _c;
|
|
737
786
|
this.throwIfDisposed();
|
|
738
787
|
// Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
|
|
739
788
|
if (subdirName === undefined || subdirName === null) {
|
|
@@ -743,19 +792,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
743
792
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
744
793
|
}
|
|
745
794
|
// Create the sub directory locally first.
|
|
746
|
-
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
747
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
795
|
+
const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
748
796
|
const subDir = this._subdirectories.get(subdirName);
|
|
797
|
+
assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
|
|
749
798
|
// If we are not attached, don't submit the op.
|
|
750
799
|
if (!this.directory.isAttached()) {
|
|
751
800
|
return subDir;
|
|
752
801
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
802
|
+
// Only submit the op, if it is newly created.
|
|
803
|
+
if (isNew) {
|
|
804
|
+
const op = {
|
|
805
|
+
path: this.absolutePath,
|
|
806
|
+
subdirName,
|
|
807
|
+
type: "createSubDirectory",
|
|
808
|
+
};
|
|
809
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
810
|
+
}
|
|
759
811
|
return subDir;
|
|
760
812
|
}
|
|
761
813
|
/**
|
|
@@ -783,12 +835,15 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
783
835
|
if (!this.directory.isAttached()) {
|
|
784
836
|
return subDir !== undefined;
|
|
785
837
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
838
|
+
// Only submit the op, if the directory existed and we deleted it.
|
|
839
|
+
if (subDir !== undefined) {
|
|
840
|
+
const op = {
|
|
841
|
+
path: this.absolutePath,
|
|
842
|
+
subdirName,
|
|
843
|
+
type: "deleteSubDirectory",
|
|
844
|
+
};
|
|
845
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
846
|
+
}
|
|
792
847
|
return subDir !== undefined;
|
|
793
848
|
}
|
|
794
849
|
/**
|
|
@@ -805,6 +860,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
805
860
|
this.throwIfDisposed();
|
|
806
861
|
return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
|
|
807
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* This checks if there is pending delete op for local delete for a given child subdirectory.
|
|
865
|
+
* @param subDirName - directory name.
|
|
866
|
+
* @returns - true if there is pending delete.
|
|
867
|
+
*/
|
|
868
|
+
isSubDirectoryDeletePending(subDirName) {
|
|
869
|
+
const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
|
|
870
|
+
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
808
875
|
/**
|
|
809
876
|
* Deletes the given key from within this IDirectory.
|
|
810
877
|
* @param key - The key to delete
|
|
@@ -920,14 +987,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
920
987
|
}
|
|
921
988
|
/**
|
|
922
989
|
* Process a clear operation.
|
|
990
|
+
* @param msg - The message from the server to apply.
|
|
923
991
|
* @param op - The op to process
|
|
924
992
|
* @param local - Whether the message originated from the local client
|
|
925
993
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
926
994
|
* For messages from a remote client, this will be undefined.
|
|
927
995
|
* @internal
|
|
928
996
|
*/
|
|
929
|
-
processClearMessage(op, local, localOpMetadata) {
|
|
997
|
+
processClearMessage(msg, op, local, localOpMetadata) {
|
|
930
998
|
this.throwIfDisposed();
|
|
999
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
931
1002
|
if (local) {
|
|
932
1003
|
assert(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
|
|
933
1004
|
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
@@ -956,15 +1027,17 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
956
1027
|
}
|
|
957
1028
|
/**
|
|
958
1029
|
* Process a delete operation.
|
|
1030
|
+
* @param msg - The message from the server to apply.
|
|
959
1031
|
* @param op - The op to process
|
|
960
1032
|
* @param local - Whether the message originated from the local client
|
|
961
1033
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
962
1034
|
* For messages from a remote client, this will be undefined.
|
|
963
1035
|
* @internal
|
|
964
1036
|
*/
|
|
965
|
-
processDeleteMessage(op, local, localOpMetadata) {
|
|
1037
|
+
processDeleteMessage(msg, op, local, localOpMetadata) {
|
|
966
1038
|
this.throwIfDisposed();
|
|
967
|
-
if (!this.
|
|
1039
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1040
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
968
1041
|
return;
|
|
969
1042
|
}
|
|
970
1043
|
this.deleteCore(op.key, local);
|
|
@@ -987,15 +1060,17 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
987
1060
|
}
|
|
988
1061
|
/**
|
|
989
1062
|
* Process a set operation.
|
|
1063
|
+
* @param msg - The message from the server to apply.
|
|
990
1064
|
* @param op - The op to process
|
|
991
1065
|
* @param local - Whether the message originated from the local client
|
|
992
1066
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
993
1067
|
* For messages from a remote client, this will be undefined.
|
|
994
1068
|
* @internal
|
|
995
1069
|
*/
|
|
996
|
-
processSetMessage(op, context, local, localOpMetadata) {
|
|
1070
|
+
processSetMessage(msg, op, context, local, localOpMetadata) {
|
|
997
1071
|
this.throwIfDisposed();
|
|
998
|
-
if (!this.
|
|
1072
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1073
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
999
1074
|
return;
|
|
1000
1075
|
}
|
|
1001
1076
|
// needProcessStorageOperation should have returned false if local is true
|
|
@@ -1023,18 +1098,19 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1023
1098
|
}
|
|
1024
1099
|
/**
|
|
1025
1100
|
* Process a create subdirectory operation.
|
|
1101
|
+
* @param msg - The message from the server to apply.
|
|
1026
1102
|
* @param op - The op to process
|
|
1027
1103
|
* @param local - Whether the message originated from the local client
|
|
1028
1104
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1029
1105
|
* For messages from a remote client, this will be undefined.
|
|
1030
1106
|
* @internal
|
|
1031
1107
|
*/
|
|
1032
|
-
processCreateSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1108
|
+
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1033
1109
|
this.throwIfDisposed();
|
|
1034
|
-
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1110
|
+
if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
|
|
1035
1111
|
return;
|
|
1036
1112
|
}
|
|
1037
|
-
this.createSubDirectoryCore(op.subdirName, local);
|
|
1113
|
+
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1038
1114
|
}
|
|
1039
1115
|
/**
|
|
1040
1116
|
* Apply createSubDirectory operation locally and generate metadata
|
|
@@ -1042,28 +1118,30 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1042
1118
|
* @returns metadata generated for stahed op
|
|
1043
1119
|
*/
|
|
1044
1120
|
applyStashedCreateSubDirMessage(op) {
|
|
1121
|
+
var _c;
|
|
1045
1122
|
this.throwIfDisposed();
|
|
1046
1123
|
// Create the sub directory locally first.
|
|
1047
|
-
|
|
1124
|
+
this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
1048
1125
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1049
1126
|
const localOpMetadata = {
|
|
1050
1127
|
type: "createSubDir",
|
|
1051
1128
|
pendingMessageId: newMessageId,
|
|
1052
|
-
previouslyExisted: !isNew,
|
|
1053
1129
|
};
|
|
1054
1130
|
return localOpMetadata;
|
|
1055
1131
|
}
|
|
1056
1132
|
/**
|
|
1057
1133
|
* Process a delete subdirectory operation.
|
|
1134
|
+
* @param msg - The message from the server to apply.
|
|
1058
1135
|
* @param op - The op to process
|
|
1059
1136
|
* @param local - Whether the message originated from the local client
|
|
1060
1137
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1061
1138
|
* For messages from a remote client, this will be undefined.
|
|
1062
1139
|
* @internal
|
|
1063
1140
|
*/
|
|
1064
|
-
processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1141
|
+
processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1065
1142
|
this.throwIfDisposed();
|
|
1066
|
-
if (!this.
|
|
1143
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1144
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1067
1145
|
return;
|
|
1068
1146
|
}
|
|
1069
1147
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1159,6 +1237,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1159
1237
|
* Get a new pending message id for the op and cache it to track the pending op
|
|
1160
1238
|
*/
|
|
1161
1239
|
getSubDirMessageId(op) {
|
|
1240
|
+
var _c;
|
|
1162
1241
|
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1163
1242
|
const newMessageId = ++this.pendingMessageId;
|
|
1164
1243
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
@@ -1168,20 +1247,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1168
1247
|
else {
|
|
1169
1248
|
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1170
1249
|
}
|
|
1250
|
+
if (op.type === "deleteSubDirectory") {
|
|
1251
|
+
const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1252
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
|
|
1253
|
+
}
|
|
1171
1254
|
return newMessageId;
|
|
1172
1255
|
}
|
|
1173
1256
|
/**
|
|
1174
1257
|
* Submit a create subdirectory operation.
|
|
1175
1258
|
* @param op - The operation
|
|
1176
|
-
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1177
1259
|
*/
|
|
1178
|
-
submitCreateSubDirectoryMessage(op
|
|
1260
|
+
submitCreateSubDirectoryMessage(op) {
|
|
1179
1261
|
this.throwIfDisposed();
|
|
1180
1262
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1181
1263
|
const localOpMetadata = {
|
|
1182
1264
|
type: "createSubDir",
|
|
1183
1265
|
pendingMessageId: newMessageId,
|
|
1184
|
-
previouslyExisted: prevExisted,
|
|
1185
1266
|
};
|
|
1186
1267
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1187
1268
|
}
|
|
@@ -1217,7 +1298,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1217
1298
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1218
1299
|
}
|
|
1219
1300
|
if (localOpMetadata.type === "createSubDir") {
|
|
1220
|
-
this.submitCreateSubDirectoryMessage(op
|
|
1301
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1221
1302
|
}
|
|
1222
1303
|
else {
|
|
1223
1304
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
@@ -1237,6 +1318,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1237
1318
|
yield res;
|
|
1238
1319
|
}
|
|
1239
1320
|
}
|
|
1321
|
+
getSerializableCreateInfo() {
|
|
1322
|
+
this.throwIfDisposed();
|
|
1323
|
+
const createInfo = {
|
|
1324
|
+
csn: this.sequenceNumber,
|
|
1325
|
+
ccIds: Array.from(this.clientIds),
|
|
1326
|
+
};
|
|
1327
|
+
return createInfo;
|
|
1328
|
+
}
|
|
1240
1329
|
/**
|
|
1241
1330
|
* Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
|
|
1242
1331
|
* @param key - The key to populate
|
|
@@ -1314,9 +1403,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1314
1403
|
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1315
1404
|
}
|
|
1316
1405
|
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1317
|
-
|
|
1318
|
-
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1319
|
-
}
|
|
1406
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1320
1407
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1321
1408
|
}
|
|
1322
1409
|
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
@@ -1327,6 +1414,12 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1327
1414
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1328
1415
|
}
|
|
1329
1416
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1417
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1418
|
+
assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
1419
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1420
|
+
if (count === 1) {
|
|
1421
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1422
|
+
}
|
|
1330
1423
|
}
|
|
1331
1424
|
else {
|
|
1332
1425
|
throw new Error("Unsupported op for rollback");
|
|
@@ -1356,6 +1449,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1356
1449
|
assert(localOpMetadata !== undefined &&
|
|
1357
1450
|
isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1358
1451
|
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1452
|
+
// Remove all pendingMessageIds lower than first pendingClearMessageId.
|
|
1453
|
+
const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
|
|
1454
|
+
const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
|
|
1455
|
+
if (pendingKeyMessageIdArray !== undefined) {
|
|
1456
|
+
let index = 0;
|
|
1457
|
+
while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
|
|
1458
|
+
index += 1;
|
|
1459
|
+
}
|
|
1460
|
+
const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
|
|
1461
|
+
if (newPendingKeyMessageId.length === 0) {
|
|
1462
|
+
this.pendingKeys.delete(op.key);
|
|
1463
|
+
}
|
|
1464
|
+
else {
|
|
1465
|
+
this.pendingKeys.set(op.key, newPendingKeyMessageId);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1359
1468
|
}
|
|
1360
1469
|
// If I have a NACK clear, we can ignore all ops.
|
|
1361
1470
|
return false;
|
|
@@ -1379,6 +1488,19 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1379
1488
|
// If we don't have a NACK op on the key, we need to process the remote ops.
|
|
1380
1489
|
return !local;
|
|
1381
1490
|
}
|
|
1491
|
+
/**
|
|
1492
|
+
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
1493
|
+
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
1494
|
+
* @param msg - message for the directory
|
|
1495
|
+
*/
|
|
1496
|
+
isMessageForCurrentInstanceOfSubDirectory(msg) {
|
|
1497
|
+
// If the message is either from the creator of directory or this directory was created when
|
|
1498
|
+
// container was detached or in case this directory is already live(known to other clients)
|
|
1499
|
+
// and the op was created after the directory was created then apply this op.
|
|
1500
|
+
return (this.clientIds.has(msg.clientId) ||
|
|
1501
|
+
this.clientIds.has("detached") ||
|
|
1502
|
+
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
|
|
1503
|
+
}
|
|
1382
1504
|
/**
|
|
1383
1505
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1384
1506
|
* not process the incoming operation.
|
|
@@ -1389,7 +1511,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1389
1511
|
* For messages from a remote client, this will be undefined.
|
|
1390
1512
|
* @returns True if the operation should be processed, false otherwise
|
|
1391
1513
|
*/
|
|
1392
|
-
needProcessSubDirectoryOperation(op, local, localOpMetadata) {
|
|
1514
|
+
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1393
1515
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1394
1516
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1395
1517
|
if (local) {
|
|
@@ -1401,6 +1523,40 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1401
1523
|
if (pendingMessageIds.length === 0) {
|
|
1402
1524
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1403
1525
|
}
|
|
1526
|
+
if (op.type === "deleteSubDirectory") {
|
|
1527
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1528
|
+
assert(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
|
|
1529
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1530
|
+
if (count === 1) {
|
|
1531
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
else if (op.type === "deleteSubDirectory") {
|
|
1536
|
+
// If this is remote delete op and we have keys in this subDirectory, then we need to delete these
|
|
1537
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
1538
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1539
|
+
if (subDirectory) {
|
|
1540
|
+
subDirectory.clearExceptPendingKeys(local);
|
|
1541
|
+
// In case of remote delete op, we need to reset the creation seq number and client ids of
|
|
1542
|
+
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1543
|
+
// we will receive op for the create again.
|
|
1544
|
+
subDirectory.sequenceNumber = -1;
|
|
1545
|
+
subDirectory.clientIds.clear();
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
if (op.type === "createSubDirectory") {
|
|
1549
|
+
const dir = this._subdirectories.get(op.subdirName);
|
|
1550
|
+
if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
|
|
1551
|
+
// Only set the seq on the first message, could be more
|
|
1552
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
1553
|
+
}
|
|
1554
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1555
|
+
if (dir !== undefined &&
|
|
1556
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
1557
|
+
dir.sequenceNumber <= msg.sequenceNumber) {
|
|
1558
|
+
dir.clientIds.add(msg.clientId);
|
|
1559
|
+
}
|
|
1404
1560
|
}
|
|
1405
1561
|
return false;
|
|
1406
1562
|
}
|
|
@@ -1414,8 +1570,11 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1414
1570
|
// we will get the value for the pendingKeys and clear the map
|
|
1415
1571
|
const temp = new Map();
|
|
1416
1572
|
for (const [key] of this.pendingKeys) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1573
|
+
const value = this._storage.get(key);
|
|
1574
|
+
// If this key is already deleted, then we don't need to add it again.
|
|
1575
|
+
if (value !== undefined) {
|
|
1576
|
+
temp.set(key, value);
|
|
1577
|
+
}
|
|
1419
1578
|
}
|
|
1420
1579
|
this.clearCore(local);
|
|
1421
1580
|
for (const [key, value] of temp.entries()) {
|
|
@@ -1469,17 +1628,23 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1469
1628
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1470
1629
|
* @param subdirName - The name of the subdirectory being created
|
|
1471
1630
|
* @param local - Whether the message originated from the local client
|
|
1631
|
+
* @param seq - Sequence number at which this directory is created
|
|
1632
|
+
* @param clientId - Id of client which created this directory.
|
|
1472
1633
|
* @returns - True if is newly created, false if it already existed.
|
|
1473
1634
|
*/
|
|
1474
|
-
createSubDirectoryCore(subdirName, local) {
|
|
1475
|
-
|
|
1635
|
+
createSubDirectoryCore(subdirName, local, seq, clientId) {
|
|
1636
|
+
const subdir = this._subdirectories.get(subdirName);
|
|
1637
|
+
if (subdir === undefined) {
|
|
1476
1638
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1477
|
-
const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
|
|
1639
|
+
const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
|
|
1478
1640
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1479
1641
|
this._subdirectories.set(subdirName, subDir);
|
|
1480
1642
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1481
1643
|
return true;
|
|
1482
1644
|
}
|
|
1645
|
+
else {
|
|
1646
|
+
subdir.clientIds.add(clientId);
|
|
1647
|
+
}
|
|
1483
1648
|
return false;
|
|
1484
1649
|
}
|
|
1485
1650
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|