@optimystic/db-p2p 1.0.0-beta.2 → 1.0.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 (234) hide show
  1. package/dist/src/cluster/block-transfer-service.d.ts +73 -1
  2. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  3. package/dist/src/cluster/block-transfer-service.js +70 -3
  4. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  5. package/dist/src/cluster/block-transfer.d.ts +16 -1
  6. package/dist/src/cluster/block-transfer.d.ts.map +1 -1
  7. package/dist/src/cluster/block-transfer.js +73 -72
  8. package/dist/src/cluster/block-transfer.js.map +1 -1
  9. package/dist/src/cluster/client.d.ts +4 -6
  10. package/dist/src/cluster/client.d.ts.map +1 -1
  11. package/dist/src/cluster/client.js +8 -13
  12. package/dist/src/cluster/client.js.map +1 -1
  13. package/dist/src/cluster/cluster-policy.d.ts +2 -1
  14. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  15. package/dist/src/cluster/cluster-policy.js.map +1 -1
  16. package/dist/src/cluster/cluster-repo.d.ts +172 -17
  17. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  18. package/dist/src/cluster/cluster-repo.js +553 -149
  19. package/dist/src/cluster/cluster-repo.js.map +1 -1
  20. package/dist/src/cluster/commit-proof.js +2 -2
  21. package/dist/src/cluster/race-resolution.d.ts +51 -7
  22. package/dist/src/cluster/race-resolution.d.ts.map +1 -1
  23. package/dist/src/cluster/race-resolution.js +71 -7
  24. package/dist/src/cluster/race-resolution.js.map +1 -1
  25. package/dist/src/cluster/rebalance-monitor.d.ts +67 -5
  26. package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
  27. package/dist/src/cluster/rebalance-monitor.js +120 -26
  28. package/dist/src/cluster/rebalance-monitor.js.map +1 -1
  29. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  30. package/dist/src/cluster/spread-on-churn.js +34 -47
  31. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  32. package/dist/src/cohort-topic/host.d.ts.map +1 -1
  33. package/dist/src/cohort-topic/host.js +8 -6
  34. package/dist/src/cohort-topic/host.js.map +1 -1
  35. package/dist/src/cohort-topic/membership-source.js +1 -1
  36. package/dist/src/cohort-topic/membership-source.js.map +1 -1
  37. package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -1
  38. package/dist/src/cohort-topic/peer-codec.js +5 -2
  39. package/dist/src/cohort-topic/peer-codec.js.map +1 -1
  40. package/dist/src/cohort-topic/stream-util.d.ts +24 -3
  41. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  42. package/dist/src/cohort-topic/stream-util.js +36 -3
  43. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  44. package/dist/src/cohort-topic/topic-router.d.ts +9 -0
  45. package/dist/src/cohort-topic/topic-router.d.ts.map +1 -1
  46. package/dist/src/cohort-topic/topic-router.js +12 -2
  47. package/dist/src/cohort-topic/topic-router.js.map +1 -1
  48. package/dist/src/dispute/client.d.ts.map +1 -1
  49. package/dist/src/dispute/client.js +15 -3
  50. package/dist/src/dispute/client.js.map +1 -1
  51. package/dist/src/index.d.ts +5 -0
  52. package/dist/src/index.d.ts.map +1 -1
  53. package/dist/src/index.js +5 -0
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/libp2p-key-network.d.ts +78 -17
  56. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  57. package/dist/src/libp2p-key-network.js +241 -147
  58. package/dist/src/libp2p-key-network.js.map +1 -1
  59. package/dist/src/libp2p-node-base.d.ts +35 -1
  60. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  61. package/dist/src/libp2p-node-base.js +151 -26
  62. package/dist/src/libp2p-node-base.js.map +1 -1
  63. package/dist/src/libp2p-node-rn.d.ts +2 -2
  64. package/dist/src/libp2p-node-rn.d.ts.map +1 -1
  65. package/dist/src/libp2p-node-rn.js.map +1 -1
  66. package/dist/src/libp2p-node.d.ts +2 -2
  67. package/dist/src/libp2p-node.d.ts.map +1 -1
  68. package/dist/src/libp2p-node.js.map +1 -1
  69. package/dist/src/logger.d.ts.map +1 -1
  70. package/dist/src/logger.js +19 -7
  71. package/dist/src/logger.js.map +1 -1
  72. package/dist/src/matchmaking/query-transport.d.ts +3 -4
  73. package/dist/src/matchmaking/query-transport.d.ts.map +1 -1
  74. package/dist/src/matchmaking/query-transport.js +9 -9
  75. package/dist/src/matchmaking/query-transport.js.map +1 -1
  76. package/dist/src/network/network-manager-service.d.ts +10 -5
  77. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  78. package/dist/src/network/network-manager-service.js +11 -2
  79. package/dist/src/network/network-manager-service.js.map +1 -1
  80. package/dist/src/network/relay-reservation.d.ts +170 -0
  81. package/dist/src/network/relay-reservation.d.ts.map +1 -0
  82. package/dist/src/network/relay-reservation.js +527 -0
  83. package/dist/src/network/relay-reservation.js.map +1 -0
  84. package/dist/src/pend-validation.d.ts +3 -2
  85. package/dist/src/pend-validation.d.ts.map +1 -1
  86. package/dist/src/pend-validation.js +3 -2
  87. package/dist/src/pend-validation.js.map +1 -1
  88. package/dist/src/protocol-client.d.ts +8 -1
  89. package/dist/src/protocol-client.d.ts.map +1 -1
  90. package/dist/src/protocol-client.js +11 -0
  91. package/dist/src/protocol-client.js.map +1 -1
  92. package/dist/src/reactivity/forwarder-host.d.ts +2 -1
  93. package/dist/src/reactivity/forwarder-host.d.ts.map +1 -1
  94. package/dist/src/reactivity/forwarder-host.js +2 -1
  95. package/dist/src/reactivity/forwarder-host.js.map +1 -1
  96. package/dist/src/reactivity/origination-manager.d.ts +3 -3
  97. package/dist/src/reactivity/origination-manager.js +3 -3
  98. package/dist/src/reactivity/recover-transport.d.ts +22 -14
  99. package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
  100. package/dist/src/reactivity/recover-transport.js +32 -19
  101. package/dist/src/reactivity/recover-transport.js.map +1 -1
  102. package/dist/src/reactivity/subscription-manager.d.ts +2 -2
  103. package/dist/src/reactivity/topic-bytes.d.ts +6 -7
  104. package/dist/src/reactivity/topic-bytes.d.ts.map +1 -1
  105. package/dist/src/reactivity/topic-bytes.js +8 -9
  106. package/dist/src/reactivity/topic-bytes.js.map +1 -1
  107. package/dist/src/repo/client.d.ts +5 -2
  108. package/dist/src/repo/client.d.ts.map +1 -1
  109. package/dist/src/repo/client.js +49 -40
  110. package/dist/src/repo/client.js.map +1 -1
  111. package/dist/src/repo/cluster-coordinator.d.ts +119 -5
  112. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  113. package/dist/src/repo/cluster-coordinator.js +181 -29
  114. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  115. package/dist/src/repo/coordinator-repo.d.ts +259 -76
  116. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  117. package/dist/src/repo/coordinator-repo.js +716 -328
  118. package/dist/src/repo/coordinator-repo.js.map +1 -1
  119. package/dist/src/repo/i-under-replication-ledger.d.ts +87 -0
  120. package/dist/src/repo/i-under-replication-ledger.d.ts.map +1 -0
  121. package/dist/src/repo/i-under-replication-ledger.js +2 -0
  122. package/dist/src/repo/i-under-replication-ledger.js.map +1 -0
  123. package/dist/src/repo/kv-under-replication-ledger.d.ts +91 -0
  124. package/dist/src/repo/kv-under-replication-ledger.d.ts.map +1 -0
  125. package/dist/src/repo/kv-under-replication-ledger.js +287 -0
  126. package/dist/src/repo/kv-under-replication-ledger.js.map +1 -0
  127. package/dist/src/repo/responsibility.d.ts +37 -0
  128. package/dist/src/repo/responsibility.d.ts.map +1 -0
  129. package/dist/src/repo/responsibility.js +37 -0
  130. package/dist/src/repo/responsibility.js.map +1 -0
  131. package/dist/src/repo/service.d.ts +44 -15
  132. package/dist/src/repo/service.d.ts.map +1 -1
  133. package/dist/src/repo/service.js +64 -27
  134. package/dist/src/repo/service.js.map +1 -1
  135. package/dist/src/repo/stuck-reservation.d.ts +127 -0
  136. package/dist/src/repo/stuck-reservation.d.ts.map +1 -0
  137. package/dist/src/repo/stuck-reservation.js +179 -0
  138. package/dist/src/repo/stuck-reservation.js.map +1 -0
  139. package/dist/src/repo/under-replication-drain.d.ts +208 -0
  140. package/dist/src/repo/under-replication-drain.d.ts.map +1 -0
  141. package/dist/src/repo/under-replication-drain.js +461 -0
  142. package/dist/src/repo/under-replication-drain.js.map +1 -0
  143. package/dist/src/rn.d.ts +5 -0
  144. package/dist/src/rn.d.ts.map +1 -1
  145. package/dist/src/rn.js +5 -0
  146. package/dist/src/rn.js.map +1 -1
  147. package/dist/src/storage/block-latch.d.ts +3 -2
  148. package/dist/src/storage/block-latch.d.ts.map +1 -1
  149. package/dist/src/storage/block-latch.js +7 -4
  150. package/dist/src/storage/block-latch.js.map +1 -1
  151. package/dist/src/storage/block-storage.d.ts +45 -3
  152. package/dist/src/storage/block-storage.d.ts.map +1 -1
  153. package/dist/src/storage/block-storage.js +156 -9
  154. package/dist/src/storage/block-storage.js.map +1 -1
  155. package/dist/src/storage/i-block-storage.d.ts +57 -5
  156. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  157. package/dist/src/storage/i-block-storage.js.map +1 -1
  158. package/dist/src/storage/pending-claim.d.ts +167 -0
  159. package/dist/src/storage/pending-claim.d.ts.map +1 -0
  160. package/dist/src/storage/pending-claim.js +156 -0
  161. package/dist/src/storage/pending-claim.js.map +1 -0
  162. package/dist/src/storage/raw-store-codec.d.ts +13 -0
  163. package/dist/src/storage/raw-store-codec.d.ts.map +1 -1
  164. package/dist/src/storage/raw-store-codec.js +24 -3
  165. package/dist/src/storage/raw-store-codec.js.map +1 -1
  166. package/dist/src/storage/restoration-coordinator.d.ts.map +1 -1
  167. package/dist/src/storage/restoration-coordinator.js +2 -1
  168. package/dist/src/storage/restoration-coordinator.js.map +1 -1
  169. package/dist/src/storage/ring-shift-coordinator.d.ts.map +1 -1
  170. package/dist/src/storage/ring-shift-coordinator.js +3 -3
  171. package/dist/src/storage/ring-shift-coordinator.js.map +1 -1
  172. package/dist/src/storage/storage-repo.d.ts +137 -5
  173. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  174. package/dist/src/storage/storage-repo.js +273 -68
  175. package/dist/src/storage/storage-repo.js.map +1 -1
  176. package/dist/src/storage/struct.d.ts +58 -0
  177. package/dist/src/storage/struct.d.ts.map +1 -1
  178. package/dist/src/testing/mesh-harness.d.ts +92 -11
  179. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  180. package/dist/src/testing/mesh-harness.js +246 -103
  181. package/dist/src/testing/mesh-harness.js.map +1 -1
  182. package/package.json +2 -2
  183. package/readme.md +27 -3
  184. package/src/cluster/block-transfer-service.ts +132 -4
  185. package/src/cluster/block-transfer.ts +79 -84
  186. package/src/cluster/client.ts +115 -119
  187. package/src/cluster/cluster-policy.ts +2 -1
  188. package/src/cluster/cluster-repo.ts +3115 -2671
  189. package/src/cluster/commit-proof.ts +2 -2
  190. package/src/cluster/race-resolution.ts +76 -7
  191. package/src/cluster/rebalance-monitor.ts +677 -539
  192. package/src/cluster/spread-on-churn.ts +34 -54
  193. package/src/cohort-topic/host.ts +8 -6
  194. package/src/cohort-topic/membership-source.ts +1 -1
  195. package/src/cohort-topic/peer-codec.ts +34 -31
  196. package/src/cohort-topic/stream-util.ts +39 -4
  197. package/src/cohort-topic/topic-router.ts +12 -2
  198. package/src/dispute/client.ts +17 -7
  199. package/src/index.ts +64 -59
  200. package/src/libp2p-key-network.ts +1377 -1253
  201. package/src/libp2p-node-base.ts +186 -27
  202. package/src/libp2p-node-rn.ts +31 -30
  203. package/src/libp2p-node.ts +37 -36
  204. package/src/logger.ts +20 -7
  205. package/src/matchmaking/query-transport.ts +9 -9
  206. package/src/network/network-manager-service.ts +16 -5
  207. package/src/network/relay-reservation.ts +636 -0
  208. package/src/pend-validation.ts +3 -2
  209. package/src/protocol-client.ts +13 -1
  210. package/src/reactivity/forwarder-host.ts +2 -1
  211. package/src/reactivity/origination-manager.ts +3 -3
  212. package/src/reactivity/recover-transport.ts +39 -22
  213. package/src/reactivity/subscription-manager.ts +2 -2
  214. package/src/reactivity/topic-bytes.ts +35 -37
  215. package/src/repo/client.ts +172 -173
  216. package/src/repo/cluster-coordinator.ts +1296 -1113
  217. package/src/repo/coordinator-repo.ts +3087 -2687
  218. package/src/repo/i-under-replication-ledger.ts +89 -0
  219. package/src/repo/kv-under-replication-ledger.ts +302 -0
  220. package/src/repo/responsibility.ts +48 -0
  221. package/src/repo/service.ts +76 -39
  222. package/src/repo/stuck-reservation.ts +224 -0
  223. package/src/repo/under-replication-drain.ts +582 -0
  224. package/src/rn.ts +74 -69
  225. package/src/storage/block-latch.ts +7 -4
  226. package/src/storage/block-storage.ts +161 -10
  227. package/src/storage/i-block-storage.ts +60 -5
  228. package/src/storage/pending-claim.ts +200 -0
  229. package/src/storage/raw-store-codec.ts +26 -3
  230. package/src/storage/restoration-coordinator.ts +212 -211
  231. package/src/storage/ring-shift-coordinator.ts +271 -271
  232. package/src/storage/storage-repo.ts +311 -73
  233. package/src/storage/struct.ts +58 -0
  234. package/src/testing/mesh-harness.ts +329 -115
