@frostpillar/frostpillar-btree 0.2.8 → 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 CHANGED
@@ -359,6 +359,8 @@ tree.getPairOrNextLower(10); // { entryId: ..., key: 10, value: 'a' }(完全
359
359
 
360
360
  #### イテレーション
361
361
 
362
+ > **注意:** イテレータはライブなツリーを読み取ります。反復中(`entries()`、`entriesReversed()`、`keys()`、`values()`、`forEach`、`for...of`)にツリーを変更した場合、反復中エントリの包含や訪問順序は実装依存です。反復しながら変更する必要がある場合は、先に `snapshot()` を取得してください。
363
+
362
364
  **`entries()`** -- スナップショット配列を生成せず、昇順でエントリを遅延イテレーションします。
363
365
 
364
366
  ```ts
@@ -481,6 +483,24 @@ const tree = new InMemoryBTree<number, string>({
481
483
  | `'reject'` | キーが既に存在する場合、`BTreeValidationError` をスローする。 | 一意インデックス / セット |
482
484
  | `'allow'` | 同一キーの複数エントリを許可し、挿入順で並べる。 | マルチマップ / イベントログ / 優先度キュー |
483
485
 
486
+ #### 削除リバランスポリシー
487
+
488
+ `deleteRebalancePolicy` オプションで、削除時のリーフのリバランス頻度を制御できます。
489
+
490
+ ```ts
491
+ const tree = new InMemoryBTree<number, string>({
492
+ compareKeys: (a, b) => a - b,
493
+ deleteRebalancePolicy: 'lazy', // デフォルトは 'standard'
494
+ });
495
+ ```
496
+
497
+ | ポリシー | 動作 | 用途 |
498
+ | -------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------- |
499
+ | `'standard'`(デフォルト) | 削除でリーフが最小占有数(`ceil(maxLeafEntries / 2)`)を下回った時点でリバランスする。 | 読み書きバランス型ワークロード |
500
+ | `'lazy'` | リーフが `max(1, ceil(最小占有数 / 4))` を下回るまでリバランスを遅延し、大量削除時のマージ回数を削減する。 | 大量削除ワークロード、`autoScale` 併用 |
501
+
502
+ 緩和されたしきい値はリーフのリバランス判定にのみ適用され、ブランチのリバランスは常に標準のしきい値を使用します。ポリシーが `'lazy'` の場合、`assertInvariants()` は緩和されたしきい値を満たすリーフを許容します。ポリシーは `clone()` と `toJSON()` / `fromJSON()` で維持されます。
503
+
484
504
  #### 動作に関する注意事項
485
505
 
486
506
  - `range(start, end)` はデフォルトで両端を含みます。`RangeBounds` で除外境界を指定できます。`start > end` の場合は `[]` を返します。
@@ -499,7 +519,7 @@ const tree = new InMemoryBTree<number, string>({
499
519
  | 1,000,000+ | 512 | 256 |
500
520
 
501
521
  - `autoScale` は `maxLeafEntries` / `maxBranchChildren` の明示指定と同時には使えません。
502
- - `fromJSON` は `1,000,000` 件を超えるエントリを含むペイロードを拒否します。
522
+ - `fromJSON` は `1,000,000` 件を超えるエントリを含むペイロードを拒否します。`toJSON` はこの上限を強制しません。上限を超えるツリーもシリアライズ自体は成功しますが、生成されたペイロードは `fromJSON` で再インポートできません。大きなデータセットはエクスポート前に分割してください。
503
523
 
504
524
  ---
505
525
 
@@ -651,6 +671,18 @@ await localInstance.sync(); // 明示的に最新状態を取得
651
671
  const value = await localInstance.get(1);
652
672
  ```
653
673
 
