@fluidframework/map 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229
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/.eslintrc.js +12 -14
- package/.mocharc.js +2 -2
- package/README.md +3 -3
- package/api-extractor.json +2 -2
- package/dist/directory.d.ts +38 -5
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +285 -88
- 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/interfaces.d.ts +27 -17
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/internalInterfaces.d.ts +39 -0
- package/dist/internalInterfaces.d.ts.map +1 -1
- package/dist/internalInterfaces.js.map +1 -1
- package/dist/localValues.d.ts +12 -3
- package/dist/localValues.d.ts.map +1 -1
- package/dist/localValues.js +10 -0
- package/dist/localValues.js.map +1 -1
- package/dist/map.d.ts +5 -5
- package/dist/map.d.ts.map +1 -1
- package/dist/map.js +15 -2
- package/dist/map.js.map +1 -1
- package/dist/mapKernel.d.ts +5 -5
- package/dist/mapKernel.d.ts.map +1 -1
- package/dist/mapKernel.js +58 -33
- package/dist/mapKernel.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 +38 -5
- package/lib/directory.d.ts.map +1 -1
- package/lib/directory.js +287 -90
- 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/interfaces.d.ts +27 -17
- package/lib/interfaces.d.ts.map +1 -1
- package/lib/interfaces.js.map +1 -1
- package/lib/internalInterfaces.d.ts +39 -0
- package/lib/internalInterfaces.d.ts.map +1 -1
- package/lib/internalInterfaces.js.map +1 -1
- package/lib/localValues.d.ts +12 -3
- package/lib/localValues.d.ts.map +1 -1
- package/lib/localValues.js +10 -0
- package/lib/localValues.js.map +1 -1
- package/lib/map.d.ts +5 -5
- package/lib/map.d.ts.map +1 -1
- package/lib/map.js +16 -3
- package/lib/map.js.map +1 -1
- package/lib/mapKernel.d.ts +5 -5
- package/lib/mapKernel.d.ts.map +1 -1
- package/lib/mapKernel.js +59 -34
- package/lib/mapKernel.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 +60 -59
- package/prettier.config.cjs +1 -1
- package/src/directory.ts +2207 -1848
- package/src/index.ts +1 -0
- package/src/interfaces.ts +309 -288
- package/src/internalInterfaces.ts +83 -38
- package/src/localValues.ts +95 -93
- package/src/map.ts +364 -345
- package/src/mapKernel.ts +729 -676
- package/src/packageVersion.ts +1 -1
- package/tsconfig.esnext.json +5 -5
- package/tsconfig.json +9 -15
package/dist/directory.js
CHANGED
|
@@ -115,7 +115,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
115
115
|
/**
|
|
116
116
|
* Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
|
|
117
117
|
*/
|
|
118
|
-
this.root = new SubDirectory(this, this.runtime, this.serializer, posix.sep);
|
|
118
|
+
this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
|
|
119
119
|
/**
|
|
120
120
|
* Mapping of op types to message handlers.
|
|
121
121
|
*/
|
|
@@ -160,6 +160,8 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
160
160
|
/**
|
|
161
161
|
* {@inheritDoc IDirectory.get}
|
|
162
162
|
*/
|
|
163
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
165
|
get(key) {
|
|
164
166
|
return this.root.get(key);
|
|
165
167
|
}
|
|
@@ -208,13 +210,18 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
208
210
|
* Issue a callback on each entry under this IDirectory.
|
|
209
211
|
* @param callback - Callback to issue
|
|
210
212
|
*/
|
|
213
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
211
215
|
forEach(callback) {
|
|
216
|
+
// eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference
|
|
212
217
|
this.root.forEach(callback);
|
|
213
218
|
}
|
|
214
219
|
/**
|
|
215
220
|
* Get an iterator over the entries under this IDirectory.
|
|
216
221
|
* @returns The iterator
|
|
217
222
|
*/
|
|
223
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
225
|
[(_a = Symbol.toStringTag, Symbol.iterator)]() {
|
|
219
226
|
return this.root[Symbol.iterator]();
|
|
220
227
|
}
|
|
@@ -222,6 +229,8 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
222
229
|
* Get an iterator over the entries under this IDirectory.
|
|
223
230
|
* @returns The iterator
|
|
224
231
|
*/
|
|
232
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
225
234
|
entries() {
|
|
226
235
|
return this.root.entries();
|
|
227
236
|
}
|
|
@@ -242,6 +251,8 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
242
251
|
* Get an iterator over the values under this IDirectory.
|
|
243
252
|
* @returns The iterator
|
|
244
253
|
*/
|
|
254
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
255
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
245
256
|
values() {
|
|
246
257
|
return this.root.values();
|
|
247
258
|
}
|
|
@@ -284,7 +295,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
284
295
|
return this.root;
|
|
285
296
|
}
|
|
286
297
|
let currentSubDir = this.root;
|
|
287
|
-
const subdirs = absolutePath.
|
|
298
|
+
const subdirs = absolutePath.slice(1).split(posix.sep);
|
|
288
299
|
for (const subdir of subdirs) {
|
|
289
300
|
currentSubDir = currentSubDir.getSubDirectory(subdir);
|
|
290
301
|
if (!currentSubDir) {
|
|
@@ -360,7 +371,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
360
371
|
for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
|
|
361
372
|
let newSubDir = currentSubDir.getSubDirectory(subdirName);
|
|
362
373
|
if (!newSubDir) {
|
|
363
|
-
|
|
374
|
+
const createInfo = subdirObject.ci;
|
|
375
|
+
newSubDir = new SubDirectory(createInfo !== undefined ? createInfo.csn : 0, createInfo !== undefined
|
|
376
|
+
? new Set(createInfo.ccIds)
|
|
377
|
+
: new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
|
|
364
378
|
currentSubDir.populateSubDirectory(subdirName, newSubDir);
|
|
365
379
|
}
|
|
366
380
|
stack.push([newSubDir, subdirObject]);
|
|
@@ -383,13 +397,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
383
397
|
const op = message.contents;
|
|
384
398
|
const handler = this.messageHandlers.get(op.type);
|
|
385
399
|
(0, common_utils_1.assert)(handler !== undefined, 0x00e /* Missing message handler for message type */);
|
|
386
|
-
handler.process(op, local, localOpMetadata);
|
|
400
|
+
handler.process(message, op, local, localOpMetadata);
|
|
387
401
|
}
|
|
388
402
|
}
|
|
389
403
|
/**
|
|
390
404
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
|
|
391
405
|
* @internal
|
|
392
|
-
|
|
406
|
+
*/
|
|
393
407
|
rollback(content, localOpMetadata) {
|
|
394
408
|
const op = content;
|
|
395
409
|
const subdir = this.getWorkingDirectory(op.path);
|
|
@@ -415,18 +429,45 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
415
429
|
* @returns The local value that was produced
|
|
416
430
|
*/
|
|
417
431
|
makeLocal(key, absolutePath, serializable) {
|
|
418
|
-
(0, common_utils_1.assert)(serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Plain] ||
|
|
432
|
+
(0, common_utils_1.assert)(serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Plain] ||
|
|
433
|
+
serializable.type === shared_object_base_1.ValueType[shared_object_base_1.ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
|
|
419
434
|
return this.localValueMaker.fromSerializable(serializable);
|
|
420
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* This checks if there is pending delete op for local delete for a subdirectory.
|
|
438
|
+
* @param relativePath - path of sub directory.
|
|
439
|
+
* @returns - true if there is pending delete.
|
|
440
|
+
*/
|
|
441
|
+
isSubDirectoryDeletePending(relativePath) {
|
|
442
|
+
const parentSubDir = this.getParentDirectory(relativePath);
|
|
443
|
+
const index = relativePath.lastIndexOf(posix.sep);
|
|
444
|
+
const dirName = relativePath.substring(index + 1);
|
|
445
|
+
return !!(parentSubDir === null || parentSubDir === void 0 ? void 0 : parentSubDir.isSubDirectoryDeletePending(dirName));
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Gets the parent directory of a sub directory.
|
|
449
|
+
* @param relativePath - path of sub directory of which parent needs to be find out.
|
|
450
|
+
*/
|
|
451
|
+
getParentDirectory(relativePath) {
|
|
452
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
453
|
+
if (absolutePath === posix.sep) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const index = absolutePath.lastIndexOf(posix.sep);
|
|
457
|
+
const parentAbsPath = absolutePath.substring(0, index);
|
|
458
|
+
return this.getWorkingDirectory(parentAbsPath);
|
|
459
|
+
}
|
|
421
460
|
/**
|
|
422
461
|
* Set the message handlers for the directory.
|
|
423
462
|
*/
|
|
424
463
|
setMessageHandlers() {
|
|
425
464
|
this.messageHandlers.set("clear", {
|
|
426
|
-
process: (op, local, localOpMetadata) => {
|
|
465
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
427
466
|
const subdir = this.getWorkingDirectory(op.path);
|
|
428
|
-
|
|
429
|
-
|
|
467
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
468
|
+
// to delete this subDirectory.
|
|
469
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
470
|
+
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
430
471
|
}
|
|
431
472
|
},
|
|
432
473
|
submit: (op, localOpMetadata) => {
|
|
@@ -443,10 +484,12 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
443
484
|
},
|
|
444
485
|
});
|
|
445
486
|
this.messageHandlers.set("delete", {
|
|
446
|
-
process: (op, local, localOpMetadata) => {
|
|
487
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
447
488
|
const subdir = this.getWorkingDirectory(op.path);
|
|
448
|
-
|
|
449
|
-
|
|
489
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
490
|
+
// to delete this subDirectory.
|
|
491
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
492
|
+
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
450
493
|
}
|
|
451
494
|
},
|
|
452
495
|
submit: (op, localOpMetadata) => {
|
|
@@ -463,11 +506,13 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
463
506
|
},
|
|
464
507
|
});
|
|
465
508
|
this.messageHandlers.set("set", {
|
|
466
|
-
process: (op, local, localOpMetadata) => {
|
|
509
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
467
510
|
const subdir = this.getWorkingDirectory(op.path);
|
|
468
|
-
|
|
511
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
512
|
+
// to delete this subDirectory.
|
|
513
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
469
514
|
const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
|
|
470
|
-
subdir.processSetMessage(op, context, local, localOpMetadata);
|
|
515
|
+
subdir.processSetMessage(msg, op, context, local, localOpMetadata);
|
|
471
516
|
}
|
|
472
517
|
},
|
|
473
518
|
submit: (op, localOpMetadata) => {
|
|
@@ -485,10 +530,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
485
530
|
},
|
|
486
531
|
});
|
|
487
532
|
this.messageHandlers.set("createSubDirectory", {
|
|
488
|
-
process: (op, local, localOpMetadata) => {
|
|
533
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
489
534
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
490
535
|
if (parentSubdir) {
|
|
491
|
-
parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
|
|
536
|
+
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
492
537
|
}
|
|
493
538
|
},
|
|
494
539
|
submit: (op, localOpMetadata) => {
|
|
@@ -498,7 +543,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
498
543
|
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
499
544
|
}
|
|
500
545
|
},
|
|
501
|
-
// eslint-disable-next-line max-len
|
|
502
546
|
applyStashedOp: (op) => {
|
|
503
547
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
504
548
|
if (parentSubdir) {
|
|
@@ -507,10 +551,10 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
507
551
|
},
|
|
508
552
|
});
|
|
509
553
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
510
|
-
process: (op, local, localOpMetadata) => {
|
|
554
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
511
555
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
512
556
|
if (parentSubdir) {
|
|
513
|
-
parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
|
|
557
|
+
parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
514
558
|
}
|
|
515
559
|
},
|
|
516
560
|
submit: (op, localOpMetadata) => {
|
|
@@ -520,7 +564,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
520
564
|
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
521
565
|
}
|
|
522
566
|
},
|
|
523
|
-
// eslint-disable-next-line max-len
|
|
524
567
|
applyStashedOp: (op) => {
|
|
525
568
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
526
569
|
if (parentSubdir) {
|
|
@@ -530,6 +573,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
530
573
|
});
|
|
531
574
|
}
|
|
532
575
|
/**
|
|
576
|
+
* {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
|
|
533
577
|
* @internal
|
|
534
578
|
*/
|
|
535
579
|
applyStashedOp(op) {
|
|
@@ -550,20 +594,20 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
550
594
|
while (stack.length > 0) {
|
|
551
595
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
552
596
|
const [currentSubDir, currentSubDirObject] = stack.pop();
|
|
597
|
+
currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
|
|
553
598
|
for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
|
|
554
599
|
if (!currentSubDirObject.storage) {
|
|
555
600
|
currentSubDirObject.storage = {};
|
|
556
601
|
}
|
|
557
602
|
const result = {
|
|
558
603
|
type: value.type,
|
|
559
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
560
604
|
value: value.value && JSON.parse(value.value),
|
|
561
605
|
};
|
|
562
606
|
if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
|
|
563
607
|
const extraContent = {};
|
|
564
608
|
let largeContent = extraContent;
|
|
565
609
|
if (currentSubDir.absolutePath !== posix.sep) {
|
|
566
|
-
for (const dir of currentSubDir.absolutePath.
|
|
610
|
+
for (const dir of currentSubDir.absolutePath.slice(1).split(posix.sep)) {
|
|
567
611
|
const subDataObject = {};
|
|
568
612
|
largeContent.subdirectories = { [dir]: subDataObject };
|
|
569
613
|
largeContent = subDataObject;
|
|
@@ -597,24 +641,32 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
597
641
|
}
|
|
598
642
|
}
|
|
599
643
|
exports.SharedDirectory = SharedDirectory;
|
|
644
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
600
645
|
function isKeyEditLocalOpMetadata(metadata) {
|
|
601
|
-
return metadata !== undefined &&
|
|
646
|
+
return (metadata !== undefined &&
|
|
647
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
648
|
+
metadata.type === "edit");
|
|
602
649
|
}
|
|
603
650
|
function isClearLocalOpMetadata(metadata) {
|
|
604
|
-
return metadata !== undefined &&
|
|
605
|
-
|
|
651
|
+
return (metadata !== undefined &&
|
|
652
|
+
metadata.type === "clear" &&
|
|
653
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
654
|
+
typeof metadata.previousStorage === "object");
|
|
606
655
|
}
|
|
607
656
|
function isSubDirLocalOpMetadata(metadata) {
|
|
608
|
-
return metadata !== undefined &&
|
|
609
|
-
|
|
610
|
-
|
|
657
|
+
return (metadata !== undefined &&
|
|
658
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
659
|
+
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
|
|
611
660
|
}
|
|
612
661
|
function isDirectoryLocalOpMetadata(metadata) {
|
|
613
|
-
return metadata !== undefined &&
|
|
614
|
-
|
|
662
|
+
return (metadata !== undefined &&
|
|
663
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
664
|
+
(metadata.type === "edit" ||
|
|
665
|
+
metadata.type === "deleteSubDir" ||
|
|
615
666
|
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
616
|
-
|
|
667
|
+
metadata.type === "createSubDir"));
|
|
617
668
|
}
|
|
669
|
+
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
618
670
|
/**
|
|
619
671
|
* Node of the directory tree.
|
|
620
672
|
* @sealed
|
|
@@ -622,13 +674,17 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
622
674
|
class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
623
675
|
/**
|
|
624
676
|
* Constructor.
|
|
677
|
+
* @param sequenceNumber - Message seq number at which this was created.
|
|
678
|
+
* @param clientIds - Ids of client which created this directory.
|
|
625
679
|
* @param directory - Reference back to the SharedDirectory to perform operations
|
|
626
680
|
* @param runtime - The data store runtime this directory is associated with
|
|
627
681
|
* @param serializer - The serializer to serialize / parse handles
|
|
628
682
|
* @param absolutePath - The absolute path of this IDirectory
|
|
629
683
|
*/
|
|
630
|
-
constructor(directory, runtime, serializer, absolutePath) {
|
|
684
|
+
constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
|
|
631
685
|
super();
|
|
686
|
+
this.sequenceNumber = sequenceNumber;
|
|
687
|
+
this.clientIds = clientIds;
|
|
632
688
|
this.directory = directory;
|
|
633
689
|
this.runtime = runtime;
|
|
634
690
|
this.serializer = serializer;
|
|
@@ -654,9 +710,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
654
710
|
*/
|
|
655
711
|
this.pendingKeys = new Map();
|
|
656
712
|
/**
|
|
657
|
-
* Subdirectories that have been
|
|
713
|
+
* Subdirectories that have been created/deleted locally but not yet ack'd from the server.
|
|
658
714
|
*/
|
|
659
715
|
this.pendingSubDirectories = new Map();
|
|
716
|
+
/**
|
|
717
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
|
|
718
|
+
* of delete op that are pending or yet to be acked from server.
|
|
719
|
+
*/
|
|
720
|
+
this.pendingDeleteSubDirectoriesCount = new Map();
|
|
660
721
|
/**
|
|
661
722
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
662
723
|
*/
|
|
@@ -671,10 +732,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
671
732
|
this.emit("disposed", this);
|
|
672
733
|
}
|
|
673
734
|
/**
|
|
674
|
-
* Unmark the deleted property when rolling back delete.
|
|
735
|
+
* Unmark the deleted property only when rolling back delete.
|
|
675
736
|
*/
|
|
676
737
|
undispose() {
|
|
677
738
|
this._deleted = false;
|
|
739
|
+
this.emit("undisposed", this);
|
|
678
740
|
}
|
|
679
741
|
get disposed() {
|
|
680
742
|
return this._deleted;
|
|
@@ -738,6 +800,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
738
800
|
* {@inheritDoc IDirectory.createSubDirectory}
|
|
739
801
|
*/
|
|
740
802
|
createSubDirectory(subdirName) {
|
|
803
|
+
var _c;
|
|
741
804
|
this.throwIfDisposed();
|
|
742
805
|
// Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
|
|
743
806
|
if (subdirName === undefined || subdirName === null) {
|
|
@@ -747,19 +810,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
747
810
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
748
811
|
}
|
|
749
812
|
// Create the sub directory locally first.
|
|
750
|
-
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
751
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
813
|
+
const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
752
814
|
const subDir = this._subdirectories.get(subdirName);
|
|
815
|
+
(0, common_utils_1.assert)(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
|
|
753
816
|
// If we are not attached, don't submit the op.
|
|
754
817
|
if (!this.directory.isAttached()) {
|
|
755
818
|
return subDir;
|
|
756
819
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
820
|
+
// Only submit the op, if it is newly created.
|
|
821
|
+
if (isNew) {
|
|
822
|
+
const op = {
|
|
823
|
+
path: this.absolutePath,
|
|
824
|
+
subdirName,
|
|
825
|
+
type: "createSubDirectory",
|
|
826
|
+
};
|
|
827
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
828
|
+
}
|
|
763
829
|
return subDir;
|
|
764
830
|
}
|
|
765
831
|
/**
|
|
@@ -787,12 +853,15 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
787
853
|
if (!this.directory.isAttached()) {
|
|
788
854
|
return subDir !== undefined;
|
|
789
855
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
856
|
+
// Only submit the op, if the directory existed and we deleted it.
|
|
857
|
+
if (subDir !== undefined) {
|
|
858
|
+
const op = {
|
|
859
|
+
path: this.absolutePath,
|
|
860
|
+
subdirName,
|
|
861
|
+
type: "deleteSubDirectory",
|
|
862
|
+
};
|
|
863
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
864
|
+
}
|
|
796
865
|
return subDir !== undefined;
|
|
797
866
|
}
|
|
798
867
|
/**
|
|
@@ -809,6 +878,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
809
878
|
this.throwIfDisposed();
|
|
810
879
|
return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
|
|
811
880
|
}
|
|
881
|
+
/**
|
|
882
|
+
* This checks if there is pending delete op for local delete for a given child subdirectory.
|
|
883
|
+
* @param subDirName - directory name.
|
|
884
|
+
* @returns - true if there is pending delete.
|
|
885
|
+
*/
|
|
886
|
+
isSubDirectoryDeletePending(subDirName) {
|
|
887
|
+
const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
|
|
888
|
+
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
812
893
|
/**
|
|
813
894
|
* Deletes the given key from within this IDirectory.
|
|
814
895
|
* @param key - The key to delete
|
|
@@ -854,6 +935,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
854
935
|
*/
|
|
855
936
|
forEach(callback) {
|
|
856
937
|
this.throwIfDisposed();
|
|
938
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
857
939
|
this._storage.forEach((localValue, key, map) => {
|
|
858
940
|
callback(localValue.value, key, map);
|
|
859
941
|
});
|
|
@@ -923,14 +1005,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
923
1005
|
}
|
|
924
1006
|
/**
|
|
925
1007
|
* Process a clear operation.
|
|
1008
|
+
* @param msg - The message from the server to apply.
|
|
926
1009
|
* @param op - The op to process
|
|
927
1010
|
* @param local - Whether the message originated from the local client
|
|
928
1011
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
929
1012
|
* For messages from a remote client, this will be undefined.
|
|
930
1013
|
* @internal
|
|
931
1014
|
*/
|
|
932
|
-
processClearMessage(op, local, localOpMetadata) {
|
|
1015
|
+
processClearMessage(msg, op, local, localOpMetadata) {
|
|
933
1016
|
this.throwIfDisposed();
|
|
1017
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
934
1020
|
if (local) {
|
|
935
1021
|
(0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
|
|
936
1022
|
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
@@ -959,15 +1045,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
959
1045
|
}
|
|
960
1046
|
/**
|
|
961
1047
|
* Process a delete operation.
|
|
1048
|
+
* @param msg - The message from the server to apply.
|
|
962
1049
|
* @param op - The op to process
|
|
963
1050
|
* @param local - Whether the message originated from the local client
|
|
964
1051
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
965
1052
|
* For messages from a remote client, this will be undefined.
|
|
966
1053
|
* @internal
|
|
967
1054
|
*/
|
|
968
|
-
processDeleteMessage(op, local, localOpMetadata) {
|
|
1055
|
+
processDeleteMessage(msg, op, local, localOpMetadata) {
|
|
969
1056
|
this.throwIfDisposed();
|
|
970
|
-
if (!this.
|
|
1057
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1058
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
971
1059
|
return;
|
|
972
1060
|
}
|
|
973
1061
|
this.deleteCore(op.key, local);
|
|
@@ -981,20 +1069,26 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
981
1069
|
this.throwIfDisposed();
|
|
982
1070
|
const previousValue = this.deleteCore(op.key, true);
|
|
983
1071
|
const pendingMessageId = this.getKeyMessageId(op);
|
|
984
|
-
const localMetadata = {
|
|
1072
|
+
const localMetadata = {
|
|
1073
|
+
type: "edit",
|
|
1074
|
+
pendingMessageId,
|
|
1075
|
+
previousValue,
|
|
1076
|
+
};
|
|
985
1077
|
return localMetadata;
|
|
986
1078
|
}
|
|
987
1079
|
/**
|
|
988
1080
|
* Process a set operation.
|
|
1081
|
+
* @param msg - The message from the server to apply.
|
|
989
1082
|
* @param op - The op to process
|
|
990
1083
|
* @param local - Whether the message originated from the local client
|
|
991
1084
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
992
1085
|
* For messages from a remote client, this will be undefined.
|
|
993
1086
|
* @internal
|
|
994
1087
|
*/
|
|
995
|
-
processSetMessage(op, context, local, localOpMetadata) {
|
|
1088
|
+
processSetMessage(msg, op, context, local, localOpMetadata) {
|
|
996
1089
|
this.throwIfDisposed();
|
|
997
|
-
if (!this.
|
|
1090
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1091
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
998
1092
|
return;
|
|
999
1093
|
}
|
|
1000
1094
|
// needProcessStorageOperation should have returned false if local is true
|
|
@@ -1013,23 +1107,28 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1013
1107
|
const previousValue = this.setCore(op.key, context, true);
|
|
1014
1108
|
// Create metadata
|
|
1015
1109
|
const pendingMessageId = this.getKeyMessageId(op);
|
|
1016
|
-
const localMetadata = {
|
|
1110
|
+
const localMetadata = {
|
|
1111
|
+
type: "edit",
|
|
1112
|
+
pendingMessageId,
|
|
1113
|
+
previousValue,
|
|
1114
|
+
};
|
|
1017
1115
|
return localMetadata;
|
|
1018
1116
|
}
|
|
1019
1117
|
/**
|
|
1020
1118
|
* Process a create subdirectory operation.
|
|
1119
|
+
* @param msg - The message from the server to apply.
|
|
1021
1120
|
* @param op - The op to process
|
|
1022
1121
|
* @param local - Whether the message originated from the local client
|
|
1023
1122
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1024
1123
|
* For messages from a remote client, this will be undefined.
|
|
1025
1124
|
* @internal
|
|
1026
1125
|
*/
|
|
1027
|
-
processCreateSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1126
|
+
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1028
1127
|
this.throwIfDisposed();
|
|
1029
|
-
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1128
|
+
if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
|
|
1030
1129
|
return;
|
|
1031
1130
|
}
|
|
1032
|
-
this.createSubDirectoryCore(op.subdirName, local);
|
|
1131
|
+
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1033
1132
|
}
|
|
1034
1133
|
/**
|
|
1035
1134
|
* Apply createSubDirectory operation locally and generate metadata
|
|
@@ -1037,28 +1136,30 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1037
1136
|
* @returns metadata generated for stahed op
|
|
1038
1137
|
*/
|
|
1039
1138
|
applyStashedCreateSubDirMessage(op) {
|
|
1139
|
+
var _c;
|
|
1040
1140
|
this.throwIfDisposed();
|
|
1041
1141
|
// Create the sub directory locally first.
|
|
1042
|
-
|
|
1142
|
+
this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
1043
1143
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1044
1144
|
const localOpMetadata = {
|
|
1045
1145
|
type: "createSubDir",
|
|
1046
1146
|
pendingMessageId: newMessageId,
|
|
1047
|
-
previouslyExisted: !isNew,
|
|
1048
1147
|
};
|
|
1049
1148
|
return localOpMetadata;
|
|
1050
1149
|
}
|
|
1051
1150
|
/**
|
|
1052
1151
|
* Process a delete subdirectory operation.
|
|
1152
|
+
* @param msg - The message from the server to apply.
|
|
1053
1153
|
* @param op - The op to process
|
|
1054
1154
|
* @param local - Whether the message originated from the local client
|
|
1055
1155
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1056
1156
|
* For messages from a remote client, this will be undefined.
|
|
1057
1157
|
* @internal
|
|
1058
1158
|
*/
|
|
1059
|
-
processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1159
|
+
processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1060
1160
|
this.throwIfDisposed();
|
|
1061
|
-
if (!this.
|
|
1161
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1162
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1062
1163
|
return;
|
|
1063
1164
|
}
|
|
1064
1165
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1142,7 +1243,8 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1142
1243
|
(0, common_utils_1.assert)(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
|
|
1143
1244
|
// clear the old pending message id
|
|
1144
1245
|
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1145
|
-
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1246
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1247
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
|
|
1146
1248
|
pendingMessageIds.shift();
|
|
1147
1249
|
if (pendingMessageIds.length === 0) {
|
|
1148
1250
|
this.pendingKeys.delete(op.key);
|
|
@@ -1153,6 +1255,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1153
1255
|
* Get a new pending message id for the op and cache it to track the pending op
|
|
1154
1256
|
*/
|
|
1155
1257
|
getSubDirMessageId(op) {
|
|
1258
|
+
var _c;
|
|
1156
1259
|
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1157
1260
|
const newMessageId = ++this.pendingMessageId;
|
|
1158
1261
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
@@ -1162,20 +1265,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1162
1265
|
else {
|
|
1163
1266
|
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1164
1267
|
}
|
|
1268
|
+
if (op.type === "deleteSubDirectory") {
|
|
1269
|
+
const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1270
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
|
|
1271
|
+
}
|
|
1165
1272
|
return newMessageId;
|
|
1166
1273
|
}
|
|
1167
1274
|
/**
|
|
1168
1275
|
* Submit a create subdirectory operation.
|
|
1169
1276
|
* @param op - The operation
|
|
1170
|
-
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1171
1277
|
*/
|
|
1172
|
-
submitCreateSubDirectoryMessage(op
|
|
1278
|
+
submitCreateSubDirectoryMessage(op) {
|
|
1173
1279
|
this.throwIfDisposed();
|
|
1174
1280
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1175
1281
|
const localOpMetadata = {
|
|
1176
1282
|
type: "createSubDir",
|
|
1177
1283
|
pendingMessageId: newMessageId,
|
|
1178
|
-
previouslyExisted: prevExisted,
|
|
1179
1284
|
};
|
|
1180
1285
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1181
1286
|
}
|
|
@@ -1204,13 +1309,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1204
1309
|
(0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
|
|
1205
1310
|
// clear the old pending message id
|
|
1206
1311
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1207
|
-
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1312
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1313
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
|
|
1208
1314
|
pendingMessageIds.shift();
|
|
1209
1315
|
if (pendingMessageIds.length === 0) {
|
|
1210
1316
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1211
1317
|
}
|
|
1212
1318
|
if (localOpMetadata.type === "createSubDir") {
|
|
1213
|
-
this.submitCreateSubDirectoryMessage(op
|
|
1319
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1214
1320
|
}
|
|
1215
1321
|
else {
|
|
1216
1322
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
@@ -1230,6 +1336,14 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1230
1336
|
yield res;
|
|
1231
1337
|
}
|
|
1232
1338
|
}
|
|
1339
|
+
getSerializableCreateInfo() {
|
|
1340
|
+
this.throwIfDisposed();
|
|
1341
|
+
const createInfo = {
|
|
1342
|
+
csn: this.sequenceNumber,
|
|
1343
|
+
ccIds: Array.from(this.clientIds),
|
|
1344
|
+
};
|
|
1345
|
+
return createInfo;
|
|
1346
|
+
}
|
|
1233
1347
|
/**
|
|
1234
1348
|
* Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
|
|
1235
1349
|
* @param key - The key to populate
|
|
@@ -1276,21 +1390,24 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1276
1390
|
map.delete(key);
|
|
1277
1391
|
}
|
|
1278
1392
|
}
|
|
1393
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
1279
1394
|
/**
|
|
1280
1395
|
* Rollback a local op
|
|
1281
1396
|
* @param op - The operation to rollback
|
|
1282
1397
|
* @param localOpMetadata - The local metadata associated with the op.
|
|
1283
1398
|
*/
|
|
1399
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1284
1400
|
rollback(op, localOpMetadata) {
|
|
1285
1401
|
if (!isDirectoryLocalOpMetadata(localOpMetadata)) {
|
|
1286
1402
|
throw new Error("Invalid localOpMetadata");
|
|
1287
1403
|
}
|
|
1288
1404
|
if (op.type === "clear" && localOpMetadata.type === "clear") {
|
|
1289
|
-
localOpMetadata.previousStorage.
|
|
1405
|
+
for (const [key, localValue] of localOpMetadata.previousStorage.entries()) {
|
|
1290
1406
|
this.setCore(key, localValue, true);
|
|
1291
|
-
}
|
|
1407
|
+
}
|
|
1292
1408
|
const lastPendingClearId = this.pendingClearMessageIds.pop();
|
|
1293
|
-
if (lastPendingClearId === undefined ||
|
|
1409
|
+
if (lastPendingClearId === undefined ||
|
|
1410
|
+
lastPendingClearId !== localOpMetadata.pendingMessageId) {
|
|
1294
1411
|
throw new Error("Rollback op does match last clear");
|
|
1295
1412
|
}
|
|
1296
1413
|
}
|
|
@@ -1304,9 +1421,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1304
1421
|
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1305
1422
|
}
|
|
1306
1423
|
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1307
|
-
|
|
1308
|
-
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1309
|
-
}
|
|
1424
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1310
1425
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1311
1426
|
}
|
|
1312
1427
|
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
@@ -1317,11 +1432,18 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1317
1432
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1318
1433
|
}
|
|
1319
1434
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1435
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1436
|
+
(0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
1437
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1438
|
+
if (count === 1) {
|
|
1439
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1440
|
+
}
|
|
1320
1441
|
}
|
|
1321
1442
|
else {
|
|
1322
1443
|
throw new Error("Unsupported op for rollback");
|
|
1323
1444
|
}
|
|
1324
1445
|
}
|
|
1446
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
1325
1447
|
/**
|
|
1326
1448
|
* Converts the given relative path into an absolute path.
|
|
1327
1449
|
* @param path - Relative path to convert
|
|
@@ -1342,8 +1464,25 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1342
1464
|
needProcessStorageOperation(op, local, localOpMetadata) {
|
|
1343
1465
|
if (this.pendingClearMessageIds.length > 0) {
|
|
1344
1466
|
if (local) {
|
|
1345
|
-
(0, common_utils_1.assert)(localOpMetadata !== undefined &&
|
|
1467
|
+
(0, common_utils_1.assert)(localOpMetadata !== undefined &&
|
|
1468
|
+
isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1346
1469
|
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1470
|
+
// Remove all pendingMessageIds lower than first pendingClearMessageId.
|
|
1471
|
+
const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
|
|
1472
|
+
const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
|
|
1473
|
+
if (pendingKeyMessageIdArray !== undefined) {
|
|
1474
|
+
let index = 0;
|
|
1475
|
+
while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
|
|
1476
|
+
index += 1;
|
|
1477
|
+
}
|
|
1478
|
+
const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
|
|
1479
|
+
if (newPendingKeyMessageId.length === 0) {
|
|
1480
|
+
this.pendingKeys.delete(op.key);
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
this.pendingKeys.set(op.key, newPendingKeyMessageId);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1347
1486
|
}
|
|
1348
1487
|
// If I have a NACK clear, we can ignore all ops.
|
|
1349
1488
|
return false;
|
|
@@ -1355,7 +1494,8 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1355
1494
|
if (local) {
|
|
1356
1495
|
(0, common_utils_1.assert)(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata), 0x011 /* pendingMessageId is missing from the local client's operation */);
|
|
1357
1496
|
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1358
|
-
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1497
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1498
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
|
|
1359
1499
|
pendingMessageIds.shift();
|
|
1360
1500
|
if (pendingMessageIds.length === 0) {
|
|
1361
1501
|
this.pendingKeys.delete(op.key);
|
|
@@ -1366,6 +1506,19 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1366
1506
|
// If we don't have a NACK op on the key, we need to process the remote ops.
|
|
1367
1507
|
return !local;
|
|
1368
1508
|
}
|
|
1509
|
+
/**
|
|
1510
|
+
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
1511
|
+
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
1512
|
+
* @param msg - message for the directory
|
|
1513
|
+
*/
|
|
1514
|
+
isMessageForCurrentInstanceOfSubDirectory(msg) {
|
|
1515
|
+
// If the message is either from the creator of directory or this directory was created when
|
|
1516
|
+
// container was detached or in case this directory is already live(known to other clients)
|
|
1517
|
+
// and the op was created after the directory was created then apply this op.
|
|
1518
|
+
return (this.clientIds.has(msg.clientId) ||
|
|
1519
|
+
this.clientIds.has("detached") ||
|
|
1520
|
+
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
|
|
1521
|
+
}
|
|
1369
1522
|
/**
|
|
1370
1523
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1371
1524
|
* not process the incoming operation.
|
|
@@ -1376,17 +1529,52 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1376
1529
|
* For messages from a remote client, this will be undefined.
|
|
1377
1530
|
* @returns True if the operation should be processed, false otherwise
|
|
1378
1531
|
*/
|
|
1379
|
-
needProcessSubDirectoryOperation(op, local, localOpMetadata) {
|
|
1532
|
+
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1380
1533
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1381
1534
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1382
1535
|
if (local) {
|
|
1383
1536
|
(0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
|
|
1384
1537
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1385
|
-
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1538
|
+
(0, common_utils_1.assert)(pendingMessageIds !== undefined &&
|
|
1539
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
|
|
1386
1540
|
pendingMessageIds.shift();
|
|
1387
1541
|
if (pendingMessageIds.length === 0) {
|
|
1388
1542
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1389
1543
|
}
|
|
1544
|
+
if (op.type === "deleteSubDirectory") {
|
|
1545
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1546
|
+
(0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
|
|
1547
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1548
|
+
if (count === 1) {
|
|
1549
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
else if (op.type === "deleteSubDirectory") {
|
|
1554
|
+
// If this is remote delete op and we have keys in this subDirectory, then we need to delete these
|
|
1555
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
1556
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1557
|
+
if (subDirectory) {
|
|
1558
|
+
subDirectory.clearExceptPendingKeys(local);
|
|
1559
|
+
// In case of remote delete op, we need to reset the creation seq number and client ids of
|
|
1560
|
+
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1561
|
+
// we will receive op for the create again.
|
|
1562
|
+
subDirectory.sequenceNumber = -1;
|
|
1563
|
+
subDirectory.clientIds.clear();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (op.type === "createSubDirectory") {
|
|
1567
|
+
const dir = this._subdirectories.get(op.subdirName);
|
|
1568
|
+
if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
|
|
1569
|
+
// Only set the seq on the first message, could be more
|
|
1570
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
1571
|
+
}
|
|
1572
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1573
|
+
if (dir !== undefined &&
|
|
1574
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
1575
|
+
dir.sequenceNumber <= msg.sequenceNumber) {
|
|
1576
|
+
dir.clientIds.add(msg.clientId);
|
|
1577
|
+
}
|
|
1390
1578
|
}
|
|
1391
1579
|
return false;
|
|
1392
1580
|
}
|
|
@@ -1399,14 +1587,17 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1399
1587
|
// Assuming the pendingKeys is small and the map is large
|
|
1400
1588
|
// we will get the value for the pendingKeys and clear the map
|
|
1401
1589
|
const temp = new Map();
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1590
|
+
for (const [key] of this.pendingKeys) {
|
|
1591
|
+
const value = this._storage.get(key);
|
|
1592
|
+
// If this key is already deleted, then we don't need to add it again.
|
|
1593
|
+
if (value !== undefined) {
|
|
1594
|
+
temp.set(key, value);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1406
1597
|
this.clearCore(local);
|
|
1407
|
-
|
|
1598
|
+
for (const [key, value] of temp.entries()) {
|
|
1408
1599
|
this.setCore(key, value, true);
|
|
1409
|
-
}
|
|
1600
|
+
}
|
|
1410
1601
|
}
|
|
1411
1602
|
/**
|
|
1412
1603
|
* Clear implementation used for both locally sourced clears as well as incoming remote clears.
|
|
@@ -1455,17 +1646,23 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1455
1646
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1456
1647
|
* @param subdirName - The name of the subdirectory being created
|
|
1457
1648
|
* @param local - Whether the message originated from the local client
|
|
1649
|
+
* @param seq - Sequence number at which this directory is created
|
|
1650
|
+
* @param clientId - Id of client which created this directory.
|
|
1458
1651
|
* @returns - True if is newly created, false if it already existed.
|
|
1459
1652
|
*/
|
|
1460
|
-
createSubDirectoryCore(subdirName, local) {
|
|
1461
|
-
|
|
1653
|
+
createSubDirectoryCore(subdirName, local, seq, clientId) {
|
|
1654
|
+
const subdir = this._subdirectories.get(subdirName);
|
|
1655
|
+
if (subdir === undefined) {
|
|
1462
1656
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1463
|
-
const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
|
|
1657
|
+
const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
|
|
1464
1658
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1465
1659
|
this._subdirectories.set(subdirName, subDir);
|
|
1466
1660
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1467
1661
|
return true;
|
|
1468
1662
|
}
|
|
1663
|
+
else {
|
|
1664
|
+
subdir.clientIds.add(clientId);
|
|
1665
|
+
}
|
|
1469
1666
|
return false;
|
|
1470
1667
|
}
|
|
1471
1668
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|