package/src/rn.ts CHANGED
@@ -1,69 +1,74 @@
1
- // React Native / browser entry point. `package.json` routes the `react-native` condition on `.`
2
- // here, plus the explicit `./rn` subpath.
3
- //
4
- // NOTE: this entry re-exports exactly the same module set as `./index.js`, with
5
- // `./libp2p-node.js` -> `./libp2p-node-rn.js` as the single substitution. Because the module set
6
- // is identical, a class obtained from either entry is the *same* class object on Node, so
7
- // `instanceof` holds across them; downstream code (e.g. sereus' `cached-storage.ts`) relies on
8
- // that. Enforced from this side by `test/entry-parity.spec.ts` — keep the two files in step, and
9
- // keep every line here a plain `export * from '...'` so that spec can compare them.
10
-
11
- export * from './cluster/client.js';
12
- export * from './cluster/client-signature-verifier.js';
13
- export * from './cluster/cluster-repo.js';
14
- export * from './cluster/cluster-policy.js';
15
- export * from './cluster/commit-cert.js';
16
- export * from './cluster/commit-proof.js';
17
- export * from './cluster/service.js';
18
- export * from './cluster/rebalance-monitor.js';
19
- export * from './cluster/spread-on-churn.js';
20
- export * from './cluster/block-transfer.js';
21
- export * from './cluster/block-transfer-service.js';
22
- export * from './inbound-authorization.js';
23
- export * from './protocol-client.js';
24
- export * from './repo/client.js';
25
- export * from './repo/cluster-coordinator.js';
26
- export * from './repo/coordinator-repo.js';
27
- export * from './repo/served-repo-proxy.js';
28
- export * from './repo/service.js';
29
- export * from './storage/block-storage.js';
30
- export * from './storage/block-latch.js';
31
- export * from './storage/store-identity.js';
32
- export * from './storage/raw-store-driver.js';
33
- export * from './storage/kv-raw-storage.js';
34
- export * from './storage/shared-cache-pool.js';
35
- export * from './storage/cached-store-driver.js';
36
- export * from './storage/cached-raw-storage.js';
37
- export * from './storage/with-read-cache.js';
38
- export * from './storage/memory-store-driver.js';
39
- export * from './storage/memory-storage.js';
40
- export * from './storage/i-block-storage.js';
41
- export * from './storage/i-raw-storage.js';
42
- export * from './storage/struct.js';
43
- export * from './storage/block-archive.js';
44
- export * from './storage/storage-repo.js';
45
- export * from './storage/restoration-coordinator.js';
46
- export * from './storage/ring-selector.js';
47
- export * from './storage/storage-monitor.js';
48
- export * from './storage/arachnode-fret-adapter.js';
49
- export * from './sync/protocol.js';
50
- export * from './sync/client.js';
51
- export * from './sync/service.js';
52
- export * from './it-utility.js';
53
- export * from './libp2p-key-network.js';
54
- export * from './libp2p-node-rn.js';
55
- export * from './optimystic-node.js';
56
- export * from './routing/responsibility.js';
57
- export * from './routing/libp2p-known-peers.js';
58
- export * from './network/network-manager-service.js';
59
- export * from './network/get-network-manager.js';
60
- export * from './reputation/index.js';
61
- export * from './dispute/index.js';
62
- export * from './cohort-topic/index.js';
63
- export * from './matchmaking/index.js';
64
- export * from './reactivity/index.js';
65
- export * from './cluster/i-transaction-state-store.js';
66
- export * from './cluster/memory-transaction-state-store.js';
67
- export * from './cluster/persistent-transaction-state-store.js';
68
- export * from './storage/i-kv-store.js';
69
- export * from './storage/memory-kv-store.js';
1
+ // React Native / browser entry point. `package.json` routes the `react-native` condition on `.`
2
+ // here, plus the explicit `./rn` subpath.
3
+ //
4
+ // NOTE: this entry re-exports exactly the same module set as `./index.js`, with
5
+ // `./libp2p-node.js` -> `./libp2p-node-rn.js` as the single substitution. Because the module set
6
+ // is identical, a class obtained from either entry is the *same* class object on Node, so
7
+ // `instanceof` holds across them; downstream code (e.g. sereus' `cached-storage.ts`) relies on
8
+ // that. Enforced from this side by `test/entry-parity.spec.ts` — keep the two files in step, and
9
+ // keep every line here a plain `export * from '...'` so that spec can compare them.
10
+
11
+ export * from './cluster/client.js';
12
+ export * from './cluster/client-signature-verifier.js';
13
+ export * from './cluster/cluster-repo.js';
14
+ export * from './cluster/cluster-policy.js';
15
+ export * from './cluster/commit-cert.js';
16
+ export * from './cluster/commit-proof.js';
17
+ export * from './cluster/service.js';
18
+ export * from './cluster/rebalance-monitor.js';
19
+ export * from './cluster/spread-on-churn.js';
20
+ export * from './cluster/block-transfer.js';
21
+ export * from './cluster/block-transfer-service.js';
22
+ export * from './inbound-authorization.js';
23
+ export * from './protocol-client.js';
24
+ export * from './repo/client.js';
25
+ export * from './repo/cluster-coordinator.js';
26
+ export * from './repo/coordinator-repo.js';
27
+ export * from './repo/i-under-replication-ledger.js';
28
+ export * from './repo/kv-under-replication-ledger.js';
29
+ export * from './repo/under-replication-drain.js';
30
+ export * from './repo/responsibility.js';
31
+ export * from './repo/served-repo-proxy.js';
32
+ export * from './repo/service.js';
33
+ export * from './storage/block-storage.js';
34
+ export * from './storage/block-latch.js';
35
+ export * from './storage/store-identity.js';
36
+ export * from './storage/raw-store-driver.js';
37
+ export * from './storage/kv-raw-storage.js';
38
+ export * from './storage/shared-cache-pool.js';
39
+ export * from './storage/cached-store-driver.js';
40
+ export * from './storage/cached-raw-storage.js';
41
+ export * from './storage/with-read-cache.js';
42
+ export * from './storage/memory-store-driver.js';
43
+ export * from './storage/memory-storage.js';
44
+ export * from './storage/i-block-storage.js';
45
+ export * from './storage/i-raw-storage.js';
46
+ export * from './storage/struct.js';
47
+ export * from './storage/pending-claim.js';
48
+ export * from './storage/block-archive.js';
49
+ export * from './storage/storage-repo.js';
50
+ export * from './storage/restoration-coordinator.js';
51
+ export * from './storage/ring-selector.js';
52
+ export * from './storage/storage-monitor.js';
53
+ export * from './storage/arachnode-fret-adapter.js';
54
+ export * from './sync/protocol.js';
55
+ export * from './sync/client.js';
56
+ export * from './sync/service.js';
57
+ export * from './it-utility.js';
58
+ export * from './libp2p-key-network.js';
59
+ export * from './libp2p-node-rn.js';
60
+ export * from './optimystic-node.js';
61
+ export * from './routing/responsibility.js';
62
+ export * from './routing/libp2p-known-peers.js';
63
+ export * from './network/network-manager-service.js';
64
+ export * from './network/get-network-manager.js';
65
+ export * from './reputation/index.js';
66
+ export * from './dispute/index.js';
67
+ export * from './cohort-topic/index.js';
68
+ export * from './matchmaking/index.js';
69
+ export * from './reactivity/index.js';
70
+ export * from './cluster/i-transaction-state-store.js';
71
+ export * from './cluster/memory-transaction-state-store.js';
72
+ export * from './cluster/persistent-transaction-state-store.js';
73
+ export * from './storage/i-kv-store.js';
74
+ export * from './storage/memory-kv-store.js';
@@ -34,8 +34,9 @@ let expire!: (latch: BlockWriteLatch) => void;
34
34
  /**
35
35
  * Opaque proof that the bearer is executing inside {@link blockWriteLatchKey}`(blockId)`. Only
36
36
  * {@link acquireBlockWriteLatch} (and {@link withBlockWriteLatch} through it) can construct one:
37
- * the constructor is private and the module-scoped minter is assigned from a static block, where
38
- * the private constructor is callable — no cast, nothing outside this module can build a token.
37
+ * the constructor is private and the module-scoped minter is assigned from a static field
38
+ * initializer, where the private constructor is callable — no cast, nothing outside this module can
39
+ * build a token.
39
40
  *
40
41
  * A token is only valid while the latch it proves is actually held: releasing expires it, so a
41
42
  * callback that stashes its token and writes after its scope closed is rejected instead of silently
@@ -54,10 +55,12 @@ export class BlockWriteLatch {
54
55
  return this.#live;
55
56
  }
56
57
 
57
- static {
58
+ // A static field initializer, not a `static { }` block: React Native's Metro/Babel bundling cannot
59
+ // transform static blocks, and this module ships in the RN bundle (lint enforces it).
60
+ static #wired = (() => {
58
61
  mint = (blockId) => new BlockWriteLatch(blockId);
59
62
  expire = (latch) => { latch.#live = false; };
60
- }
63
+ })();
61
64
  }
62
65
 
63
66
  /**
@@ -1,4 +1,4 @@
1
- import type { BlockId, IBlock, Transform, ActionId, ActionRev, ActionTransform } from "@optimystic/db-core";
1
+ import type { BlockId, IBlock, Transform, ActionId, ActionRev, ActionTransform, BlockLineage } from "@optimystic/db-core";
2
2
  import { applyTransform, canonicalJson, hashString } from "@optimystic/db-core";
3
3
  import type { BlockCommitProof } from "../cluster/commit-proof.js";
4
4
  import type { BlockArchive, BlockMetadata, RestoreCallback, RevisionRange } from "./struct.js";
@@ -6,6 +6,7 @@ import type { IRawStorage } from "./i-raw-storage.js";
6
6
  import { mergeRanges } from "./helpers.js";
7
7
  import { RevisionNotCoveredError, PendRevisionTakenError, type IBlockStorage } from "./i-block-storage.js";
8
8
  import type { BlockWriteLatch } from "./block-latch.js";
9
+ import type { PendingClaim } from "./pending-claim.js";
9
10
  import { createLogger } from "../logger.js";
10
11
 
11
12
  const log = createLogger('block-storage');
@@ -155,9 +156,39 @@ export class BlockStorage implements IBlockStorage {
155
156
  yield* this.storage.listPendingTransactions(this.blockId);
156
157
  }
157
158
 
158
- async savePendingTransaction(actionId: ActionId, transform: Transform, rev: number | undefined, latch: BlockWriteLatch): Promise<void> {
159
+ async listPendingClaims(): Promise<PendingClaim[]> {
160
+ const meta = await this.storage.getMetadata(this.blockId);
161
+ const claims: PendingClaim[] = [];
162
+ for await (const actionId of this.storage.listPendingTransactions(this.blockId)) {
163
+ claims.push(BlockStorage.claimOf(meta, actionId));
164
+ }
165
+ return claims;
166
+ }
167
+
168
+ async pendingClaimOf(actionId: ActionId): Promise<PendingClaim | undefined> {
169
+ // Joined against the record, exactly as the listing is: a metadata entry whose record is gone
170
+ // is inert (see BlockMetadata.pendingRevs) and must not read as a live claim.
171
+ if (await this.storage.getPendingTransaction(this.blockId, actionId) === undefined) {
172
+ return undefined;
173
+ }
174
+ return BlockStorage.claimOf(await this.storage.getMetadata(this.blockId), actionId);
175
+ }
176
+
177
+ /** The claim `meta` describes for `actionId`'s (existing) record: its slot and its base, each
178
+ * present only when on file. */
179
+ private static claimOf(meta: BlockMetadata | undefined, actionId: ActionId): PendingClaim {
180
+ const rev = meta?.pendingRevs?.[actionId];
181
+ const baseRev = meta?.pendingBases?.[actionId];
182
+ return {
183
+ actionId,
184
+ ...(rev === undefined ? {} : { rev }),
185
+ ...(baseRev === undefined ? {} : { baseRev })
186
+ };
187
+ }
188
+
189
+ async savePendingTransaction(actionId: ActionId, transform: Transform, rev: number | undefined, baseRev: number | undefined, latch: BlockWriteLatch): Promise<void> {
159
190
  this.assertLatch(latch);
160
- log('pend blockId=%s actionId=%s rev=%s', this.blockId, actionId, rev);
191
+ log('pend blockId=%s actionId=%s rev=%s baseRev=%s', this.blockId, actionId, rev, baseRev);
161
192
  let meta = await this.storage.getMetadata(this.blockId);
162
193
  // Refuse a record that could never be promoted (see IBlockStorage.savePendingTransaction).
163
194
  // The metadata read above is unconditional anyway, so this costs one comparison and no I/O.
@@ -177,8 +208,15 @@ export class BlockStorage implements IBlockStorage {
177
208
  // This read-then-seed is exactly the window a concurrent replica used to land in (the
178
209
  // seed then erased its `latest`); the latch the caller holds is what closes it.
179
210
  meta = { latest: undefined, ranges: [] };
180
- await this.storage.saveMetadata(this.blockId, meta);
181
211
  }
212
+ // Record the slot the record claims (see BlockMetadata.pendingRevs) and the base it was computed
213
+ // against (`pendingBases`) in the same metadata write that seeds a fresh block, BEFORE the record
214
+ // itself: a crash between the two leaves an inert entry with no record, whereas the other order
215
+ // would leave a record with no claim — which every reader treats as the strongest kind and would
216
+ // refuse rivals on more than it should — and with no base, which the read-driven promotion
217
+ // would then decline to apply. A redelivered pend for the same action overwrites all three.
218
+ BlockStorage.recordClaim(meta, actionId, { rev, baseRev });
219
+ await this.storage.saveMetadata(this.blockId, meta);
182
220
  await this.storage.savePendingTransaction(this.blockId, actionId, transform);
183
221
  }
