@sovereignbase/convergent-replicated-list 1.1.0 → 1.2.1

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/dist/index.js CHANGED
@@ -15,18 +15,27 @@
15
15
  */
16
16
 
17
17
 
18
- // src/.helpers/assertListIndices/index.ts
19
- function assertListIndices(crListReplica) {
20
- if (!crListReplica.cursor) return;
18
+ // src/.helpers/rebuildLiveIndex/index.ts
19
+ function rebuildLiveIndex(crListReplica) {
20
+ if (!crListReplica.cursor) {
21
+ crListReplica.index?.clear();
22
+ crListReplica.cursorIndex = void 0;
23
+ return;
24
+ }
21
25
  let index = crListReplica.size;
26
+ const entries = crListReplica.index ?? /* @__PURE__ */ new Map();
27
+ void entries.clear();
22
28
  while (crListReplica.cursor.next)
23
29
  crListReplica.cursor = crListReplica.cursor.next;
24
30
  while (index >= 1) {
25
31
  index--;
26
32
  crListReplica.cursor.index = index;
33
+ void entries.set(index, crListReplica.cursor);
27
34
  if (crListReplica.cursor.prev === void 0) break;
28
35
  crListReplica.cursor = crListReplica.cursor.prev;
29
36
  }
37
+ crListReplica.index = entries;
38
+ crListReplica.cursorIndex = 0;
30
39
  }
31
40
 
32
41
  // src/.errors/class.ts
@@ -49,102 +58,115 @@ var CRListError = class extends Error {
49
58
  }
50
59
  };
51
60
 
