@sovereignbase/sequencer 1.0.2 → 1.1.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/INTERNALS.md CHANGED
@@ -752,9 +752,9 @@ The application must exchange pending history before treating an acknowledgement
752
752
 
753
753
  When all active Actors agree on the same frontier for a reducing Session, the acknowledged part can be removed from future retained Sequences.
754
754
 
755
- This is whole-Session eligibility: every active Actor must report the same complete Session frontier. A missing claim or a different endpoint blocks that Session. Taking the minimum of unequal frontiers is not this compaction rule.
755
+ This is whole-Session eligibility: every active Actor must report the same complete Session frontier. A missing claim or a different endpoint blocks that Session.
756
756
 
757
- Eligible masks must disappear from the exported Sequence. Wholly removed positive Insertions and deleted Footage disappear as well. Claims for compacted Sessions are dropped; Actor membership is retained.
757
+ Eligible masks must disappear from the exported Sequence. Wholly removed positive Insertions and deleted Footage disappear as well. Acknowledgments of compacted Sessions are dropped, Actor membership is retained.
758
758
 
759
759
  Suppose history contains:
760
760
 
@@ -794,7 +794,7 @@ The running Projection does not need to be rewritten when this happens. Compacti
794
794
 
795
795
  The exported Sequence is a retained checkpoint. Its shorter Insertions and translated anchors can use different coordinates from the live graph before export. Continued editors must use compatible retained state; a compacted checkpoint does not preserve a permanent mapping to discarded history.
796
796
 
797
- An offline Actor's unacknowledged history blocks compaction only if that Actor was registered. A test that omits its membership, allows compaction, then demands that its discarded anchors still work is outside this contract. Tests must not retain masks to satisfy that demand.
797
+ An offline Actor's unacknowledged history blocks compaction only if that Actor was registered.
798
798
 
799
799
  ## Collapsed historical points
800
800
 
package/README.md CHANGED
@@ -27,27 +27,27 @@ console.log(projection.value(0)) // undefined
27
27
  projection.destroy()
