@sovereignbase/sequencer 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
+ [![npm version](https://img.shields.io/npm/v/@sovereignbase/sequencer)](https://www.npmjs.com/package/@sovereignbase/sequencer)
2
+ [![JSR](https://jsr.io/badges/@sovereignbase/sequencer)](https://jsr.io/@sovereignbase/sequencer)
3
+ [![CI](https://github.com/sovereignbase/sequencer/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/sovereignbase/sequencer/actions/workflows/ci.yaml)
4
+ [![codecov](https://codecov.io/gh/sovereignbase/sequencer/branch/master/graph/badge.svg)](https://codecov.io/gh/sovereignbase/sequencer)
5
+ [![license](https://img.shields.io/npm/l/@sovereignbase/sequencer)](LICENSE)
6
+
1
7
  # Sequencer
2
8
 
3
- Sequencer provides a deterministic total ordering for distributed data. It allows independently operating replicas to make concurrent changes and later converge on the same state without relying on network arrival order or perfectly synchronized clocks.
9
+ Sequencer provides a deterministic total ordering for distributed data. It allows independently operating actors to make concurrent changes and eventually converge on the same state without relying on network arrival order or perfectly synchronized clocks.
4
10
 
5
11
  ## Usage
6
12
 
@@ -10,128 +16,160 @@ Sequencer provides a deterministic total ordering for distributed data. It allow
10
16
  import { Projection } from '@sovereignbase/sequencer'
11
17
 
12
18
  const projection = new Projection<string>(1)
13
- const gossip = projection.insert(['Hello World'], 0)
19
+
20
+ const gossip0 = projection.insert(['Hello World'], 0)
14
21
  console.log(projection.value(0)) // "Hello World"
22
+ const gossip1 = projection.replace(['Hello Dude'], 0, 0)
23
+ console.log(projection.value(0)) // "Hello Dude"
24
+ const gossip2 = projection.remove(0, 0)
25
+ console.log(projection.value(0)) // undefined
15
26
 
16
27
  projection.destroy()
17
28
  ```
18
29
 
30
+ ### See documentation
31
+
32
+ - [How sequencer works?](/INTERNALS.md)
33
+ - [Typescript](https://sovereignbase.dev/sequencer/)
34
+ - [C++](https://sovereignbase.dev/sequencer/c++)
35
+
19
36
  ## Benchmarks
20
37
 
21
- ### Exceptional performance
38
+ ### Moderate bundle size
22
39
 
23
- The [benchmark documentation](benchmark/README.md) describes both the dynamic
24
- lifecycle workload and API throughput bursts. Full results are available for
25
- [lifecycle](benchmark/.results/lifecycle.md) and
26
- [throughput](benchmark/.results/throughput.md).
40
+ | Format | Raw (KiB) | Minified (KiB) | Minified + gzip (KiB) |
41
+ | -------- | --------: | -------------: | --------------------: |
42
+ | ESM | 123.83 | 100.04 | 36.07 |
43
+ | CommonJS | 123.94 | 100.86 | 36.28 |
27
44
 
28
- Latest throughput results, measured on 12 October 2026. Latencies are in µs per
29
- public API call, weighted by call count across five runs.
45
+ Both bundles include WASM. Terser uses three compression passes; gzip uses level 9.
30
46
 
31
- | Operation | 100 initial insertions | 1,000 initial insertions | 10,000 initial insertions |
32
- | ----------------------------------- | ---------------------: | -----------------------: | ------------------------: |
33
- | `new Projection(actorID, sequence)` | 1.732 | 1.725 | 1.648 |
34
- | `insert`, random position | 1.043 | 1.036 | 1.081 |
35
- | `remove`, middle | 1.091 | 1.269 | 1.433 |
36
- | `replace`, middle | 1.145 | 1.369 | 1.430 |
37
- | `sequence()` | 0.353 | 0.390 | 0.363 |
38
- | `value`, random position | 0.065 | 0.053 | 0.115 |
39
- | `values()` | 4.352 | 147.965 | 1156.844 |
47
+ ### Average throughput during editing to checkpoint
40
48
 
41
- Environment: Node 24.16.0, V8 13.6.233.17-node.49, Windows x64, Intel Core
42
- i5-10210U. Generated Strip lengths are 1–100 Frames. Each case warms up for
43
- 64 calls, then measures a burst of approximately 100 ms or at most 10,000 calls.
49
+ Arithmetic means of 3 runs, in ops/s, from each phase's start to checkpoint.
50
+ Strips contain 1–100 Frames (visible entries). Headers show Strip counts and mean Frame counts.
44
51
 
45
- ## Memory efficiency
52
+ **↑ Scale up (0 → 25,000 Strips)**
46
53
 
47
- Retained-memory comparison for eight Projections, each containing 6,000
48
- single-Frame insertions. These are five-run medians from isolated Node processes:
54
+ | Method / case | 1,000 Strips<br>50,871 Frames | 5,000 Strips<br>252,214 Frames | 10,000 Strips<br>504,871 Frames | 15,000 Strips<br>758,280 Frames | 20,000 Strips<br>1,010,749 Frames | 25,000 Strips<br>1,264,003 Frames |
55
+ | -------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
56
+ | apply.random | 146,512 | 50,162 | 35,425 | 25,372 | 17,992 | 13,401 |
57
+ | insert.head | 1,103,240 | 627,952 | 723,841 | 741,193 | 692,500 | 629,581 |
58
+ | insert.random | 423,106 | 138,442 | 91,906 | 64,213 | 44,063 | 33,092 |
59
+ | insert.tail | 1,247,342 | 779,594 | 895,027 | 892,070 | 830,660 | 765,706 |
60
+ | remove.head | — | — | — | — | — | — |
61
+ | remove.random | 258,681 | 95,210 | 65,861 | 46,668 | 31,655 | 23,556 |
62
+ | remove.tail | — | — | — | — | — | — |
63
+ | replace.head | 380,869 | 309,212 | 303,788 | 291,544 | 270,032 | 245,792 |
64
+ | replace.random | 253,774 | 94,640 | 63,352 | 45,272 | 31,633 | 23,736 |
65
+ | replace.tail | 392,296 | 315,718 | 313,645 | 310,052 | 268,791 | 247,351 |
66
+ | value.head | 2,283,059 | 2,095,440 | 1,999,974 | 2,016,602 | 1,907,994 | 1,721,581 |
67
+ | value.random | 569,971 | 133,693 | 78,867 | 51,725 | 34,540 | 25,316 |
68
+ | value.tail | 4,749,438 | 3,636,984 | 3,461,656 | 3,227,876 | 2,914,996 | 2,408,625 |
49
69
 
50
- | Measurement | Before extension coalescing | Current |
51
- | --------------------------- | --------------------------: | -------: |
52
- | Additional JS heap after GC | 2.038 MB | 0.520 MB |
53
- | Shared WASM memory capacity | 7.406 MB | 1.049 MB |
70
+ **↓ Scale down (25,000 → 0 Strips)**
54
71
 
55
- Module import and a small warm-up are excluded from the heap delta. WASM
56
- capacity includes the initial 1 MiB allocation. These are not process RSS or
57
- per-Projection total memory. Measurements and complete before/after results are
58
- retained in [the optimization report](archives/optimizations/flat_sequence/README.md).
72
+ | Method / case | 20,000 Strips<br>1,012,742 Frames | 15,000 Strips<br>755,860 Frames | 10,000 Strips<br>504,103 Frames | 5,000 Strips<br>251,203 Frames | 1,000 Strips<br>51,057 Frames | 0 Strips<br>0 Frames |
73
+ | -------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
74
+ | apply.random | 7,268 | 7,592 | 8,263 | 8,783 | 9,083 | 9,219 |
75
+ | insert.head | — | — | — | — | — | — |
76
+ | insert.random | 17,296 | 18,458 | 20,290 | 21,279 | 22,052 | 22,459 |
77
+ | insert.tail | — | — | — | — | — | — |
78
+ | remove.head | 157,981 | 165,917 | 180,514 | 178,894 | 181,826 | 183,709 |
79
+ | remove.random | 13,036 | 13,992 | 15,527 | 16,535 | 17,163 | 17,459 |
80
+ | remove.tail | 192,199 | 192,220 | 203,528 | 209,615 | 209,694 | 210,394 |
81
+ | replace.head | 176,953 | 178,240 | 186,921 | 185,389 | 181,259 | 161,071 |
82
+ | replace.random | 13,038 | 14,084 | 15,647 | 16,708 | 17,397 | 17,697 |
83
+ | replace.tail | 190,938 | 184,284 | 193,773 | 187,715 | 182,304 | 182,496 |
84
+ | value.head | 794,128 | 838,606 | 885,880 | 922,502 | 940,112 | 948,944 |
85
+ | value.random | 13,677 | 14,768 | 16,600 | 18,037 | 18,894 | 19,271 |
86
+ | value.tail | 1,070,897 | 1,038,948 | 1,093,604 | 1,140,368 | 1,178,006 | 1,192,164 |
59
87
 
60
- ### Moderate bundle size
88
+ ### API throughput at checkpoint
61
89
 
62
- | Format | Raw | Minified | Minified + gzip |
63
- | -------- | -------: | -------: | --------------: |
64
- | ESM | 117.9 kB | 99.7 kB | 36.4 kB |
65
- | CommonJS | 118.0 kB | 100.6 kB | 36.6 kB |
90
+ Checkpoint calls, in ops/s, averaged across runs.
66
91
 
67
- Both bundles include the WASM module. Minified sizes use Terser with three
68
- compression passes; source maps and type declarations are excluded. kB and MB
69
- use decimal units.
92
+ **↑ Scale up (0 → 25,000 Strips)**
70
93
 
71
- ### Compact data model
94
+ | Method | 1,000 Strips<br>50,871 Frames | 5,000 Strips<br>252,214 Frames | 10,000 Strips<br>504,871 Frames | 15,000 Strips<br>758,280 Frames | 20,000 Strips<br>1,010,749 Frames | 25,000 Strips<br>1,264,003 Frames |
95
+ | --------------------------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
96
+ | new Projection(actorID, sequence) | 2,513 | 795 | 408 | 215 | 154 | 107 |
97
+ | sequence() | 375 | 66 | 33 | 18 | 11 | 6 |
98
+ | values() | 480 | 163 | 108 | 70 | 49 | 32 |
72
99
 
73
- An insertion contains six uint32 structural words: 24 bytes before transport
74
- framing or content. Numeric metadata accepts `Uint32Array` and JSON numeric
75
- arrays. Generic content, called Footage, stays in JavaScript; only numeric
76
- metadata enters WASM.
100
+ **↓ Scale down (25,000 → 0 Strips)**
77
101
 
78
- `sequence()` returns `[words, footage]`: one numeric array and one content
79
- array, with no nested insertion or acknowledgement arrays. `words[0]` locates
80
- the first six-word insertion record; preceding acknowledgements are length
81
- prefixed. Positive insertion lengths identify successive spans of `footage`.
82
- An empty Sequence is `[[], []]`. Construction and `merge()` accept numeric
83
- arrays or `Uint32Array` metadata; exported numeric arrays can be sent as JSON.
102
+ | Method | 20,000 Strips<br>1,012,742 Frames | 15,000 Strips<br>755,860 Frames | 10,000 Strips<br>504,103 Frames | 5,000 Strips<br>251,203 Frames | 1,000 Strips<br>51,057 Frames | 0 Strips<br>0 Frames |
103
+ | --------------------------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
104
+ | new Projection(actorID, sequence) | 116 | 212 | 273 | 586 | 1,881 | 16,098 |
105
+ | sequence() | 7 | 6 | 5 | 4 | 4 | 38,383 |
106
+ | values() | 44 | 51 | 58 | 62 | 199 | 68,912 |
84
107
 
85
- ```ts
86
- const retained = projection.sequence()
87
- const restored = new Projection<string>(2, retained)
88
- ```
108
+ ### Sequence disk size at checkpoint
109
+
110
+ Complete exported Sequences, including Footage and acknowledgements. Sizes are in KiB.
89
111
 
90
- Consecutive local insertions extend an intact insertion from the same Session
91
- without reserving another zero in its logical clock. Gossip contains only the
92
- new suffix, still using six structural words. A removal or fragmentation ends
93
- the local extension chain. Retained snapshots remain unchanged by later edits.
112
+ **↑ Scale up (0 → 25,000 Strips)**
94
113
 
95
- Measured JSON transport sizes for 1,000 one-Frame insertions containing `'x'`,
96
- their removal Gossip, and a snapshot captured before removal:
114
+ | Encoding | 1,000 Strips<br>50,871 Frames | 5,000 Strips<br>252,214 Frames | 10,000 Strips<br>504,871 Frames | 15,000 Strips<br>758,280 Frames | 20,000 Strips<br>1,010,749 Frames | 25,000 Strips<br>1,264,003 Frames |
115
+ | ------------------ | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
116
+ | JSON | 277.17 | 1,621.65 | 3,261.72 | 4,901.02 | 7,041.25 | 9,162.75 |
117
+ | JSON + gzip | 14.59 | 73.84 | 150.38 | 226.94 | 311.22 | 391.69 |
118
+ | MessagePack | 164.81 | 818.88 | 1,639.69 | 3,529.41 | 5,081.04 | 6,483.22 |
119
+ | MessagePack + gzip | 12.38 | 61.62 | 124.91 | 198.59 | 263.54 | 332.77 |
97
120
 
98
- | Workload | Average bytes per operation or insertion | JSON | JSON + gzip | gzip reduction |
99
- | ---------------------------------------------- | ---------------------------------------: | ------: | ----------: | -------------: |
100
- | 1,000 one-frame insertions | 44.8 B | 45.8 kB | 5.1 kB | 88.9% |
101
- | 1,000 one-frame removals | 67.8 B | 68.8 kB | 7.7 kB | 88.9% |
102
- | Snapshot containing 1,000 one-frame insertions | 4.0 B | 4.0 kB | 0.1 kB | 98.3% |
121
+ **↓ Scale down (25,000 → 0 Strips)**
103
122
 
104
- Gossip sizes include acknowledgements and content where present. Delta averages
105
- measure individual Gossip packets; batch totals include their enclosing array.
106
- Snapshot averages divide the complete Sequence size by the 1,000 authored insertions. Consecutive insertions are retained as one insertion.
107
- Sizes vary with Session IDs and content.
123
+ | Encoding | 20,000 Strips<br>1,012,742 Frames | 15,000 Strips<br>755,860 Frames | 10,000 Strips<br>504,103 Frames | 5,000 Strips<br>251,203 Frames | 1,000 Strips<br>51,057 Frames | 0 Strips<br>0 Frames |
124
+ | ------------------ | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
125
+ | JSON | 7,446.44 | 5,617.36 | 3,769.76 | 1,885.51 | 383.23 | 0.02 |
126
+ | JSON + gzip | 323.23 | 244.45 | 163.91 | 80.61 | 16.33 | 0.03 |
127
+ | MessagePack | 5,237.17 | 3,930.33 | 2,628.38 | 1,312.43 | 266.67 | 0.01 |
128
+ | MessagePack + gzip | 274.96 | 207.78 | 137.38 | 67.41 | 13.67 | 0.03 |
129
+
130
+ ### Memory usage at checkpoint
131
+
132
+ One instance: WASM memory plus retained JS array slots, in KiB (1,024 bytes).
133
+
134
+ **↑ Scale up (0 → 25,000 Strips)**
135
+
136
+ | Measurement | 1,000 Strips<br>50,871 Frames | 5,000 Strips<br>252,214 Frames | 10,000 Strips<br>504,871 Frames | 15,000 Strips<br>758,280 Frames | 20,000 Strips<br>1,010,749 Frames | 25,000 Strips<br>1,264,003 Frames |
137
+ | ---------------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
138
+ | Estimated memory (KiB) | 5,343.67 | 25,100.58 | 50,470.89 | 82,701.27 | 98,036.35 | 153,462.93 |
139
+
140
+ **↓ Scale down (25,000 → 0 Strips)**
141
+
142
+ | Measurement | 20,000 Strips<br>1,012,742 Frames | 15,000 Strips<br>755,860 Frames | 10,000 Strips<br>504,103 Frames | 5,000 Strips<br>251,203 Frames | 1,000 Strips<br>51,057 Frames | 0 Strips<br>0 Frames |
143
+ | ---------------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
144
+ | Estimated memory (KiB) | 163,746.93 | 174,034.00 | 202,660.72 | 293,941.85 | 302,158.29 | 304,203.30 |
145
+
146
+ Measured on Node 24.16.0, V8 13.6.233.17-node.49,
147
+ win32 x64, Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz.
148
+ Peer synchronization, encoding and JS process garbage collection are outside API timings.
149
+ [Full lifecycle results](benchmark/.results/lifecycle.md) · [Throughput results](benchmark/.results/throughput.md).
108
150
 
109
151
  ## Tests
110
152
 
111
153
  ### Behaviour guaranteed by excessive tests
112
154
 
113
- `npm test` rebuilds the native WebAssembly module and TypeScript package,
114
- typechecks tests, runs unit, convergence, integration and generative stress suites
115
- with V8 coverage, then runs runtime and browser compatibility checks.
116
-
117
- The latest verification passed 167 Vitest tests and 88 compatibility checks.
118
- Convergence tests include 1,024 generated editing histories for each compaction
119
- mode. See the [test suites](test/README.md) and
155
+ The latest verification passed 178 Vitest tests and 88 compatibility checks (1 failed).
156
+ The configured 100% coverage threshold is met (100% statements, 100% branches, 100% functions, 100% lines).
157
+ See the [test suites](test/README.md) and
120
158
  [automated test report](https://sovereignbase.dev/sequencer/tests/).
121
159
 
122
160
  ### Works everywhere where JavaScript and Wasm works
123
161
 
124
162
  Runtimes covered by the latest compatibility verification:
125
163
 
126
- - Node.js, ESM and CommonJS
127
- - Deno
128
- - Bun
129
- - Edge Runtime
130
- - Cloudflare Workers through workerd
131
- - Chromium and WebKit browser windows
132
-
133
- Firefox is also in the compatibility matrix; its matching Playwright browser
134
- binary was unavailable in the latest run.
164
+ - Node.js, ESM: passed
165
+ - Node.js, CommonJS: passed
166
+ - Deno: passed
167
+ - Bun: passed
168
+ - Edge Runtime: passed
169
+ - Cloudflare Workers through workerd: passed
170
+ - Chromium browser windows: passed
171
+ - Firefox browser windows: failed (matching Playwright browser binary unavailable)
172
+ - WebKit browser windows: passed
135
173
 
136
174
  ### Tested realtime and offline text editing behaviour examples
137
175
 
@@ -143,13 +181,13 @@ binary was unavailable in the latest run.
143
181
 
144
182
  ## Credits
145
183
 
146
- None of this could have been done without years of work from many great engineers of many great projects, to mention a few:
184
+ This could not have been made without years of work from many great engineers of many great projects, to mention a few:
147
185
 
148
- - AUTOMERGE
186
+ - [AUTOMERGE](https://github.com/automerge)
149
187
 
150
- - YJS
188
+ - [YJS](https://github.com/yjs)
151
189
 
152
- - DIAMOND TYPES
190
+ - [DIAMOND TYPES](https://github.com/josephg/diamond-types)
153
191
 
154
192
  ## License
155
193