@optimystic/db-core 0.0.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 (345) hide show
  1. package/README.md +328 -0
  2. package/dist/index.min.js +18 -0
  3. package/dist/index.min.js.map +7 -0
  4. package/dist/src/blocks/block-store.d.ts +12 -0
  5. package/dist/src/blocks/block-store.d.ts.map +1 -0
  6. package/dist/src/blocks/block-store.js +2 -0
  7. package/dist/src/blocks/block-store.js.map +1 -0
  8. package/dist/src/blocks/block-types.d.ts +3 -0
  9. package/dist/src/blocks/block-types.d.ts.map +1 -0
  10. package/dist/src/blocks/block-types.js +9 -0
  11. package/dist/src/blocks/block-types.js.map +1 -0
  12. package/dist/src/blocks/helpers.d.ts +4 -0
  13. package/dist/src/blocks/helpers.d.ts.map +1 -0
  14. package/dist/src/blocks/helpers.js +12 -0
  15. package/dist/src/blocks/helpers.js.map +1 -0
  16. package/dist/src/blocks/index.d.ts +5 -0
  17. package/dist/src/blocks/index.d.ts.map +1 -0
  18. package/dist/src/blocks/index.js +5 -0
  19. package/dist/src/blocks/index.js.map +1 -0
  20. package/dist/src/blocks/structs.d.ts +14 -0
  21. package/dist/src/blocks/structs.d.ts.map +1 -0
  22. package/dist/src/blocks/structs.js +2 -0
  23. package/dist/src/blocks/structs.js.map +1 -0
  24. package/dist/src/btree/btree.d.ts +135 -0
  25. package/dist/src/btree/btree.d.ts.map +1 -0
  26. package/dist/src/btree/btree.js +727 -0
  27. package/dist/src/btree/btree.js.map +1 -0
  28. package/dist/src/btree/independent-trunk.d.ts +17 -0
  29. package/dist/src/btree/independent-trunk.d.ts.map +1 -0
  30. package/dist/src/btree/independent-trunk.js +41 -0
  31. package/dist/src/btree/independent-trunk.js.map +1 -0
  32. package/dist/src/btree/index.d.ts +6 -0
  33. package/dist/src/btree/index.d.ts.map +1 -0
  34. package/dist/src/btree/index.js +6 -0
  35. package/dist/src/btree/index.js.map +1 -0
  36. package/dist/src/btree/key-range.d.ts +13 -0
  37. package/dist/src/btree/key-range.d.ts.map +1 -0
  38. package/dist/src/btree/key-range.js +20 -0
  39. package/dist/src/btree/key-range.js.map +1 -0
  40. package/dist/src/btree/keyset.d.ts +4 -0
  41. package/dist/src/btree/keyset.d.ts.map +1 -0
  42. package/dist/src/btree/keyset.js +4 -0
  43. package/dist/src/btree/keyset.js.map +1 -0
  44. package/dist/src/btree/nodes.d.ts +16 -0
  45. package/dist/src/btree/nodes.d.ts.map +1 -0
  46. package/dist/src/btree/nodes.js +9 -0
  47. package/dist/src/btree/nodes.js.map +1 -0
  48. package/dist/src/btree/path.d.ts +22 -0
  49. package/dist/src/btree/path.d.ts.map +1 -0
  50. package/dist/src/btree/path.js +39 -0
  51. package/dist/src/btree/path.js.map +1 -0
  52. package/dist/src/btree/tree-block.d.ts +7 -0
  53. package/dist/src/btree/tree-block.d.ts.map +1 -0
  54. package/dist/src/btree/tree-block.js +5 -0
  55. package/dist/src/btree/tree-block.js.map +1 -0
  56. package/dist/src/btree/trunk.d.ts +13 -0
  57. package/dist/src/btree/trunk.d.ts.map +1 -0
  58. package/dist/src/btree/trunk.js +2 -0
  59. package/dist/src/btree/trunk.js.map +1 -0
  60. package/dist/src/chain/chain-nodes.d.ts +18 -0
  61. package/dist/src/chain/chain-nodes.d.ts.map +1 -0
  62. package/dist/src/chain/chain-nodes.js +10 -0
  63. package/dist/src/chain/chain-nodes.js.map +1 -0
  64. package/dist/src/chain/chain.d.ts +75 -0
  65. package/dist/src/chain/chain.d.ts.map +1 -0
  66. package/dist/src/chain/chain.js +268 -0
  67. package/dist/src/chain/chain.js.map +1 -0
  68. package/dist/src/chain/index.d.ts +2 -0
  69. package/dist/src/chain/index.d.ts.map +1 -0
  70. package/dist/src/chain/index.js +2 -0
  71. package/dist/src/chain/index.js.map +1 -0
  72. package/dist/src/cluster/i-cluster.d.ts +5 -0
  73. package/dist/src/cluster/i-cluster.d.ts.map +1 -0
  74. package/dist/src/cluster/i-cluster.js +2 -0
  75. package/dist/src/cluster/i-cluster.js.map +1 -0
  76. package/dist/src/cluster/index.d.ts +3 -0
  77. package/dist/src/cluster/index.d.ts.map +1 -0
  78. package/dist/src/cluster/index.js +3 -0
  79. package/dist/src/cluster/index.js.map +1 -0
  80. package/dist/src/cluster/structs.d.ts +47 -0
  81. package/dist/src/cluster/structs.d.ts.map +1 -0
  82. package/dist/src/cluster/structs.js +2 -0
  83. package/dist/src/cluster/structs.js.map +1 -0
  84. package/dist/src/collection/action.d.ts +26 -0
  85. package/dist/src/collection/action.d.ts.map +1 -0
  86. package/dist/src/collection/action.js +2 -0
  87. package/dist/src/collection/action.js.map +1 -0
  88. package/dist/src/collection/collection.d.ts +48 -0
  89. package/dist/src/collection/collection.d.ts.map +1 -0
  90. package/dist/src/collection/collection.js +175 -0
  91. package/dist/src/collection/collection.js.map +1 -0
  92. package/dist/src/collection/index.d.ts +4 -0
  93. package/dist/src/collection/index.d.ts.map +1 -0
  94. package/dist/src/collection/index.js +4 -0
  95. package/dist/src/collection/index.js.map +1 -0
  96. package/dist/src/collection/struct.d.ts +16 -0
  97. package/dist/src/collection/struct.d.ts.map +1 -0
  98. package/dist/src/collection/struct.js +2 -0
  99. package/dist/src/collection/struct.js.map +1 -0
  100. package/dist/src/collections/diary/diary.d.ts +9 -0
  101. package/dist/src/collections/diary/diary.d.ts.map +1 -0
  102. package/dist/src/collections/diary/diary.js +37 -0
  103. package/dist/src/collections/diary/diary.js.map +1 -0
  104. package/dist/src/collections/diary/index.d.ts +3 -0
  105. package/dist/src/collections/diary/index.d.ts.map +1 -0
  106. package/dist/src/collections/diary/index.js +3 -0
  107. package/dist/src/collections/diary/index.js.map +1 -0
  108. package/dist/src/collections/diary/struct.d.ts +2 -0
  109. package/dist/src/collections/diary/struct.d.ts.map +1 -0
  110. package/dist/src/collections/diary/struct.js +3 -0
  111. package/dist/src/collections/diary/struct.js.map +1 -0
  112. package/dist/src/collections/index.d.ts +3 -0
  113. package/dist/src/collections/index.d.ts.map +1 -0
  114. package/dist/src/collections/index.js +3 -0
  115. package/dist/src/collections/index.js.map +1 -0
  116. package/dist/src/collections/tree/collection-trunk.d.ts +11 -0
  117. package/dist/src/collections/tree/collection-trunk.d.ts.map +1 -0
  118. package/dist/src/collections/tree/collection-trunk.js +22 -0
  119. package/dist/src/collections/tree/collection-trunk.js.map +1 -0
  120. package/dist/src/collections/tree/index.d.ts +3 -0
  121. package/dist/src/collections/tree/index.d.ts.map +1 -0
  122. package/dist/src/collections/tree/index.js +3 -0
  123. package/dist/src/collections/tree/index.js.map +1 -0
  124. package/dist/src/collections/tree/struct.d.ts +12 -0
  125. package/dist/src/collections/tree/struct.d.ts.map +1 -0
  126. package/dist/src/collections/tree/struct.js +4 -0
  127. package/dist/src/collections/tree/struct.js.map +1 -0
  128. package/dist/src/collections/tree/tree.d.ts +34 -0
  129. package/dist/src/collections/tree/tree.d.ts.map +1 -0
  130. package/dist/src/collections/tree/tree.js +100 -0
  131. package/dist/src/collections/tree/tree.js.map +1 -0
  132. package/dist/src/index.d.ts +18 -0
  133. package/dist/src/index.d.ts.map +1 -0
  134. package/dist/src/index.js +18 -0
  135. package/dist/src/index.js.map +1 -0
  136. package/dist/src/log/index.d.ts +3 -0
  137. package/dist/src/log/index.d.ts.map +1 -0
  138. package/dist/src/log/index.js +3 -0
  139. package/dist/src/log/index.js.map +1 -0
  140. package/dist/src/log/log.d.ts +57 -0
  141. package/dist/src/log/log.d.ts.map +1 -0
  142. package/dist/src/log/log.js +131 -0
  143. package/dist/src/log/log.js.map +1 -0
  144. package/dist/src/log/struct.d.ts +36 -0
  145. package/dist/src/log/struct.d.ts.map +1 -0
  146. package/dist/src/log/struct.js +3 -0
  147. package/dist/src/log/struct.js.map +1 -0
  148. package/dist/src/network/i-key-network.d.ts +21 -0
  149. package/dist/src/network/i-key-network.d.ts.map +1 -0
  150. package/dist/src/network/i-key-network.js +2 -0
  151. package/dist/src/network/i-key-network.js.map +1 -0
  152. package/dist/src/network/i-peer-network.d.ts +8 -0
  153. package/dist/src/network/i-peer-network.d.ts.map +1 -0
  154. package/dist/src/network/i-peer-network.js +2 -0
  155. package/dist/src/network/i-peer-network.js.map +1 -0
  156. package/dist/src/network/i-repo.d.ts +17 -0
  157. package/dist/src/network/i-repo.d.ts.map +1 -0
  158. package/dist/src/network/i-repo.js +2 -0
  159. package/dist/src/network/i-repo.js.map +1 -0
  160. package/dist/src/network/index.d.ts +6 -0
  161. package/dist/src/network/index.d.ts.map +1 -0
  162. package/dist/src/network/index.js +6 -0
  163. package/dist/src/network/index.js.map +1 -0
  164. package/dist/src/network/repo-protocol.d.ts +19 -0
  165. package/dist/src/network/repo-protocol.d.ts.map +1 -0
  166. package/dist/src/network/repo-protocol.js +2 -0
  167. package/dist/src/network/repo-protocol.js.map +1 -0
  168. package/dist/src/network/struct.d.ts +115 -0
  169. package/dist/src/network/struct.d.ts.map +1 -0
  170. package/dist/src/network/struct.js +2 -0
  171. package/dist/src/network/struct.js.map +1 -0
  172. package/dist/src/transaction/actions-engine.d.ts +37 -0
  173. package/dist/src/transaction/actions-engine.d.ts.map +1 -0
  174. package/dist/src/transaction/actions-engine.js +67 -0
  175. package/dist/src/transaction/actions-engine.js.map +1 -0
  176. package/dist/src/transaction/context.d.ts +60 -0
  177. package/dist/src/transaction/context.d.ts.map +1 -0
  178. package/dist/src/transaction/context.js +91 -0
  179. package/dist/src/transaction/context.js.map +1 -0
  180. package/dist/src/transaction/coordinator.d.ts +118 -0
  181. package/dist/src/transaction/coordinator.d.ts.map +1 -0
  182. package/dist/src/transaction/coordinator.js +417 -0
  183. package/dist/src/transaction/coordinator.js.map +1 -0
  184. package/dist/src/transaction/index.d.ts +10 -0
  185. package/dist/src/transaction/index.d.ts.map +1 -0
  186. package/dist/src/transaction/index.js +7 -0
  187. package/dist/src/transaction/index.js.map +1 -0
  188. package/dist/src/transaction/session.d.ts +80 -0
  189. package/dist/src/transaction/session.d.ts.map +1 -0
  190. package/dist/src/transaction/session.js +161 -0
  191. package/dist/src/transaction/session.js.map +1 -0
  192. package/dist/src/transaction/transaction.d.ts +156 -0
  193. package/dist/src/transaction/transaction.d.ts.map +1 -0
  194. package/dist/src/transaction/transaction.js +31 -0
  195. package/dist/src/transaction/transaction.js.map +1 -0
  196. package/dist/src/transaction/validator.d.ts +46 -0
  197. package/dist/src/transaction/validator.d.ts.map +1 -0
  198. package/dist/src/transaction/validator.js +97 -0
  199. package/dist/src/transaction/validator.js.map +1 -0
  200. package/dist/src/transactor/index.d.ts +4 -0
  201. package/dist/src/transactor/index.d.ts.map +1 -0
  202. package/dist/src/transactor/index.js +4 -0
  203. package/dist/src/transactor/index.js.map +1 -0
  204. package/dist/src/transactor/network-transactor.d.ts +36 -0
  205. package/dist/src/transactor/network-transactor.d.ts.map +1 -0
  206. package/dist/src/transactor/network-transactor.js +297 -0
  207. package/dist/src/transactor/network-transactor.js.map +1 -0
  208. package/dist/src/transactor/transactor-source.d.ts +24 -0
  209. package/dist/src/transactor/transactor-source.d.ts.map +1 -0
  210. package/dist/src/transactor/transactor-source.js +62 -0
  211. package/dist/src/transactor/transactor-source.js.map +1 -0
  212. package/dist/src/transactor/transactor.d.ts +38 -0
  213. package/dist/src/transactor/transactor.d.ts.map +1 -0
  214. package/dist/src/transactor/transactor.js +2 -0
  215. package/dist/src/transactor/transactor.js.map +1 -0
  216. package/dist/src/transform/atomic.d.ts +8 -0
  217. package/dist/src/transform/atomic.d.ts.map +1 -0
  218. package/dist/src/transform/atomic.js +14 -0
  219. package/dist/src/transform/atomic.js.map +1 -0
  220. package/dist/src/transform/cache-source.d.ts +13 -0
  221. package/dist/src/transform/cache-source.d.ts.map +1 -0
  222. package/dist/src/transform/cache-source.js +52 -0
  223. package/dist/src/transform/cache-source.js.map +1 -0
  224. package/dist/src/transform/helpers.d.ts +25 -0
  225. package/dist/src/transform/helpers.d.ts.map +1 -0
  226. package/dist/src/transform/helpers.js +105 -0
  227. package/dist/src/transform/helpers.js.map +1 -0
  228. package/dist/src/transform/index.d.ts +6 -0
  229. package/dist/src/transform/index.d.ts.map +1 -0
  230. package/dist/src/transform/index.js +6 -0
  231. package/dist/src/transform/index.js.map +1 -0
  232. package/dist/src/transform/struct.d.ts +19 -0
  233. package/dist/src/transform/struct.d.ts.map +1 -0
  234. package/dist/src/transform/struct.js +2 -0
  235. package/dist/src/transform/struct.js.map +1 -0
  236. package/dist/src/transform/tracker.d.ts +22 -0
  237. package/dist/src/transform/tracker.d.ts.map +1 -0
  238. package/dist/src/transform/tracker.js +64 -0
  239. package/dist/src/transform/tracker.js.map +1 -0
  240. package/dist/src/utility/actor.d.ts +11 -0
  241. package/dist/src/utility/actor.d.ts.map +1 -0
  242. package/dist/src/utility/actor.js +39 -0
  243. package/dist/src/utility/actor.js.map +1 -0
  244. package/dist/src/utility/batch-coordinator.d.ts +56 -0
  245. package/dist/src/utility/batch-coordinator.d.ts.map +1 -0
  246. package/dist/src/utility/batch-coordinator.js +127 -0
  247. package/dist/src/utility/batch-coordinator.js.map +1 -0
  248. package/dist/src/utility/block-id-to-bytes.d.ts +3 -0
  249. package/dist/src/utility/block-id-to-bytes.d.ts.map +1 -0
  250. package/dist/src/utility/block-id-to-bytes.js +7 -0
  251. package/dist/src/utility/block-id-to-bytes.js.map +1 -0
  252. package/dist/src/utility/ensured.d.ts +3 -0
  253. package/dist/src/utility/ensured.d.ts.map +1 -0
  254. package/dist/src/utility/ensured.js +24 -0
  255. package/dist/src/utility/ensured.js.map +1 -0
  256. package/dist/src/utility/groupby.d.ts +8 -0
  257. package/dist/src/utility/groupby.d.ts.map +1 -0
  258. package/dist/src/utility/groupby.js +15 -0
  259. package/dist/src/utility/groupby.js.map +1 -0
  260. package/dist/src/utility/is-record-empty.d.ts +3 -0
  261. package/dist/src/utility/is-record-empty.d.ts.map +1 -0
  262. package/dist/src/utility/is-record-empty.js +7 -0
  263. package/dist/src/utility/is-record-empty.js.map +1 -0
  264. package/dist/src/utility/latches.d.ts +11 -0
  265. package/dist/src/utility/latches.d.ts.map +1 -0
  266. package/dist/src/utility/latches.js +36 -0
  267. package/dist/src/utility/latches.js.map +1 -0
  268. package/dist/src/utility/nameof.d.ts +3 -0
  269. package/dist/src/utility/nameof.d.ts.map +1 -0
  270. package/dist/src/utility/nameof.js +5 -0
  271. package/dist/src/utility/nameof.js.map +1 -0
  272. package/dist/src/utility/pending.d.ts +13 -0
  273. package/dist/src/utility/pending.d.ts.map +1 -0
  274. package/dist/src/utility/pending.js +37 -0
  275. package/dist/src/utility/pending.js.map +1 -0
  276. package/package.json +56 -0
  277. package/src/blocks/block-store.ts +13 -0
  278. package/src/blocks/block-types.ts +11 -0
  279. package/src/blocks/helpers.ts +13 -0
  280. package/src/blocks/index.ts +5 -0
  281. package/src/blocks/structs.ts +17 -0
  282. package/src/btree/btree.ts +804 -0
  283. package/src/btree/independent-trunk.ts +54 -0
  284. package/src/btree/index.ts +5 -0
  285. package/src/btree/key-range.ts +15 -0
  286. package/src/btree/keyset.ts +6 -0
  287. package/src/btree/nodes.ts +25 -0
  288. package/src/btree/path.ts +37 -0
  289. package/src/btree/tree-block.ts +11 -0
  290. package/src/btree/trunk.ts +14 -0
  291. package/src/chain/chain-nodes.ts +24 -0
  292. package/src/chain/chain.ts +324 -0
  293. package/src/chain/index.ts +2 -0
  294. package/src/cluster/i-cluster.ts +6 -0
  295. package/src/cluster/index.ts +2 -0
  296. package/src/cluster/structs.ts +46 -0
  297. package/src/collection/action.ts +31 -0
  298. package/src/collection/collection.ts +200 -0
  299. package/src/collection/index.ts +3 -0
  300. package/src/collection/struct.ts +20 -0
  301. package/src/collections/diary/diary.ts +43 -0
  302. package/src/collections/diary/index.ts +2 -0
  303. package/src/collections/diary/struct.ts +3 -0
  304. package/src/collections/index.ts +2 -0
  305. package/src/collections/tree/collection-trunk.ts +25 -0
  306. package/src/collections/tree/index.ts +2 -0
  307. package/src/collections/tree/readme.md +19 -0
  308. package/src/collections/tree/struct.ts +18 -0
  309. package/src/collections/tree/tree.ts +124 -0
  310. package/src/index.ts +17 -0
  311. package/src/log/index.ts +2 -0
  312. package/src/log/log.ts +155 -0
  313. package/src/log/struct.ts +40 -0
  314. package/src/network/i-key-network.ts +24 -0
  315. package/src/network/i-peer-network.ts +8 -0
  316. package/src/network/i-repo.ts +19 -0
  317. package/src/network/index.ts +5 -0
  318. package/src/network/repo-protocol.ts +12 -0
  319. package/src/network/struct.ts +137 -0
  320. package/src/transaction/actions-engine.ts +83 -0
  321. package/src/transaction/context.ts +103 -0
  322. package/src/transaction/coordinator.ts +583 -0
  323. package/src/transaction/index.ts +30 -0
  324. package/src/transaction/session.ts +182 -0
  325. package/src/transaction/transaction.ts +205 -0
  326. package/src/transaction/validator.ts +150 -0
  327. package/src/transactor/index.ts +4 -0
  328. package/src/transactor/network-transactor.ts +435 -0
  329. package/src/transactor/transactor-source.ts +65 -0
  330. package/src/transactor/transactor.ts +44 -0
  331. package/src/transform/atomic.ts +16 -0
  332. package/src/transform/cache-source.ts +57 -0
  333. package/src/transform/helpers.ts +117 -0
  334. package/src/transform/index.ts +5 -0
  335. package/src/transform/struct.ts +22 -0
  336. package/src/transform/tracker.ts +70 -0
  337. package/src/utility/actor.ts +62 -0
  338. package/src/utility/batch-coordinator.ts +174 -0
  339. package/src/utility/block-id-to-bytes.ts +8 -0
  340. package/src/utility/ensured.ts +32 -0
  341. package/src/utility/groupby.ts +18 -0
  342. package/src/utility/is-record-empty.ts +5 -0
  343. package/src/utility/latches.ts +42 -0
  344. package/src/utility/nameof.ts +7 -0
  345. package/src/utility/pending.ts +41 -0
