@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/lib/directory.js
CHANGED
|
@@ -6,11 +6,11 @@ var _a, _b;
|
|
|
6
6
|
import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
7
7
|
import { UsageError } from "@fluidframework/container-utils";
|
|
8
8
|
import { readAndParse } from "@fluidframework/driver-utils";
|
|
9
|
-
import { MessageType
|
|
9
|
+
import { MessageType } from "@fluidframework/protocol-definitions";
|
|
10
10
|
import { SharedObject, ValueType } from "@fluidframework/shared-object-base";
|
|
11
11
|
import { SummaryTreeBuilder } from "@fluidframework/runtime-utils";
|
|
12
12
|
import * as path from "path-browserify";
|
|
13
|
-
import { LocalValueMaker, makeSerializable
|
|
13
|
+
import { LocalValueMaker, makeSerializable } from "./localValues";
|
|
14
14
|
import { pkgVersion } from "./packageVersion";
|
|
15
15
|
// We use path-browserify since this code can run safely on the server or the browser.
|
|
16
16
|
// We standardize on using posix slashes everywhere.
|
|
@@ -92,7 +92,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
92
92
|
/**
|
|
93
93
|
* Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
|
|
94
94
|
*/
|
|
95
|
-
this.root = new SubDirectory(this, this.runtime, this.serializer, posix.sep);
|
|
95
|
+
this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
|
|
96
96
|
/**
|
|
97
97
|
* Mapping of op types to message handlers.
|
|
98
98
|
*/
|
|
@@ -137,6 +137,8 @@ export class SharedDirectory extends SharedObject {
|
|
|
137
137
|
/**
|
|
138
138
|
* {@inheritDoc IDirectory.get}
|
|
139
139
|
*/
|
|
140
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
142
|
get(key) {
|
|
141
143
|
return this.root.get(key);
|
|
142
144
|
}
|
|
@@ -185,13 +187,18 @@ export class SharedDirectory extends SharedObject {
|
|
|
185
187
|
* Issue a callback on each entry under this IDirectory.
|
|
186
188
|
* @param callback - Callback to issue
|
|
187
189
|
*/
|
|
190
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
188
192
|
forEach(callback) {
|
|
193
|
+
// eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference
|
|
189
194
|
this.root.forEach(callback);
|
|
190
195
|
}
|
|
191
196
|
/**
|
|
192
197
|
* Get an iterator over the entries under this IDirectory.
|
|
193
198
|
* @returns The iterator
|
|
194
199
|
*/
|
|
200
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
202
|
[(_a = Symbol.toStringTag, Symbol.iterator)]() {
|
|
196
203
|
return this.root[Symbol.iterator]();
|
|
197
204
|
}
|
|
@@ -199,6 +206,8 @@ export class SharedDirectory extends SharedObject {
|
|
|
199
206
|
* Get an iterator over the entries under this IDirectory.
|
|
200
207
|
* @returns The iterator
|
|
201
208
|
*/
|
|
209
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
211
|
entries() {
|
|
203
212
|
return this.root.entries();
|
|
204
213
|
}
|
|
@@ -219,6 +228,8 @@ export class SharedDirectory extends SharedObject {
|
|
|
219
228
|
* Get an iterator over the values under this IDirectory.
|
|
220
229
|
* @returns The iterator
|
|
221
230
|
*/
|
|
231
|
+
// TODO: Use `unknown` instead (breaking change).
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
233
|
values() {
|
|
223
234
|
return this.root.values();
|
|
224
235
|
}
|
|
@@ -261,7 +272,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
261
272
|
return this.root;
|
|
262
273
|
}
|
|
263
274
|
let currentSubDir = this.root;
|
|
264
|
-
const subdirs = absolutePath.
|
|
275
|
+
const subdirs = absolutePath.slice(1).split(posix.sep);
|
|
265
276
|
for (const subdir of subdirs) {
|
|
266
277
|
currentSubDir = currentSubDir.getSubDirectory(subdir);
|
|
267
278
|
if (!currentSubDir) {
|
|
@@ -337,7 +348,10 @@ export class SharedDirectory extends SharedObject {
|
|
|
337
348
|
for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
|
|
338
349
|
let newSubDir = currentSubDir.getSubDirectory(subdirName);
|
|
339
350
|
if (!newSubDir) {
|
|
340
|
-
|
|
351
|
+
const createInfo = subdirObject.ci;
|
|
352
|
+
newSubDir = new SubDirectory(createInfo !== undefined ? createInfo.csn : 0, createInfo !== undefined
|
|
353
|
+
? new Set(createInfo.ccIds)
|
|
354
|
+
: new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
|
|
341
355
|
currentSubDir.populateSubDirectory(subdirName, newSubDir);
|
|
342
356
|
}
|
|
343
357
|
stack.push([newSubDir, subdirObject]);
|
|
@@ -360,13 +374,13 @@ export class SharedDirectory extends SharedObject {
|
|
|
360
374
|
const op = message.contents;
|
|
361
375
|
const handler = this.messageHandlers.get(op.type);
|
|
362
376
|
assert(handler !== undefined, 0x00e /* Missing message handler for message type */);
|
|
363
|
-
handler.process(op, local, localOpMetadata);
|
|
377
|
+
handler.process(message, op, local, localOpMetadata);
|
|
364
378
|
}
|
|
365
379
|
}
|
|
366
380
|
/**
|
|
367
381
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
|
|
368
382
|
* @internal
|
|
369
|
-
|
|
383
|
+
*/
|
|
370
384
|
rollback(content, localOpMetadata) {
|
|
371
385
|
const op = content;
|
|
372
386
|
const subdir = this.getWorkingDirectory(op.path);
|
|
@@ -392,18 +406,45 @@ export class SharedDirectory extends SharedObject {
|
|
|
392
406
|
* @returns The local value that was produced
|
|
393
407
|
*/
|
|
394
408
|
makeLocal(key, absolutePath, serializable) {
|
|
395
|
-
assert(serializable.type === ValueType[ValueType.Plain] ||
|
|
409
|
+
assert(serializable.type === ValueType[ValueType.Plain] ||
|
|
410
|
+
serializable.type === ValueType[ValueType.Shared], 0x1e4 /* "Unexpected serializable type" */);
|
|
396
411
|
return this.localValueMaker.fromSerializable(serializable);
|
|
397
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* This checks if there is pending delete op for local delete for a subdirectory.
|
|
415
|
+
* @param relativePath - path of sub directory.
|
|
416
|
+
* @returns - true if there is pending delete.
|
|
417
|
+
*/
|
|
418
|
+
isSubDirectoryDeletePending(relativePath) {
|
|
419
|
+
const parentSubDir = this.getParentDirectory(relativePath);
|
|
420
|
+
const index = relativePath.lastIndexOf(posix.sep);
|
|
421
|
+
const dirName = relativePath.substring(index + 1);
|
|
422
|
+
return !!(parentSubDir === null || parentSubDir === void 0 ? void 0 : parentSubDir.isSubDirectoryDeletePending(dirName));
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Gets the parent directory of a sub directory.
|
|
426
|
+
* @param relativePath - path of sub directory of which parent needs to be find out.
|
|
427
|
+
*/
|
|
428
|
+
getParentDirectory(relativePath) {
|
|
429
|
+
const absolutePath = this.makeAbsolute(relativePath);
|
|
430
|
+
if (absolutePath === posix.sep) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
const index = absolutePath.lastIndexOf(posix.sep);
|
|
434
|
+
const parentAbsPath = absolutePath.substring(0, index);
|
|
435
|
+
return this.getWorkingDirectory(parentAbsPath);
|
|
436
|
+
}
|
|
398
437
|
/**
|
|
399
438
|
* Set the message handlers for the directory.
|
|
400
439
|
*/
|
|
401
440
|
setMessageHandlers() {
|
|
402
441
|
this.messageHandlers.set("clear", {
|
|
403
|
-
process: (op, local, localOpMetadata) => {
|
|
442
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
404
443
|
const subdir = this.getWorkingDirectory(op.path);
|
|
405
|
-
|
|
406
|
-
|
|
444
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
445
|
+
// to delete this subDirectory.
|
|
446
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
447
|
+
subdir.processClearMessage(msg, op, local, localOpMetadata);
|
|
407
448
|
}
|
|
408
449
|
},
|
|
409
450
|
submit: (op, localOpMetadata) => {
|
|
@@ -420,10 +461,12 @@ export class SharedDirectory extends SharedObject {
|
|
|
420
461
|
},
|
|
421
462
|
});
|
|
422
463
|
this.messageHandlers.set("delete", {
|
|
423
|
-
process: (op, local, localOpMetadata) => {
|
|
464
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
424
465
|
const subdir = this.getWorkingDirectory(op.path);
|
|
425
|
-
|
|
426
|
-
|
|
466
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
467
|
+
// to delete this subDirectory.
|
|
468
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
469
|
+
subdir.processDeleteMessage(msg, op, local, localOpMetadata);
|
|
427
470
|
}
|
|
428
471
|
},
|
|
429
472
|
submit: (op, localOpMetadata) => {
|
|
@@ -440,11 +483,13 @@ export class SharedDirectory extends SharedObject {
|
|
|
440
483
|
},
|
|
441
484
|
});
|
|
442
485
|
this.messageHandlers.set("set", {
|
|
443
|
-
process: (op, local, localOpMetadata) => {
|
|
486
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
444
487
|
const subdir = this.getWorkingDirectory(op.path);
|
|
445
|
-
|
|
488
|
+
// If there is pending delete op for this subDirectory, then don't apply the this op as we are going
|
|
489
|
+
// to delete this subDirectory.
|
|
490
|
+
if (subdir && !this.isSubDirectoryDeletePending(op.path)) {
|
|
446
491
|
const context = local ? undefined : this.makeLocal(op.key, op.path, op.value);
|
|
447
|
-
subdir.processSetMessage(op, context, local, localOpMetadata);
|
|
492
|
+
subdir.processSetMessage(msg, op, context, local, localOpMetadata);
|
|
448
493
|
}
|
|
449
494
|
},
|
|
450
495
|
submit: (op, localOpMetadata) => {
|
|
@@ -462,10 +507,10 @@ export class SharedDirectory extends SharedObject {
|
|
|
462
507
|
},
|
|
463
508
|
});
|
|
464
509
|
this.messageHandlers.set("createSubDirectory", {
|
|
465
|
-
process: (op, local, localOpMetadata) => {
|
|
510
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
466
511
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
467
512
|
if (parentSubdir) {
|
|
468
|
-
parentSubdir.processCreateSubDirectoryMessage(op, local, localOpMetadata);
|
|
513
|
+
parentSubdir.processCreateSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
469
514
|
}
|
|
470
515
|
},
|
|
471
516
|
submit: (op, localOpMetadata) => {
|
|
@@ -475,7 +520,6 @@ export class SharedDirectory extends SharedObject {
|
|
|
475
520
|
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
476
521
|
}
|
|
477
522
|
},
|
|
478
|
-
// eslint-disable-next-line max-len
|
|
479
523
|
applyStashedOp: (op) => {
|
|
480
524
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
481
525
|
if (parentSubdir) {
|
|
@@ -484,10 +528,10 @@ export class SharedDirectory extends SharedObject {
|
|
|
484
528
|
},
|
|
485
529
|
});
|
|
486
530
|
this.messageHandlers.set("deleteSubDirectory", {
|
|
487
|
-
process: (op, local, localOpMetadata) => {
|
|
531
|
+
process: (msg, op, local, localOpMetadata) => {
|
|
488
532
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
489
533
|
if (parentSubdir) {
|
|
490
|
-
parentSubdir.processDeleteSubDirectoryMessage(op, local, localOpMetadata);
|
|
534
|
+
parentSubdir.processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata);
|
|
491
535
|
}
|
|
492
536
|
},
|
|
493
537
|
submit: (op, localOpMetadata) => {
|
|
@@ -497,7 +541,6 @@ export class SharedDirectory extends SharedObject {
|
|
|
497
541
|
parentSubdir.resubmitSubDirectoryMessage(op, localOpMetadata);
|
|
498
542
|
}
|
|
499
543
|
},
|
|
500
|
-
// eslint-disable-next-line max-len
|
|
501
544
|
applyStashedOp: (op) => {
|
|
502
545
|
const parentSubdir = this.getWorkingDirectory(op.path);
|
|
503
546
|
if (parentSubdir) {
|
|
@@ -507,6 +550,7 @@ export class SharedDirectory extends SharedObject {
|
|
|
507
550
|
});
|
|
508
551
|
}
|
|
509
552
|
/**
|
|
553
|
+
* {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
|
|
510
554
|
* @internal
|
|
511
555
|
*/
|
|
512
556
|
applyStashedOp(op) {
|
|
@@ -527,20 +571,20 @@ export class SharedDirectory extends SharedObject {
|
|
|
527
571
|
while (stack.length > 0) {
|
|
528
572
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
529
573
|
const [currentSubDir, currentSubDirObject] = stack.pop();
|
|
574
|
+
currentSubDirObject.ci = currentSubDir.getSerializableCreateInfo();
|
|
530
575
|
for (const [key, value] of currentSubDir.getSerializedStorage(serializer)) {
|
|
531
576
|
if (!currentSubDirObject.storage) {
|
|
532
577
|
currentSubDirObject.storage = {};
|
|
533
578
|
}
|
|
534
579
|
const result = {
|
|
535
580
|
type: value.type,
|
|
536
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
537
581
|
value: value.value && JSON.parse(value.value),
|
|
538
582
|
};
|
|
539
583
|
if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
|
|
540
584
|
const extraContent = {};
|
|
541
585
|
let largeContent = extraContent;
|
|
542
586
|
if (currentSubDir.absolutePath !== posix.sep) {
|
|
543
|
-
for (const dir of currentSubDir.absolutePath.
|
|
587
|
+
for (const dir of currentSubDir.absolutePath.slice(1).split(posix.sep)) {
|
|
544
588
|
const subDataObject = {};
|
|
545
589
|
largeContent.subdirectories = { [dir]: subDataObject };
|
|
546
590
|
largeContent = subDataObject;
|
|
@@ -573,24 +617,32 @@ export class SharedDirectory extends SharedObject {
|
|
|
573
617
|
return builder.getSummaryTree();
|
|
574
618
|
}
|
|
575
619
|
}
|
|
620
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
576
621
|
function isKeyEditLocalOpMetadata(metadata) {
|
|
577
|
-
return metadata !== undefined &&
|
|
622
|
+
return (metadata !== undefined &&
|
|
623
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
624
|
+
metadata.type === "edit");
|
|
578
625
|
}
|
|
579
626
|
function isClearLocalOpMetadata(metadata) {
|
|
580
|
-
return metadata !== undefined &&
|
|
581
|
-
|
|
627
|
+
return (metadata !== undefined &&
|
|
628
|
+
metadata.type === "clear" &&
|
|
629
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
630
|
+
typeof metadata.previousStorage === "object");
|
|
582
631
|
}
|
|
583
632
|
function isSubDirLocalOpMetadata(metadata) {
|
|
584
|
-
return metadata !== undefined &&
|
|
585
|
-
|
|
586
|
-
|
|
633
|
+
return (metadata !== undefined &&
|
|
634
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
635
|
+
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
|
|
587
636
|
}
|
|
588
637
|
function isDirectoryLocalOpMetadata(metadata) {
|
|
589
|
-
return metadata !== undefined &&
|
|
590
|
-
|
|
638
|
+
return (metadata !== undefined &&
|
|
639
|
+
typeof metadata.pendingMessageId === "number" &&
|
|
640
|
+
(metadata.type === "edit" ||
|
|
641
|
+
metadata.type === "deleteSubDir" ||
|
|
591
642
|
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
592
|
-
|
|
643
|
+
metadata.type === "createSubDir"));
|
|
593
644
|
}
|
|
645
|
+
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
594
646
|
/**
|
|
595
647
|
* Node of the directory tree.
|
|
596
648
|
* @sealed
|
|
@@ -598,13 +650,17 @@ function isDirectoryLocalOpMetadata(metadata) {
|
|
|
598
650
|
class SubDirectory extends TypedEventEmitter {
|
|
599
651
|
/**
|
|
600
652
|
* Constructor.
|
|
653
|
+
* @param sequenceNumber - Message seq number at which this was created.
|
|
654
|
+
* @param clientIds - Ids of client which created this directory.
|
|
601
655
|
* @param directory - Reference back to the SharedDirectory to perform operations
|
|
602
656
|
* @param runtime - The data store runtime this directory is associated with
|
|
603
657
|
* @param serializer - The serializer to serialize / parse handles
|
|
604
658
|
* @param absolutePath - The absolute path of this IDirectory
|
|
605
659
|
*/
|
|
606
|
-
constructor(directory, runtime, serializer, absolutePath) {
|
|
660
|
+
constructor(sequenceNumber, clientIds, directory, runtime, serializer, absolutePath) {
|
|
607
661
|
super();
|
|
662
|
+
this.sequenceNumber = sequenceNumber;
|
|
663
|
+
this.clientIds = clientIds;
|
|
608
664
|
this.directory = directory;
|
|
609
665
|
this.runtime = runtime;
|
|
610
666
|
this.serializer = serializer;
|
|
@@ -630,9 +686,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
630
686
|
*/
|
|
631
687
|
this.pendingKeys = new Map();
|
|
632
688
|
/**
|
|
633
|
-
* Subdirectories that have been
|
|
689
|
+
* Subdirectories that have been created/deleted locally but not yet ack'd from the server.
|
|
634
690
|
*/
|
|
635
691
|
this.pendingSubDirectories = new Map();
|
|
692
|
+
/**
|
|
693
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the count
|
|
694
|
+
* of delete op that are pending or yet to be acked from server.
|
|
695
|
+
*/
|
|
696
|
+
this.pendingDeleteSubDirectoriesCount = new Map();
|
|
636
697
|
/**
|
|
637
698
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
638
699
|
*/
|
|
@@ -647,10 +708,11 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
647
708
|
this.emit("disposed", this);
|
|
648
709
|
}
|
|
649
710
|
/**
|
|
650
|
-
* Unmark the deleted property when rolling back delete.
|
|
711
|
+
* Unmark the deleted property only when rolling back delete.
|
|
651
712
|
*/
|
|
652
713
|
undispose() {
|
|
653
714
|
this._deleted = false;
|
|
715
|
+
this.emit("undisposed", this);
|
|
654
716
|
}
|
|
655
717
|
get disposed() {
|
|
656
718
|
return this._deleted;
|
|
@@ -714,6 +776,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
714
776
|
* {@inheritDoc IDirectory.createSubDirectory}
|
|
715
777
|
*/
|
|
716
778
|
createSubDirectory(subdirName) {
|
|
779
|
+
var _c;
|
|
717
780
|
this.throwIfDisposed();
|
|
718
781
|
// Undefined/null subdirectory names can't be serialized to JSON in the manner we currently snapshot.
|
|
719
782
|
if (subdirName === undefined || subdirName === null) {
|
|
@@ -723,19 +786,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
723
786
|
throw new Error(`SubDirectory name may not contain ${posix.sep}`);
|
|
724
787
|
}
|
|
725
788
|
// Create the sub directory locally first.
|
|
726
|
-
const isNew = this.createSubDirectoryCore(subdirName, true);
|
|
727
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
789
|
+
const isNew = this.createSubDirectoryCore(subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
728
790
|
const subDir = this._subdirectories.get(subdirName);
|
|
791
|
+
assert(subDir !== undefined, 0x5aa /* subdirectory should exist after creation */);
|
|
729
792
|
// If we are not attached, don't submit the op.
|
|
730
793
|
if (!this.directory.isAttached()) {
|
|
731
794
|
return subDir;
|
|
732
795
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
796
|
+
// Only submit the op, if it is newly created.
|
|
797
|
+
if (isNew) {
|
|
798
|
+
const op = {
|
|
799
|
+
path: this.absolutePath,
|
|
800
|
+
subdirName,
|
|
801
|
+
type: "createSubDirectory",
|
|
802
|
+
};
|
|
803
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
804
|
+
}
|
|
739
805
|
return subDir;
|
|
740
806
|
}
|
|
741
807
|
/**
|
|
@@ -763,12 +829,15 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
763
829
|
if (!this.directory.isAttached()) {
|
|
764
830
|
return subDir !== undefined;
|
|
765
831
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
832
|
+
// Only submit the op, if the directory existed and we deleted it.
|
|
833
|
+
if (subDir !== undefined) {
|
|
834
|
+
const op = {
|
|
835
|
+
path: this.absolutePath,
|
|
836
|
+
subdirName,
|
|
837
|
+
type: "deleteSubDirectory",
|
|
838
|
+
};
|
|
839
|
+
this.submitDeleteSubDirectoryMessage(op, subDir);
|
|
840
|
+
}
|
|
772
841
|
return subDir !== undefined;
|
|
773
842
|
}
|
|
774
843
|
/**
|
|
@@ -785,6 +854,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
785
854
|
this.throwIfDisposed();
|
|
786
855
|
return this.directory.getWorkingDirectory(this.makeAbsolute(relativePath));
|
|
787
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* This checks if there is pending delete op for local delete for a given child subdirectory.
|
|
859
|
+
* @param subDirName - directory name.
|
|
860
|
+
* @returns - true if there is pending delete.
|
|
861
|
+
*/
|
|
862
|
+
isSubDirectoryDeletePending(subDirName) {
|
|
863
|
+
const pendingDeleteSubDirectory = this.pendingDeleteSubDirectoriesCount.get(subDirName);
|
|
864
|
+
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
788
869
|
/**
|
|
789
870
|
* Deletes the given key from within this IDirectory.
|
|
790
871
|
* @param key - The key to delete
|
|
@@ -830,6 +911,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
830
911
|
*/
|
|
831
912
|
forEach(callback) {
|
|
832
913
|
this.throwIfDisposed();
|
|
914
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
833
915
|
this._storage.forEach((localValue, key, map) => {
|
|
834
916
|
callback(localValue.value, key, map);
|
|
835
917
|
});
|
|
@@ -899,14 +981,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
899
981
|
}
|
|
900
982
|
/**
|
|
901
983
|
* Process a clear operation.
|
|
984
|
+
* @param msg - The message from the server to apply.
|
|
902
985
|
* @param op - The op to process
|
|
903
986
|
* @param local - Whether the message originated from the local client
|
|
904
987
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
905
988
|
* For messages from a remote client, this will be undefined.
|
|
906
989
|
* @internal
|
|
907
990
|
*/
|
|
908
|
-
processClearMessage(op, local, localOpMetadata) {
|
|
991
|
+
processClearMessage(msg, op, local, localOpMetadata) {
|
|
909
992
|
this.throwIfDisposed();
|
|
993
|
+
if (!this.isMessageForCurrentInstanceOfSubDirectory(msg)) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
910
996
|
if (local) {
|
|
911
997
|
assert(isClearLocalOpMetadata(localOpMetadata), 0x00f /* pendingMessageId is missing from the local client's operation */);
|
|
912
998
|
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
@@ -935,15 +1021,17 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
935
1021
|
}
|
|
936
1022
|
/**
|
|
937
1023
|
* Process a delete operation.
|
|
1024
|
+
* @param msg - The message from the server to apply.
|
|
938
1025
|
* @param op - The op to process
|
|
939
1026
|
* @param local - Whether the message originated from the local client
|
|
940
1027
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
941
1028
|
* For messages from a remote client, this will be undefined.
|
|
942
1029
|
* @internal
|
|
943
1030
|
*/
|
|
944
|
-
processDeleteMessage(op, local, localOpMetadata) {
|
|
1031
|
+
processDeleteMessage(msg, op, local, localOpMetadata) {
|
|
945
1032
|
this.throwIfDisposed();
|
|
946
|
-
if (!this.
|
|
1033
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1034
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
947
1035
|
return;
|
|
948
1036
|
}
|
|
949
1037
|
this.deleteCore(op.key, local);
|
|
@@ -957,20 +1045,26 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
957
1045
|
this.throwIfDisposed();
|
|
958
1046
|
const previousValue = this.deleteCore(op.key, true);
|
|
959
1047
|
const pendingMessageId = this.getKeyMessageId(op);
|
|
960
|
-
const localMetadata = {
|
|
1048
|
+
const localMetadata = {
|
|
1049
|
+
type: "edit",
|
|
1050
|
+
pendingMessageId,
|
|
1051
|
+
previousValue,
|
|
1052
|
+
};
|
|
961
1053
|
return localMetadata;
|
|
962
1054
|
}
|
|
963
1055
|
/**
|
|
964
1056
|
* Process a set operation.
|
|
1057
|
+
* @param msg - The message from the server to apply.
|
|
965
1058
|
* @param op - The op to process
|
|
966
1059
|
* @param local - Whether the message originated from the local client
|
|
967
1060
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
968
1061
|
* For messages from a remote client, this will be undefined.
|
|
969
1062
|
* @internal
|
|
970
1063
|
*/
|
|
971
|
-
processSetMessage(op, context, local, localOpMetadata) {
|
|
1064
|
+
processSetMessage(msg, op, context, local, localOpMetadata) {
|
|
972
1065
|
this.throwIfDisposed();
|
|
973
|
-
if (!this.
|
|
1066
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1067
|
+
this.needProcessStorageOperation(op, local, localOpMetadata))) {
|
|
974
1068
|
return;
|
|
975
1069
|
}
|
|
976
1070
|
// needProcessStorageOperation should have returned false if local is true
|
|
@@ -989,23 +1083,28 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
989
1083
|
const previousValue = this.setCore(op.key, context, true);
|
|
990
1084
|
// Create metadata
|
|
991
1085
|
const pendingMessageId = this.getKeyMessageId(op);
|
|
992
|
-
const localMetadata = {
|
|
1086
|
+
const localMetadata = {
|
|
1087
|
+
type: "edit",
|
|
1088
|
+
pendingMessageId,
|
|
1089
|
+
previousValue,
|
|
1090
|
+
};
|
|
993
1091
|
return localMetadata;
|
|
994
1092
|
}
|
|
995
1093
|
/**
|
|
996
1094
|
* Process a create subdirectory operation.
|
|
1095
|
+
* @param msg - The message from the server to apply.
|
|
997
1096
|
* @param op - The op to process
|
|
998
1097
|
* @param local - Whether the message originated from the local client
|
|
999
1098
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1000
1099
|
* For messages from a remote client, this will be undefined.
|
|
1001
1100
|
* @internal
|
|
1002
1101
|
*/
|
|
1003
|
-
processCreateSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1102
|
+
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1004
1103
|
this.throwIfDisposed();
|
|
1005
|
-
if (!this.needProcessSubDirectoryOperation(op, local, localOpMetadata)) {
|
|
1104
|
+
if (!this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata)) {
|
|
1006
1105
|
return;
|
|
1007
1106
|
}
|
|
1008
|
-
this.createSubDirectoryCore(op.subdirName, local);
|
|
1107
|
+
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1009
1108
|
}
|
|
1010
1109
|
/**
|
|
1011
1110
|
* Apply createSubDirectory operation locally and generate metadata
|
|
@@ -1013,28 +1112,30 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1013
1112
|
* @returns metadata generated for stahed op
|
|
1014
1113
|
*/
|
|
1015
1114
|
applyStashedCreateSubDirMessage(op) {
|
|
1115
|
+
var _c;
|
|
1016
1116
|
this.throwIfDisposed();
|
|
1017
1117
|
// Create the sub directory locally first.
|
|
1018
|
-
|
|
1118
|
+
this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
1019
1119
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1020
1120
|
const localOpMetadata = {
|
|
1021
1121
|
type: "createSubDir",
|
|
1022
1122
|
pendingMessageId: newMessageId,
|
|
1023
|
-
previouslyExisted: !isNew,
|
|
1024
1123
|
};
|
|
1025
1124
|
return localOpMetadata;
|
|
1026
1125
|
}
|
|
1027
1126
|
/**
|
|
1028
1127
|
* Process a delete subdirectory operation.
|
|
1128
|
+
* @param msg - The message from the server to apply.
|
|
1029
1129
|
* @param op - The op to process
|
|
1030
1130
|
* @param local - Whether the message originated from the local client
|
|
1031
1131
|
* @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message.
|
|
1032
1132
|
* For messages from a remote client, this will be undefined.
|
|
1033
1133
|
* @internal
|
|
1034
1134
|
*/
|
|
1035
|
-
processDeleteSubDirectoryMessage(op, local, localOpMetadata) {
|
|
1135
|
+
processDeleteSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1036
1136
|
this.throwIfDisposed();
|
|
1037
|
-
if (!this.
|
|
1137
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1138
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1038
1139
|
return;
|
|
1039
1140
|
}
|
|
1040
1141
|
this.deleteSubDirectoryCore(op.subdirName, local);
|
|
@@ -1118,7 +1219,8 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1118
1219
|
assert(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
|
|
1119
1220
|
// clear the old pending message id
|
|
1120
1221
|
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1121
|
-
assert(pendingMessageIds !== undefined &&
|
|
1222
|
+
assert(pendingMessageIds !== undefined &&
|
|
1223
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x32e /* Unexpected pending message received */);
|
|
1122
1224
|
pendingMessageIds.shift();
|
|
1123
1225
|
if (pendingMessageIds.length === 0) {
|
|
1124
1226
|
this.pendingKeys.delete(op.key);
|
|
@@ -1129,6 +1231,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1129
1231
|
* Get a new pending message id for the op and cache it to track the pending op
|
|
1130
1232
|
*/
|
|
1131
1233
|
getSubDirMessageId(op) {
|
|
1234
|
+
var _c;
|
|
1132
1235
|
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1133
1236
|
const newMessageId = ++this.pendingMessageId;
|
|
1134
1237
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
@@ -1138,20 +1241,22 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1138
1241
|
else {
|
|
1139
1242
|
this.pendingSubDirectories.set(op.subdirName, [newMessageId]);
|
|
1140
1243
|
}
|
|
1244
|
+
if (op.type === "deleteSubDirectory") {
|
|
1245
|
+
const count = (_c = this.pendingDeleteSubDirectoriesCount.get(op.subdirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1246
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count + 1);
|
|
1247
|
+
}
|
|
1141
1248
|
return newMessageId;
|
|
1142
1249
|
}
|
|
1143
1250
|
/**
|
|
1144
1251
|
* Submit a create subdirectory operation.
|
|
1145
1252
|
* @param op - The operation
|
|
1146
|
-
* @param prevExisted - Whether the subdirectory existed before the op
|
|
1147
1253
|
*/
|
|
1148
|
-
submitCreateSubDirectoryMessage(op
|
|
1254
|
+
submitCreateSubDirectoryMessage(op) {
|
|
1149
1255
|
this.throwIfDisposed();
|
|
1150
1256
|
const newMessageId = this.getSubDirMessageId(op);
|
|
1151
1257
|
const localOpMetadata = {
|
|
1152
1258
|
type: "createSubDir",
|
|
1153
1259
|
pendingMessageId: newMessageId,
|
|
1154
|
-
previouslyExisted: prevExisted,
|
|
1155
1260
|
};
|
|
1156
1261
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1157
1262
|
}
|
|
@@ -1180,13 +1285,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1180
1285
|
assert(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
|
|
1181
1286
|
// clear the old pending message id
|
|
1182
1287
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1183
|
-
assert(pendingMessageIds !== undefined &&
|
|
1288
|
+
assert(pendingMessageIds !== undefined &&
|
|
1289
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x330 /* Unexpected pending message received */);
|
|
1184
1290
|
pendingMessageIds.shift();
|
|
1185
1291
|
if (pendingMessageIds.length === 0) {
|
|
1186
1292
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1187
1293
|
}
|
|
1188
1294
|
if (localOpMetadata.type === "createSubDir") {
|
|
1189
|
-
this.submitCreateSubDirectoryMessage(op
|
|
1295
|
+
this.submitCreateSubDirectoryMessage(op);
|
|
1190
1296
|
}
|
|
1191
1297
|
else {
|
|
1192
1298
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
@@ -1206,6 +1312,14 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1206
1312
|
yield res;
|
|
1207
1313
|
}
|
|
1208
1314
|
}
|
|
1315
|
+
getSerializableCreateInfo() {
|
|
1316
|
+
this.throwIfDisposed();
|
|
1317
|
+
const createInfo = {
|
|
1318
|
+
csn: this.sequenceNumber,
|
|
1319
|
+
ccIds: Array.from(this.clientIds),
|
|
1320
|
+
};
|
|
1321
|
+
return createInfo;
|
|
1322
|
+
}
|
|
1209
1323
|
/**
|
|
1210
1324
|
* Populate a key value in this subdirectory's storage, to be used when loading from snapshot.
|
|
1211
1325
|
* @param key - The key to populate
|
|
@@ -1252,21 +1366,24 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1252
1366
|
map.delete(key);
|
|
1253
1367
|
}
|
|
1254
1368
|
}
|
|
1369
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
1255
1370
|
/**
|
|
1256
1371
|
* Rollback a local op
|
|
1257
1372
|
* @param op - The operation to rollback
|
|
1258
1373
|
* @param localOpMetadata - The local metadata associated with the op.
|
|
1259
1374
|
*/
|
|
1375
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1260
1376
|
rollback(op, localOpMetadata) {
|
|
1261
1377
|
if (!isDirectoryLocalOpMetadata(localOpMetadata)) {
|
|
1262
1378
|
throw new Error("Invalid localOpMetadata");
|
|
1263
1379
|
}
|
|
1264
1380
|
if (op.type === "clear" && localOpMetadata.type === "clear") {
|
|
1265
|
-
localOpMetadata.previousStorage.
|
|
1381
|
+
for (const [key, localValue] of localOpMetadata.previousStorage.entries()) {
|
|
1266
1382
|
this.setCore(key, localValue, true);
|
|
1267
|
-
}
|
|
1383
|
+
}
|
|
1268
1384
|
const lastPendingClearId = this.pendingClearMessageIds.pop();
|
|
1269
|
-
if (lastPendingClearId === undefined ||
|
|
1385
|
+
if (lastPendingClearId === undefined ||
|
|
1386
|
+
lastPendingClearId !== localOpMetadata.pendingMessageId) {
|
|
1270
1387
|
throw new Error("Rollback op does match last clear");
|
|
1271
1388
|
}
|
|
1272
1389
|
}
|
|
@@ -1280,9 +1397,7 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1280
1397
|
this.rollbackPendingMessageId(this.pendingKeys, op.key, localOpMetadata.pendingMessageId);
|
|
1281
1398
|
}
|
|
1282
1399
|
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1283
|
-
|
|
1284
|
-
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1285
|
-
}
|
|
1400
|
+
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1286
1401
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1287
1402
|
}
|
|
1288
1403
|
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
@@ -1293,11 +1408,18 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1293
1408
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1294
1409
|
}
|
|
1295
1410
|
this.rollbackPendingMessageId(this.pendingSubDirectories, op.subdirName, localOpMetadata.pendingMessageId);
|
|
1411
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1412
|
+
assert(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
1413
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1414
|
+
if (count === 1) {
|
|
1415
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1416
|
+
}
|
|
1296
1417
|
}
|
|
1297
1418
|
else {
|
|
1298
1419
|
throw new Error("Unsupported op for rollback");
|
|
1299
1420
|
}
|
|
1300
1421
|
}
|
|
1422
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
1301
1423
|
/**
|
|
1302
1424
|
* Converts the given relative path into an absolute path.
|
|
1303
1425
|
* @param path - Relative path to convert
|
|
@@ -1318,8 +1440,25 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1318
1440
|
needProcessStorageOperation(op, local, localOpMetadata) {
|
|
1319
1441
|
if (this.pendingClearMessageIds.length > 0) {
|
|
1320
1442
|
if (local) {
|
|
1321
|
-
assert(localOpMetadata !== undefined &&
|
|
1443
|
+
assert(localOpMetadata !== undefined &&
|
|
1444
|
+
isKeyEditLocalOpMetadata(localOpMetadata) &&
|
|
1322
1445
|
localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], 0x010 /* "Received out of order storage op when there is an unackd clear message" */);
|
|
1446
|
+
// Remove all pendingMessageIds lower than first pendingClearMessageId.
|
|
1447
|
+
const lowestPendingClearMessageId = this.pendingClearMessageIds[0];
|
|
1448
|
+
const pendingKeyMessageIdArray = this.pendingKeys.get(op.key);
|
|
1449
|
+
if (pendingKeyMessageIdArray !== undefined) {
|
|
1450
|
+
let index = 0;
|
|
1451
|
+
while (pendingKeyMessageIdArray[index] < lowestPendingClearMessageId) {
|
|
1452
|
+
index += 1;
|
|
1453
|
+
}
|
|
1454
|
+
const newPendingKeyMessageId = pendingKeyMessageIdArray.splice(index);
|
|
1455
|
+
if (newPendingKeyMessageId.length === 0) {
|
|
1456
|
+
this.pendingKeys.delete(op.key);
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.pendingKeys.set(op.key, newPendingKeyMessageId);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1323
1462
|
}
|
|
1324
1463
|
// If I have a NACK clear, we can ignore all ops.
|
|
1325
1464
|
return false;
|
|
@@ -1331,7 +1470,8 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1331
1470
|
if (local) {
|
|
1332
1471
|
assert(localOpMetadata !== undefined && isKeyEditLocalOpMetadata(localOpMetadata), 0x011 /* pendingMessageId is missing from the local client's operation */);
|
|
1333
1472
|
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1334
|
-
assert(pendingMessageIds !== undefined &&
|
|
1473
|
+
assert(pendingMessageIds !== undefined &&
|
|
1474
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x331 /* Unexpected pending message received */);
|
|
1335
1475
|
pendingMessageIds.shift();
|
|
1336
1476
|
if (pendingMessageIds.length === 0) {
|
|
1337
1477
|
this.pendingKeys.delete(op.key);
|
|
@@ -1342,6 +1482,19 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1342
1482
|
// If we don't have a NACK op on the key, we need to process the remote ops.
|
|
1343
1483
|
return !local;
|
|
1344
1484
|
}
|
|
1485
|
+
/**
|
|
1486
|
+
* This return true if the message is for the current instance of this sub directory. As the sub directory
|
|
1487
|
+
* can be deleted and created again, then this finds if the message is for current instance of directory or not.
|
|
1488
|
+
* @param msg - message for the directory
|
|
1489
|
+
*/
|
|
1490
|
+
isMessageForCurrentInstanceOfSubDirectory(msg) {
|
|
1491
|
+
// If the message is either from the creator of directory or this directory was created when
|
|
1492
|
+
// container was detached or in case this directory is already live(known to other clients)
|
|
1493
|
+
// and the op was created after the directory was created then apply this op.
|
|
1494
|
+
return (this.clientIds.has(msg.clientId) ||
|
|
1495
|
+
this.clientIds.has("detached") ||
|
|
1496
|
+
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
|
|
1497
|
+
}
|
|
1345
1498
|
/**
|
|
1346
1499
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
1347
1500
|
* not process the incoming operation.
|
|
@@ -1352,17 +1505,52 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1352
1505
|
* For messages from a remote client, this will be undefined.
|
|
1353
1506
|
* @returns True if the operation should be processed, false otherwise
|
|
1354
1507
|
*/
|
|
1355
|
-
needProcessSubDirectoryOperation(op, local, localOpMetadata) {
|
|
1508
|
+
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1356
1509
|
const pendingSubDirectoryMessageId = this.pendingSubDirectories.get(op.subdirName);
|
|
1357
1510
|
if (pendingSubDirectoryMessageId !== undefined) {
|
|
1358
1511
|
if (local) {
|
|
1359
1512
|
assert(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
|
|
1360
1513
|
const pendingMessageIds = this.pendingSubDirectories.get(op.subdirName);
|
|
1361
|
-
assert(pendingMessageIds !== undefined &&
|
|
1514
|
+
assert(pendingMessageIds !== undefined &&
|
|
1515
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId, 0x332 /* Unexpected pending message received */);
|
|
1362
1516
|
pendingMessageIds.shift();
|
|
1363
1517
|
if (pendingMessageIds.length === 0) {
|
|
1364
1518
|
this.pendingSubDirectories.delete(op.subdirName);
|
|
1365
1519
|
}
|
|
1520
|
+
if (op.type === "deleteSubDirectory") {
|
|
1521
|
+
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1522
|
+
assert(count !== undefined && count > 0, 0x5ac /* should have record for delete op */);
|
|
1523
|
+
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1524
|
+
if (count === 1) {
|
|
1525
|
+
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
else if (op.type === "deleteSubDirectory") {
|
|
1530
|
+
// If this is remote delete op and we have keys in this subDirectory, then we need to delete these
|
|
1531
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
1532
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1533
|
+
if (subDirectory) {
|
|
1534
|
+
subDirectory.clearExceptPendingKeys(local);
|
|
1535
|
+
// In case of remote delete op, we need to reset the creation seq number and client ids of
|
|
1536
|
+
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1537
|
+
// we will receive op for the create again.
|
|
1538
|
+
subDirectory.sequenceNumber = -1;
|
|
1539
|
+
subDirectory.clientIds.clear();
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (op.type === "createSubDirectory") {
|
|
1543
|
+
const dir = this._subdirectories.get(op.subdirName);
|
|
1544
|
+
if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
|
|
1545
|
+
// Only set the seq on the first message, could be more
|
|
1546
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
1547
|
+
}
|
|
1548
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1549
|
+
if (dir !== undefined &&
|
|
1550
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
1551
|
+
dir.sequenceNumber <= msg.sequenceNumber) {
|
|
1552
|
+
dir.clientIds.add(msg.clientId);
|
|
1553
|
+
}
|
|
1366
1554
|
}
|
|
1367
1555
|
return false;
|
|
1368
1556
|
}
|
|
@@ -1375,14 +1563,17 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1375
1563
|
// Assuming the pendingKeys is small and the map is large
|
|
1376
1564
|
// we will get the value for the pendingKeys and clear the map
|
|
1377
1565
|
const temp = new Map();
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1566
|
+
for (const [key] of this.pendingKeys) {
|
|
1567
|
+
const value = this._storage.get(key);
|
|
1568
|
+
// If this key is already deleted, then we don't need to add it again.
|
|
1569
|
+
if (value !== undefined) {
|
|
1570
|
+
temp.set(key, value);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1382
1573
|
this.clearCore(local);
|
|
1383
|
-
|
|
1574
|
+
for (const [key, value] of temp.entries()) {
|
|
1384
1575
|
this.setCore(key, value, true);
|
|
1385
|
-
}
|
|
1576
|
+
}
|
|
1386
1577
|
}
|
|
1387
1578
|
/**
|
|
1388
1579
|
* Clear implementation used for both locally sourced clears as well as incoming remote clears.
|
|
@@ -1431,17 +1622,23 @@ class SubDirectory extends TypedEventEmitter {
|
|
|
1431
1622
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1432
1623
|
* @param subdirName - The name of the subdirectory being created
|
|
1433
1624
|
* @param local - Whether the message originated from the local client
|
|
1625
|
+
* @param seq - Sequence number at which this directory is created
|
|
1626
|
+
* @param clientId - Id of client which created this directory.
|
|
1434
1627
|
* @returns - True if is newly created, false if it already existed.
|
|
1435
1628
|
*/
|
|
1436
|
-
createSubDirectoryCore(subdirName, local) {
|
|
1437
|
-
|
|
1629
|
+
createSubDirectoryCore(subdirName, local, seq, clientId) {
|
|
1630
|
+
const subdir = this._subdirectories.get(subdirName);
|
|
1631
|
+
if (subdir === undefined) {
|
|
1438
1632
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1439
|
-
const subDir = new SubDirectory(this.directory, this.runtime, this.serializer, absolutePath);
|
|
1633
|
+
const subDir = new SubDirectory(seq, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
|
|
1440
1634
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1441
1635
|
this._subdirectories.set(subdirName, subDir);
|
|
1442
1636
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
1443
1637
|
return true;
|
|
1444
1638
|
}
|
|
1639
|
+
else {
|
|
1640
|
+
subdir.clientIds.add(clientId);
|
|
1641
|
+
}
|
|
1445
1642
|
return false;
|
|
1446
1643
|
}
|
|
1447
1644
|
registerEventsOnSubDirectory(subDirectory, subDirName) {
|