@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.
- package/README.md +328 -0
- package/dist/index.min.js +18 -0
- package/dist/index.min.js.map +7 -0
- package/dist/src/blocks/block-store.d.ts +12 -0
- package/dist/src/blocks/block-store.d.ts.map +1 -0
- package/dist/src/blocks/block-store.js +2 -0
- package/dist/src/blocks/block-store.js.map +1 -0
- package/dist/src/blocks/block-types.d.ts +3 -0
- package/dist/src/blocks/block-types.d.ts.map +1 -0
- package/dist/src/blocks/block-types.js +9 -0
- package/dist/src/blocks/block-types.js.map +1 -0
- package/dist/src/blocks/helpers.d.ts +4 -0
- package/dist/src/blocks/helpers.d.ts.map +1 -0
- package/dist/src/blocks/helpers.js +12 -0
- package/dist/src/blocks/helpers.js.map +1 -0
- package/dist/src/blocks/index.d.ts +5 -0
- package/dist/src/blocks/index.d.ts.map +1 -0
- package/dist/src/blocks/index.js +5 -0
- package/dist/src/blocks/index.js.map +1 -0
- package/dist/src/blocks/structs.d.ts +14 -0
- package/dist/src/blocks/structs.d.ts.map +1 -0
- package/dist/src/blocks/structs.js +2 -0
- package/dist/src/blocks/structs.js.map +1 -0
- package/dist/src/btree/btree.d.ts +135 -0
- package/dist/src/btree/btree.d.ts.map +1 -0
- package/dist/src/btree/btree.js +727 -0
- package/dist/src/btree/btree.js.map +1 -0
- package/dist/src/btree/independent-trunk.d.ts +17 -0
- package/dist/src/btree/independent-trunk.d.ts.map +1 -0
- package/dist/src/btree/independent-trunk.js +41 -0
- package/dist/src/btree/independent-trunk.js.map +1 -0
- package/dist/src/btree/index.d.ts +6 -0
- package/dist/src/btree/index.d.ts.map +1 -0
- package/dist/src/btree/index.js +6 -0
- package/dist/src/btree/index.js.map +1 -0
- package/dist/src/btree/key-range.d.ts +13 -0
- package/dist/src/btree/key-range.d.ts.map +1 -0
- package/dist/src/btree/key-range.js +20 -0
- package/dist/src/btree/key-range.js.map +1 -0
- package/dist/src/btree/keyset.d.ts +4 -0
- package/dist/src/btree/keyset.d.ts.map +1 -0
- package/dist/src/btree/keyset.js +4 -0
- package/dist/src/btree/keyset.js.map +1 -0
- package/dist/src/btree/nodes.d.ts +16 -0
- package/dist/src/btree/nodes.d.ts.map +1 -0
- package/dist/src/btree/nodes.js +9 -0
- package/dist/src/btree/nodes.js.map +1 -0
- package/dist/src/btree/path.d.ts +22 -0
- package/dist/src/btree/path.d.ts.map +1 -0
- package/dist/src/btree/path.js +39 -0
- package/dist/src/btree/path.js.map +1 -0
- package/dist/src/btree/tree-block.d.ts +7 -0
- package/dist/src/btree/tree-block.d.ts.map +1 -0
- package/dist/src/btree/tree-block.js +5 -0
- package/dist/src/btree/tree-block.js.map +1 -0
- package/dist/src/btree/trunk.d.ts +13 -0
- package/dist/src/btree/trunk.d.ts.map +1 -0
- package/dist/src/btree/trunk.js +2 -0
- package/dist/src/btree/trunk.js.map +1 -0
- package/dist/src/chain/chain-nodes.d.ts +18 -0
- package/dist/src/chain/chain-nodes.d.ts.map +1 -0
- package/dist/src/chain/chain-nodes.js +10 -0
- package/dist/src/chain/chain-nodes.js.map +1 -0
- package/dist/src/chain/chain.d.ts +75 -0
- package/dist/src/chain/chain.d.ts.map +1 -0
- package/dist/src/chain/chain.js +268 -0
- package/dist/src/chain/chain.js.map +1 -0
- package/dist/src/chain/index.d.ts +2 -0
- package/dist/src/chain/index.d.ts.map +1 -0
- package/dist/src/chain/index.js +2 -0
- package/dist/src/chain/index.js.map +1 -0
- package/dist/src/cluster/i-cluster.d.ts +5 -0
- package/dist/src/cluster/i-cluster.d.ts.map +1 -0
- package/dist/src/cluster/i-cluster.js +2 -0
- package/dist/src/cluster/i-cluster.js.map +1 -0
- package/dist/src/cluster/index.d.ts +3 -0
- package/dist/src/cluster/index.d.ts.map +1 -0
- package/dist/src/cluster/index.js +3 -0
- package/dist/src/cluster/index.js.map +1 -0
- package/dist/src/cluster/structs.d.ts +47 -0
- package/dist/src/cluster/structs.d.ts.map +1 -0
- package/dist/src/cluster/structs.js +2 -0
- package/dist/src/cluster/structs.js.map +1 -0
- package/dist/src/collection/action.d.ts +26 -0
- package/dist/src/collection/action.d.ts.map +1 -0
- package/dist/src/collection/action.js +2 -0
- package/dist/src/collection/action.js.map +1 -0
- package/dist/src/collection/collection.d.ts +48 -0
- package/dist/src/collection/collection.d.ts.map +1 -0
- package/dist/src/collection/collection.js +175 -0
- package/dist/src/collection/collection.js.map +1 -0
- package/dist/src/collection/index.d.ts +4 -0
- package/dist/src/collection/index.d.ts.map +1 -0
- package/dist/src/collection/index.js +4 -0
- package/dist/src/collection/index.js.map +1 -0
- package/dist/src/collection/struct.d.ts +16 -0
- package/dist/src/collection/struct.d.ts.map +1 -0
- package/dist/src/collection/struct.js +2 -0
- package/dist/src/collection/struct.js.map +1 -0
- package/dist/src/collections/diary/diary.d.ts +9 -0
- package/dist/src/collections/diary/diary.d.ts.map +1 -0
- package/dist/src/collections/diary/diary.js +37 -0
- package/dist/src/collections/diary/diary.js.map +1 -0
- package/dist/src/collections/diary/index.d.ts +3 -0
- package/dist/src/collections/diary/index.d.ts.map +1 -0
- package/dist/src/collections/diary/index.js +3 -0
- package/dist/src/collections/diary/index.js.map +1 -0
- package/dist/src/collections/diary/struct.d.ts +2 -0
- package/dist/src/collections/diary/struct.d.ts.map +1 -0
- package/dist/src/collections/diary/struct.js +3 -0
- package/dist/src/collections/diary/struct.js.map +1 -0
- package/dist/src/collections/index.d.ts +3 -0
- package/dist/src/collections/index.d.ts.map +1 -0
- package/dist/src/collections/index.js +3 -0
- package/dist/src/collections/index.js.map +1 -0
- package/dist/src/collections/tree/collection-trunk.d.ts +11 -0
- package/dist/src/collections/tree/collection-trunk.d.ts.map +1 -0
- package/dist/src/collections/tree/collection-trunk.js +22 -0
- package/dist/src/collections/tree/collection-trunk.js.map +1 -0
- package/dist/src/collections/tree/index.d.ts +3 -0
- package/dist/src/collections/tree/index.d.ts.map +1 -0
- package/dist/src/collections/tree/index.js +3 -0
- package/dist/src/collections/tree/index.js.map +1 -0
- package/dist/src/collections/tree/struct.d.ts +12 -0
- package/dist/src/collections/tree/struct.d.ts.map +1 -0
- package/dist/src/collections/tree/struct.js +4 -0
- package/dist/src/collections/tree/struct.js.map +1 -0
- package/dist/src/collections/tree/tree.d.ts +34 -0
- package/dist/src/collections/tree/tree.d.ts.map +1 -0
- package/dist/src/collections/tree/tree.js +100 -0
- package/dist/src/collections/tree/tree.js.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/log/index.d.ts +3 -0
- package/dist/src/log/index.d.ts.map +1 -0
- package/dist/src/log/index.js +3 -0
- package/dist/src/log/index.js.map +1 -0
- package/dist/src/log/log.d.ts +57 -0
- package/dist/src/log/log.d.ts.map +1 -0
- package/dist/src/log/log.js +131 -0
- package/dist/src/log/log.js.map +1 -0
- package/dist/src/log/struct.d.ts +36 -0
- package/dist/src/log/struct.d.ts.map +1 -0
- package/dist/src/log/struct.js +3 -0
- package/dist/src/log/struct.js.map +1 -0
- package/dist/src/network/i-key-network.d.ts +21 -0
- package/dist/src/network/i-key-network.d.ts.map +1 -0
- package/dist/src/network/i-key-network.js +2 -0
- package/dist/src/network/i-key-network.js.map +1 -0
- package/dist/src/network/i-peer-network.d.ts +8 -0
- package/dist/src/network/i-peer-network.d.ts.map +1 -0
- package/dist/src/network/i-peer-network.js +2 -0
- package/dist/src/network/i-peer-network.js.map +1 -0
- package/dist/src/network/i-repo.d.ts +17 -0
- package/dist/src/network/i-repo.d.ts.map +1 -0
- package/dist/src/network/i-repo.js +2 -0
- package/dist/src/network/i-repo.js.map +1 -0
- package/dist/src/network/index.d.ts +6 -0
- package/dist/src/network/index.d.ts.map +1 -0
- package/dist/src/network/index.js +6 -0
- package/dist/src/network/index.js.map +1 -0
- package/dist/src/network/repo-protocol.d.ts +19 -0
- package/dist/src/network/repo-protocol.d.ts.map +1 -0
- package/dist/src/network/repo-protocol.js +2 -0
- package/dist/src/network/repo-protocol.js.map +1 -0
- package/dist/src/network/struct.d.ts +115 -0
- package/dist/src/network/struct.d.ts.map +1 -0
- package/dist/src/network/struct.js +2 -0
- package/dist/src/network/struct.js.map +1 -0
- package/dist/src/transaction/actions-engine.d.ts +37 -0
- package/dist/src/transaction/actions-engine.d.ts.map +1 -0
- package/dist/src/transaction/actions-engine.js +67 -0
- package/dist/src/transaction/actions-engine.js.map +1 -0
- package/dist/src/transaction/context.d.ts +60 -0
- package/dist/src/transaction/context.d.ts.map +1 -0
- package/dist/src/transaction/context.js +91 -0
- package/dist/src/transaction/context.js.map +1 -0
- package/dist/src/transaction/coordinator.d.ts +118 -0
- package/dist/src/transaction/coordinator.d.ts.map +1 -0
- package/dist/src/transaction/coordinator.js +417 -0
- package/dist/src/transaction/coordinator.js.map +1 -0
- package/dist/src/transaction/index.d.ts +10 -0
- package/dist/src/transaction/index.d.ts.map +1 -0
- package/dist/src/transaction/index.js +7 -0
- package/dist/src/transaction/index.js.map +1 -0
- package/dist/src/transaction/session.d.ts +80 -0
- package/dist/src/transaction/session.d.ts.map +1 -0
- package/dist/src/transaction/session.js +161 -0
- package/dist/src/transaction/session.js.map +1 -0
- package/dist/src/transaction/transaction.d.ts +156 -0
- package/dist/src/transaction/transaction.d.ts.map +1 -0
- package/dist/src/transaction/transaction.js +31 -0
- package/dist/src/transaction/transaction.js.map +1 -0
- package/dist/src/transaction/validator.d.ts +46 -0
- package/dist/src/transaction/validator.d.ts.map +1 -0
- package/dist/src/transaction/validator.js +97 -0
- package/dist/src/transaction/validator.js.map +1 -0
- package/dist/src/transactor/index.d.ts +4 -0
- package/dist/src/transactor/index.d.ts.map +1 -0
- package/dist/src/transactor/index.js +4 -0
- package/dist/src/transactor/index.js.map +1 -0
- package/dist/src/transactor/network-transactor.d.ts +36 -0
- package/dist/src/transactor/network-transactor.d.ts.map +1 -0
- package/dist/src/transactor/network-transactor.js +297 -0
- package/dist/src/transactor/network-transactor.js.map +1 -0
- package/dist/src/transactor/transactor-source.d.ts +24 -0
- package/dist/src/transactor/transactor-source.d.ts.map +1 -0
- package/dist/src/transactor/transactor-source.js +62 -0
- package/dist/src/transactor/transactor-source.js.map +1 -0
- package/dist/src/transactor/transactor.d.ts +38 -0
- package/dist/src/transactor/transactor.d.ts.map +1 -0
- package/dist/src/transactor/transactor.js +2 -0
- package/dist/src/transactor/transactor.js.map +1 -0
- package/dist/src/transform/atomic.d.ts +8 -0
- package/dist/src/transform/atomic.d.ts.map +1 -0
- package/dist/src/transform/atomic.js +14 -0
- package/dist/src/transform/atomic.js.map +1 -0
- package/dist/src/transform/cache-source.d.ts +13 -0
- package/dist/src/transform/cache-source.d.ts.map +1 -0
- package/dist/src/transform/cache-source.js +52 -0
- package/dist/src/transform/cache-source.js.map +1 -0
- package/dist/src/transform/helpers.d.ts +25 -0
- package/dist/src/transform/helpers.d.ts.map +1 -0
- package/dist/src/transform/helpers.js +105 -0
- package/dist/src/transform/helpers.js.map +1 -0
- package/dist/src/transform/index.d.ts +6 -0
- package/dist/src/transform/index.d.ts.map +1 -0
- package/dist/src/transform/index.js +6 -0
- package/dist/src/transform/index.js.map +1 -0
- package/dist/src/transform/struct.d.ts +19 -0
- package/dist/src/transform/struct.d.ts.map +1 -0
- package/dist/src/transform/struct.js +2 -0
- package/dist/src/transform/struct.js.map +1 -0
- package/dist/src/transform/tracker.d.ts +22 -0
- package/dist/src/transform/tracker.d.ts.map +1 -0
- package/dist/src/transform/tracker.js +64 -0
- package/dist/src/transform/tracker.js.map +1 -0
- package/dist/src/utility/actor.d.ts +11 -0
- package/dist/src/utility/actor.d.ts.map +1 -0
- package/dist/src/utility/actor.js +39 -0
- package/dist/src/utility/actor.js.map +1 -0
- package/dist/src/utility/batch-coordinator.d.ts +56 -0
- package/dist/src/utility/batch-coordinator.d.ts.map +1 -0
- package/dist/src/utility/batch-coordinator.js +127 -0
- package/dist/src/utility/batch-coordinator.js.map +1 -0
- package/dist/src/utility/block-id-to-bytes.d.ts +3 -0
- package/dist/src/utility/block-id-to-bytes.d.ts.map +1 -0
- package/dist/src/utility/block-id-to-bytes.js +7 -0
- package/dist/src/utility/block-id-to-bytes.js.map +1 -0
- package/dist/src/utility/ensured.d.ts +3 -0
- package/dist/src/utility/ensured.d.ts.map +1 -0
- package/dist/src/utility/ensured.js +24 -0
- package/dist/src/utility/ensured.js.map +1 -0
- package/dist/src/utility/groupby.d.ts +8 -0
- package/dist/src/utility/groupby.d.ts.map +1 -0
- package/dist/src/utility/groupby.js +15 -0
- package/dist/src/utility/groupby.js.map +1 -0
- package/dist/src/utility/is-record-empty.d.ts +3 -0
- package/dist/src/utility/is-record-empty.d.ts.map +1 -0
- package/dist/src/utility/is-record-empty.js +7 -0
- package/dist/src/utility/is-record-empty.js.map +1 -0
- package/dist/src/utility/latches.d.ts +11 -0
- package/dist/src/utility/latches.d.ts.map +1 -0
- package/dist/src/utility/latches.js +36 -0
- package/dist/src/utility/latches.js.map +1 -0
- package/dist/src/utility/nameof.d.ts +3 -0
- package/dist/src/utility/nameof.d.ts.map +1 -0
- package/dist/src/utility/nameof.js +5 -0
- package/dist/src/utility/nameof.js.map +1 -0
- package/dist/src/utility/pending.d.ts +13 -0
- package/dist/src/utility/pending.d.ts.map +1 -0
- package/dist/src/utility/pending.js +37 -0
- package/dist/src/utility/pending.js.map +1 -0
- package/package.json +56 -0
- package/src/blocks/block-store.ts +13 -0
- package/src/blocks/block-types.ts +11 -0
- package/src/blocks/helpers.ts +13 -0
- package/src/blocks/index.ts +5 -0
- package/src/blocks/structs.ts +17 -0
- package/src/btree/btree.ts +804 -0
- package/src/btree/independent-trunk.ts +54 -0
- package/src/btree/index.ts +5 -0
- package/src/btree/key-range.ts +15 -0
- package/src/btree/keyset.ts +6 -0
- package/src/btree/nodes.ts +25 -0
- package/src/btree/path.ts +37 -0
- package/src/btree/tree-block.ts +11 -0
- package/src/btree/trunk.ts +14 -0
- package/src/chain/chain-nodes.ts +24 -0
- package/src/chain/chain.ts +324 -0
- package/src/chain/index.ts +2 -0
- package/src/cluster/i-cluster.ts +6 -0
- package/src/cluster/index.ts +2 -0
- package/src/cluster/structs.ts +46 -0
- package/src/collection/action.ts +31 -0
- package/src/collection/collection.ts +200 -0
- package/src/collection/index.ts +3 -0
- package/src/collection/struct.ts +20 -0
- package/src/collections/diary/diary.ts +43 -0
- package/src/collections/diary/index.ts +2 -0
- package/src/collections/diary/struct.ts +3 -0
- package/src/collections/index.ts +2 -0
- package/src/collections/tree/collection-trunk.ts +25 -0
- package/src/collections/tree/index.ts +2 -0
- package/src/collections/tree/readme.md +19 -0
- package/src/collections/tree/struct.ts +18 -0
- package/src/collections/tree/tree.ts +124 -0
- package/src/index.ts +17 -0
- package/src/log/index.ts +2 -0
- package/src/log/log.ts +155 -0
- package/src/log/struct.ts +40 -0
- package/src/network/i-key-network.ts +24 -0
- package/src/network/i-peer-network.ts +8 -0
- package/src/network/i-repo.ts +19 -0
- package/src/network/index.ts +5 -0
- package/src/network/repo-protocol.ts +12 -0
- package/src/network/struct.ts +137 -0
- package/src/transaction/actions-engine.ts +83 -0
- package/src/transaction/context.ts +103 -0
- package/src/transaction/coordinator.ts +583 -0
- package/src/transaction/index.ts +30 -0
- package/src/transaction/session.ts +182 -0
- package/src/transaction/transaction.ts +205 -0
- package/src/transaction/validator.ts +150 -0
- package/src/transactor/index.ts +4 -0
- package/src/transactor/network-transactor.ts +435 -0
- package/src/transactor/transactor-source.ts +65 -0
- package/src/transactor/transactor.ts +44 -0
- package/src/transform/atomic.ts +16 -0
- package/src/transform/cache-source.ts +57 -0
- package/src/transform/helpers.ts +117 -0
- package/src/transform/index.ts +5 -0
- package/src/transform/struct.ts +22 -0
- package/src/transform/tracker.ts +70 -0
- package/src/utility/actor.ts +62 -0
- package/src/utility/batch-coordinator.ts +174 -0
- package/src/utility/block-id-to-bytes.ts +8 -0
- package/src/utility/ensured.ts +32 -0
- package/src/utility/groupby.ts +18 -0
- package/src/utility/is-record-empty.ts +5 -0
- package/src/utility/latches.ts +42 -0
- package/src/utility/nameof.ts +7 -0
- 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
|
+
}
|