@frostpillar/frostpillar-btree 0.2.7 → 0.2.9
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-JA.md +93 -59
- package/README.md +93 -59
- package/dist/InMemoryBTree.d.cts +45 -0
- package/dist/btree/autoScale.d.cts +11 -0
- package/dist/btree/bulkLoad.d.cts +5 -0
- package/dist/btree/deleteRange.d.cts +3 -0
- package/dist/btree/entry-lookup.d.cts +8 -0
- package/dist/btree/integrity-helpers.d.cts +6 -0
- package/dist/btree/integrity.d.cts +2 -0
- package/dist/btree/mutations.d.cts +10 -0
- package/dist/btree/navigation.d.cts +23 -0
- package/dist/btree/node-ops.d.cts +15 -0
- package/dist/btree/rangeQuery.d.cts +7 -0
- package/dist/btree/rebalance-branch.d.cts +4 -0
- package/dist/btree/rebalance.d.cts +7 -0
- package/dist/btree/serialization.d.cts +18 -0
- package/dist/btree/split.d.cts +3 -0
- package/dist/btree/stats.d.cts +2 -0
- package/dist/btree/traversal.d.cts +7 -0
- package/dist/btree/types.d.cts +110 -0
- package/dist/{chunk-UGGWGP4E.js → chunk-OFXDCKLC.js} +4 -3
- package/dist/concurrency/ConcurrentInMemoryBTree.d.cts +49 -0
- package/dist/concurrency/coordinator.d.cts +21 -0
- package/dist/concurrency/helpers.d.cts +34 -0
- package/dist/concurrency/index.d.cts +2 -0
- package/dist/concurrency/syncLogValidation.d.cts +2 -0
- package/dist/concurrency/types.d.cts +54 -0
- package/dist/concurrency/writeOps.d.cts +48 -0
- package/dist/core.cjs +4 -3
- package/dist/core.d.cts +4 -0
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +12 -0
- package/dist/frostpillar-btree-core.min.js +1 -1
- package/dist/frostpillar-btree.min.js +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.d.cts +6 -0
- package/dist/index.js +1 -1
- package/package.json +21 -9
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type BTreeState, type LeafNode } from './types.cjs';
|
|
2
|
+
export declare const findLeafForKey: <TKey, TValue>(state: BTreeState<TKey, TValue>, userKey: TKey, sequence: number) => LeafNode<TKey, TValue>;
|
|
3
|
+
export declare const lowerBoundInLeaf: <TKey, TValue>(state: BTreeState<TKey, TValue>, leaf: LeafNode<TKey, TValue>, userKey: TKey, sequence: number) => number;
|
|
4
|
+
export declare const upperBoundInLeaf: <TKey, TValue>(state: BTreeState<TKey, TValue>, leaf: LeafNode<TKey, TValue>, userKey: TKey, sequence: number) => number;
|
|
5
|
+
export declare const findLeafFromHint: <TKey, TValue>(state: BTreeState<TKey, TValue>, hint: LeafNode<TKey, TValue>, userKey: TKey, sequence: number) => LeafNode<TKey, TValue>;
|
|
6
|
+
/** Returns shared cursor — caller must consume result before the next navigation call. */
|
|
7
|
+
export declare const findFirstMatchingUserKey: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => {
|
|
8
|
+
leaf: LeafNode<TKey, TValue>;
|
|
9
|
+
index: number;
|
|
10
|
+
} | null;
|
|
11
|
+
/** Returns shared cursor — caller must consume result before the next navigation call. */
|
|
12
|
+
export declare const findLastMatchingUserKey: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => {
|
|
13
|
+
leaf: LeafNode<TKey, TValue>;
|
|
14
|
+
index: number;
|
|
15
|
+
} | null;
|
|
16
|
+
export declare const hasKeyEntry: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => boolean;
|
|
17
|
+
export declare const findNextHigherKey: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => TKey | null;
|
|
18
|
+
export declare const findNextLowerKey: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => TKey | null;
|
|
19
|
+
/** Returns shared cursor — caller must consume result before the next navigation call. */
|
|
20
|
+
export declare const findPairOrNextLower: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => {
|
|
21
|
+
leaf: LeafNode<TKey, TValue>;
|
|
22
|
+
index: number;
|
|
23
|
+
} | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BranchNode, BTreeNode, LeafEntry, LeafNode, NodeKey } from './types.cjs';
|
|
2
|
+
export declare const createLeafNode: <TKey, TValue>(entries: LeafEntry<TKey, TValue>[], parent: BranchNode<TKey, TValue> | null) => LeafNode<TKey, TValue>;
|
|
3
|
+
export declare const createBranchNode: <TKey, TValue>(children: BTreeNode<TKey, TValue>[], parent: BranchNode<TKey, TValue> | null) => BranchNode<TKey, TValue>;
|
|
4
|
+
export declare const leafEntryCount: <TKey, TValue>(leaf: LeafNode<TKey, TValue>) => number;
|
|
5
|
+
export declare const leafEntryAt: <TKey, TValue>(leaf: LeafNode<TKey, TValue>, i: number) => LeafEntry<TKey, TValue>;
|
|
6
|
+
export declare const leafShiftEntry: <TKey, TValue>(leaf: LeafNode<TKey, TValue>) => LeafEntry<TKey, TValue> | undefined;
|
|
7
|
+
export declare const leafPopEntry: <TKey, TValue>(leaf: LeafNode<TKey, TValue>) => LeafEntry<TKey, TValue> | undefined;
|
|
8
|
+
export declare const leafUnshiftEntry: <TKey, TValue>(leaf: LeafNode<TKey, TValue>, entry: LeafEntry<TKey, TValue>) => void;
|
|
9
|
+
export declare const leafRemoveAt: <TKey, TValue>(leaf: LeafNode<TKey, TValue>, logicalIndex: number) => void;
|
|
10
|
+
export declare const leafInsertAt: <TKey, TValue>(leaf: LeafNode<TKey, TValue>, logicalIndex: number, entry: LeafEntry<TKey, TValue>) => void;
|
|
11
|
+
export declare const leafCompact: <TKey, TValue>(leaf: LeafNode<TKey, TValue>) => void;
|
|
12
|
+
export declare const branchCompact: <TKey, TValue>(branch: BranchNode<TKey, TValue>) => void;
|
|
13
|
+
export declare const branchChildCount: <TKey, TValue>(branch: BranchNode<TKey, TValue>) => number;
|
|
14
|
+
export declare const branchInsertAt: <TKey, TValue>(branch: BranchNode<TKey, TValue>, logicalIndex: number, child: BTreeNode<TKey, TValue>, key: NodeKey<TKey>) => void;
|
|
15
|
+
export declare const branchRemoveAt: <TKey, TValue>(branch: BranchNode<TKey, TValue>, physIndex: number) => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BTreeEntry, type BTreeState, type RangeBounds } from './types.cjs';
|
|
2
|
+
export declare function isEmptyRange<TKey>(compare: (a: TKey, b: TKey) => number, startKey: TKey, endKey: TKey, options?: RangeBounds): boolean;
|
|
3
|
+
export declare const countRangeEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>, startKey: TKey, endKey: TKey, options?: RangeBounds) => number;
|
|
4
|
+
/** Single-pass range query that produces public entries (via freezeEntry) inline. */
|
|
5
|
+
export declare const rangeQueryPublicEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>, startKey: TKey, endKey: TKey, options?: RangeBounds) => BTreeEntry<TKey, TValue>[];
|
|
6
|
+
/** Streaming range iteration — invokes callback for each entry without array allocation. */
|
|
7
|
+
export declare const forEachRangeEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>, startKey: TKey, endKey: TKey, callback: (entry: BTreeEntry<TKey, TValue>) => void, options?: RangeBounds) => void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type BTreeNode, type BTreeState, type BranchNode } from './types.cjs';
|
|
2
|
+
export declare const updateMinKeyInAncestors: <TKey, TValue>(node: BTreeNode<TKey, TValue>) => void;
|
|
3
|
+
export declare const removeChildFromBranch: <TKey, TValue>(branch: BranchNode<TKey, TValue>, childIndex: number) => void;
|
|
4
|
+
export declare const rebalanceAfterBranchRemoval: <TKey, TValue>(state: BTreeState<TKey, TValue>, branch: BranchNode<TKey, TValue>) => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BTreeState, type LeafNode } from './types.cjs';
|
|
2
|
+
import { updateMinKeyInAncestors } from './rebalance-branch.cjs';
|
|
3
|
+
export { updateMinKeyInAncestors };
|
|
4
|
+
/** Applies the lazy divisor to a minimum-occupancy value. */
|
|
5
|
+
export declare const applyLazyThreshold: (min: number) => number;
|
|
6
|
+
export declare const leafRebalanceThreshold: <TKey, TValue>(state: BTreeState<TKey, TValue>) => number;
|
|
7
|
+
export declare const rebalanceAfterLeafRemoval: <TKey, TValue>(state: BTreeState<TKey, TValue>, leaf: LeafNode<TKey, TValue>) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type BTreeState, type DeleteRebalancePolicy, type DuplicateKeyPolicy, type InMemoryBTreeConfig, type KeyComparator } from './types.cjs';
|
|
2
|
+
export interface BTreeJSON<TKey, TValue> {
|
|
3
|
+
version: number;
|
|
4
|
+
config: {
|
|
5
|
+
maxLeafEntries: number;
|
|
6
|
+
maxBranchChildren: number;
|
|
7
|
+
duplicateKeys: DuplicateKeyPolicy;
|
|
8
|
+
enableEntryIdLookup: boolean;
|
|
9
|
+
autoScale: boolean;
|
|
10
|
+
deleteRebalancePolicy?: DeleteRebalancePolicy;
|
|
11
|
+
};
|
|
12
|
+
entries: [TKey, TValue][];
|
|
13
|
+
}
|
|
14
|
+
export declare const buildConfigFromState: <TKey, TValue>(state: BTreeState<TKey, TValue>) => InMemoryBTreeConfig<TKey>;
|
|
15
|
+
export declare const serializeToJSON: <TKey, TValue>(state: BTreeState<TKey, TValue>) => BTreeJSON<TKey, TValue>;
|
|
16
|
+
export declare const validateBTreeJSON: <TKey, TValue>(json: BTreeJSON<TKey, TValue>) => void;
|
|
17
|
+
export declare const validateBTreeJSONSortOrder: <TKey, TValue>(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>) => void;
|
|
18
|
+
export declare const buildConfigFromJSON: <TKey>(json: BTreeJSON<TKey, unknown>, compareKeys: KeyComparator<TKey>) => InMemoryBTreeConfig<TKey>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type BTreeState, type BranchNode, type LeafNode } from './types.cjs';
|
|
2
|
+
export declare const splitLeaf: <TKey, TValue>(state: BTreeState<TKey, TValue>, leaf: LeafNode<TKey, TValue>) => void;
|
|
3
|
+
export declare const splitBranch: <TKey, TValue>(state: BTreeState<TKey, TValue>, branch: BranchNode<TKey, TValue>) => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BTreeEntry, type BTreeState } from './types.cjs';
|
|
2
|
+
/** Collect all entries into a pre-allocated array, frozen for safe external use. */
|
|
3
|
+
export declare const snapshotEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>) => BTreeEntry<TKey, TValue>[];
|
|
4
|
+
/** Collect all internal entries (no freeze) for internal use (clone, serialize). */
|
|
5
|
+
export declare const collectInternalEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>) => BTreeEntry<TKey, TValue>[];
|
|
6
|
+
/** Iterate all entries, invoking callback with frozen entries. */
|
|
7
|
+
export declare const forEachEntry: <TKey, TValue>(state: BTreeState<TKey, TValue>, callback: (entry: BTreeEntry<TKey, TValue>) => void, thisArg?: unknown) => void;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export declare const DEFAULT_MAX_LEAF_ENTRIES = 64;
|
|
2
|
+
export declare const DEFAULT_MAX_BRANCH_CHILDREN = 64;
|
|
3
|
+
export declare const MIN_NODE_CAPACITY = 3;
|
|
4
|
+
export declare const MAX_NODE_CAPACITY = 16384;
|
|
5
|
+
export declare const NODE_LEAF: 0;
|
|
6
|
+
export declare const NODE_BRANCH: 1;
|
|
7
|
+
export type KeyComparator<TKey> = (left: TKey, right: TKey) => number;
|
|
8
|
+
export type DuplicateKeyPolicy = 'allow' | 'reject' | 'replace';
|
|
9
|
+
export type DeleteRebalancePolicy = 'standard' | 'lazy';
|
|
10
|
+
/**
|
|
11
|
+
* Defines the inclusivity of the lower and upper bounds for a key range scan.
|
|
12
|
+
* Both bounds default to `'inclusive'` when omitted.
|
|
13
|
+
*/
|
|
14
|
+
export interface RangeBounds {
|
|
15
|
+
/** Lower bound type. Defaults to `'inclusive'` when omitted. */
|
|
16
|
+
lowerBound?: 'inclusive' | 'exclusive';
|
|
17
|
+
/** Upper bound type. Defaults to `'inclusive'` when omitted. */
|
|
18
|
+
upperBound?: 'inclusive' | 'exclusive';
|
|
19
|
+
}
|
|
20
|
+
export declare const normalizeDuplicateKeyPolicy: (value: DuplicateKeyPolicy | undefined) => DuplicateKeyPolicy;
|
|
21
|
+
export declare const normalizeDeleteRebalancePolicy: (value: DeleteRebalancePolicy | undefined) => DeleteRebalancePolicy;
|
|
22
|
+
export type EntryId = number & {
|
|
23
|
+
readonly __brand: 'EntryId';
|
|
24
|
+
};
|
|
25
|
+
export interface BTreeEntry<TKey, TValue> {
|
|
26
|
+
readonly entryId: EntryId;
|
|
27
|
+
readonly key: TKey;
|
|
28
|
+
readonly value: TValue;
|
|
29
|
+
}
|
|
30
|
+
export interface NodeKey<TKey> {
|
|
31
|
+
key: TKey;
|
|
32
|
+
sequence: number;
|
|
33
|
+
}
|
|
34
|
+
/** Internal mutable entry stored in leaf nodes. */
|
|
35
|
+
export interface LeafEntry<TKey, TValue> {
|
|
36
|
+
entryId: EntryId;
|
|
37
|
+
key: TKey;
|
|
38
|
+
value: TValue;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Freezes and returns an internal entry for safe exposure via the public API.
|
|
42
|
+
* Idempotent: re-freezing an already-frozen object is a no-op in V8.
|
|
43
|
+
* All entries are frozen at creation via createEntry, so this is a zero-allocation cast.
|
|
44
|
+
*/
|
|
45
|
+
export declare const freezeEntry: <TKey, TValue>(entry: LeafEntry<TKey, TValue>) => BTreeEntry<TKey, TValue>;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a frozen LeafEntry with a canonical property order.
|
|
48
|
+
* All entry creation MUST go through this function to guarantee a single
|
|
49
|
+
* V8 hidden class across all entries in the tree.
|
|
50
|
+
*/
|
|
51
|
+
export declare const createEntry: <TKey, TValue>(key: TKey, entryId: EntryId, value: TValue) => LeafEntry<TKey, TValue>;
|
|
52
|
+
export interface LeafNode<TKey, TValue> {
|
|
53
|
+
kind: typeof NODE_LEAF;
|
|
54
|
+
entries: LeafEntry<TKey, TValue>[];
|
|
55
|
+
entryOffset: number;
|
|
56
|
+
parent: BranchNode<TKey, TValue> | null;
|
|
57
|
+
indexInParent: number;
|
|
58
|
+
prev: LeafNode<TKey, TValue> | null;
|
|
59
|
+
next: LeafNode<TKey, TValue> | null;
|
|
60
|
+
}
|
|
61
|
+
export interface BranchNode<TKey, TValue> {
|
|
62
|
+
kind: typeof NODE_BRANCH;
|
|
63
|
+
children: BTreeNode<TKey, TValue>[];
|
|
64
|
+
keys: NodeKey<TKey>[];
|
|
65
|
+
childOffset: number;
|
|
66
|
+
parent: BranchNode<TKey, TValue> | null;
|
|
67
|
+
indexInParent: number;
|
|
68
|
+
}
|
|
69
|
+
export type BTreeNode<TKey, TValue> = LeafNode<TKey, TValue> | BranchNode<TKey, TValue>;
|
|
70
|
+
export interface BTreeState<TKey, TValue> {
|
|
71
|
+
compareKeys: KeyComparator<TKey>;
|
|
72
|
+
maxLeafEntries: number;
|
|
73
|
+
maxBranchChildren: number;
|
|
74
|
+
duplicateKeys: DuplicateKeyPolicy;
|
|
75
|
+
root: BTreeNode<TKey, TValue>;
|
|
76
|
+
leftmostLeaf: LeafNode<TKey, TValue>;
|
|
77
|
+
rightmostLeaf: LeafNode<TKey, TValue>;
|
|
78
|
+
entryCount: number;
|
|
79
|
+
nextSequence: number;
|
|
80
|
+
minLeafEntries: number;
|
|
81
|
+
minBranchChildren: number;
|
|
82
|
+
entryKeys: Map<EntryId, TKey> | null;
|
|
83
|
+
autoScale: boolean;
|
|
84
|
+
deleteRebalancePolicy: DeleteRebalancePolicy;
|
|
85
|
+
_nextAutoScaleThreshold: number;
|
|
86
|
+
/** @internal Shared return object for navigation functions — never store a reference across calls. */
|
|
87
|
+
_cursor: {
|
|
88
|
+
leaf: LeafNode<TKey, TValue>;
|
|
89
|
+
index: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export interface InMemoryBTreeConfig<TKey> {
|
|
93
|
+
compareKeys: KeyComparator<TKey>;
|
|
94
|
+
maxLeafEntries?: number;
|
|
95
|
+
maxBranchChildren?: number;
|
|
96
|
+
duplicateKeys?: DuplicateKeyPolicy;
|
|
97
|
+
enableEntryIdLookup?: boolean;
|
|
98
|
+
autoScale?: boolean;
|
|
99
|
+
deleteRebalancePolicy?: DeleteRebalancePolicy;
|
|
100
|
+
}
|
|
101
|
+
export interface BTreeStats {
|
|
102
|
+
height: number;
|
|
103
|
+
leafCount: number;
|
|
104
|
+
branchCount: number;
|
|
105
|
+
entryCount: number;
|
|
106
|
+
}
|
|
107
|
+
export declare const isLeafNode: <TKey, TValue>(node: BTreeNode<TKey, TValue>) => node is LeafNode<TKey, TValue>;
|
|
108
|
+
export declare const writeMinKeyTo: <TKey, TValue>(node: BTreeNode<TKey, TValue>, target: NodeKey<TKey>) => boolean;
|
|
109
|
+
export declare const normalizeNodeCapacity: (value: number | undefined, field: string, defaultValue: number) => number;
|
|
110
|
+
export { createLeafNode, createBranchNode, leafEntryCount, leafEntryAt, leafShiftEntry, leafPopEntry, leafUnshiftEntry, leafRemoveAt, leafInsertAt, leafCompact, branchCompact, branchChildCount, branchInsertAt, branchRemoveAt, } from './node-ops.cjs';
|
|
@@ -806,8 +806,10 @@ var tryBorrowFromLeafSibling = (state, leaf, parent, leafIndex, leftSibling, rig
|
|
|
806
806
|
const borrowed = leafShiftEntry(rightSibling);
|
|
807
807
|
if (borrowed === void 0)
|
|
808
808
|
throw new BTreeInvariantError("right leaf borrow failed");
|
|
809
|
+
const leafWasEmpty = leafEntryCount(leaf) === 0;
|
|
809
810
|
leaf.entries.push(borrowed);
|
|
810
811
|
writeMinKeyTo(rightSibling, parent.keys[leafIndex + 1]);
|
|
812
|
+
if (leafWasEmpty) updateMinKeyInAncestors(leaf);
|
|
811
813
|
return true;
|
|
812
814
|
}
|
|
813
815
|
if (leftSibling !== null && leafEntryCount(leftSibling) > state.minLeafEntries) {
|
|
@@ -830,8 +832,10 @@ var mergeLeafWithSibling = (state, leaf, parent, leafIndex, leftSibling, rightSi
|
|
|
830
832
|
return;
|
|
831
833
|
}
|
|
832
834
|
if (rightSibling !== null) {
|
|
835
|
+
const leafWasEmpty = leafEntryCount(leaf) === 0;
|
|
833
836
|
mergeLeafEntries(leaf, rightSibling);
|
|
834
837
|
detachLeafFromChain(state, rightSibling);
|
|
838
|
+
if (leafWasEmpty) updateMinKeyInAncestors(leaf);
|
|
835
839
|
removeChildFromBranch(parent, leafIndex + 1);
|
|
836
840
|
rebalanceAfterBranchRemoval(state, parent);
|
|
837
841
|
return;
|
|
@@ -902,9 +906,6 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount) => {
|
|
|
902
906
|
break;
|
|
903
907
|
safetyGuard -= 1;
|
|
904
908
|
}
|
|
905
|
-
if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
|
|
906
|
-
updateMinKeyInAncestors(leaf);
|
|
907
|
-
}
|
|
908
909
|
return countAfterSplice;
|
|
909
910
|
};
|
|
910
911
|
var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { InMemoryBTree, type BTreeEntry, type BTreeJSON, type BTreeStats, type EntryId, type RangeBounds } from '../InMemoryBTree.cjs';
|
|
2
|
+
import type { KeyComparator } from '../btree/types.cjs';
|
|
3
|
+
import type { ConcurrentInMemoryBTreeConfig } from './types.cjs';
|
|
4
|
+
export declare class ConcurrentInMemoryBTree<TKey, TValue> {
|
|
5
|
+
private readonly coord;
|
|
6
|
+
private readonly compareKeys;
|
|
7
|
+
private readonly duplicateKeys;
|
|
8
|
+
constructor(config: ConcurrentInMemoryBTreeConfig<TKey, TValue>);
|
|
9
|
+
sync(): Promise<void>;
|
|
10
|
+
syncThenRead<TResult>(fn: (tree: InMemoryBTree<TKey, TValue>) => TResult): Promise<TResult>;
|
|
11
|
+
put(key: TKey, value: TValue): Promise<EntryId>;
|
|
12
|
+
remove(key: TKey): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
13
|
+
removeById(entryId: EntryId): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
14
|
+
updateById(entryId: EntryId, value: TValue): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
15
|
+
popFirst(): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
16
|
+
popLast(): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
17
|
+
putMany(entries: readonly {
|
|
18
|
+
key: TKey;
|
|
19
|
+
value: TValue;
|
|
20
|
+
}[]): Promise<EntryId[]>;
|
|
21
|
+
deleteRange(startKey: TKey, endKey: TKey, options?: RangeBounds): Promise<number>;
|
|
22
|
+
clear(): Promise<void>;
|
|
23
|
+
get(key: TKey): Promise<TValue | null>;
|
|
24
|
+
hasKey(key: TKey): Promise<boolean>;
|
|
25
|
+
findFirst(key: TKey): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
26
|
+
findLast(key: TKey): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
27
|
+
range(startKey: TKey, endKey: TKey, options?: RangeBounds): Promise<BTreeEntry<TKey, TValue>[]>;
|
|
28
|
+
snapshot(): Promise<BTreeEntry<TKey, TValue>[]>;
|
|
29
|
+
size(): Promise<number>;
|
|
30
|
+
assertInvariants(): Promise<void>;
|
|
31
|
+
getStats(): Promise<BTreeStats>;
|
|
32
|
+
peekFirst(): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
33
|
+
peekLast(): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
34
|
+
peekById(entryId: EntryId): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
35
|
+
count(startKey: TKey, endKey: TKey, options?: RangeBounds): Promise<number>;
|
|
36
|
+
nextHigherKey(key: TKey): Promise<TKey | null>;
|
|
37
|
+
nextLowerKey(key: TKey): Promise<TKey | null>;
|
|
38
|
+
getPairOrNextLower(key: TKey): Promise<BTreeEntry<TKey, TValue> | null>;
|
|
39
|
+
entries(): Promise<BTreeEntry<TKey, TValue>[]>;
|
|
40
|
+
entriesReversed(): Promise<BTreeEntry<TKey, TValue>[]>;
|
|
41
|
+
keys(): Promise<TKey[]>;
|
|
42
|
+
values(): Promise<TValue[]>;
|
|
43
|
+
forEach(callback: (entry: BTreeEntry<TKey, TValue>) => void): Promise<void>;
|
|
44
|
+
forEachRange(startKey: TKey, endKey: TKey, callback: (entry: BTreeEntry<TKey, TValue>) => void, options?: RangeBounds): Promise<void>;
|
|
45
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<BTreeEntry<TKey, TValue>>;
|
|
46
|
+
clone(): Promise<InMemoryBTree<TKey, TValue>>;
|
|
47
|
+
toJSON(): Promise<BTreeJSON<TKey, TValue>>;
|
|
48
|
+
static fromJSON<TKey, TValue>(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>): InMemoryBTree<TKey, TValue>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { InMemoryBTree } from '../InMemoryBTree.cjs';
|
|
2
|
+
import type { BTreeMutation, ReadMode, SharedTreeStore } from './types.cjs';
|
|
3
|
+
import { type MutationResult } from './helpers.cjs';
|
|
4
|
+
export declare class Coordinator<TKey, TValue> {
|
|
5
|
+
readonly tree: InMemoryBTree<TKey, TValue>;
|
|
6
|
+
readonly store: SharedTreeStore<TKey, TValue>;
|
|
7
|
+
readonly maxRetries: number;
|
|
8
|
+
readonly maxSyncMutationsPerBatch: number;
|
|
9
|
+
readonly configFingerprint: string;
|
|
10
|
+
readonly readMode: ReadMode;
|
|
11
|
+
currentVersion: bigint;
|
|
12
|
+
operationQueue: Promise<void>;
|
|
13
|
+
initSeen: boolean;
|
|
14
|
+
corrupted: boolean;
|
|
15
|
+
constructor(tree: InMemoryBTree<TKey, TValue>, store: SharedTreeStore<TKey, TValue>, maxRetries: number, maxSyncMutationsPerBatch: number, configFingerprint: string, readMode: ReadMode);
|
|
16
|
+
syncUnlocked(): Promise<void>;
|
|
17
|
+
runExclusive<TResult>(operation: () => Promise<TResult>): Promise<TResult>;
|
|
18
|
+
readOp<TResult>(fn: (tree: InMemoryBTree<TKey, TValue>) => TResult): Promise<TResult>;
|
|
19
|
+
appendAndApply<TMutation extends BTreeMutation<TKey, TValue>>(evaluate: (tree: InMemoryBTree<TKey, TValue>) => TMutation | null): Promise<MutationResult<TKey, TValue, TMutation> | null>;
|
|
20
|
+
writeOp<TMutation extends BTreeMutation<TKey, TValue>>(evaluator: (tree: InMemoryBTree<TKey, TValue>) => TMutation | null): Promise<MutationResult<TKey, TValue, TMutation> | null>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BTreeMutation, ConcurrentInMemoryBTreeConfig, ReadMode } from './types.cjs';
|
|
2
|
+
import type { BTreeEntry, EntryId } from '../InMemoryBTree.cjs';
|
|
3
|
+
export declare const computeConfigFingerprint: <TKey>(config: ConcurrentInMemoryBTreeConfig<TKey, unknown>) => string;
|
|
4
|
+
export type MutationResult<TKey, TValue, TMutation extends BTreeMutation<TKey, TValue>> = TMutation extends {
|
|
5
|
+
type: 'init';
|
|
6
|
+
} ? null : TMutation extends {
|
|
7
|
+
type: 'put';
|
|
8
|
+
} ? EntryId : TMutation extends {
|
|
9
|
+
type: 'putMany';
|
|
10
|
+
} ? EntryId[] : TMutation extends {
|
|
11
|
+
type: 'remove';
|
|
12
|
+
} ? BTreeEntry<TKey, TValue> | null : TMutation extends {
|
|
13
|
+
type: 'removeById';
|
|
14
|
+
} ? BTreeEntry<TKey, TValue> | null : TMutation extends {
|
|
15
|
+
type: 'updateById';
|
|
16
|
+
} ? BTreeEntry<TKey, TValue> | null : TMutation extends {
|
|
17
|
+
type: 'popFirst';
|
|
18
|
+
} ? BTreeEntry<TKey, TValue> | null : TMutation extends {
|
|
19
|
+
type: 'popLast';
|
|
20
|
+
} ? BTreeEntry<TKey, TValue> | null : TMutation extends {
|
|
21
|
+
type: 'deleteRange';
|
|
22
|
+
} ? number : TMutation extends {
|
|
23
|
+
type: 'clear';
|
|
24
|
+
} ? null : never;
|
|
25
|
+
export type AnyMutationResult<TKey, TValue> = EntryId | EntryId[] | BTreeEntry<TKey, TValue> | number | null;
|
|
26
|
+
export declare const assertNeverMutation: (mutation: never) => never;
|
|
27
|
+
export declare const validateMutationBatch: <TKey, TValue>(mutations: BTreeMutation<TKey, TValue>[], expectedConfigFingerprint?: string) => void;
|
|
28
|
+
export declare const normalizeMaxRetries: (value: number | undefined) => number;
|
|
29
|
+
export declare const normalizeMaxSyncMutationsPerBatch: (value: number | undefined) => number;
|
|
30
|
+
export declare const normalizeReadMode: (value: ReadMode | undefined) => ReadMode;
|
|
31
|
+
export declare function assertAppendVersionContract(expectedVersion: bigint, appendResult: unknown): asserts appendResult is {
|
|
32
|
+
applied: boolean;
|
|
33
|
+
version: bigint;
|
|
34
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { EntryId, InMemoryBTreeConfig, RangeBounds } from '../InMemoryBTree.cjs';
|
|
2
|
+
export type BTreeMutation<TKey, TValue> = {
|
|
3
|
+
type: 'init';
|
|
4
|
+
configFingerprint: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'put';
|
|
7
|
+
key: TKey;
|
|
8
|
+
value: TValue;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'putMany';
|
|
11
|
+
entries: readonly {
|
|
12
|
+
key: TKey;
|
|
13
|
+
value: TValue;
|
|
14
|
+
}[];
|
|
15
|
+
} | {
|
|
16
|
+
type: 'remove';
|
|
17
|
+
key: TKey;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'removeById';
|
|
20
|
+
entryId: EntryId;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'updateById';
|
|
23
|
+
entryId: EntryId;
|
|
24
|
+
value: TValue;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'popFirst';
|
|
27
|
+
} | {
|
|
28
|
+
type: 'popLast';
|
|
29
|
+
} | {
|
|
30
|
+
type: 'deleteRange';
|
|
31
|
+
startKey: TKey;
|
|
32
|
+
endKey: TKey;
|
|
33
|
+
options?: RangeBounds;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'clear';
|
|
36
|
+
};
|
|
37
|
+
export interface SharedTreeLog<TKey, TValue> {
|
|
38
|
+
version: bigint;
|
|
39
|
+
mutations: BTreeMutation<TKey, TValue>[];
|
|
40
|
+
}
|
|
41
|
+
export interface SharedTreeStore<TKey, TValue> {
|
|
42
|
+
getLogEntriesSince(version: bigint): Promise<SharedTreeLog<TKey, TValue>>;
|
|
43
|
+
append(expectedVersion: bigint, mutations: BTreeMutation<TKey, TValue>[]): Promise<{
|
|
44
|
+
applied: boolean;
|
|
45
|
+
version: bigint;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
export type ReadMode = 'strong' | 'local';
|
|
49
|
+
export interface ConcurrentInMemoryBTreeConfig<TKey, TValue> extends InMemoryBTreeConfig<TKey> {
|
|
50
|
+
store: SharedTreeStore<TKey, TValue>;
|
|
51
|
+
maxRetries?: number;
|
|
52
|
+
maxSyncMutationsPerBatch?: number;
|
|
53
|
+
readMode?: ReadMode;
|
|
54
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { InMemoryBTree, type EntryId, type RangeBounds } from '../InMemoryBTree.cjs';
|
|
2
|
+
import type { KeyComparator, DuplicateKeyPolicy } from '../btree/types.cjs';
|
|
3
|
+
import type { BTreeMutation } from './types.cjs';
|
|
4
|
+
import { type AnyMutationResult } from './helpers.cjs';
|
|
5
|
+
export declare const applyMutationLocal: <TKey, TValue>(tree: InMemoryBTree<TKey, TValue>, mutation: BTreeMutation<TKey, TValue>, onInit: () => void) => AnyMutationResult<TKey, TValue>;
|
|
6
|
+
export declare const createPutEvaluator: <TKey, TValue>(duplicateKeys: DuplicateKeyPolicy, key: TKey, value: TValue) => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
7
|
+
type: "put";
|
|
8
|
+
key: TKey;
|
|
9
|
+
value: TValue;
|
|
10
|
+
});
|
|
11
|
+
export declare const createRemoveEvaluator: <TKey, TValue>(key: TKey) => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
12
|
+
type: "remove";
|
|
13
|
+
key: TKey;
|
|
14
|
+
} | null);
|
|
15
|
+
export declare const createRemoveByIdEvaluator: <TKey, TValue>(entryId: EntryId) => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
16
|
+
type: "removeById";
|
|
17
|
+
entryId: EntryId;
|
|
18
|
+
} | null);
|
|
19
|
+
export declare const createUpdateByIdEvaluator: <TKey, TValue>(entryId: EntryId, value: TValue) => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
20
|
+
type: "updateById";
|
|
21
|
+
entryId: EntryId;
|
|
22
|
+
value: TValue;
|
|
23
|
+
} | null);
|
|
24
|
+
export declare const createPopFirstEvaluator: <TKey, TValue>() => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
25
|
+
type: "popFirst";
|
|
26
|
+
} | null);
|
|
27
|
+
export declare const createPopLastEvaluator: <TKey, TValue>() => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
28
|
+
type: "popLast";
|
|
29
|
+
} | null);
|
|
30
|
+
export declare const createPutManyEvaluator: <TKey, TValue>(entries: readonly {
|
|
31
|
+
key: TKey;
|
|
32
|
+
value: TValue;
|
|
33
|
+
}[], duplicateKeys: DuplicateKeyPolicy, compareKeys: KeyComparator<TKey>) => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
34
|
+
type: "putMany";
|
|
35
|
+
entries: readonly {
|
|
36
|
+
key: TKey;
|
|
37
|
+
value: TValue;
|
|
38
|
+
}[];
|
|
39
|
+
});
|
|
40
|
+
export declare const createDeleteRangeEvaluator: <TKey, TValue>(startKey: TKey, endKey: TKey, options: RangeBounds | undefined) => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
41
|
+
type: "deleteRange";
|
|
42
|
+
startKey: TKey;
|
|
43
|
+
endKey: TKey;
|
|
44
|
+
options?: RangeBounds;
|
|
45
|
+
} | null);
|
|
46
|
+
export declare const createClearEvaluator: <TKey, TValue>() => ((tree: InMemoryBTree<TKey, TValue>) => {
|
|
47
|
+
type: "clear";
|
|
48
|
+
});
|
package/dist/core.cjs
CHANGED
|
@@ -827,8 +827,10 @@ var tryBorrowFromLeafSibling = (state, leaf, parent, leafIndex, leftSibling, rig
|
|
|
827
827
|
const borrowed = leafShiftEntry(rightSibling);
|
|
828
828
|
if (borrowed === void 0)
|
|
829
829
|
throw new BTreeInvariantError("right leaf borrow failed");
|
|
830
|
+
const leafWasEmpty = leafEntryCount(leaf) === 0;
|
|
830
831
|
leaf.entries.push(borrowed);
|
|
831
832
|
writeMinKeyTo(rightSibling, parent.keys[leafIndex + 1]);
|
|
833
|
+
if (leafWasEmpty) updateMinKeyInAncestors(leaf);
|
|
832
834
|
return true;
|
|
833
835
|
}
|
|
834
836
|
if (leftSibling !== null && leafEntryCount(leftSibling) > state.minLeafEntries) {
|
|
@@ -851,8 +853,10 @@ var mergeLeafWithSibling = (state, leaf, parent, leafIndex, leftSibling, rightSi
|
|
|
851
853
|
return;
|
|
852
854
|
}
|
|
853
855
|
if (rightSibling !== null) {
|
|
856
|
+
const leafWasEmpty = leafEntryCount(leaf) === 0;
|
|
854
857
|
mergeLeafEntries(leaf, rightSibling);
|
|
855
858
|
detachLeafFromChain(state, rightSibling);
|
|
859
|
+
if (leafWasEmpty) updateMinKeyInAncestors(leaf);
|
|
856
860
|
removeChildFromBranch(parent, leafIndex + 1);
|
|
857
861
|
rebalanceAfterBranchRemoval(state, parent);
|
|
858
862
|
return;
|
|
@@ -923,9 +927,6 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount) => {
|
|
|
923
927
|
break;
|
|
924
928
|
safetyGuard -= 1;
|
|
925
929
|
}
|
|
926
|
-
if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
|
|
927
|
-
updateMinKeyInAncestors(leaf);
|
|
928
|
-
}
|
|
929
930
|
return countAfterSplice;
|
|
930
931
|
};
|
|
931
932
|
var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
|
package/dist/core.d.cts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { InMemoryBTree } from './InMemoryBTree.cjs';
|
|
2
|
+
export type { BTreeEntry, BTreeJSON, BTreeStats, DeleteRebalancePolicy, DuplicateKeyPolicy, EntryId, InMemoryBTreeConfig, RangeBounds, } from './InMemoryBTree.cjs';
|
|
3
|
+
export { BTreeInvariantError, BTreeValidationError } from './errors.cjs';
|
|
4
|
+
export type { KeyComparator } from './btree/types.cjs';
|
package/dist/core.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Thrown when a caller supplies invalid input, such as a duplicate key, sequence overflow, or malformed configuration. */
|
|
2
|
+
export declare class BTreeValidationError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** Thrown when an internal tree invariant is violated, indicating a bug in the library rather than invalid caller input. */
|
|
6
|
+
export declare class BTreeInvariantError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
/** Thrown when the concurrent store contract is violated or a mutation batch is malformed during optimistic concurrency operations. */
|
|
10
|
+
export declare class BTreeConcurrencyError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|