Sequencer provides a total order guarantee for distributed editing. It allows independently operating actors to make concurrent edits and eventually converge on the same state without relying on network arrival order or perfectly synchronized clocks.
import { Projection } from '@sovereignbase/sequencer'
const projectionA = new Projection<string>(1)
const projectionB = new Projection<string>(2)
const insertDelta = projectionA.insert(['Hello World'], 0)
console.log(projectionA.value(0)) // "Hello World"
projectionB.apply(insertDelta)
console.log(projectionB.value(0)) // "Hello World"
const replaceDelta = projectionA.replace(['Hello Dude'], 0, 0)
console.log(projectionA.value(0)) // "Hello Dude"
const replaceAcknowledgement = projectionB.apply(replaceDelta)
console.log(projectionB.value(0)) // "Hello Dude"
projectionA.apply(replaceAcknowledgement)
const removeDelta = projectionA.remove(0, 0)
console.log(projectionA.value(0)) // undefined
const removeAcknowledgement = projectionB.apply(removeDelta)
console.log(projectionB.value(0)) // undefined
projectionA.apply(removeAcknowledgement)
const serializable = projectionA.sequence()
const acknowledgements = projectionB.merge(serializable)
projectionA.apply(acknowledgements)
projectionA.destroy()
projectionB.destroy()| Code | Meaning |
|---|---|
PROJECTION_DESTROYED |
An operation was called after the Projection was destroyed. |
NATIVE_OPERATION_FAILED |
The native runtime reported an initialization or operation failure. |
STRING_ENCODING_FAILED |
Snapshot Footage concatenation failed. |
UNKNOWN_ERROR |
All other errors. |
Invalid input to apply() or merge() returns undefined instead of throwing.
| Format | Raw (KiB) | Minified (KiB) | Minified + gzip (KiB) |
|---|---|---|---|
| ESM | 138.11 | 110.79 | 39.32 |
| CommonJS | 138.25 | 111.81 | 39.39 |
Both bundles include WASM. Terser uses three compression passes; gzip uses level 9.
Arithmetic means of 3 runs, in ops/s, from each phase's start to checkpoint. Strips contain 1–100 Frames (visible entries). Headers show Strip counts and mean Frame counts.
↑ Scale up (0 → 25,000 Strips)
| Method / case | 1,000 Strips 50,871 Frames |
5,000 Strips 252,214 Frames |
10,000 Strips 504,871 Frames |
15,000 Strips 758,280 Frames |
20,000 Strips 1,010,749 Frames |
25,000 Strips 1,264,003 Frames |
|---|---|---|---|---|---|---|
| apply.random | 168,988 | 83,955 | 61,650 | 47,049 | 38,583 | 32,071 |
| insert.head | 1,206,471 | 1,005,683 | 1,120,929 | 1,152,814 | 1,182,905 | 1,200,605 |
| insert.random | 478,940 | 239,168 | 162,373 | 122,729 | 98,990 | 81,755 |
| insert.tail | 1,334,433 | 1,104,239 | 1,256,494 | 1,303,969 | 1,339,136 | 1,349,061 |
| remove.head | — | — | — | — | — | — |
| remove.random | 341,765 | 165,565 | 113,075 | 83,800 | 67,429 | 55,746 |
| remove.tail | — | — | — | — | — | — |
| replace.head | 434,921 | 413,737 | 437,844 | 440,051 | 452,138 | 445,770 |
| replace.random | 289,668 | 153,680 | 102,130 | 77,675 | 63,528 | 53,108 |
| replace.tail | 378,410 | 435,943 | 470,911 | 489,325 | 467,618 | 465,423 |
| value.head | 2,248,956 | 3,197,040 | 3,540,159 | 3,716,627 | 3,920,698 | 3,991,212 |
| value.random | 723,289 | 231,174 | 138,597 | 96,795 | 75,674 | 61,147 |
| value.tail | 5,010,118 | 5,355,455 | 5,275,683 | 5,289,080 | 5,356,586 | 5,294,392 |
↓ Scale down (25,000 → 0 Strips)
| Method / case | 20,000 Strips 1,012,742 Frames |
15,000 Strips 755,860 Frames |
10,000 Strips 504,103 Frames |
5,000 Strips 251,203 Frames |
1,000 Strips 51,057 Frames |
0 Strips 0 Frames |
|---|---|---|---|---|---|---|
| apply.random | 20,767 | 21,129 | 21,221 | 21,621 | 22,254 | 22,548 |
| insert.head | — | — | — | — | — | — |
| insert.random | 49,291 | 50,341 | 51,237 | 51,902 | 53,706 | 53,811 |
| insert.tail | — | — | — | — | — | — |
| remove.head | 609,754 | 609,062 | 614,121 | 617,274 | 622,099 | 624,577 |
| remove.random | 35,772 | 36,882 | 38,342 | 40,067 | 41,973 | 42,560 |
| remove.tail | 596,805 | 587,004 | 596,862 | 604,122 | 610,763 | 504,986 |
| replace.head | 423,126 | 383,070 | 388,964 | 374,385 | 371,780 | 311,835 |
| replace.random | 34,738 | 36,411 | 37,348 | 39,079 | 40,869 | 40,848 |
| replace.tail | 227,897 | 198,045 | 194,265 | 184,618 | 184,097 | 170,615 |
| value.head | 2,076,382 | 1,986,100 | 1,933,034 | 1,933,944 | 1,982,144 | 2,003,444 |
| value.random | 38,599 | 39,752 | 41,093 | 42,883 | 44,849 | 45,853 |
| value.tail | 2,349,312 | 2,266,890 | 2,232,286 | 2,259,013 | 2,327,296 | 2,364,396 |
Checkpoint calls, in ops/s, averaged across runs.
↑ Scale up (0 → 25,000 Strips)
| Method | 1,000 Strips 50,871 Frames |
5,000 Strips 252,214 Frames |
10,000 Strips 504,871 Frames |
15,000 Strips 758,280 Frames |
20,000 Strips 1,010,749 Frames |
25,000 Strips 1,264,003 Frames |
|---|---|---|---|---|---|---|
| new Projection(actorID, sequence) | 4,586 | 948 | 590 | 417 | 290 | 240 |
| sequence() | 520 | 96 | 55 | 30 | 24 | 18 |
| values() | 555 | 226 | 57 | 79 | 29 | 54 |
↓ Scale down (25,000 → 0 Strips)
| Method | 20,000 Strips 1,012,742 Frames |
15,000 Strips 755,860 Frames |
10,000 Strips 504,103 Frames |
5,000 Strips 251,203 Frames |
1,000 Strips 51,057 Frames |
0 Strips 0 Frames |
|---|---|---|---|---|---|---|
| new Projection(actorID, sequence) | 284 | 414 | 493 | 1,276 | 5,419 | 21,553 |
| sequence() | 16 | 14 | 10 | 10 | 9 | 8 |
| values() | 63 | 34 | 79 | 82 | 209 | 109,439 |
Complete exported Sequences, including Footage and acknowledgements. Sizes are in KiB.
↑ Scale up (0 → 25,000 Strips)
| Encoding | 1,000 Strips 50,871 Frames |
5,000 Strips 252,214 Frames |
10,000 Strips 504,871 Frames |
15,000 Strips 758,280 Frames |
20,000 Strips 1,010,749 Frames |
25,000 Strips 1,264,003 Frames |
|---|---|---|---|---|---|---|
| JSON | 277.41 | 1,622.78 | 3,264.00 | 4,904.44 | 7,045.81 | 9,168.42 |
| JSON + gzip | 14.60 | 73.84 | 150.39 | 226.88 | 311.17 | 391.65 |
| MessagePack | 164.81 | 818.88 | 1,639.69 | 3,529.41 | 5,081.04 | 6,483.22 |
| MessagePack + gzip | 12.38 | 61.62 | 124.91 | 198.59 | 263.54 | 332.77 |
↓ Scale down (25,000 → 0 Strips)
| Encoding | 20,000 Strips 1,012,742 Frames |
15,000 Strips 755,860 Frames |
10,000 Strips 504,103 Frames |
5,000 Strips 251,203 Frames |
1,000 Strips 51,057 Frames |
0 Strips 0 Frames |
|---|---|---|---|---|---|---|
| JSON | 7,451.86 | 5,621.57 | 3,772.57 | 1,886.91 | 383.51 | 0.02 |
| JSON + gzip | 323.17 | 244.37 | 163.86 | 80.61 | 16.34 | 0.03 |
| MessagePack | 5,237.17 | 3,930.33 | 2,628.38 | 1,312.43 | 266.67 | 0.01 |
| MessagePack + gzip | 274.96 | 207.78 | 137.38 | 67.41 | 13.67 | 0.03 |
One instance: WASM memory plus retained JS array slots, in KiB (1,024 bytes).
↑ Scale up (0 → 25,000 Strips)
| Measurement | 1,000 Strips 50,871 Frames |
5,000 Strips 252,214 Frames |
10,000 Strips 504,871 Frames |
15,000 Strips 758,280 Frames |
20,000 Strips 1,010,749 Frames |
25,000 Strips 1,264,003 Frames |
|---|---|---|---|---|---|---|
| Estimated memory (KiB) | 5,343.67 | 25,100.58 | 50,470.89 | 82,701.27 | 98,036.35 | 153,462.93 |
↓ Scale down (25,000 → 0 Strips)
| Measurement | 20,000 Strips 1,012,742 Frames |
15,000 Strips 755,860 Frames |
10,000 Strips 504,103 Frames |
5,000 Strips 251,203 Frames |
1,000 Strips 51,057 Frames |
0 Strips 0 Frames |
|---|---|---|---|---|---|---|
| Estimated memory (KiB) | 163,746.93 | 174,034.00 | 202,660.72 | 293,941.85 | 302,158.29 | 304,203.30 |
Measured on Node 24.21.0, V8 13.6.233.17-node.53, linux x64, AMD EPYC 7763 64-Core Processor. Peer synchronization, encoding and JS process garbage collection are outside API timings. Full lifecycle results · Throughput results.
The latest verification passed 249 Vitest tests and 117 compatibility checks. The configured 100% coverage threshold is met (100% statements, 100% branches, 100% functions, 100% lines). See the test suites and automated test report.
Runtimes covered by the latest compatibility verification:
- Node.js, ESM: passed
- Node.js, CommonJS: passed
- Deno: passed
- Bun: passed
- Edge Runtime: passed
- Cloudflare Workers through workerd: passed
- Chromium browser windows: passed
- Firefox browser windows: passed
- WebKit browser windows: passed
- Online and offline editors: six editors converge through delayed, reversed and shuffled delivery, including offline changes arriving during online editing.
- Concurrent replacements: independent replacement histories retain the same surviving content across delivery orders.
- Restart and redelivery: stored Sequences preserve visible content through hydration, duplicate delivery and further edits.
- Peer-to-peer fanout: independently editing browser peers synchronize through BroadcastChannel.
- WebSocket fanout: three browser peers converge through a stateless JSON Gossip relay.
This could not have been made without years of work from many great engineers of many great projects, to mention a few:
Apache-2.0