@sovereignbase/convergent-replicated-list 0.0.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/LICENSE +201 -0
- package/README.md +326 -0
- package/dist/index.cjs +859 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +380 -0
- package/dist/index.d.ts +380 -0
- package/dist/index.js +859 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Sovereignbase
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// src/core/crud/create/index.ts
|
|
19
|
+
import { isUuidV7 as isUuidV72, prototype } from "@sovereignbase/utils";
|
|
20
|
+
|
|
21
|
+
// src/.helpers/assertListIndices/index.ts
|
|
22
|
+
function assertListIndices(crListReplica) {
|
|
23
|
+
if (!crListReplica.cursor) return;
|
|
24
|
+
let index = crListReplica.size;
|
|
25
|
+
while (crListReplica.cursor.next)
|
|
26
|
+
crListReplica.cursor = crListReplica.cursor.next;
|
|
27
|
+
while (index >= 1) {
|
|
28
|
+
index--;
|
|
29
|
+
crListReplica.cursor.index = index;
|
|
30
|
+
if (crListReplica.cursor.prev === void 0) break;
|
|
31
|
+
crListReplica.cursor = crListReplica.cursor.prev;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/.errors/class.ts
|
|
36
|
+
var CRListError = class extends Error {
|
|
37
|
+
/**
|
|
38
|
+
* The semantic error code for the failure.
|
|
39
|
+
*/
|
|
40
|
+
code;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a typed CRList error.
|
|
43
|
+
*
|
|
44
|
+
* @param code - The semantic error code.
|
|
45
|
+
* @param message - An optional human-readable detail message.
|
|
46
|
+
*/
|
|
47
|
+
constructor(code, message) {
|
|
48
|
+
const detail = message ?? code;
|
|
49
|
+
super(`{@sovereignbase/convergent-replicated-list} ${detail}`);
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.name = "CRListError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/.helpers/walkToIndex/index.ts
|
|
56
|
+
function walkToIndex(targetIndex, crListReplica) {
|
|
57
|
+
if (targetIndex < 0 || targetIndex >= crListReplica.size)
|
|
58
|
+
throw new CRListError("INDEX_OUT_OF_BOUNDS", "Index out of bounds");
|
|
59
|
+
if (!crListReplica.cursor)
|
|
60
|
+
throw new CRListError("LIST_EMPTY", "List is empty");
|
|
61
|
+
const direction = crListReplica.cursor.index > targetIndex ? "prev" : "next";
|
|
62
|
+
while (crListReplica.cursor && crListReplica.cursor.index !== targetIndex) {
|
|
63
|
+
crListReplica.cursor = crListReplica.cursor[direction];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/.helpers/insertBetween/index.ts
|
|
68
|
+
function insertBetween(prev, linkedListEntry, next) {
|
|
69
|
+
linkedListEntry.prev = prev;
|
|
70
|
+
linkedListEntry.next = next;
|
|
71
|
+
if (prev) prev.next = linkedListEntry;
|
|
72
|
+
if (next) next.prev = linkedListEntry;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/.helpers/flattenAndLinkTrustedState/index.ts
|
|
76
|
+
function flattenAndLinkTrustedState(crListReplica) {
|
|
77
|
+
crListReplica.cursor = void 0;
|
|
78
|
+
const resolvedSiblingPredecessors = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const entry of crListReplica.parentMap.values()) {
|
|
80
|
+
if (!entry) continue;
|
|
81
|
+
entry.prev = void 0;
|
|
82
|
+
entry.next = void 0;
|
|
83
|
+
}
|
|
84
|
+
const keys = [...crListReplica.childrenMap.keys()].sort(
|
|
85
|
+
(a, b) => a > b ? 1 : -1
|
|
86
|
+
);
|
|
87
|
+
let hasProgress = true;
|
|
88
|
+
while (hasProgress) {
|
|
89
|
+
hasProgress = false;
|
|
90
|
+
for (const predecessorIdentifier of keys) {
|
|
91
|
+
if (resolvedSiblingPredecessors.has(predecessorIdentifier)) continue;
|
|
92
|
+
const siblings = crListReplica.childrenMap.get(predecessorIdentifier);
|
|
93
|
+
if (!siblings) continue;
|
|
94
|
+
if (siblings.length > 1)
|
|
95
|
+
siblings.sort((a, b) => a.uuidv7 > b.uuidv7 ? 1 : -1);
|
|
96
|
+
const predecessor = predecessorIdentifier === "\0" ? void 0 : crListReplica.parentMap.get(predecessorIdentifier);
|
|
97
|
+
if (predecessor && !predecessor.prev && !predecessor.next && crListReplica.cursor !== predecessor)
|
|
98
|
+
continue;
|
|
99
|
+
let prev = predecessor ?? crListReplica.cursor;
|
|
100
|
+
const predecessorNext = predecessor?.next;
|
|
101
|
+
if (siblings.length === 1) {
|
|
102
|
+
const sibling = siblings[0];
|
|
103
|
+
insertBetween(prev, sibling, sibling.next);
|
|
104
|
+
prev = sibling;
|
|
105
|
+
if (predecessorNext && predecessorNext !== sibling) {
|
|
106
|
+
prev.next = predecessorNext;
|
|
107
|
+
predecessorNext.prev = prev;
|
|
108
|
+
} else {
|
|
109
|
+
prev.next = void 0;
|
|
110
|
+
}
|
|
111
|
+
if (!predecessorNext) crListReplica.cursor = prev;
|
|
112
|
+
resolvedSiblingPredecessors.add(predecessorIdentifier);
|
|
113
|
+
hasProgress = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const siblingSet = new Set(siblings);
|
|
117
|
+
for (let index = 0; index < siblings.length; index++) {
|
|
118
|
+
const sibling = siblings[index];
|
|
119
|
+
const next = siblings[index + 1];
|
|
120
|
+
insertBetween(prev, sibling, sibling.next);
|
|
121
|
+
prev = sibling;
|
|
122
|
+
if (next) {
|
|
123
|
+
prev.next = next;
|
|
124
|
+
next.prev = prev;
|
|
125
|
+
} else if (predecessorNext && !siblingSet.has(predecessorNext)) {
|
|
126
|
+
prev.next = predecessorNext;
|
|
127
|
+
predecessorNext.prev = prev;
|
|
128
|
+
} else {
|
|
129
|
+
prev.next = void 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!predecessorNext) crListReplica.cursor = prev;
|
|
133
|
+
resolvedSiblingPredecessors.add(predecessorIdentifier);
|
|
134
|
+
hasProgress = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
crListReplica.size = crListReplica.parentMap.size;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/.helpers/snapshotValueToLinkedListValue/index.ts
|
|
141
|
+
import { isUuidV7, safeStructuredClone } from "@sovereignbase/utils";
|
|
142
|
+
function snapshotValueToLinkedListValue(valueEntry, crListReplica) {
|
|
143
|
+
if (!isUuidV7(valueEntry.uuidv7) || crListReplica.tombstones.has(valueEntry.uuidv7) || crListReplica.parentMap.has(valueEntry.uuidv7) || !isUuidV7(valueEntry.predecessor) && valueEntry.predecessor !== "\0" && !crListReplica.tombstones.has(valueEntry.predecessor))
|
|
144
|
+
return void 0;
|
|
145
|
+
const [cloned, copiedValue] = safeStructuredClone(valueEntry.value);
|
|
146
|
+
if (!cloned) return void 0;
|
|
147
|
+
return {
|
|
148
|
+
uuidv7: valueEntry.uuidv7,
|
|
149
|
+
value: copiedValue,
|
|
150
|
+
predecessor: valueEntry.predecessor,
|
|
151
|
+
index: 0,
|
|
152
|
+
next: void 0,
|
|
153
|
+
prev: void 0
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/.helpers/updateEntryToMaps/index.ts
|
|
158
|
+
function updateEntryToMaps(crListReplica, linkedListEntry, deltaBuf) {
|
|
159
|
+
crListReplica.parentMap.set(linkedListEntry.uuidv7, linkedListEntry);
|
|
160
|
+
const siblings = crListReplica.childrenMap.get(linkedListEntry.predecessor);
|
|
161
|
+
if (siblings) {
|
|
162
|
+
siblings.push(linkedListEntry);
|
|
163
|
+
} else {
|
|
164
|
+
crListReplica.childrenMap.set(linkedListEntry.predecessor, [
|
|
165
|
+
linkedListEntry
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
if (deltaBuf && !Array.isArray(deltaBuf.values)) deltaBuf.values = [];
|
|
169
|
+
if (deltaBuf?.values)
|
|
170
|
+
deltaBuf.values.push({
|
|
171
|
+
uuidv7: linkedListEntry.uuidv7,
|
|
172
|
+
value: linkedListEntry.value,
|
|
173
|
+
predecessor: linkedListEntry.predecessor
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/.helpers/deleteEntryFromMaps/index.ts
|
|
178
|
+
function deleteEntryFromMaps(crListReplica, linkedListEntry) {
|
|
179
|
+
crListReplica.parentMap.delete(linkedListEntry.uuidv7);
|
|
180
|
+
const siblings = crListReplica.childrenMap.get(linkedListEntry.predecessor);
|
|
181
|
+
if (!siblings) return;
|
|
182
|
+
const index = siblings.indexOf(linkedListEntry);
|
|
183
|
+
if (index !== -1) siblings.splice(index, 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/.helpers/deleteLinkedEntry/index.ts
|
|
187
|
+
function deleteLinkedEntry(crListReplica, linkedListEntry, deltaBuf) {
|
|
188
|
+
const prev = linkedListEntry.prev;
|
|
189
|
+
const next = linkedListEntry.next;
|
|
190
|
+
crListReplica.tombstones.add(linkedListEntry.uuidv7);
|
|
191
|
+
if (deltaBuf && !Array.isArray(deltaBuf.tombstones)) deltaBuf.tombstones = [];
|
|
192
|
+
deltaBuf?.tombstones?.push(linkedListEntry.uuidv7);
|
|
193
|
+
if (prev) prev.next = next;
|
|
194
|
+
if (next) {
|
|
195
|
+
next.prev = prev;
|
|
196
|
+
}
|
|
197
|
+
void deleteEntryFromMaps(crListReplica, linkedListEntry);
|
|
198
|
+
if (crListReplica.cursor === linkedListEntry)
|
|
199
|
+
crListReplica.cursor = next ?? prev;
|
|
200
|
+
linkedListEntry.prev = void 0;
|
|
201
|
+
linkedListEntry.next = void 0;
|
|
202
|
+
crListReplica.size = crListReplica.parentMap.size;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/.helpers/moveEntryToPredecessor/index.ts
|
|
206
|
+
function moveEntryToPredecessor(crListReplica, linkedListEntry, predecessor, deltaBuf) {
|
|
207
|
+
void deleteEntryFromMaps(crListReplica, linkedListEntry);
|
|
208
|
+
linkedListEntry.predecessor = predecessor;
|
|
209
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, deltaBuf);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/.helpers/indexFromPropertyKey/index.ts
|
|
213
|
+
function indexFromPropertyKey(index) {
|
|
214
|
+
if (typeof index !== "string" || !/^(0|[1-9]\d*)$/.test(index))
|
|
215
|
+
return void 0;
|
|
216
|
+
const listIndex = Number(index);
|
|
217
|
+
return Number.isSafeInteger(listIndex) ? listIndex : void 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/core/crud/create/index.ts
|
|
221
|
+
function __create(snapshot) {
|
|
222
|
+
const crListReplica = {
|
|
223
|
+
size: 0,
|
|
224
|
+
cursor: void 0,
|
|
225
|
+
tombstones: /* @__PURE__ */ new Set(),
|
|
226
|
+
parentMap: /* @__PURE__ */ new Map(),
|
|
227
|
+
childrenMap: /* @__PURE__ */ new Map()
|
|
228
|
+
};
|
|
229
|
+
if (!snapshot || prototype(snapshot) !== "record") return crListReplica;
|
|
230
|
+
if (Object.hasOwn(snapshot, "tombstones") && Array.isArray(snapshot.tombstones)) {
|
|
231
|
+
for (const tombstone of snapshot.tombstones) {
|
|
232
|
+
if (crListReplica.tombstones.has(tombstone) || !isUuidV72(tombstone))
|
|
233
|
+
continue;
|
|
234
|
+
crListReplica.tombstones.add(tombstone);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!Object.hasOwn(snapshot, "values") || !Array.isArray(snapshot.values))
|
|
238
|
+
return crListReplica;
|
|
239
|
+
for (const valueEntry of snapshot.values) {
|
|
240
|
+
const linkedListEntry = snapshotValueToLinkedListValue(
|
|
241
|
+
valueEntry,
|
|
242
|
+
crListReplica
|
|
243
|
+
);
|
|
244
|
+
if (!linkedListEntry) continue;
|
|
245
|
+
void updateEntryToMaps(crListReplica, linkedListEntry);
|
|
246
|
+
}
|
|
247
|
+
void flattenAndLinkTrustedState(crListReplica);
|
|
248
|
+
void assertListIndices(crListReplica);
|
|
249
|
+
return crListReplica;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/core/crud/read/index.ts
|
|
253
|
+
function __read(targetIndex, crListReplica) {
|
|
254
|
+
try {
|
|
255
|
+
void walkToIndex(targetIndex, crListReplica);
|
|
256
|
+
return crListReplica?.cursor?.value;
|
|
257
|
+
} catch {
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/core/crud/update/index.ts
|
|
263
|
+
import { safeStructuredClone as safeStructuredClone2 } from "@sovereignbase/utils";
|
|
264
|
+
import { v7 as uuidv7 } from "uuid";
|
|
265
|
+
function __update(listIndex, listValues, crListReplica, mode) {
|
|
266
|
+
if (listIndex < 0 || listIndex > crListReplica.size)
|
|
267
|
+
throw new CRListError("INDEX_OUT_OF_BOUNDS");
|
|
268
|
+
if (!Array.isArray(listValues))
|
|
269
|
+
throw new CRListError(
|
|
270
|
+
"UPDATE_EXPECTED_AN_ARRAY",
|
|
271
|
+
"`listValues` must be an Array"
|
|
272
|
+
);
|
|
273
|
+
if (listValues.length === 0) return false;
|
|
274
|
+
const change = {};
|
|
275
|
+
const delta = { values: [], tombstones: [] };
|
|
276
|
+
let shiftCursor;
|
|
277
|
+
for (const listValue of listValues) {
|
|
278
|
+
const [cloned, copiedValue] = safeStructuredClone2(listValue);
|
|
279
|
+
if (!cloned) throw new CRListError("VALUE_NOT_CLONEABLE");
|
|
280
|
+
const v7 = uuidv7();
|
|
281
|
+
const linkedListEntry = {
|
|
282
|
+
uuidv7: v7,
|
|
283
|
+
value: copiedValue,
|
|
284
|
+
predecessor: "\0",
|
|
285
|
+
index: 0,
|
|
286
|
+
next: void 0,
|
|
287
|
+
prev: void 0
|
|
288
|
+
};
|
|
289
|
+
switch (mode) {
|
|
290
|
+
case "overwrite": {
|
|
291
|
+
if (listIndex === crListReplica.size) {
|
|
292
|
+
if (crListReplica.size === 0) {
|
|
293
|
+
crListReplica.cursor = linkedListEntry;
|
|
294
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
295
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
void walkToIndex(crListReplica.size - 1, crListReplica);
|
|
299
|
+
if (!crListReplica.cursor) return false;
|
|
300
|
+
linkedListEntry.index = crListReplica.cursor.index + 1;
|
|
301
|
+
linkedListEntry.predecessor = crListReplica.cursor.uuidv7;
|
|
302
|
+
insertBetween(crListReplica.cursor, linkedListEntry, void 0);
|
|
303
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
304
|
+
crListReplica.cursor = linkedListEntry;
|
|
305
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
void walkToIndex(listIndex, crListReplica);
|
|
309
|
+
if (!crListReplica.cursor) return false;
|
|
310
|
+
const entryToOverwrite = crListReplica.cursor;
|
|
311
|
+
linkedListEntry.predecessor = entryToOverwrite.predecessor;
|
|
312
|
+
linkedListEntry.index = entryToOverwrite.index;
|
|
313
|
+
insertBetween(
|
|
314
|
+
entryToOverwrite.prev,
|
|
315
|
+
linkedListEntry,
|
|
316
|
+
entryToOverwrite.next
|
|
317
|
+
);
|
|
318
|
+
if (entryToOverwrite.next) {
|
|
319
|
+
if (entryToOverwrite.next.predecessor === entryToOverwrite.uuidv7) {
|
|
320
|
+
void moveEntryToPredecessor(
|
|
321
|
+
crListReplica,
|
|
322
|
+
entryToOverwrite.next,
|
|
323
|
+
linkedListEntry.uuidv7,
|
|
324
|
+
delta
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
329
|
+
crListReplica.tombstones.add(entryToOverwrite.uuidv7);
|
|
330
|
+
delta.tombstones?.push(entryToOverwrite.uuidv7);
|
|
331
|
+
void deleteEntryFromMaps(crListReplica, entryToOverwrite);
|
|
332
|
+
entryToOverwrite.next = void 0;
|
|
333
|
+
entryToOverwrite.prev = void 0;
|
|
334
|
+
crListReplica.cursor = linkedListEntry;
|
|
335
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case "after": {
|
|
339
|
+
if (crListReplica.size === 0 && listIndex === 0) {
|
|
340
|
+
crListReplica.cursor = linkedListEntry;
|
|
341
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
342
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
if (listIndex === crListReplica.size) {
|
|
346
|
+
void walkToIndex(crListReplica.size - 1, crListReplica);
|
|
347
|
+
} else {
|
|
348
|
+
void walkToIndex(listIndex, crListReplica);
|
|
349
|
+
}
|
|
350
|
+
if (!crListReplica.cursor) return false;
|
|
351
|
+
const next = listIndex === crListReplica.size ? void 0 : crListReplica.cursor.next;
|
|
352
|
+
shiftCursor = next;
|
|
353
|
+
linkedListEntry.index = crListReplica.cursor.index + 1;
|
|
354
|
+
linkedListEntry.predecessor = crListReplica.cursor.uuidv7;
|
|
355
|
+
insertBetween(crListReplica.cursor, linkedListEntry, next);
|
|
356
|
+
if (next) {
|
|
357
|
+
if (next.predecessor === crListReplica.cursor.uuidv7) {
|
|
358
|
+
void moveEntryToPredecessor(
|
|
359
|
+
crListReplica,
|
|
360
|
+
next,
|
|
361
|
+
linkedListEntry.uuidv7,
|
|
362
|
+
delta
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
367
|
+
crListReplica.cursor = linkedListEntry;
|
|
368
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
case "before": {
|
|
372
|
+
if (crListReplica.size === 0 && listIndex === 0) {
|
|
373
|
+
crListReplica.cursor = linkedListEntry;
|
|
374
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
375
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
376
|
+
mode = "after";
|
|
377
|
+
listIndex = linkedListEntry.index - 1;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
void walkToIndex(listIndex, crListReplica);
|
|
381
|
+
if (!crListReplica.cursor) return false;
|
|
382
|
+
const prev = crListReplica.cursor.prev;
|
|
383
|
+
shiftCursor = crListReplica.cursor;
|
|
384
|
+
linkedListEntry.index = crListReplica.cursor.index;
|
|
385
|
+
linkedListEntry.predecessor = prev?.uuidv7 ?? "\0";
|
|
386
|
+
insertBetween(prev, linkedListEntry, crListReplica.cursor);
|
|
387
|
+
if (crListReplica.cursor.predecessor === linkedListEntry.predecessor) {
|
|
388
|
+
void moveEntryToPredecessor(
|
|
389
|
+
crListReplica,
|
|
390
|
+
crListReplica.cursor,
|
|
391
|
+
linkedListEntry.uuidv7,
|
|
392
|
+
delta
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
void updateEntryToMaps(crListReplica, linkedListEntry, delta);
|
|
396
|
+
crListReplica.cursor = linkedListEntry;
|
|
397
|
+
change[linkedListEntry.index] = linkedListEntry.value;
|
|
398
|
+
mode = "after";
|
|
399
|
+
listIndex = linkedListEntry.index - 1;
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
crListReplica.size = crListReplica.parentMap.size;
|
|
404
|
+
listIndex++;
|
|
405
|
+
}
|
|
406
|
+
if (mode !== "overwrite")
|
|
407
|
+
while (shiftCursor) {
|
|
408
|
+
shiftCursor.index += listValues.length;
|
|
409
|
+
shiftCursor = shiftCursor.next;
|
|
410
|
+
}
|
|
411
|
+
return { change, delta };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/core/crud/delete/index.ts
|
|
415
|
+
function __delete(crListReplica, startIndex, endIndex) {
|
|
416
|
+
const change = {};
|
|
417
|
+
const delta = { values: [], tombstones: [] };
|
|
418
|
+
const listIndex = startIndex ?? 0;
|
|
419
|
+
const targetEndIndex = endIndex ?? crListReplica.size;
|
|
420
|
+
if (listIndex < 0 || targetEndIndex < listIndex || listIndex > crListReplica.size)
|
|
421
|
+
throw new CRListError("INDEX_OUT_OF_BOUNDS");
|
|
422
|
+
const deleteCount = Math.min(targetEndIndex, crListReplica.size) - listIndex;
|
|
423
|
+
if (deleteCount <= 0) return false;
|
|
424
|
+
void walkToIndex(listIndex, crListReplica);
|
|
425
|
+
if (!crListReplica.cursor) return false;
|
|
426
|
+
let current = crListReplica.cursor;
|
|
427
|
+
let deleted = 0;
|
|
428
|
+
while (current && deleted < deleteCount) {
|
|
429
|
+
const next = current.next;
|
|
430
|
+
change[current.index] = void 0;
|
|
431
|
+
void deleteLinkedEntry(crListReplica, current, delta);
|
|
432
|
+
current = next;
|
|
433
|
+
deleted++;
|
|
434
|
+
}
|
|
435
|
+
crListReplica.size = crListReplica.parentMap.size;
|
|
436
|
+
while (current) {
|
|
437
|
+
current.index -= deleted;
|
|
438
|
+
current = current.next;
|
|
439
|
+
}
|
|
440
|
+
return { change, delta };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/core/mags/merge/index.ts
|
|
444
|
+
import { prototype as prototype2, isUuidV7 as isUuidV73 } from "@sovereignbase/utils";
|
|
445
|
+
function __merge(crListReplica, crListDelta) {
|
|
446
|
+
if (!crListDelta || prototype2(crListDelta) !== "record") return false;
|
|
447
|
+
const newVals = [];
|
|
448
|
+
const newTombsIndices = [];
|
|
449
|
+
const change = {};
|
|
450
|
+
let needsRelink = false;
|
|
451
|
+
if (Object.hasOwn(crListDelta, "tombstones") && Array.isArray(crListDelta.tombstones)) {
|
|
452
|
+
for (const tombstone of crListDelta.tombstones) {
|
|
453
|
+
if (crListReplica.tombstones.has(tombstone) || !isUuidV73(tombstone))
|
|
454
|
+
continue;
|
|
455
|
+
crListReplica.tombstones.add(tombstone);
|
|
456
|
+
const linkedListEntry = crListReplica.parentMap.get(tombstone);
|
|
457
|
+
if (linkedListEntry) {
|
|
458
|
+
void newTombsIndices.push(linkedListEntry.index);
|
|
459
|
+
void deleteLinkedEntry(crListReplica, linkedListEntry);
|
|
460
|
+
needsRelink = true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (!Object.hasOwn(crListDelta, "values") || !Array.isArray(crListDelta.values)) {
|
|
465
|
+
if (newTombsIndices.length === 0) return false;
|
|
466
|
+
void assertListIndices(crListReplica);
|
|
467
|
+
for (const index of newTombsIndices) {
|
|
468
|
+
change[index] = void 0;
|
|
469
|
+
}
|
|
470
|
+
return change;
|
|
471
|
+
}
|
|
472
|
+
for (const valueEntry of crListDelta.values) {
|
|
473
|
+
const existingEntry = crListReplica.parentMap.get(valueEntry.uuidv7);
|
|
474
|
+
if (existingEntry) {
|
|
475
|
+
if (crListReplica.tombstones.has(valueEntry.uuidv7) || !isUuidV73(valueEntry.predecessor) && valueEntry.predecessor !== "\0")
|
|
476
|
+
continue;
|
|
477
|
+
if (existingEntry.predecessor >= valueEntry.predecessor) continue;
|
|
478
|
+
void moveEntryToPredecessor(
|
|
479
|
+
crListReplica,
|
|
480
|
+
existingEntry,
|
|
481
|
+
valueEntry.predecessor
|
|
482
|
+
);
|
|
483
|
+
needsRelink = true;
|
|
484
|
+
void newVals.push(existingEntry);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const linkedListEntry = snapshotValueToLinkedListValue(
|
|
488
|
+
valueEntry,
|
|
489
|
+
crListReplica
|
|
490
|
+
);
|
|
491
|
+
if (!linkedListEntry) continue;
|
|
492
|
+
const predecessor = linkedListEntry.predecessor === "\0" ? void 0 : crListReplica.parentMap.get(linkedListEntry.predecessor);
|
|
493
|
+
void updateEntryToMaps(crListReplica, linkedListEntry);
|
|
494
|
+
void newVals.push(linkedListEntry);
|
|
495
|
+
if (!needsRelink && linkedListEntry.predecessor === "\0") {
|
|
496
|
+
if (crListReplica.size === 0) {
|
|
497
|
+
crListReplica.cursor = linkedListEntry;
|
|
498
|
+
crListReplica.size = crListReplica.parentMap.size;
|
|
499
|
+
} else {
|
|
500
|
+
needsRelink = true;
|
|
501
|
+
}
|
|
502
|
+
} else if (!needsRelink && predecessor && !predecessor.next) {
|
|
503
|
+
linkedListEntry.prev = predecessor;
|
|
504
|
+
linkedListEntry.index = predecessor.index + 1;
|
|
505
|
+
predecessor.next = linkedListEntry;
|
|
506
|
+
crListReplica.cursor = linkedListEntry;
|
|
507
|
+
crListReplica.size = crListReplica.parentMap.size;
|
|
508
|
+
} else {
|
|
509
|
+
needsRelink = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (needsRelink) {
|
|
513
|
+
void flattenAndLinkTrustedState(crListReplica);
|
|
514
|
+
void assertListIndices(crListReplica);
|
|
515
|
+
}
|
|
516
|
+
if (newTombsIndices.length === 0 && newVals.length === 0) return false;
|
|
517
|
+
for (const index of newTombsIndices) {
|
|
518
|
+
change[index] = void 0;
|
|
519
|
+
}
|
|
520
|
+
for (const val of newVals) {
|
|
521
|
+
change[val.index] = val.value;
|
|
522
|
+
}
|
|
523
|
+
return change;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/core/mags/acknowledge/index.ts
|
|
527
|
+
function __acknowledge(crListReplica) {
|
|
528
|
+
let frontier = false;
|
|
529
|
+
crListReplica.tombstones.forEach((tombstone) => {
|
|
530
|
+
if (frontier === false || frontier < tombstone) frontier = tombstone;
|
|
531
|
+
});
|
|
532
|
+
if (typeof frontier === "string") return frontier;
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/core/mags/garbageCollect/index.ts
|
|
537
|
+
function __garbageCollect(frontiers, crListReplica) {
|
|
538
|
+
if (!Array.isArray(frontiers)) return;
|
|
539
|
+
const frontier = frontiers.sort().shift();
|
|
540
|
+
if (typeof frontier !== "string") return;
|
|
541
|
+
crListReplica.tombstones.forEach((tombstone, __, tombstones) => {
|
|
542
|
+
if (tombstone <= frontier) {
|
|
543
|
+
tombstones.delete(tombstone);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/core/mags/snapshot/index.ts
|
|
549
|
+
function __snapshot(crListReplica) {
|
|
550
|
+
return {
|
|
551
|
+
values: Array.from(crListReplica.parentMap.values()).map(
|
|
552
|
+
(linkedListEntry) => {
|
|
553
|
+
if (!linkedListEntry) throw new CRListError("LIST_INTEGRITY_VIOLATION");
|
|
554
|
+
return {
|
|
555
|
+
uuidv7: linkedListEntry.uuidv7,
|
|
556
|
+
value: structuredClone(linkedListEntry.value),
|
|
557
|
+
predecessor: linkedListEntry.predecessor
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
),
|
|
561
|
+
tombstones: Array.from(crListReplica.tombstones)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/CRList/class.ts
|
|
566
|
+
var CRList = class {
|
|
567
|
+
/**
|
|
568
|
+
* Creates a replicated list from an optional serializable snapshot.
|
|
569
|
+
*
|
|
570
|
+
* @param snapshot - A previously emitted CRList snapshot.
|
|
571
|
+
*/
|
|
572
|
+
constructor(snapshot) {
|
|
573
|
+
Object.defineProperties(this, {
|
|
574
|
+
state: {
|
|
575
|
+
value: __create(snapshot),
|
|
576
|
+
enumerable: false,
|
|
577
|
+
configurable: false,
|
|
578
|
+
writable: false
|
|
579
|
+
},
|
|
580
|
+
eventTarget: {
|
|
581
|
+
value: new EventTarget(),
|
|
582
|
+
enumerable: false,
|
|
583
|
+
configurable: false,
|
|
584
|
+
writable: false
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
return new Proxy(this, {
|
|
588
|
+
get(target, index, receiver) {
|
|
589
|
+
const listIndex = indexFromPropertyKey(index);
|
|
590
|
+
if (listIndex === void 0) return Reflect.get(target, index, receiver);
|
|
591
|
+
return __read(listIndex, target.state);
|
|
592
|
+
},
|
|
593
|
+
has(target, index) {
|
|
594
|
+
const listIndex = indexFromPropertyKey(index);
|
|
595
|
+
if (listIndex === void 0) return Reflect.has(target, index);
|
|
596
|
+
return listIndex >= 0 && listIndex < target.state.size;
|
|
597
|
+
},
|
|
598
|
+
set(target, index, value) {
|
|
599
|
+
const listIndex = indexFromPropertyKey(index);
|
|
600
|
+
if (listIndex === void 0) return false;
|
|
601
|
+
try {
|
|
602
|
+
const result = __update(listIndex, [value], target.state, "overwrite");
|
|
603
|
+
if (!result) return false;
|
|
604
|
+
const { delta, change } = result;
|
|
605
|
+
if (delta)
|
|
606
|
+
void target.eventTarget.dispatchEvent(
|
|
607
|
+
new CustomEvent("delta", { detail: delta })
|
|
608
|
+
);
|
|
609
|
+
if (change)
|
|
610
|
+
void target.eventTarget.dispatchEvent(
|
|
611
|
+
new CustomEvent("change", { detail: change })
|
|
612
|
+
);
|
|
613
|
+
return true;
|
|
614
|
+
} catch {
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
deleteProperty(target, index) {
|
|
619
|
+
const listIndex = indexFromPropertyKey(index);
|
|
620
|
+
if (listIndex === void 0) return false;
|
|
621
|
+
try {
|
|
622
|
+
const result = __delete(target.state, listIndex, listIndex + 1);
|
|
623
|
+
if (!result) return false;
|
|
624
|
+
const { delta, change } = result;
|
|
625
|
+
if (delta) {
|
|
626
|
+
void target.eventTarget.dispatchEvent(
|
|
627
|
+
new CustomEvent("delta", { detail: delta })
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
if (change) {
|
|
631
|
+
void target.eventTarget.dispatchEvent(
|
|
632
|
+
new CustomEvent("change", { detail: change })
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
return true;
|
|
636
|
+
} catch {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
ownKeys(target) {
|
|
641
|
+
return [
|
|
642
|
+
...Reflect.ownKeys(target),
|
|
643
|
+
...Array.from({ length: target.size }, (_, index) => String(index))
|
|
644
|
+
];
|
|
645
|
+
},
|
|
646
|
+
getOwnPropertyDescriptor(target, index) {
|
|
647
|
+
const listIndex = indexFromPropertyKey(index);
|
|
648
|
+
if (listIndex !== void 0 && listIndex < target.size) {
|
|
649
|
+
return {
|
|
650
|
+
value: __read(listIndex, target.state),
|
|
651
|
+
writable: true,
|
|
652
|
+
enumerable: true,
|
|
653
|
+
configurable: true
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return Reflect.getOwnPropertyDescriptor(target, index);
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* The current number of live entries.
|
|
662
|
+
*/
|
|
663
|
+
get size() {
|
|
664
|
+
return this.state.size;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Inserts a value before an index.
|
|
668
|
+
*
|
|
669
|
+
* If `beforeIndex` is omitted, the value is inserted at the start of the list.
|
|
670
|
+
*
|
|
671
|
+
* @param value - The value to insert.
|
|
672
|
+
* @param beforeIndex - The index to insert before.
|
|
673
|
+
*/
|
|
674
|
+
prepend(value, beforeIndex) {
|
|
675
|
+
const result = __update(beforeIndex ?? 0, [value], this.state, "before");
|
|
676
|
+
if (!result) return;
|
|
677
|
+
const { delta, change } = result;
|
|
678
|
+
if (delta)
|
|
679
|
+
void this.eventTarget.dispatchEvent(
|
|
680
|
+
new CustomEvent("delta", { detail: delta })
|
|
681
|
+
);
|
|
682
|
+
if (change)
|
|
683
|
+
void this.eventTarget.dispatchEvent(
|
|
684
|
+
new CustomEvent("change", { detail: change })
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Inserts a value after an index.
|
|
689
|
+
*
|
|
690
|
+
* If `afterIndex` is omitted, the value is appended at the end of the list.
|
|
691
|
+
*
|
|
692
|
+
* @param value - The value to insert.
|
|
693
|
+
* @param afterIndex - The index to insert after.
|
|
694
|
+
*/
|
|
695
|
+
append(value, afterIndex) {
|
|
696
|
+
const result = __update(
|
|
697
|
+
afterIndex ?? this.state.size,
|
|
698
|
+
[value],
|
|
699
|
+
this.state,
|
|
700
|
+
"after"
|
|
701
|
+
);
|
|
702
|
+
if (!result) return;
|
|
703
|
+
const { delta, change } = result;
|
|
704
|
+
if (delta)
|
|
705
|
+
void this.eventTarget.dispatchEvent(
|
|
706
|
+
new CustomEvent("delta", { detail: delta })
|
|
707
|
+
);
|
|
708
|
+
if (change)
|
|
709
|
+
void this.eventTarget.dispatchEvent(
|
|
710
|
+
new CustomEvent("change", { detail: change })
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Removes the entry at an index.
|
|
715
|
+
*
|
|
716
|
+
* @param index - The index to remove.
|
|
717
|
+
*/
|
|
718
|
+
remove(index) {
|
|
719
|
+
const result = __delete(this.state, index, index + 1);
|
|
720
|
+
if (!result) return;
|
|
721
|
+
const { delta, change } = result;
|
|
722
|
+
if (delta)
|
|
723
|
+
void this.eventTarget.dispatchEvent(
|
|
724
|
+
new CustomEvent("delta", { detail: delta })
|
|
725
|
+
);
|
|
726
|
+
if (change)
|
|
727
|
+
void this.eventTarget.dispatchEvent(
|
|
728
|
+
new CustomEvent("change", { detail: change })
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Applies a remote gossip delta to this list.
|
|
733
|
+
*
|
|
734
|
+
* Emits a `change` event when the merge changes the live projection.
|
|
735
|
+
*
|
|
736
|
+
* @param delta - The remote CRList delta to merge.
|
|
737
|
+
*/
|
|
738
|
+
merge(delta) {
|
|
739
|
+
const change = __merge(this.state, delta);
|
|
740
|
+
if (change)
|
|
741
|
+
void this.eventTarget.dispatchEvent(
|
|
742
|
+
new CustomEvent("change", { detail: change })
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Emits an acknowledgement frontier for currently retained tombstones.
|
|
747
|
+
*/
|
|
748
|
+
acknowledge() {
|
|
749
|
+
const ack = __acknowledge(this.state);
|
|
750
|
+
if (ack)
|
|
751
|
+
void this.eventTarget.dispatchEvent(
|
|
752
|
+
new CustomEvent("ack", { detail: ack })
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Garbage-collects tombstones that are covered by acknowledgement frontiers.
|
|
757
|
+
*
|
|
758
|
+
* @param frontiers - Replica acknowledgement frontiers.
|
|
759
|
+
*/
|
|
760
|
+
garbageCollect(frontiers) {
|
|
761
|
+
void __garbageCollect(frontiers, this.state);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Emits the current serializable list snapshot.
|
|
765
|
+
*/
|
|
766
|
+
snapshot() {
|
|
767
|
+
const snapshot = __snapshot(this.state);
|
|
768
|
+
if (snapshot)
|
|
769
|
+
void this.eventTarget.dispatchEvent(
|
|
770
|
+
new CustomEvent("snapshot", { detail: snapshot })
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Registers an event listener.
|
|
775
|
+
*
|
|
776
|
+
* @param type - The event type to listen for.
|
|
777
|
+
* @param listener - The listener to register.
|
|
778
|
+
* @param options - Listener registration options.
|
|
779
|
+
*/
|
|
780
|
+
addEventListener(type, listener, options) {
|
|
781
|
+
this.eventTarget.addEventListener(
|
|
782
|
+
type,
|
|
783
|
+
listener,
|
|
784
|
+
options
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Removes an event listener.
|
|
789
|
+
*
|
|
790
|
+
* @param type - The event type to stop listening for.
|
|
791
|
+
* @param listener - The listener to remove.
|
|
792
|
+
* @param options - Listener removal options.
|
|
793
|
+
*/
|
|
794
|
+
removeEventListener(type, listener, options) {
|
|
795
|
+
this.eventTarget.removeEventListener(
|
|
796
|
+
type,
|
|
797
|
+
listener,
|
|
798
|
+
options
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Returns a serializable snapshot representation of this list.
|
|
803
|
+
*
|
|
804
|
+
* Called automatically by `JSON.stringify`.
|
|
805
|
+
*/
|
|
806
|
+
toJSON() {
|
|
807
|
+
return __snapshot(this.state);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Returns this list as a JSON string.
|
|
811
|
+
*/
|
|
812
|
+
toString() {
|
|
813
|
+
return JSON.stringify(this);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Returns the Node.js console inspection representation.
|
|
817
|
+
*/
|
|
818
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
819
|
+
return this.toJSON();
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Returns the Deno console inspection representation.
|
|
823
|
+
*/
|
|
824
|
+
[/* @__PURE__ */ Symbol.for("Deno.customInspect")]() {
|
|
825
|
+
return this.toJSON();
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Iterates over the current live values in index order.
|
|
829
|
+
*/
|
|
830
|
+
*[Symbol.iterator]() {
|
|
831
|
+
for (let index = 0; index < this.size; index++) {
|
|
832
|
+
const value = this[index];
|
|
833
|
+
yield value;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Calls a function once for each live value in index order.
|
|
838
|
+
*
|
|
839
|
+
* @param callback - Function to call for each value.
|
|
840
|
+
* @param thisArg - Optional `this` value for the callback.
|
|
841
|
+
*/
|
|
842
|
+
forEach(callback, thisArg) {
|
|
843
|
+
for (let index = 0; index < this.size; index++) {
|
|
844
|
+
callback.call(thisArg, this[index], index, this);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
export {
|
|
849
|
+
CRList,
|
|
850
|
+
__acknowledge,
|
|
851
|
+
__create,
|
|
852
|
+
__delete,
|
|
853
|
+
__garbageCollect,
|
|
854
|
+
__merge,
|
|
855
|
+
__read,
|
|
856
|
+
__snapshot,
|
|
857
|
+
__update
|
|
858
|
+
};
|
|
859
|
+
//# sourceMappingURL=index.js.map
|