674
+ **`syncThenRead(fn)`** -- 読み取り中心のバッチ処理向けに、1 回の同期の後、単一の排他ロック内でローカルツリーに対して複数の読み取りを実行します。読み取りごとにストアへの往復コストを払う必要がなくなります。`readMode` が `'strong'` / `'local'` のどちらでも動作します(常に先に同期します)。コールバック内でツリーを変更しないでください。
675
+
676
+ ```ts
677
+ const total = await instanceA.syncThenRead((tree) => {
678
+ let sum = 0;
679
+ tree.forEachRange(0, 100, (entry) => {
680
+ sum += entry.value.length;
681
+ });
682
+ return sum;
683
+ });
684
+ ```
685
+
654
686
  ```ts
655
687
  // インスタンス A が挿入
656
688
  const insertedId = await instanceA.put(100, 'draft docs');
@@ -847,44 +879,45 @@ new InMemoryBTree<TKey, TValue>(config: InMemoryBTreeConfig<TKey>)
847
879
 
848
880
  `InMemoryBTree` メソッドを `Promise` を返す非同期版として提供します。書き込みは shared store を介して協調し、`readMode` が `'strong'`(デフォルト)の場合は読み取り前に同期します。`readMode` が `'local'` の場合、読み取りは同期なしでローカルツリーに対して実行されます。
849
881
 
850
- | メソッド | シグネチャ | 説明 |
851
- | ------------------------ | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
852
- | `sync` | `() => Promise<void>` | shared store の最新ログを取得して適用する。 |
853
- | `put` | `(key: TKey, value: TValue) => Promise<EntryId>` | 楽観的並行制御で挿入する。 |
854
- | `putMany` | `(entries: readonly { key: TKey; value: TValue }[]) => Promise<EntryId[]>` | 楽観的並行制御で一括挿入する。 |
855
- | `remove` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | 指定キーに一致する最初のエントリを削除する。 |
856
- | `removeById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | ID でエントリを削除する。 |
857
- | `peekById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | ID でエントリを参照する(事前に同期)。 |
858
- | `updateById` | `(entryId: EntryId, value: TValue) => Promise<BTreeEntry<TKey, TValue> \| null>` | 楽観的並行制御で ID のエントリ値を更新する。 |
859
- | `popFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最小キーのエントリを削除して返す。 |
860
- | `popLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最大キーのエントリを削除して返す。 |
861
- | `deleteRange` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | 楽観的並行制御で範囲内のエントリを削除する。 |
862
- | `clear` | `() => Promise<void>` | 楽観的並行制御で全エントリを削除する。 |
863
- | `peekFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最小キーのエントリを返す(事前に同期)。 |
864
- | `peekLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最大キーのエントリを返す(事前に同期)。 |
865
- | `findFirst` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | キーに一致する最初のエントリを返す(事前に同期)。 |
866
- | `findLast` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | キーに一致する最後のエントリを返す(事前に同期)。 |
867
- | `get` | `(key: TKey) => Promise<TValue \| null>` | キーの値を取得する(事前に同期)。 |
868
- | `hasKey` | `(key: TKey) => Promise<boolean>` | キーの存在を確認する(事前に同期)。 |
869
- | `count` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | 範囲内のエントリ数を返す(事前に同期)。 |
870
- | `range` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<BTreeEntry<TKey, TValue>[]>` | 範囲クエリ(事前に同期)。 |
871
- | `nextHigherKey` | `(key: TKey) => Promise<TKey \| null>` | 指定キーより大きい次のキー(事前に同期)。 |
872
- | `nextLowerKey` | `(key: TKey) => Promise<TKey \| null>` | 指定キーより小さい次のキー(事前に同期)。 |
873
- | `getPairOrNextLower` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | 一致または次に小さいエントリ(事前に同期)。 |
874
- | `entries` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを配列で返す(事前に同期)。 |
875
- | `entriesReversed` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを逆順で配列として返す(事前に同期)。 |
876
- | `keys` | `() => Promise<TKey[]>` | 全キーを配列で返す(事前に同期)。 |
877
- | `values` | `() => Promise<TValue[]>` | 全値を配列で返す(事前に同期)。 |
878
- | `forEach` | `(callback: (entry: BTreeEntry<TKey, TValue>) => void) => Promise<void>` | 全エントリを反復する(事前に同期)。 |
879
- | `forEachRange` | `(startKey, endKey, callback, options?) => Promise<void>` | 範囲内のエントリを反復する(事前に同期)。 |
880
- | `snapshot` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを返す(事前に同期)。 |
881
- | `size` | `() => Promise<number>` | エントリ数を返す(事前に同期)。 |
882
- | `getStats` | `() => Promise<BTreeStats>` | 構造統計を返す(事前に同期)。 |
883
- | `assertInvariants` | `() => Promise<void>` | 構造的な整合性を検証する(事前に同期)。 |
884
- | `clone` | `() => Promise<InMemoryBTree<TKey, TValue>>` | 独立したローカルコピーを返す(事前に同期)。 |
885
- | `toJSON` | `() => Promise<BTreeJSON<TKey, TValue>>` | JSON にシリアライズする(事前に同期)。 |
886
- | `fromJSON` (static) | `(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>) => InMemoryBTree<TKey, TValue>` | JSON からデシリアライズする(ローカルツリーを返す)。 |
887
- | `[Symbol.asyncIterator]` | `() => AsyncIterableIterator<BTreeEntry<TKey, TValue>>` | 全エントリを非同期反復する(事前に同期)。 |
882
+ | メソッド | シグネチャ | 説明 |
883
+ | ------------------------ | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
884
+ | `sync` | `() => Promise<void>` | shared store の最新ログを取得して適用する。 |
885
+ | `syncThenRead` | `<TResult>(fn: (tree: InMemoryBTree<TKey, TValue>) => TResult) => Promise<TResult>` | 1 回同期した後、単一の排他ロック内でローカルツリーに対して `fn` を実行する。 |
886
+ | `put` | `(key: TKey, value: TValue) => Promise<EntryId>` | 楽観的並行制御で挿入する。 |
887
+ | `putMany` | `(entries: readonly { key: TKey; value: TValue }[]) => Promise<EntryId[]>` | 楽観的並行制御で一括挿入する。 |
888
+ | `remove` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | 指定キーに一致する最初のエントリを削除する。 |
889
+ | `removeById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | ID でエントリを削除する。 |
890
+ | `peekById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | ID でエントリを参照する(事前に同期)。 |
891
+ | `updateById` | `(entryId: EntryId, value: TValue) => Promise<BTreeEntry<TKey, TValue> \| null>` | 楽観的並行制御で ID のエントリ値を更新する。 |
892
+ | `popFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最小キーのエントリを削除して返す。 |
893
+ | `popLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最大キーのエントリを削除して返す。 |
894
+ | `deleteRange` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | 楽観的並行制御で範囲内のエントリを削除する。 |
895
+ | `clear` | `() => Promise<void>` | 楽観的並行制御で全エントリを削除する。 |
896
+ | `peekFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最小キーのエントリを返す(事前に同期)。 |
897
+ | `peekLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最大キーのエントリを返す(事前に同期)。 |
898
+ | `findFirst` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | キーに一致する最初のエントリを返す(事前に同期)。 |
899
+ | `findLast` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | キーに一致する最後のエントリを返す(事前に同期)。 |
900
+ | `get` | `(key: TKey) => Promise<TValue \| null>` | キーの値を取得する(事前に同期)。 |
901
+ | `hasKey` | `(key: TKey) => Promise<boolean>` | キーの存在を確認する(事前に同期)。 |
902
+ | `count` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | 範囲内のエントリ数を返す(事前に同期)。 |
903
+ | `range` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<BTreeEntry<TKey, TValue>[]>` | 範囲クエリ(事前に同期)。 |
904
+ | `nextHigherKey` | `(key: TKey) => Promise<TKey \| null>` | 指定キーより大きい次のキー(事前に同期)。 |
905
+ | `nextLowerKey` | `(key: TKey) => Promise<TKey \| null>` | 指定キーより小さい次のキー(事前に同期)。 |
906
+ | `getPairOrNextLower` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | 一致または次に小さいエントリ(事前に同期)。 |
907
+ | `entries` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを配列で返す(事前に同期)。 |
908
+ | `entriesReversed` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを逆順で配列として返す(事前に同期)。 |
909
+ | `keys` | `() => Promise<TKey[]>` | 全キーを配列で返す(事前に同期)。 |
910
+ | `values` | `() => Promise<TValue[]>` | 全値を配列で返す(事前に同期)。 |
911
+ | `forEach` | `(callback: (entry: BTreeEntry<TKey, TValue>) => void) => Promise<void>` | 全エントリを反復する(事前に同期)。 |
912
+ | `forEachRange` | `(startKey, endKey, callback, options?) => Promise<void>` | 範囲内のエントリを反復する(事前に同期)。 |
913
+ | `snapshot` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを返す(事前に同期)。 |
914
+ | `size` | `() => Promise<number>` | エントリ数を返す(事前に同期)。 |
915
+ | `getStats` | `() => Promise<BTreeStats>` | 構造統計を返す(事前に同期)。 |
916
+ | `assertInvariants` | `() => Promise<void>` | 構造的な整合性を検証する(事前に同期)。 |
917
+ | `clone` | `() => Promise<InMemoryBTree<TKey, TValue>>` | 独立したローカルコピーを返す(事前に同期)。 |
918
+ | `toJSON` | `() => Promise<BTreeJSON<TKey, TValue>>` | JSON にシリアライズする(事前に同期)。 |
919
+ | `fromJSON` (static) | `(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>) => InMemoryBTree<TKey, TValue>` | JSON からデシリアライズする(ローカルツリーを返す)。 |
920
+ | `[Symbol.asyncIterator]` | `() => AsyncIterableIterator<BTreeEntry<TKey, TValue>>` | 全エントリを非同期反復する(事前に同期)。 |
888
921
 
889
922
  **コンストラクタ:**
890
923
 
@@ -894,26 +927,27 @@ new ConcurrentInMemoryBTree<TKey, TValue>(config: ConcurrentInMemoryBTreeConfig<
894
927
 
895
928
  ### エクスポートされる型
896
929
 
897
- | 型 | 説明 |
898
- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
899
- | `EntryId` | エントリを識別するブランド型 `number`。 |
900
- | `BTreeEntry<TKey, TValue>` | `{ entryId: EntryId; key: TKey; value: TValue }` |
901
- | `BTreeJSON<TKey, TValue>` | `toJSON()` が生成し `fromJSON()` が受け取る、バージョン付き JSON シリアライズ可能なペイロード。 |
902
- | `BTreeStats` | `{ height: number; leafCount: number; branchCount: number; entryCount: number }` |
903
- | `KeyComparator<TKey>` | `(left: TKey, right: TKey) => number` |
904
- | `DuplicateKeyPolicy` | `'allow' \| 'reject' \| 'replace'` |
905
- | `RangeBounds` | `{ lowerBound?: 'inclusive' \| 'exclusive'; upperBound?: 'inclusive' \| 'exclusive' }` |
906
- | `InMemoryBTreeConfig<TKey>` | `{ compareKeys: KeyComparator<TKey>; maxLeafEntries?: number; maxBranchChildren?: number; duplicateKeys?: DuplicateKeyPolicy; enableEntryIdLookup?: boolean; autoScale?: boolean }` |
907
- | `ReadMode` | `'strong' \| 'local'` |
908
- | `ConcurrentInMemoryBTreeConfig<TKey, TValue>` | `InMemoryBTreeConfig<TKey>` を拡張し、`store: SharedTreeStore<TKey, TValue>`、`maxRetries?: number`、`maxSyncMutationsPerBatch?: number`、`readMode?: ReadMode` を追加。 |
909
- | `SharedTreeStore<TKey, TValue>` | `getLogEntriesSince(version)` `append(expectedVersion, mutations)` を持つインターフェース。 |
910
- | `SharedTreeLog<TKey, TValue>` | `{ version: bigint; mutations: BTreeMutation<TKey, TValue>[] }` |
911
- | `BTreeMutation<TKey, TValue>` | 判別共用体: `init`、`put`、`putMany`、`remove`、`removeById`、`updateById`、`popFirst`、`popLast`、`deleteRange`、`clear`。 |
912
- | `BTreeValidationError` | コンパレータや設定の違反でスローされるエラー。 |
913
- | `BTreeInvariantError` | ツリー構造の整合性違反でスローされるエラー。 |
914
- | `BTreeConcurrencyError` | 並行処理コンフリクトやストア契約違反でスローされるエラー。 |
915
-
916
- > **サブパスエクスポート:** `/core` サブパス(`@frostpillar/frostpillar-btree/core`)は単一プロセス向けの型のみエクスポートします: `InMemoryBTree`、`EntryId`、`BTreeEntry`、`BTreeJSON`、`BTreeStats`、`KeyComparator`、`DuplicateKeyPolicy`、`RangeBounds`、`InMemoryBTreeConfig`、`BTreeValidationError`、`BTreeInvariantError`。並行処理関連のエクスポート(`ConcurrentInMemoryBTree`、`ConcurrentInMemoryBTreeConfig`、`ReadMode`、`SharedTreeStore`、`SharedTreeLog`、`BTreeMutation`、`BTreeConcurrencyError`)はメインエントリポイントからのみ利用できます。
930
+ | 型 | 説明 |
931
+ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
932
+ | `EntryId` | エントリを識別するブランド型 `number`。 |
933
+ | `BTreeEntry<TKey, TValue>` | `{ entryId: EntryId; key: TKey; value: TValue }` |
934
+ | `BTreeJSON<TKey, TValue>` | `toJSON()` が生成し `fromJSON()` が受け取る、バージョン付き JSON シリアライズ可能なペイロード。 |
935
+ | `BTreeStats` | `{ height: number; leafCount: number; branchCount: number; entryCount: number }` |
936
+ | `KeyComparator<TKey>` | `(left: TKey, right: TKey) => number` |
937
+ | `DuplicateKeyPolicy` | `'allow' \| 'reject' \| 'replace'` |
938
+ | `DeleteRebalancePolicy` | `'standard' \| 'lazy'` |
939
+ | `RangeBounds` | `{ lowerBound?: 'inclusive' \| 'exclusive'; upperBound?: 'inclusive' \| 'exclusive' }` |
940
+ | `InMemoryBTreeConfig<TKey>` | `{ compareKeys: KeyComparator<TKey>; maxLeafEntries?: number; maxBranchChildren?: number; duplicateKeys?: DuplicateKeyPolicy; enableEntryIdLookup?: boolean; autoScale?: boolean; deleteRebalancePolicy?: DeleteRebalancePolicy }` |
941
+ | `ReadMode` | `'strong' \| 'local'` |
942
+ | `ConcurrentInMemoryBTreeConfig<TKey, TValue>` | `InMemoryBTreeConfig<TKey>` を拡張し、`store: SharedTreeStore<TKey, TValue>`、`maxRetries?: number`、`maxSyncMutationsPerBatch?: number`、`readMode?: ReadMode` を追加。 |
943
+ | `SharedTreeStore<TKey, TValue>` | `getLogEntriesSince(version)` `append(expectedVersion, mutations)` を持つインターフェース。 |
944
+ | `SharedTreeLog<TKey, TValue>` | `{ version: bigint; mutations: BTreeMutation<TKey, TValue>[] }` |
945
+ | `BTreeMutation<TKey, TValue>` | 判別共用体: `init`、`put`、`putMany`、`remove`、`removeById`、`updateById`、`popFirst`、`popLast`、`deleteRange`、`clear`。 |
946
+ | `BTreeValidationError` | コンパレータや設定の違反でスローされるエラー。 |
947
+ | `BTreeInvariantError` | ツリー構造の整合性違反でスローされるエラー。 |
948
+ | `BTreeConcurrencyError` | 並行処理コンフリクトやストア契約違反でスローされるエラー。 |
949
+
950
+ > **サブパスエクスポート:** `/core` サブパス(`@frostpillar/frostpillar-btree/core`)は単一プロセス向けの型のみエクスポートします: `InMemoryBTree`、`EntryId`、`BTreeEntry`、`BTreeJSON`、`BTreeStats`、`KeyComparator`、`DuplicateKeyPolicy`、`DeleteRebalancePolicy`、`RangeBounds`、`InMemoryBTreeConfig`、`BTreeValidationError`、`BTreeInvariantError`。並行処理関連のエクスポート(`ConcurrentInMemoryBTree`、`ConcurrentInMemoryBTreeConfig`、`ReadMode`、`SharedTreeStore`、`SharedTreeLog`、`BTreeMutation`、`BTreeConcurrencyError`)はメインエントリポイントからのみ利用できます。
917
951
 
918
952
  ---
919
953
 
package/README.md CHANGED
@@ -359,6 +359,8 @@ tree.getPairOrNextLower(10); // { entryId: ..., key: 10, value: 'a' } (exact mat
359
359
 
360
360
  #### Iterating
361
361
 
362
+ > **Note:** Iterators read the live tree. If you mutate the tree during traversal (`entries()`, `entriesReversed()`, `keys()`, `values()`, `forEach`, `for...of`), inclusion and visitation order of in-flight entries are implementation-defined. Take a `snapshot()` first when you need to mutate while traversing.
363
+
362
364
  **`entries()`** -- lazily iterate all entries in ascending key order without allocating a snapshot array:
363
365
 
364
366
  ```ts
