@optimystic/db-p2p 0.22.0 → 0.24.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.
Files changed (177) hide show
  1. package/dist/src/cluster/client.d.ts +10 -0
  2. package/dist/src/cluster/client.d.ts.map +1 -1
  3. package/dist/src/cluster/client.js +30 -1
  4. package/dist/src/cluster/client.js.map +1 -1
  5. package/dist/src/cluster/cluster-repo.d.ts +39 -14
  6. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-repo.js +226 -119
  8. package/dist/src/cluster/cluster-repo.js.map +1 -1
  9. package/dist/src/cluster/service.d.ts +32 -1
  10. package/dist/src/cluster/service.d.ts.map +1 -1
  11. package/dist/src/cluster/service.js +43 -2
  12. package/dist/src/cluster/service.js.map +1 -1
  13. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  14. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  15. package/dist/src/cohort-topic/stream-util.js +56 -10
  16. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  17. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  18. package/dist/src/dispute/dispute-service.js +9 -3
  19. package/dist/src/dispute/dispute-service.js.map +1 -1
  20. package/dist/src/index.d.ts +3 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +3 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/libp2p-key-network.d.ts +88 -2
  25. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  26. package/dist/src/libp2p-key-network.js +134 -28
  27. package/dist/src/libp2p-key-network.js.map +1 -1
  28. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  29. package/dist/src/libp2p-node-base.js +25 -1
  30. package/dist/src/libp2p-node-base.js.map +1 -1
  31. package/dist/src/logger.d.ts +17 -1
  32. package/dist/src/logger.d.ts.map +1 -1
  33. package/dist/src/logger.js +19 -2
  34. package/dist/src/logger.js.map +1 -1
  35. package/dist/src/owned-block-seed.d.ts +6 -3
  36. package/dist/src/owned-block-seed.d.ts.map +1 -1
  37. package/dist/src/owned-block-seed.js +16 -3
  38. package/dist/src/owned-block-seed.js.map +1 -1
  39. package/dist/src/peer-address-book.d.ts +72 -0
  40. package/dist/src/peer-address-book.d.ts.map +1 -0
  41. package/dist/src/peer-address-book.js +123 -0
  42. package/dist/src/peer-address-book.js.map +1 -0
  43. package/dist/src/repo/client.d.ts.map +1 -1
  44. package/dist/src/repo/client.js +11 -2
  45. package/dist/src/repo/client.js.map +1 -1
  46. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  47. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  48. package/dist/src/repo/cluster-coordinator.js +95 -3
  49. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  50. package/dist/src/repo/coordinator-repo.d.ts +62 -9
  51. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  52. package/dist/src/repo/coordinator-repo.js +242 -73
  53. package/dist/src/repo/coordinator-repo.js.map +1 -1
  54. package/dist/src/rn.d.ts +3 -0
  55. package/dist/src/rn.d.ts.map +1 -1
  56. package/dist/src/rn.js +3 -0
  57. package/dist/src/rn.js.map +1 -1
  58. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  59. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  60. package/dist/src/storage/cached-raw-storage.js +152 -0
  61. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  62. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  63. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  64. package/dist/src/storage/cached-store-driver.js +775 -0
  65. package/dist/src/storage/cached-store-driver.js.map +1 -0
  66. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  67. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  68. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  69. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  70. package/dist/src/storage/shared-cache-pool.js +354 -0
  71. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  72. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  73. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  74. package/dist/src/testing/raw-storage-conformance.js +35 -2
  75. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  76. package/package.json +3 -3
  77. package/readme.md +668 -668
  78. package/src/cluster/block-transfer.ts +424 -424
  79. package/src/cluster/client.ts +119 -88
  80. package/src/cluster/cluster-error.ts +64 -64
  81. package/src/cluster/cluster-policy.ts +203 -203
  82. package/src/cluster/cluster-repo.ts +242 -122
  83. package/src/cluster/cluster-size-coupling.ts +45 -45
  84. package/src/cluster/commit-cert.ts +139 -139
  85. package/src/cluster/i-transaction-state-store.ts +43 -43
  86. package/src/cluster/memory-transaction-state-store.ts +56 -56
  87. package/src/cluster/peer-key-binding.ts +37 -37
  88. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  89. package/src/cluster/quorum-restore.ts +223 -223
  90. package/src/cluster/reconcile-block.ts +203 -203
  91. package/src/cluster/service.ts +293 -241
  92. package/src/cluster/supermajority-coupling.ts +37 -37
  93. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  94. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  95. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  96. package/src/cohort-topic/change-bridge.ts +109 -109
  97. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  98. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  99. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  100. package/src/cohort-topic/host.ts +2901 -2901
  101. package/src/cohort-topic/index.ts +13 -13
  102. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  103. package/src/cohort-topic/membership-source.ts +68 -68
  104. package/src/cohort-topic/peer-codec.ts +31 -31
  105. package/src/cohort-topic/peer-sig.ts +86 -86
  106. package/src/cohort-topic/protocols.ts +71 -71
  107. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  108. package/src/cohort-topic/size-estimator.ts +16 -16
  109. package/src/cohort-topic/stream-util.ts +135 -87
  110. package/src/cohort-topic/threshold-crypto.ts +239 -239
  111. package/src/cohort-topic/topic-router.ts +77 -77
  112. package/src/dispute/arbitrator-selection.ts +138 -138
  113. package/src/dispute/cascade.ts +524 -524
  114. package/src/dispute/dispute-service.ts +11 -5
  115. package/src/dispute/invalidation.ts +625 -625
  116. package/src/inbound-authorization.ts +190 -190
  117. package/src/index.ts +52 -49
  118. package/src/libp2p-key-network.ts +1120 -990
  119. package/src/libp2p-node-base.ts +1675 -1651
  120. package/src/libp2p-node-rn.ts +30 -30
  121. package/src/libp2p-node.ts +36 -36
  122. package/src/logger.ts +19 -2
  123. package/src/matchmaking/aggregate-counts.ts +104 -104
  124. package/src/matchmaking/index.ts +20 -20
  125. package/src/matchmaking/module.ts +363 -363
  126. package/src/matchmaking/protocols.ts +51 -51
  127. package/src/matchmaking/provider-manager.ts +95 -95
  128. package/src/matchmaking/query-handler.ts +88 -88
  129. package/src/matchmaking/query-transport.ts +492 -492
  130. package/src/matchmaking/seeker-manager.ts +64 -64
  131. package/src/matchmaking/seeker-walk-client.ts +293 -293
  132. package/src/matchmaking/traffic-validation.ts +195 -195
  133. package/src/optimystic-node.ts +36 -36
  134. package/src/owned-block-seed.ts +53 -40
  135. package/src/peer-address-book.ts +149 -0
  136. package/src/protocol-limits.ts +33 -33
  137. package/src/reactivity/forwarder-host.ts +438 -438
  138. package/src/reactivity/index.ts +19 -19
  139. package/src/reactivity/notify-transport.ts +144 -144
  140. package/src/reactivity/origination-manager.ts +192 -192
  141. package/src/reactivity/protocols.ts +61 -61
  142. package/src/reactivity/push-state-gossip.ts +291 -291
  143. package/src/reactivity/recover-transport.ts +408 -408
  144. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  145. package/src/reactivity/subscriber-registry.ts +96 -96
  146. package/src/reactivity/subscription-manager.ts +450 -450
  147. package/src/reactivity/topic-bytes.ts +37 -37
  148. package/src/repo/client.ts +12 -2
  149. package/src/repo/cluster-coordinator.ts +99 -3
  150. package/src/repo/coordinator-repo.ts +281 -74
  151. package/src/repo/types.ts +7 -7
  152. package/src/rn.ts +39 -36
  153. package/src/rpc-deadline.ts +45 -45
  154. package/src/storage/arachnode-partition.ts +74 -74
  155. package/src/storage/cached-raw-storage.ts +180 -0
  156. package/src/storage/cached-store-driver.ts +859 -0
  157. package/src/storage/i-kv-store.ts +8 -8
  158. package/src/storage/i-raw-storage.ts +12 -5
  159. package/src/storage/kv-raw-storage.ts +135 -135
  160. package/src/storage/memory-kv-store.ts +28 -28
  161. package/src/storage/memory-storage.ts +25 -25
  162. package/src/storage/memory-store-driver.ts +157 -157
  163. package/src/storage/raw-store-codec.ts +42 -42
  164. package/src/storage/raw-store-driver.ts +80 -80
  165. package/src/storage/ring-selector.ts +317 -317
  166. package/src/storage/ring-shift-coordinator.ts +271 -271
  167. package/src/storage/shared-cache-pool.ts +452 -0
  168. package/src/storage/storage-repo.ts +1014 -1014
  169. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  170. package/src/testing/index.ts +8 -8
  171. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  172. package/src/testing/raw-storage-conformance.ts +453 -417
  173. package/src/testing/reactivity-mesh-harness.ts +922 -922
  174. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  175. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  176. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  177. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,157 +1,157 @@
