@fluidframework/map 2.53.0 → 2.60.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/CHANGELOG.md +4 -0
- package/api-report/{map.legacy.alpha.api.md → map.legacy.beta.api.md} +15 -15
- package/dist/directory.d.ts +41 -91
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +448 -465
- package/dist/directory.js.map +1 -1
- package/dist/directoryFactory.d.ts +3 -6
- package/dist/directoryFactory.d.ts.map +1 -1
- package/dist/directoryFactory.js +2 -4
- package/dist/directoryFactory.js.map +1 -1
- package/dist/interfaces.d.ts +4 -8
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/internalInterfaces.d.ts +1 -2
- package/dist/internalInterfaces.d.ts.map +1 -1
- package/dist/internalInterfaces.js.map +1 -1
- package/dist/mapFactory.d.ts +3 -6
- package/dist/mapFactory.d.ts.map +1 -1
- package/dist/mapFactory.js +2 -4
- package/dist/mapFactory.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 +41 -91
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +441 -458
- package/lib/directory.js.map +1 -1
- package/lib/directoryFactory.d.ts +3 -6
- package/lib/directoryFactory.d.ts.map +1 -1
- package/lib/directoryFactory.js +2 -4
- package/lib/directoryFactory.js.map +1 -1
- package/lib/interfaces.d.ts +4 -8
- package/lib/interfaces.d.ts.map +1 -1
- package/lib/interfaces.js.map +1 -1
- package/lib/internalInterfaces.d.ts +1 -2
- package/lib/internalInterfaces.d.ts.map +1 -1
- package/lib/internalInterfaces.js.map +1 -1
- package/lib/mapFactory.d.ts +3 -6
- package/lib/mapFactory.d.ts.map +1 -1
- package/lib/mapFactory.js +2 -4
- package/lib/mapFactory.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 +18 -19
- package/src/directory.ts +565 -574
- package/src/directoryFactory.ts +3 -6
- package/src/interfaces.ts +4 -8
- package/src/internalInterfaces.ts +1 -2
- package/src/mapFactory.ts +3 -6
- package/src/packageVersion.ts +1 -1
package/dist/directory.js
CHANGED
|
@@ -13,10 +13,9 @@ const client_utils_1 = require("@fluid-internal/client-utils");
|
|
|
13
13
|
const internal_1 = require("@fluidframework/core-utils/internal");
|
|
14
14
|
const internal_2 = require("@fluidframework/driver-definitions/internal");
|
|
15
15
|
const internal_3 = require("@fluidframework/driver-utils/internal");
|
|
16
|
-
const internal_4 = require("@fluidframework/
|
|
17
|
-
const internal_5 = require("@fluidframework/
|
|
18
|
-
const internal_6 = require("@fluidframework/
|
|
19
|
-
const internal_7 = require("@fluidframework/telemetry-utils/internal");
|
|
16
|
+
const internal_4 = require("@fluidframework/runtime-utils/internal");
|
|
17
|
+
const internal_5 = require("@fluidframework/shared-object-base/internal");
|
|
18
|
+
const internal_6 = require("@fluidframework/telemetry-utils/internal");
|
|
20
19
|
const path_browserify_1 = __importDefault(require("path-browserify"));
|
|
21
20
|
const localValues_js_1 = require("./localValues.js");
|
|
22
21
|
const utils_js_1 = require("./utils.js");
|
|
@@ -67,60 +66,6 @@ const seqDataComparator = (a, b) => {
|
|
|
67
66
|
function isAcknowledgedOrDetached(seqData) {
|
|
68
67
|
return seqData.seq >= 0;
|
|
69
68
|
}
|
|
70
|
-
/**
|
|
71
|
-
* A utility class for tracking associations between keys and their creation indices.
|
|
72
|
-
* This is relevant to support map iteration in insertion order, see
|
|
73
|
-
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/%40%40iterator
|
|
74
|
-
*
|
|
75
|
-
* TODO: It can be combined with the creation tracker utilized in SharedMap
|
|
76
|
-
*/
|
|
77
|
-
class DirectoryCreationTracker {
|
|
78
|
-
constructor() {
|
|
79
|
-
this.indexToKey = new internal_4.RedBlackTree(seqDataComparator);
|
|
80
|
-
this.keyToIndex = new Map();
|
|
81
|
-
}
|
|
82
|
-
set(key, seqData) {
|
|
83
|
-
this.indexToKey.put(seqData, key);
|
|
84
|
-
this.keyToIndex.set(key, seqData);
|
|
85
|
-
}
|
|
86
|
-
has(keyOrSeqData) {
|
|
87
|
-
return typeof keyOrSeqData === "string"
|
|
88
|
-
? this.keyToIndex.has(keyOrSeqData)
|
|
89
|
-
: this.indexToKey.get(keyOrSeqData) !== undefined;
|
|
90
|
-
}
|
|
91
|
-
delete(keyOrSeqData) {
|
|
92
|
-
if (this.has(keyOrSeqData)) {
|
|
93
|
-
if (typeof keyOrSeqData === "string") {
|
|
94
|
-
const seqData = this.keyToIndex.get(keyOrSeqData);
|
|
95
|
-
this.keyToIndex.delete(keyOrSeqData);
|
|
96
|
-
this.indexToKey.remove(seqData);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
const key = this.indexToKey.get(keyOrSeqData)?.data;
|
|
100
|
-
this.indexToKey.remove(keyOrSeqData);
|
|
101
|
-
this.keyToIndex.delete(key);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Retrieves all subdirectories with creation order that satisfy an optional constraint function.
|
|
107
|
-
* @param constraint - An optional constraint function that filters keys.
|
|
108
|
-
* @returns An array of keys that satisfy the constraint (or all keys if no constraint is provided).
|
|
109
|
-
*/
|
|
110
|
-
keys(constraint) {
|
|
111
|
-
const keys = [];
|
|
112
|
-
this.indexToKey.mapRange((node) => {
|
|
113
|
-
if (!constraint || constraint(node.data)) {
|
|
114
|
-
keys.push(node.data);
|
|
115
|
-
}
|
|
116
|
-
return true;
|
|
117
|
-
}, keys);
|
|
118
|
-
return keys;
|
|
119
|
-
}
|
|
120
|
-
get size() {
|
|
121
|
-
return this.keyToIndex.size;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
69
|
/**
|
|
125
70
|
* {@inheritDoc ISharedDirectory}
|
|
126
71
|
*
|
|
@@ -134,7 +79,7 @@ class DirectoryCreationTracker {
|
|
|
134
79
|
*
|
|
135
80
|
* @sealed
|
|
136
81
|
*/
|
|
137
|
-
class SharedDirectory extends
|
|
82
|
+
class SharedDirectory extends internal_5.SharedObject {
|
|
138
83
|
/**
|
|
139
84
|
* {@inheritDoc IDirectory.absolutePath}
|
|
140
85
|
*/
|
|
@@ -321,6 +266,25 @@ class SharedDirectory extends internal_6.SharedObject {
|
|
|
321
266
|
}
|
|
322
267
|
return currentSubDir;
|
|
323
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Similar to `getWorkingDirectory`, but only returns directories that are sequenced.
|
|
271
|
+
* This can be useful for op processing since we only process ops on sequenced directories.
|
|
272
|
+
*/
|
|
273
|
+
getSequencedWorkingDirectory(relativePath) {
|
|
274
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
275
|
+
if (absolutePath === posix.sep) {
|
|
276
|
+
return this.root;
|
|
277
|
+
}
|
|
278
|
+
let currentSubDir = this.root;
|
|
279
|
+
const subdirs = absolutePath.slice(1).split(posix.sep);
|
|
280
|
+
for (const subdir of subdirs) {
|
|
281
|
+
currentSubDir = currentSubDir.sequencedSubdirectories.get(subdir);
|
|
282
|
+
if (!currentSubDir) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return currentSubDir;
|
|
287
|
+
}
|
|
324
288
|
/**
|
|
325
289
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.summarizeCore}
|
|
326
290
|
*/
|
|
@@ -413,17 +377,13 @@ class SharedDirectory extends internal_6.SharedObject {
|
|
|
413
377
|
}
|
|
414
378
|
newSubDir = new SubDirectory(seqData, createInfo === undefined ? new Set() : new Set(createInfo.ccIds), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName), this.logger);
|
|
415
379
|
currentSubDir.populateSubDirectory(subdirName, newSubDir);
|
|
416
|
-
// Record the newly inserted subdirectory to the creation tracker
|
|
417
|
-
currentSubDir.ackedCreationSeqTracker.set(subdirName, {
|
|
418
|
-
...seqData,
|
|
419
|
-
});
|
|
420
380
|
}
|
|
421
381
|
stack.push([newSubDir, subdirObject]);
|
|
422
382
|
}
|
|
423
383
|
}
|
|
424
384
|
if (currentSubDirObject.storage) {
|
|
425
385
|
for (const [key, serializable] of Object.entries(currentSubDirObject.storage)) {
|
|
426
|
-
const parsedSerializable = (0,
|
|
386
|
+
const parsedSerializable = (0, internal_5.parseHandles)(serializable, this.serializer);
|
|
427
387
|
(0, localValues_js_1.migrateIfSharedSerializable)(parsedSerializable, this.serializer, this.handle);
|
|
428
388
|
currentSubDir.populateStorage(key, parsedSerializable.value);
|
|
429
389
|
}
|
|
@@ -459,114 +419,87 @@ class SharedDirectory extends internal_6.SharedObject {
|
|
|
459
419
|
makeAbsolute(relativePath) {
|
|
460
420
|
return posix.resolve(posix.sep, relativePath);
|
|
461
421
|
}
|
|
462
|
-
/**
|
|
463
|
-
* This checks if there is pending delete op for local delete for a any subdir in the relative path.
|
|
464
|
-
* @param relativePath - path of sub directory.
|
|
465
|
-
* @returns `true` if there is pending delete, `false` otherwise.
|
|
466
|
-
*/
|
|
467
|
-
isSubDirectoryDeletePending(relativePath) {
|
|
468
|
-
const absolutePath = this.makeAbsolute(relativePath);
|
|
469
|
-
if (absolutePath === posix.sep) {
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
let currentParent = this.root;
|
|
473
|
-
const pathParts = absolutePath.split(posix.sep).slice(1);
|
|
474
|
-
for (const dirName of pathParts) {
|
|
475
|
-
if (currentParent.isSubDirectoryDeletePending(dirName)) {
|
|
476
|
-
return true;
|
|
477
|
-
}
|
|
478
|
-
currentParent = currentParent.getSubDirectory(dirName);
|
|
479
|
-
if (currentParent === undefined) {
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
422
|
/**
|
|
486
423
|
* Set the message handlers for the directory.
|
|
487
424
|
*/
|
|
488
425
|
setMessageHandlers() {
|
|
426
|
+
// Notes on how we target the correct subdirectory:
|
|
427
|
+
// `process`: When processing ops, we only ever want to process ops on sequenced directories. This prevents
|
|
428
|
+
// scenarios where ops could be processed on a pending directory instead of a sequenced directory,
|
|
429
|
+
// leading to ops effectively being processed out of order.
|
|
430
|
+
// `resubmit`: When resubmitting ops, we use `localOpMetadata` to get a reference to the subdirectory that
|
|
431
|
+
// the op was originally targeting.
|
|
489
432
|
this.messageHandlers.set("clear", {
|
|
490
433
|
process: (msg, op, local, localOpMetadata) => {
|
|
491
|
-
const subdir = this.
|
|
492
|
-
|
|
493
|
-
// as we are going to delete this subDirectory.
|
|
494
|
-
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
434
|
+
const subdir = this.getSequencedWorkingDirectory(op.path);
|
|
435
|
+
if (subdir !== undefined && !subdir?.disposed) {
|
|
495
436
|
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
496
437
|
}
|
|
497
438
|
},
|
|
498
439
|
resubmit: (op, localOpMetadata) => {
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
501
|
-
|
|
440
|
+
const targetSubdir = localOpMetadata.subdir;
|
|
441
|
+
if (!targetSubdir.disposed) {
|
|
442
|
+
targetSubdir.resubmitClearMessage(op, localOpMetadata);
|
|
502
443
|
}
|
|
503
444
|
},
|
|
504
445
|
});
|
|
505
446
|
this.messageHandlers.set("delete", {
|
|
506
447
|
process: (msg, op, local, localOpMetadata) => {
|
|
507
|
-
const subdir = this.
|
|
508
|
-
|
|
509
|
-
// as we are going to delete this subDirectory.
|
|
510
|
-
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
448
|
+
const subdir = this.getSequencedWorkingDirectory(op.path);
|
|
449
|
+
if (subdir !== undefined && !subdir?.disposed) {
|
|
511
450
|
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
512
451
|
}
|
|
513
452
|
},
|
|
514
453
|
resubmit: (op, localOpMetadata) => {
|
|
515
|
-
const
|
|
516
|
-
if (
|
|
517
|
-
|
|
454
|
+
const targetSubdir = localOpMetadata.subdir;
|
|
455
|
+
if (!targetSubdir.disposed) {
|
|
456
|
+
targetSubdir.resubmitKeyMessage(op, localOpMetadata);
|
|
518
457
|
}
|
|
519
458
|
},
|
|
520
459
|
});
|
|
521
460
|
this.messageHandlers.set("set", {
|
|
522
461
|
process: (msg, op, local, localOpMetadata) => {
|
|
523
|
-
const subdir = this.
|
|
524
|
-
|
|
525
|
-
// as we are going to delete this subDirectory.
|
|
526
|
-
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
462
|
+
const subdir = this.getSequencedWorkingDirectory(op.path);
|
|
463
|
+
if (subdir !== undefined && !subdir?.disposed) {
|
|
527
464
|
(0, localValues_js_1.migrateIfSharedSerializable)(op.value, this.serializer, this.handle);
|
|
528
465
|
const localValue = local ? undefined : op.value.value;
|
|
529
466
|
subdir.processSetMessage(msg, op, localValue, local, localOpMetadata);
|
|
530
467
|
}
|
|
531
468
|
},
|
|
532
469
|
resubmit: (op, localOpMetadata) => {
|
|
533
|
-
const
|
|
534
|
-
if (
|
|
535
|
-
|
|
470
|
+
const targetSubdir = localOpMetadata.subdir;
|
|
471
|
+
if (!targetSubdir.disposed) {
|
|
472
|
+
targetSubdir.resubmitKeyMessage(op, localOpMetadata);
|
|
536
473
|
}
|
|
537
474
|
},
|
|
538
475
|
});
|
|
539
476
|
this.messageHandlers.set("createSubDirectory", {
|
|
540
477
|
process: (msg, op, local, localOpMetadata) => {
|
|
541
|
-
const parentSubdir = this.
|
|
542
|
-
|
|
543
|
-
// as we are going to delete this subDirectory.
|
|
544
|
-
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
478
|
+
const parentSubdir = this.getSequencedWorkingDirectory(op.path);
|
|
479
|
+
if (parentSubdir !== undefined && !parentSubdir?.disposed) {
|
|
545
480
|
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
546
481
|
}
|
|
547
482
|
},
|
|
548
483
|
resubmit: (op, localOpMetadata) => {
|
|
549
|
-
const
|
|
550
|
-
if (
|
|
484
|
+
const targetSubdir = localOpMetadata.parentSubdir;
|
|
485
|
+
if (!targetSubdir.disposed) {
|
|
551
486
|
// We don't reuse the metadata but send a new one on each submit.
|
|
552
|
-
|
|
487
|
+
targetSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
553
488
|
}
|
|
554
489
|
},
|
|
555
490
|
});
|
|
556
491
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
557
492
|
process: (msg, op, local, localOpMetadata) => {
|
|
558
|
-
const parentSubdir = this.
|
|
559
|
-
|
|
560
|
-
// as we are going to delete this subDirectory.
|
|
561
|
-
if (parentSubdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
493
|
+
const parentSubdir = this.getSequencedWorkingDirectory(op.path);
|
|
494
|
+
if (parentSubdir !== undefined && !parentSubdir?.disposed) {
|
|
562
495
|
parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
563
496
|
}
|
|
564
497
|
},
|
|
565
498
|
resubmit: (op, localOpMetadata) => {
|
|
566
|
-
const
|
|
567
|
-
if (
|
|
499
|
+
const targetSubdir = localOpMetadata.parentSubdir;
|
|
500
|
+
if (!targetSubdir.disposed) {
|
|
568
501
|
// We don't reuse the metadata but send a new one on each submit.
|
|
569
|
-
|
|
502
|
+
targetSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
570
503
|
}
|
|
571
504
|
},
|
|
572
505
|
});
|
|
@@ -606,7 +539,7 @@ class SharedDirectory extends internal_6.SharedObject {
|
|
|
606
539
|
}
|
|
607
540
|
serializeDirectory(root, serializer, telemetryContext) {
|
|
608
541
|
const MinValueSizeSeparateSnapshotBlob = 8 * 1024;
|
|
609
|
-
const builder = new
|
|
542
|
+
const builder = new internal_4.SummaryTreeBuilder();
|
|
610
543
|
let counter = 0;
|
|
611
544
|
const blobs = [];
|
|
612
545
|
const stack = [];
|
|
@@ -667,7 +600,6 @@ exports.SharedDirectory = SharedDirectory;
|
|
|
667
600
|
function assertNonNullClientId(clientId) {
|
|
668
601
|
(0, internal_1.assert)(clientId !== null, 0x6af /* client id should never be null */);
|
|
669
602
|
}
|
|
670
|
-
let hasLoggedDirectoryInconsistency = false;
|
|
671
603
|
/**
|
|
672
604
|
* Node of the directory tree.
|
|
673
605
|
* @sealed
|
|
@@ -690,7 +622,6 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
690
622
|
this.runtime = runtime;
|
|
691
623
|
this.serializer = serializer;
|
|
692
624
|
this.absolutePath = absolutePath;
|
|
693
|
-
this.logger = logger;
|
|
694
625
|
/**
|
|
695
626
|
* Tells if the sub directory is deleted or not.
|
|
696
627
|
*/
|
|
@@ -700,21 +631,10 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
700
631
|
*/
|
|
701
632
|
this[_b] = "SubDirectory";
|
|
702
633
|
/**
|
|
703
|
-
* The subdirectories the directory is holding
|
|
634
|
+
* The sequenced subdirectories the directory is holding independent of any pending
|
|
635
|
+
* create/delete subdirectory operations.
|
|
704
636
|
*/
|
|
705
|
-
this.
|
|
706
|
-
/**
|
|
707
|
-
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the record
|
|
708
|
-
* of delete op that are pending or yet to be acked from server. This is maintained just to track the locally
|
|
709
|
-
* deleted sub directory.
|
|
710
|
-
*/
|
|
711
|
-
this.pendingDeleteSubDirectoriesTracker = new Map();
|
|
712
|
-
/**
|
|
713
|
-
* Subdirectories that have been created locally but not yet ack'd from the server. This maintains the record
|
|
714
|
-
* of create op that are pending or yet to be acked from server. This is maintained just to track the locally
|
|
715
|
-
* created sub directory.
|
|
716
|
-
*/
|
|
717
|
-
this.pendingCreateSubDirectoriesTracker = new Map();
|
|
637
|
+
this._sequencedSubdirectories = new Map();
|
|
718
638
|
/**
|
|
719
639
|
* Assigns a unique ID to each subdirectory created locally but pending for acknowledgement, facilitating the tracking
|
|
720
640
|
* of the creation order.
|
|
@@ -733,6 +653,11 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
733
653
|
* even across remote operations and rollbacks.
|
|
734
654
|
*/
|
|
735
655
|
this.pendingStorageData = [];
|
|
656
|
+
/**
|
|
657
|
+
* A data structure containing all local pending subdirectory create/deletes, which is used in combination
|
|
658
|
+
* with the _sequencedSubdirectories to compute optimistic values.
|
|
659
|
+
*/
|
|
660
|
+
this.pendingSubDirectoryData = [];
|
|
736
661
|
/**
|
|
737
662
|
* An internal iterator that iterates over the entries in the directory.
|
|
738
663
|
*/
|
|
@@ -821,8 +746,33 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
821
746
|
? this.sequencedStorageData.has(key)
|
|
822
747
|
: latestPendingEntry.type === "lifetime";
|
|
823
748
|
};
|
|
824
|
-
|
|
825
|
-
|
|
749
|
+
/**
|
|
750
|
+
* Get the optimistic local subdirectory. This combines the sequenced data with
|
|
751
|
+
* any pending changes that have not yet been sequenced. By default, we do not
|
|
752
|
+
* consider disposed directories as optimistically existing, but if `getIfDisposed`
|
|
753
|
+
* is true, we will include them since some scenarios require this.
|
|
754
|
+
*/
|
|
755
|
+
this.getOptimisticSubDirectory = (subdirName, getIfDisposed = false) => {
|
|
756
|
+
const latestPendingEntry = (0, utils_js_1.findLast)(this.pendingSubDirectoryData, (entry) => entry.subdirName === subdirName);
|
|
757
|
+
let subdir;
|
|
758
|
+
if (latestPendingEntry === undefined) {
|
|
759
|
+
subdir = this._sequencedSubdirectories.get(subdirName);
|
|
760
|
+
}
|
|
761
|
+
else if (latestPendingEntry.type === "createSubDirectory") {
|
|
762
|
+
subdir = latestPendingEntry.subdir;
|
|
763
|
+
(0, internal_1.assert)(subdir !== undefined, 0xc2f /* Subdirectory should exist in pending data */);
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
// Pending delete
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
// If the subdirectory is disposed, treat it as non-existent for optimistic reads (unless specified otherwise)
|
|
770
|
+
if (subdir?.disposed && !getIfDisposed) {
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
return subdir;
|
|
774
|
+
};
|
|
775
|
+
this.mc = (0, internal_6.createChildMonitoringContext)({ logger, namespace: "Directory" });
|
|
826
776
|
}
|
|
827
777
|
dispose(error) {
|
|
828
778
|
this._deleted = true;
|
|
@@ -840,7 +790,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
840
790
|
}
|
|
841
791
|
throwIfDisposed() {
|
|
842
792
|
if (this._deleted) {
|
|
843
|
-
throw new
|
|
793
|
+
throw new internal_6.UsageError("Cannot access Disposed subDirectory");
|
|
844
794
|
}
|
|
845
795
|
}
|
|
846
796
|
/**
|
|
@@ -868,8 +818,12 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
868
818
|
throw new Error("Undefined and null keys are not supported");
|
|
869
819
|
}
|
|
870
820
|
const previousOptimisticLocalValue = this.getOptimisticValue(key);
|
|
871
|
-
|
|
872
|
-
(
|
|
821
|
+
const detachedBind = this.mc.config.getBoolean("Fluid.Directory.AllowDetachedResolve") ?? false;
|
|
822
|
+
if (detachedBind) {
|
|
823
|
+
// Create a local value and serialize it.
|
|
824
|
+
// AB#47081: This will be removed once we can validate that it is no longer needed.
|
|
825
|
+
(0, internal_5.bindHandles)(value, this.serializer, this.directory.handle);
|
|
826
|
+
}
|
|
873
827
|
// If we are not attached, don't submit the op.
|
|
874
828
|
if (!this.directory.isAttached()) {
|
|
875
829
|
this.sequencedStorageData.set(key, value);
|
|
@@ -894,20 +848,27 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
894
848
|
if (latestPendingEntry === undefined ||
|
|
895
849
|
latestPendingEntry.type === "delete" ||
|
|
896
850
|
latestPendingEntry.type === "clear") {
|
|
897
|
-
latestPendingEntry = {
|
|
851
|
+
latestPendingEntry = {
|
|
852
|
+
type: "lifetime",
|
|
853
|
+
path: this.absolutePath,
|
|
854
|
+
key,
|
|
855
|
+
keySets: [],
|
|
856
|
+
subdir: this,
|
|
857
|
+
};
|
|
898
858
|
this.pendingStorageData.push(latestPendingEntry);
|
|
899
859
|
}
|
|
900
860
|
const pendingKeySet = {
|
|
901
861
|
type: "set",
|
|
902
862
|
path: this.absolutePath,
|
|
903
863
|
value,
|
|
864
|
+
subdir: this,
|
|
904
865
|
};
|
|
905
866
|
latestPendingEntry.keySets.push(pendingKeySet);
|
|
906
867
|
const op = {
|
|
907
868
|
key,
|
|
908
869
|
path: this.absolutePath,
|
|
909
870
|
type: "set",
|
|
910
|
-
value: { type:
|
|
871
|
+
value: { type: internal_5.ValueType[internal_5.ValueType.Plain], value },
|
|
911
872
|
};
|
|
912
873
|
this.submitKeyMessage(op, pendingKeySet);
|
|
913
874
|
const directoryValueChanged = {
|
|
@@ -927,7 +888,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
927
888
|
* {@inheritDoc IDirectory.countSubDirectory}
|
|
928
889
|
*/
|
|
929
890
|
countSubDirectory() {
|
|
930
|
-
return this.
|
|
891
|
+
return [...this.subdirectories()].length;
|
|
931
892
|
}
|
|
932
893
|
/**
|
|
933
894
|
* {@inheritDoc IDirectory.createSubDirectory}
|
|
@@ -941,22 +902,47 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
941
902
|
if (subdirName.includes(posix.sep)) {
|
|
942
903
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
943
904
|
}
|
|
944
|
-
|
|
945
|
-
const
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
905
|
+
let subDir = this.getOptimisticSubDirectory(subdirName, true);
|
|
906
|
+
const seqData = this.getLocalSeq();
|
|
907
|
+
const clientId = this.runtime.clientId ?? "detached";
|
|
908
|
+
const isNewSubDirectory = subDir === undefined;
|
|
909
|
+
if (subDir === undefined) {
|
|
910
|
+
// If we do not have optimistically have this subdirectory yet, we should create a new one
|
|
911
|
+
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
912
|
+
subDir = new SubDirectory({ ...seqData }, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath, this.mc.logger);
|
|
951
913
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
}
|
|
959
|
-
|
|
914
|
+
else {
|
|
915
|
+
if (subDir.disposed) {
|
|
916
|
+
// In the case that the subdir exists but is disposed, we should
|
|
917
|
+
// still use the existing subdir to maintain any pending changes but
|
|
918
|
+
// ensure it is no longer disposed.
|
|
919
|
+
this.undisposeSubdirectoryTree(subDir);
|
|
920
|
+
}
|
|
921
|
+
subDir.clientIds.add(clientId);
|
|
922
|
+
}
|
|
923
|
+
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
924
|
+
// Only submit the op/emit event if we actually created a new subdir.
|
|
925
|
+
if (isNewSubDirectory) {
|
|
926
|
+
if (this.directory.isAttached()) {
|
|
927
|
+
const pendingSubDirectoryCreate = {
|
|
928
|
+
type: "createSubDirectory",
|
|
929
|
+
subdirName,
|
|
930
|
+
subdir: subDir,
|
|
931
|
+
};
|
|
932
|
+
this.pendingSubDirectoryData.push(pendingSubDirectoryCreate);
|
|
933
|
+
const op = {
|
|
934
|
+
subdirName,
|
|
935
|
+
path: this.absolutePath,
|
|
936
|
+
type: "createSubDirectory",
|
|
937
|
+
};
|
|
938
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
// If we are detached, don't submit the op and directly commit
|
|
942
|
+
// the subdir to _sequencedSubdirectories.
|
|
943
|
+
this._sequencedSubdirectories.set(subdirName, subDir);
|
|
944
|
+
}
|
|
945
|
+
this.emit("subDirectoryCreated", subdirName, true, this);
|
|
960
946
|
}
|
|
961
947
|
return subDir;
|
|
962
948
|
}
|
|
@@ -981,81 +967,88 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
981
967
|
*/
|
|
982
968
|
getSubDirectory(subdirName) {
|
|
983
969
|
this.throwIfDisposed();
|
|
984
|
-
return this.
|
|
970
|
+
return this.getOptimisticSubDirectory(subdirName);
|
|
985
971
|
}
|
|
986
972
|
/**
|
|
987
973
|
* {@inheritDoc IDirectory.hasSubDirectory}
|
|
988
974
|
*/
|
|
989
975
|
hasSubDirectory(subdirName) {
|
|
990
976
|
this.throwIfDisposed();
|
|
991
|
-
return this.
|
|
977
|
+
return this.getOptimisticSubDirectory(subdirName) !== undefined;
|
|
992
978
|
}
|
|
993
979
|
/**
|
|
994
980
|
* {@inheritDoc IDirectory.deleteSubDirectory}
|
|
995
981
|
*/
|
|
996
982
|
deleteSubDirectory(subdirName) {
|
|
997
983
|
this.throwIfDisposed();
|
|
998
|
-
// Delete the sub directory locally first.
|
|
999
|
-
const subDir = this.deleteSubDirectoryCore(subdirName, true);
|
|
1000
|
-
// If we are not attached, don't submit the op.
|
|
1001
984
|
if (!this.directory.isAttached()) {
|
|
1002
|
-
|
|
985
|
+
const previousValue = this._sequencedSubdirectories.get(subdirName);
|
|
986
|
+
const successfullyRemoved = this._sequencedSubdirectories.delete(subdirName);
|
|
987
|
+
// Only emit if we actually deleted something.
|
|
988
|
+
if (successfullyRemoved) {
|
|
989
|
+
this.disposeSubDirectoryTree(previousValue);
|
|
990
|
+
this.emit("subDirectoryDeleted", subdirName, true, this);
|
|
991
|
+
}
|
|
992
|
+
return successfullyRemoved;
|
|
1003
993
|
}
|
|
1004
|
-
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
path: this.absolutePath,
|
|
1008
|
-
subdirName,
|
|
1009
|
-
type: "deleteSubDirectory",
|
|
1010
|
-
};
|
|
1011
|
-
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
994
|
+
const previousOptimisticSubDirectory = this.getOptimisticSubDirectory(subdirName);
|
|
995
|
+
if (previousOptimisticSubDirectory === undefined) {
|
|
996
|
+
return false;
|
|
1012
997
|
}
|
|
1013
|
-
|
|
998
|
+
const pendingSubdirDelete = {
|
|
999
|
+
type: "deleteSubDirectory",
|
|
1000
|
+
subdirName,
|
|
1001
|
+
subdir: this,
|
|
1002
|
+
};
|
|
1003
|
+
this.pendingSubDirectoryData.push(pendingSubdirDelete);
|
|
1004
|
+
const op = {
|
|
1005
|
+
subdirName,
|
|
1006
|
+
type: "deleteSubDirectory",
|
|
1007
|
+
path: this.absolutePath,
|
|
1008
|
+
};
|
|
1009
|
+
this.submitDeleteSubDirectoryMessage(op, previousOptimisticSubDirectory);
|
|
1010
|
+
this.emit("subDirectoryDeleted", subdirName, true, this);
|
|
1011
|
+
// We don't want to fully dispose the subdir tree since this is only a pending
|
|
1012
|
+
// local delete. Instead we will only emit the dispose event to reflect the
|
|
1013
|
+
// local state.
|
|
1014
|
+
this.emitDisposeForSubdirTree(previousOptimisticSubDirectory);
|
|
1015
|
+
return true;
|
|
1014
1016
|
}
|
|
1015
1017
|
/**
|
|
1016
1018
|
* {@inheritDoc IDirectory.subdirectories}
|
|
1017
1019
|
*/
|
|
1018
1020
|
subdirectories() {
|
|
1019
1021
|
this.throwIfDisposed();
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
this.logger.sendTelemetryEvent({
|
|
1030
|
-
eventName: "inconsistentSubdirectoryOrdering",
|
|
1031
|
-
localKeyCount: this.localCreationSeqTracker.size,
|
|
1032
|
-
ackedKeyCount: this.ackedCreationSeqTracker.size,
|
|
1033
|
-
subdirNamesLength: subdirNames.length,
|
|
1034
|
-
subdirectoriesSize: this._subdirectories.size,
|
|
1035
|
-
});
|
|
1036
|
-
hasLoggedDirectoryInconsistency = true;
|
|
1022
|
+
// subdirectories() should reflect the optimistic state of subdirectories.
|
|
1023
|
+
// This means that we should return both sequenced and pending subdirectories
|
|
1024
|
+
// that do not also have a pending deletion.
|
|
1025
|
+
const sequencedSubdirs = [];
|
|
1026
|
+
const sequencedSubdirNames = new Set([...this._sequencedSubdirectories.keys()]);
|
|
1027
|
+
for (const subdirName of sequencedSubdirNames) {
|
|
1028
|
+
const optimisticSubdir = this.getOptimisticSubDirectory(subdirName);
|
|
1029
|
+
if (optimisticSubdir !== undefined) {
|
|
1030
|
+
sequencedSubdirs.push([subdirName, optimisticSubdir]);
|
|
1037
1031
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
[
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
return entriesIterator;
|
|
1032
|
+
}
|
|
1033
|
+
const pendingSubdirNames = [
|
|
1034
|
+
...new Set(this.pendingSubDirectoryData
|
|
1035
|
+
.map((entry) => entry.subdirName)
|
|
1036
|
+
.filter((subdirName) => !sequencedSubdirNames.has(subdirName))),
|
|
1037
|
+
];
|
|
1038
|
+
const pendingSubdirs = [];
|
|
1039
|
+
for (const subdirName of pendingSubdirNames) {
|
|
1040
|
+
const optimisticSubdir = this.getOptimisticSubDirectory(subdirName);
|
|
1041
|
+
if (optimisticSubdir !== undefined) {
|
|
1042
|
+
pendingSubdirs.push([subdirName, optimisticSubdir]);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const allSubdirs = [...sequencedSubdirs, ...pendingSubdirs];
|
|
1046
|
+
const orderedSubdirs = allSubdirs.sort((a, b) => {
|
|
1047
|
+
const aSeqData = a[1].seqData;
|
|
1048
|
+
const bSeqData = b[1].seqData;
|
|
1049
|
+
return seqDataComparator(aSeqData, bSeqData);
|
|
1050
|
+
});
|
|
1051
|
+
return orderedSubdirs[Symbol.iterator]();
|
|
1059
1052
|
}
|
|
1060
1053
|
/**
|
|
1061
1054
|
* {@inheritDoc IDirectory.getWorkingDirectory}
|
|
@@ -1070,10 +1063,10 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1070
1063
|
* @returns true if there is pending delete.
|
|
1071
1064
|
*/
|
|
1072
1065
|
isSubDirectoryDeletePending(subDirName) {
|
|
1073
|
-
|
|
1074
|
-
return
|
|
1075
|
-
}
|
|
1076
|
-
return
|
|
1066
|
+
const lastPendingEntry = (0, utils_js_1.findLast)(this.pendingSubDirectoryData, (entry) => {
|
|
1067
|
+
return entry.subdirName === subDirName && entry.type === "deleteSubDirectory";
|
|
1068
|
+
});
|
|
1069
|
+
return lastPendingEntry !== undefined;
|
|
1077
1070
|
}
|
|
1078
1071
|
/**
|
|
1079
1072
|
* Deletes the given key from within this IDirectory.
|
|
@@ -1105,6 +1098,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1105
1098
|
type: "delete",
|
|
1106
1099
|
path: this.absolutePath,
|
|
1107
1100
|
key,
|
|
1101
|
+
subdir: this,
|
|
1108
1102
|
};
|
|
1109
1103
|
this.pendingStorageData.push(pendingKeyDelete);
|
|
1110
1104
|
const op = {
|
|
@@ -1144,6 +1138,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1144
1138
|
const pendingClear = {
|
|
1145
1139
|
type: "clear",
|
|
1146
1140
|
path: this.absolutePath,
|
|
1141
|
+
subdir: this,
|
|
1147
1142
|
};
|
|
1148
1143
|
this.pendingStorageData.push(pendingClear);
|
|
1149
1144
|
this.directory.emit("clear", true, this.directory);
|
|
@@ -1160,7 +1155,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1160
1155
|
forEach(callback) {
|
|
1161
1156
|
this.throwIfDisposed();
|
|
1162
1157
|
for (const [key, localValue] of this.internalIterator()) {
|
|
1163
|
-
callback(localValue
|
|
1158
|
+
callback(localValue, key, this);
|
|
1164
1159
|
}
|
|
1165
1160
|
}
|
|
1166
1161
|
/**
|
|
@@ -1248,6 +1243,10 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1248
1243
|
this.throwIfDisposed();
|
|
1249
1244
|
return this.internalIterator();
|
|
1250
1245
|
}
|
|
1246
|
+
get sequencedSubdirectories() {
|
|
1247
|
+
this.throwIfDisposed();
|
|
1248
|
+
return this._sequencedSubdirectories;
|
|
1249
|
+
}
|
|
1251
1250
|
/**
|
|
1252
1251
|
* Process a clear operation.
|
|
1253
1252
|
* @param msg - The message from the server to apply.
|
|
@@ -1258,7 +1257,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1258
1257
|
*/
|
|
1259
1258
|
processClearMessage(msg, op, local, localOpMetadata) {
|
|
1260
1259
|
this.throwIfDisposed();
|
|
1261
|
-
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1260
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg, localOpMetadata?.subdir)) {
|
|
1262
1261
|
return;
|
|
1263
1262
|
}
|
|
1264
1263
|
if (local) {
|
|
@@ -1302,7 +1301,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1302
1301
|
*/
|
|
1303
1302
|
processDeleteMessage(msg, op, local, localOpMetadata) {
|
|
1304
1303
|
this.throwIfDisposed();
|
|
1305
|
-
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1304
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg, localOpMetadata?.subdir)) {
|
|
1306
1305
|
return;
|
|
1307
1306
|
}
|
|
1308
1307
|
if (local) {
|
|
@@ -1340,7 +1339,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1340
1339
|
*/
|
|
1341
1340
|
processSetMessage(msg, op, value, local, localOpMetadata) {
|
|
1342
1341
|
this.throwIfDisposed();
|
|
1343
|
-
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1342
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg, localOpMetadata?.subdir)) {
|
|
1344
1343
|
return;
|
|
1345
1344
|
}
|
|
1346
1345
|
const { key } = op;
|
|
@@ -1378,12 +1377,58 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1378
1377
|
*/
|
|
1379
1378
|
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1380
1379
|
this.throwIfDisposed();
|
|
1381
|
-
if (!
|
|
1382
|
-
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1380
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg, localOpMetadata?.parentSubdir)) {
|
|
1383
1381
|
return;
|
|
1384
1382
|
}
|
|
1385
1383
|
assertNonNullClientId(msg.clientId);
|
|
1386
|
-
|
|
1384
|
+
let subDir;
|
|
1385
|
+
if (local) {
|
|
1386
|
+
const pendingEntryIndex = this.pendingSubDirectoryData.findIndex((entry) => entry.subdirName === op.subdirName);
|
|
1387
|
+
const pendingEntry = this.pendingSubDirectoryData[pendingEntryIndex];
|
|
1388
|
+
(0, internal_1.assert)(pendingEntry !== undefined && pendingEntry.type === "createSubDirectory", 0xc30 /* Got a local subdir create message we weren't expecting */);
|
|
1389
|
+
this.pendingSubDirectoryData.splice(pendingEntryIndex, 1);
|
|
1390
|
+
subDir = pendingEntry.subdir;
|
|
1391
|
+
const existingSubdir = this._sequencedSubdirectories.get(op.subdirName);
|
|
1392
|
+
if (existingSubdir !== undefined) {
|
|
1393
|
+
// If the subdirectory already exists, we don't need to create it again.
|
|
1394
|
+
// This can happen if remote clients also create the same subdir and we processed
|
|
1395
|
+
// that message first.
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (subDir.disposed) {
|
|
1399
|
+
this.undisposeSubdirectoryTree(subDir);
|
|
1400
|
+
}
|
|
1401
|
+
this._sequencedSubdirectories.set(op.subdirName, subDir);
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
subDir = this.getOptimisticSubDirectory(op.subdirName, true);
|
|
1405
|
+
if (subDir === undefined) {
|
|
1406
|
+
const absolutePath = posix.join(this.absolutePath, op.subdirName);
|
|
1407
|
+
subDir = new SubDirectory({ seq: msg.sequenceNumber, clientSeq: msg.clientSequenceNumber }, new Set([msg.clientId]), this.directory, this.runtime, this.serializer, absolutePath, this.mc.logger);
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
// If the subdirectory already optimistically exists, we don't need to create it again.
|
|
1411
|
+
// This can happen if remote clients also created the same subdir.
|
|
1412
|
+
if (subDir.disposed) {
|
|
1413
|
+
this.undisposeSubdirectoryTree(subDir);
|
|
1414
|
+
}
|
|
1415
|
+
subDir.clientIds.add(msg.clientId);
|
|
1416
|
+
}
|
|
1417
|
+
this.registerEventsOnSubDirectory(subDir, op.subdirName);
|
|
1418
|
+
this._sequencedSubdirectories.set(op.subdirName, subDir);
|
|
1419
|
+
// Suppress the event if local changes would cause the incoming change to be invisible optimistically.
|
|
1420
|
+
if (!this.pendingSubDirectoryData.some((entry) => entry.subdirName === op.subdirName)) {
|
|
1421
|
+
this.emit("subDirectoryCreated", op.subdirName, local, this);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
// Ensure correct seqData. This can be necessary if in scenarios where a subdir was created, deleted, and
|
|
1425
|
+
// then later recreated.
|
|
1426
|
+
if (this.seqData.seq !== -1 &&
|
|
1427
|
+
this.seqData.seq <= msg.sequenceNumber &&
|
|
1428
|
+
subDir.seqData.seq === -1) {
|
|
1429
|
+
subDir.seqData.seq = msg.sequenceNumber;
|
|
1430
|
+
subDir.seqData.clientSeq = msg.clientSequenceNumber;
|
|
1431
|
+
}
|
|
1387
1432
|
}
|
|
1388
1433
|
/**
|
|
1389
1434
|
* Process a delete subdirectory operation.
|
|
@@ -1395,11 +1440,43 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1395
1440
|
*/
|
|
1396
1441
|
processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1397
1442
|
this.throwIfDisposed();
|
|
1398
|
-
if (!
|
|
1399
|
-
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1443
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg, localOpMetadata?.parentSubdir)) {
|
|
1400
1444
|
return;
|
|
1401
1445
|
}
|
|
1402
|
-
this.
|
|
1446
|
+
const previousValue = this._sequencedSubdirectories.get(op.subdirName);
|
|
1447
|
+
if (previousValue === undefined) {
|
|
1448
|
+
// We are trying to delete a subdirectory that does not exist.
|
|
1449
|
+
// If this is a local delete, we should remove the pending delete entry.
|
|
1450
|
+
// This could happen if we already processed a remote delete op for
|
|
1451
|
+
// the same subdirectory.
|
|
1452
|
+
if (local) {
|
|
1453
|
+
const pendingEntryIndex = this.pendingSubDirectoryData.findIndex((entry) => entry.subdirName === op.subdirName);
|
|
1454
|
+
const pendingEntry = this.pendingSubDirectoryData[pendingEntryIndex];
|
|
1455
|
+
(0, internal_1.assert)(pendingEntry !== undefined &&
|
|
1456
|
+
pendingEntry.type === "deleteSubDirectory" &&
|
|
1457
|
+
pendingEntry.subdirName === op.subdirName, 0xc31 /* Got a local deleteSubDirectory message we weren't expecting */);
|
|
1458
|
+
this.pendingSubDirectoryData.splice(pendingEntryIndex, 1);
|
|
1459
|
+
}
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
this._sequencedSubdirectories.delete(op.subdirName);
|
|
1463
|
+
this.disposeSubDirectoryTree(previousValue);
|
|
1464
|
+
if (local) {
|
|
1465
|
+
const pendingEntryIndex = this.pendingSubDirectoryData.findIndex((entry) => entry.subdirName === op.subdirName);
|
|
1466
|
+
const pendingEntry = this.pendingSubDirectoryData[pendingEntryIndex];
|
|
1467
|
+
(0, internal_1.assert)(pendingEntry !== undefined &&
|
|
1468
|
+
pendingEntry.type === "deleteSubDirectory" &&
|
|
1469
|
+
pendingEntry.subdirName === op.subdirName, 0xc32 /* Got a local deleteSubDirectory message we weren't expecting */);
|
|
1470
|
+
this.pendingSubDirectoryData.splice(pendingEntryIndex, 1);
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
// Suppress the event if local changes would cause the incoming change to be invisible optimistically.
|
|
1474
|
+
const pendingEntryIndex = this.pendingSubDirectoryData.findIndex((entry) => entry.subdirName === op.subdirName && entry.type === "deleteSubDirectory");
|
|
1475
|
+
const pendingEntry = this.pendingSubDirectoryData[pendingEntryIndex];
|
|
1476
|
+
if (pendingEntry === undefined) {
|
|
1477
|
+
this.emit("subDirectoryDeleted", op.subdirName, local, this);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1403
1480
|
}
|
|
1404
1481
|
/**
|
|
1405
1482
|
* Submit a clear operation.
|
|
@@ -1440,44 +1517,28 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1440
1517
|
resubmitKeyMessage(op, localOpMetadata) {
|
|
1441
1518
|
// Only submit the op, if we have record for it, otherwise it is possible that the older instance
|
|
1442
1519
|
// is already deleted, in which case we don't need to submit the op.
|
|
1443
|
-
const pendingEntryIndex = this.pendingStorageData.findIndex((entry) =>
|
|
1520
|
+
const pendingEntryIndex = this.pendingStorageData.findIndex((entry) => {
|
|
1521
|
+
return op.type === "set"
|
|
1522
|
+
? entry.type === "lifetime" &&
|
|
1523
|
+
entry.key === op.key &&
|
|
1524
|
+
// We also check that the keySets include the localOpMetadata. It's possible we have new
|
|
1525
|
+
// pending key sets that are not the op we are looking for.
|
|
1526
|
+
entry.keySets.includes(localOpMetadata)
|
|
1527
|
+
: entry.type === "delete" && entry.key === op.key;
|
|
1528
|
+
});
|
|
1444
1529
|
const pendingEntry = this.pendingStorageData[pendingEntryIndex];
|
|
1445
1530
|
if (pendingEntry !== undefined) {
|
|
1446
1531
|
this.submitKeyMessage(op, localOpMetadata);
|
|
1447
1532
|
}
|
|
1448
1533
|
}
|
|
1449
|
-
incrementPendingSubDirCount(map, subDirName) {
|
|
1450
|
-
const count = map.get(subDirName) ?? 0;
|
|
1451
|
-
map.set(subDirName, count + 1);
|
|
1452
|
-
}
|
|
1453
|
-
decrementPendingSubDirCount(map, subDirName) {
|
|
1454
|
-
const count = map.get(subDirName) ?? 0;
|
|
1455
|
-
map.set(subDirName, count - 1);
|
|
1456
|
-
if (count <= 1) {
|
|
1457
|
-
map.delete(subDirName);
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Update the count for pending create/delete of the sub directory so that it can be validated on receiving op
|
|
1462
|
-
* or while resubmitting the op.
|
|
1463
|
-
*/
|
|
1464
|
-
updatePendingSubDirMessageCount(op) {
|
|
1465
|
-
if (op.type === "deleteSubDirectory") {
|
|
1466
|
-
this.incrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
|
|
1467
|
-
}
|
|
1468
|
-
else if (op.type === "createSubDirectory") {
|
|
1469
|
-
this.incrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
1534
|
/**
|
|
1473
1535
|
* Submit a create subdirectory operation.
|
|
1474
1536
|
* @param op - The operation
|
|
1475
1537
|
*/
|
|
1476
1538
|
submitCreateSubDirectoryMessage(op) {
|
|
1477
|
-
this.throwIfDisposed();
|
|
1478
|
-
this.updatePendingSubDirMessageCount(op);
|
|
1479
1539
|
const localOpMetadata = {
|
|
1480
1540
|
type: "createSubDir",
|
|
1541
|
+
parentSubdir: this,
|
|
1481
1542
|
};
|
|
1482
1543
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1483
1544
|
}
|
|
@@ -1487,11 +1548,10 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1487
1548
|
* @param subDir - Any subdirectory deleted by the op
|
|
1488
1549
|
*/
|
|
1489
1550
|
submitDeleteSubDirectoryMessage(op, subDir) {
|
|
1490
|
-
this.throwIfDisposed();
|
|
1491
|
-
this.updatePendingSubDirMessageCount(op);
|
|
1492
1551
|
const localOpMetadata = {
|
|
1493
1552
|
type: "deleteSubDir",
|
|
1494
1553
|
subDirectory: subDir,
|
|
1554
|
+
parentSubdir: this,
|
|
1495
1555
|
};
|
|
1496
1556
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1497
1557
|
}
|
|
@@ -1503,22 +1563,25 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1503
1563
|
resubmitSubDirectoryMessage(op, localOpMetadata) {
|
|
1504
1564
|
// Only submit the op, if we have record for it, otherwise it is possible that the older instance
|
|
1505
1565
|
// is already deleted, in which case we don't need to submit the op.
|
|
1506
|
-
if (localOpMetadata.type === "createSubDir" &&
|
|
1507
|
-
!this.pendingCreateSubDirectoriesTracker.has(op.subdirName)) {
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1510
|
-
else if (localOpMetadata.type === "deleteSubDir" &&
|
|
1511
|
-
!this.pendingDeleteSubDirectoriesTracker.has(op.subdirName)) {
|
|
1512
|
-
return;
|
|
1513
|
-
}
|
|
1514
1566
|
if (localOpMetadata.type === "createSubDir") {
|
|
1515
|
-
|
|
1516
|
-
this.
|
|
1567
|
+
// For create operations, look specifically for createSubDirectory entries
|
|
1568
|
+
const pendingEntry = (0, utils_js_1.findLast)(this.pendingSubDirectoryData, (entry) => entry.subdirName === op.subdirName && entry.type === "createSubDirectory");
|
|
1569
|
+
if (pendingEntry !== undefined) {
|
|
1570
|
+
(0, internal_1.assert)(pendingEntry.type === "createSubDirectory", 0xc33 /* pending entry should be createSubDirectory */);
|
|
1571
|
+
// We should add the client id, since when reconnecting it can have a different client id.
|
|
1572
|
+
pendingEntry.subdir.clientIds.add(this.runtime.clientId ?? "detached");
|
|
1573
|
+
// We also need to undelete the subdirectory tree if it was previously deleted
|
|
1574
|
+
this.undisposeSubdirectoryTree(pendingEntry.subdir);
|
|
1575
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1576
|
+
}
|
|
1517
1577
|
}
|
|
1518
1578
|
else if (localOpMetadata.type === "deleteSubDir") {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
this.
|
|
1579
|
+
(0, internal_1.assert)(localOpMetadata.subDirectory !== undefined, 0xc34 /* Subdirectory should exist */);
|
|
1580
|
+
// For delete operations, look specifically for deleteSubDirectory entries
|
|
1581
|
+
const pendingEntry = (0, utils_js_1.findLast)(this.pendingSubDirectoryData, (entry) => entry.subdirName === op.subdirName && entry.type === "deleteSubDirectory");
|
|
1582
|
+
if (pendingEntry !== undefined) {
|
|
1583
|
+
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
1584
|
+
}
|
|
1522
1585
|
}
|
|
1523
1586
|
}
|
|
1524
1587
|
/**
|
|
@@ -1559,7 +1622,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1559
1622
|
populateSubDirectory(subdirName, newSubDir) {
|
|
1560
1623
|
this.throwIfDisposed();
|
|
1561
1624
|
this.registerEventsOnSubDirectory(newSubDir, subdirName);
|
|
1562
|
-
this.
|
|
1625
|
+
this._sequencedSubdirectories.set(subdirName, newSubDir);
|
|
1563
1626
|
}
|
|
1564
1627
|
/**
|
|
1565
1628
|
* Rollback a local op
|
|
@@ -1572,9 +1635,13 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1572
1635
|
if (directoryOp.type === "clear") {
|
|
1573
1636
|
// A pending clear will be last in the list, since it terminates all prior lifetimes.
|
|
1574
1637
|
const pendingClear = this.pendingStorageData.pop();
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1638
|
+
if (pendingClear === undefined) {
|
|
1639
|
+
// If we can't find a pending entry then it's possible that we deleted an ack'd subdir
|
|
1640
|
+
// from a remote delete subdir op. If that's the case then there is nothing to rollback
|
|
1641
|
+
// since the pending data was removed with the subdirectory deletion.
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
(0, internal_1.assert)(pendingClear.type === "clear" && localOpMetadata.type === "clear", 0xc35 /* Unexpected clear rollback */);
|
|
1578
1645
|
for (const [key] of this.internalIterator()) {
|
|
1579
1646
|
const event = {
|
|
1580
1647
|
key,
|
|
@@ -1592,8 +1659,13 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1592
1659
|
// they were created, not when they were last modified.
|
|
1593
1660
|
const pendingEntryIndex = (0, utils_js_1.findLastIndex)(this.pendingStorageData, (entry) => entry.type !== "clear" && entry.key === directoryOp.key);
|
|
1594
1661
|
const pendingEntry = this.pendingStorageData[pendingEntryIndex];
|
|
1595
|
-
|
|
1596
|
-
|
|
1662
|
+
if (pendingEntry === undefined) {
|
|
1663
|
+
// If we can't find a pending entry then it's possible that we deleted an ack'd subdir
|
|
1664
|
+
// from a remote delete subdir op. If that's the case then there is nothing to rollback
|
|
1665
|
+
// since the pending data was removed with the subdirectory deletion.
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
(0, internal_1.assert)(pendingEntry.type === "delete" || pendingEntry.type === "lifetime", 0xc36 /* Unexpected pending data for set/delete op */);
|
|
1597
1669
|
if (pendingEntry.type === "delete") {
|
|
1598
1670
|
(0, internal_1.assert)(pendingEntry === localOpMetadata, 0xc0b /* Unexpected delete rollback */);
|
|
1599
1671
|
this.pendingStorageData.splice(pendingEntryIndex, 1);
|
|
@@ -1634,34 +1706,33 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1634
1706
|
else if (directoryOp.type === "createSubDirectory" &&
|
|
1635
1707
|
localOpMetadata.type === "createSubDir") {
|
|
1636
1708
|
const subdirName = directoryOp.subdirName;
|
|
1637
|
-
(0,
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1709
|
+
const pendingEntryIndex = (0, utils_js_1.findLastIndex)(this.pendingSubDirectoryData, (entry) => entry.type === "createSubDirectory" && entry.subdirName === subdirName);
|
|
1710
|
+
const pendingEntry = this.pendingSubDirectoryData[pendingEntryIndex];
|
|
1711
|
+
(0, internal_1.assert)(pendingEntry !== undefined && pendingEntry.type === "createSubDirectory", 0xc37 /* Unexpected pending data for createSubDirectory op */);
|
|
1712
|
+
// We still need to emit the disposed event for any locally created (and now
|
|
1713
|
+
// rolled back) subdirectory trees so listeners can observer the lifecycle
|
|
1714
|
+
// changes properly. We don't want to fully delete in case there is another
|
|
1715
|
+
// operation that references the same subdirectory.
|
|
1716
|
+
this.emitDisposeForSubdirTree(pendingEntry.subdir);
|
|
1717
|
+
this.pendingSubDirectoryData.splice(pendingEntryIndex, 1);
|
|
1718
|
+
this.emit("subDirectoryDeleted", subdirName, true, this);
|
|
1641
1719
|
}
|
|
1642
1720
|
else if (directoryOp.type === "deleteSubDirectory" &&
|
|
1643
1721
|
localOpMetadata.type === "deleteSubDir") {
|
|
1644
1722
|
const subdirName = directoryOp.subdirName;
|
|
1645
|
-
(0,
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
this.localCreationSeqTracker.set(subdirName, {
|
|
1659
|
-
...localOpMetadata.subDirectory.seqData,
|
|
1660
|
-
});
|
|
1661
|
-
}
|
|
1662
|
-
this.emit("subDirectoryCreated", subdirName, true, this);
|
|
1663
|
-
}
|
|
1664
|
-
this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, subdirName);
|
|
1723
|
+
const pendingEntryIndex = (0, utils_js_1.findLastIndex)(this.pendingSubDirectoryData, (entry) => entry.type === "deleteSubDirectory" && entry.subdirName === subdirName);
|
|
1724
|
+
const pendingEntry = this.pendingSubDirectoryData[pendingEntryIndex];
|
|
1725
|
+
(0, internal_1.assert)(pendingEntry !== undefined && pendingEntry.type === "deleteSubDirectory", 0xc38 /* Unexpected pending data for deleteSubDirectory op */);
|
|
1726
|
+
this.pendingSubDirectoryData.splice(pendingEntryIndex, 1);
|
|
1727
|
+
// Restore the subdirectory
|
|
1728
|
+
const subDirectoryToRestore = localOpMetadata.subDirectory;
|
|
1729
|
+
(0, internal_1.assert)(subDirectoryToRestore !== undefined, 0xc39 /* Subdirectory should exist */);
|
|
1730
|
+
// Recursively undispose all nested subdirectories before adding to the map
|
|
1731
|
+
// This ensures the subdirectory is properly restored before being exposed
|
|
1732
|
+
this.undisposeSubdirectoryTree(subDirectoryToRestore);
|
|
1733
|
+
// Re-register events
|
|
1734
|
+
this.registerEventsOnSubDirectory(subDirectoryToRestore, subdirName);
|
|
1735
|
+
this.emit("subDirectoryCreated", subdirName, true, this);
|
|
1665
1736
|
}
|
|
1666
1737
|
else {
|
|
1667
1738
|
throw new Error("Unsupported op for rollback");
|
|
@@ -1679,138 +1750,17 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1679
1750
|
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
1680
1751
|
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
1681
1752
|
* @param msg - message for the directory
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
//
|
|
1686
|
-
//
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
* not process the incoming operation.
|
|
1694
|
-
* @param op - Operation to check
|
|
1695
|
-
* @param local - Whether the message originated from the local client
|
|
1696
|
-
* @param message - The message
|
|
1697
|
-
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1698
|
-
* For messages from a remote client, this will be undefined.
|
|
1699
|
-
* @returns True if the operation should be processed, false otherwise
|
|
1700
|
-
*/
|
|
1701
|
-
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1702
|
-
assertNonNullClientId(msg.clientId);
|
|
1703
|
-
const pendingDeleteCount = this.pendingDeleteSubDirectoriesTracker.get(op.subdirName);
|
|
1704
|
-
const pendingCreateCount = this.pendingCreateSubDirectoriesTracker.get(op.subdirName);
|
|
1705
|
-
if ((pendingDeleteCount !== undefined && pendingDeleteCount > 0) ||
|
|
1706
|
-
(pendingCreateCount !== undefined && pendingCreateCount > 0)) {
|
|
1707
|
-
if (local) {
|
|
1708
|
-
(0, internal_1.assert)(localOpMetadata !== undefined, 0xc0d /* localOpMetadata should be defined */);
|
|
1709
|
-
if (localOpMetadata.type === "deleteSubDir") {
|
|
1710
|
-
(0, internal_1.assert)(pendingDeleteCount !== undefined && pendingDeleteCount > 0, 0x6c2 /* pendingDeleteCount should exist */);
|
|
1711
|
-
this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
|
|
1712
|
-
}
|
|
1713
|
-
else if (localOpMetadata.type === "createSubDir") {
|
|
1714
|
-
(0, internal_1.assert)(pendingCreateCount !== undefined && pendingCreateCount > 0, 0x6c3 /* pendingCreateCount should exist */);
|
|
1715
|
-
this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
if (op.type === "deleteSubDirectory") {
|
|
1719
|
-
const resetSubDirectoryTree = (directory) => {
|
|
1720
|
-
if (!directory) {
|
|
1721
|
-
return;
|
|
1722
|
-
}
|
|
1723
|
-
// If this is delete op and we have keys in this subDirectory, then we need to delete these
|
|
1724
|
-
// keys except the pending ones as they will be sequenced after this delete.
|
|
1725
|
-
directory.sequencedStorageData.clear();
|
|
1726
|
-
directory.emit("clear", true, directory);
|
|
1727
|
-
// In case of delete op, we need to reset the creation seqNum, clientSeqNum and client ids of
|
|
1728
|
-
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1729
|
-
// we will receive op for the create again.
|
|
1730
|
-
directory.seqData.seq = -1;
|
|
1731
|
-
directory.seqData.clientSeq = -1;
|
|
1732
|
-
directory.clientIds.clear();
|
|
1733
|
-
// Do the same thing for the subtree of the directory. If create is not pending for a child, then just
|
|
1734
|
-
// delete it.
|
|
1735
|
-
const subDirectories = directory.subdirectories();
|
|
1736
|
-
for (const [subDirName, subDir] of subDirectories) {
|
|
1737
|
-
if (directory.pendingCreateSubDirectoriesTracker.has(subDirName)) {
|
|
1738
|
-
resetSubDirectoryTree(subDir);
|
|
1739
|
-
continue;
|
|
1740
|
-
}
|
|
1741
|
-
directory.deleteSubDirectoryCore(subDirName, false);
|
|
1742
|
-
}
|
|
1743
|
-
};
|
|
1744
|
-
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1745
|
-
// Clear the creation tracker record
|
|
1746
|
-
this.ackedCreationSeqTracker.delete(op.subdirName);
|
|
1747
|
-
resetSubDirectoryTree(subDirectory);
|
|
1748
|
-
}
|
|
1749
|
-
if (op.type === "createSubDirectory") {
|
|
1750
|
-
const dir = this._subdirectories.get(op.subdirName);
|
|
1751
|
-
// Child sub directory create seq number can't be lower than the parent subdirectory.
|
|
1752
|
-
// The sequence number for multiple ops can be the same when multiple createSubDirectory occurs with grouped batching enabled, thus <= and not just <.
|
|
1753
|
-
if (this.seqData.seq !== -1 && this.seqData.seq <= msg.sequenceNumber) {
|
|
1754
|
-
if (dir?.seqData.seq === -1) {
|
|
1755
|
-
// Only set the sequence data based on the first message
|
|
1756
|
-
dir.seqData.seq = msg.sequenceNumber;
|
|
1757
|
-
dir.seqData.clientSeq = msg.clientSequenceNumber;
|
|
1758
|
-
// set the creation seq in tracker
|
|
1759
|
-
if (!this.ackedCreationSeqTracker.has(op.subdirName) &&
|
|
1760
|
-
!this.pendingDeleteSubDirectoriesTracker.has(op.subdirName)) {
|
|
1761
|
-
this.ackedCreationSeqTracker.set(op.subdirName, {
|
|
1762
|
-
seq: msg.sequenceNumber,
|
|
1763
|
-
clientSeq: msg.clientSequenceNumber,
|
|
1764
|
-
});
|
|
1765
|
-
if (local) {
|
|
1766
|
-
this.localCreationSeqTracker.delete(op.subdirName);
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1771
|
-
if (dir !== undefined &&
|
|
1772
|
-
!dir.clientIds.has(msg.clientId) &&
|
|
1773
|
-
dir.seqData.seq <= msg.sequenceNumber) {
|
|
1774
|
-
dir.clientIds.add(msg.clientId);
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
return false;
|
|
1779
|
-
}
|
|
1780
|
-
return !local;
|
|
1781
|
-
}
|
|
1782
|
-
/**
|
|
1783
|
-
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1784
|
-
* @param subdirName - The name of the subdirectory being created
|
|
1785
|
-
* @param local - Whether the message originated from the local client
|
|
1786
|
-
* @param seqData - Sequence number and client sequence number at which this directory is created
|
|
1787
|
-
* @param clientId - Id of client which created this directory.
|
|
1788
|
-
* @returns True if is newly created, false if it already existed.
|
|
1789
|
-
*/
|
|
1790
|
-
createSubDirectoryCore(subdirName, local, seqData, clientId) {
|
|
1791
|
-
const subdir = this._subdirectories.get(subdirName);
|
|
1792
|
-
if (subdir === undefined) {
|
|
1793
|
-
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1794
|
-
const subDir = new SubDirectory({ ...seqData }, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath, this.logger);
|
|
1795
|
-
/**
|
|
1796
|
-
* Store the sequence numbers of newly created subdirectory to the proper creation tracker, based
|
|
1797
|
-
* on whether the creation behavior has been ack'd or not
|
|
1798
|
-
*/
|
|
1799
|
-
if (isAcknowledgedOrDetached(seqData)) {
|
|
1800
|
-
this.ackedCreationSeqTracker.set(subdirName, { ...seqData });
|
|
1801
|
-
}
|
|
1802
|
-
else {
|
|
1803
|
-
this.localCreationSeqTracker.set(subdirName, { ...seqData });
|
|
1804
|
-
}
|
|
1805
|
-
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1806
|
-
this._subdirectories.set(subdirName, subDir);
|
|
1807
|
-
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1808
|
-
return true;
|
|
1809
|
-
}
|
|
1810
|
-
else {
|
|
1811
|
-
subdir.clientIds.add(clientId);
|
|
1812
|
-
}
|
|
1813
|
-
return false;
|
|
1753
|
+
* @param targetSubdir - subdirectory instance we are targeting from local op metadata (if a local op)
|
|
1754
|
+
*/
|
|
1755
|
+
isMessageForCurrentInstanceOfSubDirectory(msg, targetSubdir) {
|
|
1756
|
+
// The message must be from this instance of the directory (if a local op) AND one of the following must be true:
|
|
1757
|
+
// 1. The message was from the creator of this directory
|
|
1758
|
+
// 2. This directory was created while detached
|
|
1759
|
+
// 3. This directory was already live (known to other clients) and the op was created after the directory was created.
|
|
1760
|
+
return ((targetSubdir === undefined || targetSubdir === this) &&
|
|
1761
|
+
((msg.clientId !== null && this.clientIds.has(msg.clientId)) ||
|
|
1762
|
+
this.clientIds.has("detached") ||
|
|
1763
|
+
(this.seqData.seq !== -1 && this.seqData.seq <= msg.referenceSequenceNumber)));
|
|
1814
1764
|
}
|
|
1815
1765
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|
|
1816
1766
|
subDirectory.on("subDirectoryCreated", (relativePath, local) => {
|
|
@@ -1820,34 +1770,8 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1820
1770
|
this.emit("subDirectoryDeleted", posix.join(subDirName, relativePath), local, this);
|
|
1821
1771
|
});
|
|
1822
1772
|
}
|
|
1823
|
-
/**
|
|
1824
|
-
* Delete subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1825
|
-
* @param subdirName - The name of the subdirectory being deleted
|
|
1826
|
-
* @param local - Whether the message originated from the local client
|
|
1827
|
-
*/
|
|
1828
|
-
deleteSubDirectoryCore(subdirName, local) {
|
|
1829
|
-
const previousValue = this._subdirectories.get(subdirName);
|
|
1830
|
-
// This should make the subdirectory structure unreachable so it can be GC'd and won't appear in snapshots
|
|
1831
|
-
// Might want to consider cleaning out the structure more exhaustively though? But not when rollback.
|
|
1832
|
-
if (previousValue !== undefined) {
|
|
1833
|
-
this._subdirectories.delete(subdirName);
|
|
1834
|
-
/**
|
|
1835
|
-
* Remove the corresponding record from the proper creation tracker, based on whether the subdirectory has been
|
|
1836
|
-
* ack'd already or still not committed yet (could be both).
|
|
1837
|
-
*/
|
|
1838
|
-
if (this.ackedCreationSeqTracker.has(subdirName)) {
|
|
1839
|
-
this.ackedCreationSeqTracker.delete(subdirName);
|
|
1840
|
-
}
|
|
1841
|
-
if (this.localCreationSeqTracker.has(subdirName)) {
|
|
1842
|
-
this.localCreationSeqTracker.delete(subdirName);
|
|
1843
|
-
}
|
|
1844
|
-
this.disposeSubDirectoryTree(previousValue);
|
|
1845
|
-
this.emit("subDirectoryDeleted", subdirName, local, this);
|
|
1846
|
-
}
|
|
1847
|
-
return previousValue;
|
|
1848
|
-
}
|
|
1849
1773
|
disposeSubDirectoryTree(directory) {
|
|
1850
|
-
if (
|
|
1774
|
+
if (directory === undefined) {
|
|
1851
1775
|
return;
|
|
1852
1776
|
}
|
|
1853
1777
|
// Dispose the subdirectory tree. This will dispose the subdirectories from bottom to top.
|
|
@@ -1855,17 +1779,76 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1855
1779
|
for (const [_, subDirectory] of subDirectories) {
|
|
1856
1780
|
this.disposeSubDirectoryTree(subDirectory);
|
|
1857
1781
|
}
|
|
1782
|
+
// We need to reset the sequenced data as the previous directory is getting deleted and we will
|
|
1783
|
+
// initialize again when we will receive op for the create again.
|
|
1784
|
+
directory.clearSubDirectorySequencedData();
|
|
1785
|
+
directory.dispose();
|
|
1786
|
+
}
|
|
1787
|
+
emitDisposeForSubdirTree(directory) {
|
|
1788
|
+
if (directory === undefined || directory.disposed) {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
// Dispose the subdirectory tree. This will dispose the subdirectories from bottom to top.
|
|
1792
|
+
const subDirectories = directory.subdirectories();
|
|
1793
|
+
for (const [_, subDirectory] of subDirectories) {
|
|
1794
|
+
this.emitDisposeForSubdirTree(subDirectory);
|
|
1795
|
+
}
|
|
1858
1796
|
if (typeof directory.dispose === "function") {
|
|
1859
|
-
directory.
|
|
1797
|
+
directory.emit("disposed", directory);
|
|
1860
1798
|
}
|
|
1861
1799
|
}
|
|
1862
|
-
|
|
1863
|
-
//
|
|
1864
|
-
|
|
1800
|
+
undisposeSubdirectoryTree(directory) {
|
|
1801
|
+
// This will unmark "deleted" from the subdirectories from bottom to top.
|
|
1802
|
+
for (const [_, subDirectory] of directory.getSubdirectoriesEvenIfDisposed()) {
|
|
1803
|
+
this.undisposeSubdirectoryTree(subDirectory);
|
|
1804
|
+
}
|
|
1865
1805
|
directory.undispose();
|
|
1866
|
-
|
|
1867
|
-
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Similar to {@link subdirectories}, but also includes subdirectories that are disposed.
|
|
1809
|
+
*/
|
|
1810
|
+
getSubdirectoriesEvenIfDisposed() {
|
|
1811
|
+
const sequencedSubdirs = [];
|
|
1812
|
+
const sequencedSubdirNames = new Set([...this._sequencedSubdirectories.keys()]);
|
|
1813
|
+
for (const subdirName of sequencedSubdirNames) {
|
|
1814
|
+
const optimisticSubdir = this.getOptimisticSubDirectory(subdirName, true);
|
|
1815
|
+
if (optimisticSubdir !== undefined) {
|
|
1816
|
+
sequencedSubdirs.push([subdirName, optimisticSubdir]);
|
|
1817
|
+
}
|
|
1868
1818
|
}
|
|
1819
|
+
const pendingSubdirNames = [
|
|
1820
|
+
...new Set(this.pendingSubDirectoryData
|
|
1821
|
+
.map((entry) => entry.subdirName)
|
|
1822
|
+
.filter((subdirName) => !sequencedSubdirNames.has(subdirName))),
|
|
1823
|
+
];
|
|
1824
|
+
const pendingSubdirs = [];
|
|
1825
|
+
for (const subdirName of pendingSubdirNames) {
|
|
1826
|
+
const optimisticSubdir = this.getOptimisticSubDirectory(subdirName, true);
|
|
1827
|
+
if (optimisticSubdir !== undefined) {
|
|
1828
|
+
pendingSubdirs.push([subdirName, optimisticSubdir]);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const allSubdirs = [...sequencedSubdirs, ...pendingSubdirs];
|
|
1832
|
+
const orderedSubdirs = allSubdirs.sort((a, b) => {
|
|
1833
|
+
const aSeqData = a[1].seqData;
|
|
1834
|
+
const bSeqData = b[1].seqData;
|
|
1835
|
+
(0, internal_1.assert)(aSeqData !== undefined && bSeqData !== undefined, 0xc3a /* seqData should be defined */);
|
|
1836
|
+
return seqDataComparator(aSeqData, bSeqData);
|
|
1837
|
+
});
|
|
1838
|
+
return orderedSubdirs[Symbol.iterator]();
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Clears the sequenced data of a subdirectory but notably retains the pending
|
|
1842
|
+
* storage data. This is done when disposing of a directory so if we need to
|
|
1843
|
+
* re-create it, then we still have the pending ops.
|
|
1844
|
+
*/
|
|
1845
|
+
clearSubDirectorySequencedData() {
|
|
1846
|
+
this.seqData.seq = -1;
|
|
1847
|
+
this.seqData.clientSeq = -1;
|
|
1848
|
+
this.sequencedStorageData.clear();
|
|
1849
|
+
this._sequencedSubdirectories.clear();
|
|
1850
|
+
this.clientIds.clear();
|
|
1851
|
+
this.clientIds.add(this.runtime.clientId ?? "detached");
|
|
1869
1852
|
}
|
|
1870
1853
|
}
|
|
1871
1854
|
//# sourceMappingURL=directory.js.map
|