@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,804 @@
1
+ import { Path, PathBranch, type ITreeTrunk, type KeyRange, type getTrunkFunc } from "./index.js";
2
+ import type { BlockId, BlockStore } from "../blocks/index.js";
3
+ import { apply, get } from "../blocks/index.js";
4
+ import { TreeLeafBlockType, TreeBranchBlockType, entries$, nodes$, partitions$ } from "./nodes.js";
5
+ import type { BranchNode, ITreeNode, LeafNode } from "./nodes.js";
6
+ import type { TreeBlock } from "./tree-block.js";
7
+
8
+ export const NodeCapacity = 64;
9
+
10
+ /**
11
+ * Represents a lightweight B+(ish)Tree (data at leaves, but no linked list of leaves).
12
+ * Allows for efficient storage and retrieval of data in a sorted manner.
13
+ * @template TEntry The type of entries stored in the B-tree.
14
+ * @template TKey The type of keys used for indexing the entries. This might be an element of TEntry, or TEntry itself.
15
+ */
16
+ export class BTree<TKey, TEntry> {
17
+ protected _version = 0; // only for path invalidation
18
+
19
+ /**
20
+ * @param [compare=(a: TKey, b: TKey) => a < b ? -1 : a > b ? 1 : 0] a comparison function for keys. The default uses < and > operators.
21
+ * @param [keyFromEntry=(entry: TEntry) => entry as unknown as TKey] a function to extract the key from an entry. The default assumes the key is the entry itself.
22
+ */
23
+ constructor(
24
+ public readonly store: BlockStore<ITreeNode>,
25
+ public readonly trunk: ITreeTrunk,
26
+ public readonly keyFromEntry = (entry: TEntry) => entry as unknown as TKey,
27
+ public readonly compare = (a: TKey, b: TKey) => a < b ? -1 : a > b ? 1 : 0 as number,
28
+ ) {
29
+ }
30
+
31
+ static createRoot(
32
+ store: BlockStore<ITreeNode>
33
+ ) {
34
+ return newLeafNode(store, []);
35
+ }
36
+
37
+ static create<TKey, TEntry>(
38
+ store: BlockStore<ITreeNode | TreeBlock>,
39
+ createTrunk: getTrunkFunc,
40
+ keyFromEntry = (entry: TEntry) => entry as unknown as TKey,
41
+ compare = (a: TKey, b: TKey) => a < b ? -1 : a > b ? 1 : 0,
42
+ newId?: BlockId,
43
+ ) {
44
+ const root = BTree.createRoot(store as BlockStore<TreeBlock>);
45
+ store.insert(root);
46
+ const trunk = createTrunk(store as BlockStore<TreeBlock>, root.header.id, newId);
47
+ return new BTree(store, trunk, keyFromEntry, compare);
48
+ }
49
+
50
+ /** @returns a path to the first entry (on = false if no entries) */
51
+ async first(): Promise<Path<TKey, TEntry>> {
52
+ return await this.getFirst(await this.trunk.get());
53
+ }
54
+
55
+ /** @returns a path to the last entry (on = false if no entries) */
56
+ async last(): Promise<Path<TKey, TEntry>> {
57
+ return await this.getLast(await this.trunk.get());
58
+ }
59
+
60
+ /** Attempts to find the given key
61
+ * @returns Path to the key or the "crack" before it. If `on` is true on the resulting path, the key was found.
62
+ * If `on` is false, next() and prior() can attempt to move to the nearest match. */
63
+ async find(key: TKey): Promise<Path<TKey, TEntry>> {
64
+ return await this.getPath(await this.trunk.get(), key);
65
+ }
66
+
67
+ /** Retrieves the entry for the given key.
68
+ * Use find instead for a path to the key, the nearest match, or as a basis for navigation.
69
+ * @returns the entry for the given key if found; undefined otherwise. */
70
+ async get(key: TKey): Promise<TEntry | undefined> {
71
+ return this.at(await this.find(key));
72
+ }
73
+
74
+ /** @returns the entry for the given path if on an entry; undefined otherwise. */
75
+ at(path: Path<TKey, TEntry>): TEntry | undefined {
76
+ this.validatePath(path);
77
+ return path.on ? this.getEntry(path) : undefined;
78
+ }
79
+
80
+ /** Iterates based on the given range
81
+ * WARNING: mutation during iteration will result in an exception
82
+ */
83
+ async *range(range: KeyRange<TKey>): AsyncIterableIterator<Path<TKey, TEntry>> {
84
+ const startPath = range.first
85
+ ? await this.findFirst(range)
86
+ : (range.isAscending ? await this.first() : await this.last());
87
+ const endPath = range.last
88
+ ? await this.findLast(range)
89
+ : (range.isAscending ? await this.last() : await this.first());
90
+ // If the tree is empty or endPath is not on an entry, return early
91
+ if (!endPath.on) {
92
+ return;
93
+ }
94
+ const endKey = this.keyFromPath(endPath);
95
+ const iterable = range.isAscending
96
+ ? this.internalAscending(startPath)
97
+ : this.internalDescending(startPath);
98
+ const iter = iterable[Symbol.asyncIterator]();
99
+ const ascendingFactor = range.isAscending ? 1 : -1;
100
+ for await (let path of iter) {
101
+ if (!path.on || this.compare(
102
+ this.keyFromPath(path),
103
+ endKey
104
+ ) * ascendingFactor > 0) {
105
+ break;
106
+ }
107
+ yield path;
108
+ }
109
+ }
110
+
111
+ /** @returns true if the given path remains valid; false if the tree has been mutated, invalidating the path. */
112
+ isValid(path: Path<TKey, TEntry>) {
113
+ return path.version === this._version;
114
+ }
115
+
116
+ /**
117
+ * Adds a value to the tree. Be sure to check the result, as the tree does not allow duplicate keys.
118
+ * Added entries are frozen to ensure immutability
119
+ * @returns path to the new (on = true) or conflicting (on = false) row. */
120
+ async insert(entry: TEntry): Promise<Path<TKey, TEntry>> {
121
+ Object.freeze(entry); // Ensure immutability
122
+ const path = await this.internalInsert(entry);
123
+ if (path.on) {
124
+ path.version = ++this._version;
125
+ }
126
+ return path;
127
+ }
128
+
129
+ /** Updates the entry at the given path to the given value. Deletes and inserts if the key changes.
130
+ * @returns path to resulting entry and whether it was an update (as opposed to an insert).
131
+ * * on = true if update/insert succeeded.
132
+ * * wasUpdate = true if updated; false if inserted.
133
+ * * Returned path is on entry
134
+ * * on = false if update/insert failed.
135
+ * * wasUpdate = true, given path is not on an entry
136
+ * * else newEntry's new key already present; returned path is "near" existing entry */
137
+ async updateAt(path: Path<TKey, TEntry>, newEntry: TEntry): Promise<[path: Path<TKey, TEntry>, wasUpdate: boolean]> {
138
+ this.validatePath(path);
139
+ if (path.on) {
140
+ Object.freeze(newEntry);
141
+ }
142
+ const result = await this.internalUpdate(path, newEntry);
143
+ if (result[0].on) {
144
+ result[0].version = ++this._version;
145
+ }
146
+ return result;
147
+ }
148
+
149
+ /** Inserts the entry if it doesn't exist, or updates it if it does.
150
+ * The entry is frozen to ensure immutability.
151
+ * @returns path to the new entry. on = true if existing; on = false if new. */
152
+ async upsert(entry: TEntry): Promise<Path<TKey, TEntry>> {
153
+ const path = await this.find(this.keyFromEntry(entry));
154
+ Object.freeze(entry);
155
+ if (path.on) {
156
+ this.updateEntry(path, entry);
157
+ } else {
158
+ await this.internalInsertAt(path, entry);
159
+ }
160
+ path.version = ++this._version;
161
+ return path;
162
+ }
163
+
164
+ /** Inserts or updates depending on the existence of the given key, using callbacks to generate the new value.
165
+ * @param newEntry the new entry to insert if the key doesn't exist.
166
+ * @param getUpdated a callback to generate an updated entry if the key does exist. WARNING: mutation in this callback will cause merge to error.
167
+ * @returns path to new entry and whether an update or insert attempted.
168
+ * If getUpdated callback returns a row that is already present, the resulting path will not be on. */
169
+ async merge(newEntry: TEntry, getUpdated: (existing: TEntry) => TEntry): Promise<[path: Path<TKey, TEntry>, wasUpdate: boolean]> {
170
+ const newKey = await this.keyFromEntry(newEntry);
171
+ const path = await this.find(newKey);
172
+ if (path.on) {
173
+ const result = await this.updateAt(path, getUpdated(this.getEntry(path))); // Don't use internalUpdate - need to freeze and check for mutation
174
+ if (result[0].on) {
175
+ result[0].version = ++this._version;
176
+ }
177
+ return result;
178
+ } else {
179
+ await this.internalInsertAt(path, Object.freeze(newEntry));
180
+ path.on = true;
181
+ path.version = ++this._version;
182
+ return [path, false];
183
+ }
184
+ }
185
+
186
+ /** Deletes the entry at the given path.
187
+ * The on property of the path will be cleared.
188
+ * @returns true if the delete succeeded (the key was found); false otherwise.
189
+ */
190
+ async deleteAt(path: Path<TKey, TEntry>): Promise<boolean> {
191
+ this.validatePath(path);
192
+ const result = await this.internalDelete(path);
193
+ if (result) {
194
+ ++this._version;
195
+ }
196
+ return result;
197
+ }
198
+
199
+ async drop() { // Node: only when a root treeBlock
200
+ const root = await this.trunk.get();
201
+ for await (const id of this.nodeIds(root)) {
202
+ this.store.delete(id);
203
+ }
204
+ }
205
+
206
+ /** Iterates forward starting from the path location (inclusive) to the end.
207
+ * WARNING: mutation during iteration will result in an exception.
208
+ */
209
+ ascending(path: Path<TKey, TEntry>): AsyncIterableIterator<Path<TKey, TEntry>> {
210
+ this.validatePath(path);
211
+ return this.internalAscending(path.clone());
212
+ }
213
+
214
+ /** Iterates backward starting from the path location (inclusive) to the end.
215
+ * WARNING: mutation during iteration will result in an exception
216
+ */
217
+ descending(path: Path<TKey, TEntry>): AsyncIterableIterator<Path<TKey, TEntry>> {
218
+ this.validatePath(path);
219
+ return this.internalDescending(path.clone());
220
+ }
221
+
222
+ /** Computed (not stored) count. Computes the sum using leaf-node lengths. O(n/af) where af is average fill.
223
+ * @param from if provided, the count will start from the given path (inclusive). If ascending is false,
224
+ * the count will start from the end of the tree. Ascending is true by default.
225
+ */
226
+ async getCount(from?: { path: Path<TKey, TEntry>, ascending?: boolean }): Promise<number> {
227
+ let result = 0;
228
+ const path = from ? from.path.clone() : await this.first();
229
+ if (from?.ascending ?? true) {
230
+ while (path.on) {
231
+ result += path.leafNode.entries.length - path.leafIndex;
232
+ path.leafIndex = path.leafNode.entries.length - 1;
233
+ await this.internalNext(path);
234
+ }
235
+ } else {
236
+ while (path.on) {
237
+ result += path.leafIndex + 1;
238
+ path.leafIndex = 0;
239
+ await this.internalPrior(path);
240
+ }
241
+ }
242
+ return result;
243
+ }
244
+
245
+ /** @returns a path one step forward. on will be true if the path hasn't hit the end. */
246
+ async next(path: Path<TKey, TEntry>): Promise<Path<TKey, TEntry>> {
247
+ const newPath = path.clone();
248
+ await this.moveNext(newPath);
249
+ return newPath;
250
+ }
251
+
252
+ /** Attempts to advance the given path one step forward. (mutates the path) */
253
+ async moveNext(path: Path<TKey, TEntry>) {
254
+ this.validatePath(path);
255
+ await this.internalNext(path);
256
+ }
257
+
258
+ /** @returns a path one step backward. on will be true if the path hasn't hit the end. */
259
+ async prior(path: Path<TKey, TEntry>): Promise<Path<TKey, TEntry>> {
260
+ const newPath = path.clone();
261
+ this.movePrior(newPath);
262
+ return newPath;
263
+ }
264
+
265
+ /** Attempts to advance the given path one step backwards. (mutates the path) */
266
+ async movePrior(path: Path<TKey, TEntry>) {
267
+ this.validatePath(path);
268
+ await this.internalPrior(path);
269
+ }
270
+
271
+ /** @remarks Assumes the path is "on" */
272
+ protected keyFromPath(path: Path<TKey, TEntry>): TKey {
273
+ return this.keyFromEntry(path.leafNode.entries[path.leafIndex]!);
274
+ }
275
+
276
+ private async *internalAscending(path: Path<TKey, TEntry>): AsyncIterableIterator<Path<TKey, TEntry>> {
277
+ this.validatePath(path);
278
+ while (path.on) {
279
+ yield path;
280
+ await this.moveNext(path); // Not internal - re-check after yield
281
+ }
282
+ }
283
+
284
+ private async *internalDescending(path: Path<TKey, TEntry>): AsyncIterableIterator<Path<TKey, TEntry>> {
285
+ this.validatePath(path);
286
+ while (path.on) {
287
+ yield path;
288
+ await this.movePrior(path); // Not internal - re-check after yield
289
+ }
290
+ }
291
+
292
+ private async findFirst(range: KeyRange<TKey>) { // Assumes range.first is defined
293
+ const startPath = await this.find(range.first!.key)
294
+ if (!startPath.on || (range.first && !range.first.inclusive)) {
295
+ if (range.isAscending) {
296
+ await this.internalNext(startPath);
297
+ } else {
298
+ await this.internalPrior(startPath);
299
+ }
300
+ }
301
+ return startPath;
302
+ }
303
+
304
+ private async findLast(range: KeyRange<TKey>) { // Assumes range.last is defined
305
+ const endPath = await this.find(range.last!.key)
306
+ if (!endPath.on || (range.last && !range.last.inclusive)) {
307
+ if (range.isAscending) {
308
+ await this.internalPrior(endPath);
309
+ } else {
310
+ await this.internalNext(endPath);
311
+ }
312
+ }
313
+ return endPath;
314
+ }
315
+
316
+ protected async getPath(node: ITreeNode, key: TKey): Promise<Path<TKey, TEntry>> {
317
+ if (node.header.type === TreeLeafBlockType) {
318
+ const leaf = node as LeafNode<TEntry>;
319
+ const [on, index] = this.indexOfEntry(leaf.entries, key);
320
+ return new Path<TKey, TEntry>([], leaf, index, on, this._version);
321
+ } else {
322
+ const branch = node as BranchNode<TKey>;
323
+ const index = this.indexOfKey(branch.partitions, key);
324
+ const path = await this.getPath(await get(this.store, branch.nodes[index]!), key);
325
+ path.branches.unshift(new PathBranch(branch, index));
326
+ return path;
327
+ }
328
+ }
329
+
330
+ private indexOfEntry(entries: TEntry[], key: TKey): [on: boolean, index: number] {
331
+ let lo = 0;
332
+ let hi = entries.length - 1;
333
+ let split = 0;
334
+ let result = -1;
335
+
336
+ while (lo <= hi) {
337
+ split = (lo + hi) >>> 1;
338
+ result = this.compare(key, this.keyFromEntry(entries[split]!));
339
+
340
+ if (result === 0)
341
+ return [true, split];
342
+ else if (result < 0)
343
+ hi = split - 1;
344
+ else
345
+ lo = split + 1;
346
+ }
347
+
348
+ return [false, lo];
349
+ }
350
+
351
+ protected indexOfKey(keys: TKey[], key: TKey): number {
352
+ let lo = 0;
353
+ let hi = keys.length - 1;
354
+ let split = 0;
355
+ let result = -1;
356
+
357
+ while (lo <= hi) {
358
+ split = (lo + hi) >>> 1;
359
+ result = this.compare(key, keys[split]!);
360
+
361
+ if (result === 0)
362
+ return split + 1; // +1 because taking right partition
363
+ else if (result < 0)
364
+ hi = split - 1;
365
+ else
366
+ lo = split + 1;
367
+ }
368
+
369
+ return lo;
370
+ }
371
+
372
+ private async internalNext(path: Path<TKey, TEntry>) {
373
+ if (!path.on) { // Attempt to move off of crack
374
+ path.on = path.branches.every(branch => branch.index >= 0 && branch.index < branch.node.nodes.length)
375
+ && path.leafIndex >= 0 && path.leafIndex < path.leafNode.entries.length;
376
+ if (path.on) {
377
+ return;
378
+ }
379
+ } else if (path.leafIndex >= path.leafNode.entries.length - 1) {
380
+ let popCount = 0;
381
+ let found = false;
382
+ const last = path.branches.length - 1;
383
+ while (popCount <= last && !found) {
384
+ const branch = path.branches[last - popCount]!;
385
+ if (branch.index === branch.node.partitions.length) // last node in branch
386
+ ++popCount;
387
+ else
388
+ found = true;
389
+ }
390
+
391
+ if (!found) {
392
+ path.leafIndex = path.leafNode.entries.length; // after last row = end crack
393
+ path.on = false;
394
+ } else {
395
+ path.branches.splice(-popCount, popCount);
396
+ const branch = path.branches.at(-1)!;
397
+ ++branch.index;
398
+ this.moveToFirst(await get(this.store, branch.node.nodes[branch.index]!), path);
399
+ }
400
+ }
401
+ else {
402
+ ++path.leafIndex;
403
+ path.on = true;
404
+ }
405
+ }
406
+
407
+ private async internalPrior(path: Path<TKey, TEntry>) {
408
+ this.validatePath(path);
409
+ if (path.leafIndex <= 0) {
410
+ let popCount = 0;
411
+ let opening = false;
412
+ const last = path.branches.length - 1;
413
+ while (popCount <= last && !opening) {
414
+ const branch = path.branches[last - popCount]!;
415
+ if (branch.index === 0) // first node in branch
416
+ ++popCount;
417
+ else
418
+ opening = true;
419
+ }
420
+
421
+ if (!opening) {
422
+ path.leafIndex = 0;
423
+ path.on = false;
424
+ } else {
425
+ path.branches.splice(-popCount, popCount);
426
+ const branch = path.branches.at(-1)!;
427
+ --branch.index;
428
+ await this.moveToLast(await get(this.store, branch.node.nodes[branch.index]!), path);
429
+ }
430
+ }
431
+ else {
432
+ --path.leafIndex;
433
+ path.on = true;
434
+ }
435
+ }
436
+
437
+ private async internalUpdate(path: Path<TKey, TEntry>, newEntry: TEntry): Promise<[path: Path<TKey, TEntry>, wasUpdate: boolean]> {
438
+ if (path.on) {
439
+ const oldKey = this.keyFromPath(path);
440
+ const newKey = this.keyFromEntry(newEntry);
441
+ if (this.compare(oldKey, newKey) !== 0) { // if key changed, delete and re-insert
442
+ let newPath = await this.internalInsert(newEntry)
443
+ if (newPath.on) { // insert succeeded
444
+ this.internalDelete(await this.find(oldKey)); // Re-find - the prior insert invalidated the path
445
+ newPath = await this.find(newKey); // Re-find- delete invalidated path
446
+ }
447
+ return [newPath, false];
448
+ } else {
449
+ this.updateEntry(path, newEntry);
450
+ }
451
+ }
452
+ return [path, true];
453
+ }
454
+
455
+ protected async internalDelete(path: Path<TKey, TEntry>): Promise<boolean> {
456
+ if (path.on) {
457
+ apply(this.store, path.leafNode, [entries$, path.leafIndex, 1, []]);
458
+ if (path.branches.length > 0) { // Only worry about underflows, balancing, etc. if not root
459
+ if (path.leafIndex === 0) { // If we deleted index 0, update branches with new key
460
+ const pathBranch = path.branches.at(-1)!;
461
+ this.updatePartition(pathBranch.index, path, path.branches.length - 1,
462
+ this.keyFromPath(path));
463
+ }
464
+ const newRoot = await this.rebalanceLeaf(path, path.branches.length);
465
+ if (newRoot) {
466
+ await this.trunk.set(newRoot);
467
+ }
468
+ }
469
+ path.on = false;
470
+ return true;
471
+ } else {
472
+ return false;
473
+ }
474
+ }
475
+
476
+ private async internalInsert(entry: TEntry): Promise<Path<TKey, TEntry>> {
477
+ const path = await this.find(this.keyFromEntry(entry));
478
+ if (path.on) {
479
+ path.on = false;
480
+ return path;
481
+ }
482
+ await this.internalInsertAt(path, entry);
483
+ path.on = true;
484
+ return path;
485
+ }
486
+
487
+ private async internalInsertAt(path: Path<TKey, TEntry>, entry: TEntry) {
488
+ let split = this.leafInsert(path, entry);
489
+ let branchIndex = path.branches.length - 1;
490
+ while (split && branchIndex >= 0) {
491
+ split = await this.branchInsert(path, branchIndex, split);
492
+ --branchIndex;
493
+ }
494
+ if (split) {
495
+ const newBranch = newBranchNode(this.store, [split.key], [await this.trunk.getId(), split.right.header.id]);
496
+ await this.store.insert(newBranch);
497
+ await this.trunk.set(newBranch);
498
+ path.branches.unshift(new PathBranch(newBranch, split.indexDelta));
499
+ }
500
+ }
501
+
502
+ /** Starting from the given node, recursively working down to the leaf, build onto the path based on the beginning-most entry. */
503
+ private async moveToFirst(node: ITreeNode, path: Path<TKey, TEntry>) {
504
+ if (node.header.type === TreeLeafBlockType) {
505
+ const leaf = node as LeafNode<TEntry>;
506
+ path.leafNode = leaf;
507
+ path.leafIndex = 0;
508
+ path.on = leaf.entries.length > 0;
509
+ } else {
510
+ path.branches.push(new PathBranch(node as BranchNode<TKey>, 0));
511
+ await this.moveToFirst(await get(this.store, (node as BranchNode<TKey>).nodes[0]!), path);
512
+ }
513
+ }
514
+
515
+ /** Starting from the given node, recursively working down to the leaf, build onto the path based on the end-most entry. */
516
+ private async moveToLast(node: ITreeNode, path: Path<TKey, TEntry>) {
517
+ if (node.header.type === TreeLeafBlockType) {
518
+ const leaf = node as LeafNode<TEntry>;
519
+ const count = leaf.entries.length;
520
+ path.leafNode = leaf;
521
+ path.on = count > 0;
522
+ path.leafIndex = count > 0 ? count - 1 : 0;
523
+ } else {
524
+ const branch = node as BranchNode<TKey>;
525
+ const pathBranch = new PathBranch(branch, branch.partitions.length);
526
+ path.branches.push(pathBranch);
527
+ await this.moveToLast(await get(this.store, branch.nodes[pathBranch.index]!), path);
528
+ }
529
+ }
530
+
531
+ /** Construct a path based on the first-most edge of the given. */
532
+ private async getFirst(node: ITreeNode): Promise<Path<TKey, TEntry>> {
533
+ if (node.header.type === TreeLeafBlockType) {
534
+ const leaf = node as LeafNode<TEntry>;
535
+ return new Path<TKey, TEntry>([], leaf, 0, leaf.entries.length > 0, this._version)
536
+ } else {
537
+ const branch = node as BranchNode<TKey>;
538
+ const path = await this.getFirst(await get(this.store, branch.nodes[0]!));
539
+ path.branches.unshift(new PathBranch(branch, 0));
540
+ return path;
541
+ }
542
+ }
543
+
544
+ /** Construct a path based on the last-most edge of the given node */
545
+ private async getLast(node: ITreeNode): Promise<Path<TKey, TEntry>> {
546
+ if (node.header.type === TreeLeafBlockType) {
547
+ const leaf = node as LeafNode<TEntry>;
548
+ const count = leaf.entries.length;
549
+ return new Path<TKey, TEntry>([], leaf, count > 0 ? count - 1 : 0, count > 0, this._version);
550
+ } else {
551
+ const branch = node as BranchNode<TKey>;
552
+ const index = branch.nodes.length - 1;
553
+ const path = await this.getLast(await get(this.store, branch.nodes[index]!));
554
+ path.branches.unshift(new PathBranch(branch, index));
555
+ return path;
556
+ }
557
+ }
558
+
559
+ private leafInsert(path: Path<TKey, TEntry>, entry: TEntry): Split<TKey> | undefined {
560
+ const { leafNode: leaf, leafIndex: index } = path;
561
+ if (leaf.entries.length < NodeCapacity) { // No split needed
562
+ apply(this.store, leaf, [entries$, index, 0, [entry]]);
563
+ return undefined;
564
+ }
565
+ // Full. Split needed
566
+
567
+ const midIndex = (leaf.entries.length + 1) >>> 1;
568
+ const newEntries = leaf.entries.slice(midIndex);
569
+
570
+ // New node
571
+ if (index >= midIndex) { // Put the new entry directly rather than log an insert
572
+ newEntries.splice(index - midIndex, 0, entry);
573
+ }
574
+ const newLeaf = newLeafNode(this.store, newEntries);
575
+ this.store.insert(newLeaf);
576
+
577
+ // Delete entries from old node
578
+ apply(this.store, leaf, [entries$, midIndex, leaf.entries.length - midIndex, []]);
579
+
580
+ if (index < midIndex) { // Insert new entry into old node
581
+ apply(this.store, leaf, [entries$, index, 0, [entry]]);
582
+ } else {
583
+ path.leafNode = newLeaf;
584
+ path.leafIndex -= midIndex;
585
+ }
586
+
587
+ return new Split<TKey>(this.keyFromEntry(newEntries[0]!), newLeaf, index < midIndex ? 0 : 1);
588
+ }
589
+
590
+ private async branchInsert(path: Path<TKey, TEntry>, branchIndex: number, split: Split<TKey>): Promise<Split<TKey> | undefined> {
591
+ const pathBranch = path.branches[branchIndex]!;
592
+ const { index: splitIndex, node } = pathBranch;
593
+ pathBranch.index += split.indexDelta;
594
+ if (node.nodes.length < NodeCapacity) { // no split needed
595
+ apply(this.store, node, [partitions$, splitIndex, 0, [split.key]]);
596
+ apply(this.store, node, [nodes$, splitIndex + 1, 0, [split.right.header.id]]);
597
+ return undefined;
598
+ }
599
+ // Full. Split needed
600
+
601
+ const midIndex = (node.nodes.length + 1) >>> 1;
602
+ const newPartitions = node.partitions.slice(midIndex);
603
+ const newNodes = node.nodes.slice(midIndex);
604
+ const delta = pathBranch.index < midIndex ? 0 : 1;
605
+
606
+ // New node
607
+ if (delta) { // If split is on new, add it before the split to avoid logging an insert
608
+ pathBranch.index -= midIndex;
609
+ newPartitions.splice(pathBranch.index, 0, split.key);
610
+ newNodes.splice(pathBranch.index + 1, 0, split.right.header.id);
611
+ }
612
+ const newBranch = newBranchNode(this.store, newPartitions, newNodes);
613
+
614
+ // Delete partitions and nodes
615
+ const newPartition = node.partitions[midIndex - 1]!;
616
+ apply(this.store, node, [partitions$, midIndex - 1, newPartitions.length + 1, []]);
617
+ apply(this.store, node, [nodes$, midIndex, newNodes.length, []]);
618
+
619
+ if (pathBranch.index < midIndex) { // Insert into old node
620
+ apply(this.store, node, [partitions$, splitIndex, 0, [split.key]]);
621
+ apply(this.store, node, [nodes$, splitIndex + 1, 0, [split.right.header.id]]);
622
+ }
623
+
624
+ return new Split<TKey>(newPartition, newBranch, delta);
625
+ }
626
+
627
+ protected async rebalanceLeaf(path: Path<TKey, TEntry>, depth: number): Promise<ITreeNode | undefined> {
628
+ if (depth === 0 || path.leafNode.entries.length >= (NodeCapacity >>> 1)) {
629
+ return undefined;
630
+ }
631
+
632
+ const leaf = path.leafNode;
633
+ const parent = path.branches.at(depth - 1)!;
634
+ const pIndex = parent.index;
635
+ const pNode = parent.node;
636
+
637
+ const rightSibId = pNode.nodes[pIndex + 1]!;
638
+ const rightSib = rightSibId ? (await get(this.store, rightSibId)) as LeafNode<TEntry> : undefined;
639
+ if (rightSib && rightSib.entries.length > (NodeCapacity >>> 1)) { // Attempt to borrow from right sibling
640
+ const entry = rightSib.entries[0]!;
641
+ apply(this.store, rightSib, [entries$, 0, 1, []]);
642
+ apply(this.store, leaf, [entries$, leaf.entries.length, 0, [entry]]);
643
+ this.updatePartition(pIndex + 1, path, depth - 1, this.keyFromEntry(entry));
644
+ return undefined;
645
+ }
646
+
647
+ const leftSibId = pNode.nodes[pIndex - 1]!;
648
+ const leftSib = leftSibId ? (await get(this.store, leftSibId)) as LeafNode<TEntry> : undefined;
649
+ if (leftSib && leftSib.entries.length > (NodeCapacity >>> 1)) { // Attempt to borrow from left sibling
650
+ const entry = leftSib.entries[leftSib.entries.length - 1]!;
651
+ apply(this.store, leftSib, [entries$, leftSib.entries.length - 1, 1, []]);
652
+ apply(this.store, leaf, [entries$, 0, 0, [entry]]);
653
+ this.updatePartition(pIndex, path, depth - 1, this.keyFromEntry(entry));
654
+ path.leafIndex += 1;
655
+ return undefined;
656
+ }
657
+
658
+ if (rightSib && rightSib.entries.length + leaf.entries.length <= NodeCapacity) { // Attempt to merge right sibling into leaf (right sib deleted)
659
+ apply(this.store, leaf, [entries$, leaf.entries.length, 0, rightSib.entries]);
660
+ this.deletePartition(pNode, pIndex);
661
+ if (pIndex === 0) { // 0th node of parent, update parent key
662
+ this.updatePartition(pIndex, path, depth - 1, this.keyFromEntry(leaf.entries[0]!));
663
+ }
664
+ this.store.delete(rightSib.header.id);
665
+ return await this.rebalanceBranch(path, depth - 1);
666
+ }
667
+
668
+ if (leftSib && leftSib.entries.length + leaf.entries.length <= NodeCapacity) { // Attempt to merge into left sibling (leaf deleted)
669
+ path.leafNode = leftSib;
670
+ path.leafIndex += leftSib.entries.length;
671
+ apply(this.store, leftSib, [entries$, leftSib.entries.length, 0, leaf.entries]);
672
+ this.deletePartition(pNode, pIndex - 1);
673
+ this.store.delete(leaf.header.id);
674
+ return await this.rebalanceBranch(path, depth - 1);
675
+ }
676
+ }
677
+
678
+ protected async rebalanceBranch(path: Path<TKey, TEntry>, depth: number): Promise<ITreeNode | undefined> {
679
+ const pathBranch = path.branches[depth]!;
680
+ const branch = pathBranch.node;
681
+ if (depth === 0 && branch.partitions.length === 0) { // last node... collapse child into root
682
+ return path.branches[depth + 1]?.node ?? path.leafNode;
683
+ }
684
+
685
+ if (depth === 0 || (branch.nodes.length >= NodeCapacity << 1)) {
686
+ return undefined;
687
+ }
688
+
689
+ const parent = path.branches.at(depth - 1)!;
690
+ const pIndex = parent.index;
691
+ const pNode = parent.node;
692
+
693
+ const rightSibId = pNode.nodes[pIndex + 1]!;
694
+ const rightSib = rightSibId ? (await get(this.store, rightSibId)) as BranchNode<TKey> : undefined;
695
+ if (rightSib && rightSib.nodes.length > (NodeCapacity >>> 1)) { // Attempt to borrow from right sibling
696
+ const node = rightSib.nodes[0]!;
697
+ const rightKey = rightSib.partitions[0]!;
698
+ this.insertPartition(branch, branch.partitions.length, pNode.partitions[pIndex]!, node);
699
+ this.deletePartition(rightSib, 0, 0);
700
+ this.updatePartition(pIndex + 1, path, depth - 1, rightKey);
701
+ return undefined;
702
+ }
703
+
704
+ const leftSibId = pNode.nodes[pIndex - 1];
705
+ const leftSib = leftSibId ? (await get(this.store, leftSibId)) as BranchNode<TKey> : undefined;
706
+ if (leftSib && leftSib.nodes.length > (NodeCapacity >>> 1)) { // Attempt to borrow from left sibling
707
+ const node = leftSib.nodes[leftSib.nodes.length - 1]!;
708
+ const pKey = leftSib.partitions[leftSib.partitions.length - 1]!;
709
+ this.insertPartition(branch, 0, pNode.partitions[pIndex - 1]!, node, 0);
710
+ this.deletePartition(leftSib, leftSib.partitions.length - 1);
711
+ pathBranch.index += 1;
712
+ this.updatePartition(pIndex, path, depth - 1, pKey);
713
+ return undefined;
714
+ }
715
+
716
+ if (rightSib && rightSib.nodes.length + branch.nodes.length <= NodeCapacity) { // Attempt to merge right sibling into self
717
+ const pKey = pNode.partitions[pIndex]!;
718
+ this.deletePartition(pNode, pIndex);
719
+ apply(this.store, branch, [partitions$, branch.partitions.length, 0, [pKey]]);
720
+ apply(this.store, branch, [partitions$, branch.partitions.length, 0, rightSib.partitions]);
721
+ apply(this.store, branch, [nodes$, branch.nodes.length, 0, rightSib.nodes]);
722
+ if (pIndex === 0 && pNode.partitions.length > 0) { // if parent is left edge, new right sibling is now the first partition
723
+ this.updatePartition(pIndex, path, depth - 1, pNode.partitions[0]!);
724
+ }
725
+ this.store.delete(rightSib.header.id);
726
+ return this.rebalanceBranch(path, depth - 1);
727
+ }
728
+
729
+ if (leftSib && leftSib.nodes.length + branch.nodes.length <= NodeCapacity) { // Attempt to merge self into left sibling
730
+ const pKey = pNode.partitions[pIndex - 1]!;
731
+ this.deletePartition(pNode, pIndex - 1);
732
+ apply(this.store, leftSib, [partitions$, leftSib.partitions.length, 0, [pKey]]);
733
+ apply(this.store, leftSib, [partitions$, leftSib.partitions.length, 0, branch.partitions]);
734
+ apply(this.store, leftSib, [nodes$, leftSib.nodes.length, 0, branch.nodes]);
735
+ pathBranch.node = leftSib;
736
+ pathBranch.index += leftSib.nodes.length;
737
+ this.store.delete(branch.header.id);
738
+ return this.rebalanceBranch(path, depth - 1);
739
+ }
740
+ }
741
+
742
+ protected updatePartition(nodeIndex: number, path: Path<TKey, TEntry>, depth: number, newKey: TKey) {
743
+ const pathBranch = path.branches[depth]!;
744
+ if (nodeIndex > 0) { // Only affects this branch; just update the partition key
745
+ apply(this.store, pathBranch.node, [partitions$, nodeIndex - 1, 1, [newKey]]);
746
+ } else if (depth !== 0) {
747
+ this.updatePartition(path.branches[depth - 1]!.index, path, depth - 1, newKey);
748
+ }
749
+ }
750
+
751
+ protected insertPartition(branch: BranchNode<TKey>, index: number, key: TKey, node: BlockId, nodeOffset = 1) {
752
+ apply(this.store, branch, [partitions$, index, 0, [key]]);
753
+ apply(this.store, branch, [nodes$, index + nodeOffset, 0, [node]]);
754
+ }
755
+
756
+ protected deletePartition(branch: BranchNode<TKey>, index: number, nodeOffset = 1) {
757
+ apply(this.store, branch, [partitions$, index, 1, []]);
758
+ apply(this.store, branch, [nodes$, index + nodeOffset, 1, []]);
759
+ }
760
+
761
+ private validatePath(path: Path<TKey, TEntry>) {
762
+ if (!this.isValid(path)) {
763
+ throw new Error("Path is invalid due to mutation of the tree");
764
+ }
765
+ }
766
+
767
+ /** Iterates every node ID below and including the given node. */
768
+ private async *nodeIds(node: ITreeNode): AsyncIterableIterator<BlockId> {
769
+ // TODO: This would be much more efficient if we avoided iterating into leaf nodes
770
+ if (node.header.type === TreeBranchBlockType) {
771
+ const subNodes = await Promise.all((node as BranchNode<TKey>).nodes.map(id => get(this.store, id)));
772
+ for (let subNode of subNodes) {
773
+ yield* this.nodeIds(subNode);
774
+ }
775
+ }
776
+ yield node.header.id;
777
+ }
778
+
779
+ protected getEntry(path: Path<TKey, TEntry>): TEntry {
780
+ return path.leafNode.entries[path.leafIndex]!;
781
+ }
782
+
783
+ protected updateEntry(path: Path<TKey, TEntry>, entry: TEntry) {
784
+ apply(this.store, path.leafNode, [entries$, path.leafIndex, 1, [entry]]);
785
+ }
786
+ }
787
+
788
+ class Split<TKey> {
789
+ constructor(
790
+ public key: TKey,
791
+ public right: ITreeNode,
792
+ public indexDelta: number
793
+ ) { }
794
+ }
795
+
796
+ function newLeafNode<TEntry>(store: BlockStore<ITreeNode>, entries: TEntry[]): LeafNode<TEntry> {
797
+ const header = store.createBlockHeader(TreeLeafBlockType);
798
+ return { header, entries };
799
+ }
800
+
801
+ function newBranchNode<TKey>(store: BlockStore<ITreeNode>, partitions: TKey[], nodes: BlockId[]): BranchNode<TKey> {
802
+ const header = store.createBlockHeader(TreeBranchBlockType);
803
+ return { header, partitions, nodes };
804
+ }