@sovereignbase/sequencer 0.0.2 → 1.0.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/INTERNALS.md +897 -0
- package/LICENSE +201 -201
- package/README.md +146 -99
- package/dist/index.cjs +200 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +143 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -114
- package/dist/index.js.map +1 -1
- package/package.json +23 -23
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@sovereignbase/sequencer)
|
|
2
|
+
[](https://jsr.io/@sovereignbase/sequencer)
|
|
3
|
+
[](https://github.com/sovereignbase/sequencer/actions/workflows/ci.yaml)
|
|
4
|
+
[](https://codecov.io/gh/sovereignbase/sequencer)
|
|
5
|
+
[](https://github.com/sovereignbase/sequencer/blob/master/LICENSE)
|
|
6
|
+
|
|
1
7
|
# Sequencer
|
|
2
8
|
|
|
3
|
-
Sequencer provides a deterministic total ordering for distributed data. It allows independently operating
|
|
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,174 @@ 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
|
-
|
|
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
|
+
|
|
36
|
+
### Errors
|
|
37
|
+
|
|
38
|
+
Library-reported failures throw `SequencerError`, with a stable `SequencerErrorCode`
|
|
39
|
+
and a message prefixed with `{@sovereignbase/sequencer}`. Both are exported from
|
|
40
|
+
the package. The constructor accepts standard `ErrorOptions`, including `cause`.
|
|
41
|
+
|
|
42
|
+
| Code | Meaning |
|
|
43
|
+
| ------------------------- | ------------------------------------------------------------------- |
|
|
44
|
+
| `PROJECTION_DESTROYED` | An operation was called after the Projection was destroyed. |
|
|
45
|
+
| `NATIVE_OPERATION_FAILED` | The native runtime reported an initialization or operation failure. |
|
|
46
|
+
|
|
47
|
+
Invalid Gossip or Sequence input to `apply()` or `merge()` returns `undefined`.
|
|
48
|
+
Platform errors retain their original types.
|
|
49
|
+
|
|
19
50
|
## Benchmarks
|
|
20
51
|
|
|
21
|
-
###
|
|
52
|
+
### Moderate bundle size
|
|
22
53
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
| Format | Raw (KiB) | Minified (KiB) | Minified + gzip (KiB) |
|
|
55
|
+
| -------- | --------: | -------------: | --------------------: |
|
|
56
|
+
| ESM | 125.98 | 100.80 | 36.45 |
|
|
57
|
+
| CommonJS | 126.12 | 101.49 | 36.53 |
|
|
27
58
|
|
|
28
|
-
|
|
29
|
-
public API call, weighted by call count across three runs.
|
|
59
|
+
Both bundles include WASM. Terser uses three compression passes; gzip uses level 9.
|
|
30
60
|
|
|
31
|
-
|
|
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 |
|
|
61
|
+
### Average throughput during editing to checkpoint
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
2,048 calls, then measures a burst of approximately 100 ms or at most 8,192 calls.
|
|
63
|
+
Arithmetic means of 3 runs, in ops/s, from each phase's start to checkpoint.
|
|
64
|
+
Strips contain 1–100 Frames (visible entries). Headers show Strip counts and mean Frame counts.
|
|
44
65
|
|
|
45
|
-
|
|
66
|
+
**↑ Scale up (0 → 25,000 Strips)**
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
| 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 |
|
|
69
|
+
| -------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
|
|
70
|
+
| apply.random | 146,512 | 50,162 | 35,425 | 25,372 | 17,992 | 13,401 |
|
|
71
|
+
| insert.head | 1,103,240 | 627,952 | 723,841 | 741,193 | 692,500 | 629,581 |
|
|
72
|
+
| insert.random | 423,106 | 138,442 | 91,906 | 64,213 | 44,063 | 33,092 |
|
|
73
|
+
| insert.tail | 1,247,342 | 779,594 | 895,027 | 892,070 | 830,660 | 765,706 |
|
|
74
|
+
| remove.head | — | — | — | — | — | — |
|
|
75
|
+
| remove.random | 258,681 | 95,210 | 65,861 | 46,668 | 31,655 | 23,556 |
|
|
76
|
+
| remove.tail | — | — | — | — | — | — |
|
|
77
|
+
| replace.head | 380,869 | 309,212 | 303,788 | 291,544 | 270,032 | 245,792 |
|
|
78
|
+
| replace.random | 253,774 | 94,640 | 63,352 | 45,272 | 31,633 | 23,736 |
|
|
79
|
+
| replace.tail | 392,296 | 315,718 | 313,645 | 310,052 | 268,791 | 247,351 |
|
|
80
|
+
| value.head | 2,283,059 | 2,095,440 | 1,999,974 | 2,016,602 | 1,907,994 | 1,721,581 |
|
|
81
|
+
| value.random | 569,971 | 133,693 | 78,867 | 51,725 | 34,540 | 25,316 |
|
|
82
|
+
| value.tail | 4,749,438 | 3,636,984 | 3,461,656 | 3,227,876 | 2,914,996 | 2,408,625 |
|
|
49
83
|
|
|
50
|
-
|
|
51
|
-
| --------------------------- | -------: |
|
|
52
|
-
| Additional JS heap after GC | 0.501 MB |
|
|
53
|
-
| Shared WASM memory capacity | 1.049 MB |
|
|
84
|
+
**↓ Scale down (25,000 → 0 Strips)**
|
|
54
85
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
86
|
+
| 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 |
|
|
87
|
+
| -------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
|
|
88
|
+
| apply.random | 7,268 | 7,592 | 8,263 | 8,783 | 9,083 | 9,219 |
|
|
89
|
+
| insert.head | — | — | — | — | — | — |
|
|
90
|
+
| insert.random | 17,296 | 18,458 | 20,290 | 21,279 | 22,052 | 22,459 |
|
|
91
|
+
| insert.tail | — | — | — | — | — | — |
|
|
92
|
+
| remove.head | 157,981 | 165,917 | 180,514 | 178,894 | 181,826 | 183,709 |
|
|
93
|
+
| remove.random | 13,036 | 13,992 | 15,527 | 16,535 | 17,163 | 17,459 |
|
|
94
|
+
| remove.tail | 192,199 | 192,220 | 203,528 | 209,615 | 209,694 | 210,394 |
|
|
95
|
+
| replace.head | 176,953 | 178,240 | 186,921 | 185,389 | 181,259 | 161,071 |
|
|
96
|
+
| replace.random | 13,038 | 14,084 | 15,647 | 16,708 | 17,397 | 17,697 |
|
|
97
|
+
| replace.tail | 190,938 | 184,284 | 193,773 | 187,715 | 182,304 | 182,496 |
|
|
98
|
+
| value.head | 794,128 | 838,606 | 885,880 | 922,502 | 940,112 | 948,944 |
|
|
99
|
+
| value.random | 13,677 | 14,768 | 16,600 | 18,037 | 18,894 | 19,271 |
|
|
100
|
+
| value.tail | 1,070,897 | 1,038,948 | 1,093,604 | 1,140,368 | 1,178,006 | 1,192,164 |
|
|
58
101
|
|
|
59
|
-
###
|
|
102
|
+
### API throughput at checkpoint
|
|
60
103
|
|
|
61
|
-
|
|
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.
|
|
104
|
+
Checkpoint calls, in ops/s, averaged across runs.
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
**↑ Scale up (0 → 25,000 Strips)**
|
|
107
|
+
|
|
108
|
+
| 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 |
|
|
109
|
+
| --------------------------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
|
|
110
|
+
| new Projection(actorID, sequence) | 2,513 | 795 | 408 | 215 | 154 | 107 |
|
|
111
|
+
| sequence() | 375 | 66 | 33 | 18 | 11 | 6 |
|
|
112
|
+
| values() | 480 | 163 | 108 | 70 | 49 | 32 |
|
|
92
113
|
|
|
93
|
-
|
|
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.
|
|
114
|
+
**↓ Scale down (25,000 → 0 Strips)**
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
|
|
116
|
+
| 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 |
|
|
117
|
+
| --------------------------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
|
|
118
|
+
| new Projection(actorID, sequence) | 116 | 212 | 273 | 586 | 1,881 | 16,098 |
|
|
119
|
+
| sequence() | 7 | 6 | 5 | 4 | 4 | 38,383 |
|
|
120
|
+
| values() | 44 | 51 | 58 | 62 | 199 | 68,912 |
|
|
100
121
|
|
|
101
|
-
|
|
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% |
|
|
122
|
+
### Sequence disk size at checkpoint
|
|
106
123
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
Complete exported Sequences, including Footage and acknowledgements. Sizes are in KiB.
|
|
125
|
+
|
|
126
|
+
**↑ Scale up (0 → 25,000 Strips)**
|
|
127
|
+
|
|
128
|
+
| 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 |
|
|
129
|
+
| ------------------ | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
|
|
130
|
+
| JSON | 277.17 | 1,621.65 | 3,261.72 | 4,901.02 | 7,041.25 | 9,162.75 |
|
|
131
|
+
| JSON + gzip | 14.59 | 73.84 | 150.38 | 226.94 | 311.22 | 391.69 |
|
|
132
|
+
| MessagePack | 164.81 | 818.88 | 1,639.69 | 3,529.41 | 5,081.04 | 6,483.22 |
|
|
133
|
+
| MessagePack + gzip | 12.38 | 61.62 | 124.91 | 198.59 | 263.54 | 332.77 |
|
|
134
|
+
|
|
135
|
+
**↓ Scale down (25,000 → 0 Strips)**
|
|
136
|
+
|
|
137
|
+
| 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 |
|
|
138
|
+
| ------------------ | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
|
|
139
|
+
| JSON | 7,446.44 | 5,617.36 | 3,769.76 | 1,885.51 | 383.23 | 0.02 |
|
|
140
|
+
| JSON + gzip | 323.23 | 244.45 | 163.91 | 80.61 | 16.33 | 0.03 |
|
|
141
|
+
| MessagePack | 5,237.17 | 3,930.33 | 2,628.38 | 1,312.43 | 266.67 | 0.01 |
|
|
142
|
+
| MessagePack + gzip | 274.96 | 207.78 | 137.38 | 67.41 | 13.67 | 0.03 |
|
|
143
|
+
|
|
144
|
+
### Memory usage at checkpoint
|
|
145
|
+
|
|
146
|
+
One instance: WASM memory plus retained JS array slots, in KiB (1,024 bytes).
|
|
147
|
+
|
|
148
|
+
**↑ Scale up (0 → 25,000 Strips)**
|
|
149
|
+
|
|
150
|
+
| 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 |
|
|
151
|
+
| ---------------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
|
|
152
|
+
| Estimated memory (KiB) | 5,343.67 | 25,100.58 | 50,470.89 | 82,701.27 | 98,036.35 | 153,462.93 |
|
|
153
|
+
|
|
154
|
+
**↓ Scale down (25,000 → 0 Strips)**
|
|
155
|
+
|
|
156
|
+
| 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 |
|
|
157
|
+
| ---------------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
|
|
158
|
+
| Estimated memory (KiB) | 163,746.93 | 174,034.00 | 202,660.72 | 293,941.85 | 302,158.29 | 304,203.30 |
|
|
159
|
+
|
|
160
|
+
Measured on Node 24.16.0, V8 13.6.233.17-node.49,
|
|
161
|
+
win32 x64, Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz.
|
|
162
|
+
Peer synchronization, encoding and JS process garbage collection are outside API timings.
|
|
163
|
+
[Full lifecycle results](benchmark/.results/lifecycle.md) · [Throughput results](benchmark/.results/throughput.md).
|
|
111
164
|
|
|
112
165
|
## Tests
|
|
113
166
|
|
|
114
167
|
### Behaviour guaranteed by excessive tests
|
|
115
168
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
169
|
+
The latest verification passed 184 Vitest tests and 96 compatibility checks (1 failed).
|
|
170
|
+
The configured 100% coverage threshold is met (100% statements, 100% branches, 100% functions, 100% lines).
|
|
171
|
+
See the [test suites](test/README.md) and
|
|
125
172
|
[automated test report](https://sovereignbase.dev/sequencer/tests/).
|
|
126
173
|
|
|
127
174
|
### Works everywhere where JavaScript and Wasm works
|
|
128
175
|
|
|
129
176
|
Runtimes covered by the latest compatibility verification:
|
|
130
177
|
|
|
131
|
-
- Node.js, ESM
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
Firefox
|
|
139
|
-
|
|
178
|
+
- Node.js, ESM: passed
|
|
179
|
+
- Node.js, CommonJS: passed
|
|
180
|
+
- Deno: passed
|
|
181
|
+
- Bun: passed
|
|
182
|
+
- Edge Runtime: passed
|
|
183
|
+
- Cloudflare Workers through workerd: passed
|
|
184
|
+
- Chromium browser windows: passed
|
|
185
|
+
- Firefox browser windows: failed (matching Playwright browser binary unavailable)
|
|
186
|
+
- WebKit browser windows: passed
|
|
140
187
|
|
|
141
188
|
### Tested realtime and offline text editing behaviour examples
|
|
142
189
|
|
|
@@ -148,13 +195,13 @@ binary was unavailable in the latest run.
|
|
|
148
195
|
|
|
149
196
|
## Credits
|
|
150
197
|
|
|
151
|
-
|
|
198
|
+
This could not have been made without years of work from many great engineers of many great projects, to mention a few:
|
|
152
199
|
|
|
153
|
-
- AUTOMERGE
|
|
200
|
+
- [AUTOMERGE](https://github.com/automerge)
|
|
154
201
|
|
|
155
|
-
- YJS
|
|
202
|
+
- [YJS](https://github.com/yjs)
|
|
156
203
|
|
|
157
|
-
- DIAMOND TYPES
|
|
204
|
+
- [DIAMOND TYPES](https://github.com/josephg/diamond-types)
|
|
158
205
|
|
|
159
206
|
## License
|
|
160
207
|
|