@optimystic/db-p2p 0.22.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) 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/host.js +34 -11
  14. package/dist/src/cohort-topic/host.js.map +1 -1
  15. package/dist/src/cohort-topic/stream-util.d.ts +37 -7
  16. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  17. package/dist/src/cohort-topic/stream-util.js +77 -19
  18. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  19. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  20. package/dist/src/dispute/dispute-service.js +9 -3
  21. package/dist/src/dispute/dispute-service.js.map +1 -1
  22. package/dist/src/index.d.ts +3 -0
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +3 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/libp2p-key-network.d.ts +88 -2
  27. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  28. package/dist/src/libp2p-key-network.js +134 -28
  29. package/dist/src/libp2p-key-network.js.map +1 -1
  30. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  31. package/dist/src/libp2p-node-base.js +25 -1
  32. package/dist/src/libp2p-node-base.js.map +1 -1
  33. package/dist/src/logger.d.ts +17 -1
  34. package/dist/src/logger.d.ts.map +1 -1
  35. package/dist/src/logger.js +19 -2
  36. package/dist/src/logger.js.map +1 -1
  37. package/dist/src/matchmaking/query-transport.js +3 -3
  38. package/dist/src/matchmaking/query-transport.js.map +1 -1
  39. package/dist/src/owned-block-seed.d.ts +6 -3
  40. package/dist/src/owned-block-seed.d.ts.map +1 -1
  41. package/dist/src/owned-block-seed.js +16 -3
  42. package/dist/src/owned-block-seed.js.map +1 -1
  43. package/dist/src/peer-address-book.d.ts +72 -0
  44. package/dist/src/peer-address-book.d.ts.map +1 -0
  45. package/dist/src/peer-address-book.js +123 -0
  46. package/dist/src/peer-address-book.js.map +1 -0
  47. package/dist/src/reactivity/notify-transport.d.ts +4 -4
  48. package/dist/src/reactivity/notify-transport.js +6 -6
  49. package/dist/src/reactivity/notify-transport.js.map +1 -1
  50. package/dist/src/reactivity/push-state-gossip.js +2 -2
  51. package/dist/src/reactivity/push-state-gossip.js.map +1 -1
  52. package/dist/src/reactivity/recover-transport.d.ts +6 -2
  53. package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
  54. package/dist/src/reactivity/recover-transport.js +7 -3
  55. package/dist/src/reactivity/recover-transport.js.map +1 -1
  56. package/dist/src/repo/client.d.ts.map +1 -1
  57. package/dist/src/repo/client.js +11 -2
  58. package/dist/src/repo/client.js.map +1 -1
  59. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  60. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  61. package/dist/src/repo/cluster-coordinator.js +95 -3
  62. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  63. package/dist/src/repo/coordinator-repo.d.ts +62 -9
  64. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  65. package/dist/src/repo/coordinator-repo.js +242 -73
  66. package/dist/src/repo/coordinator-repo.js.map +1 -1
  67. package/dist/src/rn.d.ts +3 -0
  68. package/dist/src/rn.d.ts.map +1 -1
  69. package/dist/src/rn.js +3 -0
  70. package/dist/src/rn.js.map +1 -1
  71. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  72. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  73. package/dist/src/storage/cached-raw-storage.js +152 -0
  74. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  75. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  76. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  77. package/dist/src/storage/cached-store-driver.js +775 -0
  78. package/dist/src/storage/cached-store-driver.js.map +1 -0
  79. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  80. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  81. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  82. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  83. package/dist/src/storage/shared-cache-pool.js +354 -0
  84. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  85. package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
  86. package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
  87. package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
  88. package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
  89. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  90. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  91. package/dist/src/testing/raw-storage-conformance.js +35 -2
  92. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  93. package/package.json +3 -3
  94. package/readme.md +668 -668
  95. package/src/cluster/block-transfer.ts +424 -424
  96. package/src/cluster/client.ts +119 -88
  97. package/src/cluster/cluster-error.ts +64 -64
  98. package/src/cluster/cluster-policy.ts +203 -203
  99. package/src/cluster/cluster-repo.ts +242 -122
  100. package/src/cluster/cluster-size-coupling.ts +45 -45
  101. package/src/cluster/commit-cert.ts +139 -139
  102. package/src/cluster/i-transaction-state-store.ts +43 -43
  103. package/src/cluster/memory-transaction-state-store.ts +56 -56
  104. package/src/cluster/peer-key-binding.ts +37 -37
  105. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  106. package/src/cluster/quorum-restore.ts +223 -223
  107. package/src/cluster/reconcile-block.ts +203 -203
  108. package/src/cluster/service.ts +293 -241
  109. package/src/cluster/supermajority-coupling.ts +37 -37
  110. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  111. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  112. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  113. package/src/cohort-topic/change-bridge.ts +109 -109
  114. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  115. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  116. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  117. package/src/cohort-topic/host.ts +42 -11
  118. package/src/cohort-topic/index.ts +13 -13
  119. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  120. package/src/cohort-topic/membership-source.ts +68 -68
  121. package/src/cohort-topic/peer-codec.ts +31 -31
  122. package/src/cohort-topic/peer-sig.ts +86 -86
  123. package/src/cohort-topic/protocols.ts +71 -71
  124. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  125. package/src/cohort-topic/size-estimator.ts +16 -16
  126. package/src/cohort-topic/stream-util.ts +79 -19
  127. package/src/cohort-topic/threshold-crypto.ts +239 -239
  128. package/src/cohort-topic/topic-router.ts +77 -77
  129. package/src/dispute/arbitrator-selection.ts +138 -138
  130. package/src/dispute/cascade.ts +524 -524
  131. package/src/dispute/dispute-service.ts +11 -5
  132. package/src/dispute/invalidation.ts +625 -625
  133. package/src/inbound-authorization.ts +190 -190
  134. package/src/index.ts +52 -49
  135. package/src/libp2p-key-network.ts +1120 -990
  136. package/src/libp2p-node-base.ts +1675 -1651
  137. package/src/libp2p-node-rn.ts +30 -30
  138. package/src/libp2p-node.ts +36 -36
  139. package/src/logger.ts +19 -2
  140. package/src/matchmaking/aggregate-counts.ts +104 -104
  141. package/src/matchmaking/index.ts +20 -20
  142. package/src/matchmaking/module.ts +363 -363
  143. package/src/matchmaking/protocols.ts +51 -51
  144. package/src/matchmaking/provider-manager.ts +95 -95
  145. package/src/matchmaking/query-handler.ts +88 -88
  146. package/src/matchmaking/query-transport.ts +3 -3
  147. package/src/matchmaking/seeker-manager.ts +64 -64
  148. package/src/matchmaking/seeker-walk-client.ts +293 -293
  149. package/src/matchmaking/traffic-validation.ts +195 -195
  150. package/src/optimystic-node.ts +36 -36
  151. package/src/owned-block-seed.ts +53 -40
  152. package/src/peer-address-book.ts +149 -0
  153. package/src/protocol-limits.ts +33 -33
  154. package/src/reactivity/forwarder-host.ts +438 -438
  155. package/src/reactivity/index.ts +19 -19
  156. package/src/reactivity/notify-transport.ts +144 -144
  157. package/src/reactivity/origination-manager.ts +192 -192
  158. package/src/reactivity/protocols.ts +61 -61
  159. package/src/reactivity/push-state-gossip.ts +291 -291
  160. package/src/reactivity/recover-transport.ts +7 -3
  161. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  162. package/src/reactivity/subscriber-registry.ts +96 -96
  163. package/src/reactivity/subscription-manager.ts +450 -450
  164. package/src/reactivity/topic-bytes.ts +37 -37
  165. package/src/repo/client.ts +12 -2
  166. package/src/repo/cluster-coordinator.ts +99 -3
  167. package/src/repo/coordinator-repo.ts +281 -74
  168. package/src/repo/types.ts +7 -7
  169. package/src/rn.ts +39 -36
  170. package/src/rpc-deadline.ts +45 -45
  171. package/src/storage/arachnode-partition.ts +74 -74
  172. package/src/storage/cached-raw-storage.ts +180 -0
  173. package/src/storage/cached-store-driver.ts +859 -0
  174. package/src/storage/i-kv-store.ts +8 -8
  175. package/src/storage/i-raw-storage.ts +12 -5
  176. package/src/storage/kv-raw-storage.ts +135 -135
  177. package/src/storage/memory-kv-store.ts +28 -28
  178. package/src/storage/memory-storage.ts +25 -25
  179. package/src/storage/memory-store-driver.ts +157 -157
  180. package/src/storage/raw-store-codec.ts +42 -42
  181. package/src/storage/raw-store-driver.ts +80 -80
  182. package/src/storage/ring-selector.ts +317 -317
  183. package/src/storage/ring-shift-coordinator.ts +271 -271
  184. package/src/storage/shared-cache-pool.ts +452 -0
  185. package/src/storage/storage-repo.ts +1014 -1014
  186. package/src/testing/cohort-topic-mesh-harness.ts +673 -663
  187. package/src/testing/index.ts +8 -8
  188. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  189. package/src/testing/raw-storage-conformance.ts +453 -417
  190. package/src/testing/reactivity-mesh-harness.ts +922 -922
  191. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  192. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  193. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  194. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -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
+ }