@sovereignbase/sequencer 0.0.2 → 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,133 +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
- Current throughput results, measured on 12 October 2026. Latencies are in µs per
29
- public API call, weighted by call count across three 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)` | 2.013 | 1.598 | 1.503 |
34
- | `insert`, random position | 1.108 | 1.045 | 1.076 |
35
- | `remove`, middle | 1.534 | 1.605 | 1.493 |
36
- | `replace`, middle | 1.507 | 1.364 | 1.625 |
37
- | `sequence()` | 0.288 | 0.320 | 0.331 |
38
- | `value`, random position | 0.090 | 0.082 | 0.155 |
39
- | `values()` | 4.787 | 174.213 | 1252.987 |
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
- 2,048 calls, then measures a burst of approximately 100 ms or at most 8,192 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 for eight Projections, each containing 6,000 consecutive
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 | Current |
51
- | --------------------------- | -------: |
52
- | Additional JS heap after GC | 0.501 MB |
53
- | Shared WASM memory capacity | 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.
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 |
58
87
 
59
- ### Moderate bundle size
88
+ ### API throughput at checkpoint
60
89
 
61
- | Format | Raw | Minified | Minified + gzip |
62
- | -------- | -------: | -------: | --------------: |
63
- | ESM | 120.9 kB | 101.6 kB | 36.8 kB |
64
- | CommonJS | 121.0 kB | 102.4 kB | 37.0 kB |
65
-
66
- Both bundles include the WASM module. Minified sizes use Terser with three
67
- compression passes; source maps and type declarations are excluded. kB and MB
68
- use decimal units.
69
-
70
- ### Compact data model
71
-
72
- An insertion contains six uint32 structural words: 24 bytes before transport
73
- framing or content. Numeric metadata accepts `Uint32Array` and JSON numeric
74
- arrays. Generic content, called Footage, stays in JavaScript; only numeric
75
- metadata enters WASM.
76
-
77
- `sequence()` returns `[words, footage]`: one numeric array and one content
78
- array, with no nested insertion or acknowledgement arrays. Small snapshots use
79
- six-word records after length-prefixed acknowledgements. From 64 retained
80
- insertions onward, predictable fields are omitted as WASM writes the snapshot;
81
- `[0, frontiersEnd, insertionCount]` identifies this compact numeric layout.
82
- Coordinates, acknowledgements and content order are preserved. The runtime
83
- reads both layouts; compact snapshots require an updated reader.
84
- Positive insertion lengths identify successive spans of `footage`.
85
- An empty Sequence is `[[], []]`. Construction and `merge()` accept numeric
86
- arrays or `Uint32Array` metadata; exported numeric arrays can be sent as JSON.
90
+ Checkpoint calls, in ops/s, averaged across runs.
87
91
 
88
- ```ts
89
- const retained = projection.sequence()
90
- const restored = new Projection<string>(2, retained)
91
- ```
92
+ **↑ Scale up (0 → 25,000 Strips)**
93
+
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 |
99
+
100
+ **↓ Scale down (25,000 → 0 Strips)**
101
+
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 |
92
107
 
93
- Consecutive local insertions extend an intact insertion from the same Session
94
- without reserving another zero in its logical clock. Gossip contains only the
95
- new suffix, still using six structural words. A removal or fragmentation ends
96
- the local extension chain. Retained snapshots remain unchanged by later edits.
108
+ ### Sequence disk size at checkpoint
97
109
 
98
- Measured JSON transport sizes for 1,000 one-Frame insertions containing `'x'`,
99
- their removal Gossip, and a snapshot captured before removal:
110
+ Complete exported Sequences, including Footage and acknowledgements. Sizes are in KiB.
100
111
 
101
- | Workload | Average bytes per operation or insertion | JSON | JSON + gzip | gzip reduction |
102
- | ---------------------------------------------- | ---------------------------------------: | ------: | ----------: | -------------: |
103
- | 1,000 one-frame insertions | 44.8 B | 45.8 kB | 5.1 kB | 88.9% |
104
- | 1,000 one-frame removals | 65.8 B | 66.8 kB | 7.5 kB | 88.8% |
105
- | Snapshot containing 1,000 one-frame insertions | 4.0 B | 4.0 kB | 0.1 kB | 98.3% |
112
+ **↑ Scale up (0 → 25,000 Strips)**
106
113
 
107
- Gossip sizes include acknowledgements and content where present. Delta averages
108
- measure individual Gossip packets; batch totals include their enclosing array.
109
- Snapshot averages divide the complete Sequence size by the 1,000 authored insertions. Consecutive insertions are retained as one insertion.
110
- Sizes vary with Session IDs and content.
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 |
120
+
121
+ **↓ Scale down (25,000 → 0 Strips)**
122
+
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).
111
150
 
112
151
  ## Tests
113
152
 
114
153
  ### Behaviour guaranteed by excessive tests
115
154
 
116
- `npm test` rebuilds the native WebAssembly module and TypeScript package,
117
- typechecks tests, runs unit, convergence, integration and generative stress suites
118
- with V8 coverage, then runs runtime and browser compatibility checks.
119
-
120
- The latest verification passed 170 Vitest tests and 88 compatibility checks.
121
- The configured 100% coverage threshold remains unmet (95.66% statements,
122
- 94.94% branches, 100% functions, 97.23% lines).
123
- Convergence tests include 1,024 generated editing histories for each compaction
124
- 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
125
158
  [automated test report](https://sovereignbase.dev/sequencer/tests/).
126
159
 
127
160
  ### Works everywhere where JavaScript and Wasm works
128
161
 
129
162
  Runtimes covered by the latest compatibility verification:
130
163
 
131
- - Node.js, ESM and CommonJS
132
- - Deno
133
- - Bun
134
- - Edge Runtime
135
- - Cloudflare Workers through workerd
136
- - Chromium and WebKit browser windows
137
-
138
- Firefox is also in the compatibility matrix; its matching Playwright browser
139
- 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
140
173
 
141
174
  ### Tested realtime and offline text editing behaviour examples
142
175
 
@@ -148,13 +181,13 @@ binary was unavailable in the latest run.
148
181
 
149
182
  ## Credits
150
183
 
151
- 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:
152
185
 
153
- - AUTOMERGE
186
+ - [AUTOMERGE](https://github.com/automerge)
154
187
 
155
- - YJS
188
+ - [YJS](https://github.com/yjs)
156
189
 
157
- - DIAMOND TYPES
190
+ - [DIAMOND TYPES](https://github.com/josephg/diamond-types)
158
191
 
159
192
  ## License
160
193