@fluidframework/merge-tree 2.10.0-306579 → 2.10.0-307399
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/api-report/merge-tree.legacy.alpha.api.md +27 -0
- package/dist/collections/index.d.ts +1 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/collections/index.js +2 -1
- package/dist/collections/index.js.map +1 -1
- package/dist/collections/list.d.ts +7 -0
- package/dist/collections/list.d.ts.map +1 -1
- package/dist/collections/list.js +27 -1
- package/dist/collections/list.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +2 -0
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +10 -9
- package/dist/mergeTree.js.map +1 -1
- package/dist/ops.d.ts +36 -0
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js.map +1 -1
- package/dist/segmentPropertiesManager.d.ts +81 -8
- package/dist/segmentPropertiesManager.d.ts.map +1 -1
- package/dist/segmentPropertiesManager.js +211 -61
- package/dist/segmentPropertiesManager.js.map +1 -1
- package/dist/test/propertyManager.spec.d.ts +6 -0
- package/dist/test/propertyManager.spec.d.ts.map +1 -0
- package/dist/test/propertyManager.spec.js +156 -0
- package/dist/test/propertyManager.spec.js.map +1 -0
- package/dist/zamboni.d.ts.map +1 -1
- package/dist/zamboni.js +1 -0
- package/dist/zamboni.js.map +1 -1
- package/lib/collections/index.d.ts +1 -1
- package/lib/collections/index.d.ts.map +1 -1
- package/lib/collections/index.js +1 -1
- package/lib/collections/index.js.map +1 -1
- package/lib/collections/list.d.ts +7 -0
- package/lib/collections/list.d.ts.map +1 -1
- package/lib/collections/list.js +25 -0
- package/lib/collections/list.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +2 -0
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +10 -9
- package/lib/mergeTree.js.map +1 -1
- package/lib/ops.d.ts +36 -0
- package/lib/ops.d.ts.map +1 -1
- package/lib/ops.js.map +1 -1
- package/lib/segmentPropertiesManager.d.ts +81 -8
- package/lib/segmentPropertiesManager.d.ts.map +1 -1
- package/lib/segmentPropertiesManager.js +212 -62
- package/lib/segmentPropertiesManager.js.map +1 -1
- package/lib/test/propertyManager.spec.d.ts +6 -0
- package/lib/test/propertyManager.spec.d.ts.map +1 -0
- package/lib/test/propertyManager.spec.js +154 -0
- package/lib/test/propertyManager.spec.js.map +1 -0
- package/lib/zamboni.d.ts.map +1 -1
- package/lib/zamboni.js +1 -0
- package/lib/zamboni.js.map +1 -1
- package/package.json +24 -17
- package/src/collections/index.ts +7 -1
- package/src/collections/list.ts +29 -0
- package/src/index.ts +3 -0
- package/src/mergeTree.ts +16 -11
- package/src/ops.ts +38 -0
- package/src/segmentPropertiesManager.ts +277 -88
- package/src/zamboni.ts +3 -0
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
7
|
-
|
|
8
6
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
9
7
|
|
|
8
|
+
import { DoublyLinkedList, iterateListValuesWhile } from "./collections/index.js";
|
|
10
9
|
import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js";
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import type {
|
|
11
|
+
AdjustParams,
|
|
12
|
+
IMergeTreeAnnotateAdjustMsg,
|
|
13
|
+
IMergeTreeAnnotateMsg,
|
|
14
|
+
} from "./ops.js";
|
|
15
|
+
import { MapLike, PropertySet, clone, createMap } from "./properties.js";
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* @internal
|
|
@@ -25,7 +28,6 @@ export enum PropertiesRollback {
|
|
|
25
28
|
*/
|
|
26
29
|
Rollback,
|
|
27
30
|
}
|
|
28
|
-
|
|
29
31
|
/**
|
|
30
32
|
* Minimally copies properties and the property manager from source to destination.
|
|
31
33
|
* @internal
|
|
@@ -45,133 +47,320 @@ export function copyPropertiesAndManager(
|
|
|
45
47
|
destination.properties = clone(source.properties);
|
|
46
48
|
} else {
|
|
47
49
|
destination.propertyManager ??= new PropertiesManager();
|
|
48
|
-
|
|
49
|
-
source.properties,
|
|
50
|
-
destination.properties,
|
|
51
|
-
destination.propertyManager,
|
|
52
|
-
);
|
|
50
|
+
source.propertyManager.copyTo(source.properties, destination);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
54
|
|
|
55
|
+
type PropertyChange = {
|
|
56
|
+
seq: number;
|
|
57
|
+
} & ({ adjust: AdjustParams; raw?: undefined } | { raw: unknown; adjust?: undefined });
|
|
58
|
+
|
|
59
|
+
interface PropertyChanges {
|
|
60
|
+
msnConsensus: unknown;
|
|
61
|
+
remote: DoublyLinkedList<PropertyChange>;
|
|
62
|
+
local: DoublyLinkedList<PropertyChange>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function computePropertyValue(
|
|
66
|
+
consensus: unknown,
|
|
67
|
+
...changes: Iterable<PropertyChange>[]
|
|
68
|
+
): unknown {
|
|
69
|
+
let computedValue: unknown = consensus;
|
|
70
|
+
for (const change of changes) {
|
|
71
|
+
for (const op of change) {
|
|
72
|
+
const { raw, adjust } = op;
|
|
73
|
+
if (adjust === undefined) {
|
|
74
|
+
computedValue = raw;
|
|
75
|
+
} else {
|
|
76
|
+
const adjusted =
|
|
77
|
+
(typeof computedValue === "number" ? computedValue : 0) + adjust.delta;
|
|
78
|
+
if (adjust.max !== undefined && adjusted > adjust.max) {
|
|
79
|
+
computedValue = adjust.max;
|
|
80
|
+
} else if (adjust.min !== undefined && adjusted < adjust.min) {
|
|
81
|
+
computedValue = adjust.min;
|
|
82
|
+
} else {
|
|
83
|
+
computedValue = adjusted;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return computedValue;
|
|
89
|
+
}
|
|
90
|
+
|
|
57
91
|
/**
|
|
58
92
|
* @internal
|
|
59
93
|
*/
|
|
60
|
-
export
|
|
61
|
-
|
|
94
|
+
export type PropsOrAdjust =
|
|
95
|
+
| Pick<IMergeTreeAnnotateAdjustMsg, "props" | "adjust">
|
|
96
|
+
| Pick<IMergeTreeAnnotateMsg, "props" | "adjust">;
|
|
97
|
+
|
|
98
|
+
const opToChanges = (op: PropsOrAdjust, seq: number): [string, PropertyChange][] => [
|
|
99
|
+
...Object.entries(op.props ?? {})
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
101
|
+
.map<[string, PropertyChange]>(([k, raw]) => [k, { raw, seq }])
|
|
102
|
+
.filter(([_, v]) => v.raw !== undefined),
|
|
103
|
+
...Object.entries(op.adjust ?? {}).map<[string, PropertyChange]>(([k, adjust]) => [
|
|
104
|
+
k,
|
|
105
|
+
{ adjust, seq },
|
|
106
|
+
]),
|
|
107
|
+
];
|
|
62
108
|
|
|
63
|
-
|
|
64
|
-
|
|
109
|
+
function applyChanges(
|
|
110
|
+
op: PropsOrAdjust,
|
|
111
|
+
seg: { properties?: MapLike<unknown> },
|
|
112
|
+
seq: number,
|
|
113
|
+
run: (
|
|
114
|
+
properties: MapLike<unknown>,
|
|
115
|
+
deltas: MapLike<unknown>,
|
|
116
|
+
key: string,
|
|
117
|
+
value: PropertyChange,
|
|
118
|
+
) => void,
|
|
119
|
+
): MapLike<unknown> {
|
|
120
|
+
const properties = (seg.properties ??= createMap<unknown>());
|
|
121
|
+
const deltas: MapLike<unknown> = {};
|
|
122
|
+
for (const [key, value] of opToChanges(op, seq)) {
|
|
123
|
+
run(properties, deltas, key, value);
|
|
124
|
+
if (properties[key] === null) {
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
126
|
+
delete properties[key];
|
|
127
|
+
}
|
|
65
128
|
}
|
|
129
|
+
return deltas;
|
|
130
|
+
}
|
|
66
131
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
132
|
+
/**
|
|
133
|
+
* The PropertiesManager class handles changes to properties, both remote and local.
|
|
134
|
+
* It manages the lifecycle for local property changes, ensures all property changes are eventually consistent,
|
|
135
|
+
* and provides methods to acknowledge changes, update the minimum sequence number (msn), and copy properties to another manager.
|
|
136
|
+
* This class is essential for maintaining the integrity and consistency of property changes in collaborative environments.
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
export class PropertiesManager {
|
|
140
|
+
private readonly changes = new Map<string, PropertyChanges>();
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Rolls back local property changes.
|
|
144
|
+
* This method reverts property changes based on the provided operation and segment.
|
|
145
|
+
* If the operation is part of a collaborative session, it ensures that the changes are consistent with the remote state.
|
|
146
|
+
*
|
|
147
|
+
* @param op - The operation containing property changes. This can be an adjustment or a set of properties.
|
|
148
|
+
* @param seg - The segment containing properties. This object may have a properties map that will be modified.
|
|
149
|
+
* @param collaborating - Indicates if the operation is part of a collaborative session. Defaults to false.
|
|
150
|
+
* @returns The deltas of the rolled-back properties. This is a map-like object representing the changes that were reverted.
|
|
151
|
+
*/
|
|
152
|
+
public rollbackProperties(
|
|
153
|
+
op: PropsOrAdjust,
|
|
154
|
+
seg: { properties?: MapLike<unknown> },
|
|
155
|
+
collaborating: boolean = false,
|
|
156
|
+
): MapLike<unknown> {
|
|
157
|
+
return applyChanges(op, seg, UniversalSequenceNumber, (properties, deltas, key, value) => {
|
|
158
|
+
// eslint-disable-next-line unicorn/no-null
|
|
159
|
+
const previousValue = properties[key] ?? null;
|
|
160
|
+
|
|
161
|
+
const pending = this.changes.get(key);
|
|
162
|
+
if (collaborating) {
|
|
70
163
|
assert(
|
|
71
|
-
|
|
72
|
-
|
|
164
|
+
pending !== undefined,
|
|
165
|
+
"Pending changes must exist for rollback when collaborating",
|
|
166
|
+
);
|
|
167
|
+
pending.local.pop();
|
|
168
|
+
properties[key] = computePropertyValue(
|
|
169
|
+
pending.msnConsensus,
|
|
170
|
+
pending.remote.map((n) => n.data),
|
|
171
|
+
pending.local.map((n) => n.data),
|
|
73
172
|
);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
77
|
-
delete this.pendingKeyUpdateCount[key];
|
|
173
|
+
if (pending.local.empty && pending.remote.empty) {
|
|
174
|
+
this.changes.delete(key);
|
|
78
175
|
}
|
|
176
|
+
} else {
|
|
177
|
+
assert(pending === undefined, "Pending changes must not exist when not collaborating");
|
|
178
|
+
properties[key] = computePropertyValue(previousValue, [value]);
|
|
79
179
|
}
|
|
80
|
-
|
|
180
|
+
deltas[key] = previousValue;
|
|
181
|
+
});
|
|
81
182
|
}
|
|
82
183
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Handles property changes.
|
|
186
|
+
* This method applies property changes based on the provided operation, segment, sequence number, and collaboration state.
|
|
187
|
+
* It also handles rolling back changes if specified.
|
|
188
|
+
*
|
|
189
|
+
* @param op - The operation containing property changes.
|
|
190
|
+
* @param seg - The segment containing properties.
|
|
191
|
+
* @param seq - The sequence number for the operation.
|
|
192
|
+
* @param msn - The minimum sequence number for the operation.
|
|
193
|
+
* @param collaborating - Indicates if the operation is part of a collaborative session. Defaults to false.
|
|
194
|
+
* @param rollback - Specifies if the changes should be rolled back. Defaults to PropertiesRollback.None.
|
|
195
|
+
* @returns The deltas of the applied or rolled-back properties. This is a map-like object representing the changes.
|
|
196
|
+
*/
|
|
197
|
+
public handleProperties(
|
|
198
|
+
op: PropsOrAdjust,
|
|
199
|
+
seg: { properties?: MapLike<unknown> },
|
|
200
|
+
seq: number,
|
|
201
|
+
msn: number,
|
|
87
202
|
collaborating: boolean = false,
|
|
88
203
|
rollback: PropertiesRollback = PropertiesRollback.None,
|
|
89
|
-
):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Clean up counts for rolled back edits before modifying oldProps
|
|
93
|
-
if (collaborating && rollback === PropertiesRollback.Rollback) {
|
|
94
|
-
this.decrementPendingCounts(newProps);
|
|
204
|
+
): MapLike<unknown> {
|
|
205
|
+
if (rollback === PropertiesRollback.Rollback) {
|
|
206
|
+
return this.rollbackProperties(op, seg, collaborating);
|
|
95
207
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
seq === UnassignedSequenceNumber ||
|
|
100
|
-
seq === UniversalSequenceNumber ||
|
|
101
|
-
this.pendingKeyUpdateCount?.[key] === undefined
|
|
102
|
-
) {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const deltas: PropertySet = {};
|
|
109
|
-
|
|
110
|
-
for (const [key, newValue] of Object.entries(newProps)) {
|
|
111
|
-
if (newValue === undefined) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
208
|
+
const rtn = applyChanges(op, seg, seq, (properties, deltas, key, value) => {
|
|
209
|
+
// eslint-disable-next-line unicorn/no-null
|
|
210
|
+
const previousValue = properties[key] ?? null;
|
|
115
211
|
if (collaborating) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
212
|
+
const pending: PropertyChanges | undefined = this.changes.get(key) ?? {
|
|
213
|
+
msnConsensus: previousValue,
|
|
214
|
+
remote: new DoublyLinkedList(),
|
|
215
|
+
local: new DoublyLinkedList(),
|
|
216
|
+
};
|
|
217
|
+
this.changes.set(key, pending);
|
|
218
|
+
const local = seq === UnassignedSequenceNumber;
|
|
219
|
+
if (local) {
|
|
220
|
+
pending.local.push(value);
|
|
221
|
+
} else {
|
|
222
|
+
// we only track remotes if there are adjusts, as only adjusts make application anti-commutative
|
|
223
|
+
// this will limit the impact of this change to only those using adjusts. Additionally, we only
|
|
224
|
+
// need to track remotes at all to support emitting the legacy snapshot format, which only sharedstring
|
|
225
|
+
// uses. when we remove the ability to emit that format, we can remove all remote op tracking
|
|
226
|
+
if (value.raw !== undefined && pending.remote.empty) {
|
|
227
|
+
pending.msnConsensus = computePropertyValue(pending.msnConsensus, [value]);
|
|
228
|
+
} else {
|
|
229
|
+
pending.remote.push(value);
|
|
119
230
|
}
|
|
120
|
-
this.pendingKeyUpdateCount[key]++;
|
|
121
|
-
} else if (!shouldModifyKey(key)) {
|
|
122
|
-
continue;
|
|
123
231
|
}
|
|
232
|
+
properties[key] = computePropertyValue(
|
|
233
|
+
pending.msnConsensus,
|
|
234
|
+
pending.remote.map((n) => n.data),
|
|
235
|
+
pending.local.map((n) => n.data),
|
|
236
|
+
);
|
|
237
|
+
if (local || pending.local.empty || properties[key] !== previousValue) {
|
|
238
|
+
deltas[key] = previousValue;
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
properties[key] = computePropertyValue(previousValue, [value]);
|
|
242
|
+
deltas[key] = previousValue;
|
|
124
243
|
}
|
|
244
|
+
});
|
|
245
|
+
this.updateMsn(msn);
|
|
246
|
+
return rtn;
|
|
247
|
+
}
|
|
125
248
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Acknowledges property changes.
|
|
251
|
+
* This method acknowledges the property changes based on the provided sequence number and operation.
|
|
252
|
+
*
|
|
253
|
+
* @param seq - The sequence number for the operation.
|
|
254
|
+
* @param msn - The minimum sequence number for the operation.
|
|
255
|
+
* @param op - The operation containing property changes.
|
|
256
|
+
*/
|
|
257
|
+
public ack(seq: number, msn: number, op: PropsOrAdjust): void {
|
|
258
|
+
for (const [key, value] of opToChanges(op, seq)) {
|
|
259
|
+
const change = this.changes.get(key);
|
|
260
|
+
const acked = change?.local?.shift();
|
|
261
|
+
assert(change !== undefined && acked !== undefined, "must have local change to ack");
|
|
262
|
+
// we only track remotes if there are adjusts, as only adjusts make application anti-commutative
|
|
263
|
+
// this will limit the impact of this change to only those using adjusts. Additionally, we only
|
|
264
|
+
// need to track remotes at all to support emitting the legacy snapshot format, which only sharedstring
|
|
265
|
+
// uses. when we remove the ability to emit that format, we can remove all remote op tracking
|
|
266
|
+
if (value.raw !== undefined && change.remote.empty) {
|
|
267
|
+
change.msnConsensus = computePropertyValue(change.msnConsensus, [value]);
|
|
133
268
|
} else {
|
|
134
|
-
|
|
135
|
-
oldProps[key] = newValue;
|
|
269
|
+
change.remote.push(value);
|
|
136
270
|
}
|
|
137
271
|
}
|
|
272
|
+
this.updateMsn(msn);
|
|
273
|
+
}
|
|
138
274
|
|
|
139
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Updates the minimum sequence number (msn).
|
|
277
|
+
* This method updates the minimum sequence number and removes any changes that have been acknowledged.
|
|
278
|
+
*
|
|
279
|
+
* @param msn - The minimum sequence number to update.
|
|
280
|
+
*/
|
|
281
|
+
public updateMsn(msn: number): void {
|
|
282
|
+
for (const [key, pending] of this.changes) {
|
|
283
|
+
pending.msnConsensus = computePropertyValue(
|
|
284
|
+
pending.msnConsensus,
|
|
285
|
+
iterateListValuesWhile(pending.remote.first, (n) => {
|
|
286
|
+
if (n.data.seq <= msn) {
|
|
287
|
+
n.list?.remove(n);
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
if (pending.local.empty && pending.remote.empty) {
|
|
294
|
+
this.changes.delete(key);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
140
297
|
}
|
|
141
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Copies properties to another manager.
|
|
301
|
+
* This method copies the properties and their changes from the current manager to the destination manager.
|
|
302
|
+
*
|
|
303
|
+
* @param oldProps - The old properties to be copied.
|
|
304
|
+
* @param dest - The destination object containing properties and property manager.
|
|
305
|
+
*/
|
|
142
306
|
public copyTo(
|
|
143
|
-
oldProps: PropertySet,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
307
|
+
oldProps: PropertySet | undefined,
|
|
308
|
+
dest: {
|
|
309
|
+
properties?: PropertySet;
|
|
310
|
+
propertyManager?: PropertiesManager;
|
|
311
|
+
},
|
|
312
|
+
): void {
|
|
313
|
+
const newManager = (dest.propertyManager ??= new PropertiesManager());
|
|
314
|
+
dest.properties = clone(oldProps);
|
|
315
|
+
for (const [key, { local, remote, msnConsensus }] of this.changes.entries()) {
|
|
316
|
+
newManager.changes.set(key, {
|
|
317
|
+
msnConsensus,
|
|
318
|
+
remote: new DoublyLinkedList(remote.empty ? undefined : remote.map((c) => c.data)),
|
|
319
|
+
local: new DoublyLinkedList(local.empty ? undefined : local.map((c) => c.data)),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
154
323
|
|
|
155
|
-
|
|
156
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Gets properties at a specific sequence number.
|
|
326
|
+
* This method retrieves the properties at the given sequence number.
|
|
327
|
+
* This is only needed to support emitting snapshots in the legacy format.
|
|
328
|
+
* If we remove the ability to emit the legacy format, we can remove this method, along with the need to track remote changes at all.
|
|
329
|
+
*
|
|
330
|
+
* @param oldProps - The old properties to be retrieved.
|
|
331
|
+
* @param sequenceNumber - The sequence number to get properties at.
|
|
332
|
+
* @returns The properties at the given sequence number.
|
|
333
|
+
*/
|
|
334
|
+
public getAtSeq(
|
|
335
|
+
oldProps: MapLike<unknown> | undefined,
|
|
336
|
+
sequenceNumber: number,
|
|
337
|
+
): MapLike<unknown> {
|
|
338
|
+
const properties: MapLike<unknown> = { ...oldProps };
|
|
339
|
+
for (const [key, changes] of this.changes) {
|
|
340
|
+
properties[key] = computePropertyValue(
|
|
341
|
+
changes.msnConsensus,
|
|
342
|
+
iterateListValuesWhile(changes.remote.first, (c) => c.data.seq <= sequenceNumber),
|
|
343
|
+
);
|
|
344
|
+
if (properties[key] === null) {
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
346
|
+
delete properties[key];
|
|
157
347
|
}
|
|
158
348
|
}
|
|
159
|
-
return
|
|
349
|
+
return properties;
|
|
160
350
|
}
|
|
161
351
|
|
|
162
352
|
/**
|
|
163
353
|
* Determines if all of the defined properties in a given property set are pending.
|
|
354
|
+
*
|
|
355
|
+
* @param props - The properties to check.
|
|
356
|
+
* @returns True if all the properties are pending, false otherwise.
|
|
164
357
|
*/
|
|
165
358
|
public hasPendingProperties(props: PropertySet): boolean {
|
|
166
359
|
for (const [key, value] of Object.entries(props)) {
|
|
167
|
-
if (value !== undefined && this.
|
|
360
|
+
if (value !== undefined && this.changes.get(key)?.local.empty !== false) {
|
|
168
361
|
return false;
|
|
169
362
|
}
|
|
170
363
|
}
|
|
171
364
|
return true;
|
|
172
365
|
}
|
|
173
|
-
|
|
174
|
-
public hasPendingProperty(key: string): boolean {
|
|
175
|
-
return (this.pendingKeyUpdateCount?.[key] ?? 0) > 0;
|
|
176
|
-
}
|
|
177
366
|
}
|
package/src/zamboni.ts
CHANGED
|
@@ -35,6 +35,9 @@ export function zamboniSegments(
|
|
|
35
35
|
|
|
36
36
|
for (let i = 0; i < zamboniSegmentsMaxCount; i++) {
|
|
37
37
|
let segmentToScour = mergeTree.segmentsToScour.peek()?.value;
|
|
38
|
+
|
|
39
|
+
segmentToScour?.segment?.propertyManager?.updateMsn(mergeTree.collabWindow.minSeq);
|
|
40
|
+
|
|
38
41
|
if (!segmentToScour || segmentToScour.maxSeq > mergeTree.collabWindow.minSeq) {
|
|
39
42
|
break;
|
|
40
43
|
}
|