@@ -481,6 +483,24 @@ const tree = new InMemoryBTree<number, string>({
481
483
  | `'reject'` | Throws `BTreeValidationError` if the key already exists. | Unique index / set |
482
484
  | `'allow'` | Allows multiple entries with the same key, ordered by insertion time. | Multimap / event log / priority queue |
483
485
 
486
+ #### Delete Rebalance Policy
487
+
488
+ Control how aggressively delete operations rebalance leaves via the `deleteRebalancePolicy` option:
489
+
490
+ ```ts
491
+ const tree = new InMemoryBTree<number, string>({
492
+ compareKeys: (a, b) => a - b,
493
+ deleteRebalancePolicy: 'lazy', // default: 'standard'
494
+ });
495
+ ```
496
+
497
+ | Policy | Behavior | Use case |
498
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ |
499
+ | `'standard'` (default) | Rebalances a leaf as soon as a delete drops it below the minimum occupancy (`ceil(maxLeafEntries / 2)`). | Balanced read/write workloads |
500
+ | `'lazy'` | Delays rebalancing until a leaf falls below `max(1, ceil(minimum occupancy / 4))`, reducing merge churn during mass deletions. | Mass-deletion workloads, `autoScale` |
501
+
502
+ The relaxed threshold applies to the leaf rebalance decision only; branch rebalancing always uses the standard threshold. `assertInvariants()` accepts leaves that satisfy the relaxed threshold when the policy is `'lazy'`. The policy is preserved by `clone()` and `toJSON()` / `fromJSON()`.
503
+
484
504
  #### Behavior Notes
485
505
 
486
506
  - `range(start, end)` is inclusive on both bounds by default. Pass `RangeBounds` to use exclusive bounds. Returns `[]` when `start > end`.
@@ -499,7 +519,7 @@ const tree = new InMemoryBTree<number, string>({
499
519
  | 1,000,000+ | 512 | 256 |
500
520
 
501
521
  - `autoScale` cannot be combined with explicit `maxLeafEntries` or `maxBranchChildren`.
502
- - `fromJSON` rejects payloads with more than `1,000,000` entries.
522
+ - `fromJSON` rejects payloads with more than `1,000,000` entries. `toJSON` does not enforce this cap: a tree holding more entries serializes successfully, but the resulting payload cannot be re-imported via `fromJSON`. Split larger datasets before export.
503
523
 
504
524
  ---
505
525
 
@@ -651,6 +671,18 @@ await localInstance.sync(); // explicitly pull latest state
651
671
  const value = await localInstance.get(1);
652
672
  ```
653
673
 
674
+ **`syncThenRead(fn)`** -- for read-heavy batches, sync once and run many reads against the local tree within a single exclusive lock, instead of paying a store round-trip per read. It works in both `'strong'` and `'local'` read modes (it always syncs first). Do not mutate the tree inside the callback:
675
+
676
+ ```ts
677
+ const total = await instanceA.syncThenRead((tree) => {
678
+ let sum = 0;
679
+ tree.forEachRange(0, 100, (entry) => {
680
+ sum += entry.value.length;
681
+ });
682
+ return sum;
683
+ });
684
+ ```
685
+
654
686
  ```ts
655
687
  // Instance A inserts
656
688
  const insertedId = await instanceA.put(100, 'draft docs');
@@ -847,44 +879,45 @@ new InMemoryBTree<TKey, TValue>(config: InMemoryBTreeConfig<TKey>)
847
879
 
848
880
  Exposes `InMemoryBTree` methods as async equivalents returning `Promise`. Writes coordinate through the shared store; reads sync before returning when `readMode` is `'strong'` (the default). When `readMode` is `'local'`, reads execute against the local tree without syncing.
849
881
 
850
- | Method | Signature | Description |
851
- | ------------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
852
- | `sync` | `() => Promise<void>` | Fetch and apply the latest log entries from the shared store. |
853
- | `put` | `(key: TKey, value: TValue) => Promise<EntryId>` | Insert with optimistic concurrency. |
854
- | `putMany` | `(entries: readonly { key: TKey; value: TValue }[]) => Promise<EntryId[]>` | Batch insert with optimistic concurrency. |
855
- | `remove` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove the first matching entry by key. |
856
- | `removeById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove a specific entry by ID. |
857
- | `peekById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | Look up an entry by ID (syncs first). |
858
- | `updateById` | `(entryId: EntryId, value: TValue) => Promise<BTreeEntry<TKey, TValue> \| null>` | Update an entry by ID with optimistic concurrency. |
859
- | `popFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove and return the smallest entry. |
860
- | `popLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove and return the largest entry. |
861
- | `deleteRange` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | Delete entries in range with optimistic concurrency. |
862
- | `clear` | `() => Promise<void>` | Remove all entries with optimistic concurrency. |
863
- | `peekFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the smallest entry (syncs first). |
864
- | `peekLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the largest entry (syncs first). |
865
- | `findFirst` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the first entry matching key (syncs first). |
866
- | `findLast` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the last entry matching key (syncs first). |
867
- | `get` | `(key: TKey) => Promise<TValue \| null>` | Return value by key (syncs first). |
868
- | `hasKey` | `(key: TKey) => Promise<boolean>` | Check key existence (syncs first). |
869
- | `count` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | Count entries in range (syncs first). |
870
- | `range` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<BTreeEntry<TKey, TValue>[]>` | Range query (syncs first). |
871
- | `nextHigherKey` | `(key: TKey) => Promise<TKey \| null>` | Next key strictly greater (syncs first). |
872
- | `nextLowerKey` | `(key: TKey) => Promise<TKey \| null>` | Next key strictly less (syncs first). |
873
- | `getPairOrNextLower` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Exact match or next lower (syncs first). |
874
- | `entries` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | Return all entries as array (syncs first). |
875
- | `entriesReversed` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | Return all entries in reverse as array (syncs first). |
876
- | `keys` | `() => Promise<TKey[]>` | Return all keys as array (syncs first). |
877
- | `values` | `() => Promise<TValue[]>` | Return all values as array (syncs first). |
878
- | `forEach` | `(callback: (entry: BTreeEntry<TKey, TValue>) => void) => Promise<void>` | Iterate all entries (syncs first). |
879
- | `forEachRange` | `(startKey, endKey, callback, options?) => Promise<void>` | Iterate entries in range (syncs first). |
880
- | `snapshot` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | Return all entries (syncs first). |
881
- | `size` | `() => Promise<number>` | Return entry count (syncs first). |
882
- | `getStats` | `() => Promise<BTreeStats>` | Return structural statistics (syncs first). |
883
- | `assertInvariants` | `() => Promise<void>` | Assert structural integrity (syncs first). |
884
- | `clone` | `() => Promise<InMemoryBTree<TKey, TValue>>` | Return an independent local copy (syncs first). |
885
- | `toJSON` | `() => Promise<BTreeJSON<TKey, TValue>>` | Serialize to JSON (syncs first). |
886
- | `fromJSON` (static) | `(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>) => InMemoryBTree<TKey, TValue>` | Deserialize from JSON (returns local tree). |
887
- | `[Symbol.asyncIterator]` | `() => AsyncIterableIterator<BTreeEntry<TKey, TValue>>` | Async iteration over all entries (syncs first). |
882
+ | Method | Signature | Description |
883
+ | ------------------------ | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
884
+ | `sync` | `() => Promise<void>` | Fetch and apply the latest log entries from the shared store. |
885
+ | `syncThenRead` | `<TResult>(fn: (tree: InMemoryBTree<TKey, TValue>) => TResult) => Promise<TResult>` | Sync once, then run `fn` against the local tree within one exclusive lock. |
886
+ | `put` | `(key: TKey, value: TValue) => Promise<EntryId>` | Insert with optimistic concurrency. |
887
+ | `putMany` | `(entries: readonly { key: TKey; value: TValue }[]) => Promise<EntryId[]>` | Batch insert with optimistic concurrency. |
888
+ | `remove` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove the first matching entry by key. |
889
+ | `removeById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove a specific entry by ID. |
890
+ | `peekById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | Look up an entry by ID (syncs first). |
891
+ | `updateById` | `(entryId: EntryId, value: TValue) => Promise<BTreeEntry<TKey, TValue> \| null>` | Update an entry by ID with optimistic concurrency. |
892
+ | `popFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove and return the smallest entry. |
893
+ | `popLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Remove and return the largest entry. |
894
+ | `deleteRange` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | Delete entries in range with optimistic concurrency. |
895
+ | `clear` | `() => Promise<void>` | Remove all entries with optimistic concurrency. |
896
+ | `peekFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the smallest entry (syncs first). |
897
+ | `peekLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the largest entry (syncs first). |
898
+ | `findFirst` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the first entry matching key (syncs first). |
899
+ | `findLast` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Return the last entry matching key (syncs first). |
900
+ | `get` | `(key: TKey) => Promise<TValue \| null>` | Return value by key (syncs first). |
901
+ | `hasKey` | `(key: TKey) => Promise<boolean>` | Check key existence (syncs first). |
902
+ | `count` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | Count entries in range (syncs first). |
903
+ | `range` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<BTreeEntry<TKey, TValue>[]>` | Range query (syncs first). |
904
+ | `nextHigherKey` | `(key: TKey) => Promise<TKey \| null>` | Next key strictly greater (syncs first). |
905
+ | `nextLowerKey` | `(key: TKey) => Promise<TKey \| null>` | Next key strictly less (syncs first). |
906
+ | `getPairOrNextLower` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | Exact match or next lower (syncs first). |
907
+ | `entries` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | Return all entries as array (syncs first). |
908
+ | `entriesReversed` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | Return all entries in reverse as array (syncs first). |
909
+ | `keys` | `() => Promise<TKey[]>` | Return all keys as array (syncs first). |
910
+ | `values` | `() => Promise<TValue[]>` | Return all values as array (syncs first). |
911
+ | `forEach` | `(callback: (entry: BTreeEntry<TKey, TValue>) => void) => Promise<void>` | Iterate all entries (syncs first). |
912
+ | `forEachRange` | `(startKey, endKey, callback, options?) => Promise<void>` | Iterate entries in range (syncs first). |
913
+ | `snapshot` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | Return all entries (syncs first). |
914
+ | `size` | `() => Promise<number>` | Return entry count (syncs first). |
915
+ | `getStats` | `() => Promise<BTreeStats>` | Return structural statistics (syncs first). |
916
+ | `assertInvariants` | `() => Promise<void>` | Assert structural integrity (syncs first). |
917
+ | `clone` | `() => Promise<InMemoryBTree<TKey, TValue>>` | Return an independent local copy (syncs first). |
918
+ | `toJSON` | `() => Promise<BTreeJSON<TKey, TValue>>` | Serialize to JSON (syncs first). |
919
+ | `fromJSON` (static) | `(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>) => InMemoryBTree<TKey, TValue>` | Deserialize from JSON (returns local tree). |
920
+ | `[Symbol.asyncIterator]` | `() => AsyncIterableIterator<BTreeEntry<TKey, TValue>>` | Async iteration over all entries (syncs first). |
888
921
 
889
922
  **Constructor:**
890
923
 
@@ -894,26 +927,27 @@ new ConcurrentInMemoryBTree<TKey, TValue>(config: ConcurrentInMemoryBTreeConfig<
894
927
 
895
928
  ### Exported Types
896
929
 
897
- | Type | Description |
898
- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
899
- | `EntryId` | Branded `number` identifying a specific entry. |
900
- | `BTreeEntry<TKey, TValue>` | `{ entryId: EntryId; key: TKey; value: TValue }` |
901
- | `BTreeJSON<TKey, TValue>` | Versioned JSON-serializable payload produced by `toJSON()` and consumed by `fromJSON()`. |
902
- | `BTreeStats` | `{ height: number; leafCount: number; branchCount: number; entryCount: number }` |
903
- | `KeyComparator<TKey>` | `(left: TKey, right: TKey) => number` |
904
- | `DuplicateKeyPolicy` | `'allow' \| 'reject' \| 'replace'` |
905
- | `RangeBounds` | `{ lowerBound?: 'inclusive' \| 'exclusive'; upperBound?: 'inclusive' \| 'exclusive' }` |
906
- | `InMemoryBTreeConfig<TKey>` | `{ compareKeys: KeyComparator<TKey>; maxLeafEntries?: number; maxBranchChildren?: number; duplicateKeys?: DuplicateKeyPolicy; enableEntryIdLookup?: boolean; autoScale?: boolean }` |
907
- | `ReadMode` | `'strong' \| 'local'` |
908
- | `ConcurrentInMemoryBTreeConfig<TKey, TValue>` | Extends `InMemoryBTreeConfig<TKey>` with `store: SharedTreeStore<TKey, TValue>`, `maxRetries?: number`, `maxSyncMutationsPerBatch?: number`, and `readMode?: ReadMode`. |
909
- | `SharedTreeStore<TKey, TValue>` | Interface with `getLogEntriesSince(version)` and `append(expectedVersion, mutations)`. |
910
- | `SharedTreeLog<TKey, TValue>` | `{ version: bigint; mutations: BTreeMutation<TKey, TValue>[] }` |
911
- | `BTreeMutation<TKey, TValue>` | Discriminated union: `init`, `put`, `putMany`, `remove`, `removeById`, `updateById`, `popFirst`, `popLast`, `deleteRange`, `clear`. |
912
- | `BTreeValidationError` | Error thrown for comparator or config violations. |
913
- | `BTreeInvariantError` | Error thrown for tree structural integrity violations. |
914
- | `BTreeConcurrencyError` | Error thrown for concurrency conflicts or store contract violations. |
915
-
916
- > **Subpath exports:** The `/core` subpath (`@frostpillar/frostpillar-btree/core`) exports only single-process types: `InMemoryBTree`, `EntryId`, `BTreeEntry`, `BTreeJSON`, `BTreeStats`, `KeyComparator`, `DuplicateKeyPolicy`, `RangeBounds`, `InMemoryBTreeConfig`, `BTreeValidationError`, and `BTreeInvariantError`. Concurrency-related exports (`ConcurrentInMemoryBTree`, `ConcurrentInMemoryBTreeConfig`, `ReadMode`, `SharedTreeStore`, `SharedTreeLog`, `BTreeMutation`, `BTreeConcurrencyError`) are available only from the main entry point.
930
+ | Type | Description |
931
+ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
932
+ | `EntryId` | Branded `number` identifying a specific entry. |
933
+ | `BTreeEntry<TKey, TValue>` | `{ entryId: EntryId; key: TKey; value: TValue }` |
934
+ | `BTreeJSON<TKey, TValue>` | Versioned JSON-serializable payload produced by `toJSON()` and consumed by `fromJSON()`. |
935
+ | `BTreeStats` | `{ height: number; leafCount: number; branchCount: number; entryCount: number }` |
936
+ | `KeyComparator<TKey>` | `(left: TKey, right: TKey) => number` |
937
+ | `DuplicateKeyPolicy` | `'allow' \| 'reject' \| 'replace'` |
938
+ | `DeleteRebalancePolicy` | `'standard' \| 'lazy'` |
939
+ | `RangeBounds` | `{ lowerBound?: 'inclusive' \| 'exclusive'; upperBound?: 'inclusive' \| 'exclusive' }` |
940
+ | `InMemoryBTreeConfig<TKey>` | `{ compareKeys: KeyComparator<TKey>; maxLeafEntries?: number; maxBranchChildren?: number; duplicateKeys?: DuplicateKeyPolicy; enableEntryIdLookup?: boolean; autoScale?: boolean; deleteRebalancePolicy?: DeleteRebalancePolicy }` |
941
+ | `ReadMode` | `'strong' \| 'local'` |
942
+ | `ConcurrentInMemoryBTreeConfig<TKey, TValue>` | Extends `InMemoryBTreeConfig<TKey>` with `store: SharedTreeStore<TKey, TValue>`, `maxRetries?: number`, `maxSyncMutationsPerBatch?: number`, and `readMode?: ReadMode`. |
943
+ | `SharedTreeStore<TKey, TValue>` | Interface with `getLogEntriesSince(version)` and `append(expectedVersion, mutations)`. |
944
+ | `SharedTreeLog<TKey, TValue>` | `{ version: bigint; mutations: BTreeMutation<TKey, TValue>[] }` |
945
+ | `BTreeMutation<TKey, TValue>` | Discriminated union: `init`, `put`, `putMany`, `remove`, `removeById`, `updateById`, `popFirst`, `popLast`, `deleteRange`, `clear`. |
946
+ | `BTreeValidationError` | Error thrown for comparator or config violations. |
947
+ | `BTreeInvariantError` | Error thrown for tree structural integrity violations. |
948
+ | `BTreeConcurrencyError` | Error thrown for concurrency conflicts or store contract violations. |
949
+
950
+ > **Subpath exports:** The `/core` subpath (`@frostpillar/frostpillar-btree/core`) exports only single-process types: `InMemoryBTree`, `EntryId`, `BTreeEntry`, `BTreeJSON`, `BTreeStats`, `KeyComparator`, `DuplicateKeyPolicy`, `DeleteRebalancePolicy`, `RangeBounds`, `InMemoryBTreeConfig`, `BTreeValidationError`, and `BTreeInvariantError`. Concurrency-related exports (`ConcurrentInMemoryBTree`, `ConcurrentInMemoryBTreeConfig`, `ReadMode`, `SharedTreeStore`, `SharedTreeLog`, `BTreeMutation`, `BTreeConcurrencyError`) are available only from the main entry point.
917
951
 
918
952
  ---
919
953
 
@@ -0,0 +1,45 @@
1
+ import { type BTreeJSON } from './btree/serialization.cjs';
2
+ import { type BTreeEntry, type BTreeStats, type DeleteRebalancePolicy, type DuplicateKeyPolicy, type EntryId, type InMemoryBTreeConfig, type KeyComparator, type RangeBounds } from './btree/types.cjs';
3
+ export type { BTreeEntry, BTreeJSON, BTreeStats, DeleteRebalancePolicy, DuplicateKeyPolicy, EntryId, InMemoryBTreeConfig, RangeBounds, };
4
+ export declare class InMemoryBTree<TKey, TValue> {
5
+ private readonly state;
6
+ constructor(config: InMemoryBTreeConfig<TKey>);
7
+ put(key: TKey, value: TValue): EntryId;
8
+ putMany(entries: readonly {
9
+ key: TKey;
10
+ value: TValue;
11
+ }[]): EntryId[];
12
+ remove(key: TKey): BTreeEntry<TKey, TValue> | null;
13
+ removeById(entryId: EntryId): BTreeEntry<TKey, TValue> | null;
14
+ peekById(entryId: EntryId): BTreeEntry<TKey, TValue> | null;
15
+ updateById(entryId: EntryId, value: TValue): BTreeEntry<TKey, TValue> | null;
16
+ popFirst(): BTreeEntry<TKey, TValue> | null;
17
+ peekFirst(): BTreeEntry<TKey, TValue> | null;
18
+ peekLast(): BTreeEntry<TKey, TValue> | null;
19
+ popLast(): BTreeEntry<TKey, TValue> | null;
20
+ clear(): void;
21
+ get(key: TKey): TValue | null;
22
+ hasKey(key: TKey): boolean;
23
+ findFirst(key: TKey): BTreeEntry<TKey, TValue> | null;
24
+ findLast(key: TKey): BTreeEntry<TKey, TValue> | null;
25
+ nextHigherKey(key: TKey): TKey | null;
26
+ nextLowerKey(key: TKey): TKey | null;
27
+ getPairOrNextLower(key: TKey): BTreeEntry<TKey, TValue> | null;
28
+ count(startKey: TKey, endKey: TKey, options?: RangeBounds): number;
29
+ deleteRange(startKey: TKey, endKey: TKey, options?: RangeBounds): number;
30
+ range(startKey: TKey, endKey: TKey, options?: RangeBounds): BTreeEntry<TKey, TValue>[];
31
+ entries(): IterableIterator<BTreeEntry<TKey, TValue>>;
32
+ entriesReversed(): IterableIterator<BTreeEntry<TKey, TValue>>;
33
+ keys(): IterableIterator<TKey>;
34
+ values(): IterableIterator<TValue>;
35
+ [Symbol.iterator](): IterableIterator<BTreeEntry<TKey, TValue>>;
36
+ forEachRange(startKey: TKey, endKey: TKey, callback: (entry: BTreeEntry<TKey, TValue>) => void, options?: RangeBounds): void;
37
+ forEach(callback: (entry: BTreeEntry<TKey, TValue>) => void, thisArg?: unknown): void;
38
+ snapshot(): BTreeEntry<TKey, TValue>[];
39
+ clone(): InMemoryBTree<TKey, TValue>;
40
+ toJSON(): BTreeJSON<TKey, TValue>;
41
+ static fromJSON<TKey, TValue>(json: BTreeJSON<TKey, TValue>, compareKeys: KeyComparator<TKey>): InMemoryBTree<TKey, TValue>;
42
+ size(): number;
43
+ assertInvariants(): void;
44
+ getStats(): BTreeStats;
45
+ }
@@ -0,0 +1,11 @@
1
+ import { type BTreeState, type InMemoryBTreeConfig } from './types.cjs';
2
+ export declare const minOccupancy: (max: number) => number;
3
+ export declare const computeAutoScaleTier: (entryCount: number) => {
4
+ readonly maxLeaf: number;
5
+ readonly maxBranch: number;
6
+ };
7
+ export declare const computeNextAutoScaleThreshold: (entryCount: number) => number;
8
+ export declare const createInitialState: <TKey, TValue>(config: InMemoryBTreeConfig<TKey>) => BTreeState<TKey, TValue>;
9
+ export declare const maybeAutoScale: <TKey, TValue>(state: BTreeState<TKey, TValue>) => void;
10
+ export declare const applyAutoScaleCapacitySnapshot: <TKey, TValue>(state: BTreeState<TKey, TValue>, maxLeafEntries: number, maxBranchChildren: number) => void;
11
+ export declare const resetAutoScaleToTier0: <TKey, TValue>(state: BTreeState<TKey, TValue>) => void;
@@ -0,0 +1,5 @@
1
+ import { type BTreeState, type EntryId } from './types.cjs';
2
+ export declare const bulkLoadEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>, entries: readonly {
3
+ key: TKey;
4
+ value: TValue;
5
+ }[]) => EntryId[];
@@ -0,0 +1,3 @@
1
+ import type { RangeBounds } from './types.cjs';
2
+ import { type BTreeState } from './types.cjs';
3
+ export declare const deleteRangeEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>, startKey: TKey, endKey: TKey, options?: RangeBounds) => number;
@@ -0,0 +1,8 @@
1
+ import { type BTreeEntry, type BTreeState, type EntryId, type LeafNode } from './types.cjs';
2
+ export declare const findLeafEntryBySequence: <TKey, TValue>(state: BTreeState<TKey, TValue>, userKey: TKey, sequence: number) => {
3
+ leaf: LeafNode<TKey, TValue>;
4
+ index: number;
5
+ } | null;
6
+ export declare const peekEntryById: <TKey, TValue>(state: BTreeState<TKey, TValue>, entryId: EntryId) => BTreeEntry<TKey, TValue> | null;
7
+ export declare const updateEntryById: <TKey, TValue>(state: BTreeState<TKey, TValue>, entryId: EntryId, newValue: TValue) => BTreeEntry<TKey, TValue> | null;
8
+ export declare const removeEntryById: <TKey, TValue>(state: BTreeState<TKey, TValue>, entryId: EntryId) => BTreeEntry<TKey, TValue> | null;
@@ -0,0 +1,6 @@
1
+ import { type BTreeNode, type BTreeState, type KeyComparator, type NodeKey } from './types.cjs';
2
+ export declare const nodeMinKey: <TKey, TValue>(node: BTreeNode<TKey, TValue>) => NodeKey<TKey> | null;
3
+ export declare const compareNodeKeys: <TKey>(comparator: KeyComparator<TKey>, leftKey: TKey, leftSeq: number, rightKey: TKey, rightSeq: number) => number;
4
+ export declare const assertReflexivityAsInvariant: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => void;
5
+ export declare const assertTransitivityAsInvariant: <TKey, TValue>(state: BTreeState<TKey, TValue>, first: TKey, second: TKey, third: TKey) => void;
6
+ export declare const validateLeafLinks: <TKey, TValue>(state: BTreeState<TKey, TValue>, expectedLeafCount: number) => void;
@@ -0,0 +1,2 @@
1
+ import { type BTreeState } from './types.cjs';
2
+ export declare const assertInvariants: <TKey, TValue>(state: BTreeState<TKey, TValue>) => void;
@@ -0,0 +1,10 @@
1
+ import { type BTreeEntry, type BTreeState, type EntryId } from './types.cjs';
2
+ export { peekEntryById, removeEntryById, updateEntryById, } from './entry-lookup.cjs';
3
+ export declare const putEntry: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey, value: TValue) => EntryId;
4
+ export declare const popFirstEntry: <TKey, TValue>(state: BTreeState<TKey, TValue>) => BTreeEntry<TKey, TValue> | null;
5
+ export declare const popLastEntry: <TKey, TValue>(state: BTreeState<TKey, TValue>) => BTreeEntry<TKey, TValue> | null;
6
+ export declare const removeFirstMatchingEntry: <TKey, TValue>(state: BTreeState<TKey, TValue>, key: TKey) => BTreeEntry<TKey, TValue> | null;
7
+ export declare const putManyEntries: <TKey, TValue>(state: BTreeState<TKey, TValue>, entries: readonly {
8
+ key: TKey;
9
+ value: TValue;
10
+ }[]) => EntryId[];
@@ -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,2 @@
1
+ import { type BTreeState, type BTreeStats } from './types.cjs';
2
+ export declare const getStats: <TKey, TValue>(state: BTreeState<TKey, TValue>) => BTreeStats;
@@ -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';
@@ -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,2 @@
1
+ export { ConcurrentInMemoryBTree } from './ConcurrentInMemoryBTree.cjs';
2
+ export type { BTreeMutation, ConcurrentInMemoryBTreeConfig, ReadMode, SharedTreeLog, SharedTreeStore, } from './types.cjs';
@@ -0,0 +1,2 @@
1
+ import type { SharedTreeLog } from './types.cjs';
2
+ export declare const validateSyncLog: <TKey, TValue>(log: SharedTreeLog<TKey, TValue>, maxSyncMutationsPerBatch: number) => void;
@@ -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
+ });
@@ -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';
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ export { InMemoryBTree } from './InMemoryBTree.cjs';
2
+ export type { BTreeEntry, BTreeJSON, BTreeStats, DeleteRebalancePolicy, DuplicateKeyPolicy, EntryId, InMemoryBTreeConfig, RangeBounds, } from './InMemoryBTree.cjs';
3
+ export { ConcurrentInMemoryBTree } from './concurrency/index.cjs';
4
+ export type { BTreeMutation, ConcurrentInMemoryBTreeConfig, ReadMode, SharedTreeLog, SharedTreeStore, } from './concurrency/index.cjs';
5
+ export { BTreeConcurrencyError, BTreeInvariantError, BTreeValidationError, } from './errors.cjs';
6
+ export type { KeyComparator } from './btree/types.cjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frostpillar/frostpillar-btree",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "A tiny, zero-dependency in-memory B+ tree for TypeScript, Node.js, and browser JavaScript.",
5
5
  "type": "module",
