@frostpillar/frostpillar-btree 0.2.0
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/LICENSE +21 -0
- package/README-JA.md +912 -0
- package/README.md +912 -0
- package/dist/InMemoryBTree.d.ts +45 -0
- package/dist/btree/autoScale.d.ts +9 -0
- package/dist/btree/bulkLoad.d.ts +5 -0
- package/dist/btree/deleteRange.d.ts +3 -0
- package/dist/btree/integrity-helpers.d.ts +6 -0
- package/dist/btree/integrity.d.ts +2 -0
- package/dist/btree/mutations.d.ts +12 -0
- package/dist/btree/navigation.d.ts +23 -0
- package/dist/btree/rangeQuery.d.ts +3 -0
- package/dist/btree/rebalance.d.ts +4 -0
- package/dist/btree/serialization.d.ts +20 -0
- package/dist/btree/stats.d.ts +2 -0
- package/dist/btree/types.d.ts +113 -0
- package/dist/chunk-ZA3EQNDI.js +1902 -0
- package/dist/concurrency/ConcurrentInMemoryBTree.d.ts +56 -0
- package/dist/concurrency/helpers.d.ts +27 -0
- package/dist/concurrency/index.d.ts +2 -0
- package/dist/concurrency/types.d.ts +41 -0
- package/dist/core.cjs +1919 -0
- package/dist/core.d.ts +4 -0
- package/dist/core.js +10 -0
- package/dist/errors.d.ts +9 -0
- package/dist/frostpillar-btree-core.min.js +1 -0
- package/dist/frostpillar-btree.min.js +1 -0
- package/dist/index.cjs +2230 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +316 -0
- package/package.json +80 -0
package/README-JA.md
ADDED
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
# frostpillar-btree
|
|
2
|
+
|
|
3
|
+
[English/英語](./README.md) | [Japanese/日本語](./README-JA.md)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@frostpillar/frostpillar-btree)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](https://github.com/hjmsano/frostpillar-btree/actions/workflows/ci.yml)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
[B+ tree](https://en.wikipedia.org/wiki/B%2B_tree) は、データをソート済みに保ち、検索・挿入・削除を O(log n) で実行できる自己平衡木データ構造です。ソート済み配列と異なり、頻繁な挿入・削除を再ソートなしで効率的に処理します。
|
|
11
|
+
|
|
12
|
+
`frostpillar-btree` は TypeScript、Node.js、およびブラウザ(JavaScript)で動作する、依存関係ゼロの軽量なインメモリ B+ tree ライブラリです。タスクキュー、優先度リスト、リーダーボード、高速な順序付きアクセスが必要なあらゆる場面でソート済みキーバリューストアとして利用できます。プラガブルな共有ストアを介した複数プロセス間の状態協調もサポートしています。
|
|
13
|
+
|
|
14
|
+
## 特徴
|
|
15
|
+
|
|
16
|
+
- **依存関係ゼロ** -- ランタイムパッケージ不要
|
|
17
|
+
- **どこでも動作** -- Node.js(ESM および CJS)、TypeScript、ブラウザ(IIFE バンドル)
|
|
18
|
+
- **ブラウザ向け 2 種類のバンドル** -- フル API と単一プロセス向けコアバンドル
|
|
19
|
+
- **キー一意性ポリシー** -- `'replace'`(デフォルト、マップ)、`'reject'`(一意制約)、`'allow'`(マルチマップ)を選択可能
|
|
20
|
+
- **厳格な TypeScript 型** -- ブランド型 `EntryId` による型安全なジェネリクス
|
|
21
|
+
- **プロセス間協調** -- `ConcurrentInMemoryBTree` がプラガブルな共有ストアによる楽観的並行制御を提供
|
|
22
|
+
|
|
23
|
+
## 簡単な例
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { InMemoryBTree } from '@frostpillar/frostpillar-btree';
|
|
27
|
+
|
|
28
|
+
const tree = new InMemoryBTree<number, string>({
|
|
29
|
+
compareKeys: (left: number, right: number): number => left - right,
|
|
30
|
+
enableEntryIdLookup: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const idTen = tree.put(10, 'ten');
|
|
34
|
+
tree.put(20, 'twenty');
|
|
35
|
+
|
|
36
|
+
console.log(tree.peekById(idTen));
|
|
37
|
+
console.log(tree.range(10, 20));
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 目次
|
|
43
|
+
|
|
44
|
+
- [はじめに](#はじめに)
|
|
45
|
+
- [ユーザーマニュアル](#ユーザーマニュアル)
|
|
46
|
+
- [InMemoryBTree(単一プロセス)](#inmemorybtree単一プロセス)
|
|
47
|
+
- [ConcurrentInMemoryBTree(マルチプロセス)](#concurrentinmemorybtreeマルチプロセス)
|
|
48
|
+
- [エラーハンドリング](#エラーハンドリング)
|
|
49
|
+
- [API リファレンス](#api-リファレンス)
|
|
50
|
+
- [InMemoryBTree](#inmemorybtree)
|
|
51
|
+
- [ConcurrentInMemoryBTree](#concurrentinmemorybtree)
|
|
52
|
+
- [エクスポートされる型](#エクスポートされる型)
|
|
53
|
+
- [コントリビュートガイド](#コントリビュートガイド)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## はじめに
|
|
58
|
+
|
|
59
|
+
### インストール(Node.js / TypeScript)
|
|
60
|
+
|
|
61
|
+
インストール方法は以下のとおりです。
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @frostpillar/frostpillar-btree
|
|
65
|
+
# または
|
|
66
|
+
pnpm add @frostpillar/frostpillar-btree
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
単一プロセス API だけが必要な場合は、core サブパスから import できます。
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { InMemoryBTree } from '@frostpillar/frostpillar-btree/core';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### CommonJS
|
|
76
|
+
|
|
77
|
+
CommonJS もサポートしています。通常どおり `require()` で利用できます。
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const { InMemoryBTree } = require('@frostpillar/frostpillar-btree');
|
|
81
|
+
// または core サブパス:
|
|
82
|
+
const { InMemoryBTree } = require('@frostpillar/frostpillar-btree/core');
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### インストール(ブラウザ)
|
|
86
|
+
|
|
87
|
+
minify 済みの IIFE バンドルは [GitHub Releases](https://github.com/hjmsano/frostpillar-btree/releases) から取得できます。どちらも ES2020 ターゲットです。
|
|
88
|
+
|
|
89
|
+
- `frostpillar-btree.min.js`(フル API): `window.FrostpillarBTree`
|
|
90
|
+
- `frostpillar-btree-core.min.js`(単一プロセス向けコア): `window.FrostpillarBTreeCore`
|
|
91
|
+
|
|
92
|
+
1. Releases から必要なバンドルをダウンロードします。
|
|
93
|
+
2. 静的配信ディレクトリに配置します。
|
|
94
|
+
3. `<script>` タグで読み込みます。
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<script src="./frostpillar-btree.min.js"></script>
|
|
98
|
+
<!-- または -->
|
|
99
|
+
<script src="./frostpillar-btree-core.min.js"></script>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
読み込み後、対応するグローバルから利用できます。
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const { InMemoryBTree } = window.FrostpillarBTree;
|
|
106
|
+
// または:
|
|
107
|
+
// const { InMemoryBTree } = window.FrostpillarBTreeCore;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 動作環境
|
|
111
|
+
|
|
112
|
+
| 環境 | 要件 |
|
|
113
|
+
| ---------- | ------------------------------------------------------------ |
|
|
114
|
+
| Node.js | >= 24.0.0(ESM および CJS) |
|
|
115
|
+
| ブラウザ | ES2020 対応(Chrome 80+、Firefox 74+、Safari 14+、Edge 80+) |
|
|
116
|
+
| TypeScript | >= 5.0 |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## ユーザーマニュアル
|
|
121
|
+
|
|
122
|
+
> **エラー概要:** 操作は `BTreeValidationError`(不正なコンパレータや設定)、`BTreeInvariantError`(ツリー構造の破損)、`BTreeConcurrencyError`(並行リトライの枯渇)をスローする場合があります。詳細と例は[エラーハンドリング](#エラーハンドリング)を参照してください。
|
|
123
|
+
|
|
124
|
+
### InMemoryBTree(単一プロセス)
|
|
125
|
+
|
|
126
|
+
`InMemoryBTree` は単一プロセス向けのコアクラスです。キーバリューペアを B+ tree 構造で保持し、O(log n) の挿入・削除・検索を提供します。
|
|
127
|
+
|
|
128
|
+
#### ツリーの作成
|
|
129
|
+
|
|
130
|
+
ソート順を定義する `compareKeys` 関数が必要です。`Array.prototype.sort` と同じ規約に従います。`left < right` なら負の値、`left > right` なら正の値、等しければ `0` を返してください。
|
|
131
|
+
|
|
132
|
+
**Node.js / TypeScript:**
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { InMemoryBTree } from '@frostpillar/frostpillar-btree';
|
|
136
|
+
|
|
137
|
+
const tree = new InMemoryBTree<number, string>({
|
|
138
|
+
compareKeys: (left: number, right: number): number => left - right,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**ブラウザ:**
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const { InMemoryBTree } = window.FrostpillarBTree;
|
|
146
|
+
|
|
147
|
+
const tree = new InMemoryBTree({
|
|
148
|
+
compareKeys: (left, right) => {
|
|
149
|
+
if (left < right) return -1;
|
|
150
|
+
if (left > right) return 1;
|
|
151
|
+
return 0;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`maxLeafEntries` と `maxBranchChildren`(いずれもデフォルト 64、最小 3、最大 16384)でツリー構造をチューニングできます。
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const tree = new InMemoryBTree<string, number>({
|
|
160
|
+
compareKeys: (a, b) => a.localeCompare(b),
|
|
161
|
+
maxLeafEntries: 128,
|
|
162
|
+
maxBranchChildren: 128,
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### エントリの挿入
|
|
167
|
+
|
|
168
|
+
`put()` はキーバリューペアを追加し、`EntryId`(ブランド型 `number`)を返します。`'replace'` モードでは、既存キーへの挿入は元のエントリの `EntryId` を返します。この ID は、あとからエントリの参照・更新・削除に使えます。
|
|
169
|
+
|
|
170
|
+
**Node.js / TypeScript:**
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const id1 = tree.put(10, 'ten');
|
|
174
|
+
const id2 = tree.put(20, 'twenty');
|
|
175
|
+
tree.put(10, 'updated ten'); // デフォルト 'replace' モード: 上書きされ、id1 は維持
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**ブラウザ:**
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
const id1 = tree.put(10, 'ten');
|
|
182
|
+
const id2 = tree.put(20, 'twenty');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**`putMany(entries)`** -- ソート済みの複数エントリを一括挿入します。ツリーが空の場合、O(n log n) ではなく O(n) の最適化されたバルクロードでツリーを構築します。エントリは昇順キー順である必要があります(`duplicateKeys` が `'reject'` または `'replace'` の場合は厳密な昇順)。
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const ids = tree.putMany([
|
|
189
|
+
{ key: 1, value: 'a' },
|
|
190
|
+
{ key: 2, value: 'b' },
|
|
191
|
+
{ key: 3, value: 'c' },
|
|
192
|
+
]);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### エントリの読み取り
|
|
196
|
+
|
|
197
|
+
**`peekById(entryId)`** -- ID でエントリを削除せずに参照します。
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const entry = tree.peekById(id1);
|
|
201
|
+
// { entryId: 0, key: 10, value: 'updated ten' } または null(見つからない場合)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**`peekFirst()`** -- 最小キーのエントリを削除せずに取得します。
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const first = tree.peekFirst();
|
|
208
|
+
// { entryId: ..., key: 10, value: 'ten' } または null(空の場合)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**`get(key)`** -- 結果配列を生成せずにキーの値を取得します。
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const value = tree.get(10); // 'ten' またはキーが存在しない場合 null
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**`hasKey(key)`** -- 指定キーのエントリが 1 件以上存在するか確認します。
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const exists = tree.hasKey(10); // true
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**`findFirst(key)`** -- 指定キーに一致する最初のエントリを返します。
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const entry = tree.findFirst(10);
|
|
227
|
+
// { entryId: ..., key: 10, value: 'ten' } または null(見つからない場合)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**`findLast(key)`** -- 指定キーに一致する最後(最も新しく挿入された)のエントリを返します。
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const entry = tree.findLast(10);
|
|
234
|
+
// { entryId: ..., key: 10, value: 'ten' } または null(見つからない場合)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**`peekLast()`** -- 最大キーのエントリを削除せずに取得します。
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const last = tree.peekLast();
|
|
241
|
+
// { entryId: ..., key: 20, value: 'twenty' } または null(空の場合)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### エントリの更新
|
|
245
|
+
|
|
246
|
+
**`updateById(entryId, newValue)`** -- 既存エントリの値を更新します。キーとツリー内の位置は変わりません。
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const updated = tree.updateById(id1, 'TEN');
|
|
250
|
+
// { entryId: 0, key: 10, value: 'TEN' } または null(見つからない場合)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### エントリの削除
|
|
254
|
+
|
|
255
|
+
**`remove(key)`** -- 指定キーに一致する最初のエントリを削除します。
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const removed = tree.remove(10);
|
|
259
|
+
// { entryId: ..., key: 10, value: 'ten' } または null(見つからない場合)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**`removeById(entryId)`** -- ID で特定のエントリを削除します。
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const removed = tree.removeById(id2);
|
|
266
|
+
// { entryId: ..., key: 20, value: 'twenty' } または null(見つからない場合)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**`popFirst()`** -- 最小キーのエントリを削除して返します(優先度キューとして活用できます)。
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const first = tree.popFirst();
|
|
273
|
+
// { entryId: ..., key: 10, value: 'ten' } または null(空の場合)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**`popLast()`** -- 最大キーのエントリを削除して返します。
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
const last = tree.popLast();
|
|
280
|
+
// { entryId: ..., key: 20, value: 'twenty' } または null(ツリーが空の場合)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**`clear()`** -- 全エントリを削除し、ツリーを空の状態に O(1) でリセットします。内部シーケンスカウンタもリセットされるため、新しい `EntryId` はゼロから始まります。`clear()` 前に取得した `EntryId` は無効になります。
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
tree.clear();
|
|
287
|
+
tree.size(); // 0
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**`deleteRange(startKey, endKey, options?)`** -- 範囲内のエントリを削除し、削除件数を返します。`range` と同じ境界セマンティクスに従います。
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
tree.deleteRange(2, 4); // キー 2, 3, 4 を削除 -- 削除件数を返す
|
|
294
|
+
tree.deleteRange(2, 4, { lowerBound: 'exclusive' }); // キー 3, 4 を削除
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### クエリ
|
|
298
|
+
|
|
299
|
+
**`count(startKey, endKey, options?)`** -- 結果配列を割り当てずに範囲内のエントリ数をカウントします。`range` と同じ境界セマンティクスに従います。
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
tree.put(1, 'a');
|
|
303
|
+
tree.put(2, 'b');
|
|
304
|
+
tree.put(3, 'c');
|
|
305
|
+
tree.put(4, 'd');
|
|
306
|
+
|
|
307
|
+
tree.count(2, 3); // 2
|
|
308
|
+
tree.count(1, 4, { lowerBound: 'exclusive' }); // 3
|
|
309
|
+
tree.count(1, 4, { upperBound: 'exclusive' }); // 3
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**`range(startKey, endKey, options?)`** -- `startKey` から `endKey` までの全エントリを取得します(デフォルトで両端含む)。
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
tree.put(1, 'a');
|
|
316
|
+
tree.put(2, 'b');
|
|
317
|
+
tree.put(3, 'c');
|
|
318
|
+
tree.put(4, 'd');
|
|
319
|
+
|
|
320
|
+
const entries = tree.range(2, 3);
|
|
321
|
+
// [{ entryId: ..., key: 2, value: 'b' }, { entryId: ..., key: 3, value: 'c' }]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`RangeBounds` で各境界の包含・除外を制御できます。
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
tree.range(2, 4, { lowerBound: 'exclusive' });
|
|
328
|
+
// key 2 を除外 → [{ key: 3, ... }, { key: 4, ... }]
|
|
329
|
+
|
|
330
|
+
tree.range(2, 4, { upperBound: 'exclusive' });
|
|
331
|
+
// key 4 を除外 → [{ key: 2, ... }, { key: 3, ... }]
|
|
332
|
+
|
|
333
|
+
tree.range(2, 4, { lowerBound: 'exclusive', upperBound: 'exclusive' });
|
|
334
|
+
// 両端除外 → [{ key: 3, ... }]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**`nextHigherKey(key)`** -- 指定キーより厳密に大きい最小のキーを返します。
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
tree.put(10, 'a');
|
|
341
|
+
tree.put(20, 'b');
|
|
342
|
+
tree.nextHigherKey(10); // 20
|
|
343
|
+
tree.nextHigherKey(20); // null
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**`nextLowerKey(key)`** -- 指定キーより厳密に小さい最大のキーを返します。
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
tree.nextLowerKey(20); // 10
|
|
350
|
+
tree.nextLowerKey(10); // null
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**`getPairOrNextLower(key)`** -- 指定キーに一致するエントリ、またはそれより小さい最大のエントリを返します。
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
tree.getPairOrNextLower(15); // { entryId: ..., key: 10, value: 'a' }
|
|
357
|
+
tree.getPairOrNextLower(10); // { entryId: ..., key: 10, value: 'a' }(完全一致)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### イテレーション
|
|
361
|
+
|
|
362
|
+
**`entries()`** -- スナップショット配列を生成せず、昇順でエントリを遅延イテレーションします。
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
for (const entry of tree.entries()) {
|
|
366
|
+
console.log(entry.key, entry.value);
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**`entriesReversed()`** -- 降順でエントリを遅延イテレーションします。
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
for (const entry of tree.entriesReversed()) {
|
|
374
|
+
console.log(entry.key, entry.value); // 最大キーから順に出力
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**`keys()`** / **`values()`** -- キーまたは値のみをイテレーションします。
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
const allKeys = [...tree.keys()]; // [1, 2, 3]
|
|
382
|
+
const allValues = [...tree.values()]; // ['a', 'b', 'c']
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**`for...of`** -- ツリー自体がイテラブルです(`entries()` に委譲)。
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
for (const entry of tree) {
|
|
389
|
+
console.log(entry.key, entry.value);
|
|
390
|
+
}
|
|
391
|
+
const asArray = [...tree]; // スプレッドも使えます
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**`forEach(callback, thisArg?)`** -- 昇順で各エントリを訪問します。
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
tree.forEach((entry) => {
|
|
398
|
+
console.log(entry.key, entry.value);
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**`snapshot()`** -- 全エントリをソート順で取得します。
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
const all = tree.snapshot();
|
|
406
|
+
// [{ entryId, key, value }, ...]
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**`size()`** -- エントリ数を取得します。
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const count = tree.size(); // 4
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### 診断
|
|
416
|
+
|
|
417
|
+
**`getStats()`** -- ツリーの内部構造を確認します。
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
const stats = tree.getStats();
|
|
421
|
+
// { height: 1, leafCount: 1, branchCount: 0, entryCount: 4 }
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**`assertInvariants()`** -- B+ tree の構造的な整合性を検証します。不正な場合は `BTreeInvariantError` をスローします。テストで便利です。
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
tree.assertInvariants(); // 不正な場合はスロー
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### クローンとシリアライズ
|
|
431
|
+
|
|
432
|
+
**`clone()`** -- 構造的に独立したディープコピーを作成します。
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
const copy = tree.clone();
|
|
436
|
+
copy.put(99, 'new');
|
|
437
|
+
tree.hasKey(99); // false -- 元のツリーには影響しない
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**`toJSON()` / `fromJSON()`** -- ツリーをシリアライズ・復元します。
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
const json = tree.toJSON();
|
|
444
|
+
const restored = InMemoryBTree.fromJSON(json, (a, b) => a - b);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### キー一意性ポリシー
|
|
448
|
+
|
|
449
|
+
`duplicateKeys` オプションで `put` の重複キー処理を制御できます。
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
const tree = new InMemoryBTree<number, string>({
|
|
453
|
+
compareKeys: (a, b) => a - b,
|
|
454
|
+
duplicateKeys: 'replace', // デフォルト
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
| ポリシー | 動作 | 用途 |
|
|
459
|
+
| ------------------------- | ------------------------------------------------------------- | ------------------------------------------ |
|
|
460
|
+
| `'replace'`(デフォルト) | 既存エントリの値を上書きし、元の `EntryId` を返す。 | キーバリューマップ / 辞書 |
|
|
461
|
+
| `'reject'` | キーが既に存在する場合、`BTreeValidationError` をスローする。 | 一意インデックス / セット |
|
|
462
|
+
| `'allow'` | 同一キーの複数エントリを許可し、挿入順で並べる。 | マルチマップ / イベントログ / 優先度キュー |
|
|
463
|
+
|
|
464
|
+
#### 動作に関する注意事項
|
|
465
|
+
|
|
466
|
+
- `range(start, end)` はデフォルトで両端を含みます。`RangeBounds` で除外境界を指定できます。`start > end` の場合は `[]` を返します。
|
|
467
|
+
- `EntryId` は `0` から始まるブランド型 `number` です。JavaScript では `0` は falsy なため、`if (entryId)` ではなく `if (entryId !== null)` や `if (entryId !== undefined)` を使用してください。
|
|
468
|
+
- コンパレータ契約(有限値、反射律、推移律)の検証は `assertInvariants()` で行われます。通常操作ごとの eager な検証は行いません。
|
|
469
|
+
- `compareKeys` は実行時にも関数である必要があります。関数以外を渡すと `BTreeValidationError` をスローします。
|
|
470
|
+
- `enableEntryIdLookup` のデフォルトは `false` です。`peekById` / `updateById` / `removeById` が必要な場合のみ `enableEntryIdLookup: true` を指定してください。
|
|
471
|
+
- `autoScale` のデフォルトは `false` です。`true` にするとエントリ数に応じてノード容量が段階的に拡大します(leaf: 32 -> 64 -> 128 -> 256 -> 512)。autoScale は容量を増加させるのみで、縮小はしません。
|
|
472
|
+
|
|
473
|
+
| エントリ数 | maxLeafEntries | maxBranchChildren |
|
|
474
|
+
| ---------- | -------------- | ----------------- |
|
|
475
|
+
| 0+ | 32 | 32 |
|
|
476
|
+
| 1,000+ | 64 | 64 |
|
|
477
|
+
| 10,000+ | 128 | 128 |
|
|
478
|
+
| 100,000+ | 256 | 128 |
|
|
479
|
+
| 1,000,000+ | 512 | 256 |
|
|
480
|
+
|
|
481
|
+
- `autoScale` は `maxLeafEntries` / `maxBranchChildren` の明示指定と同時には使えません。
|
|
482
|
+
- `fromJSON` は `1,000,000` 件を超えるエントリを含むペイロードを拒否します。
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
### ConcurrentInMemoryBTree(マルチプロセス)
|
|
487
|
+
|
|
488
|
+
`ConcurrentInMemoryBTree` は、プラガブルな共有ストアを介して複数プロセスやインスタンス間でツリー状態を共有します。楽観的並行制御を使用し、各ミューテーションをストアに追加してコンフリクトを再同期とリトライで解決します。
|
|
489
|
+
|
|
490
|
+
#### 仕組み
|
|
491
|
+
|
|
492
|
+
1. 各インスタンスはローカルの `InMemoryBTree` をキャッシュとして保持します。
|
|
493
|
+
2. 読み取り前に共有ストアから同期します。
|
|
494
|
+
3. 書き込み時はストアにミューテーションを追加します。同時書き込みが発生した場合は再同期してリトライします(最大 `maxRetries` 回、デフォルト 16)。
|
|
495
|
+
4. 1 つのインスタンス内の非同期操作はすべて直列化され、二重適用を防ぎます。
|
|
496
|
+
|
|
497
|
+
#### SharedTreeStore の実装
|
|
498
|
+
|
|
499
|
+
`ConcurrentInMemoryBTree` は以下の 2 つのメソッドを持つ共有ストアを介して協調します。
|
|
500
|
+
|
|
501
|
+
- **`getLogEntriesSince(version)`** -- 指定バージョン以降のすべてのミューテーションを返し、各インスタンスが最新状態へキャッチアップできるようにします。
|
|
502
|
+
- **`append(expectedVersion, mutations)`** -- バージョンが一致する場合にミューテーションをアトミックに追加します(compare-and-swap)。`{ applied, version }` を返します。
|
|
503
|
+
|
|
504
|
+
ストアの実装は自由です。インメモリ配列、データベーステーブル、Redis stream など何でも使えます。以下はインメモリの参考実装です。
|
|
505
|
+
|
|
506
|
+
**Node.js / TypeScript:**
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
import {
|
|
510
|
+
ConcurrentInMemoryBTree,
|
|
511
|
+
type BTreeMutation,
|
|
512
|
+
type SharedTreeLog,
|
|
513
|
+
type SharedTreeStore,
|
|
514
|
+
} from '@frostpillar/frostpillar-btree';
|
|
515
|
+
|
|
516
|
+
class InMemorySharedStore<TKey, TValue> implements SharedTreeStore<
|
|
517
|
+
TKey,
|
|
518
|
+
TValue
|
|
519
|
+
> {
|
|
520
|
+
private versions: {
|
|
521
|
+
version: bigint;
|
|
522
|
+
mutations: BTreeMutation<TKey, TValue>[];
|
|
523
|
+
}[] = [{ version: 0n, mutations: [] }];
|
|
524
|
+
|
|
525
|
+
public async getLogEntriesSince(
|
|
526
|
+
version: bigint,
|
|
527
|
+
): Promise<SharedTreeLog<TKey, TValue>> {
|
|
528
|
+
const latestVersion = this.versions[this.versions.length - 1].version;
|
|
529
|
+
if (version >= latestVersion) {
|
|
530
|
+
return { version: latestVersion, mutations: [] };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const unseen: BTreeMutation<TKey, TValue>[] = [];
|
|
534
|
+
for (const entry of this.versions) {
|
|
535
|
+
if (entry.version > version) {
|
|
536
|
+
unseen.push(...entry.mutations);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
version: latestVersion,
|
|
542
|
+
mutations: structuredClone(unseen),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
public async append(
|
|
547
|
+
expectedVersion: bigint,
|
|
548
|
+
mutations: BTreeMutation<TKey, TValue>[],
|
|
549
|
+
): Promise<{ applied: boolean; version: bigint }> {
|
|
550
|
+
const latestVersion = this.versions[this.versions.length - 1].version;
|
|
551
|
+
if (latestVersion !== expectedVersion) {
|
|
552
|
+
return { applied: false, version: latestVersion };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const nextVersion = latestVersion + 1n;
|
|
556
|
+
this.versions.push({
|
|
557
|
+
version: nextVersion,
|
|
558
|
+
mutations: structuredClone(mutations),
|
|
559
|
+
});
|
|
560
|
+
return { applied: true, version: nextVersion };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**ブラウザ:**
|
|
566
|
+
|
|
567
|
+
```js
|
|
568
|
+
const { ConcurrentInMemoryBTree } = window.FrostpillarBTree;
|
|
569
|
+
|
|
570
|
+
// 同じ方法で SharedTreeStore を実装してください。
|
|
571
|
+
// インターフェースには 2 つの非同期メソッドが必要です。
|
|
572
|
+
// getLogEntriesSince(version) => { version, mutations }
|
|
573
|
+
// append(expectedVersion, mutations) => { applied, version }
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### 協調インスタンスの作成
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const store = new InMemorySharedStore<number, string>();
|
|
580
|
+
|
|
581
|
+
const instanceA = new ConcurrentInMemoryBTree<number, string>({
|
|
582
|
+
compareKeys: (left: number, right: number): number => left - right,
|
|
583
|
+
enableEntryIdLookup: true,
|
|
584
|
+
store,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const instanceB = new ConcurrentInMemoryBTree<number, string>({
|
|
588
|
+
compareKeys: (left: number, right: number): number => left - right,
|
|
589
|
+
enableEntryIdLookup: true,
|
|
590
|
+
store,
|
|
591
|
+
});
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
`maxRetries`(デフォルト: 16、最小: 1、最大: 1024)を設定できます。
|
|
595
|
+
|
|
596
|
+
```ts
|
|
597
|
+
const instance = new ConcurrentInMemoryBTree<number, string>({
|
|
598
|
+
compareKeys: (a, b) => a - b,
|
|
599
|
+
store,
|
|
600
|
+
maxRetries: 32,
|
|
601
|
+
});
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
`sync` 1 回で適用するミューテーション数の上限として `maxSyncMutationsPerBatch` も設定できます(デフォルト: `100000`、最小: `1`、最大: `1000000`)。
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
const hardened = new ConcurrentInMemoryBTree<number, string>({
|
|
608
|
+
compareKeys: (a, b) => a - b,
|
|
609
|
+
store,
|
|
610
|
+
maxSyncMutationsPerBatch: 50000,
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
#### Concurrent API の使い方
|
|
615
|
+
|
|
616
|
+
メソッドはすべて非同期です。書き込みはストアを介して協調し、読み取りは返す前に同期します(`readMode` が `'strong'`(デフォルト)の場合)。
|
|
617
|
+
|
|
618
|
+
`readMode` を `'local'` に設定すると、読み取り時の同期をスキップできます。ローカルモードではローカルツリーに対してのみ読み取りを実行するため、古いデータを返す可能性があります。明示的な `sync()` で最新状態に追いつきます。
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
const localInstance = new ConcurrentInMemoryBTree<number, string>({
|
|
622
|
+
compareKeys: (a, b) => a - b,
|
|
623
|
+
store,
|
|
624
|
+
readMode: 'local',
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
await localInstance.put(1, 'one');
|
|
628
|
+
await localInstance.sync(); // 明示的に最新状態を取得
|
|
629
|
+
const value = await localInstance.get(1);
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
// インスタンス A が挿入
|
|
634
|
+
const insertedId = await instanceA.put(100, 'draft docs');
|
|
635
|
+
|
|
636
|
+
// インスタンス B は同じ EntryId をすぐに使える
|
|
637
|
+
const updated = await instanceB.updateById(insertedId, 'publish docs');
|
|
638
|
+
|
|
639
|
+
// インスタンス A が削除
|
|
640
|
+
const removed = await instanceA.removeById(insertedId);
|
|
641
|
+
|
|
642
|
+
// インスタンス B が同期し、削除を反映
|
|
643
|
+
await instanceB.sync();
|
|
644
|
+
const rows = await instanceB.snapshot(); // []
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
#### 動作に関する注意事項
|
|
648
|
+
|
|
649
|
+
- 同じ shared store を共有するすべてのインスタンスは、同一の設定(`compareKeys`、`duplicateKeys`、`maxLeafEntries`、`maxBranchChildren`、`enableEntryIdLookup`、`autoScale`)を使う必要があります。最初の書き込み時に設定フィンガープリントを含む `init` ミューテーションが追加され、他のインスタンスは同期時に検証します。不一致の場合は `BTreeConcurrencyError` がスローされます。コンパレータの一致は呼び出し側の責任です。
|
|
650
|
+
- `EntryId` はログ由来の識別子です。同じ shared store を参照して同期したインスタンス間で `peekById`、`removeById`、`updateById` に利用できます。
|
|
651
|
+
- 1 つのインスタンスではすべての非同期操作(`sync`、読み取り、書き込み)が直列化され、ローカルでの二重適用を防ぎます。
|
|
652
|
+
- プロセス間の保証は shared store の原子的な version 付き append に依存します。
|
|
653
|
+
- `maxRetries` 回のリトライ後もミューテーションを適用できない場合、`BTreeConcurrencyError` がスローされます。
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
### エラーハンドリング
|
|
658
|
+
|
|
659
|
+
`@frostpillar/frostpillar-btree` は 3 つのエラークラスをエクスポートします。すべて `Error` を継承します。
|
|
660
|
+
|
|
661
|
+
#### BTreeValidationError
|
|
662
|
+
|
|
663
|
+
設定・ポリシー制約へ違反した場合にスローされます。
|
|
664
|
+
|
|
665
|
+
**原因:**
|
|
666
|
+
|
|
667
|
+
- `maxLeafEntries` または `maxBranchChildren` が整数でない、3 未満、または 16384 を超える
|
|
668
|
+
- `duplicateKeys` に不正な値が設定されている
|
|
669
|
+
- `duplicateKeys` が `'reject'` のとき、既存キーで `put` が呼ばれた
|
|
670
|
+
- `enableEntryIdLookup` が `false` の状態で `removeById`、`peekById`、`updateById` が呼ばれた
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
import {
|
|
674
|
+
BTreeValidationError,
|
|
675
|
+
InMemoryBTree,
|
|
676
|
+
} from '@frostpillar/frostpillar-btree';
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
const tree = new InMemoryBTree<number, string>({
|
|
680
|
+
compareKeys: (a, b) => a - b,
|
|
681
|
+
duplicateKeys: 'reject',
|
|
682
|
+
});
|
|
683
|
+
tree.put(1, 'one');
|
|
684
|
+
tree.put(1, 'duplicate'); // BTreeValidationError をスロー
|
|
685
|
+
} catch (error) {
|
|
686
|
+
if (error instanceof BTreeValidationError) {
|
|
687
|
+
console.error('重複キーが拒否されました:', error.message);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### BTreeInvariantError
|
|
693
|
+
|
|
694
|
+
`assertInvariants()` で B+ tree の内部構造に不整合(コンパレータの反射律・推移律違反を含む)が検出された場合にスローされます。ライブラリのバグ、コンパレータ契約違反、または外部操作による破損を示します。
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
import {
|
|
698
|
+
BTreeInvariantError,
|
|
699
|
+
InMemoryBTree,
|
|
700
|
+
} from '@frostpillar/frostpillar-btree';
|
|
701
|
+
|
|
702
|
+
const tree = new InMemoryBTree<number, string>({
|
|
703
|
+
compareKeys: (a, b) => (a === b ? 1 : a - b),
|
|
704
|
+
});
|
|
705
|
+
tree.put(1, 'one');
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
tree.assertInvariants();
|
|
709
|
+
} catch (error) {
|
|
710
|
+
if (error instanceof BTreeInvariantError) {
|
|
711
|
+
console.error('ツリー構造が破損しています:', error.message);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
#### BTreeConcurrencyError
|
|
717
|
+
|
|
718
|
+
`ConcurrentInMemoryBTree` で以下の場合にスローされます。
|
|
719
|
+
|
|
720
|
+
- `maxRetries` 回のリトライ後も同時更新によりミューテーションを適用できない場合
|
|
721
|
+
- shared store がバージョン契約に違反した場合
|
|
722
|
+
- `maxRetries` に不正な値(1 以上 1024 以下の整数でない)が設定された場合
|
|
723
|
+
- `maxSyncMutationsPerBatch` に不正な値(1 以上 1000000 以下の整数でない)が設定された場合
|
|
724
|
+
- `sync` 時のミューテーション件数が `maxSyncMutationsPerBatch` を超えた場合
|
|
725
|
+
|
|
726
|
+
```ts
|
|
727
|
+
import {
|
|
728
|
+
BTreeConcurrencyError,
|
|
729
|
+
ConcurrentInMemoryBTree,
|
|
730
|
+
type SharedTreeStore,
|
|
731
|
+
} from '@frostpillar/frostpillar-btree';
|
|
732
|
+
|
|
733
|
+
const store: SharedTreeStore<number, string> = {
|
|
734
|
+
async getLogEntriesSince() {
|
|
735
|
+
return { version: 0n, mutations: [] };
|
|
736
|
+
},
|
|
737
|
+
async append() {
|
|
738
|
+
return { applied: true, version: 1n };
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
try {
|
|
743
|
+
new ConcurrentInMemoryBTree<number, string>({
|
|
744
|
+
compareKeys: (a, b) => a - b,
|
|
745
|
+
store,
|
|
746
|
+
maxRetries: 0,
|
|
747
|
+
});
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (error instanceof BTreeConcurrencyError) {
|
|
750
|
+
console.error('並行設定が不正です:', error.message);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## API リファレンス
|
|
758
|
+
|
|
759
|
+
### InMemoryBTree
|
|
760
|
+
|
|
761
|
+
| メソッド | シグネチャ | 説明 |
|
|
762
|
+
| -------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
763
|
+
| `put` | `(key: TKey, value: TValue) => EntryId` | キーバリューペアを挿入し、`EntryId` を返す。 |
|
|
764
|
+
| `putMany` | `(entries: readonly { key: TKey; value: TValue }[]) => EntryId[]` | ソート済みエントリの一括挿入。空のツリーでは O(n)。非空ツリーではカーソル最適化。 |
|
|
765
|
+
| `remove` | `(key: TKey) => BTreeEntry<TKey, TValue> \| null` | 指定キーに一致する最初のエントリを削除する。 |
|
|
766
|
+
| `removeById` | `(entryId: EntryId) => BTreeEntry<TKey, TValue> \| null` | ID でエントリを削除する。 |
|
|
767
|
+
| `peekById` | `(entryId: EntryId) => BTreeEntry<TKey, TValue> \| null` | ID でエントリを削除せずに参照する。 |
|
|
768
|
+
| `updateById` | `(entryId: EntryId, value: TValue) => BTreeEntry<TKey, TValue> \| null` | ID でエントリの値を更新する。 |
|
|
769
|
+
| `popFirst` | `() => BTreeEntry<TKey, TValue> \| null` | 最小キーのエントリを削除して返す。 |
|
|
770
|
+
| `popLast` | `() => BTreeEntry<TKey, TValue> \| null` | 最大キーのエントリを削除して返す。 |
|
|
771
|
+
| `peekFirst` | `() => BTreeEntry<TKey, TValue> \| null` | 最小キーのエントリを削除せずに返す。 |
|
|
772
|
+
| `peekLast` | `() => BTreeEntry<TKey, TValue> \| null` | 最大のエントリを削除せずに返す。 |
|
|
773
|
+
| `findFirst` | `(key: TKey) => BTreeEntry<TKey, TValue> \| null` | キーに一致する最初のエントリを返す。 |
|
|
774
|
+
| `findLast` | `(key: TKey) => BTreeEntry<TKey, TValue> \| null` | キーに一致する最後のエントリを返す。 |
|
|
775
|
+
| `get` | `(key: TKey) => TValue \| null` | 指定キーの最初の値を返す。キーがない場合は null。 |
|
|
776
|
+
| `hasKey` | `(key: TKey) => boolean` | 指定キーのエントリが 1 件以上存在するか確認する。 |
|
|
777
|
+
| `count` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => number` | 配列割り当てなしで範囲内のエントリ数を返す。境界はデフォルトで包含。 |
|
|
778
|
+
| `range` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => BTreeEntry<TKey, TValue>[]` | startKey から endKey のエントリを返す。境界はデフォルトで包含。 |
|
|
779
|
+
| `nextHigherKey` | `(key: TKey) => TKey \| null` | 指定キーより大きい次のキーを返す。 |
|
|
780
|
+
| `nextLowerKey` | `(key: TKey) => TKey \| null` | 指定キーより小さい次のキーを返す。 |
|
|
781
|
+
| `getPairOrNextLower` | `(key: TKey) => BTreeEntry<TKey, TValue> \| null` | 一致エントリまたはそれより小さい最大のエントリを返す。 |
|
|
782
|
+
| `deleteRange` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => number` | 範囲内のエントリを削除し、削除件数を返す。 |
|
|
783
|
+
| `entries` | `() => IterableIterator<BTreeEntry<TKey, TValue>>` | 昇順で全エントリを遅延イテレーションする。 |
|
|
784
|
+
| `entriesReversed` | `() => IterableIterator<BTreeEntry<TKey, TValue>>` | 降順で全エントリを遅延イテレーションする。 |
|
|
785
|
+
| `keys` | `() => IterableIterator<TKey>` | 昇順で全キーを遅延イテレーションする。 |
|
|
786
|
+
| `values` | `() => IterableIterator<TValue>` | 昇順で全値を遅延イテレーションする。 |
|
|
787
|
+
| `[Symbol.iterator]` | `() => IterableIterator<BTreeEntry<TKey, TValue>>` | `for...of` やスプレッドを有効にする。`entries()` に委譲。 |
|
|
788
|
+
| `forEach` | `(callback: (entry) => void, thisArg?) => void` | 昇順で各エントリを訪問する。 |
|
|
789
|
+
| `snapshot` | `() => BTreeEntry<TKey, TValue>[]` | 全エントリをソート順で返す。 |
|
|
790
|
+
| `clear` | `() => void` | 全エントリを削除し、空の状態に O(1) でリセットする。 |
|
|
791
|
+
| `size` | `() => number` | エントリ数を返す。 |
|
|
792
|
+
| `getStats` | `() => BTreeStats` | 構造統計を返す。 |
|
|
793
|
+
| `assertInvariants` | `() => void` | B+ tree の構造的な整合性を検証する。不正な場合はスローする。 |
|
|
794
|
+
| `clone` | `() => InMemoryBTree<TKey, TValue>` | 構造的に独立したディープコピーを返す。 |
|
|
795
|
+
| `toJSON` | `() => BTreeJSON<TKey, TValue>` | バージョン付き JSON 互換ペイロードにシリアライズする。 |
|
|
796
|
+
| `fromJSON` (静的) | `(json, compareKeys) => InMemoryBTree<TKey, TValue>` | `toJSON` ペイロードからツリーを再構築する。 |
|
|
797
|
+
|
|
798
|
+
**コンストラクタ:**
|
|
799
|
+
|
|
800
|
+
```ts
|
|
801
|
+
new InMemoryBTree<TKey, TValue>(config: InMemoryBTreeConfig<TKey>)
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### ConcurrentInMemoryBTree
|
|
805
|
+
|
|
806
|
+
`InMemoryBTree` メソッドのサブセットを `Promise` を返す非同期版として提供します。書き込みは shared store を介して協調し、`readMode` が `'strong'`(デフォルト)の場合は読み取り前に同期します。`readMode` が `'local'` の場合、読み取りは同期なしでローカルツリーに対して実行されます。`putMany`・`deleteRange`・イテレータ・`clear`・`clone`・`toJSON`/`fromJSON` は現在未対応です。
|
|
807
|
+
|
|
808
|
+
| メソッド | シグネチャ | 説明 |
|
|
809
|
+
| -------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
810
|
+
| `sync` | `() => Promise<void>` | shared store の最新ログを取得して適用する。 |
|
|
811
|
+
| `put` | `(key: TKey, value: TValue) => Promise<EntryId>` | 楽観的並行制御で挿入する。 |
|
|
812
|
+
| `remove` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | 指定キーに一致する最初のエントリを削除する。 |
|
|
813
|
+
| `removeById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | ID でエントリを削除する。 |
|
|
814
|
+
| `peekById` | `(entryId: EntryId) => Promise<BTreeEntry<TKey, TValue> \| null>` | ID でエントリを参照する(事前に同期)。 |
|
|
815
|
+
| `updateById` | `(entryId: EntryId, value: TValue) => Promise<BTreeEntry<TKey, TValue> \| null>` | 楽観的並行制御で ID のエントリ値を更新する。 |
|
|
816
|
+
| `popFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最小キーのエントリを削除して返す。 |
|
|
817
|
+
| `popLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最大キーのエントリを削除して返す。 |
|
|
818
|
+
| `peekFirst` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最小キーのエントリを返す(事前に同期)。 |
|
|
819
|
+
| `peekLast` | `() => Promise<BTreeEntry<TKey, TValue> \| null>` | 最大キーのエントリを返す(事前に同期)。 |
|
|
820
|
+
| `findFirst` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | キーに一致する最初のエントリを返す(事前に同期)。 |
|
|
821
|
+
| `findLast` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | キーに一致する最後のエントリを返す(事前に同期)。 |
|
|
822
|
+
| `get` | `(key: TKey) => Promise<TValue \| null>` | キーの値を取得する(事前に同期)。 |
|
|
823
|
+
| `hasKey` | `(key: TKey) => Promise<boolean>` | キーの存在を確認する(事前に同期)。 |
|
|
824
|
+
| `count` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<number>` | 範囲内のエントリ数を返す(事前に同期)。 |
|
|
825
|
+
| `range` | `(startKey: TKey, endKey: TKey, options?: RangeBounds) => Promise<BTreeEntry<TKey, TValue>[]>` | 範囲クエリ(事前に同期)。 |
|
|
826
|
+
| `nextHigherKey` | `(key: TKey) => Promise<TKey \| null>` | 指定キーより大きい次のキー(事前に同期)。 |
|
|
827
|
+
| `nextLowerKey` | `(key: TKey) => Promise<TKey \| null>` | 指定キーより小さい次のキー(事前に同期)。 |
|
|
828
|
+
| `getPairOrNextLower` | `(key: TKey) => Promise<BTreeEntry<TKey, TValue> \| null>` | 一致または次に小さいエントリ(事前に同期)。 |
|
|
829
|
+
| `snapshot` | `() => Promise<BTreeEntry<TKey, TValue>[]>` | 全エントリを返す(事前に同期)。 |
|
|
830
|
+
| `size` | `() => Promise<number>` | エントリ数を返す(事前に同期)。 |
|
|
831
|
+
| `getStats` | `() => Promise<BTreeStats>` | 構造統計を返す(事前に同期)。 |
|
|
832
|
+
| `assertInvariants` | `() => Promise<void>` | 構造的な整合性を検証する(事前に同期)。 |
|
|
833
|
+
|
|
834
|
+
**コンストラクタ:**
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
new ConcurrentInMemoryBTree<TKey, TValue>(config: ConcurrentInMemoryBTreeConfig<TKey, TValue>)
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### エクスポートされる型
|
|
841
|
+
|
|
842
|
+
| 型 | 説明 |
|
|
843
|
+
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
844
|
+
| `EntryId` | エントリを識別するブランド型 `number`。 |
|
|
845
|
+
| `BTreeEntry<TKey, TValue>` | `{ entryId: EntryId; key: TKey; value: TValue }` |
|
|
846
|
+
| `BTreeJSON<TKey, TValue>` | `toJSON()` が生成し `fromJSON()` が受け取る、バージョン付き JSON シリアライズ可能なペイロード。 |
|
|
847
|
+
| `BTreeStats` | `{ height: number; leafCount: number; branchCount: number; entryCount: number }` |
|
|
848
|
+
| `KeyComparator<TKey>` | `(left: TKey, right: TKey) => number` |
|
|
849
|
+
| `DuplicateKeyPolicy` | `'allow' \| 'reject' \| 'replace'` |
|
|
850
|
+
| `RangeBounds` | `{ lowerBound?: 'inclusive' \| 'exclusive'; upperBound?: 'inclusive' \| 'exclusive' }` |
|
|
851
|
+
| `InMemoryBTreeConfig<TKey>` | `{ compareKeys: KeyComparator<TKey>; maxLeafEntries?: number; maxBranchChildren?: number; duplicateKeys?: DuplicateKeyPolicy; enableEntryIdLookup?: boolean; autoScale?: boolean }` |
|
|
852
|
+
| `ReadMode` | `'strong' \| 'local'` |
|
|
853
|
+
| `ConcurrentInMemoryBTreeConfig<TKey, TValue>` | `InMemoryBTreeConfig<TKey>` を拡張し、`store: SharedTreeStore<TKey, TValue>`、`maxRetries?: number`、`maxSyncMutationsPerBatch?: number`、`readMode?: ReadMode` を追加。 |
|
|
854
|
+
| `SharedTreeStore<TKey, TValue>` | `getLogEntriesSince(version)` と `append(expectedVersion, mutations)` を持つインターフェース。 |
|
|
855
|
+
| `SharedTreeLog<TKey, TValue>` | `{ version: bigint; mutations: BTreeMutation<TKey, TValue>[] }` |
|
|
856
|
+
| `BTreeMutation<TKey, TValue>` | 判別共用体: `init`、`put`、`remove`、`removeById`、`updateById`、`popFirst`、`popLast`。 |
|
|
857
|
+
| `BTreeValidationError` | コンパレータや設定の違反でスローされるエラー。 |
|
|
858
|
+
| `BTreeInvariantError` | ツリー構造の整合性違反でスローされるエラー。 |
|
|
859
|
+
| `BTreeConcurrencyError` | 並行処理コンフリクトやストア契約違反でスローされるエラー。 |
|
|
860
|
+
|
|
861
|
+
> **サブパスエクスポート:** `/core` サブパス(`@frostpillar/frostpillar-btree/core`)は単一プロセス向けの型のみエクスポートします: `InMemoryBTree`、`EntryId`、`BTreeEntry`、`BTreeJSON`、`BTreeStats`、`KeyComparator`、`DuplicateKeyPolicy`、`RangeBounds`、`InMemoryBTreeConfig`、`BTreeValidationError`、`BTreeInvariantError`。並行処理関連のエクスポート(`ConcurrentInMemoryBTree`、`ConcurrentInMemoryBTreeConfig`、`ReadMode`、`SharedTreeStore`、`SharedTreeLog`、`BTreeMutation`、`BTreeConcurrencyError`)はメインエントリポイントからのみ利用できます。
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## コントリビュートガイド
|
|
866
|
+
|
|
867
|
+
### 前提条件
|
|
868
|
+
|
|
869
|
+
- Node.js >= 24.0.0
|
|
870
|
+
- pnpm >= 10.0.0
|
|
871
|
+
|
|
872
|
+
### セットアップ
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
git clone https://github.com/hjmsano/frostpillar-btree.git
|
|
876
|
+
cd frostpillar-btree
|
|
877
|
+
pnpm install
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### 開発コマンド
|
|
881
|
+
|
|
882
|
+
| コマンド | 説明 |
|
|
883
|
+
| --------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
884
|
+
| `pnpm build` | ESM、CJS、型宣言を `dist/` にビルドする。 |
|
|
885
|
+
| `pnpm test` | 全テストを実行する。 |
|
|
886
|
+
| `pnpm test tests/inMemoryBTree.test.ts` | InMemoryBTree テストを実行する。 |
|
|
887
|
+
| `pnpm test tests/concurrentInMemoryBTree.test.ts` | ConcurrentInMemoryBTree テストを実行する。 |
|
|
888
|
+
| `pnpm test tests/concurrentInMemoryBTree.operations.test.ts` | 同時操作テストを実行する。 |
|
|
889
|
+
| `pnpm test tests/concurrentInMemoryBTree.storeContract.test.ts` | ストア契約テストを実行する。 |
|
|
890
|
+
| `pnpm test tests/bundleBuildContract.test.ts` | バンドルビルド契約テストを実行する。 |
|
|
891
|
+
| `pnpm test tests/githubActionsWorkflows.test.ts` | ワークフロー契約テストを実行する。 |
|
|
892
|
+
| `pnpm build:bundle` | ブラウザ向けフルバンドルをビルドする(並行 API を含む)。 |
|
|
893
|
+
| `pnpm build:bundle:core` | ブラウザ向けコアバンドルをビルドする(InMemoryBTree のみ)。 |
|
|
894
|
+
| `pnpm bench` | ベンチマークを実行する(事前に `pnpm build` が必要)。 |
|
|
895
|
+
| `pnpm check` | typecheck + lint + test + textlint を実行する。 |
|
|
896
|
+
|
|
897
|
+
### ブランチとリリースモデル
|
|
898
|
+
|
|
899
|
+
- デフォルトブランチは `main` です。
|
|
900
|
+
- リリースは [Release Please](https://github.com/googleapis/release-please)(`.github/workflows/ci-release.yml`)で管理します。
|
|
901
|
+
- Conventional Commits 互換の PR を `release` ブランチにマージすると、Release Please がバージョン更新 PR を `release` に対して作成・更新します。
|
|
902
|
+
- バージョン更新 PR をマージすると、GitHub Release 作成、ブラウザバンドルのアップロード(`frostpillar-btree.min.js` および `frostpillar-btree-core.min.js`)、GitHub Packages への publish が実行されます。
|
|
903
|
+
|
|
904
|
+
### ドキュメント
|
|
905
|
+
|
|
906
|
+
- [ドキュメント目次](./docs/INDEX.md)
|
|
907
|
+
- [ライブラリ仕様](./docs/specs/01_in-memory-btree.md)
|
|
908
|
+
- [リリース仕様](./docs/specs/02_release-driven-cicd-and-publish.md)
|
|
909
|
+
|
|
910
|
+
## ライセンス
|
|
911
|
+
|
|
912
|
+
[MIT](./LICENSE)
|