52
- // src/.helpers/walkToIndex/index.ts
53
- function walkToIndex(targetIndex, crListReplica) {
61
+ // src/.helpers/seekCursorToIndex/index.ts
62
+ function seekCursorToIndex(targetIndex, crListReplica) {
54
63
  if (targetIndex < 0 || targetIndex >= crListReplica.size)
55
64
  throw new CRListError("INDEX_OUT_OF_BOUNDS", "Index out of bounds");
65
+ const indexedEntry = crListReplica.index?.get(targetIndex);
66
+ if (indexedEntry) {
67
+ if (crListReplica.parentMap.get(indexedEntry.uuidv7) === indexedEntry) {
68
+ crListReplica.cursor = indexedEntry;
69
+ crListReplica.cursorIndex = targetIndex;
70
+ return;
71
+ } else {
72
+ void crListReplica.index?.delete(targetIndex);
73
+ }
74
+ }
56
75
  if (!crListReplica.cursor)
57
76
  throw new CRListError("LIST_EMPTY", "List is empty");
58
- const direction = crListReplica.cursor.index > targetIndex ? "prev" : "next";
59
- while (crListReplica.cursor && crListReplica.cursor.index !== targetIndex) {
77
+ let cursorIndex = crListReplica.cursorIndex ?? crListReplica.cursor.index;
78
+ const direction = cursorIndex > targetIndex ? "prev" : "next";
79
+ while (crListReplica.cursor && cursorIndex !== targetIndex) {
60
80
  crListReplica.cursor = crListReplica.cursor[direction];
81
+ cursorIndex += direction === "next" ? 1 : -1;
82
+ }
83
+ if (crListReplica.cursor) {
84
+ crListReplica.cursorIndex = targetIndex;
85
+ void crListReplica.index?.set(targetIndex, crListReplica.cursor);
61
86
  }
62
87
  }
63
88
 
64
- // src/.helpers/insertBetween/index.ts
65
- function insertBetween(prev, linkedListEntry, next) {
89
+ // src/.helpers/linkEntryBetween/index.ts
90
+ function linkEntryBetween(prev, linkedListEntry, next) {
66
91
  linkedListEntry.prev = prev;
67
92
  linkedListEntry.next = next;
68
93
  if (prev) prev.next = linkedListEntry;
69
94
  if (next) next.prev = linkedListEntry;
70
95
  }
71
96
 
72
- // src/.helpers/flattenAndLinkTrustedState/index.ts
73
- function flattenAndLinkTrustedState(crListReplica) {
97
+ // src/.helpers/rebuildLiveProjection/index.ts
98
+ function rebuildLiveProjection(crListReplica) {
74
99
  crListReplica.cursor = void 0;
75
- const resolvedSiblingPredecessors = /* @__PURE__ */ new Set();
100
+ const entries = crListReplica.index ?? /* @__PURE__ */ new Map();
101
+ void entries.clear();
76
102
  for (const entry of crListReplica.parentMap.values()) {
77
103
  if (!entry) continue;
78
104
  entry.prev = void 0;
79
105
  entry.next = void 0;
80
106
  }
81
- const keys = [...crListReplica.childrenMap.keys()].sort(
82
- (a, b) => a > b ? 1 : -1
83
- );
84
- let hasProgress = true;
85
- while (hasProgress) {
86
- hasProgress = false;
87
- for (const predecessorIdentifier of keys) {
88
- if (resolvedSiblingPredecessors.has(predecessorIdentifier)) continue;
89
- const siblings = crListReplica.childrenMap.get(predecessorIdentifier);
90
- if (!siblings) continue;
91
- if (siblings.length > 1)
92
- siblings.sort((a, b) => a.uuidv7 > b.uuidv7 ? 1 : -1);
93
- const predecessor = predecessorIdentifier === "\0" ? void 0 : crListReplica.parentMap.get(predecessorIdentifier);
94
- if (predecessor && !predecessor.prev && !predecessor.next && crListReplica.cursor !== predecessor)
95
- continue;
96
- let prev = predecessor ?? crListReplica.cursor;
97
- const predecessorNext = predecessor?.next;
98
- if (siblings.length === 1) {
99
- const sibling = siblings[0];
100
- insertBetween(prev, sibling, sibling.next);
101
- prev = sibling;
102
- if (predecessorNext && predecessorNext !== sibling) {
103
- prev.next = predecessorNext;
104
- predecessorNext.prev = prev;
105
- } else {
106
- prev.next = void 0;
107
+ let previous = void 0;
108
+ let first = void 0;
109
+ let index = 0;
110
+ const appendChildren = (predecessorIdentifier) => {
111
+ const stack = [{ predecessorIdentifier, siblingIndex: 0 }];
112
+ while (stack.length > 0) {
113
+ const frame = stack[stack.length - 1];
114
+ if (!frame.siblings) {
115
+ frame.siblings = crListReplica.childrenMap.get(
116
+ frame.predecessorIdentifier
117
+ );
118
+ if (!frame.siblings) {
119
+ void stack.pop();
120
+ continue;
107
121
  }
108
- if (!predecessorNext) crListReplica.cursor = prev;
109
- resolvedSiblingPredecessors.add(predecessorIdentifier);
110
- hasProgress = true;
111
- continue;
122
+ if (frame.siblings.length > 1)
123
+ void frame.siblings.sort((a, b) => a.uuidv7 > b.uuidv7 ? 1 : -1);
112
124
  }
113
- const siblingSet = new Set(siblings);
114
- for (let index = 0; index < siblings.length; index++) {
115
- const sibling = siblings[index];
116
- const next = siblings[index + 1];
117
- insertBetween(prev, sibling, sibling.next);
118
- prev = sibling;
119
- if (next) {
120
- prev.next = next;
121
- next.prev = prev;
122
- } else if (predecessorNext && !siblingSet.has(predecessorNext)) {
123
- prev.next = predecessorNext;
124
- predecessorNext.prev = prev;
125
- } else {
126
- prev.next = void 0;
127
- }
125
+ if (frame.siblingIndex >= frame.siblings.length) {
126
+ void stack.pop();
127
+ continue;
128
128
  }
129
- if (!predecessorNext) crListReplica.cursor = prev;
130
- resolvedSiblingPredecessors.add(predecessorIdentifier);
131
- hasProgress = true;
129
+ const sibling = frame.siblings[frame.siblingIndex];
130
+ frame.siblingIndex++;
131
+ if (!sibling) continue;
132
+ if (crListReplica.parentMap.get(sibling.uuidv7) !== sibling) continue;
133
+ sibling.index = index;
134
+ index++;
135
+ void linkEntryBetween(previous, sibling, void 0);
136
+ if (!first) first = sibling;
137
+ previous = sibling;
138
+ void stack.push({
139
+ predecessorIdentifier: sibling.uuidv7,
140
+ siblingIndex: 0
141
+ });
132
142
  }
133
- }
143
+ };
144
+ void appendChildren("\0");
145
+ const detachedPredecessors = [];
146
+ for (const predecessorIdentifier of crListReplica.childrenMap.keys()) {
147
+ if (predecessorIdentifier !== "\0" && !crListReplica.parentMap.get(predecessorIdentifier))
148
+ void detachedPredecessors.push(predecessorIdentifier);
149
+ }
150
+ if (detachedPredecessors.length > 1)
151
+ detachedPredecessors.sort((a, b) => a > b ? 1 : -1);
152
+ for (const predecessorIdentifier of detachedPredecessors)
153
+ void appendChildren(predecessorIdentifier);
154
+ crListReplica.cursor = first;
155
+ crListReplica.cursorIndex = first ? 0 : void 0;
156
+ if (first) void entries.set(0, first);
157
+ crListReplica.index = entries;
134
158
  crListReplica.size = crListReplica.parentMap.size;
135
159
  }
136
160
 
137
- // src/.helpers/transformSnapshotEntryToStateEntry/index.ts
138
- import { isUuidV7, safeStructuredClone } from "@sovereignbase/utils";
139
- function transformSnapshotEntryToStateEntry(valueEntry, crListReplica) {
161
+ // src/.helpers/materializeSnapshotEntry/index.ts
162
+ import { isUuidV7 } from "@sovereignbase/utils";
163
+ function materializeSnapshotEntry(valueEntry, crListReplica) {
140
164
  if (valueEntry === null || valueEntry === void 0) return void 0;
141
165
  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))
142
166
  return void 0;
143
- const [cloned, copiedValue] = safeStructuredClone(valueEntry.value);
144
- if (!cloned) return void 0;
145
167
  return {
146
168
  uuidv7: valueEntry.uuidv7,
147
- value: copiedValue,
169
+ value: valueEntry.value,
148
170
  predecessor: valueEntry.predecessor,
149
171
  index: 0,
150
172
  next: void 0,
@@ -152,59 +174,65 @@ function transformSnapshotEntryToStateEntry(valueEntry, crListReplica) {
152
174
  };
153
175
  }
154
176
 
155
- // src/.helpers/updateEntryToMaps/index.ts
156
- function updateEntryToMaps(crListReplica, linkedListEntry, deltaBuf) {
157
- crListReplica.parentMap.set(linkedListEntry.uuidv7, linkedListEntry);
177
+ // src/.helpers/attachEntryToIndexes/index.ts
178
+ function attachEntryToIndexes(crListReplica, linkedListEntry, deltaBuf) {
179
+ void crListReplica.parentMap.set(linkedListEntry.uuidv7, linkedListEntry);
158
180
  const siblings = crListReplica.childrenMap.get(linkedListEntry.predecessor);
159
181
  if (siblings) {
160
- siblings.push(linkedListEntry);
182
+ void siblings.push(linkedListEntry);
161
183
  } else {
162
- crListReplica.childrenMap.set(linkedListEntry.predecessor, [
184
+ void crListReplica.childrenMap.set(linkedListEntry.predecessor, [
163
185
  linkedListEntry
164
186
  ]);
165
187
  }
166
188
  if (deltaBuf && !Array.isArray(deltaBuf.values)) deltaBuf.values = [];
167
189
  if (deltaBuf?.values)
168
- deltaBuf.values.push({
190
+ void deltaBuf.values.push({
169
191
  uuidv7: linkedListEntry.uuidv7,
170
192
  value: linkedListEntry.value,
171
193
  predecessor: linkedListEntry.predecessor
172
194
  });
173
195
  }
174
196
 
175
- // src/.helpers/deleteEntryFromMaps/index.ts
176
- function deleteEntryFromMaps(crListReplica, linkedListEntry) {
177
- crListReplica.parentMap.delete(linkedListEntry.uuidv7);
197
+ // src/.helpers/detachEntryFromIndexes/index.ts
198
+ function detachEntryFromIndexes(crListReplica, linkedListEntry) {
199
+ void crListReplica.parentMap.delete(linkedListEntry.uuidv7);
178
200
  const siblings = crListReplica.childrenMap.get(linkedListEntry.predecessor);
179
201
  if (!siblings) return;
180
202
  const index = siblings.indexOf(linkedListEntry);
181
- if (index !== -1) siblings.splice(index, 1);
203
+ if (index !== -1) void siblings.splice(index, 1);
182
204
  }
183
205
 
184
- // src/.helpers/deleteLinkedEntry/index.ts
185
- function deleteLinkedEntry(crListReplica, linkedListEntry, deltaBuf) {
206
+ // src/.helpers/deleteLiveEntry/index.ts
207
+ function deleteLiveEntry(crListReplica, linkedListEntry, deltaBuf) {
186
208
  const prev = linkedListEntry.prev;
187
209
  const next = linkedListEntry.next;
188
- crListReplica.tombstones.add(linkedListEntry.uuidv7);
210
+ void crListReplica.tombstones.add(linkedListEntry.uuidv7);
189
211
  if (deltaBuf && !Array.isArray(deltaBuf.tombstones)) deltaBuf.tombstones = [];
190
- deltaBuf?.tombstones?.push(linkedListEntry.uuidv7);
212
+ void deltaBuf?.tombstones?.push(linkedListEntry.uuidv7);
191
213
  if (prev) prev.next = next;
192
214
  if (next) {
193
215
  next.prev = prev;
194
216
  }
195
- void deleteEntryFromMaps(crListReplica, linkedListEntry);
217
+ void detachEntryFromIndexes(crListReplica, linkedListEntry);
196
218
  if (crListReplica.cursor === linkedListEntry)
197
219
  crListReplica.cursor = next ?? prev;
220
+ if (!crListReplica.cursor) crListReplica.cursorIndex = void 0;
198
221
  linkedListEntry.prev = void 0;
199
222
  linkedListEntry.next = void 0;
200
223
  crListReplica.size = crListReplica.parentMap.size;
201
224
  }
202
225
 
226
+ // src/.helpers/dispatchCRListEvent/index.ts
227
+ function dispatchCRListEvent(eventTarget, type, detail) {
228
+ void eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
229
+ }
230
+
203
231
  // src/.helpers/moveEntryToPredecessor/index.ts
204
232
  function moveEntryToPredecessor(crListReplica, linkedListEntry, predecessor, deltaBuf) {
205
- void deleteEntryFromMaps(crListReplica, linkedListEntry);
233
+ void detachEntryFromIndexes(crListReplica, linkedListEntry);
206
234
  linkedListEntry.predecessor = predecessor;
207
- void updateEntryToMaps(crListReplica, linkedListEntry, deltaBuf);
235
+ void attachEntryToIndexes(crListReplica, linkedListEntry, deltaBuf);
208
236
  }
209
237
 
210
238
  // src/.helpers/indexFromPropertyKey/index.ts
@@ -221,6 +249,8 @@ function __create(snapshot) {
221
249
  const crListReplica = {
222
250
  size: 0,
223
251
  cursor: void 0,
252
+ cursorIndex: void 0,
253
+ index: /* @__PURE__ */ new Map(),
224
254
  tombstones: /* @__PURE__ */ new Set(),
225
255
  parentMap: /* @__PURE__ */ new Map(),
226
256
  childrenMap: /* @__PURE__ */ new Map()
@@ -230,36 +260,50 @@ function __create(snapshot) {
230
260
  for (const tombstone of snapshot.tombstones) {
231
261
  if (crListReplica.tombstones.has(tombstone) || !isUuidV72(tombstone))
232
262
  continue;
233
- crListReplica.tombstones.add(tombstone);
263
+ void crListReplica.tombstones.add(tombstone);
234
264
  }
235
265
  }
236
266
  if (!Object.hasOwn(snapshot, "values") || !Array.isArray(snapshot.values))
237
267
  return crListReplica;
268
+ let canUseLinearProjection = true;
269
+ let previous = void 0;
238
270
  for (const valueEntry of snapshot.values) {
239
- const linkedListEntry = transformSnapshotEntryToStateEntry(
271
+ const linkedListEntry = materializeSnapshotEntry(
240
272
  valueEntry,
241
273
  crListReplica
242
274
  );
243
275
  if (!linkedListEntry) continue;
244
- void updateEntryToMaps(crListReplica, linkedListEntry);
276
+ void attachEntryToIndexes(crListReplica, linkedListEntry);
277
+ if (canUseLinearProjection && linkedListEntry.predecessor === (previous?.uuidv7 ?? "\0")) {
278
+ linkedListEntry.index = crListReplica.parentMap.size - 1;
279
+ void linkEntryBetween(previous, linkedListEntry, void 0);
280
+ previous = linkedListEntry;
281
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
282
+ continue;
283
+ }
284
+ canUseLinearProjection = false;
285
+ }
286
+ if (canUseLinearProjection) {
287
+ crListReplica.cursor = previous;
288
+ crListReplica.cursorIndex = previous ? crListReplica.parentMap.size - 1 : void 0;
289
+ crListReplica.size = crListReplica.parentMap.size;
290
+ return crListReplica;
245
291
  }
246
- void flattenAndLinkTrustedState(crListReplica);
247
- void assertListIndices(crListReplica);
292
+ void rebuildLiveProjection(crListReplica);
248
293
  return crListReplica;
249
294
  }
250
295
 
251
296
  // src/core/crud/read/index.ts
252
297
  function __read(targetIndex, crListReplica) {
253
298
  try {
254
- void walkToIndex(targetIndex, crListReplica);
255
- return structuredClone(crListReplica?.cursor?.value);
299
+ void seekCursorToIndex(targetIndex, crListReplica);
300
+ return crListReplica.cursor?.value;
256
301
  } catch {
257
302
  return void 0;
258
303
  }
259
304
  }
260
305
 
261
306
  // src/core/crud/update/index.ts
262
- import { safeStructuredClone as safeStructuredClone2 } from "@sovereignbase/utils";
263
307
  import { v7 as uuidv7 } from "uuid";
264
308
  function __update(listIndex, listValues, crListReplica, mode) {
265
309
  if (listIndex < 0 || listIndex > crListReplica.size)
@@ -272,14 +316,11 @@ function __update(listIndex, listValues, crListReplica, mode) {
272
316
  if (listValues.length === 0) return false;
273
317
  const change = {};
274
318
  const delta = { values: [], tombstones: [] };
275
- let shiftCursor;
276
319
  for (const listValue of listValues) {
277
- const [cloned, copiedValue] = safeStructuredClone2(listValue);
278
- if (!cloned) throw new CRListError("VALUE_NOT_CLONEABLE");
279
320
  const v7 = uuidv7();
280
321
  const linkedListEntry = {
281
322
  uuidv7: v7,
282
- value: copiedValue,
323
+ value: listValue,
283
324
  predecessor: "\0",
284
325
  index: 0,
285
326
  next: void 0,
@@ -290,26 +331,35 @@ function __update(listIndex, listValues, crListReplica, mode) {
290
331
  if (listIndex === crListReplica.size) {
291
332
  if (crListReplica.size === 0) {
292
333
  crListReplica.cursor = linkedListEntry;
293
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
334
+ crListReplica.cursorIndex = linkedListEntry.index;
335
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
336
+ crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
294
337
  change[linkedListEntry.index] = linkedListEntry.value;
295
338
  break;
296
339
  }
297
- void walkToIndex(crListReplica.size - 1, crListReplica);
340
+ void seekCursorToIndex(crListReplica.size - 1, crListReplica);
298
341
  if (!crListReplica.cursor) return false;
299
- linkedListEntry.index = crListReplica.cursor.index + 1;
342
+ linkedListEntry.index = (crListReplica.cursorIndex ?? 0) + 1;
300
343
  linkedListEntry.predecessor = crListReplica.cursor.uuidv7;
301
- insertBetween(crListReplica.cursor, linkedListEntry, void 0);
302
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
344
+ void linkEntryBetween(
345
+ crListReplica.cursor,
346
+ linkedListEntry,
347
+ void 0
348
+ );
349
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
303
350
  crListReplica.cursor = linkedListEntry;
304
- change[linkedListEntry.index] = structuredClone(linkedListEntry.value);
351
+ crListReplica.cursorIndex = linkedListEntry.index;
352
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
353
+ change[linkedListEntry.index] = linkedListEntry.value;
305
354
  break;
306
355
  }
307
- void walkToIndex(listIndex, crListReplica);
356
+ void seekCursorToIndex(listIndex, crListReplica);
308
357
  if (!crListReplica.cursor) return false;
309
358
  const entryToOverwrite = crListReplica.cursor;
359
+ const actualIndex = crListReplica.cursorIndex ?? listIndex;
310
360
  linkedListEntry.predecessor = entryToOverwrite.predecessor;
311
- linkedListEntry.index = entryToOverwrite.index;
312
- insertBetween(
361
+ linkedListEntry.index = actualIndex;
362
+ void linkEntryBetween(
313
363
  entryToOverwrite.prev,
314
364
  linkedListEntry,
315
365
  entryToOverwrite.next
@@ -324,34 +374,38 @@ function __update(listIndex, listValues, crListReplica, mode) {
324
374
  );
325
375
  }
326
376
  }
327
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
328
- crListReplica.tombstones.add(entryToOverwrite.uuidv7);
329
- delta.tombstones?.push(entryToOverwrite.uuidv7);
330
- void deleteEntryFromMaps(crListReplica, entryToOverwrite);
377
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
378
+ void crListReplica.tombstones.add(entryToOverwrite.uuidv7);
379
+ void delta.tombstones?.push(entryToOverwrite.uuidv7);
380
+ void detachEntryFromIndexes(crListReplica, entryToOverwrite);
331
381
  entryToOverwrite.next = void 0;
332
382
  entryToOverwrite.prev = void 0;
333
383
  crListReplica.cursor = linkedListEntry;
334
- change[linkedListEntry.index] = structuredClone(linkedListEntry.value);
384
+ crListReplica.cursorIndex = actualIndex;
385
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
386
+ change[actualIndex] = linkedListEntry.value;
335
387
  break;
336
388
  }
337
389
  case "after": {
338
390
  if (crListReplica.size === 0 && listIndex === 0) {
339
391
  crListReplica.cursor = linkedListEntry;
340
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
341
- change[linkedListEntry.index] = structuredClone(linkedListEntry.value);
392
+ crListReplica.cursorIndex = linkedListEntry.index;
393
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
394
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
395
+ change[linkedListEntry.index] = linkedListEntry.value;
342
396
  break;
343
397
  }
344
398
  if (listIndex === crListReplica.size) {
345
- void walkToIndex(crListReplica.size - 1, crListReplica);
399
+ void seekCursorToIndex(crListReplica.size - 1, crListReplica);
346
400
  } else {
347
- void walkToIndex(listIndex, crListReplica);
401
+ void seekCursorToIndex(listIndex, crListReplica);
348
402
  }
349
403
  if (!crListReplica.cursor) return false;
404
+ const actualIndex = crListReplica.cursorIndex ?? listIndex;
350
405
  const next = listIndex === crListReplica.size ? void 0 : crListReplica.cursor.next;
351
- shiftCursor = next;
352
- linkedListEntry.index = crListReplica.cursor.index + 1;
406
+ linkedListEntry.index = actualIndex + 1;
353
407
  linkedListEntry.predecessor = crListReplica.cursor.uuidv7;
354
- insertBetween(crListReplica.cursor, linkedListEntry, next);
408
+ void linkEntryBetween(crListReplica.cursor, linkedListEntry, next);
355
409
  if (next) {
356
410
  if (next.predecessor === crListReplica.cursor.uuidv7) {
357
411
  void moveEntryToPredecessor(
@@ -362,27 +416,32 @@ function __update(listIndex, listValues, crListReplica, mode) {
362
416
  );
363
417
  }
364
418
  }
365
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
419
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
366
420
  crListReplica.cursor = linkedListEntry;
367
- change[linkedListEntry.index] = structuredClone(linkedListEntry.value);
421
+ crListReplica.cursorIndex = linkedListEntry.index;
422
+ if (next) crListReplica.index = /* @__PURE__ */ new Map();
423
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
424
+ change[linkedListEntry.index] = linkedListEntry.value;
368
425
  break;
369
426
  }
370
427
  case "before": {
371
428
  if (crListReplica.size === 0 && listIndex === 0) {
372
429
  crListReplica.cursor = linkedListEntry;
373
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
374
- change[linkedListEntry.index] = structuredClone(linkedListEntry.value);
430
+ crListReplica.cursorIndex = linkedListEntry.index;
431
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
432
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
433
+ change[linkedListEntry.index] = linkedListEntry.value;
375
434
  mode = "after";
376
435
  listIndex = linkedListEntry.index - 1;
377
436
  break;
378
437
  }
379
- void walkToIndex(listIndex, crListReplica);
438
+ void seekCursorToIndex(listIndex, crListReplica);
380
439
  if (!crListReplica.cursor) return false;
440
+ const actualIndex = crListReplica.cursorIndex ?? listIndex;
381
441
  const prev = crListReplica.cursor.prev;
382
- shiftCursor = crListReplica.cursor;
383
- linkedListEntry.index = crListReplica.cursor.index;
442
+ linkedListEntry.index = actualIndex;
384
443
  linkedListEntry.predecessor = prev?.uuidv7 ?? "\0";
385
- insertBetween(prev, linkedListEntry, crListReplica.cursor);
444
+ void linkEntryBetween(prev, linkedListEntry, crListReplica.cursor);
386
445
  if (crListReplica.cursor.predecessor === linkedListEntry.predecessor) {
387
446
  void moveEntryToPredecessor(
388
447
  crListReplica,
@@ -391,9 +450,12 @@ function __update(listIndex, listValues, crListReplica, mode) {
391
450
  delta
392
451
  );
393
452
  }
394
- void updateEntryToMaps(crListReplica, linkedListEntry, delta);
453
+ void attachEntryToIndexes(crListReplica, linkedListEntry, delta);
395
454
  crListReplica.cursor = linkedListEntry;
396
- change[linkedListEntry.index] = structuredClone(linkedListEntry.value);
455
+ crListReplica.cursorIndex = actualIndex;
456
+ crListReplica.index = /* @__PURE__ */ new Map();
457
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
458
+ change[actualIndex] = linkedListEntry.value;
397
459
  mode = "after";
398
460
  listIndex = linkedListEntry.index - 1;
399
461
  break;
@@ -402,11 +464,6 @@ function __update(listIndex, listValues, crListReplica, mode) {
402
464
  crListReplica.size = crListReplica.parentMap.size;
403
465
  listIndex++;
404
466
  }
405
- if (mode !== "overwrite")
406
- while (shiftCursor) {
407
- shiftCursor.index += listValues.length;
408
- shiftCursor = shiftCursor.next;
409
- }
410
467
  return { change, delta };
411
468
  }
412
469
 
@@ -420,22 +477,29 @@ function __delete(crListReplica, startIndex, endIndex) {
420
477
  throw new CRListError("INDEX_OUT_OF_BOUNDS");
421
478
  const deleteCount = Math.min(targetEndIndex, crListReplica.size) - listIndex;
422
479
  if (deleteCount <= 0) return false;
423
- void walkToIndex(listIndex, crListReplica);
480
+ void seekCursorToIndex(listIndex, crListReplica);
424
481
  if (!crListReplica.cursor) return false;
425
482
  let current = crListReplica.cursor;
426
483
  let deleted = 0;
484
+ let currentIndex = crListReplica.cursorIndex ?? listIndex;
427
485
  while (current && deleted < deleteCount) {
428
486
  const next = current.next;
429
- change[current.index] = void 0;
430
- void deleteLinkedEntry(crListReplica, current, delta);
487
+ change[currentIndex] = void 0;
488
+ void crListReplica.index?.delete(currentIndex);
489
+ void deleteLiveEntry(crListReplica, current, delta);
431
490
  current = next;
491
+ currentIndex++;
432
492
  deleted++;
433
493
  }
434
494
  crListReplica.size = crListReplica.parentMap.size;
435
- while (current) {
436
- current.index -= deleted;
437
- current = current.next;
438
- }
495
+ crListReplica.cursor = current ?? crListReplica.cursor;
496
+ crListReplica.cursorIndex = current ? listIndex : crListReplica.cursor ? Math.max(0, crListReplica.size - 1) : void 0;
497
+ crListReplica.index = /* @__PURE__ */ new Map();
498
+ if (crListReplica.cursor && crListReplica.cursorIndex !== void 0)
499
+ void crListReplica.index.set(
500
+ crListReplica.cursorIndex,
501
+ crListReplica.cursor
502
+ );
439
503
  return { change, delta };
440
504
  }
441
505
 
@@ -447,22 +511,42 @@ function __merge(crListReplica, crListDelta) {
447
511
  const newTombsIndices = [];
448
512
  const change = {};
449
513
  let needsRelink = false;
514
+ if (Object.hasOwn(crListDelta, "values") && Array.isArray(crListDelta.values) && crListDelta.values.length === 1 && (!Object.hasOwn(crListDelta, "tombstones") || Array.isArray(crListDelta.tombstones) && crListDelta.tombstones.length === 0)) {
515
+ const linkedListEntry = materializeSnapshotEntry(
516
+ crListDelta.values[0],
517
+ crListReplica
518
+ );
519
+ if (!linkedListEntry) return false;
520
+ const predecessor = linkedListEntry.predecessor === "\0" ? void 0 : crListReplica.parentMap.get(linkedListEntry.predecessor);
521
+ if (linkedListEntry.predecessor === "\0" && crListReplica.size === 0 || predecessor && !predecessor.next) {
522
+ linkedListEntry.prev = predecessor;
523
+ linkedListEntry.index = crListReplica.size;
524
+ if (predecessor) predecessor.next = linkedListEntry;
525
+ crListReplica.cursor = linkedListEntry;
526
+ crListReplica.cursorIndex = linkedListEntry.index;
527
+ void attachEntryToIndexes(crListReplica, linkedListEntry);
528
+ crListReplica.size = crListReplica.parentMap.size;
529
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
530
+ return { [linkedListEntry.index]: linkedListEntry.value };
531
+ }
532
+ }
450
533
  if (Object.hasOwn(crListDelta, "tombstones") && Array.isArray(crListDelta.tombstones)) {
451
534
  for (const tombstone of crListDelta.tombstones) {
452
535
  if (crListReplica.tombstones.has(tombstone) || !isUuidV73(tombstone))
453
536
  continue;
454
- crListReplica.tombstones.add(tombstone);
537
+ void crListReplica.tombstones.add(tombstone);
455
538
  const linkedListEntry = crListReplica.parentMap.get(tombstone);
456
539
  if (linkedListEntry) {
457
540
  void newTombsIndices.push(linkedListEntry.index);
458
- void deleteLinkedEntry(crListReplica, linkedListEntry);
541
+ void crListReplica.index?.delete(linkedListEntry.index);
542
+ void deleteLiveEntry(crListReplica, linkedListEntry);
459
543
  needsRelink = true;
460
544
  }
461
545
  }
462
546
  }
463
547
  if (!Object.hasOwn(crListDelta, "values") || !Array.isArray(crListDelta.values)) {
464
548
  if (newTombsIndices.length === 0) return false;
465
- void assertListIndices(crListReplica);
549
+ void rebuildLiveIndex(crListReplica);
466
550
  for (const index of newTombsIndices) {
467
551
  change[index] = void 0;
468
552
  }
@@ -472,7 +556,8 @@ function __merge(crListReplica, crListDelta) {
472
556
  if (valueEntry === null || valueEntry === void 0) continue;
473
557
  const existingEntry = crListReplica.parentMap.get(valueEntry.uuidv7);
474
558
  if (existingEntry) {
475
- if (crListReplica.tombstones.has(valueEntry.uuidv7) || !isUuidV73(valueEntry.predecessor) && valueEntry.predecessor !== "\0")
559
+ if (crListReplica.tombstones.has(valueEntry.uuidv7)) continue;
560
+ if (valueEntry.predecessor !== "\0" && !isUuidV73(valueEntry.predecessor))
476
561
  continue;
477
562
  if (existingEntry.predecessor >= valueEntry.predecessor) continue;
478
563
  void moveEntryToPredecessor(
@@ -483,41 +568,44 @@ function __merge(crListReplica, crListDelta) {
483
568
  needsRelink = true;
484
569
  continue;
485
570
  }
486
- const linkedListEntry = transformSnapshotEntryToStateEntry(
571
+ const linkedListEntry = materializeSnapshotEntry(
487
572
  valueEntry,
488
573
  crListReplica
489
574
  );
490
575
  if (!linkedListEntry) continue;
491
576
  const predecessor = linkedListEntry.predecessor === "\0" ? void 0 : crListReplica.parentMap.get(linkedListEntry.predecessor);
492
- void updateEntryToMaps(crListReplica, linkedListEntry);
577
+ void attachEntryToIndexes(crListReplica, linkedListEntry);
493
578
  void newVals.push(linkedListEntry);
494
579
  if (!needsRelink && linkedListEntry.predecessor === "\0") {
495
580
  if (crListReplica.size === 0) {
496
581
  crListReplica.cursor = linkedListEntry;
582
+ crListReplica.cursorIndex = linkedListEntry.index;
497
583
  crListReplica.size = crListReplica.parentMap.size;
584
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
498
585
  } else {
499
586
  needsRelink = true;
500
587
  }
501
588
  } else if (!needsRelink && predecessor && !predecessor.next) {
502
589
  linkedListEntry.prev = predecessor;
503
- linkedListEntry.index = predecessor.index + 1;
590
+ linkedListEntry.index = crListReplica.size;
504
591
  predecessor.next = linkedListEntry;
505
592
  crListReplica.cursor = linkedListEntry;
593
+ crListReplica.cursorIndex = linkedListEntry.index;
506
594
  crListReplica.size = crListReplica.parentMap.size;
595
+ void crListReplica.index?.set(linkedListEntry.index, linkedListEntry);
507
596
  } else {
508
597
  needsRelink = true;
509
598
  }
510
599
  }
511
600
  if (needsRelink) {
512
- void flattenAndLinkTrustedState(crListReplica);
513
- void assertListIndices(crListReplica);
601
+ void rebuildLiveProjection(crListReplica);
514
602
  }
515
603
  if (newTombsIndices.length === 0 && newVals.length === 0) return false;
516
604
  for (const index of newTombsIndices) {
517
605
  change[index] = void 0;
518
606
  }
519
607
  for (const val of newVals) {
520
- change[val.index] = structuredClone(val.value);
608
+ change[val.index] = val.value;
521
609
  }
522
610
  return change;
523
611
  }
@@ -525,7 +613,7 @@ function __merge(crListReplica, crListDelta) {
525
613
  // src/core/mags/acknowledge/index.ts
526
614
  function __acknowledge(crListReplica) {
527
615
  let largest = false;
528
- crListReplica.tombstones.forEach((tombstone) => {
616
+ void crListReplica.tombstones.forEach((tombstone) => {
529
617
  if (largest === false || largest < tombstone) largest = tombstone;
530
618
  });
531
619
  if (typeof largest === "string") return largest;
@@ -536,12 +624,12 @@ function __acknowledge(crListReplica) {
536
624
  import { isUuidV7 as isUuidV74 } from "@sovereignbase/utils";
537
625
  function __garbageCollect(frontiers, crListReplica) {
538
626
  if (!Array.isArray(frontiers)) return;
539
- frontiers.sort();
627
+ void frontiers.sort();
540
628
  const smallest = frontiers.find((frontier) => isUuidV74(frontier));
541
629
  if (typeof smallest !== "string") return;
542
- crListReplica.tombstones.forEach((tombstone, __, tombstones) => {
630
+ void crListReplica.tombstones.forEach((tombstone, __, tombstones) => {
543
631
  if (tombstone <= smallest) {
544
- tombstones.delete(tombstone);
632
+ void tombstones.delete(tombstone);
545
633
  }
546
634
  });
547
635
  }
@@ -554,7 +642,7 @@ function __snapshot(crListReplica) {
554
642
  if (!linkedListEntry) throw new CRListError("LIST_INTEGRITY_VIOLATION");
555
643
  return {
556
644
  uuidv7: linkedListEntry.uuidv7,
557
- value: structuredClone(linkedListEntry.value),
645
+ value: linkedListEntry.value,
558
646
  predecessor: linkedListEntry.predecessor
559
647
  };
560
648
  }
@@ -566,12 +654,12 @@ function __snapshot(crListReplica) {
566
654
  // src/CRList/class.ts
567
655
  var CRList = class {
568
656
  /**
569
- * Creates a replicated list from an optional detached structured-clone-compatible snapshot.
657
+ * Creates a replicated list from an optional CRList snapshot.
570
658
  *
571
659
  * @param snapshot - A previously emitted CRList snapshot.
572
660
  */
573
661
  constructor(snapshot) {
574
- Object.defineProperties(this, {
662
+ void Object.defineProperties(this, {
575
663
  state: {
576
664
  value: __create(snapshot),
577
665
  enumerable: false,
@@ -604,13 +692,9 @@ var CRList = class {
604
692
  if (!result) return false;
605
693
  const { delta, change } = result;
606
694
  if (delta)
607
- void target.eventTarget.dispatchEvent(
608
- new CustomEvent("delta", { detail: delta })
609
- );
695
+ void dispatchCRListEvent(target.eventTarget, "delta", delta);
610
696
  if (change)
611
- void target.eventTarget.dispatchEvent(
612
- new CustomEvent("change", { detail: change })
613
- );
697
+ void dispatchCRListEvent(target.eventTarget, "change", change);
614
698
  return true;
615
699
  } catch (error) {
616
700
  if (error instanceof CRListError) throw error;
@@ -624,16 +708,10 @@ var CRList = class {
624
708
  const result = __delete(target.state, listIndex, listIndex + 1);
625
709
  if (!result) return false;
626
710
  const { delta, change } = result;
627
- if (delta) {
628
- void target.eventTarget.dispatchEvent(
629
- new CustomEvent("delta", { detail: delta })
630
- );
631
- }
632
- if (change) {
633
- void target.eventTarget.dispatchEvent(
634
- new CustomEvent("change", { detail: change })
635
- );
636
- }
711
+ if (delta)
712
+ void dispatchCRListEvent(target.eventTarget, "delta", delta);
713
+ if (change)
714
+ void dispatchCRListEvent(target.eventTarget, "change", change);
637
715
  return true;
638
716
  } catch (error) {
639
717
  if (error instanceof CRListError) throw error;
@@ -678,14 +756,8 @@ var CRList = class {
678
756
  const result = __update(beforeIndex ?? 0, [value], this.state, "before");
679
757
  if (!result) return;
680
758
  const { delta, change } = result;
681
- if (delta)
682
- void this.eventTarget.dispatchEvent(
683
- new CustomEvent("delta", { detail: delta })
684
- );
685
- if (change)
686
- void this.eventTarget.dispatchEvent(
687
- new CustomEvent("change", { detail: change })
688
- );
759
+ if (delta) void dispatchCRListEvent(this.eventTarget, "delta", delta);
760
+ if (change) void dispatchCRListEvent(this.eventTarget, "change", change);
689
761
  }
690
762
  /**
691
763
  * Inserts a value after an index.
@@ -704,14 +776,8 @@ var CRList = class {
704
776
  );
705
777
  if (!result) return;
706
778
  const { delta, change } = result;
707
- if (delta)
708
- void this.eventTarget.dispatchEvent(
709
- new CustomEvent("delta", { detail: delta })
710
- );
711
- if (change)
712
- void this.eventTarget.dispatchEvent(
713
- new CustomEvent("change", { detail: change })
714
- );
779
+ if (delta) void dispatchCRListEvent(this.eventTarget, "delta", delta);
780
+ if (change) void dispatchCRListEvent(this.eventTarget, "change", change);
715
781
  }
716
782
  /**
717
783
  * Removes the entry at an index.
@@ -722,28 +788,26 @@ var CRList = class {
722
788
  const result = __delete(this.state, index, index + 1);
723
789
  if (!result) return;
724
790
  const { delta, change } = result;
725
- if (delta)
726
- void this.eventTarget.dispatchEvent(
727
- new CustomEvent("delta", { detail: delta })
728
- );
729
- if (change)
730
- void this.eventTarget.dispatchEvent(
731
- new CustomEvent("change", { detail: change })
732
- );
791
+ if (delta) void dispatchCRListEvent(this.eventTarget, "delta", delta);
792
+ if (change) void dispatchCRListEvent(this.eventTarget, "change", change);
733
793
  }
734
794
  /**
735
- * Returns the first live value copy matching a predicate in index order.
795
+ * Returns the first live value matching a predicate in index order.
736
796
  *
737
- * Predicate values are detached copies, so mutating them does not mutate the
738
- * list.
797
+ * Predicate values are live references. Mutating them directly can mutate the
798
+ * list without emitting a delta.
739
799
  *
740
- * @param predicate - Function to test each value copy.
800
+ * @param predicate - Function to test each live value.
741
801
  * @param thisArg - Optional `this` value for the predicate.
742
802
  */
743
803
  find(predicate, thisArg) {
804
+ let linkedListEntry = this.state.index?.get(0) ?? this.state.cursor;
805
+ while (linkedListEntry?.prev) linkedListEntry = linkedListEntry.prev;
744
806
  let index = 0;
745
- for (const value of this) {
746
- if (predicate.call(thisArg, value, index, this)) return value;
807
+ while (linkedListEntry) {
808
+ if (predicate.call(thisArg, linkedListEntry.value, index, this))
809
+ return linkedListEntry.value;
810
+ linkedListEntry = linkedListEntry.next;
747
811
  index++;
748
812
  }
749
813
  return void 0;
@@ -757,20 +821,14 @@ var CRList = class {
757
821
  */
758
822
  merge(delta) {
759
823
  const change = __merge(this.state, delta);
760
- if (change)
761
- void this.eventTarget.dispatchEvent(
762
- new CustomEvent("change", { detail: change })
763
- );
824
+ if (change) void dispatchCRListEvent(this.eventTarget, "change", change);
764
825
  }
765
826
  /**
766
827
  * Emits an acknowledgement frontier for currently retained tombstones.
767
828
  */
768
829
  acknowledge() {
769
830
  const ack = __acknowledge(this.state);
770
- if (ack)
771
- void this.eventTarget.dispatchEvent(
772
- new CustomEvent("ack", { detail: ack })
773
- );
831
+ if (ack) void dispatchCRListEvent(this.eventTarget, "ack", ack);
774
832
  }
775
833
  /**
776
834
  * Garbage-collects tombstones that are covered by acknowledgement frontiers.
@@ -781,14 +839,15 @@ var CRList = class {
781
839
  void __garbageCollect(frontiers, this.state);
782
840
  }
783
841
  /**
784
- * Emits the current detached structured-clone-compatible list snapshot.
842
+ * Emits the current CRList snapshot.
843
+ *
844
+ * Snapshot value payloads are live references. Mutating them can mutate
845
+ * replica state without emitting a delta.
785
846
  */
786
847
  snapshot() {
787
848
  const snapshot = __snapshot(this.state);
788
849
  if (snapshot)
789
- void this.eventTarget.dispatchEvent(
790
- new CustomEvent("snapshot", { detail: snapshot })
791
- );
850
+ void dispatchCRListEvent(this.eventTarget, "snapshot", snapshot);
792
851
  }
793
852
  /**
794
853
  * Registers an event listener.
@@ -798,7 +857,7 @@ var CRList = class {
798
857
  * @param options - Listener registration options.
799
858
  */
800
859
  addEventListener(type, listener, options) {
801
- this.eventTarget.addEventListener(
860
+ void this.eventTarget.addEventListener(
802
861
  type,
803
862
  listener,
804
863
  options
@@ -812,14 +871,17 @@ var CRList = class {
812
871
  * @param options - Listener removal options.
813
872
  */
814
873
  removeEventListener(type, listener, options) {
815
- this.eventTarget.removeEventListener(
874
+ void this.eventTarget.removeEventListener(
816
875
  type,
817
876
  listener,
818
877
  options
819
878
  );
820
879
  }
821
880
  /**
822
- * Returns a detached structured-clone-compatible snapshot of this list.
881
+ * Returns a CRList snapshot of this list.
882
+ *
883
+ * Snapshot value payloads are live references. Mutating them can mutate
884
+ * replica state without emitting a delta.
823
885
  *
824
886
  * Called automatically by `JSON.stringify`.
825
887
  */
@@ -847,7 +909,7 @@ var CRList = class {
847
909
  return this.toJSON();
848
910
  }
849
911
  /**
850
- * Iterates over detached copies of the current live values in index order.
912
+ * Iterates over current live values in index order.
851
913
  */
852
914
  *[Symbol.iterator]() {
853
915
  for (let index = 0; index < this.size; index++) {
@@ -856,17 +918,17 @@ var CRList = class {
856
918
  }
857
919
  }
858
920
  /**
859
- * Calls a function once for each live value copy in index order.
921
+ * Calls a function once for each live value in index order.
860
922
  *
861
- * Callback values are detached copies, so mutating them does not mutate the
862
- * list.
923
+ * Callback values are live references. Mutating them directly can mutate the
924
+ * list without emitting a delta.
863
925
  *
864
- * @param callback - Function to call for each value copy.
926
+ * @param callback - Function to call for each live value.
865
927
  * @param thisArg - Optional `this` value for the callback.
866
928
  */
867
929
  forEach(callback, thisArg) {
868
930
  for (let index = 0; index < this.size; index++) {
869
- callback.call(thisArg, this[index], index, this);
931
+ void callback.call(thisArg, this[index], index, this);
870
932
  }
871
933
  }
872
934
  };