6
6
  "author": "Hajime Sano",
@@ -8,14 +8,24 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
14
19
  },
15
20
  "./core": {
16
- "types": "./dist/core.d.ts",
17
- "import": "./dist/core.js",
18
- "require": "./dist/core.cjs"
21
+ "import": {
22
+ "types": "./dist/core.d.ts",
23
+ "default": "./dist/core.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/core.d.cts",
27
+ "default": "./dist/core.cjs"
28
+ }
19
29
  }
20
30
  },
21
31
  "sideEffects": false,
@@ -23,6 +33,7 @@
23
33
  "dist/**/*.js",
24
34
  "dist/**/*.cjs",
25
35
  "dist/**/*.d.ts",
36
+ "dist/**/*.d.cts",
26
37
  "README.md",
27
38
  "README-JA.md",
28
39
  "LICENSE"
@@ -60,10 +71,11 @@
60
71
  "typescript-eslint": "^8.56.1"
61
72
  },
62
73
  "scripts": {
63
- "build": "rm -rf dist && pnpm build:esm && pnpm build:cjs && pnpm build:types",
74
+ "build": "rm -rf dist && pnpm build:esm && pnpm build:cjs && pnpm build:types && pnpm build:types:cjs",
64
75
  "build:esm": "esbuild src/index.ts src/core.ts --bundle --splitting --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.bundle.json --outdir=dist",
65
76
  "build:cjs": "esbuild src/index.ts src/core.ts --bundle --platform=node --format=cjs --target=es2022 --tsconfig=tsconfig.bundle.json --outdir=dist --out-extension:.js=.cjs",
66
77
  "build:types": "tsc --project tsconfig.build.json",
78
+ "build:types:cjs": "node ./scripts/build-cts-types.mjs",
67
79
  "build:bundle": "esbuild src/index.ts --bundle --minify --target=es2020 --tsconfig=tsconfig.bundle.json --platform=browser --format=iife --global-name=FrostpillarBTree --outfile=dist/frostpillar-btree.min.js",
68
80
  "build:bundle:core": "esbuild src/InMemoryBTree.ts --bundle --minify --target=es2020 --tsconfig=tsconfig.bundle.json --platform=browser --format=iife --global-name=FrostpillarBTreeCore --outfile=dist/frostpillar-btree-core.min.js",
69
81
  "lint": "eslint .",