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