184
222
 
@@ -186,6 +224,58 @@ export class BlockStorage implements IBlockStorage {
186
224
  this.assertLatch(latch);
187
225
  log('cancel blockId=%s actionId=%s', this.blockId, actionId);
188
226
  await this.storage.deletePendingTransaction(this.blockId, actionId);
227
+ // Metadata is written only when the record had a claim to drop: a cancel of an absent or
228
+ // claim-less record (the common torn-cancel retry) stays a pure no-op on the metadata blob.
229
+ const meta = await this.storage.getMetadata(this.blockId);
230
+ if (meta && (meta.pendingRevs?.[actionId] !== undefined || meta.pendingBases?.[actionId] !== undefined)) {
231
+ BlockStorage.recordClaim(meta, actionId, undefined);
232
+ await this.storage.saveMetadata(this.blockId, meta);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Set (`claim` given) or clear (`undefined`) what `meta` says about `actionId`'s pending record —
238
+ * the slot it claims and the base it was computed against — keeping each map absent while it is
239
+ * empty. The two are always written together, so no path can drop a record's slot and leave its
240
+ * base behind, or the reverse: clearing is what every drop site calls — a delete, a promotion
241
+ * (`setLatest`), a same-action forward write (`saveForwardRevision`) and the dead-claim sweep.
242
+ */
243
+ private static recordClaim(meta: BlockMetadata, actionId: ActionId, claim: { rev?: number; baseRev?: number } | undefined): void {
244
+ BlockStorage.setEntry(meta, 'pendingRevs', actionId, claim?.rev);
245
+ BlockStorage.setEntry(meta, 'pendingBases', actionId, claim?.baseRev);
246
+ }
247
+
248
+ /** `meta[map][actionId] = value`, or removes the entry when `value` is undefined; the map itself
249
+ * is dropped from `meta` once empty, so an idle block's metadata carries neither key. */
250
+ private static setEntry(meta: BlockMetadata, map: 'pendingRevs' | 'pendingBases', actionId: ActionId, value: number | undefined): void {
251
+ const entries = { ...meta[map] };
252
+ if (value === undefined) delete entries[actionId];
253
+ else entries[actionId] = value;
254
+ if (Object.keys(entries).length === 0) delete meta[map];
255
+ else meta[map] = entries;
256
+ }
257
+
258
+ /**
259
+ * Every pending record claiming a revision at or below `latestRev` can never be promoted here:
260
+ * promotion needs `latest.rev < rev`, and `latest` only advances. Such a record is DEAD — a rival
261
+ * that lost the slot, or a torn write's leftover on a block a later commit has since moved past —
262
+ * and left in place it is reported as a live conflicting action to every reader of the block.
263
+ * Delete those records and drop their claims from `meta`, which the caller then saves; a record
264
+ * with no claim on file is left alone (its slot is unknown, so its death is unprovable here).
265
+ *
266
+ * Runs under the block's write latch on every path that advances `latest`, which is the one moment
267
+ * a record's death becomes provable. This is the locally decidable half of what backlog
268
+ * `debt-unpromotable-pending-records-need-a-sweep` asks for; the other half — a record whose slot
269
+ * the block has NOT reached, whose writer simply never came back — is not decidable from local
270
+ * state and is not touched here.
271
+ */
272
+ private async sweepDeadClaims(meta: BlockMetadata, latestRev: number): Promise<void> {
273
+ for (const [actionId, rev] of Object.entries(meta.pendingRevs ?? {}) as [ActionId, number][]) {
274
+ if (rev > latestRev) continue;
275
+ await this.storage.deletePendingTransaction(this.blockId, actionId);
276
+ BlockStorage.recordClaim(meta, actionId, undefined);
277
+ log('sweep-dead-claim blockId=%s actionId=%s claimedRev=%d latestRev=%d', this.blockId, actionId, rev, latestRev);
278
+ }
189
279
  }
190
280
 
191
281
  async *listRevisions(startRev: number, endRev: number): AsyncIterable<ActionRev> {
@@ -228,7 +318,31 @@ export class BlockStorage implements IBlockStorage {
228
318
  await this.storage.promotePendingTransaction(this.blockId, actionId);
229
319
  }
230
320
 
231
- async setLatest(latest: ActionRev, latch: BlockWriteLatch): Promise<void> {
321
+ async lineageOf({ actionId, rev }: ActionRev): Promise<BlockLineage> {
322
+ const meta = await this.storage.getMetadata(this.blockId);
323
+ const latest = meta?.latest;
324
+ if (!latest || latest.rev < rev) {
325
+ return 'behind';
326
+ }
327
+ if (latest.rev === rev) {
328
+ return latest.actionId === actionId ? 'contains' : 'excludes';
329
+ }
330
+ const heldBy = await this.storage.getRevision(this.blockId, rev);
331
+ if (heldBy !== undefined && heldBy !== actionId) {
332
+ return 'excludes';
333
+ }
334
+ // Past the revision. The index alone cannot answer from here: holding the action at `rev`
335
+ // does not prove `latest` was built from it (a replica taken since may descend from a base
336
+ // below `rev`), and holding nothing at `rev` does not prove it was not (a replica taken since
337
+ // may descend from it). Only a history derived HERE across `rev` settles it either way.
338
+ const floor = meta.lineageFloor;
339
+ if (floor === undefined || floor > rev) {
340
+ return 'unknown';
341
+ }
342
+ return heldBy === actionId ? 'contains' : 'excludes';
343
+ }
344
+
345
+ async setLatest(latest: ActionRev, builtOnPrior: boolean, latch: BlockWriteLatch): Promise<void> {
232
346
  this.assertLatch(latch);
233
347
  const meta = await this.storage.getMetadata(this.blockId);
234
348
  if (!meta) {
@@ -237,6 +351,7 @@ export class BlockStorage implements IBlockStorage {
237
351
  // Capture the prior latest rev BEFORE overwriting: coverage anchors to the earliest held rev.
238
352
  const prevRev = meta.latest?.rev;
239
353
  meta.latest = latest;
354
+ meta.lineageFloor = BlockStorage.nextLineageFloor(meta.lineageFloor, prevRev, latest.rev, builtOnPrior);
240
355
  // NOTE: re-sorts (mergeRanges) the whole ranges array on every commit; if a block ever
241
356
  // accumulates many disjoint ranges and commits show as slow, keep a running merged structure.
242
357
  // `getBlock(r)` is served by materializeBlock's DESCENDING walk (highest committed rev <= r).
@@ -252,6 +367,11 @@ export class BlockStorage implements IBlockStorage {
252
367
  // before this call advances neither.
253
368
  meta.ranges.unshift([prevRev ?? latest.rev]);
254
369
  meta.ranges = mergeRanges(meta.ranges);
370
+ // The committing action's own record was just MOVED by `promotePendingTransaction`: drop its
371
+ // claim without a delete, so the sweep does not pay a raw-storage op on a record that is already
372
+ // gone. Any other record claiming a slot at or below the new latest is dead and swept.
373
+ BlockStorage.recordClaim(meta, latest.actionId, undefined);
374
+ await this.sweepDeadClaims(meta, latest.rev);
255
375
  await this.storage.saveMetadata(this.blockId, meta);
256
376
  }
257
377
 
@@ -265,6 +385,7 @@ export class BlockStorage implements IBlockStorage {
265
385
  const currentRev = meta.latest?.rev ?? 0;
266
386
  let maxRev = currentRev;
267
387
  let maxActionId = meta.latest?.actionId;
388
+ let lineageFloor = meta.lineageFloor;
268
389
 
269
390
  // Probe forward until we hit a gap or a revision whose action is not yet
270
391
  // in the committed log (Crash-D2 state — retry-commit owns that advance).
@@ -273,6 +394,9 @@ export class BlockStorage implements IBlockStorage {
273
394
  if (actionId === undefined) break;
274
395
  const promoted = await this.storage.getTransaction(this.blockId, actionId);
275
396
  if (promoted === undefined) break;
397
+ // Each recovered revision is a commit whose `setLatest` was lost, so it owes the floor
398
+ // exactly what that `setLatest` would have recorded.
399
+ lineageFloor = BlockStorage.nextLineageFloor(lineageFloor, maxRev === 0 ? undefined : maxRev, next, promoted.insert === undefined);
276
400
  maxRev = next;
277
401
  maxActionId = actionId;
278
402
  }
@@ -280,6 +404,7 @@ export class BlockStorage implements IBlockStorage {
280
404
  if (maxRev > currentRev && maxActionId !== undefined) {
281
405
  const advanced: ActionRev = { rev: maxRev, actionId: maxActionId };
282
406
  meta.latest = advanced;
407
+ meta.lineageFloor = lineageFloor;
283
408
  // The lost setLatest would have merged each recovered revision's range; redo that
284
409
  // here. Open-ended from currentRev+1 (see setLatest): every rev in (currentRev, maxRev]
285
410
  // was verified present in the committed log above, and any rev > maxRev resolves via the
@@ -287,6 +412,13 @@ export class BlockStorage implements IBlockStorage {
287
412
  // the prior [E, currentRev+1) (from the earlier setLatest) into one open-ended [E, +inf).
288
413
  meta.ranges.unshift([currentRev + 1]);
289
414
  meta.ranges = mergeRanges(meta.ranges);
415
+ // Each recovered revision's lost `setLatest` also owed this sweep. Unlike `setLatest`, it
416
+ // DELETES each recovered action's record rather than just dropping its claim: a pend checks
417
+ // only `latest`, so a same-action retry re-pended while the `setLatest` was owed leaves a
418
+ // pending record beside the committed one (reachable through `StorageRepo.recoverBlock`).
419
+ // Dropping only its claim would leave a claim-less record, the strongest kind of reservation,
420
+ // that nothing ever removes. Recover runs only after a crash, so the extra op costs nothing.
421
+ await this.sweepDeadClaims(meta, maxRev);
290
422
  await this.storage.saveMetadata(this.blockId, meta);
291
423
  log('recover blockId=%s advanced latest from rev=%d to rev=%d', this.blockId, currentRev, maxRev);
292
424
  return { reconciled: true, latest: advanced };
@@ -404,11 +536,9 @@ export class BlockStorage implements IBlockStorage {
404
536
  // record is not this writer's to delete. Both paths run under the block's write latch, so
405
537
  // this deletion is already mutually exclusive with a live commit.
406
538
  //
407
- // NOTE: deletes only this revision's actionId, not every pending whose action is already
408
- // committed. A broader sweep would repair records orphaned by routes that do not carry the
409
- // committing actionId; if orphaned pendings ever show up in the field on blocks whose
410
- // committing action id differs, widen to a sweep over listPendingTransactions filtered by
411
- // getTransaction.
539
+ // This same-action delete stays explicit even though the sweep below would usually cover it:
540
+ // Invariant P must hold for a record that has no claim on file (pended before claims were
541
+ // recorded), which the sweep which reasons from claims cannot see.
412
542
  await this.storage.deletePendingTransaction(this.blockId, actionId);
413
543
 
414
544
  // Seed metadata when absent, advance latest, and merge the covered range.
@@ -416,7 +546,16 @@ export class BlockStorage implements IBlockStorage {
416
546
  if (!meta) {
417
547
  meta = { latest: undefined, ranges: [] };
418
548
  }
549
+ // Every OTHER record claiming a slot at or below the landing revision is dead too: the
550
+ // orphaned records a route that does not carry the committing action id used to leave behind
551
+ // (the cohort reconcile of a block a member fell behind on lands a later revision under a
552
+ // later action, and the missed action's record would otherwise stand forever).
553
+ BlockStorage.recordClaim(meta, actionId, undefined);
554
+ await this.sweepDeadClaims(meta, rev);
419
555
  meta.latest = { rev, actionId };
556
+ // Content this node did not derive: nothing here says what it was built from, so the known
557
+ // lineage starts over at this revision (see BlockMetadata.lineageFloor).
558
+ meta.lineageFloor = rev;
420
559
  // Open-ended coverage from the earliest held rev (see setLatest): the descending walk serves
421
560
  // any rev >= the anchor. A prior latest at prevRev (< rev per the monotonic guard) is a
422
561
  // materialized point, so anchor at prevRev; the first write (prevRev undefined) anchors at
@@ -758,6 +897,18 @@ export class BlockStorage implements IBlockStorage {
758
897
  }
759
898
  }
760
899
 
900
+ /**
901
+ * The lineage floor after a COMMIT lands `rev` on top of `prevRev` (see
902
+ * {@link BlockMetadata.lineageFloor}). A revision built on the prior content extends the known
903
+ * lineage downwards as far as it already reached — or, for metadata that predates the field,
904
+ * to the revision it was just built on, which is all this commit can vouch for. A revision that
905
+ * stands on its own (an insert replaces the block wholesale), and a block's first revision,
906
+ * start the lineage at themselves.
907
+ */
908
+ private static nextLineageFloor(floor: number | undefined, prevRev: number | undefined, rev: number, builtOnPrior: boolean): number {
909
+ return builtOnPrior && prevRev !== undefined ? (floor ?? prevRev) : rev;
910
+ }
911
+
761
912
  private inRanges(rev: number, ranges: RevisionRange[]): boolean {
762
913
  return ranges.some(range =>
763
914
  rev >= range[0] && (range[1] === undefined || rev < range[1])
@@ -1,6 +1,7 @@
1
- import type { BlockId, IBlock, Transform, ActionId, ActionRev } from "@optimystic/db-core";
1
+ import type { BlockId, IBlock, Transform, ActionId, ActionRev, BlockLineage } from "@optimystic/db-core";
2
2
  import type { BlockCommitProof } from "../cluster/commit-proof.js";
3
3
  import type { BlockWriteLatch } from "./block-latch.js";
4
+ import type { PendingClaim } from "./pending-claim.js";
4
5
 
5
6
  /**
6
7
  * Thrown by {@link IBlockStorage.getBlock} when the block has metadata here but the target revision
@@ -111,12 +112,44 @@ export interface IBlockStorage {
111
112
  * under one multi-block write-latch hold, so every block it saves was observed under that same
112
113
  * hold to satisfy `latest === undefined || latest.rev < rev`, and no writer can advance a block
113
114
  * without its latch. A throw here means a caller reintroduced check-then-act.
115
+ *
116
+ * The revision is also KEPT, in the block's metadata (`BlockMetadata.pendingRevs`), so the record
117
+ * can later be read as a claim on a specific slot — see {@link listPendingClaims}.
118
+ *
119
+ * `baseRev` is the committed revision the record's update operations were computed against — the
120
+ * pend's `baseRevs[blockId]` — or `undefined` when the pend named none for this block (an
121
+ * inserted or deleted block, or a base the author did not know). Kept beside the revision
122
+ * (`BlockMetadata.pendingBases`), in the same metadata write, so that every later step that
123
+ * applies the record can refuse to apply it to a different version of the block — see
124
+ * {@link pendingClaimOf}. Stored as told: whether it matches this node's `latest` is decided
125
+ * where the record is APPLIED (`StorageRepo.internalCommit`, and the read-driven promotion in
126
+ * `StorageRepo.get`), never here.
114
127
  */
115
- savePendingTransaction(actionId: ActionId, transform: Transform, rev: number | undefined, latch: BlockWriteLatch): Promise<void>;
128
+ savePendingTransaction(actionId: ActionId, transform: Transform, rev: number | undefined, baseRev: number | undefined, latch: BlockWriteLatch): Promise<void>;
116
129
 
117
- /** Deletes a pending action */
130
+ /** Deletes a pending action, and the revision and base it was recorded as claiming. */
118
131
  deletePendingTransaction(actionId: ActionId, latch: BlockWriteLatch): Promise<void>;
119
132
 
133
+ /**
134
+ * Every pending record on this block, each with the revision it was pended at and the base it
135
+ * was computed against when those are known — the pending namespace joined against
136
+ * `BlockMetadata.pendingRevs` and `pendingBases`. This is the view the rival-pend checks read
137
+ * (`StorageRepo.pend`'s apply-time scan and `ClusterMember.validatePendOperations`' promise
138
+ * vote), because whether a record reserves the block against a given pend depends on the slot
139
+ * it claims measured against the pend's revision — and, at the vote of a cohort that can leave a
140
+ * member out, its base (`isReservationAgainst`) — not on its mere presence. Read-only; no latch.
141
+ */
142
+ listPendingClaims(): Promise<PendingClaim[]>;
143
+
144
+ /**
145
+ * The claim of ONE pending record — `actionId`'s entry of {@link listPendingClaims} — or
146
+ * `undefined` when this block holds no pending record for the action. For the two apply sites
147
+ * (`StorageRepo.internalCommit` and the read-driven promotion in `StorageRepo.get`), which need
148
+ * one record's base on the hot commit path and must not enumerate every record to get it.
149
+ * Read-only; no latch of its own: both callers already hold the block's write latch.
150
+ */
151
+ pendingClaimOf(actionId: ActionId): Promise<PendingClaim | undefined>;
152
+
120
153
  /** Lists revisions in ascending or descending order between startRev and endRev (inclusive) */
121
154
  listRevisions(startRev: number, endRev: number): AsyncIterable<ActionRev>;
122
155
 
@@ -152,8 +185,30 @@ export interface IBlockStorage {
152
185
  */
153
186
  promotePendingTransaction(actionId: ActionId, latch: BlockWriteLatch): Promise<void>;
154
187
 
155
- /** Sets the latest revision information */
156
- setLatest(latest: ActionRev, latch: BlockWriteLatch): Promise<void>;
188
+ /**
189
+ * Sets the latest revision information, as the last step of committing `latest` here.
190
+ *
191
+ * Call it only after {@link promotePendingTransaction} has moved `latest.actionId`'s record: it
192
+ * drops that action's claim without deleting a record, so a caller that skipped promotion would
193
+ * leave a claim-less pending record behind (see `BlockMetadata.pendingRevs`).
194
+ *
195
+ * `builtOnPrior` says how the revision's content was produced, which is what the block's known
196
+ * lineage depends on (see `BlockMetadata.lineageFloor`): `true` when this node applied the
197
+ * revision's transform to the content of its previous revision (an update-only or delete
198
+ * transform); `false` when the content stands on its own because the transform carried an
199
+ * `insert`. Required rather than defaulted: a caller that guessed `true` for an insert would let
200
+ * {@link lineageOf} vouch for history the content never came from.
201
+ */
202
+ setLatest(latest: ActionRev, builtOnPrior: boolean, latch: BlockWriteLatch): Promise<void>;
203
+
204
+ /**
205
+ * What THIS node's records say about whether the block's content at `latest` was built from the
206
+ * committed `target` — see `BlockLineage` in db-core for the four answers. Local and read-only:
207
+ * no latch, no peer fetch, no promotion. `unknown` is the honest answer whenever this node is
208
+ * past `target.rev` but took later content it did not derive itself (a replica, a restore);
209
+ * another cohort member that applied those revisions locally can still answer.
210
+ */
211
+ lineageOf(target: ActionRev): Promise<BlockLineage>;
157
212
 
158
213
  /**
159
214
  * Persist a replica of a block received out-of-band (churn re-replication).