1
- import type { BlockId, ActionId } from "@optimystic/db-core";
2
- import type { RawStoreDriver } from "./raw-store-driver.js";
3
-
4
- /**
5
- * In-memory {@link RawStoreDriver}: the five logical block-storage stores as
6
- * `Map`s of `Uint8Array` values. `KvRawStorage` hands this driver bytes produced
7
- * by `JSON`-encode and reads them back via `JSON`-decode, so every get yields a
8
- * fresh object and every save stored an independent byte snapshot BY
9
- * CONSTRUCTION — the old `structuredClone`-on-every-get/put discipline is now
10
- * structural, not a rule a maintainer has to remember. The driver therefore
11
- * stores the byte reference directly: those bytes are never mutated by the
12
- * kernel or exposed to callers as a mutable handle.
13
- */
14
- export class MemoryStoreDriver implements RawStoreDriver {
15
- private readonly metadata = new Map<BlockId, Uint8Array>();
16
- private readonly revisions = new Map<string, Uint8Array>(); // `${blockId}:${rev}` -> actionId bytes
17
- private readonly pending = new Map<string, Uint8Array>(); // `${blockId}:${actionId}` -> transform bytes
18
- private readonly transactions = new Map<string, Uint8Array>();
19
- private readonly materialized = new Map<string, Uint8Array>();
20
-
21
- private revisionKey(blockId: BlockId, rev: number): string {
22
- return `${blockId}:${rev}`;
23
- }
24
-
25
- private actionKey(blockId: BlockId, actionId: ActionId): string {
26
- return `${blockId}:${actionId}`;
27
- }
28
-
29
- // --- metadata ---
30
-
31
- async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
32
- return this.metadata.get(blockId);
33
- }
34
-
35
- async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
36
- this.metadata.set(blockId, value);
37
- }
38
-
39
- // --- revisions ---
40
-
41
- async getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
42
- return this.revisions.get(this.revisionKey(blockId, rev));
43
- }
44
-
45
- async putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
46
- this.revisions.set(this.revisionKey(blockId, rev), value);
47
- }
48
-
49
- async *rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]> {
50
- // Drain into an array before yielding (drain-before-yield contract): the
51
- // consumer awaits between yields, and a Map has no live cursor to pin, but
52
- // this keeps the memory driver's semantics identical to the native backends.
53
- const results: [number, Uint8Array][] = [];
54
- for (let rev = lo; rev <= hi; rev++) {
55
- const value = this.revisions.get(this.revisionKey(blockId, rev));
56
- if (value !== undefined) {
57
- results.push([rev, value]);
58
- }
59
- }
60
- if (reverse) {
61
- results.reverse();
62
- }
63
- for (const result of results) {
64
- yield result;
65
- }
66
- }
67
-
68
- // --- pending ---
69
-
70
- async getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
71
- return this.pending.get(this.actionKey(blockId, actionId));
72
- }
73
-
74
- async putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
75
- this.pending.set(this.actionKey(blockId, actionId), value);
76
- }
77
-
78
- async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
79
- this.pending.delete(this.actionKey(blockId, actionId));
80
- }
81
-
82
- async *listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId> {
83
- const prefix = `${blockId}:`;
84
- // Snapshot before yielding (drain-before-yield): a concurrent putPending during
85
- // the scan must not invalidate a live map iterator.
86
- const ids: ActionId[] = [];
87
- for (const key of Array.from(this.pending.keys())) {
88
- if (key.startsWith(prefix)) {
89
- ids.push(key.substring(prefix.length) as ActionId);
90
- }
91
- }
92
- for (const id of ids) {
93
- yield id;
94
- }
95
- }
96
-
97
- // --- transactions ---
98
-
99
- async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
100
- return this.transactions.get(this.actionKey(blockId, actionId));
101
- }
102
-
103
- async putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
104
- this.transactions.set(this.actionKey(blockId, actionId), value);
105
- }
106
-
107
- // --- materialized ---
108
-
109
- async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
110
- return this.materialized.get(this.actionKey(blockId, actionId));
111
- }
112
-
113
- async putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
114
- this.materialized.set(this.actionKey(blockId, actionId), value);
115
- }
116
-
117
- async deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void> {
118
- this.materialized.delete(this.actionKey(blockId, actionId));
119
- }
120
-
121
- // --- promote (atomic move; synchronous Map ops make it indivisible here) ---
122
-
123
- async promote(blockId: BlockId, actionId: ActionId): Promise<void> {
124
- const key = this.actionKey(blockId, actionId);
125
- const value = this.pending.get(key);
126
- if (value === undefined) {
127
- throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
128
- }
129
- this.transactions.set(key, value);
130
- this.pending.delete(key);
131
- }
132
-
133
- // --- optional passthroughs ---
134
-
135
- async *listBlockIds(): AsyncIterable<BlockId> {
136
- // Snapshot the keys before yielding so a concurrent putMetadata during the scan
137
- // doesn't invalidate a live map iterator. Fresh in-memory storage is empty, so at
138
- // real process startup this yields nothing — it exists so the seed path can be
139
- // unit-tested against a pre-populated store.
140
- for (const blockId of Array.from(this.metadata.keys())) {
141
- yield blockId;
142
- }
143
- }
144
-
145
- async approximateBytesUsed(): Promise<number> {
146
- let total = 0;
147
- for (const [blockId, value] of this.metadata) {
148
- total += blockId.length + value.byteLength;
149
- }
150
- for (const store of [this.revisions, this.pending, this.transactions, this.materialized]) {
151
- for (const [key, value] of store) {
152
- total += key.length + value.byteLength;
153
- }
154
- }
155
- return total;
156
- }
157
- }
1
+ import type { BlockId, ActionId } from "@optimystic/db-core";
2
+ import type { RawStoreDriver } from "./raw-store-driver.js";
3
+
4
+ /**
5
+ * In-memory {@link RawStoreDriver}: the five logical block-storage stores as
6
+ * `Map`s of `Uint8Array` values. `KvRawStorage` hands this driver bytes produced
7
+ * by `JSON`-encode and reads them back via `JSON`-decode, so every get yields a
8
+ * fresh object and every save stored an independent byte snapshot BY
9
+ * CONSTRUCTION — the old `structuredClone`-on-every-get/put discipline is now
10
+ * structural, not a rule a maintainer has to remember. The driver therefore
11
+ * stores the byte reference directly: those bytes are never mutated by the
12
+ * kernel or exposed to callers as a mutable handle.
13
+ */
14
+ export class MemoryStoreDriver implements RawStoreDriver {
15
+ private readonly metadata = new Map<BlockId, Uint8Array>();
16
+ private readonly revisions = new Map<string, Uint8Array>(); // `${blockId}:${rev}` -> actionId bytes
17
+ private readonly pending = new Map<string, Uint8Array>(); // `${blockId}:${actionId}` -> transform bytes
18
+ private readonly transactions = new Map<string, Uint8Array>();
19
+ private readonly materialized = new Map<string, Uint8Array>();
20
+
21
+ private revisionKey(blockId: BlockId, rev: number): string {
22
+ return `${blockId}:${rev}`;
23
+ }
24
+
25
+ private actionKey(blockId: BlockId, actionId: ActionId): string {
26
+ return `${blockId}:${actionId}`;
27
+ }
28
+
29
+ // --- metadata ---
30
+
31
+ async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
32
+ return this.metadata.get(blockId);
33
+ }
34
+
35
+ async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
36
+ this.metadata.set(blockId, value);
37
+ }
38
+
39
+ // --- revisions ---
40
+
41
+ async getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
42
+ return this.revisions.get(this.revisionKey(blockId, rev));
43
+ }
44
+
45
+ async putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
46
+ this.revisions.set(this.revisionKey(blockId, rev), value);
47
+ }
48
+
49
+ async *rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]> {
50
+ // Drain into an array before yielding (drain-before-yield contract): the
51
+ // consumer awaits between yields, and a Map has no live cursor to pin, but
52
+ // this keeps the memory driver's semantics identical to the native backends.
53
+ const results: [number, Uint8Array][] = [];
54
+ for (let rev = lo; rev <= hi; rev++) {
55
+ const value = this.revisions.get(this.revisionKey(blockId, rev));
56
+ if (value !== undefined) {
57
+ results.push([rev, value]);
58
+ }
59
+ }
60
+ if (reverse) {
61
+ results.reverse();
62
+ }
63
+ for (const result of results) {
64
+ yield result;
65
+ }
66
+ }
67
+
68
+ // --- pending ---
69
+
70
+ async getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
71
+ return this.pending.get(this.actionKey(blockId, actionId));
72
+ }
73
+
74
+ async putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
75
+ this.pending.set(this.actionKey(blockId, actionId), value);
76
+ }
77
+
78
+ async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
79
+ this.pending.delete(this.actionKey(blockId, actionId));
80
+ }
81
+
82
+ async *listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId> {
83
+ const prefix = `${blockId}:`;
84
+ // Snapshot before yielding (drain-before-yield): a concurrent putPending during
85
+ // the scan must not invalidate a live map iterator.
86
+ const ids: ActionId[] = [];
87
+ for (const key of Array.from(this.pending.keys())) {
88
+ if (key.startsWith(prefix)) {
89
+ ids.push(key.substring(prefix.length) as ActionId);
90
+ }
91
+ }
92
+ for (const id of ids) {
93
+ yield id;
94
+ }
95
+ }
96
+
97
+ // --- transactions ---
98
+
99
+ async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
100
+ return this.transactions.get(this.actionKey(blockId, actionId));
101
+ }
102
+
103
+ async putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
104
+ this.transactions.set(this.actionKey(blockId, actionId), value);
105
+ }
106
+
107
+ // --- materialized ---
108
+
109
+ async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
110
+ return this.materialized.get(this.actionKey(blockId, actionId));
111
+ }
112
+
113
+ async putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
114
+ this.materialized.set(this.actionKey(blockId, actionId), value);
115
+ }
116
+
117
+ async deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void> {
118
+ this.materialized.delete(this.actionKey(blockId, actionId));
119
+ }
120
+
121
+ // --- promote (atomic move; synchronous Map ops make it indivisible here) ---
122
+
123
+ async promote(blockId: BlockId, actionId: ActionId): Promise<void> {
124
+ const key = this.actionKey(blockId, actionId);
125
+ const value = this.pending.get(key);
126
+ if (value === undefined) {
127
+ throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
128
+ }
129
+ this.transactions.set(key, value);
130
+ this.pending.delete(key);
131
+ }
132
+
133
+ // --- optional passthroughs ---
134
+
135
+ async *listBlockIds(): AsyncIterable<BlockId> {
136
+ // Snapshot the keys before yielding so a concurrent putMetadata during the scan
137
+ // doesn't invalidate a live map iterator. Fresh in-memory storage is empty, so at
138
+ // real process startup this yields nothing — it exists so the seed path can be
139
+ // unit-tested against a pre-populated store.
140
+ for (const blockId of Array.from(this.metadata.keys())) {
141
+ yield blockId;
142
+ }
143
+ }
144
+
145
+ async approximateBytesUsed(): Promise<number> {
146
+ let total = 0;
147
+ for (const [blockId, value] of this.metadata) {
148
+ total += blockId.length + value.byteLength;
149
+ }
150
+ for (const store of [this.revisions, this.pending, this.transactions, this.materialized]) {
151
+ for (const [key, value] of store) {
152
+ total += key.length + value.byteLength;
153
+ }
154
+ }
155
+ return total;
156
+ }
157
+ }
@@ -1,42 +1,42 @@
1
- import type { ActionId } from "@optimystic/db-core";
2
-
3
- /**
4
- * Value codec shared by every `KvRawStorage`-backed store. Values cross the
5
- * `RawStoreDriver` boundary as `Uint8Array`, so this module is the single place
6
- * that turns `BlockMetadata`/`Transform`/`IBlock` (via JSON) and the `ActionId`
7
- * string (via UTF-8) into bytes and back.
8
- *
9
- * Because a get always decodes fresh bytes into a NEW object and a save always
10
- * encodes an independent byte copy, the clone-on-store / clone-on-read invariant
11
- * that in-memory storage used to enforce by hand (`structuredClone`) is now
12
- * structural for kernel-backed stores — see docs/internals.md "Storage Returns
13
- * References".
14
- *
15
- * Round-trip fidelity matters for `BlockMetadata`: an open-ended `RevisionRange`
16
- * is encoded `[E]` (one element, upper bound `undefined`). `JSON.stringify([5])`
17
- * → `"[5]"` → `JSON.parse` → `[5]`, so the open-ended encoding survives byte-exact.
18
- * Do NOT normalize ranges here.
19
- */
20
-
21
- const encoder = new TextEncoder();
22
- const decoder = new TextDecoder();
23
-
24
- /** JSON-encode a value (`BlockMetadata` / `Transform` / `IBlock`) to UTF-8 bytes. */
25
- export function encodeJson<T>(value: T): Uint8Array {
26
- return encoder.encode(JSON.stringify(value));
27
- }
28
-
29
- /** Decode UTF-8 JSON bytes back into a value. Callers guard the miss (`undefined`) first. */
30
- export function decodeJson<T>(bytes: Uint8Array): T {
31
- return JSON.parse(decoder.decode(bytes)) as T;
32
- }
33
-
34
- /** Encode an `ActionId` string to UTF-8 bytes (stored as the revisions-store value). */
35
- export function encodeActionId(actionId: ActionId): Uint8Array {
36
- return encoder.encode(actionId);
37
- }
38
-
39
- /** Decode UTF-8 bytes back into an `ActionId` string. */
40
- export function decodeActionId(bytes: Uint8Array): ActionId {
41
- return decoder.decode(bytes) as ActionId;
42
- }
1
+ import type { ActionId } from "@optimystic/db-core";
2
+
3
+ /**
4
+ * Value codec shared by every `KvRawStorage`-backed store. Values cross the
5
+ * `RawStoreDriver` boundary as `Uint8Array`, so this module is the single place
6
+ * that turns `BlockMetadata`/`Transform`/`IBlock` (via JSON) and the `ActionId`
7
+ * string (via UTF-8) into bytes and back.
8
+ *
9
+ * Because a get always decodes fresh bytes into a NEW object and a save always
10
+ * encodes an independent byte copy, the clone-on-store / clone-on-read invariant
11
+ * that in-memory storage used to enforce by hand (`structuredClone`) is now
12
+ * structural for kernel-backed stores — see docs/internals.md "Storage Returns
13
+ * References".
14
+ *
15
+ * Round-trip fidelity matters for `BlockMetadata`: an open-ended `RevisionRange`
16
+ * is encoded `[E]` (one element, upper bound `undefined`). `JSON.stringify([5])`
17
+ * → `"[5]"` → `JSON.parse` → `[5]`, so the open-ended encoding survives byte-exact.
18
+ * Do NOT normalize ranges here.
19
+ */
20
+
21
+ const encoder = new TextEncoder();
22
+ const decoder = new TextDecoder();
23
+
24
+ /** JSON-encode a value (`BlockMetadata` / `Transform` / `IBlock`) to UTF-8 bytes. */
25
+ export function encodeJson<T>(value: T): Uint8Array {
26
+ return encoder.encode(JSON.stringify(value));
27
+ }
28
+
29
+ /** Decode UTF-8 JSON bytes back into a value. Callers guard the miss (`undefined`) first. */
30
+ export function decodeJson<T>(bytes: Uint8Array): T {
31
+ return JSON.parse(decoder.decode(bytes)) as T;
32
+ }
33
+
34
+ /** Encode an `ActionId` string to UTF-8 bytes (stored as the revisions-store value). */
35
+ export function encodeActionId(actionId: ActionId): Uint8Array {
36
+ return encoder.encode(actionId);
37
+ }
38
+
39
+ /** Decode UTF-8 bytes back into an `ActionId` string. */
40
+ export function decodeActionId(bytes: Uint8Array): ActionId {
41
+ return decoder.decode(bytes) as ActionId;
42
+ }
@@ -1,80 +1,80 @@
1
- import type { BlockId, ActionId } from "@optimystic/db-core";
2
-
3
- /**
4
- * Bytes-valued, per-logical-store driver surface. Each backend implements the
5
- * five block-storage stores (metadata, revisions, pending, transactions,
6
- * materialized) over its native mechanism (LevelDB tag-ranges, five SQLite
7
- * tables, five IndexedDB object stores, five filesystem subdirectories, five
8
- * in-memory maps). `KvRawStorage` layers all JSON serialization and call
9
- * orchestration on top — drivers never (de)serialize values and never see the
10
- * `BlockMetadata`/`Transform`/`IBlock` types. Drivers speak only
11
- * `Uint8Array`/`BlockId`/`ActionId`/`number`.
12
- *
13
- * ### Iteration semantics (drain-before-yield)
14
- *
15
- * `rangeRevisions` and `listPendingActionIds` return an `AsyncIterable`, but a
16
- * driver MUST drain its native cursor/iterator into memory BEFORE yielding to
17
- * the consumer. A live LevelDB iterator, IndexedDB transaction, or SQLite
18
- * cursor must not straddle the consumer's `await`s: IndexedDB auto-commits an
19
- * idle transaction, SQLite would hold its mutex slot, and LevelDB pins native
20
- * resources. The kernel encodes this as a contract (not a shared implementation)
21
- * because the drain is backend-specific. The conformance suite exercises it by
22
- * interleaving other awaits between yielded items.
23
- *
24
- * ### Promote atomicity
25
- *
26
- * `promote` is the ONLY cross-key atomic operation the kernel requires. Every
27
- * other write is a single put/delete. Each backend satisfies `promote` with its
28
- * native atomic mechanism (LevelDB batch, SQLite transaction, IndexedDB
29
- * readwrite transaction, filesystem rename); the kernel never assumes an
30
- * atomicity a backend cannot deliver.
31
- */
32
- export interface RawStoreDriver {
33
- // metadata store — keyed by blockId
34
- getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
35
- putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
36
-
37
- // revisions store — keyed by (blockId, rev), ORDERED BY rev
38
- getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
39
- putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
40
- /**
41
- * Yield `[rev, value]` for every present rev in `[lo, hi]` (both inclusive),
42
- * ascending when `reverse` is false, descending when `reverse` is true. The
43
- * driver MUST drain any native cursor into memory before yielding — see the
44
- * "drain-before-yield" contract above.
45
- */
46
- rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]>;
47
-
48
- // pending store — keyed by (blockId, actionId)
49
- getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
50
- putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
51
- deletePending(blockId: BlockId, actionId: ActionId): Promise<void>;
52
- /** Yield each present pending actionId for the block. MUST drain before yielding (see above). */
53
- listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
54
-
55
- // transactions store — keyed by (blockId, actionId)
56
- getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
57
- putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
58
-
59
- // materialized store — keyed by (blockId, actionId)
60
- getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
61
- putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
62
- deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
63
-
64
- /**
65
- * Atomically move `pending(blockId, actionId)` → `transactions(blockId, actionId)`:
66
- * write the transactions entry and remove the pending entry as one indivisible
67
- * step (batch / DB transaction / rename). A crash must leave exactly one of the
68
- * two states, never both/neither. Throw
69
- * `Pending action <actionId> not found for block <blockId>` when no pending
70
- * entry exists. This is the ONLY cross-key atomic operation the kernel requires.
71
- */
72
- promote(blockId: BlockId, actionId: ActionId): Promise<void>;
73
-
74
- /** Optional — enumerate block ids with durable metadata (startup seed). Passed through by the kernel. */
75
- listBlockIds?(): AsyncIterable<BlockId>;
76
- /** Optional — best cheap byte estimate. Passed through by the kernel. */
77
- approximateBytesUsed?(): Promise<number>;
78
- /** Optional — release the underlying handle. */
79
- close?(): Promise<void>;
80
- }
1
+ import type { BlockId, ActionId } from "@optimystic/db-core";
2
+
3
+ /**
4
+ * Bytes-valued, per-logical-store driver surface. Each backend implements the
5
+ * five block-storage stores (metadata, revisions, pending, transactions,
6
+ * materialized) over its native mechanism (LevelDB tag-ranges, five SQLite
7
+ * tables, five IndexedDB object stores, five filesystem subdirectories, five
8
+ * in-memory maps). `KvRawStorage` layers all JSON serialization and call
9
+ * orchestration on top — drivers never (de)serialize values and never see the
10
+ * `BlockMetadata`/`Transform`/`IBlock` types. Drivers speak only
11
+ * `Uint8Array`/`BlockId`/`ActionId`/`number`.
12
+ *
13
+ * ### Iteration semantics (drain-before-yield)
14
+ *
15
+ * `rangeRevisions` and `listPendingActionIds` return an `AsyncIterable`, but a
16
+ * driver MUST drain its native cursor/iterator into memory BEFORE yielding to
17
+ * the consumer. A live LevelDB iterator, IndexedDB transaction, or SQLite
18
+ * cursor must not straddle the consumer's `await`s: IndexedDB auto-commits an
19
+ * idle transaction, SQLite would hold its mutex slot, and LevelDB pins native
20
+ * resources. The kernel encodes this as a contract (not a shared implementation)
21
+ * because the drain is backend-specific. The conformance suite exercises it by
22
+ * interleaving other awaits between yielded items.
23
+ *
24
+ * ### Promote atomicity
25
+ *
26
+ * `promote` is the ONLY cross-key atomic operation the kernel requires. Every
27
+ * other write is a single put/delete. Each backend satisfies `promote` with its
28
+ * native atomic mechanism (LevelDB batch, SQLite transaction, IndexedDB
29
+ * readwrite transaction, filesystem rename); the kernel never assumes an
30
+ * atomicity a backend cannot deliver.
31
+ */
32
+ export interface RawStoreDriver {
33
+ // metadata store — keyed by blockId
34
+ getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
35
+ putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
36
+
37
+ // revisions store — keyed by (blockId, rev), ORDERED BY rev
38
+ getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
39
+ putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
40
+ /**
41
+ * Yield `[rev, value]` for every present rev in `[lo, hi]` (both inclusive),
42
+ * ascending when `reverse` is false, descending when `reverse` is true. The
43
+ * driver MUST drain any native cursor into memory before yielding — see the
44
+ * "drain-before-yield" contract above.
45
+ */
46
+ rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]>;
47
+
48
+ // pending store — keyed by (blockId, actionId)
49
+ getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
50
+ putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
51
+ deletePending(blockId: BlockId, actionId: ActionId): Promise<void>;
52
+ /** Yield each present pending actionId for the block. MUST drain before yielding (see above). */
53
+ listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
54
+
55
+ // transactions store — keyed by (blockId, actionId)
56
+ getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
57
+ putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
58
+
59
+ // materialized store — keyed by (blockId, actionId)
60
+ getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
61
+ putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
62
+ deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
63
+
64
+ /**
65
+ * Atomically move `pending(blockId, actionId)` → `transactions(blockId, actionId)`:
66
+ * write the transactions entry and remove the pending entry as one indivisible
67
+ * step (batch / DB transaction / rename). A crash must leave exactly one of the
68
+ * two states, never both/neither. Throw
69
+ * `Pending action <actionId> not found for block <blockId>` when no pending
70
+ * entry exists. This is the ONLY cross-key atomic operation the kernel requires.
71
+ */
72
+ promote(blockId: BlockId, actionId: ActionId): Promise<void>;
73
+
74
+ /** Optional — enumerate block ids with durable metadata (startup seed). Passed through by the kernel. */
75
+ listBlockIds?(): AsyncIterable<BlockId>;
76
+ /** Optional — best cheap byte estimate. Passed through by the kernel. */
77
+ approximateBytesUsed?(): Promise<number>;
78
+ /** Optional — release the underlying handle. */
79
+ close?(): Promise<void>;
80
+ }