@@ -0,0 +1,435 @@
1
+ import { type PeerId } from "@libp2p/interface";
2
+ import { peerIdFromString } from "@libp2p/peer-id";
3
+ import type { ActionTransforms, ActionBlocks, BlockActionStatus, ITransactor, PendSuccess, StaleFailure, IKeyNetwork, BlockId, GetBlockResults, PendResult, CommitResult, PendRequest, IRepo, BlockGets, Transforms, CommitRequest, ActionId, RepoCommitRequest, ClusterNomineesResult } from "../index.js";
4
+ import { transformForBlockId, groupBy, concatTransforms, concatTransform, transformsFromTransform, blockIdsForTransforms } from "../index.js";
5
+ import { blockIdToBytes } from "../utility/block-id-to-bytes.js";
6
+ import { isRecordEmpty } from "../utility/is-record-empty.js";
7
+ import { type CoordinatorBatch, makeBatchesByPeer, incompleteBatches, everyBatch, allBatches, mergeBlocks, processBatches, createBatchesForPayload } from "../utility/batch-coordinator.js";
8
+
9
+ type NetworkTransactorInit = {
10
+ timeoutMs: number;
11
+ abortOrCancelTimeoutMs: number;
12
+ keyNetwork: IKeyNetwork;
13
+ getRepo: (peerId: PeerId) => IRepo;
14
+ }
15
+
16
+ export class NetworkTransactor implements ITransactor {
17
+ private readonly keyNetwork: IKeyNetwork;
18
+ private readonly timeoutMs: number;
19
+ private readonly abortOrCancelTimeoutMs: number;
20
+ private readonly getRepo: (peerId: PeerId) => IRepo;
21
+
22
+ constructor(
23
+ init: NetworkTransactorInit,
24
+ ) {
25
+ this.keyNetwork = init.keyNetwork;
26
+ this.timeoutMs = init.timeoutMs;
27
+ this.abortOrCancelTimeoutMs = init.abortOrCancelTimeoutMs;
28
+ this.getRepo = init.getRepo;
29
+ }
30
+
31
+ async get(blockGets: BlockGets): Promise<GetBlockResults> {
32
+ // Group by block id
33
+ const distinctBlockIds = Array.from(new Set(blockGets.blockIds));
34
+
35
+ const batches = await this.batchesForPayload<BlockId[], GetBlockResults>(
36
+ distinctBlockIds,
37
+ distinctBlockIds,
38
+ (gets, blockId, mergeWithGets) => [...(mergeWithGets ?? []), ...gets.filter(bid => bid === blockId)],
39
+ []
40
+ );
41
+
42
+ const expiration = Date.now() + this.timeoutMs;
43
+
44
+ let error: Error | undefined;
45
+ try {
46
+ await processBatches(
47
+ batches,
48
+ (batch) => this.getRepo(batch.peerId).get({ blockIds: batch.payload, context: blockGets.context }, { expiration }),
49
+ batch => batch.payload,
50
+ (gets, blockId, mergeWithGets) => [...(mergeWithGets ?? []), ...gets.filter(bid => bid === blockId)],
51
+ expiration,
52
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
53
+ );
54
+ } catch (e) {
55
+ error = e as Error;
56
+ }
57
+
58
+ // Second-chance retry: if batch failed to respond OR responded with "not found"
59
+ // Different cluster members may have different views; retry with other coordinators
60
+ const hasValidResponse = (b: CoordinatorBatch<BlockId[], GetBlockResults>) => {
61
+ return b.request?.isResponse === true && b.request.response != null;
62
+ };
63
+
64
+ const hasBlockInResponse = (b: CoordinatorBatch<BlockId[], GetBlockResults>) => {
65
+ if (!hasValidResponse(b)) return false;
66
+ const resp = b.request!.response! as GetBlockResults;
67
+ return b.payload.some(bid => {
68
+ const entry = resp[bid];
69
+ return entry && typeof entry === 'object' && 'block' in entry && entry.block != null;
70
+ });
71
+ };
72
+
73
+ // Retry batches that either failed to respond OR responded with "not found"
74
+ // This provides tolerance for different cluster member views
75
+ const retryable = Array.from(allBatches(batches)).filter(b =>
76
+ !hasValidResponse(b as any) || !hasBlockInResponse(b as any)
77
+ ) as CoordinatorBatch<BlockId[], GetBlockResults>[];
78
+
79
+ if (retryable.length > 0 && Date.now() < expiration) {
80
+ try {
81
+ const excludedByRoot = new Map<CoordinatorBatch<BlockId[], GetBlockResults>, Set<PeerId>>();
82
+ for (const b of retryable) {
83
+ const excluded = new Set<PeerId>([b.peerId, ...((b.excludedPeers ?? []) as PeerId[])]);
84
+ excludedByRoot.set(b, excluded);
85
+ const retries = await createBatchesForPayload<BlockId[], GetBlockResults>(
86
+ b.payload,
87
+ b.payload,
88
+ (gets, blockId, mergeWithGets) => [...(mergeWithGets ?? []), ...gets.filter(id => id === blockId)],
89
+ Array.from(excluded),
90
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
91
+ );
92
+ if (retries.length > 0) {
93
+ b.subsumedBy = [...(b.subsumedBy ?? []), ...retries];
94
+ await processBatches(
95
+ retries,
96
+ (batch) => this.getRepo(batch.peerId).get({ blockIds: batch.payload, context: blockGets.context }, { expiration }),
97
+ batch => batch.payload,
98
+ (gets, blockId, mergeWithGets) => [...(mergeWithGets ?? []), ...gets.filter(id => id === blockId)],
99
+ expiration,
100
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
101
+ );
102
+ }
103
+ }
104
+ } catch (e) {
105
+ // keep original error if any
106
+ if (!error) error = e as Error;
107
+ }
108
+ }
109
+
110
+
111
+ // Cache the completed batches that had actual responses (not just coordinator not found)
112
+ const completedBatches = Array.from(allBatches(batches, b => b.request?.isResponse as boolean && !isRecordEmpty(b.request!.response!)));
113
+
114
+ // Create a lookup map from successful responses only
115
+ const resultEntries = new Map<string, any>();
116
+ for (const batch of completedBatches) {
117
+ const resp = batch.request!.response! as any;
118
+ for (const [bid, res] of Object.entries(resp)) {
119
+ const existing = resultEntries.get(bid);
120
+ // Prefer responses that include a materialized block
121
+ const resHasBlock = res && typeof res === 'object' && 'block' in (res as any) && (res as any).block != null;
122
+ const existingHasBlock = existing && typeof existing === 'object' && 'block' in (existing as any) && (existing as any).block != null;
123
+ if (!existing || (resHasBlock && !existingHasBlock)) {
124
+ resultEntries.set(bid, res);
125
+ }
126
+ }
127
+ }
128
+ // Ensure we have at least one response per requested block id
129
+ const missingIds = distinctBlockIds.filter(bid => !resultEntries.has(bid));
130
+ if (missingIds.length > 0) {
131
+ const details = this.formatBatchStatuses(batches,
132
+ b => (b.request?.isResponse as boolean) ?? false,
133
+ b => {
134
+ const status = b.request == null ? 'no-response' : (b.request.isResponse ? 'response' : 'in-flight')
135
+ return `${b.peerId.toString()}[block:${b.blockId}](${status})`
136
+ });
137
+ const aggregate = new Error(`Some peers did not complete: ${details}${error ? `; root: ${error.message}` : ''}`);
138
+ (aggregate as any).cause = error;
139
+ throw aggregate;
140
+ }
141
+
142
+ return Object.fromEntries(resultEntries) as GetBlockResults;
143
+ }
144
+
145
+ async getStatus(blockActions: ActionBlocks[]): Promise<BlockActionStatus[]> {
146
+ throw new Error("Method not implemented.");
147
+ }
148
+
149
+ private async consolidateCoordinators(
150
+ blockIds: BlockId[],
151
+ transforms: Transforms,
152
+ transformForBlock: (payload: Transforms, blockId: BlockId, mergeWith?: Transforms) => Transforms
153
+ ): Promise<CoordinatorBatch<Transforms, PendResult>[]> {
154
+ const blockCoordinators = await Promise.all(
155
+ blockIds.map(async bid => ({
156
+ blockId: bid,
157
+ coordinator: await this.keyNetwork.findCoordinator(await blockIdToBytes(bid), { excludedPeers: [] })
158
+ }))
159
+ );
160
+
161
+ const byCoordinator = new Map<string, BlockId[]>();
162
+ for (const { blockId, coordinator } of blockCoordinators) {
163
+ const key = coordinator.toString();
164
+ const blocks = byCoordinator.get(key) ?? [];
165
+ blocks.push(blockId);
166
+ byCoordinator.set(key, blocks);
167
+ }
168
+
169
+ const batches: CoordinatorBatch<Transforms, PendResult>[] = [];
170
+ for (const [coordinatorStr, consolidatedBlocks] of byCoordinator) {
171
+ const coordinator = blockCoordinators.find(bc => bc.coordinator.toString() === coordinatorStr)!.coordinator;
172
+
173
+ let batchTransforms: Transforms = { inserts: {}, updates: {}, deletes: [] };
174
+ for (const bid of consolidatedBlocks) {
175
+ const blockTransforms = transformForBlock(transforms, bid, batchTransforms);
176
+ batchTransforms = blockTransforms;
177
+ }
178
+
179
+ batches.push({
180
+ peerId: coordinator,
181
+ payload: batchTransforms,
182
+ blockId: consolidatedBlocks[0]!,
183
+ coordinatingBlockIds: consolidatedBlocks,
184
+ excludedPeers: []
185
+ } as any);
186
+ }
187
+
188
+ return batches;
189
+ }
190
+
191
+ async pend(blockAction: PendRequest): Promise<PendResult> {
192
+ const transformForBlock = (payload: Transforms, blockId: BlockId, mergeWithPayload: Transforms | undefined): Transforms => {
193
+ const filteredTransform = transformForBlockId(payload, blockId);
194
+ return mergeWithPayload
195
+ ? concatTransform(mergeWithPayload, blockId, filteredTransform)
196
+ : transformsFromTransform(filteredTransform, blockId);
197
+ };
198
+ const blockIds = blockIdsForTransforms(blockAction.transforms);
199
+ const batches = await this.consolidateCoordinators(blockIds, blockAction.transforms, transformForBlock);
200
+ const expiration = Date.now() + this.timeoutMs;
201
+
202
+ let error: Error | undefined;
203
+ try {
204
+ // Process all batches, noting all outstanding peers
205
+ await processBatches(
206
+ batches,
207
+ (batch) => this.getRepo(batch.peerId).pend(
208
+ { ...blockAction, transforms: batch.payload },
209
+ {
210
+ expiration,
211
+ coordinatingBlockIds: (batch as any).coordinatingBlockIds
212
+ } as any
213
+ ),
214
+ batch => blockIdsForTransforms(batch.payload),
215
+ transformForBlock,
216
+ expiration,
217
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
218
+ );
219
+ // Cache resolved coordinators for follow-up commit to hit the same peers
220
+ try {
221
+ const pn: any = this.keyNetwork as any;
222
+ if (typeof pn?.recordCoordinator === 'function') {
223
+ for (const b of Array.from(allBatches(batches))) {
224
+ pn.recordCoordinator(await blockIdToBytes(b.blockId), b.peerId);
225
+ }
226
+ }
227
+ } catch (e) { console.warn('Failed to record coordinator hint', e); }
228
+ } catch (e) {
229
+ error = e as Error;
230
+ }
231
+
232
+ if (!everyBatch(batches, b => b.request?.isResponse as boolean && b.request!.response!.success)) {
233
+ const details = this.formatBatchStatuses(batches,
234
+ b => (b.request?.isResponse as boolean && (b.request as any).response?.success) ?? false,
235
+ b => {
236
+ const status = b.request == null ? 'no-response' : (b.request.isResponse ? 'non-success' : 'in-flight')
237
+ return `${b.peerId.toString()}[block:${b.blockId}](${status})`
238
+ });
239
+ const aggregate = new Error(`Some peers did not complete: ${details}${error ? `; root: ${error.message}` : ''}`);
240
+ const prior = error;
241
+ (aggregate as any).cause = prior;
242
+ (aggregate as AggregateError).errors = prior ? [prior] : [];
243
+ error = aggregate;
244
+ }
245
+
246
+ if (error) { // If any failures, cancel all pending actions as background microtask
247
+ Promise.resolve().then(() => this.cancelBatch(batches, { blockIds, actionId: blockAction.actionId }));
248
+ const stale = Array.from(allBatches(batches, b => b.request?.isResponse as boolean && !b.request!.response!.success));
249
+ if (stale.length > 0) { // Any active stale failures should preempt reporting connection or other potential transient errors (we have information)
250
+ return {
251
+ success: false,
252
+ missing: distinctBlockActionTransforms(stale.flatMap(b => (b.request!.response! as StaleFailure).missing).filter((x): x is ActionTransforms => x !== undefined)),
253
+ };
254
+ }
255
+ throw error; // No stale failures, report the original error
256
+ }
257
+
258
+ // Collect replies back into result structure
259
+ const completed = Array.from(allBatches(batches, b => b.request?.isResponse as boolean && b.request!.response!.success));
260
+ return {
261
+ success: true,
262
+ pending: completed.flatMap(b => (b.request!.response! as PendSuccess).pending),
263
+ blockIds: blockIdsForTransforms(blockAction.transforms)
264
+ };
265
+ }
266
+
267
+ async cancel(actionRef: ActionBlocks): Promise<void> {
268
+ const batches = await this.batchesForPayload<BlockId[], void>(
269
+ actionRef.blockIds,
270
+ actionRef.blockIds,
271
+ mergeBlocks,
272
+ []
273
+ );
274
+ const expiration = Date.now() + this.abortOrCancelTimeoutMs;
275
+ await processBatches(
276
+ batches,
277
+ (batch) => this.getRepo(batch.peerId).cancel({ actionId: actionRef.actionId, blockIds: batch.payload }, { expiration }),
278
+ batch => batch.payload,
279
+ mergeBlocks,
280
+ expiration,
281
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
282
+ );
283
+ }
284
+
285
+ async queryClusterNominees(blockId: BlockId): Promise<ClusterNomineesResult> {
286
+ const blockIdBytes = await blockIdToBytes(blockId);
287
+ const clusterPeers = await this.keyNetwork.findCluster(blockIdBytes);
288
+ const nominees = Object.keys(clusterPeers).map(idStr => peerIdFromString(idStr));
289
+ return { nominees };
290
+ }
291
+
292
+ async commit(request: CommitRequest): Promise<CommitResult> {
293
+ const allBlockIds = [...new Set([...request.blockIds, request.tailId])];
294
+
295
+ // Commit the header block if provided and not already in blockIds
296
+ if (request.headerId && !request.blockIds.includes(request.headerId)) {
297
+ const headerResult = await this.commitBlock(request.headerId, allBlockIds, request.actionId, request.rev);
298
+ if (!headerResult.success) {
299
+ return headerResult;
300
+ }
301
+ }
302
+
303
+ // Commit the tail block
304
+ const tailResult = await this.commitBlock(request.tailId, allBlockIds, request.actionId, request.rev);
305
+ if (!tailResult.success) {
306
+ return tailResult;
307
+ }
308
+
309
+ // Commit all remaining block ids (excluding tail and header if it was already handled)
310
+ const remainingBlocks = request.blockIds.filter(bid =>
311
+ bid !== request.tailId &&
312
+ !(request.headerId && bid === request.headerId && !request.blockIds.includes(request.headerId))
313
+ );
314
+ if (remainingBlocks.length > 0) {
315
+ const { batches, error } = await this.commitBlocks({ blockIds: remainingBlocks, actionId: request.actionId, rev: request.rev });
316
+ if (error) {
317
+ // Non-tail block commit failures should not fail the overall action once the tail has committed.
318
+ // Proceed and rely on reconciliation paths (e.g. reads with context) to finalize state on lagging peers.
319
+ try { console.warn('[NetworkTransactor] non-tail commit had errors; proceeding after tail commit:', error.message); } catch { /* ignore */ }
320
+ }
321
+ }
322
+
323
+ return { success: true };
324
+ }
325
+
326
+ private async commitBlock(blockId: BlockId, blockIds: BlockId[], actionId: ActionId, rev: number): Promise<CommitResult> {
327
+ const { batches: tailBatches, error: tailError } = await this.commitBlocks({ blockIds: [blockId], actionId, rev });
328
+ if (tailError) {
329
+ // Cancel all pending actions as background microtask
330
+ Promise.resolve().then(() => this.cancel({ blockIds, actionId }));
331
+ // Collect and return any active stale failures
332
+ const stale = Array.from(allBatches(tailBatches, b => b.request?.isResponse as boolean && !b.request!.response!.success));
333
+ if (stale.length > 0) {
334
+ return { missing: distinctBlockActionTransforms(stale.flatMap(b => (b.request!.response! as StaleFailure).missing!)), success: false as const };
335
+ }
336
+ throw tailError;
337
+ }
338
+ return { success: true };
339
+ }
340
+
341
+ /** Attempts to commit a set of blocks, and handles failures and errors */
342
+ private async commitBlocks({ blockIds, actionId, rev }: RepoCommitRequest) {
343
+ const expiration = Date.now() + this.timeoutMs;
344
+ const batches = await this.batchesForPayload<BlockId[], CommitResult>(blockIds, blockIds, mergeBlocks, []);
345
+ let error: Error | undefined;
346
+ try {
347
+ await processBatches(
348
+ batches,
349
+ (batch) => this.getRepo(batch.peerId).commit({ actionId, blockIds: batch.payload, rev }, { expiration }),
350
+ batch => batch.payload,
351
+ mergeBlocks,
352
+ expiration,
353
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
354
+ );
355
+ } catch (e) {
356
+ error = e as Error;
357
+ }
358
+
359
+ if (!everyBatch(batches, b => b.request?.isResponse as boolean && b.request!.response!.success)) {
360
+ const details = this.formatBatchStatuses(batches,
361
+ b => (b.request?.isResponse as boolean && (b.request as any).response?.success) ?? false,
362
+ b => {
363
+ const status = b.request == null ? 'no-response' : (b.request.isResponse ? 'non-success' : 'in-flight')
364
+ const resp: any = (b.request as any)?.response;
365
+ const extra = resp && resp.success === false ? (Array.isArray(resp.missing) ? ` missing=${resp.missing.length}` : ' success=false') : '';
366
+ return `${b.peerId.toString()}[blocks:${b.payload instanceof Array ? (b.payload as any[]).length : 1}](${status})${extra ? ' ' + extra : ''}`
367
+ });
368
+ const aggregate = new Error(`Some peers did not complete: ${details}${error ? `; root: ${error.message}` : ''}`);
369
+ (aggregate as any).cause = error;
370
+ error = aggregate;
371
+ }
372
+ return { batches, error };
373
+ };
374
+
375
+ /** Creates batches for a given payload, grouped by the coordinating peer for each block id */
376
+ private async batchesForPayload<TPayload, TResponse>(
377
+ blockIds: BlockId[],
378
+ payload: TPayload,
379
+ getBlockPayload: (payload: TPayload, blockId: BlockId, mergeWithPayload: TPayload | undefined) => TPayload,
380
+ excludedPeers: PeerId[]
381
+ ): Promise<CoordinatorBatch<TPayload, TResponse>[]> {
382
+ return createBatchesForPayload<TPayload, TResponse>(
383
+ blockIds,
384
+ payload,
385
+ getBlockPayload,
386
+ excludedPeers,
387
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
388
+ );
389
+ }
390
+
391
+ /** Cancels a pending transaction by canceling all blocks associated with the transaction, including failed peers */
392
+ private async cancelBatch<TPayload, TResponse>(
393
+ batches: CoordinatorBatch<TPayload, TResponse>[],
394
+ actionRef: ActionBlocks,
395
+ ) {
396
+ const expiration = Date.now() + this.abortOrCancelTimeoutMs;
397
+ const operationBatches = makeBatchesByPeer(
398
+ Array.from(allBatches(batches)).map(b => [b.blockId, b.peerId] as const),
399
+ actionRef.blockIds,
400
+ mergeBlocks,
401
+ []
402
+ );
403
+ await processBatches(
404
+ operationBatches,
405
+ (batch) => this.getRepo(batch.peerId).cancel({ actionId: actionRef.actionId, blockIds: batch.payload }, { expiration }),
406
+ batch => batch.payload,
407
+ mergeBlocks,
408
+ expiration,
409
+ async (blockId, options) => this.keyNetwork.findCoordinator(await blockIdToBytes(blockId), options)
410
+ );
411
+ }
412
+
413
+ private formatBatchStatuses<TPayload, TResponse>(
414
+ batches: CoordinatorBatch<TPayload, TResponse>[],
415
+ isSuccess: (b: CoordinatorBatch<TPayload, TResponse>) => boolean,
416
+ formatter: (b: CoordinatorBatch<TPayload, TResponse>) => string
417
+ ): string {
418
+ const incompletes = Array.from(incompleteBatches(batches))
419
+ let details = incompletes.map(formatter).join(', ')
420
+ if (details.length === 0) {
421
+ details = Array.from(allBatches(batches)).map(formatter).join(', ')
422
+ }
423
+ return details
424
+ }
425
+ }
426
+
427
+
428
+ /**
429
+ * Returns the block actions grouped by action id and concatenated transforms
430
+ */
431
+ export function distinctBlockActionTransforms(blockActions: ActionTransforms[]): ActionTransforms[] {
432
+ const grouped = groupBy(blockActions, ({ actionId }) => actionId);
433
+ return Object.entries(grouped).map(([actionId, actions]) =>
434
+ ({ actionId, transforms: concatTransforms(...actions.map(t => t.transforms)) } as ActionTransforms));
435
+ }
@@ -0,0 +1,65 @@
1
+ import { randomBytes } from '@libp2p/crypto'
2
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
3
+ import type { IBlock, BlockId, BlockHeader, ITransactor, ActionId, StaleFailure, ActionContext, BlockType, BlockSource, Transforms } from "../index.js";
4
+
5
+ export class TransactorSource<TBlock extends IBlock> implements BlockSource<TBlock> {
6
+ constructor(
7
+ private readonly collectionId: BlockId,
8
+ private readonly transactor: ITransactor,
9
+ public actionContext: ActionContext | undefined,
10
+ ) { }
11
+
12
+ createBlockHeader(type: BlockType, newId?: BlockId): BlockHeader {
13
+ return {
14
+ type,
15
+ id: newId ?? this.generateId(),
16
+ collectionId: this.collectionId,
17
+ };
18
+ }
19
+
20
+ generateId(): BlockId {
21
+ // 256-bits to fully utilize DHT address space
22
+ return uint8ArrayToString(randomBytes(32), 'base64url')
23
+ }
24
+
25
+ async tryGet(id: BlockId): Promise<TBlock | undefined> {
26
+ const result = await this.transactor.get({ blockIds: [id], context: this.actionContext });
27
+ if (result) {
28
+ const { block, state } = result[id]!;
29
+ // TODO: if the state reports that there is a pending action, record this so that we are sure to update before syncing
30
+ //state.pendings
31
+ return block as TBlock;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Attempts to apply the given transforms in a transactional manner.
37
+ * @param transform - The transforms to apply.
38
+ * @param actionId - The action id.
39
+ * @param rev - The revision number.
40
+ * @param headerId - The Id of the collection's header block. If specified, this block's transform is performed first,
41
+ * in the event that there is a race to create the collection itself, or in the event that the tail block is full and
42
+ * is transitioning to a new block. Ignored if the given headerId is not present in the transforms.
43
+ * @param tailId - The Id of the collection's log tail block. If specified, this block's transform is performed next
44
+ * (prior to the rest of the block operations), to resolve the "winner" of a race to commit to the collection.
45
+ * @returns A promise that resolves to undefined if the action is successful, or a StaleFailure if the action is stale.
46
+ */
47
+ async transact(transform: Transforms, actionId: ActionId, rev: number, headerId: BlockId, tailId: BlockId): Promise<undefined | StaleFailure> {
48
+ const pendResult = await this.transactor.pend({ transforms: transform, actionId, rev, policy: 'r' });
49
+ if (!pendResult.success) {
50
+ return pendResult;
51
+ }
52
+ const isNew = Object.hasOwn(transform.inserts, headerId);
53
+ const commitResult = await this.transactor.commit({
54
+ headerId: isNew ? headerId : undefined,
55
+ tailId,
56
+ blockIds: pendResult.blockIds,
57
+ actionId,
58
+ rev
59
+ });
60
+ if (!commitResult.success) {
61
+ return commitResult;
62
+ }
63
+ }
64
+ }
65
+
@@ -0,0 +1,44 @@
1
+ import type { GetBlockResults, ActionBlocks, BlockActionStatus, PendResult, CommitResult, PendRequest, CommitRequest, BlockGets, BlockId } from "../index.js";
2
+ import type { PeerId } from "@libp2p/interface";
3
+
4
+ export type ClusterNomineesResult = {
5
+ /** Peer IDs of the cluster members who can participate in consensus */
6
+ nominees: PeerId[];
7
+ };
8
+
9
+ export type ITransactor = {
10
+ /** Get blocks by their IDs and versions or a specific action
11
+ - Does not update the version of the block, but the action is available for explicit reading, and for committing
12
+ - If the action targets the correct version, the call succeeds, unless failIfPending and there are any pending actions - the caller may choose to wait for pending actions to clear rather than risk racing with them
13
+ - If the action targets an older version, the call fails, and the caller must resync using the missing actions
14
+ */
15
+ get(blockGets: BlockGets): Promise<GetBlockResults>;
16
+
17
+ /** Get statuses of block actions */
18
+ getStatus(actionRefs: ActionBlocks[]): Promise<BlockActionStatus[]>;
19
+
20
+ /** Post an action for a set of blocks
21
+ - Does not update the version of the block, but the action is available for explicit reading, and for committing
22
+ - If the action targets the correct version, the call succeeds, unless pending = 'fail' and there are any pending actions - the caller may choose to wait for pending actions to clear rather than risk racing with them
23
+ - If the action targets an older version, the call fails, and the caller must resync using the missing actions
24
+ */
25
+ pend(blockAction: PendRequest): Promise<PendResult>;
26
+
27
+ /** Cancel a pending action
28
+ - If the given action ID is pending, it is canceled
29
+ */
30
+ cancel(actionRef: ActionBlocks): Promise<void>;
31
+
32
+ /** Commit a pending action
33
+ - If the action references the current version, the pending action is committed
34
+ - If the returned fails, the transforms necessary to update all overlapping blocks are returned
35
+ - If the action mentions other collections, those are assumed conditions - returned conditions only list inherited conditions
36
+ */
37
+ commit(request: CommitRequest): Promise<CommitResult>;
38
+
39
+ /** Query cluster nominees for a critical block (used in GATHER phase for multi-collection transactions)
40
+ - Returns the peer IDs of cluster members who can participate in consensus for the given block
41
+ - Used to build the supercluster for multi-collection transaction consensus
42
+ */
43
+ queryClusterNominees?(blockId: BlockId): Promise<ClusterNomineesResult>;
44
+ }
@@ -0,0 +1,16 @@
1
+ import { Tracker } from "./tracker.js";
2
+ import type { IBlock, BlockStore } from "../index.js";
3
+ import { applyTransformToStore } from "./helpers.js";
4
+
5
+ export class Atomic<TBlock extends IBlock> extends Tracker<TBlock> {
6
+ constructor(public readonly store: BlockStore<TBlock>) {
7
+ super(store);
8
+ }
9
+
10
+ commit() {
11
+ const transform = this.reset();
12
+ applyTransformToStore(transform, this.store);
13
+ }
14
+
15
+ // rollback = reset
16
+ }
@@ -0,0 +1,57 @@
1
+ import type { IBlock, BlockHeader, BlockId, BlockSource, BlockType, Transforms } from "../index.js";
2
+ import { applyOperation } from "../index.js";
3
+
4
+ export class CacheSource<T extends IBlock> implements BlockSource<T> {
5
+ protected cache = new Map<BlockId, T>();
6
+
7
+ constructor(
8
+ protected readonly source: BlockSource<T>
9
+ ) { }
10
+
11
+ async tryGet(id: BlockId): Promise<T | undefined> {
12
+ let block = this.cache.get(id);
13
+ if (!block) {
14
+ block = await this.source.tryGet(id);
15
+ if (block) {
16
+ this.cache.set(id, block);
17
+ }
18
+ }
19
+ return structuredClone(block);
20
+ }
21
+
22
+ generateId(): BlockId {
23
+ return this.source.generateId();
24
+ }
25
+
26
+ createBlockHeader(type: BlockType, newId?: BlockId): BlockHeader {
27
+ return this.source.createBlockHeader(type, newId);
28
+ }
29
+
30
+ clear(blockIds: BlockId[] | undefined = undefined) {
31
+ if (blockIds) {
32
+ for (const id of blockIds) {
33
+ this.cache.delete(id);
34
+ }
35
+ } else {
36
+ this.cache.clear();
37
+ }
38
+ }
39
+
40
+ /** Mutates the cache without affecting the source */
41
+ transformCache(transform: Transforms) {
42
+ for (const blockId of transform.deletes) {
43
+ this.cache.delete(blockId);
44
+ }
45
+ for (const [, block] of Object.entries(transform.inserts)) {
46
+ this.cache.set(block.header.id, structuredClone(block) as T);
47
+ }
48
+ for (const [blockId, operations] of Object.entries(transform.updates)) {
49
+ for (const op of operations) {
50
+ const block = this.cache.get(blockId);
51
+ if (block) {
52
+ applyOperation(block, op);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }