@fluidframework/map 2.0.0-internal.7.2.2 → 2.0.0-internal.7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/api-extractor-lint.json +13 -0
- package/api-extractor.json +9 -1
- package/api-report/map.api.md +42 -44
- package/dist/{directory.js → directory.cjs} +228 -41
- package/dist/directory.cjs.map +1 -0
- package/dist/directory.d.ts +499 -38
- package/dist/directory.d.ts.map +1 -1
- package/dist/{index.js → index.cjs} +4 -4
- package/dist/index.cjs.map +1 -0
- package/dist/{interfaces.js → interfaces.cjs} +1 -1
- package/dist/interfaces.cjs.map +1 -0
- package/dist/interfaces.d.ts +10 -20
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/{internalInterfaces.js → internalInterfaces.cjs} +1 -1
- package/dist/internalInterfaces.cjs.map +1 -0
- package/dist/{localValues.js → localValues.cjs} +2 -3
- package/dist/localValues.cjs.map +1 -0
- package/dist/localValues.d.ts +2 -4
- package/dist/localValues.d.ts.map +1 -1
- package/dist/map-alpha.d.ts +979 -0
- package/dist/map-beta.d.ts +119 -0
- package/dist/map-public.d.ts +119 -0
- package/dist/map-untrimmed.d.ts +993 -0
- package/dist/{map.js → map.cjs} +5 -13
- package/dist/map.cjs.map +1 -0
- package/dist/map.d.ts +2 -10
- package/dist/map.d.ts.map +1 -1
- package/dist/{mapKernel.js → mapKernel.cjs} +2 -2
- package/dist/mapKernel.cjs.map +1 -0
- package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
- package/dist/packageVersion.cjs.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/directory.d.ts +499 -38
- package/lib/directory.d.ts.map +1 -1
- package/lib/{directory.js → directory.mjs} +228 -41
- package/lib/directory.mjs.map +1 -0
- package/lib/index.d.ts +4 -15
- package/lib/index.d.ts.map +1 -1
- package/lib/index.mjs +8 -0
- package/lib/index.mjs.map +1 -0
- package/lib/interfaces.d.ts +10 -20
- package/lib/interfaces.d.ts.map +1 -1
- package/lib/{interfaces.js → interfaces.mjs} +1 -1
- package/lib/interfaces.mjs.map +1 -0
- package/lib/internalInterfaces.d.ts +2 -2
- package/lib/internalInterfaces.d.ts.map +1 -1
- package/lib/{internalInterfaces.js → internalInterfaces.mjs} +1 -1
- package/{dist/internalInterfaces.js.map → lib/internalInterfaces.mjs.map} +1 -1
- package/lib/localValues.d.ts +3 -5
- package/lib/localValues.d.ts.map +1 -1
- package/lib/{localValues.js → localValues.mjs} +2 -3
- package/lib/localValues.mjs.map +1 -0
- package/lib/map-alpha.d.ts +979 -0
- package/lib/map-beta.d.ts +119 -0
- package/lib/map-public.d.ts +119 -0
- package/lib/map-untrimmed.d.ts +993 -0
- package/lib/map.d.ts +3 -11
- package/lib/map.d.ts.map +1 -1
- package/lib/{map.js → map.mjs} +5 -13
- package/lib/map.mjs.map +1 -0
- package/lib/mapKernel.d.ts +2 -2
- package/lib/mapKernel.d.ts.map +1 -1
- package/lib/{mapKernel.js → mapKernel.mjs} +2 -2
- package/lib/mapKernel.mjs.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
- package/lib/packageVersion.mjs.map +1 -0
- package/map.test-files.tar +0 -0
- package/package.json +54 -33
- package/src/directory.ts +280 -62
- package/src/interfaces.ts +10 -20
- package/src/localValues.ts +2 -4
- package/src/map.ts +2 -10
- package/src/packageVersion.ts +1 -1
- package/tsc-multi.test.json +4 -0
- package/tsconfig.json +6 -5
- package/dist/directory.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interfaces.js.map +0 -1
- package/dist/localValues.js.map +0 -1
- package/dist/map.js.map +0 -1
- package/dist/mapKernel.js.map +0 -1
- package/dist/packageVersion.js.map +0 -1
- package/lib/directory.js.map +0 -1
- package/lib/index.js +0 -19
- package/lib/index.js.map +0 -1
- package/lib/interfaces.js.map +0 -1
- package/lib/internalInterfaces.js.map +0 -1
- package/lib/localValues.js.map +0 -1
- package/lib/map.js.map +0 -1
- package/lib/mapKernel.js.map +0 -1
- package/lib/packageVersion.js.map +0 -1
- package/tsconfig.esnext.json +0 -7
|
@@ -37,8 +37,9 @@ const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
|
|
|
37
37
|
const shared_object_base_1 = require("@fluidframework/shared-object-base");
|
|
38
38
|
const runtime_utils_1 = require("@fluidframework/runtime-utils");
|
|
39
39
|
const path = __importStar(require("path-browserify"));
|
|
40
|
-
const
|
|
41
|
-
const
|
|
40
|
+
const merge_tree_1 = require("@fluidframework/merge-tree");
|
|
41
|
+
const localValues_1 = require("./localValues.cjs");
|
|
42
|
+
const packageVersion_1 = require("./packageVersion.cjs");
|
|
42
43
|
// We use path-browserify since this code can run safely on the server or the browser.
|
|
43
44
|
// We standardize on using posix slashes everywhere.
|
|
44
45
|
const posix = path.posix;
|
|
@@ -47,7 +48,7 @@ const snapshotFileName = "header";
|
|
|
47
48
|
* {@link @fluidframework/datastore-definitions#IChannelFactory} for {@link SharedDirectory}.
|
|
48
49
|
*
|
|
49
50
|
* @sealed
|
|
50
|
-
* @
|
|
51
|
+
* @alpha
|
|
51
52
|
*/
|
|
52
53
|
class DirectoryFactory {
|
|
53
54
|
/**
|
|
@@ -92,6 +93,100 @@ DirectoryFactory.Attributes = {
|
|
|
92
93
|
snapshotFormatVersion: "0.1",
|
|
93
94
|
packageVersion: packageVersion_1.pkgVersion,
|
|
94
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* The comparator essentially performs the following procedure to determine the order of subdirectory creation:
|
|
98
|
+
* 1. If subdirectory A has a non-negative 'seq' and subdirectory B has a negative 'seq', subdirectory A is always placed first due to
|
|
99
|
+
* the policy that acknowledged subdirectories precede locally created ones that have not been committed yet.
|
|
100
|
+
*
|
|
101
|
+
* 2. When both subdirectories A and B have a non-negative 'seq', they are compared as follows:
|
|
102
|
+
* - If A and B have different 'seq', they are ordered based on 'seq', and the one with the lower 'seq' will be positioned ahead. Notably this rule
|
|
103
|
+
* should not be applied in the directory ordering, since the lowest 'seq' is -1, when the directory is created locally but not acknowledged yet.
|
|
104
|
+
* - In the case where A and B have equal 'seq', the one with the lower 'clientSeq' will be positioned ahead. This scenario occurs when grouped
|
|
105
|
+
* batching is enabled, and a lower 'clientSeq' indicates that it was processed earlier after the batch was ungrouped.
|
|
106
|
+
*
|
|
107
|
+
* 3. When both subdirectories A and B have a negative 'seq', they are compared as follows:
|
|
108
|
+
* - If A and B have different 'seq', the one with lower 'seq' will be positioned ahead, which indicates the corresponding creation message was
|
|
109
|
+
* acknowledged by the server earlier.
|
|
110
|
+
* - If A and B have equal 'seq', the one with lower 'clientSeq' will be placed at the front. This scenario suggests that both subdirectories A
|
|
111
|
+
* and B were created locally and not acknowledged yet, with the one possessing the lower 'clientSeq' being created earlier.
|
|
112
|
+
*
|
|
113
|
+
* 4. A 'seq' value of zero indicates that the subdirectory was created in detached state, and it is considered acknowledged for the
|
|
114
|
+
* purpose of ordering.
|
|
115
|
+
*/
|
|
116
|
+
const seqDataComparator = (a, b) => {
|
|
117
|
+
if (isAcknowledgedOrDetached(a)) {
|
|
118
|
+
if (isAcknowledgedOrDetached(b)) {
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
120
|
+
return a.seq !== b.seq ? a.seq - b.seq : a.clientSeq - b.clientSeq;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
if (!isAcknowledgedOrDetached(b)) {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
129
|
+
return a.seq !== b.seq ? a.seq - b.seq : a.clientSeq - b.clientSeq;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function isAcknowledgedOrDetached(seqData) {
|
|
137
|
+
return seqData.seq >= 0;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A utility class for tracking associations between keys and their creation indices.
|
|
141
|
+
* This is relevant to support map iteration in insertion order, see
|
|
142
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/%40%40iterator
|
|
143
|
+
*
|
|
144
|
+
* TODO: It can be combined with the creation tracker utilized in SharedMap
|
|
145
|
+
*/
|
|
146
|
+
class DirectoryCreationTracker {
|
|
147
|
+
constructor() {
|
|
148
|
+
this.indexToKey = new merge_tree_1.RedBlackTree(seqDataComparator);
|
|
149
|
+
this.keyToIndex = new Map();
|
|
150
|
+
}
|
|
151
|
+
set(key, seqData) {
|
|
152
|
+
this.indexToKey.put(seqData, key);
|
|
153
|
+
this.keyToIndex.set(key, seqData);
|
|
154
|
+
}
|
|
155
|
+
has(keyOrSeqData) {
|
|
156
|
+
return typeof keyOrSeqData === "string"
|
|
157
|
+
? this.keyToIndex.has(keyOrSeqData)
|
|
158
|
+
: this.indexToKey.get(keyOrSeqData) !== undefined;
|
|
159
|
+
}
|
|
160
|
+
delete(keyOrSeqData) {
|
|
161
|
+
if (this.has(keyOrSeqData)) {
|
|
162
|
+
if (typeof keyOrSeqData === "string") {
|
|
163
|
+
const seqData = this.keyToIndex.get(keyOrSeqData);
|
|
164
|
+
this.keyToIndex.delete(keyOrSeqData);
|
|
165
|
+
this.indexToKey.remove(seqData);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const key = this.indexToKey.get(keyOrSeqData)?.data;
|
|
169
|
+
this.indexToKey.remove(keyOrSeqData);
|
|
170
|
+
this.keyToIndex.delete(key);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Retrieves all subdirectories with creation order that satisfy an optional constraint function.
|
|
176
|
+
* @param constraint - An optional constraint function that filters keys.
|
|
177
|
+
* @returns An array of keys that satisfy the constraint (or all keys if no constraint is provided).
|
|
178
|
+
*/
|
|
179
|
+
keys(constraint) {
|
|
180
|
+
const keys = [];
|
|
181
|
+
this.indexToKey.mapRange((node) => {
|
|
182
|
+
if (!constraint || constraint(node.data)) {
|
|
183
|
+
keys.push(node.data);
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}, keys);
|
|
187
|
+
return keys;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
95
190
|
/**
|
|
96
191
|
* {@inheritDoc ISharedDirectory}
|
|
97
192
|
*
|
|
@@ -104,7 +199,7 @@ DirectoryFactory.Attributes = {
|
|
|
104
199
|
* ```
|
|
105
200
|
*
|
|
106
201
|
* @sealed
|
|
107
|
-
* @
|
|
202
|
+
* @alpha
|
|
108
203
|
*/
|
|
109
204
|
class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
110
205
|
/**
|
|
@@ -147,7 +242,7 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
147
242
|
/**
|
|
148
243
|
* Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root.
|
|
149
244
|
*/
|
|
150
|
-
this.root = new SubDirectory(0, new Set(), this, this.runtime, this.serializer, posix.sep);
|
|
245
|
+
this.root = new SubDirectory({ seq: 0, clientSeq: 0 }, new Set(), this, this.runtime, this.serializer, posix.sep);
|
|
151
246
|
/**
|
|
152
247
|
* Mapping of op types to message handlers.
|
|
153
248
|
*/
|
|
@@ -314,7 +409,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
314
409
|
}
|
|
315
410
|
/**
|
|
316
411
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.summarizeCore}
|
|
317
|
-
* @internal
|
|
318
412
|
*/
|
|
319
413
|
summarizeCore(serializer, telemetryContext) {
|
|
320
414
|
return this.serializeDirectory(this.root, serializer);
|
|
@@ -324,19 +418,16 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
324
418
|
* @param op - Op to submit
|
|
325
419
|
* @param localOpMetadata - The local metadata associated with the op. We send a unique id that is used to track
|
|
326
420
|
* this op while it has not been ack'd. This will be sent when we receive this op back from the server.
|
|
327
|
-
* @internal
|
|
328
421
|
*/
|
|
329
422
|
submitDirectoryMessage(op, localOpMetadata) {
|
|
330
423
|
this.submitLocalMessage(op, localOpMetadata);
|
|
331
424
|
}
|
|
332
425
|
/**
|
|
333
426
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.onDisconnect}
|
|
334
|
-
* @internal
|
|
335
427
|
*/
|
|
336
428
|
onDisconnect() { }
|
|
337
429
|
/**
|
|
338
430
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.reSubmitCore}
|
|
339
|
-
* @internal
|
|
340
431
|
*/
|
|
341
432
|
reSubmitCore(content, localOpMetadata) {
|
|
342
433
|
const message = content;
|
|
@@ -346,7 +437,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
346
437
|
}
|
|
347
438
|
/**
|
|
348
439
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
|
|
349
|
-
* @internal
|
|
350
440
|
*/
|
|
351
441
|
async loadCore(storage) {
|
|
352
442
|
const data = await (0, driver_utils_1.readAndParse)(storage, snapshotFileName);
|
|
@@ -367,7 +457,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
367
457
|
/**
|
|
368
458
|
* Populate the directory with the given directory data.
|
|
369
459
|
* @param data - A JSON string containing serialized directory data
|
|
370
|
-
* @internal
|
|
371
460
|
*/
|
|
372
461
|
populate(data) {
|
|
373
462
|
const stack = [];
|
|
@@ -376,18 +465,42 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
376
465
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
377
466
|
const [currentSubDir, currentSubDirObject] = stack.pop();
|
|
378
467
|
if (currentSubDirObject.subdirectories) {
|
|
468
|
+
// Utilize a map to store the seq -> clientSeq for the newly created subdirectory
|
|
469
|
+
const tempSeqNums = new Map();
|
|
379
470
|
for (const [subdirName, subdirObject] of Object.entries(currentSubDirObject.subdirectories)) {
|
|
380
471
|
let newSubDir = currentSubDir.getSubDirectory(subdirName);
|
|
472
|
+
let seqData;
|
|
381
473
|
if (!newSubDir) {
|
|
382
474
|
const createInfo = subdirObject.ci;
|
|
383
|
-
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
createInfo !== undefined && createInfo.csn > -1
|
|
475
|
+
// We do not store the client sequence number in the storage because the order has already been
|
|
476
|
+
// guaranteed during the serialization process. As a result, it is only essential to utilize the
|
|
477
|
+
// "fake" client sequence number to signify the loading order, and there is no need to retain
|
|
478
|
+
// the actual client sequence number at this point.
|
|
479
|
+
if (createInfo !== undefined && createInfo.csn > -1) {
|
|
480
|
+
// If csn is -1, then initialize it with 0, otherwise we will never process ops for this
|
|
481
|
+
// sub directory. This could be done at serialization time too, but we need to maintain
|
|
482
|
+
// back compat too and also we will actually know the state when it was serialized.
|
|
483
|
+
if (!tempSeqNums.has(createInfo.csn)) {
|
|
484
|
+
tempSeqNums.set(createInfo.csn, 0);
|
|
485
|
+
}
|
|
486
|
+
let fakeClientSeq = tempSeqNums.get(createInfo.csn);
|
|
487
|
+
seqData = { seq: createInfo.csn, clientSeq: fakeClientSeq };
|
|
488
|
+
tempSeqNums.set(createInfo.csn, ++fakeClientSeq);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
seqData = {
|
|
492
|
+
seq: 0,
|
|
493
|
+
clientSeq: ++currentSubDir.localCreationSeq,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
newSubDir = new SubDirectory(seqData, createInfo !== undefined
|
|
388
497
|
? new Set(createInfo.ccIds)
|
|
389
498
|
: new Set(), this, this.runtime, this.serializer, posix.join(currentSubDir.absolutePath, subdirName));
|
|
390
499
|
currentSubDir.populateSubDirectory(subdirName, newSubDir);
|
|
500
|
+
// Record the newly inserted subdirectory to the creation tracker
|
|
501
|
+
currentSubDir.ackedCreationSeqTracker.set(subdirName, {
|
|
502
|
+
...seqData,
|
|
503
|
+
});
|
|
391
504
|
}
|
|
392
505
|
stack.push([newSubDir, subdirObject]);
|
|
393
506
|
}
|
|
@@ -402,7 +515,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
402
515
|
}
|
|
403
516
|
/**
|
|
404
517
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.processCore}
|
|
405
|
-
* @internal
|
|
406
518
|
*/
|
|
407
519
|
processCore(message, local, localOpMetadata) {
|
|
408
520
|
if (message.type === protocol_definitions_1.MessageType.Operation) {
|
|
@@ -414,7 +526,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
414
526
|
}
|
|
415
527
|
/**
|
|
416
528
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
|
|
417
|
-
* @internal
|
|
418
529
|
*/
|
|
419
530
|
rollback(content, localOpMetadata) {
|
|
420
531
|
const op = content;
|
|
@@ -594,7 +705,6 @@ class SharedDirectory extends shared_object_base_1.SharedObject {
|
|
|
594
705
|
}
|
|
595
706
|
/**
|
|
596
707
|
* {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
|
|
597
|
-
* @internal
|
|
598
708
|
*/
|
|
599
709
|
applyStashedOp(op) {
|
|
600
710
|
const handler = this.messageHandlers.get(op.type);
|
|
@@ -701,9 +811,9 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
701
811
|
* @param serializer - The serializer to serialize / parse handles
|
|
702
812
|
* @param absolutePath - The absolute path of this IDirectory
|
|
703
813
|
*/
|
|
704
|
-
constructor(
|
|
814
|
+
constructor(seqData, clientIds, directory, runtime, serializer, absolutePath) {
|
|
705
815
|
super();
|
|
706
|
-
this.
|
|
816
|
+
this.seqData = seqData;
|
|
707
817
|
this.clientIds = clientIds;
|
|
708
818
|
this.directory = directory;
|
|
709
819
|
this.runtime = runtime;
|
|
@@ -752,6 +862,13 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
752
862
|
* The pending ids of any clears that have been performed locally but not yet ack'd from the server
|
|
753
863
|
*/
|
|
754
864
|
this.pendingClearMessageIds = [];
|
|
865
|
+
/**
|
|
866
|
+
* Assigns a unique ID to each subdirectory created locally but pending for acknowledgement, facilitating the tracking
|
|
867
|
+
* of the creation order.
|
|
868
|
+
*/
|
|
869
|
+
this.localCreationSeq = 0;
|
|
870
|
+
this.localCreationSeqTracker = new DirectoryCreationTracker();
|
|
871
|
+
this.ackedCreationSeqTracker = new DirectoryCreationTracker();
|
|
755
872
|
}
|
|
756
873
|
dispose(error) {
|
|
757
874
|
this._deleted = true;
|
|
@@ -853,14 +970,18 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
853
970
|
return subDir;
|
|
854
971
|
}
|
|
855
972
|
/**
|
|
856
|
-
* @returns
|
|
973
|
+
* @returns The Sequence Data which should be used for local changes.
|
|
857
974
|
* @remarks While detached, 0 is used rather than -1 to represent a change which should be universally known (as opposed to known
|
|
858
975
|
* only by the local client). This ensures that if the directory is later attached, none of its data needs to be updated (the values
|
|
859
976
|
* last set while detached will now be known to any new client, until they are changed).
|
|
977
|
+
*
|
|
978
|
+
* The client sequence number is incremented by 1 for maintaining the internal order of locally created subdirectories
|
|
860
979
|
* TODO: Convert these conventions to named constants. The semantics used here match those for merge-tree.
|
|
861
980
|
*/
|
|
862
981
|
getLocalSeq() {
|
|
863
|
-
return this.directory.isAttached()
|
|
982
|
+
return this.directory.isAttached()
|
|
983
|
+
? { seq: -1, clientSeq: ++this.localCreationSeq }
|
|
984
|
+
: { seq: 0, clientSeq: ++this.localCreationSeq };
|
|
864
985
|
}
|
|
865
986
|
/**
|
|
866
987
|
* {@inheritDoc IDirectory.getSubDirectory}
|
|
@@ -903,7 +1024,26 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
903
1024
|
*/
|
|
904
1025
|
subdirectories() {
|
|
905
1026
|
this.throwIfDisposed();
|
|
906
|
-
|
|
1027
|
+
const ackedSubdirsInOrder = this.ackedCreationSeqTracker.keys();
|
|
1028
|
+
const localSubdirsInOrder = this.localCreationSeqTracker.keys((key) => !this.ackedCreationSeqTracker.has(key));
|
|
1029
|
+
const subdirNames = [...ackedSubdirsInOrder, ...localSubdirsInOrder];
|
|
1030
|
+
(0, core_utils_1.assert)(subdirNames.length === this._subdirectories.size, 0x85c /* The count of keys for iteration should be consistent with the size of actual data */);
|
|
1031
|
+
const entriesIterator = {
|
|
1032
|
+
index: 0,
|
|
1033
|
+
dirs: this._subdirectories,
|
|
1034
|
+
next() {
|
|
1035
|
+
if (this.index < subdirNames.length) {
|
|
1036
|
+
const subdirName = subdirNames[this.index++];
|
|
1037
|
+
const subdir = this.dirs.get(subdirName);
|
|
1038
|
+
return { value: [subdirName, subdir], done: false };
|
|
1039
|
+
}
|
|
1040
|
+
return { value: undefined, done: true };
|
|
1041
|
+
},
|
|
1042
|
+
[Symbol.iterator]() {
|
|
1043
|
+
return this;
|
|
1044
|
+
},
|
|
1045
|
+
};
|
|
1046
|
+
return entriesIterator;
|
|
907
1047
|
}
|
|
908
1048
|
/**
|
|
909
1049
|
* {@inheritDoc IDirectory.getWorkingDirectory}
|
|
@@ -1163,7 +1303,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1163
1303
|
return;
|
|
1164
1304
|
}
|
|
1165
1305
|
assertNonNullClientId(msg.clientId);
|
|
1166
|
-
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1306
|
+
this.createSubDirectoryCore(op.subdirName, local, { seq: msg.sequenceNumber, clientSeq: msg.clientSequenceNumber }, msg.clientId);
|
|
1167
1307
|
}
|
|
1168
1308
|
/**
|
|
1169
1309
|
* Apply createSubDirectory operation locally and generate metadata
|
|
@@ -1385,7 +1525,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1385
1525
|
getSerializableCreateInfo() {
|
|
1386
1526
|
this.throwIfDisposed();
|
|
1387
1527
|
const createInfo = {
|
|
1388
|
-
csn: this.
|
|
1528
|
+
csn: this.seqData.seq,
|
|
1389
1529
|
ccIds: Array.from(this.clientIds),
|
|
1390
1530
|
};
|
|
1391
1531
|
return createInfo;
|
|
@@ -1475,6 +1615,17 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1475
1615
|
this.undeleteSubDirectoryTree(localOpMetadata.subDirectory);
|
|
1476
1616
|
// don't need to register events because deleting never unregistered
|
|
1477
1617
|
this._subdirectories.set(op.subdirName, localOpMetadata.subDirectory);
|
|
1618
|
+
// Restore the record in creation tracker
|
|
1619
|
+
if (isAcknowledgedOrDetached(localOpMetadata.subDirectory.seqData)) {
|
|
1620
|
+
this.ackedCreationSeqTracker.set(op.subdirName, {
|
|
1621
|
+
...localOpMetadata.subDirectory.seqData,
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
else {
|
|
1625
|
+
this.localCreationSeqTracker.set(op.subdirName, {
|
|
1626
|
+
...localOpMetadata.subDirectory.seqData,
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1478
1629
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1479
1630
|
}
|
|
1480
1631
|
this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subDirName);
|
|
@@ -1555,7 +1706,7 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1555
1706
|
// and the op was created after the directory was created then apply this op.
|
|
1556
1707
|
return ((msg.clientId !== null && this.clientIds.has(msg.clientId)) ||
|
|
1557
1708
|
this.clientIds.has("detached") ||
|
|
1558
|
-
(this.
|
|
1709
|
+
(this.seqData.seq !== -1 && this.seqData.seq <= msg.referenceSequenceNumber));
|
|
1559
1710
|
}
|
|
1560
1711
|
/**
|
|
1561
1712
|
* If our local operations that have not yet been ack'd will eventually overwrite an incoming operation, we should
|
|
@@ -1592,10 +1743,11 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1592
1743
|
// If this is delete op and we have keys in this subDirectory, then we need to delete these
|
|
1593
1744
|
// keys except the pending ones as they will be sequenced after this delete.
|
|
1594
1745
|
directory.clearExceptPendingKeys(local);
|
|
1595
|
-
// In case of delete op, we need to reset the creation
|
|
1746
|
+
// In case of delete op, we need to reset the creation seqNum, clientSeqNum and client ids of
|
|
1596
1747
|
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1597
1748
|
// we will receive op for the create again.
|
|
1598
|
-
directory.
|
|
1749
|
+
directory.seqData.seq = -1;
|
|
1750
|
+
directory.seqData.clientSeq = -1;
|
|
1599
1751
|
directory.clientIds.clear();
|
|
1600
1752
|
// Do the same thing for the subtree of the directory. If create is not pending for a child, then just
|
|
1601
1753
|
// delete it.
|
|
@@ -1609,21 +1761,35 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1609
1761
|
}
|
|
1610
1762
|
};
|
|
1611
1763
|
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1764
|
+
// Clear the creation tracker record
|
|
1765
|
+
this.ackedCreationSeqTracker.delete(op.subdirName);
|
|
1612
1766
|
resetSubDirectoryTree(subDirectory);
|
|
1613
1767
|
}
|
|
1614
1768
|
if (op.type === "createSubDirectory") {
|
|
1615
1769
|
const dir = this._subdirectories.get(op.subdirName);
|
|
1616
1770
|
// Child sub directory create seq number can't be lower than the parent subdirectory.
|
|
1617
1771
|
// The sequence number for multiple ops can be the same when multiple createSubDirectory occurs with grouped batching enabled, thus <= and not just <.
|
|
1618
|
-
if (this.
|
|
1619
|
-
if (dir?.
|
|
1620
|
-
// Only set the
|
|
1621
|
-
dir.
|
|
1772
|
+
if (this.seqData.seq !== -1 && this.seqData.seq <= msg.sequenceNumber) {
|
|
1773
|
+
if (dir?.seqData.seq === -1) {
|
|
1774
|
+
// Only set the sequence data based on the first message
|
|
1775
|
+
dir.seqData.seq = msg.sequenceNumber;
|
|
1776
|
+
dir.seqData.clientSeq = msg.clientSequenceNumber;
|
|
1777
|
+
// set the creation seq in tracker
|
|
1778
|
+
if (!this.ackedCreationSeqTracker.has(op.subdirName) &&
|
|
1779
|
+
!this.pendingDeleteSubDirectoriesTracker.has(op.subdirName)) {
|
|
1780
|
+
this.ackedCreationSeqTracker.set(op.subdirName, {
|
|
1781
|
+
seq: msg.sequenceNumber,
|
|
1782
|
+
clientSeq: msg.clientSequenceNumber,
|
|
1783
|
+
});
|
|
1784
|
+
if (local) {
|
|
1785
|
+
this.localCreationSeqTracker.delete(op.subdirName);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1622
1788
|
}
|
|
1623
1789
|
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1624
1790
|
if (dir !== undefined &&
|
|
1625
1791
|
!dir.clientIds.has(msg.clientId) &&
|
|
1626
|
-
dir.
|
|
1792
|
+
dir.seqData.seq <= msg.sequenceNumber) {
|
|
1627
1793
|
dir.clientIds.add(msg.clientId);
|
|
1628
1794
|
}
|
|
1629
1795
|
}
|
|
@@ -1698,15 +1864,25 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1698
1864
|
* Create subdirectory implementation used for both locally sourced creation as well as incoming remote creation.
|
|
1699
1865
|
* @param subdirName - The name of the subdirectory being created
|
|
1700
1866
|
* @param local - Whether the message originated from the local client
|
|
1701
|
-
* @param
|
|
1867
|
+
* @param seqData - Sequence number and client sequence number at which this directory is created
|
|
1702
1868
|
* @param clientId - Id of client which created this directory.
|
|
1703
1869
|
* @returns True if is newly created, false if it already existed.
|
|
1704
1870
|
*/
|
|
1705
|
-
createSubDirectoryCore(subdirName, local,
|
|
1871
|
+
createSubDirectoryCore(subdirName, local, seqData, clientId) {
|
|
1706
1872
|
const subdir = this._subdirectories.get(subdirName);
|
|
1707
1873
|
if (subdir === undefined) {
|
|
1708
1874
|
const absolutePath = posix.join(this.absolutePath, subdirName);
|
|
1709
|
-
const subDir = new SubDirectory(
|
|
1875
|
+
const subDir = new SubDirectory({ ...seqData }, new Set([clientId]), this.directory, this.runtime, this.serializer, absolutePath);
|
|
1876
|
+
/**
|
|
1877
|
+
* Store the sequnce numbers of newly created subdirectory to the proper creation tracker, based
|
|
1878
|
+
* on whether the creation behavior has been ack'd or not
|
|
1879
|
+
*/
|
|
1880
|
+
if (!isAcknowledgedOrDetached(seqData)) {
|
|
1881
|
+
this.localCreationSeqTracker.set(subdirName, { ...seqData });
|
|
1882
|
+
}
|
|
1883
|
+
else {
|
|
1884
|
+
this.ackedCreationSeqTracker.set(subdirName, { ...seqData });
|
|
1885
|
+
}
|
|
1710
1886
|
this.registerEventsOnSubDirectory(subDir, subdirName);
|
|
1711
1887
|
this._subdirectories.set(subdirName, subDir);
|
|
1712
1888
|
this.emit("subDirectoryCreated", subdirName, local, this);
|
|
@@ -1736,6 +1912,16 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1736
1912
|
// Might want to consider cleaning out the structure more exhaustively though? But not when rollback.
|
|
1737
1913
|
if (previousValue !== undefined) {
|
|
1738
1914
|
this._subdirectories.delete(subdirName);
|
|
1915
|
+
/**
|
|
1916
|
+
* Remove the corresponding record from the proper creation tracker, based on whether the subdirectory has been
|
|
1917
|
+
* ack'd already or still not committed yet (could be both).
|
|
1918
|
+
*/
|
|
1919
|
+
if (this.ackedCreationSeqTracker.has(subdirName)) {
|
|
1920
|
+
this.ackedCreationSeqTracker.delete(subdirName);
|
|
1921
|
+
}
|
|
1922
|
+
if (this.localCreationSeqTracker.has(subdirName)) {
|
|
1923
|
+
this.localCreationSeqTracker.delete(subdirName);
|
|
1924
|
+
}
|
|
1739
1925
|
this.disposeSubDirectoryTree(previousValue);
|
|
1740
1926
|
this.emit("subDirectoryDeleted", subdirName, local, this);
|
|
1741
1927
|
}
|
|
@@ -1755,11 +1941,12 @@ class SubDirectory extends client_utils_1.TypedEventEmitter {
|
|
|
1755
1941
|
}
|
|
1756
1942
|
}
|
|
1757
1943
|
undeleteSubDirectoryTree(directory) {
|
|
1758
|
-
// Restore deleted subdirectory tree.
|
|
1759
|
-
|
|
1944
|
+
// Restore deleted subdirectory tree. Need to undispose the current directory first, then get access to the iterator.
|
|
1945
|
+
// This will unmark "deleted" from the subdirectories from top to bottom.
|
|
1946
|
+
directory.undispose();
|
|
1947
|
+
for (const [_, subDirectory] of directory.subdirectories()) {
|
|
1760
1948
|
this.undeleteSubDirectoryTree(subDirectory);
|
|
1761
1949
|
}
|
|
1762
|
-
directory.undispose();
|
|
1763
1950
|
}
|
|
1764
1951
|
}
|
|
1765
|
-
//# sourceMappingURL=directory.
|
|
1952
|
+
//# sourceMappingURL=directory.cjs.map
|