@sovereignbase/convergent-replicated-list 1.0.9 → 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/README.md CHANGED
@@ -7,9 +7,8 @@
7
7
 
8
8
  Convergent Replicated List (CR-List), a delta CRDT for an ordered sequence of entries.
9
9
 
10
- Read the specification:
11
-
12
- - https://sovereignbase.dev/convergent-replicated-list
10
+ - [Check the docs](https://sovereignbase.dev/convergent-replicated-list/docs/)
11
+ - [Read the specification](https://sovereignbase.dev/convergent-replicated-list/)
13
12
 
14
13
  ## Compatibility
15
14
 
@@ -146,6 +145,11 @@ list.forEach((value, index, target) => {
146
145
  console.log(index, value, target.size)
147
146
  })
148
147
 
148
+ const found = list.find((value, index, target) => {
149
+ return index === 1 && target.size === 3 && value === 'up'
150
+ })
151
+
152
+ console.log(found) // 'up'
149
153
  console.log([...restored]) // ['What is', 'up', 'dude!']
150
154
  ```
151
155
 
@@ -154,9 +158,9 @@ This example assumes your list values are JSON-compatible. For general
154
158
  snapshots with a structured-clone-capable store or an application-level codec
155
159
  instead of plain `JSON.stringify` / `JSON.parse`.
156
160
 
157
- Numeric reads, `for...of`, and `forEach()` return detached copies of visible
158
- values. Mutating those returned values does not mutate the underlying replica
159
- state.
161
+ Numeric reads, `for...of`, `find()`, and `forEach()` return detached copies of
162
+ visible values. Mutating those returned values does not mutate the underlying
163
+ replica state.
160
164
 
161
165
  ### Acknowledgements and garbage collection
162
166
 
@@ -260,8 +264,8 @@ Ingress stays tolerant:
260
264
  - `toJSON()` returns a detached structured-clone snapshot.
261
265
  - `JSON.stringify()` and `toString()` are only reliable when list values are
262
266
  JSON-compatible.
263
- - Numeric reads, `for...of`, and `forEach()` expose detached copies of visible values rather than mutable references into replica state.
264
- - `for...of`, `forEach()`, numeric indexing, `append()`, `prepend()`, `remove()`, `merge()`, `snapshot()`, `acknowledge()`, and `garbageCollect()` all operate on the live list projection.
267
+ - Numeric reads, `for...of`, `find()`, and `forEach()` expose detached copies of visible values rather than mutable references into replica state.
268
+ - `for...of`, `find()`, `forEach()`, numeric indexing, `append()`, `prepend()`, `remove()`, `merge()`, `snapshot()`, `acknowledge()`, and `garbageCollect()` all operate on the live list projection.
265
269
 
266
270
  ### Convergence and compaction
267
271
 
@@ -279,7 +283,7 @@ npm run test
279
283
  What the current test suite covers:
280
284
 
281
285
  - Coverage on built `dist/**/*.js`: `100%` statements, `100%` branches, `100%` functions, and `100%` lines, together with focused source-coverage tests for helper edge paths.
282
- - Public `CRList` surface: indexing, iteration, `forEach`, proxy traps, events, JSON/inspect behavior.
286
+ - Public `CRList` surface: indexing, iteration, `find`, `forEach`, proxy traps, events, JSON/inspect behavior.
283
287
  - Core edge paths and malicious ingress handling for `__create`, `__read`, `__update`, `__delete`, `__merge`, `__snapshot`, `__acknowledge`, and `__garbageCollect`.
284
288
  - Internal defensive branches under intentionally corrupt in-memory replica state.
285
289
  - Integration convergence stress for:
@@ -309,29 +313,58 @@ npm run bench
309
313
 
310
314
  Last measured on Node `v22.14.0` (`win32 x64`):
311
315
 
312
- | group | scenario | n | ops | ms | ms/op | ops/sec |
313
- | ------- | ------------------------------------- | ----: | --: | -------: | ----: | ---------: |
314
- | `crud` | `create / hydrate snapshot` | 5,000 | 250 | 8,817.80 | 35.27 | 28.35 |
315
- | `crud` | `read / random indexed reads` | 5,000 | 250 | 12.59 | 0.05 | 19,854.03 |
316
- | `crud` | `update / append after tail` | 5,000 | 250 | 2.26 | 0.01 | 110,507.01 |
317
- | `crud` | `update / insert before middle` | 5,000 | 250 | 22.27 | 0.09 | 11,224.10 |
318
- | `crud` | `update / overwrite random` | 5,000 | 250 | 13.95 | 0.06 | 17,919.61 |
319
- | `crud` | `delete / single deletes from middle` | 5,000 | 250 | 23.21 | 0.09 | 10,772.98 |
320
- | `crud` | `delete / range deletes` | 5,000 | 250 | 6.32 | 0.03 | 39,562.60 |
321
- | `mags` | `snapshot` | 5,000 | 250 | 5,354.28 | 21.42 | 46.69 |
322
- | `mags` | `acknowledge` | 5,000 | 250 | 47.24 | 0.19 | 5,291.76 |
323
- | `mags` | `garbage collect` | 5,000 | 250 | 163.55 | 0.65 | 1,528.59 |
324
- | `mags` | `merge ordered deltas` | 5,000 | 250 | 19.25 | 0.08 | 12,986.74 |
325
- | `mags` | `merge shuffled gossip` | 5,000 | 250 | 485.99 | 1.94 | 514.41 |
326
- | `class` | `constructor / hydrate snapshot` | 5,000 | 250 | 8,119.67 | 32.48 | 30.79 |
327
- | `class` | `append after tail` | 5,000 | 250 | 2.87 | 0.01 | 87,232.63 |
328
- | `class` | `prepend before middle` | 5,000 | 250 | 7.76 | 0.03 | 32,228.95 |
329
- | `class` | `remove from middle` | 5,000 | 250 | 5.20 | 0.02 | 48,084.32 |
330
- | `class` | `snapshot` | 5,000 | 250 | 5,842.47 | 23.37 | 42.79 |
331
- | `class` | `acknowledge` | 5,000 | 250 | 127.77 | 0.51 | 1,956.68 |
332
- | `class` | `garbage collect` | 5,000 | 250 | 323.20 | 1.29 | 773.51 |
333
- | `class` | `merge ordered deltas` | 5,000 | 250 | 8.61 | 0.03 | 29,021.17 |
334
- | `class` | `merge shuffled gossip` | 5,000 | 250 | 523.53 | 2.09 | 477.53 |
316
+ | group | scenario | n | ops | crlist ms | crlist ms/op | crlist ops/sec | yjs ms/op | yjs ops/sec | json-joy ms/op | json-joy ops/sec | automerge ms/op | automerge ops/sec | winner |
317
+ |---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
318
+ | crud | create / hydrate snapshot | 5,000 | 250 | 2,096.39 | 8.39 | 119.25 | 15.07 | 66.34 | 19.3 | 51.82 | 232.99 | 4.29 | crlist |
319
+ | crud | read / random indexed reads | 5,000 | 250 | 0.35 | 0 | 712,047.85 | 0 | 213,949.51 | 0.01 | 72,573.15 | 0 | 1,487,209.99 | automerge |
320
+ | crud | update / append after tail | 5,000 | 250 | 3.75 | 0.01 | 66,712.92 | 0.04 | 26,447.75 | 0.03 | 34,200.66 | 2.79 | 358 | crlist |
321
+ | crud | update / insert before middle | 5,000 | 250 | 3.97 | 0.02 | 62,962.78 | 0.02 | 47,542.98 | 0.02 | 59,963.54 | 2.77 | 360.61 | crlist |
322
+ | crud | update / insert at head | 5,000 | 250 | 1.53 | 0.01 | 163,302.63 | 0.01 | 77,939.89 | 0.02 | 51,651.83 | 2.58 | 387.17 | crlist |
323
+ | crud | update / overwrite random | 5,000 | 250 | 3.77 | 0.02 | 66,267.3 | 0.07 | 14,168.64 | 0.05 | 19,413.25 | 2.91 | 343.26 | crlist |
324
+ | crud | delete / single deletes from middle | 5,000 | 250 | 1.94 | 0.01 | 129,098.89 | 0.03 | 31,265.24 | 0.13 | 7,665.23 | 0.42 | 2,369.86 | crlist |
325
+ | crud | delete / range deletes | 5,000 | 250 | 5.19 | 0.02 | 48,199.28 | 0.04 | 24,172.81 | 0.25 | 3,945.2 | 0.77 | 1,295.13 | crlist |
326
+ | mags | snapshot | 5,000 | 250 | 65.59 | 0.26 | 3,811.53 | 8.41 | 118.88 | 14.09 | 70.97 | 19.99 | 50.02 | crlist |
327
+ | mags | acknowledge | 5,000 | 250 | 76.25 | 0.31 | 3,278.61 | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
328
+ | mags | garbage collect | 5,000 | 250 | 164.23 | 0.66 | 1,522.22 | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
329
+ | mags | merge ordered deltas | 5,000 | 250 | 4.13 | 0.02 | 60,460.95 | 0.06 | 17,959 | 0.02 | 58,147.65 | 4.68 | 213.68 | crlist |
330
+ | mags | merge shuffled gossip | 5,000 | 250 | 478.37 | 1.91 | 522.61 | 0.64 | 1,555.98 | 0.09 | 11,595.98 | 0.41 | 2,456.81 | json-joy |
331
+ | class | constructor / hydrate snapshot | 5,000 | 250 | 1,886.2 | 7.54 | 132.54 | 12.85 | 77.8 | 18.34 | 54.51 | 211.32 | 4.73 | crlist |
332
+ | class | append after tail | 5,000 | 250 | 3.09 | 0.01 | 80,992.65 | 0.02 | 52,369.18 | 0.02 | 53,936.27 | 2.09 | 479.59 | crlist |
333
+ | class | prepend before middle | 5,000 | 250 | 7.79 | 0.03 | 32,077.6 | 0.01 | 81,163.56 | 0.01 | 80,744.14 | 2.68 | 372.46 | yjs |
334
+ | class | remove from middle | 5,000 | 250 | 1.82 | 0.01 | 137,287.2 | 0.03 | 37,143.24 | 0.03 | 35,865.43 | 0.57 | 1,761.16 | crlist |
335
+ | class | find near tail | 5,000 | 250 | 33.64 | 0.13 | 7,431.54 | 0.44 | 2,276.89 | 5.13 | 195.02 | 0.03 | 38,407.18 | automerge |
336
+ | class | snapshot | 5,000 | 250 | 83.09 | 0.33 | 3,008.93 | 7.99 | 125.19 | 14.86 | 67.28 | 19.63 | 50.93 | crlist |
337
+ | class | acknowledge | 5,000 | 250 | 46.72 | 0.19 | 5,351.54 | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
338
+ | class | garbage collect | 5,000 | 250 | 156.68 | 0.63 | 1,595.56 | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
339
+ | class | merge ordered deltas | 5,000 | 250 | 2.43 | 0.01 | 102,720.03 | 0.04 | 24,436.26 | 0.01 | 79,953.95 | 4.08 | 244.99 | crlist |
340
+ | class | merge shuffled gossip | 5,000 | 250 | 265.96 | 1.06 | 940 | 0.72 | 1,384.5 | 0.01 | 73,120.8 | 0.38 | 2,648.01 | json-joy |
341
+ |
342
+
343
+ These benchmarks compare the work a JavaScript consumer asks each library to do:
344
+ hydrate state, read indexed values, mutate list position, emit or apply deltas,
345
+ and materialize snapshots. CRList is strongest where its live linked projection
346
+ and index cache can be updated incrementally, especially local CRUD, snapshot
347
+ hydration, snapshots, and ordered append deltas.
348
+
349
+ The shuffled-gossip costs more than
350
+ json-joy because CRList immediately maintains a JS live projection and returns
351
+ index-keyed change patches from every merge.
352
+
353
+ json-joy's benchmark path applies compact JSON CRDT patches directly to its
354
+ model, so shuffled patch application is extremely cheap in this scenario. That
355
+ does not prove a weaker convergence model by itself, but it is a different
356
+ tradeoff from CRList's immediate event/change surface.
357
+
358
+ Yjs integrates updates
359
+ into a mature struct store with pending update/delete-set handling, which keeps
360
+ out-of-order gossip relatively cheap.
361
+
362
+ Automerge delegates change application and
363
+ indexed reads to its WASM-backed document store and lazy proxies, explaining its
364
+ very fast random reads and `find` path; its local writes are slower here because
365
+ each write goes through immutable document changes and change generation.
366
+
367
+ Analysis by ChatGPT-5.5.
335
368
 
336
369
  ## License
337
370