@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,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
+ }