28
28
  ```
29
29
 
30
+ For text stored as UTF-16 code units, `projection.sequence(true)` returns string
31
+ Footage. Construction and merge accept either form; `sequence()` keeps array
32
+ Footage. See [Projection.sequence](https://sovereignbase.dev/sequencer/classes/Projection.html#sequence).
33
+
30
34
  ### See documentation
31
35
 
32
36
  - [How sequencer works?](INTERNALS.md)
33
37
  - [Typescript](https://sovereignbase.dev/sequencer/classes/Projection.html)
34
38
  - [C++](https://sovereignbase.dev/sequencer/c++)
35
39
 
36
- Register active Actors, including offline Actors, before removals. When every
37
- active Actor acknowledges the same complete removal Session frontier,
38
- `sequence()` omits its masks and deleted Footage. Actor membership remains.
39
- See the [compaction contract](INTERNALS.md#compaction).
40
-
41
40
  ### Errors
42
41
 
43
42
  Library-reported failures throw `SequencerError`, with a stable `SequencerErrorCode`
44
43
  and a message prefixed with `{@sovereignbase/sequencer}`. Both are exported from
45
44
  the package. The constructor accepts standard `ErrorOptions`, including `cause`.
46
45
 
47
- | Code | Meaning |
48
- | ------------------------- | ------------------------------------------------------------------- |
49
- | `PROJECTION_DESTROYED` | An operation was called after the Projection was destroyed. |
50
- | `NATIVE_OPERATION_FAILED` | The native runtime reported an initialization or operation failure. |
46
+ | Code | Meaning |
47
+ | ------------------------- | ---------------------------------------------------------------------------- |
48
+ | `PROJECTION_DESTROYED` | An operation was called after the Projection was destroyed. |
49
+ | `NATIVE_OPERATION_FAILED` | The native runtime reported an initialization or operation failure. |
50
+ | `STRING_ENCODING_FAILED` | Snapshot Footage concatenation failed; `cause` preserves the original error. |
51
51
 
52
52
  Invalid Gossip or Sequence input to `apply()` or `merge()` returns `undefined`.
53
53
  Platform errors retain their original types.
@@ -58,8 +58,8 @@ Platform errors retain their original types.
58
58
 
59
59
  | Format | Raw (KiB) | Minified (KiB) | Minified + gzip (KiB) |
60
60
  | -------- | --------: | -------------: | --------------------: |
61
- | ESM | 135.00 | 109.15 | 39.03 |
62
- | CommonJS | 135.14 | 109.84 | 39.11 |
61
+ | ESM | 136.19 | 109.61 | 39.20 |
62
+ | CommonJS | 136.33 | 110.32 | 39.27 |
63
63
 
64
64
  Both bundles include WASM. Terser uses three compression passes; gzip uses level 9.
65
65
 
@@ -72,37 +72,37 @@ Strips contain 1–100 Frames (visible entries). Headers show Strip counts and m
72
72
 
73
73
  | 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 |
74
74
  | -------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
75
- | apply.random | 152,481 | 82,407 | 54,972 | 41,327 | 33,404 | 28,087 |
76
- | insert.head | 1,125,803 | 917,202 | 989,477 | 1,002,460 | 1,008,049 | 1,005,481 |
77
- | insert.random | 459,801 | 233,221 | 141,139 | 106,743 | 85,552 | 71,705 |
78
- | insert.tail | 1,235,888 | 1,046,362 | 1,129,451 | 1,163,582 | 1,164,652 | 963,762 |
75
+ | apply.random | 168,473 | 87,171 | 60,658 | 46,603 | 38,343 | 32,658 |
76
+ | insert.head | 1,271,814 | 999,755 | 1,074,002 | 1,093,264 | 1,106,172 | 1,105,184 |
77
+ | insert.random | 475,390 | 255,768 | 161,979 | 124,466 | 100,709 | 85,015 |
78
+ | insert.tail | 1,470,843 | 1,177,272 | 1,266,352 | 1,276,470 | 1,281,022 | 1,280,911 |
79
79
  | remove.head | — | — | — | — | — | — |
80
- | remove.random | 318,832 | 161,201 | 103,195 | 76,208 | 61,027 | 50,419 |
80
+ | remove.random | 358,067 | 180,092 | 117,059 | 86,936 | 69,330 | 57,290 |
81
81
  | remove.tail | — | — | — | — | — | — |
82
- | replace.head | 371,269 | 378,358 | 390,442 | 381,650 | 374,659 | 380,819 |
83
- | replace.random | 257,863 | 147,949 | 96,725 | 71,868 | 58,024 | 48,412 |
84
- | replace.tail | 316,736 | 369,208 | 364,734 | 383,559 | 366,881 | 350,473 |
85
- | value.head | 2,529,757 | 3,200,174 | 3,231,484 | 3,365,642 | 3,481,984 | 3,549,918 |
86
- | value.random | 644,467 | 230,616 | 126,832 | 88,155 | 68,446 | 55,752 |
87
- | value.tail | 5,503,982 | 5,458,159 | 5,069,620 | 4,982,739 | 4,949,590 | 4,829,221 |
82
+ | replace.head | 453,369 | 416,649 | 429,360 | 394,513 | 395,389 | 403,713 |
83
+ | replace.random | 298,112 | 160,606 | 108,525 | 80,892 | 65,452 | 54,680 |
84
+ | replace.tail | 401,284 | 449,608 | 412,634 | 430,354 | 408,033 | 387,953 |
85
+ | value.head | 2,827,881 | 3,525,681 | 3,620,595 | 3,723,069 | 3,810,856 | 3,811,845 |
86
+ | value.random | 731,172 | 255,071 | 143,482 | 100,113 | 78,156 | 63,980 |
87
+ | value.tail | 5,453,695 | 5,310,238 | 5,096,734 | 5,044,621 | 5,054,127 | 4,915,815 |
88
88
 
89
89
  **↓ Scale down (25,000 → 0 Strips)**
90
90
 
91
91
  | 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 |
92
92
  | -------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
93
- | apply.random | 17,289 | 17,067 | 17,078 | 17,347 | 17,927 | 18,214 |
93
+ | apply.random | 21,832 | 22,291 | 22,406 | 22,966 | 23,864 | 24,211 |
94
94
  | insert.head | — | — | — | — | — | — |
95
- | insert.random | 42,405 | 41,938 | 42,228 | 42,440 | 44,086 | 44,230 |
95
+ | insert.random | 53,637 | 55,767 | 57,227 | 57,796 | 60,536 | 60,616 |
96
96
  | insert.tail | — | — | — | — | — | — |
97
- | remove.head | 449,484 | 439,424 | 439,835 | 443,041 | 448,352 | 451,829 |
98
- | remove.random | 31,440 | 31,670 | 32,353 | 33,471 | 35,064 | 35,635 |
99
- | remove.tail | 447,064 | 423,868 | 431,746 | 378,400 | 389,534 | 351,739 |
100
- | replace.head | 342,862 | 287,099 | 291,717 | 258,805 | 263,535 | 236,146 |
101
- | replace.random | 30,799 | 30,980 | 31,637 | 32,586 | 34,069 | 34,091 |
102
- | replace.tail | 188,491 | 164,510 | 155,028 | 137,842 | 134,383 | 126,497 |
103
- | value.head | 1,613,774 | 1,552,830 | 1,534,695 | 1,545,267 | 1,574,964 | 1,598,674 |
104
- | value.random | 34,644 | 34,727 | 35,474 | 36,721 | 38,524 | 39,415 |
105
- | value.tail | 1,908,571 | 1,799,032 | 1,759,746 | 1,768,794 | 1,828,720 | 1,863,802 |
97
+ | remove.head | 493,654 | 493,034 | 492,611 | 492,704 | 494,704 | 497,520 |
98
+ | remove.random | 37,466 | 39,462 | 41,048 | 43,073 | 45,209 | 45,778 |
99
+ | remove.tail | 491,829 | 472,405 | 399,771 | 416,614 | 426,344 | 383,461 |
100
+ | replace.head | 380,504 | 371,861 | 365,935 | 312,468 | 315,039 | 271,629 |
101
+ | replace.random | 35,794 | 37,986 | 39,619 | 41,464 | 43,674 | 43,633 |
102
+ | replace.tail | 198,219 | 176,520 | 166,748 | 150,292 | 149,123 | 140,224 |
103
+ | value.head | 1,820,081 | 1,813,320 | 1,792,267 | 1,804,349 | 1,844,976 | 1,872,844 |
104
+ | value.random | 41,888 | 43,912 | 45,754 | 48,224 | 51,108 | 52,192 |
105
+ | value.tail | 2,154,911 | 2,130,711 | 2,112,616 | 2,133,349 | 2,207,459 | 2,246,004 |
106
106
 
107
107
  ### API throughput at checkpoint
108
108
 
@@ -112,17 +112,17 @@ Checkpoint calls, in ops/s, averaged across runs.
112
112
 
113
113
  | 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 |
114
114
  | --------------------------------- | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
115
- | new Projection(actorID, sequence) | 3,734 | 1,016 | 594 | 409 | 287 | 237 |
116
- | sequence() | 479 | 85 | 42 | 26 | 17 | 12 |
117
- | values() | 480 | 205 | 63 | 55 | 34 | 37 |
115
+ | new Projection(actorID, sequence) | 4,665 | 968 | 614 | 420 | 291 | 244 |
116
+ | sequence() | 611 | 102 | 50 | 31 | 21 | 15 |
117
+ | values() | 669 | 251 | 84 | 50 | 50 | 31 |
118
118
 
119
119
  **↓ Scale down (25,000 → 0 Strips)**
120
120
 
121
121
  | 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 |
122
122
  | --------------------------------- | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
123
- | new Projection(actorID, sequence) | 284 | 410 | 505 | 1,271 | 5,357 | 22,172 |
124
- | sequence() | 12 | 11 | 8 | 9 | 9 | 7 |
125
- | values() | 52 | 52 | 53 | 81 | 209 | 140,305 |
123
+ | new Projection(actorID, sequence) | 276 | 420 | 499 | 1,294 | 5,450 | 23,249 |
124
+ | sequence() | 15 | 14 | 10 | 12 | 11 | 10 |
125
+ | values() | 46 | 45 | 68 | 96 | 258 | 150,052 |
126
126
 
127
127
  ### Sequence disk size at checkpoint
128
128
 
@@ -132,8 +132,8 @@ Complete exported Sequences, including Footage and acknowledgements. Sizes are i
132
132
 
133
133
  | 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 |
134
134
  | ------------------ | ----------------------------: | -----------------------------: | ------------------------------: | ------------------------------: | --------------------------------: | --------------------------------: |
135
- | JSON | 277.41 | 1,622.78 | 3,264.00 | 4,904.44 | 7,045.81 | 9,168.42 |
136
- | JSON + gzip | 14.59 | 73.83 | 150.35 | 226.86 | 311.12 | 391.57 |
135
+ | JSON | 277.29 | 1,622.20 | 3,262.86 | 4,902.77 | 7,043.50 | 9,165.60 |
136
+ | JSON + gzip | 14.60 | 73.83 | 150.38 | 226.92 | 311.16 | 391.60 |
137
137
  | MessagePack | 164.81 | 818.88 | 1,639.69 | 3,529.41 | 5,081.04 | 6,483.22 |
138
138
  | MessagePack + gzip | 12.38 | 61.62 | 124.91 | 198.59 | 263.54 | 332.77 |
139
139
 
@@ -141,8 +141,8 @@ Complete exported Sequences, including Footage and acknowledgements. Sizes are i
141
141
 
142
142
  | 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 |
143
143
  | ------------------ | --------------------------------: | ------------------------------: | ------------------------------: | -----------------------------: | ----------------------------: | -------------------: |
144
- | JSON | 7,451.86 | 5,621.57 | 3,772.57 | 1,886.91 | 383.51 | 0.02 |
145
- | JSON + gzip | 323.11 | 244.34 | 163.82 | 80.59 | 16.33 | 0.03 |
144
+ | JSON | 7,449.19 | 5,619.45 | 3,771.17 | 1,886.19 | 383.37 | 0.02 |
145
+ | JSON + gzip | 323.17 | 244.42 | 163.89 | 80.61 | 16.33 | 0.03 |
146
146
  | MessagePack | 5,237.17 | 3,930.33 | 2,628.38 | 1,312.43 | 266.67 | 0.01 |
147
147
  | MessagePack + gzip | 274.96 | 207.78 | 137.38 | 67.41 | 13.67 | 0.03 |
148
148
 
@@ -171,7 +171,7 @@ Peer synchronization, encoding and JS process garbage collection are outside API
171
171
 
172
172
  ### Behaviour guaranteed by excessive tests
173
173
 
174
- The latest verification passed 212 Vitest tests and 108 compatibility checks.
174
+ The latest verification passed 221 Vitest tests and 117 compatibility checks.
175
175
  The configured 100% coverage threshold is met (100% statements, 100% branches, 100% functions, 100% lines).
176
176
  See the [test suites](test/README.md) and
177
177
  [automated test report](https://sovereignbase.dev/sequencer/tests/).
package/dist/index.cjs CHANGED
@@ -332,6 +332,11 @@ var Runtime = class {
332
332
  const heap = module$1.HEAPU32;
333
333
  return this.content[id][heap[pointer]]?.[heap[pointer + 1]];
334
334
  }
335
+ /** Exports native spans directly, with optional string Footage assembled in JavaScript. */
336
+ sequence(id, stringEncode) {
337
+ if (id < 0) throw new SequencerError("PROJECTION_DESTROYED", "Projection has been destroyed.");
338
+ return this.result(Operation.Sequence, id, module$1._runtime_dispatch(Operation.Sequence, id, 0, 0, 0, 0, 0), stringEncode);
339
+ }
335
340
  /** Authors trusted local Frames through the dedicated six-word result path. */
336
341
  insert(id, values, at) {
337
342
  if (id < 0) throw new SequencerError("PROJECTION_DESTROYED", "Projection has been destroyed.");
@@ -449,16 +454,16 @@ var Runtime = class {
449
454
  a0 = value ?? absent;
450
455
  a1 = at ?? absent;
451
456
  } else if (operation === Operation.Retire) a0 = value;
452
- else if (operation === Operation.Create || operation === Operation.Merge) {
457
+ else {
453
458
  a0 = operation === Operation.Create ? value : 0;
454
459
  const sequence = operation === Operation.Create ? at : value;
455
- const valid = operation === Operation.Create && sequence === void 0 || Array.isArray(sequence) && sequence.length === 2 && (Array.isArray(sequence[0]) || sequence[0] instanceof Uint32Array) && Array.isArray(sequence[1]) && (operation === Operation.Create || sequence[0] instanceof Uint32Array || sequence[0].every((word) => Number.isInteger(word) && word >= 0 && word <= absent));
460
+ const valid = operation === Operation.Create && sequence === void 0 || Array.isArray(sequence) && sequence.length === 2 && (Array.isArray(sequence[0]) || sequence[0] instanceof Uint32Array) && (Array.isArray(sequence[1]) || typeof sequence[1] === "string") && (operation === Operation.Create || sequence[0] instanceof Uint32Array || sequence[0].every((word) => Number.isInteger(word) && word >= 0 && word <= absent));
456
461
  a2 = valid ? 1 : 0;
457
462
  if (valid && sequence !== void 0) {
458
463
  const words = sequence[0];
459
464
  heap.set(words, base);
460
465
  a1 = contents.length;
461
- footage = sequence[1];
466
+ footage = typeof sequence[1] === "string" ? sequence[1].split("") : sequence[1];
462
467
  }
463
468
  }
464
469
  const resultPointer = module$1._runtime_dispatch(operation, id, a0, a1, a2, pointer, count);
@@ -472,7 +477,7 @@ var Runtime = class {
472
477
  return this.result(operation, id, resultPointer);
473
478
  }
474
479
  /** Consumes borrowed native output before another call may overwrite its storage. */
475
- result(operation, id, pointer) {
480
+ result(operation, id, pointer, stringEncode) {
476
481
  const heap = module$1.HEAPU32, header = pointer >>> 2, content = this.content[id];
477
482
  const kind = heap[header], p0 = heap[header + 3], n0 = heap[header + 4], p1 = heap[header + 5], n1 = heap[header + 6];
478
483
  const mutable = this.mutable[id];
@@ -589,6 +594,22 @@ var Runtime = class {
589
594
  metadata.length = n0;
590
595
  for (let i = 0, at = p0 >>> 2; i < n0; ++i) metadata[i] = heap[at + i];
591
596
  } else metadata = words(p0, n0);
597
+ if (stringEncode) {
598
+ let text = "";
599
+ try {
600
+ for (let at = p1 >>> 2, end = at + n1 * 3; at < end; at += 3) {
601
+ const source = content[heap[at]], start = heap[at + 1], count = heap[at + 2];
602
+ for (let i = 0; i < count; ++i) {
603
+ const value = source?.[start + i];
604
+ text += value === void 0 ? "\0" : value;
605
+ }
606
+ }
607
+ } catch (cause) {
608
+ throw new SequencerError("STRING_ENCODING_FAILED", "Sequence Footage could not be encoded as a string.", { cause });
609
+ }
610
+ result = [metadata, text];
611
+ break;
612
+ }
592
613
  const span = p1 >>> 2, token = heap[span];
593
614
  const shared = n1 === 1 && heap[span + 1] === 0 && content[token]?.length === heap[span + 2] && !!mutable[token];
594
615
  if (shared && mutable[token] !== 3) {
@@ -657,7 +678,7 @@ var Projection = class {
657
678
  * Prepares to sequence a projection, optionally hydrating from a trusted sequence stored by the application.
658
679
  *
659
680
  * @param actorID Actor identifier used in acknowledgements.
660
- * @param trustedSequence Optional trusted Sequence in Structural Order, stored by the application.
681
+ * @param trustedSequence Optional trusted Sequence in Structural Order; string Footage is split into UTF-16 code units.
661
682
  * @throws {@link SequencerError} if native initialization fails.
662
683
  */
663
684
  constructor(actorID, trustedSequence) {
@@ -750,11 +771,13 @@ var Projection = class {
750
771
  * permit masks and deleted Footage to be omitted from the retained checkpoint.
751
772
  * Compaction may translate retained anchors without rewriting this instance.
752
773
  *
774
+ * @param stringEncode Whether to concatenate Footage into a string; each text Frame must be one UTF-16 code unit. Defaults to `false`.
753
775
  * @returns A Sequence with acknowledged removals compacted in the exported state.
754
776
  * @throws {@link SequencerError} if destroyed or the native runtime reports an operation failure.
777
+ * @throws {@link SequencerError} with code `STRING_ENCODING_FAILED` if Footage concatenation fails.
755
778
  */
756
- sequence() {
757
- return runtime.call(Operation.Sequence, this.id);
779
+ sequence(stringEncode) {
780
+ return runtime.sequence(this.id, stringEncode);
758
781
  }
759
782
  /**
760
783
  * Returns the value at a Projection position.