@optimystic/db-p2p 0.21.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 (219) 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-policy.d.ts +13 -2
  6. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-policy.js +51 -4
  8. package/dist/src/cluster/cluster-policy.js.map +1 -1
  9. package/dist/src/cluster/cluster-repo.d.ts +42 -17
  10. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  11. package/dist/src/cluster/cluster-repo.js +229 -122
  12. package/dist/src/cluster/cluster-repo.js.map +1 -1
  13. package/dist/src/cluster/cluster-size-coupling.d.ts +28 -0
  14. package/dist/src/cluster/cluster-size-coupling.d.ts.map +1 -0
  15. package/dist/src/cluster/cluster-size-coupling.js +35 -0
  16. package/dist/src/cluster/cluster-size-coupling.js.map +1 -0
  17. package/dist/src/cluster/quorum-restore.d.ts +6 -0
  18. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  19. package/dist/src/cluster/quorum-restore.js +1 -1
  20. package/dist/src/cluster/quorum-restore.js.map +1 -1
  21. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  22. package/dist/src/cluster/reconcile-block.js +15 -3
  23. package/dist/src/cluster/reconcile-block.js.map +1 -1
  24. package/dist/src/cluster/service.d.ts +32 -1
  25. package/dist/src/cluster/service.d.ts.map +1 -1
  26. package/dist/src/cluster/service.js +43 -2
  27. package/dist/src/cluster/service.js.map +1 -1
  28. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  29. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  30. package/dist/src/cohort-topic/stream-util.js +56 -10
  31. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  32. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  33. package/dist/src/dispute/dispute-service.js +9 -3
  34. package/dist/src/dispute/dispute-service.js.map +1 -1
  35. package/dist/src/index.d.ts +5 -0
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +5 -0
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/libp2p-key-network.d.ts +134 -7
  40. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  41. package/dist/src/libp2p-key-network.js +174 -37
  42. package/dist/src/libp2p-key-network.js.map +1 -1
  43. package/dist/src/libp2p-node-base.d.ts +3 -2
  44. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  45. package/dist/src/libp2p-node-base.js +859 -778
  46. package/dist/src/libp2p-node-base.js.map +1 -1
  47. package/dist/src/libp2p-node-rn.d.ts +2 -2
  48. package/dist/src/libp2p-node-rn.d.ts.map +1 -1
  49. package/dist/src/libp2p-node-rn.js.map +1 -1
  50. package/dist/src/libp2p-node.d.ts +2 -2
  51. package/dist/src/libp2p-node.d.ts.map +1 -1
  52. package/dist/src/libp2p-node.js.map +1 -1
  53. package/dist/src/logger.d.ts +17 -1
  54. package/dist/src/logger.d.ts.map +1 -1
  55. package/dist/src/logger.js +19 -2
  56. package/dist/src/logger.js.map +1 -1
  57. package/dist/src/network/network-manager-service.d.ts +2 -0
  58. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  59. package/dist/src/network/network-manager-service.js +4 -0
  60. package/dist/src/network/network-manager-service.js.map +1 -1
  61. package/dist/src/optimystic-node.d.ts +35 -0
  62. package/dist/src/optimystic-node.d.ts.map +1 -0
  63. package/dist/src/optimystic-node.js +2 -0
  64. package/dist/src/optimystic-node.js.map +1 -0
  65. package/dist/src/owned-block-seed.d.ts +6 -3
  66. package/dist/src/owned-block-seed.d.ts.map +1 -1
  67. package/dist/src/owned-block-seed.js +16 -3
  68. package/dist/src/owned-block-seed.js.map +1 -1
  69. package/dist/src/peer-address-book.d.ts +72 -0
  70. package/dist/src/peer-address-book.d.ts.map +1 -0
  71. package/dist/src/peer-address-book.js +123 -0
  72. package/dist/src/peer-address-book.js.map +1 -0
  73. package/dist/src/repo/client.d.ts.map +1 -1
  74. package/dist/src/repo/client.js +11 -2
  75. package/dist/src/repo/client.js.map +1 -1
  76. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  77. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  78. package/dist/src/repo/cluster-coordinator.js +95 -3
  79. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  80. package/dist/src/repo/coordinator-repo.d.ts +78 -14
  81. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  82. package/dist/src/repo/coordinator-repo.js +266 -81
  83. package/dist/src/repo/coordinator-repo.js.map +1 -1
  84. package/dist/src/rn.d.ts +5 -0
  85. package/dist/src/rn.d.ts.map +1 -1
  86. package/dist/src/rn.js +5 -0
  87. package/dist/src/rn.js.map +1 -1
  88. package/dist/src/storage/block-storage.d.ts.map +1 -1
  89. package/dist/src/storage/block-storage.js +57 -5
  90. package/dist/src/storage/block-storage.js.map +1 -1
  91. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  92. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  93. package/dist/src/storage/cached-raw-storage.js +152 -0
  94. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  95. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  96. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  97. package/dist/src/storage/cached-store-driver.js +775 -0
  98. package/dist/src/storage/cached-store-driver.js.map +1 -0
  99. package/dist/src/storage/i-block-storage.d.ts +20 -1
  100. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  101. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  102. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  103. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  104. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  105. package/dist/src/storage/shared-cache-pool.js +354 -0
  106. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  107. package/dist/src/storage/storage-repo.d.ts +56 -3
  108. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  109. package/dist/src/storage/storage-repo.js +124 -18
  110. package/dist/src/storage/storage-repo.js.map +1 -1
  111. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  112. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  113. package/dist/src/testing/raw-storage-conformance.js +52 -2
  114. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  115. package/package.json +3 -3
  116. package/readme.md +668 -653
  117. package/src/cluster/block-transfer.ts +424 -424
  118. package/src/cluster/client.ts +119 -88
  119. package/src/cluster/cluster-error.ts +64 -64
  120. package/src/cluster/cluster-policy.ts +203 -152
  121. package/src/cluster/cluster-repo.ts +245 -125
  122. package/src/cluster/cluster-size-coupling.ts +45 -0
  123. package/src/cluster/commit-cert.ts +139 -139
  124. package/src/cluster/i-transaction-state-store.ts +43 -43
  125. package/src/cluster/memory-transaction-state-store.ts +56 -56
  126. package/src/cluster/peer-key-binding.ts +37 -37
  127. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  128. package/src/cluster/quorum-restore.ts +223 -223
  129. package/src/cluster/reconcile-block.ts +203 -191
  130. package/src/cluster/service.ts +293 -241
  131. package/src/cluster/supermajority-coupling.ts +37 -37
  132. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  133. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  134. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  135. package/src/cohort-topic/change-bridge.ts +109 -109
  136. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  137. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  138. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  139. package/src/cohort-topic/host.ts +2901 -2901
  140. package/src/cohort-topic/index.ts +13 -13
  141. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  142. package/src/cohort-topic/membership-source.ts +68 -68
  143. package/src/cohort-topic/peer-codec.ts +31 -31
  144. package/src/cohort-topic/peer-sig.ts +86 -86
  145. package/src/cohort-topic/protocols.ts +71 -71
  146. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  147. package/src/cohort-topic/size-estimator.ts +16 -16
  148. package/src/cohort-topic/stream-util.ts +135 -87
  149. package/src/cohort-topic/threshold-crypto.ts +239 -239
  150. package/src/cohort-topic/topic-router.ts +77 -77
  151. package/src/dispute/arbitrator-selection.ts +138 -138
  152. package/src/dispute/cascade.ts +524 -524
  153. package/src/dispute/dispute-service.ts +11 -5
  154. package/src/dispute/invalidation.ts +625 -625
  155. package/src/inbound-authorization.ts +190 -190
  156. package/src/index.ts +52 -47
  157. package/src/libp2p-key-network.ts +1120 -958
  158. package/src/libp2p-node-base.ts +1675 -1591
  159. package/src/libp2p-node-rn.ts +30 -30
  160. package/src/libp2p-node.ts +36 -36
  161. package/src/logger.ts +19 -2
  162. package/src/matchmaking/aggregate-counts.ts +104 -104
  163. package/src/matchmaking/index.ts +20 -20
  164. package/src/matchmaking/module.ts +363 -363
  165. package/src/matchmaking/protocols.ts +51 -51
  166. package/src/matchmaking/provider-manager.ts +95 -95
  167. package/src/matchmaking/query-handler.ts +88 -88
  168. package/src/matchmaking/query-transport.ts +492 -492
  169. package/src/matchmaking/seeker-manager.ts +64 -64
  170. package/src/matchmaking/seeker-walk-client.ts +293 -293
  171. package/src/matchmaking/traffic-validation.ts +195 -195
  172. package/src/network/network-manager-service.ts +5 -0
  173. package/src/optimystic-node.ts +36 -0
  174. package/src/owned-block-seed.ts +53 -40
  175. package/src/peer-address-book.ts +149 -0
  176. package/src/protocol-limits.ts +33 -33
  177. package/src/reactivity/forwarder-host.ts +438 -438
  178. package/src/reactivity/index.ts +19 -19
  179. package/src/reactivity/notify-transport.ts +144 -144
  180. package/src/reactivity/origination-manager.ts +192 -192
  181. package/src/reactivity/protocols.ts +61 -61
  182. package/src/reactivity/push-state-gossip.ts +291 -291
  183. package/src/reactivity/recover-transport.ts +408 -408
  184. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  185. package/src/reactivity/subscriber-registry.ts +96 -96
  186. package/src/reactivity/subscription-manager.ts +450 -450
  187. package/src/reactivity/topic-bytes.ts +37 -37
  188. package/src/repo/client.ts +12 -2
  189. package/src/repo/cluster-coordinator.ts +99 -3
  190. package/src/repo/coordinator-repo.ts +305 -82
  191. package/src/repo/types.ts +7 -7
  192. package/src/rn.ts +39 -34
  193. package/src/rpc-deadline.ts +45 -45
  194. package/src/storage/arachnode-partition.ts +74 -74
  195. package/src/storage/block-storage.ts +59 -6
  196. package/src/storage/cached-raw-storage.ts +180 -0
  197. package/src/storage/cached-store-driver.ts +859 -0
  198. package/src/storage/i-block-storage.ts +20 -1
  199. package/src/storage/i-kv-store.ts +8 -8
  200. package/src/storage/i-raw-storage.ts +12 -5
  201. package/src/storage/kv-raw-storage.ts +135 -135
  202. package/src/storage/memory-kv-store.ts +28 -28
  203. package/src/storage/memory-storage.ts +25 -25
  204. package/src/storage/memory-store-driver.ts +157 -157
  205. package/src/storage/raw-store-codec.ts +42 -42
  206. package/src/storage/raw-store-driver.ts +80 -80
  207. package/src/storage/ring-selector.ts +317 -317
  208. package/src/storage/ring-shift-coordinator.ts +271 -271
  209. package/src/storage/shared-cache-pool.ts +452 -0
  210. package/src/storage/storage-repo.ts +1014 -903
  211. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  212. package/src/testing/index.ts +8 -8
  213. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  214. package/src/testing/raw-storage-conformance.ts +453 -397
  215. package/src/testing/reactivity-mesh-harness.ts +922 -922
  216. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  217. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  218. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  219. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -47,7 +47,18 @@ export interface IBlockStorage {
47
47
  /** Saves a revision */
48
48
  saveRevision(rev: number, actionId: ActionId): Promise<void>;
49
49
 
50
- /** Promotes a pending action to committed */
50
+ /**
51
+ * Promotes a pending action to committed, MOVING the record from the pending namespace to the
52
+ * committed one in a single atomic step.
53
+ *
54
+ * **Invariant P** — a block never holds a pending record and a committed record for the same
55
+ * action id at the same time. This method maintains it on the commit path; every OTHER writer of
56
+ * a committed transform for a block ({@link saveReplica}, {@link saveDeletion}, and any forward
57
+ * path added later) must maintain it too, by deleting that action's pending record when it
58
+ * writes the committed one. A pending record left beside a committed one can never be promoted,
59
+ * and is reported as a phantom conflicting action by `StorageRepo.pend` on every later write to
60
+ * the block.
61
+ */
51
62
  promotePendingTransaction(actionId: ActionId): Promise<void>;
52
63
 
53
64
  /** Sets the latest revision information */
@@ -62,6 +73,11 @@ export interface IBlockStorage {
62
73
  * it falls back to `rev = 1` and a deterministic `actionId` derived from the block
63
74
  * (so retries stay idempotent — never random).
64
75
  *
76
+ * Maintains **Invariant P** (see {@link promotePendingTransaction}): writing the committed
77
+ * transform for `actionId` also deletes that action's pending record on this block, so a node
78
+ * that pended the action but diverged before committing it does not keep an unpromotable record.
79
+ * Only on the write path — the monotonic no-op below deletes nothing.
80
+ *
65
81
  * No-op (still durable) when an equal-or-newer revision is already present: `latest`
66
82
  * is never downgraded. Idempotent for a fixed `(rev, actionId)`. Returns the
67
83
  * effective latest `ActionRev`.
@@ -75,6 +91,9 @@ export interface IBlockStorage {
75
91
  * materialization as a deletion, so a `getBlock()` after a tombstone reads back as *absent*
76
92
  * (`undefined`) while a historical `getBlock(creationRev)` still materializes the created content.
77
93
  *
94
+ * Maintains **Invariant P** (see {@link promotePendingTransaction}) on the write path, exactly as
95
+ * {@link saveReplica} does: the tombstone's `actionId` loses its pending record on this block.
96
+ *
78
97
  * Idempotent for a fixed `(rev, actionId)`; never downgrades `latest` (a no-op — still durable —
79
98
  * when an equal-or-newer revision is already present). Returns the effective latest `ActionRev`.
80
99
  */
@@ -1,8 +1,8 @@
1
- /** Portable async key-value store. Platform packages provide implementations. */
2
- export interface IKVStore {
3
- get(key: string): Promise<string | undefined>;
4
- set(key: string, value: string): Promise<void>;
5
- delete(key: string): Promise<void>;
6
- /** Return all keys matching the given prefix */
7
- list(prefix: string): Promise<string[]>;
8
- }
1
+ /** Portable async key-value store. Platform packages provide implementations. */
2
+ export interface IKVStore {
3
+ get(key: string): Promise<string | undefined>;
4
+ set(key: string, value: string): Promise<void>;
5
+ delete(key: string): Promise<void>;
6
+ /** Return all keys matching the given prefix */
7
+ list(prefix: string): Promise<string[]>;
8
+ }
@@ -39,11 +39,18 @@ export interface IRawStorage {
39
39
  getApproximateBytesUsed?(): Promise<number>;
40
40
 
41
41
  /**
42
- * Enumerate the block ids that currently have durable state in this backend
43
- * (one id per block that has committed/replicated metadata). Used at node
44
- * startup to seed the resilience monitors' owned-block tracked set from blocks
45
- * already on disk from a previous run, so churn-spread / rebalance protection
46
- * covers them without waiting for each to be touched again.
42
+ * Enumerate the block ids that currently have durable state in this backend
43
+ * one id per block with ANY durable metadata. That includes a block whose only
44
+ * durable state is an uncommitted pending transform, since
45
+ * `BlockStorage.savePendingTransaction` writes metadata for a block that has none
46
+ * before storing the transform. Implementations enumerate metadata keys only; they
47
+ * must NOT read or decode each block's metadata to filter by committed revision
48
+ * (that would turn a cheap key scan into a per-block read).
49
+ *
50
+ * Used at node startup to seed the resilience monitors' owned-block tracked set
51
+ * from blocks already on disk from a previous run, so churn-spread / rebalance
52
+ * protection covers them without waiting for each to be touched again. See the
53
+ * NOTE in `seedOwnedBlocksFromStorage` for why the over-inclusion is accepted.
47
54
  *
48
55
  * Streamed (AsyncIterable) so a large store does not force the whole id list
49
56
  * into memory at once. Order is unspecified. Optional: a backend that omits it
@@ -1,135 +1,135 @@
1
- import type { BlockId, ActionId, ActionRev, Transform, IBlock } from "@optimystic/db-core";
2
- import type { BlockMetadata } from "./struct.js";
3
- import type { IRawStorage } from "./i-raw-storage.js";
4
- import type { RawStoreDriver } from "./raw-store-driver.js";
5
- import { encodeJson, decodeJson, encodeActionId, decodeActionId } from "./raw-store-codec.js";
6
-
7
- /**
8
- * Shared ordered-KV storage kernel. Implements the full {@link IRawStorage}
9
- * surface over a {@link RawStoreDriver}, owning all value (de)serialization and
10
- * call orchestration so each backend only has to expose its five logical stores
11
- * as bytes-valued maps over its native mechanism.
12
- *
13
- * The genuinely-shared logic lives ABOVE the storage primitive: the JSON/string
14
- * codec for the four value types, `listRevisions`' lo/hi/reverse bound
15
- * computation, `saveMaterializedBlock`'s put-or-delete branch, and the
16
- * passthroughs. Key layout / storage topology stays in the driver, because the
17
- * backends do NOT share one — LevelDB is a single ordered byte keyspace, SQLite
18
- * five relational tables, IndexedDB five object stores, the filesystem a
19
- * directory tree.
20
- */
21
- export class KvRawStorage implements IRawStorage {
22
- /**
23
- * Optional passthroughs are wired in the constructor ONLY when the driver
24
- * provides them, so a `StorageMonitor` / owned-block seed that feature-detects
25
- * (`typeof storage.listBlockIds === 'function'`) sees the driver's true
26
- * capability instead of a stub that silently reports 0 / no seed.
27
- */
28
- listBlockIds?: () => AsyncIterable<BlockId>;
29
- getApproximateBytesUsed?: () => Promise<number>;
30
-
31
- constructor(private readonly driver: RawStoreDriver) {
32
- if (driver.listBlockIds) {
33
- this.listBlockIds = () => driver.listBlockIds!();
34
- }
35
- if (driver.approximateBytesUsed) {
36
- this.getApproximateBytesUsed = () => driver.approximateBytesUsed!();
37
- }
38
- }
39
-
40
- // --- Metadata ---
41
-
42
- async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
43
- const bytes = await this.driver.getMetadata(blockId);
44
- return bytes === undefined ? undefined : decodeJson<BlockMetadata>(bytes);
45
- }
46
-
47
- // NOTE: every value write funnels through the driver put/delete calls in the
48
- // methods below (saveMetadata / saveRevision / save*Transaction /
49
- // saveMaterializedBlock). This is the single choke point where an incremental
50
- // byte counter would hook in, replacing the per-driver full-scan
51
- // getApproximateBytesUsed. The chosen capacity-estimate mechanism is instead a
52
- // short-TTL cache over the full scan in StorageMonitor (see storage-monitor.ts
53
- // `usedBytesCacheTtlMs`) — backend-agnostic, no write-path changes, and ring
54
- // selection tolerates the bounded staleness by design. The counter remains a
55
- // future option only if TTL staleness ever proves insufficient; do NOT
56
- // implement it here — leave the write path a single seam.
57
- async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
58
- await this.driver.putMetadata(blockId, encodeJson(metadata));
59
- }
60
-
61
- // --- Revisions ---
62
-
63
- async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
64
- const bytes = await this.driver.getRevision(blockId, rev);
65
- return bytes === undefined ? undefined : decodeActionId(bytes);
66
- }
67
-
68
- async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
69
- await this.driver.putRevision(blockId, rev, encodeActionId(actionId));
70
- }
71
-
72
- async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
73
- // Both bounds inclusive; ordering follows startRev→endRev direction. Empty
74
- // revs are skipped by the driver's range. Preserves the exact semantics of
75
- // every existing backend.
76
- const ascending = startRev <= endRev;
77
- const lo = ascending ? startRev : endRev;
78
- const hi = ascending ? endRev : startRev;
79
- for await (const [rev, bytes] of this.driver.rangeRevisions(blockId, lo, hi, !ascending)) {
80
- yield { rev, actionId: decodeActionId(bytes) };
81
- }
82
- }
83
-
84
- // --- Pending transactions ---
85
-
86
- async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
87
- const bytes = await this.driver.getPending(blockId, actionId);
88
- return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
89
- }
90
-
91
- async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
92
- await this.driver.putPending(blockId, actionId, encodeJson(transform));
93
- }
94
-
95
- async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
96
- await this.driver.deletePending(blockId, actionId);
97
- }
98
-
99
- async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
100
- yield* this.driver.listPendingActionIds(blockId);
101
- }
102
-
103
- // --- Committed transactions ---
104
-
105
- async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
106
- const bytes = await this.driver.getTransaction(blockId, actionId);
107
- return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
108
- }
109
-
110
- async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
111
- await this.driver.putTransaction(blockId, actionId, encodeJson(transform));
112
- }
113
-
114
- // --- Materialized blocks ---
115
-
116
- async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
117
- const bytes = await this.driver.getMaterialized(blockId, actionId);
118
- return bytes === undefined ? undefined : decodeJson<IBlock>(bytes);
119
- }
120
-
121
- async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
122
- // Present ⇒ put the materialized block; absent ⇒ delete (tombstone / truncation).
123
- if (block) {
124
- await this.driver.putMaterialized(blockId, actionId, encodeJson(block));
125
- } else {
126
- await this.driver.deleteMaterialized(blockId, actionId);
127
- }
128
- }
129
-
130
- // --- Promote (the only cross-key atomic op; driver owns atomicity) ---
131
-
132
- async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
133
- await this.driver.promote(blockId, actionId);
134
- }
135
- }
1
+ import type { BlockId, ActionId, ActionRev, Transform, IBlock } from "@optimystic/db-core";
2
+ import type { BlockMetadata } from "./struct.js";
3
+ import type { IRawStorage } from "./i-raw-storage.js";
4
+ import type { RawStoreDriver } from "./raw-store-driver.js";
5
+ import { encodeJson, decodeJson, encodeActionId, decodeActionId } from "./raw-store-codec.js";
6
+
7
+ /**
8
+ * Shared ordered-KV storage kernel. Implements the full {@link IRawStorage}
9
+ * surface over a {@link RawStoreDriver}, owning all value (de)serialization and
10
+ * call orchestration so each backend only has to expose its five logical stores
11
+ * as bytes-valued maps over its native mechanism.
12
+ *
13
+ * The genuinely-shared logic lives ABOVE the storage primitive: the JSON/string
14
+ * codec for the four value types, `listRevisions`' lo/hi/reverse bound
15
+ * computation, `saveMaterializedBlock`'s put-or-delete branch, and the
16
+ * passthroughs. Key layout / storage topology stays in the driver, because the
17
+ * backends do NOT share one — LevelDB is a single ordered byte keyspace, SQLite
18
+ * five relational tables, IndexedDB five object stores, the filesystem a
19
+ * directory tree.
20
+ */
21
+ export class KvRawStorage implements IRawStorage {
22
+ /**
23
+ * Optional passthroughs are wired in the constructor ONLY when the driver
24
+ * provides them, so a `StorageMonitor` / owned-block seed that feature-detects
25
+ * (`typeof storage.listBlockIds === 'function'`) sees the driver's true
26
+ * capability instead of a stub that silently reports 0 / no seed.
27
+ */
28
+ listBlockIds?: () => AsyncIterable<BlockId>;
29
+ getApproximateBytesUsed?: () => Promise<number>;
30
+
31
+ constructor(private readonly driver: RawStoreDriver) {
32
+ if (driver.listBlockIds) {
33
+ this.listBlockIds = () => driver.listBlockIds!();
34
+ }
35
+ if (driver.approximateBytesUsed) {
36
+ this.getApproximateBytesUsed = () => driver.approximateBytesUsed!();
37
+ }
38
+ }
39
+
40
+ // --- Metadata ---
41
+
42
+ async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
43
+ const bytes = await this.driver.getMetadata(blockId);
44
+ return bytes === undefined ? undefined : decodeJson<BlockMetadata>(bytes);
45
+ }
46
+
47
+ // NOTE: every value write funnels through the driver put/delete calls in the
48
+ // methods below (saveMetadata / saveRevision / save*Transaction /
49
+ // saveMaterializedBlock). This is the single choke point where an incremental
50
+ // byte counter would hook in, replacing the per-driver full-scan
51
+ // getApproximateBytesUsed. The chosen capacity-estimate mechanism is instead a
52
+ // short-TTL cache over the full scan in StorageMonitor (see storage-monitor.ts
53
+ // `usedBytesCacheTtlMs`) — backend-agnostic, no write-path changes, and ring
54
+ // selection tolerates the bounded staleness by design. The counter remains a
55
+ // future option only if TTL staleness ever proves insufficient; do NOT
56
+ // implement it here — leave the write path a single seam.
57
+ async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
58
+ await this.driver.putMetadata(blockId, encodeJson(metadata));
59
+ }
60
+
61
+ // --- Revisions ---
62
+
63
+ async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
64
+ const bytes = await this.driver.getRevision(blockId, rev);
65
+ return bytes === undefined ? undefined : decodeActionId(bytes);
66
+ }
67
+
68
+ async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
69
+ await this.driver.putRevision(blockId, rev, encodeActionId(actionId));
70
+ }
71
+
72
+ async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
73
+ // Both bounds inclusive; ordering follows startRev→endRev direction. Empty
74
+ // revs are skipped by the driver's range. Preserves the exact semantics of
75
+ // every existing backend.
76
+ const ascending = startRev <= endRev;
77
+ const lo = ascending ? startRev : endRev;
78
+ const hi = ascending ? endRev : startRev;
79
+ for await (const [rev, bytes] of this.driver.rangeRevisions(blockId, lo, hi, !ascending)) {
80
+ yield { rev, actionId: decodeActionId(bytes) };
81
+ }
82
+ }
83
+
84
+ // --- Pending transactions ---
85
+
86
+ async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
87
+ const bytes = await this.driver.getPending(blockId, actionId);
88
+ return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
89
+ }
90
+
91
+ async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
92
+ await this.driver.putPending(blockId, actionId, encodeJson(transform));
93
+ }
94
+
95
+ async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
96
+ await this.driver.deletePending(blockId, actionId);
97
+ }
98
+
99
+ async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
100
+ yield* this.driver.listPendingActionIds(blockId);
101
+ }
102
+
103
+ // --- Committed transactions ---
104
+
105
+ async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
106
+ const bytes = await this.driver.getTransaction(blockId, actionId);
107
+ return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
108
+ }
109
+
110
+ async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
111
+ await this.driver.putTransaction(blockId, actionId, encodeJson(transform));
112
+ }
113
+
114
+ // --- Materialized blocks ---
115
+
116
+ async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
117
+ const bytes = await this.driver.getMaterialized(blockId, actionId);
118
+ return bytes === undefined ? undefined : decodeJson<IBlock>(bytes);
119
+ }
120
+
121
+ async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
122
+ // Present ⇒ put the materialized block; absent ⇒ delete (tombstone / truncation).
123
+ if (block) {
124
+ await this.driver.putMaterialized(blockId, actionId, encodeJson(block));
125
+ } else {
126
+ await this.driver.deleteMaterialized(blockId, actionId);
127
+ }
128
+ }
129
+
130
+ // --- Promote (the only cross-key atomic op; driver owns atomicity) ---
131
+
132
+ async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
133
+ await this.driver.promote(blockId, actionId);
134
+ }
135
+ }
@@ -1,28 +1,28 @@
1
- import type { IKVStore } from "./i-kv-store.js";
2
-
3
- /** In-memory IKVStore backed by a Map. Used for testing. */
4
- export class MemoryKVStore implements IKVStore {
5
- private readonly store = new Map<string, string>();
6
-
7
- async get(key: string): Promise<string | undefined> {
8
- return this.store.get(key);
9
- }
10
-
11
- async set(key: string, value: string): Promise<void> {
12
- this.store.set(key, value);
13
- }
14
-
15
- async delete(key: string): Promise<void> {
16
- this.store.delete(key);
17
- }
18
-
19
- async list(prefix: string): Promise<string[]> {
20
- const result: string[] = [];
21
- for (const key of this.store.keys()) {
22
- if (key.startsWith(prefix)) {
23
- result.push(key);
24
- }
25
- }
26
- return result;
27
- }
28
- }
1
+ import type { IKVStore } from "./i-kv-store.js";
2
+
3
+ /** In-memory IKVStore backed by a Map. Used for testing. */
4
+ export class MemoryKVStore implements IKVStore {
5
+ private readonly store = new Map<string, string>();
6
+
7
+ async get(key: string): Promise<string | undefined> {
8
+ return this.store.get(key);
9
+ }
10
+
11
+ async set(key: string, value: string): Promise<void> {
12
+ this.store.set(key, value);
13
+ }
14
+
15
+ async delete(key: string): Promise<void> {
16
+ this.store.delete(key);
17
+ }
18
+
19
+ async list(prefix: string): Promise<string[]> {
20
+ const result: string[] = [];
21
+ for (const key of this.store.keys()) {
22
+ if (key.startsWith(prefix)) {
23
+ result.push(key);
24
+ }
25
+ }
26
+ return result;
27
+ }
28
+ }
@@ -1,25 +1,25 @@
1
- import { KvRawStorage } from "./kv-raw-storage.js";
2
- import { MemoryStoreDriver } from "./memory-store-driver.js";
3
-
4
- /**
5
- * In-memory {@link IRawStorage}, now a thin shell over the shared
6
- * {@link KvRawStorage} kernel backed by a {@link MemoryStoreDriver}.
7
- *
8
- * @pitfall The old hand-rolled version had to `structuredClone` on every get and
9
- * put — otherwise callers mutating a returned/stored object corrupted the store
10
- * (see docs/internals.md "Storage Returns References"). That discipline is GONE
11
- * and must not be reintroduced: values cross the driver boundary as
12
- * `Uint8Array` produced by `JSON`-encode and consumed by `JSON`-decode, so every
13
- * read decodes a fresh object and every write stored an independent byte
14
- * snapshot BY CONSTRUCTION. The clone-on-store / clone-on-read invariant is now
15
- * structural, not a rule. The conformance suite asserts it so a future driver
16
- * that shortcuts the byte boundary is caught.
17
- *
18
- * The name is kept stable so existing imports (`from '@optimystic/db-p2p'`)
19
- * continue to resolve.
20
- */
21
- export class MemoryRawStorage extends KvRawStorage {
22
- constructor() {
23
- super(new MemoryStoreDriver());
24
- }
25
- }
1
+ import { KvRawStorage } from "./kv-raw-storage.js";
2
+ import { MemoryStoreDriver } from "./memory-store-driver.js";
3
+
4
+ /**
5
+ * In-memory {@link IRawStorage}, now a thin shell over the shared
6
+ * {@link KvRawStorage} kernel backed by a {@link MemoryStoreDriver}.
7
+ *
8
+ * @pitfall The old hand-rolled version had to `structuredClone` on every get and
9
+ * put — otherwise callers mutating a returned/stored object corrupted the store
10
+ * (see docs/internals.md "Storage Returns References"). That discipline is GONE
11
+ * and must not be reintroduced: values cross the driver boundary as
12
+ * `Uint8Array` produced by `JSON`-encode and consumed by `JSON`-decode, so every
13
+ * read decodes a fresh object and every write stored an independent byte
14
+ * snapshot BY CONSTRUCTION. The clone-on-store / clone-on-read invariant is now
15
+ * structural, not a rule. The conformance suite asserts it so a future driver
16
+ * that shortcuts the byte boundary is caught.
17
+ *
18
+ * The name is kept stable so existing imports (`from '@optimystic/db-p2p'`)
19
+ * continue to resolve.
20
+ */
21
+ export class MemoryRawStorage extends KvRawStorage {
22
+ constructor() {
23
+ super(new MemoryStoreDriver());
24
+ }
25
+ }