@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,117 @@
1
+ import type { BlockId, BlockOperation, BlockOperations, BlockStore, IBlock, Transform, Transforms } from "../index.js";
2
+
3
+ /** Mutates the given block with a copy of the given operation */
4
+ export function applyOperation(block: IBlock, [entity, index, deleteCount, inserted]: BlockOperation) {
5
+ if (Array.isArray(inserted)) {
6
+ (block as unknown as any)[entity].splice(index, deleteCount, ...structuredClone(inserted));
7
+ } else {
8
+ (block as unknown as any)[entity] = structuredClone(inserted);
9
+ }
10
+ }
11
+
12
+ /** Mutates the given block with the given set of operations */
13
+ export function applyOperations(block: IBlock, operations: BlockOperations) {
14
+ for (const op of operations) {
15
+ applyOperation(block, op);
16
+ }
17
+ }
18
+
19
+ /** Returns a copy of the block with the given operation applied */
20
+ export function withOperation(block: IBlock, [entity, index, deleteCount, inserted]: BlockOperation) {
21
+ if (Array.isArray(inserted)) {
22
+ const source = (block as any)[entity];
23
+ return { ...block, [entity]: [...source.slice(0, index), ...structuredClone(inserted), ...source.slice(index + deleteCount)] };
24
+ } else {
25
+ return { ...block, [entity]: structuredClone(inserted) };
26
+ }
27
+ }
28
+
29
+ /** The set of distinct block ids affected by the transform */
30
+ export function blockIdsForTransforms(transforms: Transforms | undefined) {
31
+ if (!transforms) return [];
32
+ const insertIds = Object.keys(transforms.inserts ?? {});
33
+ const updateIds = Object.keys(transforms.updates ?? {});
34
+ const deleteIds = transforms.deletes ?? [];
35
+ return [...new Set([...insertIds, ...updateIds, ...deleteIds])];
36
+ }
37
+
38
+ /** Returns an empty transform */
39
+ export function emptyTransforms(): Transforms {
40
+ return { inserts: {}, updates: {}, deletes: [] };
41
+ }
42
+
43
+ export function copyTransforms(transform: Transforms): Transforms {
44
+ return { inserts: { ...transform.inserts }, updates: { ...transform.updates }, deletes: transform.deletes };
45
+ }
46
+
47
+ export function mergeTransforms(a: Transforms, b: Transforms): Transforms {
48
+ return {
49
+ inserts: { ...a.inserts, ...b.inserts },
50
+ updates: { ...a.updates, ...b.updates },
51
+ deletes: [...a.deletes, ...b.deletes]
52
+ };
53
+ }
54
+
55
+ export function isTransformsEmpty(transform: Transforms): boolean {
56
+ return Object.keys(transform.inserts).length === 0
57
+ && Object.keys(transform.updates).length === 0
58
+ && transform.deletes.length === 0;
59
+ }
60
+
61
+ export function concatTransforms(...transforms: Transforms[]): Transforms {
62
+ return transforms.reduce((acc, m) => mergeTransforms(acc, m), emptyTransforms());
63
+ }
64
+
65
+
66
+ export function transformForBlockId(transform: Transforms, blockId: BlockId): Transform {
67
+ return {
68
+ ...(blockId in transform.inserts ? { insert: transform.inserts[blockId] } : {}),
69
+ ...(blockId in transform.updates ? { updates: transform.updates[blockId] } : {}),
70
+ ...(blockId in transform.deletes ? { delete: true } : {})
71
+ };
72
+ }
73
+
74
+ export function transformsFromTransform(transform: Transform, blockId: BlockId): Transforms {
75
+ return {
76
+ inserts: transform.insert ? { [blockId]: transform.insert } : {},
77
+ updates: transform.updates ? { [blockId]: transform.updates } : {},
78
+ deletes: transform.delete ? [blockId] : []
79
+ };
80
+ }
81
+
82
+ export function applyTransformToStore<T extends IBlock>(transform: Transforms, store: BlockStore<T>) {
83
+ for (const blockId of transform.deletes) {
84
+ store.delete(blockId);
85
+ }
86
+ for (const [, block] of Object.entries(transform.inserts)) {
87
+ store.insert(block as T);
88
+ }
89
+ for (const [blockId, operations] of Object.entries(transform.updates)) {
90
+ for (const op of operations) {
91
+ store.update(blockId, op);
92
+ }
93
+ }
94
+ }
95
+
96
+ /** Applies a transform to the given block */
97
+ export function applyTransform(block: IBlock | undefined, transform: Transform): IBlock | undefined {
98
+ if (transform.insert) {
99
+ block = transform.insert;
100
+ }
101
+ if (block && transform.updates) {
102
+ applyOperations(block, transform.updates);
103
+ }
104
+ if (transform.delete) {
105
+ return undefined;
106
+ }
107
+ return block;
108
+ }
109
+
110
+ /** Concatenates a transform to the given transforms */
111
+ export function concatTransform(transforms: Transforms, blockId: BlockId, transform: Transform): Transforms {
112
+ return {
113
+ inserts: { ...transforms.inserts, ...(transform.insert ? { [blockId]: transform.insert } : {}) },
114
+ updates: { ...transforms.updates, ...(transform.updates ? { [blockId]: transform.updates } : {}) },
115
+ deletes: [...transforms.deletes, ...(transform.delete ? [blockId] : [])]
116
+ };
117
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./atomic.js";
2
+ export * from "./cache-source.js";
3
+ export * from "./helpers.js";
4
+ export * from "./struct.js";
5
+ export * from "./tracker.js";
@@ -0,0 +1,22 @@
1
+ import type { IBlock, BlockId, BlockOperations } from "../blocks/structs.js";
2
+
3
+ // TODO: make each of these optional (assumes empty)
4
+
5
+ /** A transform is a set of block mutations to be performed.
6
+ * If a block is present in more than one field, they are applied in order of: insert, update, delete. */
7
+ export type Transforms = {
8
+ /** Inserted blocks by BlockId */
9
+ inserts: Record<BlockId, IBlock>;
10
+ /** Block update operations by BlockId */
11
+ updates: Record<BlockId, BlockOperations>;
12
+ /** Set of deleted BlockIds */
13
+ deletes: BlockId[];
14
+ };
15
+
16
+ /** A transform is a block-level mutation.
17
+ * If more than one field is set, they are applied in order of: insert, update, delete. */
18
+ export type Transform = {
19
+ insert?: IBlock;
20
+ updates?: BlockOperations;
21
+ delete?: boolean;
22
+ };
@@ -0,0 +1,70 @@
1
+ import type { IBlock, BlockId, BlockStore as IBlockStore, BlockHeader, BlockOperation, BlockType, BlockSource as IBlockSource } from "../index.js";
2
+ import { applyOperation, emptyTransforms, blockIdsForTransforms, ensured } from "../index.js";
3
+
4
+ /** A block store that collects transformations, without applying them to the underlying source.
5
+ * Transformations are also applied to the retrieved blocks, making it seem like the source has been modified.
6
+ */
7
+ export class Tracker<T extends IBlock> implements IBlockStore<T> {
8
+ constructor(
9
+ private readonly source: IBlockSource<T>,
10
+ /** The collected set of transformations to be applied. Treat as immutable */
11
+ public transforms = emptyTransforms(),
12
+ ) { }
13
+
14
+ async tryGet(id: BlockId): Promise<T | undefined> {
15
+ const block = await this.source.tryGet(id);
16
+ if (block) {
17
+ const ops = this.transforms.updates[id] ?? [];
18
+ ops.forEach(op => applyOperation(block!, op));
19
+ if (this.transforms.deletes?.includes(id)) {
20
+ return undefined;
21
+ }
22
+ } else if (Object.hasOwn(this.transforms.inserts, id)) {
23
+ return structuredClone(this.transforms.inserts[id]) as T;
24
+ }
25
+
26
+ return block;
27
+ }
28
+
29
+ generateId(): BlockId {
30
+ return this.source.generateId();
31
+ }
32
+
33
+ createBlockHeader(type: BlockType, newId?: BlockId): BlockHeader {
34
+ return this.source.createBlockHeader(type, newId);
35
+ }
36
+
37
+ insert(block: T) {
38
+ this.transforms.inserts[block.header.id] = structuredClone(block);
39
+ this.transforms.deletes.splice(this.transforms.deletes.indexOf(block.header.id), 1);
40
+ }
41
+
42
+ update(blockId: BlockId, op: BlockOperation) {
43
+ const inserted = this.transforms.inserts[blockId];
44
+ if (inserted) {
45
+ applyOperation(inserted, op);
46
+ } else {
47
+ ensured(this.transforms.updates, blockId, () => []).push(structuredClone(op));
48
+ }
49
+ }
50
+
51
+ delete(blockId: BlockId) {
52
+ delete this.transforms.inserts[blockId];
53
+ delete this.transforms.updates[blockId];
54
+ this.transforms.deletes.push(blockId);
55
+ }
56
+
57
+ reset(newTransform = emptyTransforms()) {
58
+ const oldTransform = this.transforms;
59
+ this.transforms = newTransform;
60
+ return oldTransform;
61
+ }
62
+
63
+ transformedBlockIds(): BlockId[] {
64
+ return Array.from(new Set(blockIdsForTransforms(this.transforms)));
65
+ }
66
+
67
+ conflicts(blockIds: Set<BlockId>) {
68
+ return this.transformedBlockIds().filter(id => blockIds.has(id));
69
+ }
70
+ }
@@ -0,0 +1,62 @@
1
+ type AsyncMethodKeys<T> = {
2
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
3
+ }[keyof T];
4
+
5
+ type Actor<T> = {
6
+ [K in AsyncMethodKeys<T>]:
7
+ T[K] extends (...args: infer A) => infer R
8
+ ? (...args: A) => Promise<Awaited<R>>
9
+ : never;
10
+ } & {
11
+ // pass through property reads
12
+ [K in Exclude<keyof T, AsyncMethodKeys<T>>]: T[K];
13
+ };
14
+
15
+ // makes an actor-like proxy around any object
16
+ export function createActor<T extends object>(target: T): Actor<T> {
17
+ // a queue of calls waiting to run
18
+ const callQueue: Array<{
19
+ method: keyof T;
20
+ args: unknown[];
21
+ resolve: (val: unknown) => void;
22
+ reject: (err: unknown) => void;
23
+ }> = [];
24
+
25
+ let running = false;
26
+
27
+ async function runQueue() {
28
+ if (running) return;
29
+ running = true;
30
+
31
+ while (callQueue.length > 0) {
32
+ const { method, args, resolve, reject } = callQueue.shift()!;
33
+
34
+ try {
35
+ // call method on the original target
36
+ const result = (target[method] as any)(...args);
37
+ // if it’s a promise, await it; otherwise just pass it back
38
+ const awaited = result instanceof Promise ? await result : result;
39
+ resolve(awaited);
40
+ } catch (err) {
41
+ reject(err);
42
+ }
43
+ }
44
+
45
+ running = false;
46
+ }
47
+
48
+ return new Proxy(target, {
49
+ get(_obj, prop, _receiver) {
50
+ const value = (target as any)[prop];
51
+ // if it’s not a function, pass it through directly (optional)
52
+ if (typeof value !== 'function') return value;
53
+
54
+ // otherwise return a function that queues the call
55
+ return (...args: unknown[]) =>
56
+ new Promise((resolve, reject) => {
57
+ callQueue.push({ method: prop as keyof T, args, resolve, reject });
58
+ runQueue();
59
+ });
60
+ },
61
+ }) as Actor<T>;
62
+ }
@@ -0,0 +1,174 @@
1
+ import { type PeerId } from "@libp2p/interface";
2
+ import type { BlockId } from "../index.js";
3
+ import { Pending } from "./pending.js";
4
+
5
+ /**
6
+ * Represents a batch of operations for a specific block coordinated by a peer
7
+ */
8
+ export type CoordinatorBatch<TPayload, TResponse> = {
9
+ peerId: PeerId;
10
+ blockId: BlockId;
11
+ payload: TPayload;
12
+ request?: Pending<TResponse>;
13
+ /** Whether this batch has been subsumed by other successful batches */
14
+ subsumedBy?: CoordinatorBatch<TPayload, TResponse>[];
15
+ /** Peers that have already been tried (and failed) */
16
+ excludedPeers?: PeerId[];
17
+ }
18
+
19
+ /**
20
+ * Creates batches for a given payload, grouped by the coordinating peer for each block id
21
+ */
22
+ export function makeBatchesByPeer<TPayload, TResponse>(
23
+ blockPeers: (readonly [BlockId, PeerId])[],
24
+ payload: TPayload,
25
+ getBlockPayload: (payload: TPayload, blockId: BlockId, mergeWithPayload: TPayload | undefined) => TPayload,
26
+ excludedPeers?: PeerId[]
27
+ ): CoordinatorBatch<TPayload, TResponse>[] {
28
+ const groups = blockPeers.reduce((acc, [blockId, peerId]) => {
29
+ const peerId_str = peerId.toString();
30
+ const coordinator = acc.get(peerId_str) ?? { peerId, blockId, excludedPeers } as Partial<CoordinatorBatch<TPayload, TResponse>>;
31
+ acc.set(peerId_str, { ...coordinator, payload: getBlockPayload(payload, blockId, coordinator.payload) } as CoordinatorBatch<TPayload, TResponse>);
32
+ return acc;
33
+ }, new Map<string, CoordinatorBatch<TPayload, TResponse>>());
34
+ return Array.from(groups.values());
35
+ }
36
+
37
+ /**
38
+ * Iterates over all batches that have not completed, whether subsumed or not
39
+ */
40
+ export function* incompleteBatches<TPayload, TResponse>(batches: CoordinatorBatch<TPayload, TResponse>[]): IterableIterator<CoordinatorBatch<TPayload, TResponse>> {
41
+ const stack: CoordinatorBatch<TPayload, TResponse>[] = [...batches];
42
+ while (stack.length > 0) {
43
+ const batch = stack.pop()!;
44
+ if (!batch.request || !batch.request.isResponse) {
45
+ yield batch;
46
+ }
47
+ if (batch.subsumedBy && batch.subsumedBy.length) {
48
+ stack.push(...batch.subsumedBy);
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Checks if all completed batches (ignoring failures) satisfy a predicate
55
+ */
56
+ export function everyBatch<TPayload, TResponse>(batches: CoordinatorBatch<TPayload, TResponse>[], predicate: (batch: CoordinatorBatch<TPayload, TResponse>) => boolean): boolean {
57
+ // For each root batch require that SOME node in its retry tree satisfies the predicate.
58
+ // Use iterative DFS to avoid recursion depth and minimize allocations.
59
+ for (const root of batches) {
60
+ let found = false;
61
+ const stack: CoordinatorBatch<TPayload, TResponse>[] = [root];
62
+ while (stack.length > 0) {
63
+ const node = stack.pop()!;
64
+ if (predicate(node)) { found = true; break; }
65
+ if (node.subsumedBy && node.subsumedBy.length) {
66
+ for (let i = 0; i < node.subsumedBy.length; i++) stack.push(node.subsumedBy[i]!);
67
+ }
68
+ }
69
+ if (!found) return false;
70
+ }
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Iterates over all batches that satisfy an optional predicate, whether subsumed or not
76
+ */
77
+ export function* allBatches<TPayload, TResponse>(batches: CoordinatorBatch<TPayload, TResponse>[], predicate?: (batch: CoordinatorBatch<TPayload, TResponse>) => boolean): IterableIterator<CoordinatorBatch<TPayload, TResponse>> {
78
+ const stack: CoordinatorBatch<TPayload, TResponse>[] = [...batches];
79
+ while (stack.length > 0) {
80
+ const batch = stack.pop()!;
81
+ if (!predicate || predicate(batch)) {
82
+ yield batch;
83
+ }
84
+ if (batch.subsumedBy && batch.subsumedBy.length) {
85
+ stack.push(...batch.subsumedBy);
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Returns a new blockId list payload with the given block id appended
92
+ */
93
+ export function mergeBlocks(payload: BlockId[], blockId: BlockId, mergeWithPayload: BlockId[] | undefined): BlockId[] {
94
+ return [...(mergeWithPayload ?? []), blockId];
95
+ }
96
+
97
+ /**
98
+ * Processes a set of batches, retrying any failures until success or expiration
99
+ * @param batches - The batches to process - each represents a group of blocks centered on a coordinating peer
100
+ * @param process - The function to call for a given batch
101
+ * @param getBlockIds - The function to call to get the block ids for a given batch
102
+ * @param getBlockPayload - The function to call to get the payload given a parent payload and block id, and optionally merge with an existing payload
103
+ * @param expiration - The expiration time for the operation
104
+ * @param findCoordinator - The function to call to find a coordinator for a block id
105
+ */
106
+ export async function processBatches<TPayload, TResponse>(
107
+ batches: CoordinatorBatch<TPayload, TResponse>[],
108
+ process: (batch: CoordinatorBatch<TPayload, TResponse>) => Promise<TResponse>,
109
+ getBlockIds: (batch: CoordinatorBatch<TPayload, TResponse>) => BlockId[],
110
+ getBlockPayload: (payload: TPayload, blockId: BlockId, mergeWithPayload: TPayload | undefined) => TPayload,
111
+ expiration: number,
112
+ findCoordinator: (blockId: BlockId, options: { excludedPeers: PeerId[] }) => Promise<PeerId>
113
+ ): Promise<void> {
114
+ // Root-map ensures retries are recorded on the original batch to avoid deep trees
115
+ const rootOf = new WeakMap<CoordinatorBatch<TPayload, TResponse>, CoordinatorBatch<TPayload, TResponse>>();
116
+ for (const b of batches) rootOf.set(b, b);
117
+
118
+ // Process a set of batches concurrently and enqueue retries flatly onto the root's subsumedBy list
119
+ const processSet = async (set: CoordinatorBatch<TPayload, TResponse>[]) => {
120
+ await Promise.all(set.map(async (batch) => {
121
+ batch.request = new Pending(process(batch)
122
+ .catch(async e => {
123
+ if (expiration > Date.now()) {
124
+ const excludedPeers = [batch.peerId, ...(batch.excludedPeers ?? [])];
125
+ const retries = await createBatchesForPayload<TPayload, TResponse>(
126
+ getBlockIds(batch),
127
+ batch.payload,
128
+ getBlockPayload,
129
+ excludedPeers,
130
+ findCoordinator
131
+ );
132
+ if (retries.length > 0 && expiration > Date.now()) {
133
+ const root = rootOf.get(batch) ?? batch;
134
+ root.subsumedBy = [...(root.subsumedBy ?? []), ...retries];
135
+ for (const r of retries) rootOf.set(r, root);
136
+ // Process retries, but ensure further failures also attach to the same root
137
+ await processSet(retries);
138
+ }
139
+ }
140
+ throw e;
141
+ }));
142
+ }));
143
+
144
+ // Wait for all in this set to settle
145
+ await Promise.all(set.map(b => b.request?.result().catch(() => { /* ignore */ })));
146
+ };
147
+
148
+ await processSet(batches);
149
+ }
150
+
151
+ /**
152
+ * Creates batches for a given payload, grouped by the coordinating peer for each block id
153
+ * This is a placeholder function that will be implemented by the caller
154
+ */
155
+ export async function createBatchesForPayload<TPayload, TResponse>(
156
+ blockIds: BlockId[],
157
+ payload: TPayload,
158
+ getBlockPayload: (payload: TPayload, blockId: BlockId, mergeWithPayload: TPayload | undefined) => TPayload,
159
+ excludedPeers: PeerId[],
160
+ findCoordinator: (blockId: BlockId, options: { excludedPeers: PeerId[] }) => Promise<PeerId>
161
+ ): Promise<CoordinatorBatch<TPayload, TResponse>[]> {
162
+ // Group by block id
163
+ const distinctBlockIds = new Set(blockIds);
164
+
165
+ // Find coordinator for each key
166
+ const blockIdPeerId = await Promise.all(
167
+ Array.from(distinctBlockIds).map(async (bid) =>
168
+ [bid, await findCoordinator(bid, { excludedPeers })] as const
169
+ )
170
+ );
171
+
172
+ // Group blocks around their coordinating peers
173
+ return makeBatchesByPeer<TPayload, TResponse>(blockIdPeerId, payload, getBlockPayload, excludedPeers);
174
+ }
@@ -0,0 +1,8 @@
1
+ import { sha256 } from 'multiformats/hashes/sha2'
2
+ import type { BlockId } from '../index.js'
3
+
4
+ export async function blockIdToBytes(blockId: BlockId): Promise<Uint8Array> {
5
+ const input = new TextEncoder().encode(blockId)
6
+ const mh = await sha256.digest(input)
7
+ return mh.digest
8
+ }
@@ -0,0 +1,32 @@
1
+ // Retrieves a value from a record, generating an entry if none exists
2
+ export function ensured<K extends string | number | symbol, V>(
3
+ map: Record<K, V>,
4
+ key: K,
5
+ makeNew: () => Exclude<V, undefined>,
6
+ existing?: (existing: Exclude<V, undefined>) => void
7
+ ): Exclude<V, undefined> {
8
+ let v = map[key];
9
+ if (typeof v === 'undefined') {
10
+ v = makeNew();
11
+ map[key] = v;
12
+ } else if (existing) {
13
+ existing(v as Exclude<V, undefined>);
14
+ }
15
+ return v as Exclude<V, undefined>;
16
+ }
17
+
18
+ export function ensuredMap<K extends string | number | symbol, V>(
19
+ map: Map<K, V>,
20
+ key: K,
21
+ makeNew: () => Exclude<V, undefined>,
22
+ existing?: (existing: Exclude<V, undefined>) => void
23
+ ): Exclude<V, undefined> {
24
+ let v = map.get(key);
25
+ if (typeof v === 'undefined') {
26
+ v = makeNew();
27
+ map.set(key, v);
28
+ } else if (existing) {
29
+ existing(v as Exclude<V, undefined>);
30
+ }
31
+ return v as Exclude<V, undefined>;
32
+ }
@@ -0,0 +1,18 @@
1
+ // TODO: replace the usage of this with Object.groupBy in newer ES once more pervasive
2
+
3
+ /**
4
+ * Groups an array of items by a key selector function.
5
+ * @param array - The array of items to group.
6
+ * @param keySelector - The function that selects the key for each item.
7
+ * @returns An object where each key is a unique value from the key selector function, and each value is an array of items that have that key.
8
+ */
9
+ export function groupBy<T, K extends string | number | symbol>(
10
+ array: T[],
11
+ keySelector: (item: T) => K
12
+ ): Record<K, T[]> {
13
+ return array.reduce((acc, item) => {
14
+ const key = keySelector(item);
15
+ (acc[key] ??= []).push(item);
16
+ return acc;
17
+ }, {} as Record<K, T[]>);
18
+ }
@@ -0,0 +1,5 @@
1
+ /** True if the given object has no keys. This should not be used for classes or objects with proto fields. */
2
+ export function isRecordEmpty<T>(record: Record<string, T>): boolean {
3
+ for (const key in record) return false;
4
+ return true;
5
+ }
@@ -0,0 +1,42 @@
1
+ /** Lightweight implementation of a mutex lock queue. */
2
+ export class Latches {
3
+ // Stores the promise representing the completion of the last queued operation for a key.
4
+ private static lockQueues = new Map<string, Promise<void>>();
5
+
6
+ /**
7
+ * Acquires a lock for the given key. Waits if another operation holds the lock.
8
+ * Returns a release function that must be called to release the lock.
9
+ * WARNING: The key scope is global to the entire process, so follow the convention of using `ClassName.methodName:${id}` to avoid conflicts.
10
+ */
11
+ static async acquire(key: string): Promise<() => void> {
12
+ // Get the promise the current operation needs to wait for (if any)
13
+ const currentTail = this.lockQueues.get(key) ?? Promise.resolve();
14
+
15
+ let resolveNewTail: () => void;
16
+ // Create the promise that the *next* operation will wait for
17
+ const newTail = new Promise<void>(resolve => {
18
+ resolveNewTail = resolve;
19
+ });
20
+
21
+ // Immediately set the new promise as the tail for this key
22
+ this.lockQueues.set(key, newTail);
23
+
24
+ // Wait for the previous operation (if any) to complete
25
+ await currentTail;
26
+
27
+ // Lock acquired. Return the function to release *this* lock.
28
+ const release = () => {
29
+ // Signal that this operation is complete, allowing the next awaiter (if any)
30
+ resolveNewTail();
31
+
32
+ // Optimization: If this promise is still the current tail in the map,
33
+ // it means no other operation queued up behind this one while it was running.
34
+ // We can safely remove the entry from the map to prevent unbounded growth.
35
+ if (this.lockQueues.get(key) === newTail) {
36
+ this.lockQueues.delete(key);
37
+ }
38
+ };
39
+
40
+ return release;
41
+ }
42
+ }
@@ -0,0 +1,7 @@
1
+ /* eslint-disable no-redeclare, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any */
2
+ export function nameof<TObject>(obj: TObject, key: keyof TObject): string;
3
+ export function nameof<TObject>(key: keyof TObject): string;
4
+ export function nameof(key1: any, key2?: any): any {
5
+ return key2 ?? key1;
6
+ }
7
+ /* eslint-enable */
@@ -0,0 +1,41 @@
1
+ export class Pending<T> {
2
+ response?: T;
3
+ error?: unknown;
4
+ t1 = Date.now();
5
+ duration?: number;
6
+
7
+ get isResponse(): boolean {
8
+ return this.response !== undefined;
9
+ }
10
+
11
+ get isError(): boolean {
12
+ return this.error !== undefined;
13
+ }
14
+
15
+ get isComplete(): boolean {
16
+ return this.isResponse || this.isError;
17
+ }
18
+
19
+ async result(): Promise<T> {
20
+ if (this.isResponse) {
21
+ return this.response!;
22
+ }
23
+ if (this.isError) {
24
+ throw this.error!;
25
+ }
26
+ return await this.promise;
27
+ }
28
+
29
+ constructor(
30
+ public promise: Promise<T>
31
+ ) {
32
+ promise.then(response => {
33
+ this.duration = Date.now() - this.t1;
34
+ this.response = response;
35
+ return response;
36
+ }, error => {
37
+ this.duration = Date.now() - this.t1;
38
+ this.error = error;
39
+ });
40
+ }
41
+ }