@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/dist/index.cjs
ADDED
|
@@ -0,0 +1,2230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BTreeConcurrencyError: () => BTreeConcurrencyError,
|
|
24
|
+
BTreeInvariantError: () => BTreeInvariantError,
|
|
25
|
+
BTreeValidationError: () => BTreeValidationError,
|
|
26
|
+
ConcurrentInMemoryBTree: () => ConcurrentInMemoryBTree,
|
|
27
|
+
InMemoryBTree: () => InMemoryBTree
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/errors.ts
|
|
32
|
+
var BTreeValidationError = class extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "BTreeValidationError";
|
|
36
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var BTreeInvariantError = class extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "BTreeInvariantError";
|
|
43
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var BTreeConcurrencyError = class extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "BTreeConcurrencyError";
|
|
50
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/btree/types.ts
|
|
55
|
+
var DEFAULT_MAX_LEAF_ENTRIES = 64;
|
|
56
|
+
var DEFAULT_MAX_BRANCH_CHILDREN = 64;
|
|
57
|
+
var MIN_NODE_CAPACITY = 3;
|
|
58
|
+
var MAX_NODE_CAPACITY = 16384;
|
|
59
|
+
var NODE_LEAF = 0;
|
|
60
|
+
var NODE_BRANCH = 1;
|
|
61
|
+
var normalizeDuplicateKeyPolicy = (value) => {
|
|
62
|
+
if (value === void 0) {
|
|
63
|
+
return "replace";
|
|
64
|
+
}
|
|
65
|
+
if (value !== "allow" && value !== "reject" && value !== "replace") {
|
|
66
|
+
throw new BTreeValidationError(
|
|
67
|
+
`Invalid duplicateKeys option.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
};
|
|
72
|
+
var isLeafNode = (node) => {
|
|
73
|
+
return node.kind === NODE_LEAF;
|
|
74
|
+
};
|
|
75
|
+
var writeMinKeyTo = (node, target) => {
|
|
76
|
+
if (node.kind === NODE_LEAF) {
|
|
77
|
+
if (node.entryOffset >= node.entries.length) return false;
|
|
78
|
+
const e = node.entries[node.entryOffset];
|
|
79
|
+
target.key = e.key;
|
|
80
|
+
target.sequence = e.entryId;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (node.childOffset >= node.keys.length) return false;
|
|
84
|
+
target.key = node.keys[node.childOffset].key;
|
|
85
|
+
target.sequence = node.keys[node.childOffset].sequence;
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
88
|
+
var normalizeNodeCapacity = (value, field, defaultValue) => {
|
|
89
|
+
if (value === void 0) {
|
|
90
|
+
return defaultValue;
|
|
91
|
+
}
|
|
92
|
+
if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
|
|
93
|
+
throw new BTreeValidationError(
|
|
94
|
+
`${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
};
|
|
99
|
+
var createLeafNode = (entries, parent) => {
|
|
100
|
+
return {
|
|
101
|
+
kind: NODE_LEAF,
|
|
102
|
+
entries,
|
|
103
|
+
entryOffset: 0,
|
|
104
|
+
parent,
|
|
105
|
+
indexInParent: 0,
|
|
106
|
+
prev: null,
|
|
107
|
+
next: null
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
var createBranchNode = (children, parent) => {
|
|
111
|
+
const keys = [];
|
|
112
|
+
const branch = {
|
|
113
|
+
kind: NODE_BRANCH,
|
|
114
|
+
children,
|
|
115
|
+
keys,
|
|
116
|
+
childOffset: 0,
|
|
117
|
+
parent,
|
|
118
|
+
indexInParent: 0
|
|
119
|
+
};
|
|
120
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
121
|
+
const child = children[i];
|
|
122
|
+
child.parent = branch;
|
|
123
|
+
child.indexInParent = i;
|
|
124
|
+
const target = { key: void 0, sequence: 0 };
|
|
125
|
+
if (!writeMinKeyTo(child, target)) {
|
|
126
|
+
throw new BTreeInvariantError(
|
|
127
|
+
"branch child has no min key"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
keys.push(target);
|
|
131
|
+
}
|
|
132
|
+
return branch;
|
|
133
|
+
};
|
|
134
|
+
var leafEntryCount = (leaf) => leaf.entries.length - leaf.entryOffset;
|
|
135
|
+
var leafEntryAt = (leaf, i) => leaf.entries[leaf.entryOffset + i];
|
|
136
|
+
var leafShiftEntry = (leaf) => {
|
|
137
|
+
if (leaf.entryOffset >= leaf.entries.length) return void 0;
|
|
138
|
+
const entry = leaf.entries[leaf.entryOffset];
|
|
139
|
+
leaf.entryOffset += 1;
|
|
140
|
+
if (leaf.entryOffset >= leaf.entries.length >>> 1) {
|
|
141
|
+
leaf.entries.copyWithin(0, leaf.entryOffset);
|
|
142
|
+
leaf.entries.length = leaf.entries.length - leaf.entryOffset;
|
|
143
|
+
leaf.entryOffset = 0;
|
|
144
|
+
}
|
|
145
|
+
return entry;
|
|
146
|
+
};
|
|
147
|
+
var leafPopEntry = (leaf) => {
|
|
148
|
+
if (leaf.entryOffset >= leaf.entries.length) return void 0;
|
|
149
|
+
return leaf.entries.pop();
|
|
150
|
+
};
|
|
151
|
+
var leafUnshiftEntry = (leaf, entry) => {
|
|
152
|
+
if (leaf.entryOffset > 0) {
|
|
153
|
+
leaf.entryOffset -= 1;
|
|
154
|
+
leaf.entries[leaf.entryOffset] = entry;
|
|
155
|
+
} else {
|
|
156
|
+
leaf.entries.unshift(entry);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var leafRemoveAt = (leaf, logicalIndex) => {
|
|
160
|
+
const count = leaf.entries.length - leaf.entryOffset;
|
|
161
|
+
const phys = leaf.entryOffset + logicalIndex;
|
|
162
|
+
if (logicalIndex < count - 1 - logicalIndex) {
|
|
163
|
+
leaf.entries.copyWithin(leaf.entryOffset + 1, leaf.entryOffset, phys);
|
|
164
|
+
leaf.entryOffset += 1;
|
|
165
|
+
if (leaf.entryOffset >= leaf.entries.length >>> 1) {
|
|
166
|
+
leaf.entries.copyWithin(0, leaf.entryOffset);
|
|
167
|
+
leaf.entries.length -= leaf.entryOffset;
|
|
168
|
+
leaf.entryOffset = 0;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
leaf.entries.copyWithin(phys, phys + 1);
|
|
172
|
+
leaf.entries.length -= 1;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var leafInsertAt = (leaf, logicalIndex, entry) => {
|
|
176
|
+
const phys = leaf.entryOffset + logicalIndex;
|
|
177
|
+
if (leaf.entryOffset > 0 && logicalIndex < leaf.entries.length - leaf.entryOffset >>> 1) {
|
|
178
|
+
leaf.entries.copyWithin(leaf.entryOffset - 1, leaf.entryOffset, phys);
|
|
179
|
+
leaf.entryOffset -= 1;
|
|
180
|
+
leaf.entries[phys - 1] = entry;
|
|
181
|
+
} else {
|
|
182
|
+
leaf.entries.splice(phys, 0, entry);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var leafCompact = (leaf) => {
|
|
186
|
+
if (leaf.entryOffset > 0) {
|
|
187
|
+
leaf.entries.copyWithin(0, leaf.entryOffset);
|
|
188
|
+
leaf.entries.length = leaf.entries.length - leaf.entryOffset;
|
|
189
|
+
leaf.entryOffset = 0;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var BRANCH_COMPACT_GAP = 1;
|
|
193
|
+
var branchCompact = (branch) => {
|
|
194
|
+
if (branch.childOffset > 0) {
|
|
195
|
+
const gap = branch.childOffset <= BRANCH_COMPACT_GAP ? 0 : BRANCH_COMPACT_GAP;
|
|
196
|
+
branch.children.copyWithin(gap, branch.childOffset);
|
|
197
|
+
branch.children.length -= branch.childOffset - gap;
|
|
198
|
+
branch.keys.copyWithin(gap, branch.childOffset);
|
|
199
|
+
branch.keys.length -= branch.childOffset - gap;
|
|
200
|
+
branch.childOffset = gap;
|
|
201
|
+
for (let i = gap; i < branch.children.length; i += 1) {
|
|
202
|
+
branch.children[i].indexInParent = i;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var branchChildCount = (branch) => branch.children.length - branch.childOffset;
|
|
207
|
+
var branchInsertAt = (branch, logicalIndex, child, key) => {
|
|
208
|
+
const phys = branch.childOffset + logicalIndex;
|
|
209
|
+
const count = branch.children.length - branch.childOffset;
|
|
210
|
+
if (branch.childOffset > 0 && logicalIndex < count >>> 1) {
|
|
211
|
+
branch.children.copyWithin(branch.childOffset - 1, branch.childOffset, phys);
|
|
212
|
+
branch.keys.copyWithin(branch.childOffset - 1, branch.childOffset, phys);
|
|
213
|
+
branch.childOffset -= 1;
|
|
214
|
+
branch.children[phys - 1] = child;
|
|
215
|
+
branch.keys[phys - 1] = key;
|
|
216
|
+
for (let i = branch.childOffset; i < phys; i += 1) {
|
|
217
|
+
branch.children[i].indexInParent = i;
|
|
218
|
+
}
|
|
219
|
+
child.indexInParent = phys - 1;
|
|
220
|
+
} else {
|
|
221
|
+
branch.children.splice(phys, 0, child);
|
|
222
|
+
branch.keys.splice(phys, 0, key);
|
|
223
|
+
for (let i = phys; i < branch.children.length; i += 1) {
|
|
224
|
+
branch.children[i].indexInParent = i;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var branchRemoveAt = (branch, physIndex) => {
|
|
229
|
+
const logicalIndex = physIndex - branch.childOffset;
|
|
230
|
+
const count = branch.children.length - branch.childOffset;
|
|
231
|
+
if (logicalIndex < count - 1 - logicalIndex) {
|
|
232
|
+
branch.children.copyWithin(branch.childOffset + 1, branch.childOffset, physIndex);
|
|
233
|
+
branch.keys.copyWithin(branch.childOffset + 1, branch.childOffset, physIndex);
|
|
234
|
+
branch.childOffset += 1;
|
|
235
|
+
for (let i = branch.childOffset; i <= physIndex; i += 1) {
|
|
236
|
+
branch.children[i].indexInParent = i;
|
|
237
|
+
}
|
|
238
|
+
if (branch.childOffset >= branch.children.length >>> 1) {
|
|
239
|
+
branchCompact(branch);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
branch.children.copyWithin(physIndex, physIndex + 1);
|
|
243
|
+
branch.keys.copyWithin(physIndex, physIndex + 1);
|
|
244
|
+
branch.children.length -= 1;
|
|
245
|
+
branch.keys.length -= 1;
|
|
246
|
+
for (let i = physIndex; i < branch.children.length; i += 1) {
|
|
247
|
+
branch.children[i].indexInParent = i;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/btree/navigation.ts
|
|
253
|
+
var selectBranchChild = (compare, branch, userKey, sequence) => {
|
|
254
|
+
const off = branch.childOffset;
|
|
255
|
+
if (off >= branch.children.length) {
|
|
256
|
+
throw new BTreeInvariantError("branch has no children");
|
|
257
|
+
}
|
|
258
|
+
let selectedIndex = off;
|
|
259
|
+
let lower = off;
|
|
260
|
+
let upper = branch.keys.length - 1;
|
|
261
|
+
while (lower <= upper) {
|
|
262
|
+
const mid = lower + upper >>> 1;
|
|
263
|
+
const k = branch.keys[mid];
|
|
264
|
+
const cmp = compare(k.key, userKey);
|
|
265
|
+
if ((cmp !== 0 ? cmp : k.sequence - sequence) <= 0) {
|
|
266
|
+
selectedIndex = mid;
|
|
267
|
+
lower = mid + 1;
|
|
268
|
+
} else {
|
|
269
|
+
upper = mid - 1;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return branch.children[selectedIndex];
|
|
273
|
+
};
|
|
274
|
+
var findLeafForKey = (state, userKey, sequence) => {
|
|
275
|
+
const compare = state.compareKeys;
|
|
276
|
+
let cursor = state.root;
|
|
277
|
+
while (cursor.kind !== NODE_LEAF) {
|
|
278
|
+
cursor = selectBranchChild(compare, cursor, userKey, sequence);
|
|
279
|
+
}
|
|
280
|
+
return cursor;
|
|
281
|
+
};
|
|
282
|
+
var lowerBoundInLeaf = (state, leaf, userKey, sequence) => {
|
|
283
|
+
const compare = state.compareKeys;
|
|
284
|
+
let lower = leaf.entryOffset;
|
|
285
|
+
let upper = leaf.entries.length;
|
|
286
|
+
while (lower < upper) {
|
|
287
|
+
const mid = lower + upper >>> 1;
|
|
288
|
+
const e = leaf.entries[mid];
|
|
289
|
+
const cmp = compare(e.key, userKey);
|
|
290
|
+
if ((cmp !== 0 ? cmp : e.entryId - sequence) < 0) {
|
|
291
|
+
lower = mid + 1;
|
|
292
|
+
} else {
|
|
293
|
+
upper = mid;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return lower - leaf.entryOffset;
|
|
297
|
+
};
|
|
298
|
+
var upperBoundInLeaf = (state, leaf, userKey, sequence) => {
|
|
299
|
+
const compare = state.compareKeys;
|
|
300
|
+
let lower = leaf.entryOffset;
|
|
301
|
+
let upper = leaf.entries.length;
|
|
302
|
+
while (lower < upper) {
|
|
303
|
+
const mid = lower + upper >>> 1;
|
|
304
|
+
const e = leaf.entries[mid];
|
|
305
|
+
const cmp = compare(e.key, userKey);
|
|
306
|
+
if ((cmp !== 0 ? cmp : e.entryId - sequence) <= 0) {
|
|
307
|
+
lower = mid + 1;
|
|
308
|
+
} else {
|
|
309
|
+
upper = mid;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return lower - leaf.entryOffset;
|
|
313
|
+
};
|
|
314
|
+
var findLeafFromHint = (state, hint, userKey, sequence) => {
|
|
315
|
+
const compare = state.compareKeys;
|
|
316
|
+
let leaf = hint;
|
|
317
|
+
let budget = 32 - Math.clz32(state.entryCount + 1);
|
|
318
|
+
while (budget > 0 && leaf.next !== null && leafEntryCount(leaf.next) > 0) {
|
|
319
|
+
const first = leafEntryAt(leaf.next, 0);
|
|
320
|
+
const cmp = compare(first.key, userKey);
|
|
321
|
+
if (cmp > 0 || cmp === 0 && first.entryId > sequence) break;
|
|
322
|
+
leaf = leaf.next;
|
|
323
|
+
budget -= 1;
|
|
324
|
+
}
|
|
325
|
+
if (budget === 0 && leaf.next !== null && leafEntryCount(leaf.next) > 0) {
|
|
326
|
+
const first = leafEntryAt(leaf.next, 0);
|
|
327
|
+
const cmp = compare(first.key, userKey);
|
|
328
|
+
if (cmp < 0 || cmp === 0 && first.entryId <= sequence) {
|
|
329
|
+
return findLeafForKey(state, userKey, sequence);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return leaf;
|
|
333
|
+
};
|
|
334
|
+
var findFirstMatchingUserKey = (state, key) => {
|
|
335
|
+
if (state.entryCount === 0) return null;
|
|
336
|
+
let leaf = findLeafForKey(state, key, 0);
|
|
337
|
+
let idx = lowerBoundInLeaf(state, leaf, key, 0);
|
|
338
|
+
if (idx >= leafEntryCount(leaf)) {
|
|
339
|
+
if (leaf.next === null) return null;
|
|
340
|
+
leaf = leaf.next;
|
|
341
|
+
idx = lowerBoundInLeaf(state, leaf, key, 0);
|
|
342
|
+
if (idx >= leafEntryCount(leaf)) return null;
|
|
343
|
+
}
|
|
344
|
+
if (state.compareKeys(leafEntryAt(leaf, idx).key, key) !== 0) return null;
|
|
345
|
+
const cursor = state._cursor;
|
|
346
|
+
cursor.leaf = leaf;
|
|
347
|
+
cursor.index = idx;
|
|
348
|
+
return cursor;
|
|
349
|
+
};
|
|
350
|
+
var findLastMatchingUserKey = (state, key) => {
|
|
351
|
+
if (state.entryCount === 0) return null;
|
|
352
|
+
let leaf = findLeafForKey(state, key, Number.MAX_SAFE_INTEGER);
|
|
353
|
+
let idx = upperBoundInLeaf(state, leaf, key, Number.MAX_SAFE_INTEGER);
|
|
354
|
+
if (idx === 0) {
|
|
355
|
+
if (leaf.prev === null) return null;
|
|
356
|
+
leaf = leaf.prev;
|
|
357
|
+
idx = leafEntryCount(leaf);
|
|
358
|
+
if (idx === 0) return null;
|
|
359
|
+
}
|
|
360
|
+
idx -= 1;
|
|
361
|
+
if (state.compareKeys(leafEntryAt(leaf, idx).key, key) !== 0) return null;
|
|
362
|
+
const cursor = state._cursor;
|
|
363
|
+
cursor.leaf = leaf;
|
|
364
|
+
cursor.index = idx;
|
|
365
|
+
return cursor;
|
|
366
|
+
};
|
|
367
|
+
var hasKeyEntry = (state, key) => {
|
|
368
|
+
return findFirstMatchingUserKey(state, key) !== null;
|
|
369
|
+
};
|
|
370
|
+
var findNextHigherKey = (state, key) => {
|
|
371
|
+
if (state.entryCount === 0) return null;
|
|
372
|
+
const compare = state.compareKeys;
|
|
373
|
+
let leaf = findLeafForKey(state, key, Number.MAX_SAFE_INTEGER);
|
|
374
|
+
let idx = upperBoundInLeaf(state, leaf, key, Number.MAX_SAFE_INTEGER);
|
|
375
|
+
while (leaf !== null) {
|
|
376
|
+
if (idx < leafEntryCount(leaf)) {
|
|
377
|
+
const e = leafEntryAt(leaf, idx);
|
|
378
|
+
if (compare(e.key, key) > 0) return e.key;
|
|
379
|
+
idx += 1;
|
|
380
|
+
} else {
|
|
381
|
+
leaf = leaf.next;
|
|
382
|
+
idx = 0;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
};
|
|
387
|
+
var findNextLowerKey = (state, key) => {
|
|
388
|
+
if (state.entryCount === 0) return null;
|
|
389
|
+
const compare = state.compareKeys;
|
|
390
|
+
const leaf = findLeafForKey(state, key, 0);
|
|
391
|
+
const idx = lowerBoundInLeaf(state, leaf, key, 0);
|
|
392
|
+
let scanLeaf = leaf;
|
|
393
|
+
let scanIdx = idx - 1;
|
|
394
|
+
while (scanLeaf !== null) {
|
|
395
|
+
while (scanIdx >= 0) {
|
|
396
|
+
const e = leafEntryAt(scanLeaf, scanIdx);
|
|
397
|
+
if (compare(e.key, key) < 0) return e.key;
|
|
398
|
+
scanIdx -= 1;
|
|
399
|
+
}
|
|
400
|
+
scanLeaf = scanLeaf.prev;
|
|
401
|
+
if (scanLeaf !== null) {
|
|
402
|
+
scanIdx = leafEntryCount(scanLeaf) - 1;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
};
|
|
407
|
+
var findPairOrNextLower = (state, key) => {
|
|
408
|
+
if (state.entryCount === 0) return null;
|
|
409
|
+
const compare = state.compareKeys;
|
|
410
|
+
const leaf = findLeafForKey(state, key, 0);
|
|
411
|
+
const idx = lowerBoundInLeaf(state, leaf, key, 0);
|
|
412
|
+
const cursor = state._cursor;
|
|
413
|
+
if (idx < leafEntryCount(leaf)) {
|
|
414
|
+
if (compare(leafEntryAt(leaf, idx).key, key) === 0) {
|
|
415
|
+
cursor.leaf = leaf;
|
|
416
|
+
cursor.index = idx;
|
|
417
|
+
return cursor;
|
|
418
|
+
}
|
|
419
|
+
} else if (leaf.next !== null) {
|
|
420
|
+
const nextIdx = lowerBoundInLeaf(state, leaf.next, key, 0);
|
|
421
|
+
if (nextIdx < leafEntryCount(leaf.next) && compare(leafEntryAt(leaf.next, nextIdx).key, key) === 0) {
|
|
422
|
+
cursor.leaf = leaf.next;
|
|
423
|
+
cursor.index = nextIdx;
|
|
424
|
+
return cursor;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (idx > 0) {
|
|
428
|
+
cursor.leaf = leaf;
|
|
429
|
+
cursor.index = idx - 1;
|
|
430
|
+
return cursor;
|
|
431
|
+
}
|
|
432
|
+
if (leaf.prev !== null) {
|
|
433
|
+
const prevCount = leafEntryCount(leaf.prev);
|
|
434
|
+
if (prevCount > 0) {
|
|
435
|
+
cursor.leaf = leaf.prev;
|
|
436
|
+
cursor.index = prevCount - 1;
|
|
437
|
+
return cursor;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/btree/rebalance.ts
|
|
444
|
+
var updateMinKeyInAncestors = (node) => {
|
|
445
|
+
let current = node;
|
|
446
|
+
while (current.parent !== null) {
|
|
447
|
+
const idx = current.indexInParent;
|
|
448
|
+
if (!writeMinKeyTo(current, current.parent.keys[idx])) return;
|
|
449
|
+
if (idx !== current.parent.childOffset) return;
|
|
450
|
+
current = current.parent;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var requireParent = (node) => {
|
|
454
|
+
if (node.parent === null) {
|
|
455
|
+
throw new BTreeInvariantError("no parent during rebalance");
|
|
456
|
+
}
|
|
457
|
+
return node.parent;
|
|
458
|
+
};
|
|
459
|
+
var requireLeafNode = (node) => {
|
|
460
|
+
if (!isLeafNode(node)) throw new BTreeInvariantError("expected leaf, got branch");
|
|
461
|
+
return node;
|
|
462
|
+
};
|
|
463
|
+
var requireBranchNode = (node) => {
|
|
464
|
+
if (isLeafNode(node)) throw new BTreeInvariantError("expected branch, got leaf");
|
|
465
|
+
return node;
|
|
466
|
+
};
|
|
467
|
+
var removeChildFromBranch = (branch, childIndex) => {
|
|
468
|
+
if (childIndex < branch.childOffset || childIndex >= branch.children.length) {
|
|
469
|
+
throw new BTreeInvariantError("child index out of range");
|
|
470
|
+
}
|
|
471
|
+
branchRemoveAt(branch, childIndex);
|
|
472
|
+
};
|
|
473
|
+
var detachLeafFromChain = (state, leaf) => {
|
|
474
|
+
if (leaf.prev !== null) {
|
|
475
|
+
leaf.prev.next = leaf.next;
|
|
476
|
+
} else if (leaf.next !== null) {
|
|
477
|
+
state.leftmostLeaf = leaf.next;
|
|
478
|
+
}
|
|
479
|
+
if (leaf.next !== null) {
|
|
480
|
+
leaf.next.prev = leaf.prev;
|
|
481
|
+
} else if (leaf.prev !== null) {
|
|
482
|
+
state.rightmostLeaf = leaf.prev;
|
|
483
|
+
}
|
|
484
|
+
leaf.prev = null;
|
|
485
|
+
leaf.next = null;
|
|
486
|
+
};
|
|
487
|
+
var borrowFromLeftBranch = (branch, leftSibling, branchIndex) => {
|
|
488
|
+
const borrowedChild = leftSibling.children.pop();
|
|
489
|
+
if (borrowedChild === void 0) throw new BTreeInvariantError("left branch borrow failed");
|
|
490
|
+
leftSibling.keys.pop();
|
|
491
|
+
borrowedChild.parent = branch;
|
|
492
|
+
const borrowedMinKey = { key: void 0, sequence: 0 };
|
|
493
|
+
if (!writeMinKeyTo(borrowedChild, borrowedMinKey)) throw new BTreeInvariantError("borrowed child has no min key");
|
|
494
|
+
if (branch.childOffset > 0) {
|
|
495
|
+
branch.childOffset -= 1;
|
|
496
|
+
branch.children[branch.childOffset] = borrowedChild;
|
|
497
|
+
branch.keys[branch.childOffset] = borrowedMinKey;
|
|
498
|
+
borrowedChild.indexInParent = branch.childOffset;
|
|
499
|
+
} else {
|
|
500
|
+
branch.children.unshift(borrowedChild);
|
|
501
|
+
branch.keys.unshift(borrowedMinKey);
|
|
502
|
+
for (let i = 0; i < branch.children.length; i += 1) branch.children[i].indexInParent = i;
|
|
503
|
+
}
|
|
504
|
+
const parent = requireParent(branch);
|
|
505
|
+
parent.keys[branchIndex] = { key: borrowedMinKey.key, sequence: borrowedMinKey.sequence };
|
|
506
|
+
updateMinKeyInAncestors(branch);
|
|
507
|
+
};
|
|
508
|
+
var borrowFromRightBranch = (branch, rightSibling, branchIndex) => {
|
|
509
|
+
const shiftIdx = rightSibling.childOffset;
|
|
510
|
+
if (shiftIdx >= rightSibling.children.length) throw new BTreeInvariantError("right branch borrow failed");
|
|
511
|
+
const borrowedChild = rightSibling.children[shiftIdx];
|
|
512
|
+
rightSibling.childOffset += 1;
|
|
513
|
+
if (rightSibling.childOffset >= rightSibling.children.length >>> 1) {
|
|
514
|
+
branchCompact(rightSibling);
|
|
515
|
+
}
|
|
516
|
+
branch.children.push(borrowedChild);
|
|
517
|
+
borrowedChild.parent = branch;
|
|
518
|
+
const borrowedMinKey = { key: void 0, sequence: 0 };
|
|
519
|
+
if (!writeMinKeyTo(borrowedChild, borrowedMinKey)) throw new BTreeInvariantError("borrowed child has no min key");
|
|
520
|
+
branch.keys.push(borrowedMinKey);
|
|
521
|
+
borrowedChild.indexInParent = branch.children.length - 1;
|
|
522
|
+
const parent = requireParent(branch);
|
|
523
|
+
writeMinKeyTo(rightSibling, parent.keys[branchIndex + 1]);
|
|
524
|
+
};
|
|
525
|
+
var mergeBranchIntoLeft = (state, branch, leftSibling, branchIndex) => {
|
|
526
|
+
for (let i = branch.childOffset; i < branch.children.length; i += 1) {
|
|
527
|
+
const child = branch.children[i];
|
|
528
|
+
child.parent = leftSibling;
|
|
529
|
+
child.indexInParent = leftSibling.children.length;
|
|
530
|
+
leftSibling.children.push(child);
|
|
531
|
+
leftSibling.keys.push(branch.keys[i]);
|
|
532
|
+
}
|
|
533
|
+
const parent = requireParent(branch);
|
|
534
|
+
removeChildFromBranch(parent, branchIndex);
|
|
535
|
+
rebalanceAfterBranchRemoval(state, parent);
|
|
536
|
+
};
|
|
537
|
+
var mergeBranchIntoRight = (state, branch, rightSibling, branchIndex) => {
|
|
538
|
+
for (let i = rightSibling.childOffset; i < rightSibling.children.length; i += 1) {
|
|
539
|
+
const child = rightSibling.children[i];
|
|
540
|
+
child.parent = branch;
|
|
541
|
+
child.indexInParent = branch.children.length;
|
|
542
|
+
branch.children.push(child);
|
|
543
|
+
branch.keys.push(rightSibling.keys[i]);
|
|
544
|
+
}
|
|
545
|
+
const parent = requireParent(branch);
|
|
546
|
+
removeChildFromBranch(parent, branchIndex + 1);
|
|
547
|
+
rebalanceAfterBranchRemoval(state, parent);
|
|
548
|
+
};
|
|
549
|
+
var findBranchSiblings = (parent, branchIndex) => {
|
|
550
|
+
const left = branchIndex > parent.childOffset ? requireBranchNode(parent.children[branchIndex - 1]) : null;
|
|
551
|
+
const right = branchIndex + 1 < parent.children.length ? requireBranchNode(parent.children[branchIndex + 1]) : null;
|
|
552
|
+
return { left, right };
|
|
553
|
+
};
|
|
554
|
+
var rebalanceAfterBranchRemoval = (state, branch) => {
|
|
555
|
+
const liveCount = branchChildCount(branch);
|
|
556
|
+
if (branch === state.root) {
|
|
557
|
+
if (liveCount === 1) {
|
|
558
|
+
const onlyChild = branch.children[branch.childOffset];
|
|
559
|
+
onlyChild.parent = null;
|
|
560
|
+
state.root = onlyChild;
|
|
561
|
+
if (isLeafNode(onlyChild)) {
|
|
562
|
+
state.leftmostLeaf = onlyChild;
|
|
563
|
+
state.rightmostLeaf = onlyChild;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (liveCount >= state.minBranchChildren) return;
|
|
569
|
+
const parent = branch.parent;
|
|
570
|
+
if (parent === null) throw new BTreeInvariantError("branch has no parent");
|
|
571
|
+
const branchIndex = branch.indexInParent;
|
|
572
|
+
const { left: leftSibling, right: rightSibling } = findBranchSiblings(parent, branchIndex);
|
|
573
|
+
if (rightSibling !== null && branchChildCount(rightSibling) > state.minBranchChildren) {
|
|
574
|
+
borrowFromRightBranch(branch, rightSibling, branchIndex);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (leftSibling !== null && branchChildCount(leftSibling) > state.minBranchChildren) {
|
|
578
|
+
borrowFromLeftBranch(branch, leftSibling, branchIndex);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (leftSibling !== null) {
|
|
582
|
+
mergeBranchIntoLeft(state, branch, leftSibling, branchIndex);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (rightSibling !== null) {
|
|
586
|
+
mergeBranchIntoRight(state, branch, rightSibling, branchIndex);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
throw new BTreeInvariantError("no branch siblings to rebalance");
|
|
590
|
+
};
|
|
591
|
+
var mergeLeafEntries = (target, source) => {
|
|
592
|
+
if (target.entryOffset > 0) {
|
|
593
|
+
target.entries.copyWithin(0, target.entryOffset);
|
|
594
|
+
target.entries.length = target.entries.length - target.entryOffset;
|
|
595
|
+
target.entryOffset = 0;
|
|
596
|
+
}
|
|
597
|
+
const src = source.entries;
|
|
598
|
+
for (let i = source.entryOffset; i < src.length; i += 1) target.entries.push(src[i]);
|
|
599
|
+
};
|
|
600
|
+
var rebalanceAfterLeafRemoval = (state, leaf) => {
|
|
601
|
+
if (leaf === state.root) {
|
|
602
|
+
if (state.entryCount === 0) {
|
|
603
|
+
state.leftmostLeaf = leaf;
|
|
604
|
+
state.rightmostLeaf = leaf;
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (leafEntryCount(leaf) >= state.minLeafEntries) return;
|
|
609
|
+
const parent = leaf.parent;
|
|
610
|
+
if (parent === null) throw new BTreeInvariantError("Leaf node has no parent during rebalance.");
|
|
611
|
+
const leafIndex = leaf.indexInParent;
|
|
612
|
+
const leftSibling = leafIndex > parent.childOffset ? requireLeafNode(parent.children[leafIndex - 1]) : null;
|
|
613
|
+
const rightSibling = leafIndex + 1 < parent.children.length ? requireLeafNode(parent.children[leafIndex + 1]) : null;
|
|
614
|
+
if (rightSibling !== null && leafEntryCount(rightSibling) > state.minLeafEntries) {
|
|
615
|
+
const borrowed = leafShiftEntry(rightSibling);
|
|
616
|
+
if (borrowed === void 0) throw new BTreeInvariantError("right leaf borrow failed");
|
|
617
|
+
leaf.entries.push(borrowed);
|
|
618
|
+
writeMinKeyTo(rightSibling, parent.keys[leafIndex + 1]);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (leftSibling !== null && leafEntryCount(leftSibling) > state.minLeafEntries) {
|
|
622
|
+
const borrowed = leftSibling.entries.pop();
|
|
623
|
+
if (borrowed === void 0) throw new BTreeInvariantError("left leaf borrow failed");
|
|
624
|
+
leafUnshiftEntry(leaf, borrowed);
|
|
625
|
+
parent.keys[leafIndex] = { key: borrowed.key, sequence: borrowed.entryId };
|
|
626
|
+
updateMinKeyInAncestors(leaf);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (leftSibling !== null) {
|
|
630
|
+
mergeLeafEntries(leftSibling, leaf);
|
|
631
|
+
detachLeafFromChain(state, leaf);
|
|
632
|
+
removeChildFromBranch(parent, leafIndex);
|
|
633
|
+
rebalanceAfterBranchRemoval(state, parent);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (rightSibling !== null) {
|
|
637
|
+
mergeLeafEntries(leaf, rightSibling);
|
|
638
|
+
detachLeafFromChain(state, rightSibling);
|
|
639
|
+
removeChildFromBranch(parent, leafIndex + 1);
|
|
640
|
+
rebalanceAfterBranchRemoval(state, parent);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
throw new BTreeInvariantError("no leaf siblings to rebalance");
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/btree/deleteRange.ts
|
|
647
|
+
var navigateToStart = (state, startKey, lowerExclusive) => {
|
|
648
|
+
const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
|
|
649
|
+
const leaf = findLeafForKey(state, startKey, startSeq);
|
|
650
|
+
const idx = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
|
|
651
|
+
if (idx >= leafEntryCount(leaf)) {
|
|
652
|
+
if (leaf.next === null) return null;
|
|
653
|
+
return { leaf: leaf.next, idx: 0 };
|
|
654
|
+
}
|
|
655
|
+
return { leaf, idx };
|
|
656
|
+
};
|
|
657
|
+
var findRemoveEnd = (state, leaf, idx, endKey, upperExclusive) => {
|
|
658
|
+
const count = leafEntryCount(leaf);
|
|
659
|
+
let removeEnd = idx;
|
|
660
|
+
while (removeEnd < count) {
|
|
661
|
+
const e = leafEntryAt(leaf, removeEnd);
|
|
662
|
+
const cmpEnd = state.compareKeys(e.key, endKey);
|
|
663
|
+
if (upperExclusive ? cmpEnd >= 0 : cmpEnd > 0) break;
|
|
664
|
+
removeEnd += 1;
|
|
665
|
+
}
|
|
666
|
+
return removeEnd;
|
|
667
|
+
};
|
|
668
|
+
var computeTreeHeight = (state) => {
|
|
669
|
+
let h = 0;
|
|
670
|
+
let n = state.root;
|
|
671
|
+
while (!isLeafNode(n)) {
|
|
672
|
+
n = n.children[n.childOffset];
|
|
673
|
+
h += 1;
|
|
674
|
+
}
|
|
675
|
+
return h;
|
|
676
|
+
};
|
|
677
|
+
var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth) => {
|
|
678
|
+
if (state.entryKeys !== null) {
|
|
679
|
+
for (let i = idx; i < idx + removeCount; i += 1) {
|
|
680
|
+
state.entryKeys.delete(leafEntryAt(leaf, i).entryId);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const phys = leaf.entryOffset + idx;
|
|
684
|
+
leaf.entries.copyWithin(phys, phys + removeCount);
|
|
685
|
+
leaf.entries.length -= removeCount;
|
|
686
|
+
state.entryCount -= removeCount;
|
|
687
|
+
const leafEmptied = leafEntryCount(leaf) === 0;
|
|
688
|
+
if (idx === 0 && !leafEmptied && leaf.parent !== null) {
|
|
689
|
+
updateMinKeyInAncestors(leaf);
|
|
690
|
+
}
|
|
691
|
+
const countAfterSplice = leafEntryCount(leaf);
|
|
692
|
+
let maxIter = maxRebalanceDepth;
|
|
693
|
+
while (maxIter > 0 && leaf !== state.root && leafEntryCount(leaf) < state.minLeafEntries) {
|
|
694
|
+
rebalanceAfterLeafRemoval(state, leaf);
|
|
695
|
+
if (leaf.parent !== null && leaf.parent.children[leaf.indexInParent] !== leaf) break;
|
|
696
|
+
maxIter -= 1;
|
|
697
|
+
}
|
|
698
|
+
if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
|
|
699
|
+
updateMinKeyInAncestors(leaf);
|
|
700
|
+
}
|
|
701
|
+
return countAfterSplice;
|
|
702
|
+
};
|
|
703
|
+
var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
|
|
704
|
+
var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
705
|
+
if (state.entryCount === 0) return 0;
|
|
706
|
+
const boundCompared = state.compareKeys(startKey, endKey);
|
|
707
|
+
if (boundCompared > 0) return 0;
|
|
708
|
+
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
709
|
+
const upperExclusive = options?.upperBound === "exclusive";
|
|
710
|
+
if (lowerExclusive && upperExclusive && boundCompared === 0) return 0;
|
|
711
|
+
const treeHeight = computeTreeHeight(state);
|
|
712
|
+
let deleted = 0;
|
|
713
|
+
let needsNavigate = true;
|
|
714
|
+
let leaf = null;
|
|
715
|
+
let idx = 0;
|
|
716
|
+
while (state.entryCount > 0) {
|
|
717
|
+
if (needsNavigate) {
|
|
718
|
+
const cursor = navigateToStart(state, startKey, lowerExclusive);
|
|
719
|
+
if (cursor === null) break;
|
|
720
|
+
leaf = cursor.leaf;
|
|
721
|
+
idx = cursor.idx;
|
|
722
|
+
needsNavigate = false;
|
|
723
|
+
}
|
|
724
|
+
if (idx >= leafEntryCount(leaf)) break;
|
|
725
|
+
const count = leafEntryCount(leaf);
|
|
726
|
+
const removeEnd = findRemoveEnd(state, leaf, idx, endKey, upperExclusive);
|
|
727
|
+
const removeCount = removeEnd - idx;
|
|
728
|
+
if (removeCount === 0) break;
|
|
729
|
+
const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount, treeHeight);
|
|
730
|
+
deleted += removeCount;
|
|
731
|
+
if (removeEnd < count) break;
|
|
732
|
+
if (!isLeafStillValid(state, leaf)) {
|
|
733
|
+
needsNavigate = true;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (leafEntryCount(leaf) > countAfterSplice) {
|
|
737
|
+
needsNavigate = true;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (leaf.next === null) break;
|
|
741
|
+
leaf = leaf.next;
|
|
742
|
+
idx = 0;
|
|
743
|
+
}
|
|
744
|
+
return deleted;
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// src/btree/autoScale.ts
|
|
748
|
+
var AUTO_SCALE_TIERS = [
|
|
749
|
+
{ threshold: 0, maxLeaf: 32, maxBranch: 32 },
|
|
750
|
+
{ threshold: 1e3, maxLeaf: 64, maxBranch: 64 },
|
|
751
|
+
{ threshold: 1e4, maxLeaf: 128, maxBranch: 128 },
|
|
752
|
+
{ threshold: 1e5, maxLeaf: 256, maxBranch: 128 },
|
|
753
|
+
{ threshold: 1e6, maxLeaf: 512, maxBranch: 256 }
|
|
754
|
+
];
|
|
755
|
+
var computeAutoScaleTier = (entryCount) => {
|
|
756
|
+
let tier = AUTO_SCALE_TIERS[0];
|
|
757
|
+
for (let i = 1; i < AUTO_SCALE_TIERS.length; i += 1) {
|
|
758
|
+
if (entryCount >= AUTO_SCALE_TIERS[i].threshold) {
|
|
759
|
+
tier = AUTO_SCALE_TIERS[i];
|
|
760
|
+
} else {
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return tier;
|
|
765
|
+
};
|
|
766
|
+
var computeNextAutoScaleThreshold = (entryCount) => {
|
|
767
|
+
for (let i = 1; i < AUTO_SCALE_TIERS.length; i += 1) {
|
|
768
|
+
if (entryCount < AUTO_SCALE_TIERS[i].threshold) {
|
|
769
|
+
return AUTO_SCALE_TIERS[i].threshold;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return Number.MAX_SAFE_INTEGER;
|
|
773
|
+
};
|
|
774
|
+
var createInitialState = (config) => {
|
|
775
|
+
if (typeof config.compareKeys !== "function") {
|
|
776
|
+
throw new BTreeValidationError("compareKeys must be a function.");
|
|
777
|
+
}
|
|
778
|
+
const autoScale = config.autoScale === true;
|
|
779
|
+
if (autoScale && (config.maxLeafEntries !== void 0 || config.maxBranchChildren !== void 0)) {
|
|
780
|
+
throw new BTreeValidationError("autoScale conflicts with explicit capacity.");
|
|
781
|
+
}
|
|
782
|
+
let maxLeafEntries;
|
|
783
|
+
let maxBranchChildren;
|
|
784
|
+
if (autoScale) {
|
|
785
|
+
const tier = computeAutoScaleTier(0);
|
|
786
|
+
maxLeafEntries = tier.maxLeaf;
|
|
787
|
+
maxBranchChildren = tier.maxBranch;
|
|
788
|
+
} else {
|
|
789
|
+
maxLeafEntries = normalizeNodeCapacity(config.maxLeafEntries, "maxLeafEntries", DEFAULT_MAX_LEAF_ENTRIES);
|
|
790
|
+
maxBranchChildren = normalizeNodeCapacity(config.maxBranchChildren, "maxBranchChildren", DEFAULT_MAX_BRANCH_CHILDREN);
|
|
791
|
+
}
|
|
792
|
+
const duplicateKeys = normalizeDuplicateKeyPolicy(config.duplicateKeys);
|
|
793
|
+
const emptyLeaf = createLeafNode([], null);
|
|
794
|
+
return {
|
|
795
|
+
compareKeys: config.compareKeys,
|
|
796
|
+
maxLeafEntries,
|
|
797
|
+
maxBranchChildren,
|
|
798
|
+
duplicateKeys,
|
|
799
|
+
minLeafEntries: Math.ceil(maxLeafEntries / 2),
|
|
800
|
+
minBranchChildren: Math.ceil(maxBranchChildren / 2),
|
|
801
|
+
root: emptyLeaf,
|
|
802
|
+
leftmostLeaf: emptyLeaf,
|
|
803
|
+
rightmostLeaf: emptyLeaf,
|
|
804
|
+
entryCount: 0,
|
|
805
|
+
nextSequence: 0,
|
|
806
|
+
entryKeys: config.enableEntryIdLookup === true ? /* @__PURE__ */ new Map() : null,
|
|
807
|
+
autoScale,
|
|
808
|
+
_nextAutoScaleThreshold: autoScale ? computeNextAutoScaleThreshold(0) : Number.MAX_SAFE_INTEGER,
|
|
809
|
+
_cursor: { leaf: emptyLeaf, index: 0 }
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
var maybeAutoScale = (state) => {
|
|
813
|
+
if (state.entryCount < state._nextAutoScaleThreshold) return;
|
|
814
|
+
const { maxLeaf, maxBranch } = computeAutoScaleTier(state.entryCount);
|
|
815
|
+
if (maxLeaf > state.maxLeafEntries) {
|
|
816
|
+
state.maxLeafEntries = maxLeaf;
|
|
817
|
+
state.minLeafEntries = Math.ceil(maxLeaf / 2);
|
|
818
|
+
}
|
|
819
|
+
if (maxBranch > state.maxBranchChildren) {
|
|
820
|
+
state.maxBranchChildren = maxBranch;
|
|
821
|
+
state.minBranchChildren = Math.ceil(maxBranch / 2);
|
|
822
|
+
}
|
|
823
|
+
state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(state.entryCount);
|
|
824
|
+
};
|
|
825
|
+
var applyAutoScaleCapacitySnapshot = (state, maxLeafEntries, maxBranchChildren) => {
|
|
826
|
+
if (!state.autoScale) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const baseTier = computeAutoScaleTier(0);
|
|
830
|
+
const normalizedLeaf = normalizeNodeCapacity(
|
|
831
|
+
maxLeafEntries,
|
|
832
|
+
"maxLeafEntries",
|
|
833
|
+
DEFAULT_MAX_LEAF_ENTRIES
|
|
834
|
+
);
|
|
835
|
+
const normalizedBranch = normalizeNodeCapacity(
|
|
836
|
+
maxBranchChildren,
|
|
837
|
+
"maxBranchChildren",
|
|
838
|
+
DEFAULT_MAX_BRANCH_CHILDREN
|
|
839
|
+
);
|
|
840
|
+
if (normalizedLeaf < baseTier.maxLeaf || normalizedBranch < baseTier.maxBranch) {
|
|
841
|
+
throw new BTreeValidationError(
|
|
842
|
+
"autoScale capacity snapshot must be >= tier-0 capacities."
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
state.maxLeafEntries = normalizedLeaf;
|
|
846
|
+
state.maxBranchChildren = normalizedBranch;
|
|
847
|
+
state.minLeafEntries = Math.ceil(normalizedLeaf / 2);
|
|
848
|
+
state.minBranchChildren = Math.ceil(normalizedBranch / 2);
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/btree/bulkLoad.ts
|
|
852
|
+
var computeChunkBoundaries = (total, max, min) => {
|
|
853
|
+
const boundaries = [];
|
|
854
|
+
let offset = 0;
|
|
855
|
+
while (offset < total) {
|
|
856
|
+
const remaining = total - offset;
|
|
857
|
+
if (remaining > max && remaining - max < min) {
|
|
858
|
+
const half = Math.ceil(remaining / 2);
|
|
859
|
+
boundaries.push(offset + half);
|
|
860
|
+
boundaries.push(total);
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
const end = offset + max < total ? offset + max : total;
|
|
864
|
+
boundaries.push(end);
|
|
865
|
+
offset = end;
|
|
866
|
+
}
|
|
867
|
+
return boundaries;
|
|
868
|
+
};
|
|
869
|
+
var buildLeaves = (state, entries, ids, baseSequence) => {
|
|
870
|
+
const boundaries = computeChunkBoundaries(entries.length, state.maxLeafEntries, state.minLeafEntries);
|
|
871
|
+
const leaves = new Array(boundaries.length);
|
|
872
|
+
let chunkStart = 0;
|
|
873
|
+
for (let c = 0; c < boundaries.length; c += 1) {
|
|
874
|
+
const chunkEnd = boundaries[c];
|
|
875
|
+
const chunk = new Array(chunkEnd - chunkStart);
|
|
876
|
+
for (let i = chunkStart; i < chunkEnd; i += 1) {
|
|
877
|
+
const seq = baseSequence + i;
|
|
878
|
+
chunk[i - chunkStart] = { key: entries[i].key, entryId: seq, value: entries[i].value };
|
|
879
|
+
ids[i] = seq;
|
|
880
|
+
if (state.entryKeys !== null) {
|
|
881
|
+
state.entryKeys.set(seq, entries[i].key);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
leaves[c] = createLeafNode(chunk, null);
|
|
885
|
+
chunkStart = chunkEnd;
|
|
886
|
+
}
|
|
887
|
+
return leaves;
|
|
888
|
+
};
|
|
889
|
+
var bulkLoadEntries = (state, entries) => {
|
|
890
|
+
if (state.entryCount !== 0) {
|
|
891
|
+
throw new BTreeInvariantError("bulk load requires empty tree");
|
|
892
|
+
}
|
|
893
|
+
const baseSequence = state.nextSequence;
|
|
894
|
+
if (baseSequence + entries.length > Number.MAX_SAFE_INTEGER) {
|
|
895
|
+
throw new BTreeValidationError("Sequence overflow.");
|
|
896
|
+
}
|
|
897
|
+
const ids = new Array(entries.length);
|
|
898
|
+
const leaves = buildLeaves(state, entries, ids, baseSequence);
|
|
899
|
+
state.nextSequence = baseSequence + entries.length;
|
|
900
|
+
state.entryCount = entries.length;
|
|
901
|
+
for (let i = 0; i < leaves.length; i += 1) {
|
|
902
|
+
if (i > 0) leaves[i].prev = leaves[i - 1];
|
|
903
|
+
if (i < leaves.length - 1) leaves[i].next = leaves[i + 1];
|
|
904
|
+
}
|
|
905
|
+
state.leftmostLeaf = leaves[0];
|
|
906
|
+
state.rightmostLeaf = leaves[leaves.length - 1];
|
|
907
|
+
if (leaves.length === 1) {
|
|
908
|
+
state.root = leaves[0];
|
|
909
|
+
} else {
|
|
910
|
+
let currentLevel = leaves;
|
|
911
|
+
while (currentLevel.length > 1) {
|
|
912
|
+
const bounds = computeChunkBoundaries(currentLevel.length, state.maxBranchChildren, state.minBranchChildren);
|
|
913
|
+
const nextLevel = new Array(bounds.length);
|
|
914
|
+
let start = 0;
|
|
915
|
+
for (let b = 0; b < bounds.length; b += 1) {
|
|
916
|
+
nextLevel[b] = createBranchNode(currentLevel.slice(start, bounds[b]), null);
|
|
917
|
+
start = bounds[b];
|
|
918
|
+
}
|
|
919
|
+
currentLevel = nextLevel;
|
|
920
|
+
}
|
|
921
|
+
state.root = currentLevel[0];
|
|
922
|
+
}
|
|
923
|
+
maybeAutoScale(state);
|
|
924
|
+
return ids;
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// src/btree/mutations.ts
|
|
928
|
+
var insertChildAfter = (state, parent, existingChild, childToInsert) => {
|
|
929
|
+
const newChildMinKey = { key: void 0, sequence: 0 };
|
|
930
|
+
if (!writeMinKeyTo(childToInsert, newChildMinKey)) {
|
|
931
|
+
throw new BTreeInvariantError("inserted child has no min key");
|
|
932
|
+
}
|
|
933
|
+
childToInsert.parent = parent;
|
|
934
|
+
const logicalIndex = existingChild.indexInParent - parent.childOffset + 1;
|
|
935
|
+
branchInsertAt(parent, logicalIndex, childToInsert, newChildMinKey);
|
|
936
|
+
if (branchChildCount(parent) > state.maxBranchChildren) {
|
|
937
|
+
splitBranch(state, parent);
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
var splitLeaf = (state, leaf) => {
|
|
941
|
+
leafCompact(leaf);
|
|
942
|
+
const splitAt = Math.ceil(leaf.entries.length / 2);
|
|
943
|
+
const sibling = {
|
|
944
|
+
kind: NODE_LEAF,
|
|
945
|
+
entries: leaf.entries.splice(splitAt),
|
|
946
|
+
entryOffset: 0,
|
|
947
|
+
parent: leaf.parent,
|
|
948
|
+
indexInParent: 0,
|
|
949
|
+
prev: leaf,
|
|
950
|
+
next: leaf.next
|
|
951
|
+
};
|
|
952
|
+
if (leaf.next !== null) {
|
|
953
|
+
leaf.next.prev = sibling;
|
|
954
|
+
} else {
|
|
955
|
+
state.rightmostLeaf = sibling;
|
|
956
|
+
}
|
|
957
|
+
leaf.next = sibling;
|
|
958
|
+
if (leaf.parent === null) {
|
|
959
|
+
state.root = createBranchNode([leaf, sibling], null);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
insertChildAfter(state, leaf.parent, leaf, sibling);
|
|
963
|
+
};
|
|
964
|
+
var splitBranch = (state, branch) => {
|
|
965
|
+
branchCompact(branch);
|
|
966
|
+
const splitAt = Math.ceil(branch.children.length / 2);
|
|
967
|
+
const sibling = createBranchNode(branch.children.splice(splitAt), branch.parent);
|
|
968
|
+
branch.keys.splice(splitAt);
|
|
969
|
+
if (branch.parent === null) {
|
|
970
|
+
state.root = createBranchNode([branch, sibling], null);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
insertChildAfter(state, branch.parent, branch, sibling);
|
|
974
|
+
};
|
|
975
|
+
var putEntryIntoLeaf = (state, targetLeaf, key, value) => {
|
|
976
|
+
const sequence = state.nextSequence;
|
|
977
|
+
const insertAt = upperBoundInLeaf(state, targetLeaf, key, sequence);
|
|
978
|
+
if (state.duplicateKeys !== "allow") {
|
|
979
|
+
let existingEntry = null;
|
|
980
|
+
if (insertAt > 0) {
|
|
981
|
+
const candidate = leafEntryAt(targetLeaf, insertAt - 1);
|
|
982
|
+
if (state.compareKeys(candidate.key, key) === 0) {
|
|
983
|
+
existingEntry = candidate;
|
|
984
|
+
}
|
|
985
|
+
} else if (targetLeaf.prev !== null && leafEntryCount(targetLeaf.prev) > 0) {
|
|
986
|
+
const prevLeaf = targetLeaf.prev;
|
|
987
|
+
const candidate = leafEntryAt(prevLeaf, leafEntryCount(prevLeaf) - 1);
|
|
988
|
+
if (state.compareKeys(candidate.key, key) === 0) {
|
|
989
|
+
existingEntry = candidate;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (existingEntry !== null) {
|
|
993
|
+
if (state.duplicateKeys === "reject") {
|
|
994
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
995
|
+
}
|
|
996
|
+
existingEntry.value = value;
|
|
997
|
+
return existingEntry.entryId;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (state.nextSequence >= Number.MAX_SAFE_INTEGER) {
|
|
1001
|
+
throw new BTreeValidationError("Sequence overflow.");
|
|
1002
|
+
}
|
|
1003
|
+
state.nextSequence += 1;
|
|
1004
|
+
leafInsertAt(targetLeaf, insertAt, { key, entryId: sequence, value });
|
|
1005
|
+
state.entryCount += 1;
|
|
1006
|
+
if (state.entryKeys !== null) {
|
|
1007
|
+
state.entryKeys.set(sequence, key);
|
|
1008
|
+
}
|
|
1009
|
+
if (insertAt === 0 && targetLeaf.parent !== null) updateMinKeyInAncestors(targetLeaf);
|
|
1010
|
+
if (leafEntryCount(targetLeaf) > state.maxLeafEntries) splitLeaf(state, targetLeaf);
|
|
1011
|
+
maybeAutoScale(state);
|
|
1012
|
+
return sequence;
|
|
1013
|
+
};
|
|
1014
|
+
var putEntry = (state, key, value) => {
|
|
1015
|
+
const targetLeaf = findLeafForKey(state, key, state.nextSequence);
|
|
1016
|
+
return putEntryIntoLeaf(state, targetLeaf, key, value);
|
|
1017
|
+
};
|
|
1018
|
+
var popFirstEntry = (state) => {
|
|
1019
|
+
if (state.entryCount === 0) return null;
|
|
1020
|
+
const firstEntry = leafShiftEntry(state.leftmostLeaf);
|
|
1021
|
+
if (firstEntry === void 0) throw new BTreeInvariantError("leftmost leaf empty but count > 0");
|
|
1022
|
+
state.entryCount -= 1;
|
|
1023
|
+
if (state.entryKeys !== null) {
|
|
1024
|
+
state.entryKeys.delete(firstEntry.entryId);
|
|
1025
|
+
}
|
|
1026
|
+
if (leafEntryCount(state.leftmostLeaf) > 0 && state.leftmostLeaf.parent !== null) {
|
|
1027
|
+
updateMinKeyInAncestors(state.leftmostLeaf);
|
|
1028
|
+
}
|
|
1029
|
+
if (state.leftmostLeaf !== state.root && leafEntryCount(state.leftmostLeaf) < state.minLeafEntries) {
|
|
1030
|
+
rebalanceAfterLeafRemoval(state, state.leftmostLeaf);
|
|
1031
|
+
}
|
|
1032
|
+
return firstEntry;
|
|
1033
|
+
};
|
|
1034
|
+
var popLastEntry = (state) => {
|
|
1035
|
+
if (state.entryCount === 0) return null;
|
|
1036
|
+
const lastEntry = leafPopEntry(state.rightmostLeaf);
|
|
1037
|
+
if (lastEntry === void 0) throw new BTreeInvariantError("rightmost leaf empty but count > 0");
|
|
1038
|
+
state.entryCount -= 1;
|
|
1039
|
+
if (state.entryKeys !== null) {
|
|
1040
|
+
state.entryKeys.delete(lastEntry.entryId);
|
|
1041
|
+
}
|
|
1042
|
+
if (state.rightmostLeaf !== state.root && leafEntryCount(state.rightmostLeaf) < state.minLeafEntries) {
|
|
1043
|
+
rebalanceAfterLeafRemoval(state, state.rightmostLeaf);
|
|
1044
|
+
}
|
|
1045
|
+
return lastEntry;
|
|
1046
|
+
};
|
|
1047
|
+
var removeFirstMatchingEntry = (state, key) => {
|
|
1048
|
+
const found = findFirstMatchingUserKey(state, key);
|
|
1049
|
+
if (found === null) return null;
|
|
1050
|
+
const targetLeaf = found.leaf;
|
|
1051
|
+
const removeAt = found.index;
|
|
1052
|
+
const targetEntry = leafEntryAt(targetLeaf, removeAt);
|
|
1053
|
+
leafRemoveAt(targetLeaf, removeAt);
|
|
1054
|
+
state.entryCount -= 1;
|
|
1055
|
+
if (state.entryKeys !== null) {
|
|
1056
|
+
state.entryKeys.delete(targetEntry.entryId);
|
|
1057
|
+
}
|
|
1058
|
+
if (removeAt === 0 && leafEntryCount(targetLeaf) > 0 && targetLeaf.parent !== null) updateMinKeyInAncestors(targetLeaf);
|
|
1059
|
+
if (targetLeaf !== state.root && leafEntryCount(targetLeaf) < state.minLeafEntries) {
|
|
1060
|
+
rebalanceAfterLeafRemoval(state, targetLeaf);
|
|
1061
|
+
}
|
|
1062
|
+
return targetEntry;
|
|
1063
|
+
};
|
|
1064
|
+
var findLeafEntryBySequence = (state, userKey, sequence) => {
|
|
1065
|
+
const targetLeaf = findLeafForKey(state, userKey, sequence);
|
|
1066
|
+
const index = lowerBoundInLeaf(state, targetLeaf, userKey, sequence);
|
|
1067
|
+
if (index >= leafEntryCount(targetLeaf)) return null;
|
|
1068
|
+
const entry = leafEntryAt(targetLeaf, index);
|
|
1069
|
+
if (entry.entryId !== sequence) return null;
|
|
1070
|
+
return { leaf: targetLeaf, index };
|
|
1071
|
+
};
|
|
1072
|
+
var removeEntryById = (state, entryId) => {
|
|
1073
|
+
const userKey = state.entryKeys.get(entryId);
|
|
1074
|
+
if (userKey === void 0) return null;
|
|
1075
|
+
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1076
|
+
if (found === null) return null;
|
|
1077
|
+
const entry = leafEntryAt(found.leaf, found.index);
|
|
1078
|
+
leafRemoveAt(found.leaf, found.index);
|
|
1079
|
+
state.entryCount -= 1;
|
|
1080
|
+
state.entryKeys.delete(entryId);
|
|
1081
|
+
if (found.index === 0 && leafEntryCount(found.leaf) > 0 && found.leaf.parent !== null) {
|
|
1082
|
+
updateMinKeyInAncestors(found.leaf);
|
|
1083
|
+
}
|
|
1084
|
+
if (found.leaf !== state.root && leafEntryCount(found.leaf) < state.minLeafEntries) {
|
|
1085
|
+
rebalanceAfterLeafRemoval(state, found.leaf);
|
|
1086
|
+
}
|
|
1087
|
+
return entry;
|
|
1088
|
+
};
|
|
1089
|
+
var peekEntryById = (state, entryId) => {
|
|
1090
|
+
const userKey = state.entryKeys.get(entryId);
|
|
1091
|
+
if (userKey === void 0) return null;
|
|
1092
|
+
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1093
|
+
if (found === null) return null;
|
|
1094
|
+
const entry = leafEntryAt(found.leaf, found.index);
|
|
1095
|
+
return entry;
|
|
1096
|
+
};
|
|
1097
|
+
var updateEntryById = (state, entryId, newValue) => {
|
|
1098
|
+
const userKey = state.entryKeys.get(entryId);
|
|
1099
|
+
if (userKey === void 0) return null;
|
|
1100
|
+
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1101
|
+
if (found === null) return null;
|
|
1102
|
+
const entry = leafEntryAt(found.leaf, found.index);
|
|
1103
|
+
entry.value = newValue;
|
|
1104
|
+
return entry;
|
|
1105
|
+
};
|
|
1106
|
+
var putManyEntries = (state, entries) => {
|
|
1107
|
+
if (entries.length === 0) return [];
|
|
1108
|
+
const strictlyAscending = state.duplicateKeys !== "allow";
|
|
1109
|
+
for (let i = 1; i < entries.length; i += 1) {
|
|
1110
|
+
const cmp = state.compareKeys(entries[i - 1].key, entries[i].key);
|
|
1111
|
+
if (strictlyAscending ? cmp >= 0 : cmp > 0) {
|
|
1112
|
+
throw new BTreeValidationError(
|
|
1113
|
+
strictlyAscending ? "putMany: not sorted in strict ascending order." : "putMany: not sorted in non-descending order."
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (state.entryCount > 0) {
|
|
1118
|
+
const ids = new Array(entries.length);
|
|
1119
|
+
let hintLeaf = findLeafForKey(state, entries[0].key, state.nextSequence);
|
|
1120
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
1121
|
+
const entry = entries[i];
|
|
1122
|
+
const targetLeaf = findLeafFromHint(state, hintLeaf, entry.key, state.nextSequence);
|
|
1123
|
+
ids[i] = putEntryIntoLeaf(state, targetLeaf, entry.key, entry.value);
|
|
1124
|
+
hintLeaf = targetLeaf;
|
|
1125
|
+
}
|
|
1126
|
+
return ids;
|
|
1127
|
+
}
|
|
1128
|
+
return bulkLoadEntries(state, entries);
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
// src/btree/rangeQuery.ts
|
|
1132
|
+
var initRangeCursor = (state, startKey, endKey, options) => {
|
|
1133
|
+
if (state.entryCount === 0) return null;
|
|
1134
|
+
const compare = state.compareKeys;
|
|
1135
|
+
const boundCompared = compare(startKey, endKey);
|
|
1136
|
+
if (boundCompared > 0) return null;
|
|
1137
|
+
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
1138
|
+
const upperExclusive = options?.upperBound === "exclusive";
|
|
1139
|
+
if (lowerExclusive && upperExclusive && boundCompared === 0) return null;
|
|
1140
|
+
const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
|
|
1141
|
+
const leaf = findLeafForKey(state, startKey, startSeq);
|
|
1142
|
+
const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
|
|
1143
|
+
return { leaf, index, compare, upperExclusive };
|
|
1144
|
+
};
|
|
1145
|
+
var countRangeEntries = (state, startKey, endKey, options) => {
|
|
1146
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
1147
|
+
if (cursor === null) return 0;
|
|
1148
|
+
let cursorLeaf = cursor.leaf;
|
|
1149
|
+
let cursorIndex = cursor.index;
|
|
1150
|
+
const { compare, upperExclusive } = cursor;
|
|
1151
|
+
let count = 0;
|
|
1152
|
+
while (cursorLeaf !== null) {
|
|
1153
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
1154
|
+
if (cursorIndex >= leafCount) {
|
|
1155
|
+
cursorLeaf = cursorLeaf.next;
|
|
1156
|
+
cursorIndex = 0;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
|
|
1160
|
+
const cmpLast = compare(lastEntry.key, endKey);
|
|
1161
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
1162
|
+
count += leafCount - cursorIndex;
|
|
1163
|
+
cursorLeaf = cursorLeaf.next;
|
|
1164
|
+
cursorIndex = 0;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
1168
|
+
const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
|
|
1169
|
+
const limit = endBound < leafCount ? endBound : leafCount;
|
|
1170
|
+
count += limit - cursorIndex;
|
|
1171
|
+
return count;
|
|
1172
|
+
}
|
|
1173
|
+
return count;
|
|
1174
|
+
};
|
|
1175
|
+
var RANGE_PREALLOC_THRESHOLD = 200;
|
|
1176
|
+
var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
|
|
1177
|
+
const firstLeafCount = leafEntryCount(cursorLeaf);
|
|
1178
|
+
const firstLeafRemainder = firstLeafCount - cursorIndex;
|
|
1179
|
+
if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
|
|
1180
|
+
const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
|
|
1181
|
+
const cmpLast = compare(lastEntry.key, endKey);
|
|
1182
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
1183
|
+
const total = countRangeEntries(state, startKey, endKey, options);
|
|
1184
|
+
return new Array(total);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return [];
|
|
1188
|
+
};
|
|
1189
|
+
var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
|
|
1190
|
+
if (useIndexed) {
|
|
1191
|
+
for (let i = from; i < to; i += 1) {
|
|
1192
|
+
output[writeIdx++] = leafEntryAt(leaf, i);
|
|
1193
|
+
}
|
|
1194
|
+
} else {
|
|
1195
|
+
for (let i = from; i < to; i += 1) {
|
|
1196
|
+
output.push(leafEntryAt(leaf, i));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return writeIdx;
|
|
1200
|
+
};
|
|
1201
|
+
var rangeQueryEntries = (state, startKey, endKey, options) => {
|
|
1202
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
1203
|
+
if (cursor === null) return [];
|
|
1204
|
+
let cursorLeaf = cursor.leaf;
|
|
1205
|
+
let cursorIndex = cursor.index;
|
|
1206
|
+
const { compare, upperExclusive } = cursor;
|
|
1207
|
+
const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
|
|
1208
|
+
let writeIdx = 0;
|
|
1209
|
+
const useIndexed = output.length > 0;
|
|
1210
|
+
while (cursorLeaf !== null) {
|
|
1211
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
1212
|
+
if (cursorIndex >= leafCount) {
|
|
1213
|
+
cursorLeaf = cursorLeaf.next;
|
|
1214
|
+
cursorIndex = 0;
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
|
|
1218
|
+
const cmpLast = compare(lastEntry.key, endKey);
|
|
1219
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
1220
|
+
writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
|
|
1221
|
+
cursorLeaf = cursorLeaf.next;
|
|
1222
|
+
cursorIndex = 0;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
1226
|
+
const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
|
|
1227
|
+
const limit = endBound < leafCount ? endBound : leafCount;
|
|
1228
|
+
appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
|
|
1229
|
+
return output;
|
|
1230
|
+
}
|
|
1231
|
+
return output;
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
// src/btree/serialization.ts
|
|
1235
|
+
var buildConfigFromState = (state) => {
|
|
1236
|
+
const config = {
|
|
1237
|
+
compareKeys: state.compareKeys,
|
|
1238
|
+
duplicateKeys: state.duplicateKeys,
|
|
1239
|
+
enableEntryIdLookup: state.entryKeys !== null,
|
|
1240
|
+
autoScale: state.autoScale
|
|
1241
|
+
};
|
|
1242
|
+
if (!state.autoScale) {
|
|
1243
|
+
config.maxLeafEntries = state.maxLeafEntries;
|
|
1244
|
+
config.maxBranchChildren = state.maxBranchChildren;
|
|
1245
|
+
}
|
|
1246
|
+
return config;
|
|
1247
|
+
};
|
|
1248
|
+
var serializeToJSON = (state) => {
|
|
1249
|
+
const entries = new Array(state.entryCount);
|
|
1250
|
+
let leaf = state.leftmostLeaf;
|
|
1251
|
+
let writeIdx = 0;
|
|
1252
|
+
while (leaf !== null) {
|
|
1253
|
+
const count = leafEntryCount(leaf);
|
|
1254
|
+
for (let i = 0; i < count; i += 1) {
|
|
1255
|
+
const e = leafEntryAt(leaf, i);
|
|
1256
|
+
entries[writeIdx++] = [e.key, e.value];
|
|
1257
|
+
}
|
|
1258
|
+
leaf = leaf.next;
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
version: 1,
|
|
1262
|
+
config: {
|
|
1263
|
+
maxLeafEntries: state.maxLeafEntries,
|
|
1264
|
+
maxBranchChildren: state.maxBranchChildren,
|
|
1265
|
+
duplicateKeys: state.duplicateKeys,
|
|
1266
|
+
enableEntryIdLookup: state.entryKeys !== null,
|
|
1267
|
+
autoScale: state.autoScale
|
|
1268
|
+
},
|
|
1269
|
+
entries
|
|
1270
|
+
};
|
|
1271
|
+
};
|
|
1272
|
+
var MAX_SERIALIZED_ENTRIES = 1e6;
|
|
1273
|
+
var validateStructure = (json) => {
|
|
1274
|
+
if (typeof json !== "object" || json === null || json.version !== 1) {
|
|
1275
|
+
throw new BTreeValidationError(
|
|
1276
|
+
`BTreeJSON: expected version 1, got ${String(json?.version)}.`
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
if (typeof json.config !== "object" || json.config === null) {
|
|
1280
|
+
throw new BTreeValidationError("BTreeJSON: invalid config.");
|
|
1281
|
+
}
|
|
1282
|
+
if (!Array.isArray(json.entries)) {
|
|
1283
|
+
throw new BTreeValidationError("BTreeJSON: entries must be array.");
|
|
1284
|
+
}
|
|
1285
|
+
if (json.entries.length > MAX_SERIALIZED_ENTRIES) {
|
|
1286
|
+
throw new BTreeValidationError("BTreeJSON: entry count exceeds maximum.");
|
|
1287
|
+
}
|
|
1288
|
+
for (let i = 0; i < json.entries.length; i += 1) {
|
|
1289
|
+
const entry = json.entries[i];
|
|
1290
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
1291
|
+
throw new BTreeValidationError(`BTreeJSON: bad entries[${i}].`);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
var validateConfig = (cfg) => {
|
|
1296
|
+
const validateCapacity = (field, value) => {
|
|
1297
|
+
if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
|
|
1298
|
+
throw new BTreeValidationError(`BTreeJSON: invalid ${field}.`);
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
if (cfg.duplicateKeys !== "allow" && cfg.duplicateKeys !== "reject" && cfg.duplicateKeys !== "replace") {
|
|
1302
|
+
throw new BTreeValidationError(
|
|
1303
|
+
`BTreeJSON: invalid duplicateKeys: ${String(cfg.duplicateKeys)}.`
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
if (typeof cfg.enableEntryIdLookup !== "boolean") {
|
|
1307
|
+
throw new BTreeValidationError("BTreeJSON: invalid enableEntryIdLookup.");
|
|
1308
|
+
}
|
|
1309
|
+
if (typeof cfg.autoScale !== "boolean") {
|
|
1310
|
+
throw new BTreeValidationError("BTreeJSON: invalid autoScale.");
|
|
1311
|
+
}
|
|
1312
|
+
if (typeof cfg.maxLeafEntries !== "number") {
|
|
1313
|
+
throw new BTreeValidationError("BTreeJSON: invalid maxLeafEntries.");
|
|
1314
|
+
}
|
|
1315
|
+
if (typeof cfg.maxBranchChildren !== "number") {
|
|
1316
|
+
throw new BTreeValidationError("BTreeJSON: invalid maxBranchChildren.");
|
|
1317
|
+
}
|
|
1318
|
+
validateCapacity("maxLeafEntries", cfg.maxLeafEntries);
|
|
1319
|
+
validateCapacity("maxBranchChildren", cfg.maxBranchChildren);
|
|
1320
|
+
if (cfg.autoScale) {
|
|
1321
|
+
const tier0 = computeAutoScaleTier(0);
|
|
1322
|
+
if (cfg.maxLeafEntries < tier0.maxLeaf || cfg.maxBranchChildren < tier0.maxBranch) {
|
|
1323
|
+
throw new BTreeValidationError(
|
|
1324
|
+
"BTreeJSON: autoScale capacity below tier-0."
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
var validateBTreeJSON = (json) => {
|
|
1330
|
+
validateStructure(json);
|
|
1331
|
+
validateConfig(json.config);
|
|
1332
|
+
};
|
|
1333
|
+
var buildConfigFromJSON = (json, compareKeys) => {
|
|
1334
|
+
const cfg = json.config;
|
|
1335
|
+
const config = {
|
|
1336
|
+
compareKeys,
|
|
1337
|
+
duplicateKeys: cfg.duplicateKeys,
|
|
1338
|
+
enableEntryIdLookup: cfg.enableEntryIdLookup,
|
|
1339
|
+
autoScale: cfg.autoScale
|
|
1340
|
+
};
|
|
1341
|
+
if (!cfg.autoScale) {
|
|
1342
|
+
config.maxLeafEntries = cfg.maxLeafEntries;
|
|
1343
|
+
config.maxBranchChildren = cfg.maxBranchChildren;
|
|
1344
|
+
}
|
|
1345
|
+
return config;
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
// src/btree/integrity-helpers.ts
|
|
1349
|
+
var nodeMinKey = (node) => {
|
|
1350
|
+
if (isLeafNode(node)) {
|
|
1351
|
+
if (node.entryOffset >= node.entries.length) return null;
|
|
1352
|
+
const e = node.entries[node.entryOffset];
|
|
1353
|
+
return { key: e.key, sequence: e.entryId };
|
|
1354
|
+
}
|
|
1355
|
+
if (node.childOffset >= node.keys.length) return null;
|
|
1356
|
+
return { key: node.keys[node.childOffset].key, sequence: node.keys[node.childOffset].sequence };
|
|
1357
|
+
};
|
|
1358
|
+
var compareNodeKeys = (comparator, leftKey, leftSeq, rightKey, rightSeq) => {
|
|
1359
|
+
const cmp = comparator(leftKey, rightKey);
|
|
1360
|
+
if (cmp !== 0) {
|
|
1361
|
+
return cmp;
|
|
1362
|
+
}
|
|
1363
|
+
return leftSeq - rightSeq;
|
|
1364
|
+
};
|
|
1365
|
+
var getNodeMaxKey = (node) => {
|
|
1366
|
+
if (isLeafNode(node)) {
|
|
1367
|
+
if (node.entryOffset >= node.entries.length) return null;
|
|
1368
|
+
const e = node.entries[node.entries.length - 1];
|
|
1369
|
+
return { key: e.key, sequence: e.entryId };
|
|
1370
|
+
}
|
|
1371
|
+
if (node.childOffset >= node.children.length) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
return getNodeMaxKey(node.children[node.children.length - 1]);
|
|
1375
|
+
};
|
|
1376
|
+
var validateComparatorResult = (result) => {
|
|
1377
|
+
if (!Number.isFinite(result)) {
|
|
1378
|
+
throw new BTreeValidationError(
|
|
1379
|
+
"compareKeys must return a finite number."
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
return result;
|
|
1383
|
+
};
|
|
1384
|
+
var assertComparatorReflexivity = (comparator, key) => {
|
|
1385
|
+
const selfCompared = validateComparatorResult(comparator(key, key));
|
|
1386
|
+
if (selfCompared !== 0) {
|
|
1387
|
+
throw new BTreeValidationError(
|
|
1388
|
+
"compareKeys must satisfy reflexivity: compare(x, x) must return 0."
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
var assertComparatorTransitivity = (comparator, first, second, third) => {
|
|
1393
|
+
const firstToSecond = Math.sign(
|
|
1394
|
+
validateComparatorResult(comparator(first, second))
|
|
1395
|
+
);
|
|
1396
|
+
const secondToThird = Math.sign(
|
|
1397
|
+
validateComparatorResult(comparator(second, third))
|
|
1398
|
+
);
|
|
1399
|
+
if (firstToSecond < 0 && secondToThird < 0) {
|
|
1400
|
+
const firstToThird = Math.sign(
|
|
1401
|
+
validateComparatorResult(comparator(first, third))
|
|
1402
|
+
);
|
|
1403
|
+
if (firstToThird >= 0) {
|
|
1404
|
+
throw new BTreeValidationError(
|
|
1405
|
+
"compareKeys must satisfy transitivity for observed key triples."
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (firstToSecond > 0 && secondToThird > 0) {
|
|
1410
|
+
const firstToThird = Math.sign(
|
|
1411
|
+
validateComparatorResult(comparator(first, third))
|
|
1412
|
+
);
|
|
1413
|
+
if (firstToThird <= 0) {
|
|
1414
|
+
throw new BTreeValidationError(
|
|
1415
|
+
"compareKeys must satisfy transitivity for observed key triples."
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
var assertReflexivityAsInvariant = (state, key) => {
|
|
1421
|
+
try {
|
|
1422
|
+
assertComparatorReflexivity(state.compareKeys, key);
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
if (error instanceof BTreeValidationError) {
|
|
1425
|
+
throw new BTreeInvariantError(error.message);
|
|
1426
|
+
}
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
var assertTransitivityAsInvariant = (state, first, second, third) => {
|
|
1431
|
+
try {
|
|
1432
|
+
assertComparatorTransitivity(state.compareKeys, first, second, third);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
if (error instanceof BTreeValidationError) {
|
|
1435
|
+
throw new BTreeInvariantError(error.message);
|
|
1436
|
+
}
|
|
1437
|
+
throw error;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
var validateLeafChainStep = (state, cursor, previous, visited) => {
|
|
1441
|
+
if (!isLeafNode(cursor)) {
|
|
1442
|
+
throw new BTreeInvariantError("Leaf linkage cursor reached non-leaf node.");
|
|
1443
|
+
}
|
|
1444
|
+
if (visited.has(cursor)) {
|
|
1445
|
+
throw new BTreeInvariantError("Cycle detected in leaf linkage.");
|
|
1446
|
+
}
|
|
1447
|
+
if (cursor.prev !== previous) {
|
|
1448
|
+
throw new BTreeInvariantError("Leaf prev pointer mismatch.");
|
|
1449
|
+
}
|
|
1450
|
+
if (previous !== null && isLeafNode(previous)) {
|
|
1451
|
+
const prevMax = getNodeMaxKey(previous);
|
|
1452
|
+
const currentMin = nodeMinKey(cursor);
|
|
1453
|
+
if (prevMax === null || currentMin === null) {
|
|
1454
|
+
throw new BTreeInvariantError("Non-empty tree leaf chain contains empty leaf node.");
|
|
1455
|
+
}
|
|
1456
|
+
if (compareNodeKeys(state.compareKeys, prevMax.key, prevMax.sequence, currentMin.key, currentMin.sequence) > 0) {
|
|
1457
|
+
throw new BTreeInvariantError("Adjacent leaf key ranges are out of order.");
|
|
1458
|
+
}
|
|
1459
|
+
const prevCount = leafEntryCount(previous);
|
|
1460
|
+
const curCount = leafEntryCount(cursor);
|
|
1461
|
+
if (state.duplicateKeys !== "allow" && prevCount > 0 && curCount > 0 && state.compareKeys(
|
|
1462
|
+
leafEntryAt(previous, prevCount - 1).key,
|
|
1463
|
+
leafEntryAt(cursor, 0).key
|
|
1464
|
+
) === 0) {
|
|
1465
|
+
throw new BTreeInvariantError("Duplicate user key detected across adjacent leaves with uniqueness policy.");
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
var validateLeafLinks = (state, expectedLeafCount) => {
|
|
1470
|
+
if (state.entryCount === 0) {
|
|
1471
|
+
if (!isLeafNode(state.root)) {
|
|
1472
|
+
throw new BTreeInvariantError("Empty tree root must be a leaf node.");
|
|
1473
|
+
}
|
|
1474
|
+
if (state.leftmostLeaf !== state.root || state.rightmostLeaf !== state.root) {
|
|
1475
|
+
throw new BTreeInvariantError("Empty tree leaf pointers must reference root leaf.");
|
|
1476
|
+
}
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (state.leftmostLeaf.prev !== null) {
|
|
1480
|
+
throw new BTreeInvariantError("Leftmost leaf prev pointer must be null.");
|
|
1481
|
+
}
|
|
1482
|
+
if (state.rightmostLeaf.next !== null) {
|
|
1483
|
+
throw new BTreeInvariantError("Rightmost leaf next pointer must be null.");
|
|
1484
|
+
}
|
|
1485
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1486
|
+
let cursor = state.leftmostLeaf;
|
|
1487
|
+
let previous = null;
|
|
1488
|
+
let leafCount = 0;
|
|
1489
|
+
while (cursor !== null) {
|
|
1490
|
+
validateLeafChainStep(state, cursor, previous, visited);
|
|
1491
|
+
visited.add(cursor);
|
|
1492
|
+
previous = cursor;
|
|
1493
|
+
cursor = cursor.next;
|
|
1494
|
+
leafCount += 1;
|
|
1495
|
+
}
|
|
1496
|
+
if (previous !== state.rightmostLeaf) {
|
|
1497
|
+
throw new BTreeInvariantError("Rightmost leaf pointer mismatch.");
|
|
1498
|
+
}
|
|
1499
|
+
if (leafCount !== expectedLeafCount) {
|
|
1500
|
+
throw new BTreeInvariantError("Leaf chain count mismatch with tree traversal count.");
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
// src/btree/integrity.ts
|
|
1505
|
+
var validateLeafNodeOrdering = (state, node) => {
|
|
1506
|
+
const count = leafEntryCount(node);
|
|
1507
|
+
for (let i = 0; i < count; i += 1) {
|
|
1508
|
+
assertReflexivityAsInvariant(state, leafEntryAt(node, i).key);
|
|
1509
|
+
}
|
|
1510
|
+
for (let index = 1; index < count; index += 1) {
|
|
1511
|
+
if (compareNodeKeys(
|
|
1512
|
+
state.compareKeys,
|
|
1513
|
+
leafEntryAt(node, index - 1).key,
|
|
1514
|
+
leafEntryAt(node, index - 1).entryId,
|
|
1515
|
+
leafEntryAt(node, index).key,
|
|
1516
|
+
leafEntryAt(node, index).entryId
|
|
1517
|
+
) >= 0) {
|
|
1518
|
+
throw new BTreeInvariantError("Leaf entries are not strictly ordered.");
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (state.duplicateKeys !== "allow") {
|
|
1522
|
+
for (let index = 1; index < count; index += 1) {
|
|
1523
|
+
if (state.compareKeys(
|
|
1524
|
+
leafEntryAt(node, index - 1).key,
|
|
1525
|
+
leafEntryAt(node, index).key
|
|
1526
|
+
) === 0) {
|
|
1527
|
+
throw new BTreeInvariantError("Duplicate user key detected in tree with uniqueness policy.");
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
for (let index = 2; index < count; index += 1) {
|
|
1532
|
+
const first = leafEntryAt(node, index - 2);
|
|
1533
|
+
const second = leafEntryAt(node, index - 1);
|
|
1534
|
+
const third = leafEntryAt(node, index);
|
|
1535
|
+
assertTransitivityAsInvariant(
|
|
1536
|
+
state,
|
|
1537
|
+
first.key,
|
|
1538
|
+
second.key,
|
|
1539
|
+
third.key
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
if (count > state.maxLeafEntries) {
|
|
1543
|
+
throw new BTreeInvariantError("Leaf node exceeds maximum occupancy.");
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
var validateLeafNode = (state, node, depth) => {
|
|
1547
|
+
validateLeafNodeOrdering(state, node);
|
|
1548
|
+
const count = leafEntryCount(node);
|
|
1549
|
+
const baseMinLeaf = state.autoScale ? Math.ceil(computeAutoScaleTier(0).maxLeaf / 2) : state.minLeafEntries;
|
|
1550
|
+
if (node !== state.root && count < baseMinLeaf) {
|
|
1551
|
+
throw new BTreeInvariantError("Non-root leaf node violates minimum occupancy.");
|
|
1552
|
+
}
|
|
1553
|
+
const first = count === 0 ? null : leafEntryAt(node, 0);
|
|
1554
|
+
const last = count === 0 ? null : leafEntryAt(node, count - 1);
|
|
1555
|
+
const minKey = first === null ? null : { key: first.key, sequence: first.entryId };
|
|
1556
|
+
const maxKey = last === null ? null : { key: last.key, sequence: last.entryId };
|
|
1557
|
+
return {
|
|
1558
|
+
minKey,
|
|
1559
|
+
maxKey,
|
|
1560
|
+
leafDepth: count === 0 ? null : depth,
|
|
1561
|
+
leafCount: 1,
|
|
1562
|
+
branchCount: 0,
|
|
1563
|
+
entryCount: count
|
|
1564
|
+
};
|
|
1565
|
+
};
|
|
1566
|
+
var validateBranchStructure = (state, node) => {
|
|
1567
|
+
const liveCount = node.children.length - node.childOffset;
|
|
1568
|
+
if (liveCount === 0) {
|
|
1569
|
+
throw new BTreeInvariantError("Branch node has zero children.");
|
|
1570
|
+
}
|
|
1571
|
+
const baseMinBranch = state.autoScale ? Math.ceil(computeAutoScaleTier(0).maxBranch / 2) : state.minBranchChildren;
|
|
1572
|
+
if (node !== state.root && liveCount < baseMinBranch) {
|
|
1573
|
+
throw new BTreeInvariantError("Non-root branch node violates minimum occupancy.");
|
|
1574
|
+
}
|
|
1575
|
+
if (liveCount > state.maxBranchChildren) {
|
|
1576
|
+
throw new BTreeInvariantError("Branch node exceeds maximum occupancy.");
|
|
1577
|
+
}
|
|
1578
|
+
if (node.keys.length !== node.children.length) {
|
|
1579
|
+
throw new BTreeInvariantError("Branch keys array length does not match children array length.");
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
var validateBranchChild = (state, node, childIndex, depth) => {
|
|
1583
|
+
const child = node.children[childIndex];
|
|
1584
|
+
if (child.parent !== node) {
|
|
1585
|
+
throw new BTreeInvariantError("Child-parent pointer mismatch in branch node.");
|
|
1586
|
+
}
|
|
1587
|
+
if (child.indexInParent !== childIndex) {
|
|
1588
|
+
throw new BTreeInvariantError("Child indexInParent does not match actual position in parent.");
|
|
1589
|
+
}
|
|
1590
|
+
const childValidation = validateNode(state, child, depth + 1);
|
|
1591
|
+
if (childValidation.minKey === null || childValidation.maxKey === null) {
|
|
1592
|
+
throw new BTreeInvariantError("Branch child must not be empty in non-root branch tree.");
|
|
1593
|
+
}
|
|
1594
|
+
const cachedMinKey = node.keys[childIndex];
|
|
1595
|
+
const actualMinKey = nodeMinKey(child);
|
|
1596
|
+
if (actualMinKey === null || compareNodeKeys(state.compareKeys, cachedMinKey.key, cachedMinKey.sequence, actualMinKey.key, actualMinKey.sequence) !== 0) {
|
|
1597
|
+
throw new BTreeInvariantError("Branch cached key does not match actual child minimum key.");
|
|
1598
|
+
}
|
|
1599
|
+
return childValidation;
|
|
1600
|
+
};
|
|
1601
|
+
var validateNode = (state, node, depth) => {
|
|
1602
|
+
if (isLeafNode(node)) {
|
|
1603
|
+
return validateLeafNode(state, node, depth);
|
|
1604
|
+
}
|
|
1605
|
+
validateBranchStructure(state, node);
|
|
1606
|
+
let leafDepth = null;
|
|
1607
|
+
let leafCount = 0;
|
|
1608
|
+
let branchCount = 1;
|
|
1609
|
+
let entryCount = 0;
|
|
1610
|
+
let minKey = null;
|
|
1611
|
+
let maxKey = null;
|
|
1612
|
+
let previousChildMax = null;
|
|
1613
|
+
for (let childIndex = node.childOffset; childIndex < node.children.length; childIndex += 1) {
|
|
1614
|
+
const childValidation = validateBranchChild(state, node, childIndex, depth);
|
|
1615
|
+
if (leafDepth !== null && childValidation.leafDepth !== null && childValidation.leafDepth !== leafDepth) {
|
|
1616
|
+
throw new BTreeInvariantError("Leaf depth mismatch detected in tree.");
|
|
1617
|
+
}
|
|
1618
|
+
if (leafDepth === null && childValidation.leafDepth !== null) {
|
|
1619
|
+
leafDepth = childValidation.leafDepth;
|
|
1620
|
+
}
|
|
1621
|
+
if (previousChildMax !== null && compareNodeKeys(
|
|
1622
|
+
state.compareKeys,
|
|
1623
|
+
previousChildMax.key,
|
|
1624
|
+
previousChildMax.sequence,
|
|
1625
|
+
childValidation.minKey.key,
|
|
1626
|
+
childValidation.minKey.sequence
|
|
1627
|
+
) >= 0) {
|
|
1628
|
+
throw new BTreeInvariantError("Branch child key ranges are not strictly ordered.");
|
|
1629
|
+
}
|
|
1630
|
+
if (minKey === null) minKey = childValidation.minKey;
|
|
1631
|
+
maxKey = childValidation.maxKey;
|
|
1632
|
+
previousChildMax = childValidation.maxKey;
|
|
1633
|
+
leafCount += childValidation.leafCount;
|
|
1634
|
+
branchCount += childValidation.branchCount;
|
|
1635
|
+
entryCount += childValidation.entryCount;
|
|
1636
|
+
}
|
|
1637
|
+
return { minKey, maxKey, leafDepth, leafCount, branchCount, entryCount };
|
|
1638
|
+
};
|
|
1639
|
+
var assertInvariants = (state) => {
|
|
1640
|
+
const validation = validateNode(state, state.root, 0);
|
|
1641
|
+
if (validation.entryCount !== state.entryCount) {
|
|
1642
|
+
throw new BTreeInvariantError("Index entry count mismatch between tree traversal and tracked state.");
|
|
1643
|
+
}
|
|
1644
|
+
validateLeafLinks(state, validation.leafCount);
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
// src/btree/stats.ts
|
|
1648
|
+
var collectStats = (node) => {
|
|
1649
|
+
if (isLeafNode(node)) {
|
|
1650
|
+
return {
|
|
1651
|
+
height: 1,
|
|
1652
|
+
leafCount: 1,
|
|
1653
|
+
branchCount: 0
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
let maxChildHeight = 0;
|
|
1657
|
+
let leafCount = 0;
|
|
1658
|
+
let branchCount = 1;
|
|
1659
|
+
for (let ci = node.childOffset; ci < node.children.length; ci += 1) {
|
|
1660
|
+
const child = node.children[ci];
|
|
1661
|
+
const childStats = collectStats(child);
|
|
1662
|
+
if (childStats.height > maxChildHeight) {
|
|
1663
|
+
maxChildHeight = childStats.height;
|
|
1664
|
+
}
|
|
1665
|
+
leafCount += childStats.leafCount;
|
|
1666
|
+
branchCount += childStats.branchCount;
|
|
1667
|
+
}
|
|
1668
|
+
return {
|
|
1669
|
+
height: maxChildHeight + 1,
|
|
1670
|
+
leafCount,
|
|
1671
|
+
branchCount
|
|
1672
|
+
};
|
|
1673
|
+
};
|
|
1674
|
+
var getStats = (state) => {
|
|
1675
|
+
const stats = collectStats(state.root);
|
|
1676
|
+
return {
|
|
1677
|
+
height: stats.height,
|
|
1678
|
+
leafCount: stats.leafCount,
|
|
1679
|
+
branchCount: stats.branchCount,
|
|
1680
|
+
entryCount: state.entryCount
|
|
1681
|
+
};
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// src/InMemoryBTree.ts
|
|
1685
|
+
var InMemoryBTree = class _InMemoryBTree {
|
|
1686
|
+
constructor(config) {
|
|
1687
|
+
this.state = createInitialState(config);
|
|
1688
|
+
}
|
|
1689
|
+
put(key, value) {
|
|
1690
|
+
return putEntry(this.state, key, value);
|
|
1691
|
+
}
|
|
1692
|
+
putMany(entries) {
|
|
1693
|
+
return putManyEntries(this.state, entries);
|
|
1694
|
+
}
|
|
1695
|
+
remove(key) {
|
|
1696
|
+
return removeFirstMatchingEntry(this.state, key);
|
|
1697
|
+
}
|
|
1698
|
+
removeById(entryId) {
|
|
1699
|
+
if (this.state.entryKeys === null) {
|
|
1700
|
+
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1701
|
+
}
|
|
1702
|
+
return removeEntryById(this.state, entryId);
|
|
1703
|
+
}
|
|
1704
|
+
peekById(entryId) {
|
|
1705
|
+
if (this.state.entryKeys === null) {
|
|
1706
|
+
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1707
|
+
}
|
|
1708
|
+
return peekEntryById(this.state, entryId);
|
|
1709
|
+
}
|
|
1710
|
+
updateById(entryId, value) {
|
|
1711
|
+
if (this.state.entryKeys === null) {
|
|
1712
|
+
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1713
|
+
}
|
|
1714
|
+
return updateEntryById(this.state, entryId, value);
|
|
1715
|
+
}
|
|
1716
|
+
popFirst() {
|
|
1717
|
+
return popFirstEntry(this.state);
|
|
1718
|
+
}
|
|
1719
|
+
peekFirst() {
|
|
1720
|
+
if (this.state.entryCount === 0) {
|
|
1721
|
+
return null;
|
|
1722
|
+
}
|
|
1723
|
+
return leafEntryAt(this.state.leftmostLeaf, 0);
|
|
1724
|
+
}
|
|
1725
|
+
peekLast() {
|
|
1726
|
+
if (this.state.entryCount === 0) {
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
const leaf = this.state.rightmostLeaf;
|
|
1730
|
+
return leafEntryAt(leaf, leafEntryCount(leaf) - 1);
|
|
1731
|
+
}
|
|
1732
|
+
popLast() {
|
|
1733
|
+
return popLastEntry(this.state);
|
|
1734
|
+
}
|
|
1735
|
+
clear() {
|
|
1736
|
+
const emptyLeaf = createLeafNode([], null);
|
|
1737
|
+
this.state.root = emptyLeaf;
|
|
1738
|
+
this.state.leftmostLeaf = emptyLeaf;
|
|
1739
|
+
this.state.rightmostLeaf = emptyLeaf;
|
|
1740
|
+
this.state.entryCount = 0;
|
|
1741
|
+
this.state._cursor.leaf = emptyLeaf;
|
|
1742
|
+
this.state._cursor.index = 0;
|
|
1743
|
+
if (this.state.entryKeys !== null) {
|
|
1744
|
+
this.state.entryKeys.clear();
|
|
1745
|
+
}
|
|
1746
|
+
if (this.state.autoScale) {
|
|
1747
|
+
const tier = computeAutoScaleTier(0);
|
|
1748
|
+
this.state.maxLeafEntries = tier.maxLeaf;
|
|
1749
|
+
this.state.maxBranchChildren = tier.maxBranch;
|
|
1750
|
+
this.state.minLeafEntries = Math.ceil(tier.maxLeaf / 2);
|
|
1751
|
+
this.state.minBranchChildren = Math.ceil(tier.maxBranch / 2);
|
|
1752
|
+
this.state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(0);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
get(key) {
|
|
1756
|
+
const found = findFirstMatchingUserKey(this.state, key);
|
|
1757
|
+
if (found === null) return null;
|
|
1758
|
+
return leafEntryAt(found.leaf, found.index).value;
|
|
1759
|
+
}
|
|
1760
|
+
hasKey(key) {
|
|
1761
|
+
return hasKeyEntry(this.state, key);
|
|
1762
|
+
}
|
|
1763
|
+
findFirst(key) {
|
|
1764
|
+
const found = findFirstMatchingUserKey(this.state, key);
|
|
1765
|
+
if (found === null) return null;
|
|
1766
|
+
return leafEntryAt(found.leaf, found.index);
|
|
1767
|
+
}
|
|
1768
|
+
findLast(key) {
|
|
1769
|
+
const found = findLastMatchingUserKey(this.state, key);
|
|
1770
|
+
if (found === null) return null;
|
|
1771
|
+
return leafEntryAt(found.leaf, found.index);
|
|
1772
|
+
}
|
|
1773
|
+
nextHigherKey(key) {
|
|
1774
|
+
return findNextHigherKey(this.state, key);
|
|
1775
|
+
}
|
|
1776
|
+
nextLowerKey(key) {
|
|
1777
|
+
return findNextLowerKey(this.state, key);
|
|
1778
|
+
}
|
|
1779
|
+
getPairOrNextLower(key) {
|
|
1780
|
+
const found = findPairOrNextLower(this.state, key);
|
|
1781
|
+
if (found === null) return null;
|
|
1782
|
+
return leafEntryAt(found.leaf, found.index);
|
|
1783
|
+
}
|
|
1784
|
+
count(startKey, endKey, options) {
|
|
1785
|
+
return countRangeEntries(this.state, startKey, endKey, options);
|
|
1786
|
+
}
|
|
1787
|
+
deleteRange(startKey, endKey, options) {
|
|
1788
|
+
return deleteRangeEntries(this.state, startKey, endKey, options);
|
|
1789
|
+
}
|
|
1790
|
+
range(startKey, endKey, options) {
|
|
1791
|
+
return rangeQueryEntries(this.state, startKey, endKey, options);
|
|
1792
|
+
}
|
|
1793
|
+
*entries() {
|
|
1794
|
+
let leaf = this.state.leftmostLeaf;
|
|
1795
|
+
while (leaf !== null) {
|
|
1796
|
+
const count = leafEntryCount(leaf);
|
|
1797
|
+
for (let i = 0; i < count; i += 1) {
|
|
1798
|
+
yield leafEntryAt(leaf, i);
|
|
1799
|
+
}
|
|
1800
|
+
leaf = leaf.next;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
*entriesReversed() {
|
|
1804
|
+
let leaf = this.state.rightmostLeaf;
|
|
1805
|
+
while (leaf !== null) {
|
|
1806
|
+
const count = leafEntryCount(leaf);
|
|
1807
|
+
for (let i = count - 1; i >= 0; i -= 1) {
|
|
1808
|
+
yield leafEntryAt(leaf, i);
|
|
1809
|
+
}
|
|
1810
|
+
leaf = leaf.prev;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
*keys() {
|
|
1814
|
+
let leaf = this.state.leftmostLeaf;
|
|
1815
|
+
while (leaf !== null) {
|
|
1816
|
+
const count = leafEntryCount(leaf);
|
|
1817
|
+
for (let i = 0; i < count; i += 1) {
|
|
1818
|
+
yield leafEntryAt(leaf, i).key;
|
|
1819
|
+
}
|
|
1820
|
+
leaf = leaf.next;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
*values() {
|
|
1824
|
+
let leaf = this.state.leftmostLeaf;
|
|
1825
|
+
while (leaf !== null) {
|
|
1826
|
+
const count = leafEntryCount(leaf);
|
|
1827
|
+
for (let i = 0; i < count; i += 1) {
|
|
1828
|
+
yield leafEntryAt(leaf, i).value;
|
|
1829
|
+
}
|
|
1830
|
+
leaf = leaf.next;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
[Symbol.iterator]() {
|
|
1834
|
+
return this.entries();
|
|
1835
|
+
}
|
|
1836
|
+
forEach(callback, thisArg) {
|
|
1837
|
+
let leaf = this.state.leftmostLeaf;
|
|
1838
|
+
while (leaf !== null) {
|
|
1839
|
+
const count = leafEntryCount(leaf);
|
|
1840
|
+
for (let i = 0; i < count; i += 1) {
|
|
1841
|
+
callback.call(thisArg, leafEntryAt(leaf, i));
|
|
1842
|
+
}
|
|
1843
|
+
leaf = leaf.next;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
snapshot() {
|
|
1847
|
+
const result = new Array(this.state.entryCount);
|
|
1848
|
+
let leaf = this.state.leftmostLeaf;
|
|
1849
|
+
let writeIdx = 0;
|
|
1850
|
+
while (leaf !== null) {
|
|
1851
|
+
const count = leafEntryCount(leaf);
|
|
1852
|
+
for (let i = 0; i < count; i += 1) {
|
|
1853
|
+
result[writeIdx++] = leafEntryAt(leaf, i);
|
|
1854
|
+
}
|
|
1855
|
+
leaf = leaf.next;
|
|
1856
|
+
}
|
|
1857
|
+
return result;
|
|
1858
|
+
}
|
|
1859
|
+
clone() {
|
|
1860
|
+
const cloned = new _InMemoryBTree(buildConfigFromState(this.state));
|
|
1861
|
+
applyAutoScaleCapacitySnapshot(
|
|
1862
|
+
cloned.state,
|
|
1863
|
+
this.state.maxLeafEntries,
|
|
1864
|
+
this.state.maxBranchChildren
|
|
1865
|
+
);
|
|
1866
|
+
if (this.state.entryCount > 0) {
|
|
1867
|
+
const pairs = new Array(this.state.entryCount);
|
|
1868
|
+
let leaf = this.state.leftmostLeaf;
|
|
1869
|
+
let writeIdx = 0;
|
|
1870
|
+
while (leaf !== null) {
|
|
1871
|
+
const count = leafEntryCount(leaf);
|
|
1872
|
+
for (let i = 0; i < count; i += 1) {
|
|
1873
|
+
pairs[writeIdx++] = leafEntryAt(leaf, i);
|
|
1874
|
+
}
|
|
1875
|
+
leaf = leaf.next;
|
|
1876
|
+
}
|
|
1877
|
+
cloned.putMany(pairs);
|
|
1878
|
+
}
|
|
1879
|
+
return cloned;
|
|
1880
|
+
}
|
|
1881
|
+
toJSON() {
|
|
1882
|
+
return serializeToJSON(this.state);
|
|
1883
|
+
}
|
|
1884
|
+
static fromJSON(json, compareKeys) {
|
|
1885
|
+
validateBTreeJSON(json);
|
|
1886
|
+
const strict = json.config.duplicateKeys !== "allow";
|
|
1887
|
+
for (let i = 1; i < json.entries.length; i += 1) {
|
|
1888
|
+
const cmp = compareKeys(json.entries[i - 1][0], json.entries[i][0]);
|
|
1889
|
+
if (cmp > 0) {
|
|
1890
|
+
throw new BTreeValidationError("fromJSON: entries not sorted.");
|
|
1891
|
+
}
|
|
1892
|
+
if (strict && cmp === 0) {
|
|
1893
|
+
throw new BTreeValidationError(
|
|
1894
|
+
'fromJSON: duplicate keys require duplicateKeys "allow".'
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
const tree = new _InMemoryBTree(buildConfigFromJSON(json, compareKeys));
|
|
1899
|
+
applyAutoScaleCapacitySnapshot(
|
|
1900
|
+
tree.state,
|
|
1901
|
+
json.config.maxLeafEntries,
|
|
1902
|
+
json.config.maxBranchChildren
|
|
1903
|
+
);
|
|
1904
|
+
if (json.entries.length > 0) {
|
|
1905
|
+
const pairs = new Array(json.entries.length);
|
|
1906
|
+
for (let i = 0; i < json.entries.length; i += 1) {
|
|
1907
|
+
pairs[i] = { key: json.entries[i][0], value: json.entries[i][1] };
|
|
1908
|
+
}
|
|
1909
|
+
tree.putMany(pairs);
|
|
1910
|
+
}
|
|
1911
|
+
return tree;
|
|
1912
|
+
}
|
|
1913
|
+
size() {
|
|
1914
|
+
return this.state.entryCount;
|
|
1915
|
+
}
|
|
1916
|
+
assertInvariants() {
|
|
1917
|
+
assertInvariants(this.state);
|
|
1918
|
+
}
|
|
1919
|
+
getStats() {
|
|
1920
|
+
return getStats(this.state);
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
// src/concurrency/helpers.ts
|
|
1925
|
+
var DEFAULT_MAX_RETRIES = 16;
|
|
1926
|
+
var MAX_RETRIES_LIMIT = 1024;
|
|
1927
|
+
var DEFAULT_MAX_SYNC_MUTATIONS_PER_BATCH = 1e5;
|
|
1928
|
+
var MAX_SYNC_MUTATIONS_PER_BATCH_LIMIT = 1e6;
|
|
1929
|
+
var computeConfigFingerprint = (config) => {
|
|
1930
|
+
const isAutoScale = config.autoScale === true;
|
|
1931
|
+
const tier0 = isAutoScale ? computeAutoScaleTier(0) : void 0;
|
|
1932
|
+
return JSON.stringify({
|
|
1933
|
+
duplicateKeys: config.duplicateKeys ?? "replace",
|
|
1934
|
+
maxLeafEntries: config.maxLeafEntries ?? (tier0 ? tier0.maxLeaf : DEFAULT_MAX_LEAF_ENTRIES),
|
|
1935
|
+
maxBranchChildren: config.maxBranchChildren ?? (tier0 ? tier0.maxBranch : DEFAULT_MAX_BRANCH_CHILDREN),
|
|
1936
|
+
enableEntryIdLookup: config.enableEntryIdLookup === true,
|
|
1937
|
+
autoScale: isAutoScale
|
|
1938
|
+
});
|
|
1939
|
+
};
|
|
1940
|
+
var assertNeverMutation = (mutation) => {
|
|
1941
|
+
const unknownMutation = mutation;
|
|
1942
|
+
throw new BTreeConcurrencyError(
|
|
1943
|
+
`Unsupported mutation type from shared store: ${String(unknownMutation.type)}`
|
|
1944
|
+
);
|
|
1945
|
+
};
|
|
1946
|
+
var normalizeMaxRetries = (value) => {
|
|
1947
|
+
if (value === void 0) {
|
|
1948
|
+
return DEFAULT_MAX_RETRIES;
|
|
1949
|
+
}
|
|
1950
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_RETRIES_LIMIT) {
|
|
1951
|
+
throw new BTreeConcurrencyError(
|
|
1952
|
+
`maxRetries: integer 1\u2013${MAX_RETRIES_LIMIT} required.`
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1955
|
+
return value;
|
|
1956
|
+
};
|
|
1957
|
+
var normalizeMaxSyncMutationsPerBatch = (value) => {
|
|
1958
|
+
if (value === void 0) {
|
|
1959
|
+
return DEFAULT_MAX_SYNC_MUTATIONS_PER_BATCH;
|
|
1960
|
+
}
|
|
1961
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_SYNC_MUTATIONS_PER_BATCH_LIMIT) {
|
|
1962
|
+
throw new BTreeConcurrencyError(
|
|
1963
|
+
`maxSyncMutationsPerBatch: integer 1\u2013${MAX_SYNC_MUTATIONS_PER_BATCH_LIMIT} required.`
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
return value;
|
|
1967
|
+
};
|
|
1968
|
+
var normalizeReadMode = (value) => {
|
|
1969
|
+
if (value === void 0) {
|
|
1970
|
+
return "strong";
|
|
1971
|
+
}
|
|
1972
|
+
if (value !== "strong" && value !== "local") {
|
|
1973
|
+
throw new BTreeConcurrencyError(
|
|
1974
|
+
`readMode: must be 'strong' or 'local'.`
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
return value;
|
|
1978
|
+
};
|
|
1979
|
+
function assertAppendVersionContract(expectedVersion, appendResult) {
|
|
1980
|
+
if (typeof appendResult !== "object" || appendResult === null) {
|
|
1981
|
+
throw new BTreeConcurrencyError(
|
|
1982
|
+
"Store contract: append() must return {applied, version}."
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
const candidate = appendResult;
|
|
1986
|
+
if (typeof candidate.applied !== "boolean") {
|
|
1987
|
+
throw new BTreeConcurrencyError("Store contract: applied must be boolean.");
|
|
1988
|
+
}
|
|
1989
|
+
if (typeof candidate.version !== "bigint") {
|
|
1990
|
+
throw new BTreeConcurrencyError("Store contract: version must be bigint.");
|
|
1991
|
+
}
|
|
1992
|
+
if (candidate.applied && candidate.version <= expectedVersion) {
|
|
1993
|
+
throw new BTreeConcurrencyError(
|
|
1994
|
+
"Store contract: applied version must exceed expected."
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
if (!candidate.applied && candidate.version < expectedVersion) {
|
|
1998
|
+
throw new BTreeConcurrencyError(
|
|
1999
|
+
"Store contract: rejected version must be >= expected."
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/concurrency/ConcurrentInMemoryBTree.ts
|
|
2005
|
+
var ConcurrentInMemoryBTree = class {
|
|
2006
|
+
constructor(config) {
|
|
2007
|
+
this.store = config.store;
|
|
2008
|
+
this.maxRetries = normalizeMaxRetries(config.maxRetries);
|
|
2009
|
+
this.maxSyncMutationsPerBatch = normalizeMaxSyncMutationsPerBatch(
|
|
2010
|
+
config.maxSyncMutationsPerBatch
|
|
2011
|
+
);
|
|
2012
|
+
this.duplicateKeys = config.duplicateKeys ?? "replace";
|
|
2013
|
+
this.readMode = normalizeReadMode(config.readMode);
|
|
2014
|
+
this.configFingerprint = computeConfigFingerprint(config);
|
|
2015
|
+
this.tree = new InMemoryBTree({
|
|
2016
|
+
compareKeys: config.compareKeys,
|
|
2017
|
+
maxLeafEntries: config.maxLeafEntries,
|
|
2018
|
+
maxBranchChildren: config.maxBranchChildren,
|
|
2019
|
+
duplicateKeys: config.duplicateKeys,
|
|
2020
|
+
enableEntryIdLookup: config.enableEntryIdLookup,
|
|
2021
|
+
autoScale: config.autoScale
|
|
2022
|
+
});
|
|
2023
|
+
this.currentVersion = 0n;
|
|
2024
|
+
this.operationQueue = Promise.resolve();
|
|
2025
|
+
this.initSeen = false;
|
|
2026
|
+
}
|
|
2027
|
+
async sync() {
|
|
2028
|
+
await this.runExclusive(async () => {
|
|
2029
|
+
await this.syncUnlocked();
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
async syncUnlocked() {
|
|
2033
|
+
const log = await this.store.getLogEntriesSince(this.currentVersion);
|
|
2034
|
+
if (typeof log.version !== "bigint") {
|
|
2035
|
+
throw new BTreeConcurrencyError("Store contract: version must be bigint.");
|
|
2036
|
+
}
|
|
2037
|
+
if (!Array.isArray(log.mutations)) {
|
|
2038
|
+
throw new BTreeConcurrencyError("Store contract: mutations must be an array.");
|
|
2039
|
+
}
|
|
2040
|
+
if (log.mutations.length > this.maxSyncMutationsPerBatch) {
|
|
2041
|
+
throw new BTreeConcurrencyError(
|
|
2042
|
+
`Sync batch exceeded limit (${String(this.maxSyncMutationsPerBatch)}).`
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
if (log.version <= this.currentVersion) {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
for (const mutation of log.mutations) {
|
|
2049
|
+
this.applyMutationLocal(mutation);
|
|
2050
|
+
}
|
|
2051
|
+
this.currentVersion = log.version;
|
|
2052
|
+
}
|
|
2053
|
+
applyMutationLocal(mutation) {
|
|
2054
|
+
switch (mutation.type) {
|
|
2055
|
+
case "init":
|
|
2056
|
+
if (mutation.configFingerprint !== this.configFingerprint) {
|
|
2057
|
+
throw new BTreeConcurrencyError(
|
|
2058
|
+
"Config mismatch: store peers must share identical tree config."
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
this.initSeen = true;
|
|
2062
|
+
return null;
|
|
2063
|
+
case "put":
|
|
2064
|
+
return this.tree.put(mutation.key, mutation.value);
|
|
2065
|
+
case "remove":
|
|
2066
|
+
return this.tree.remove(mutation.key);
|
|
2067
|
+
case "removeById":
|
|
2068
|
+
return this.tree.removeById(mutation.entryId);
|
|
2069
|
+
case "updateById":
|
|
2070
|
+
return this.tree.updateById(mutation.entryId, mutation.value);
|
|
2071
|
+
case "popFirst":
|
|
2072
|
+
return this.tree.popFirst();
|
|
2073
|
+
case "popLast":
|
|
2074
|
+
return this.tree.popLast();
|
|
2075
|
+
default:
|
|
2076
|
+
return assertNeverMutation(mutation);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
runExclusive(operation) {
|
|
2080
|
+
const run = async () => operation();
|
|
2081
|
+
const result = this.operationQueue.then(run, run);
|
|
2082
|
+
this.operationQueue = result.then(
|
|
2083
|
+
() => void 0,
|
|
2084
|
+
() => void 0
|
|
2085
|
+
);
|
|
2086
|
+
return result;
|
|
2087
|
+
}
|
|
2088
|
+
readOp(fn) {
|
|
2089
|
+
return this.runExclusive(async () => {
|
|
2090
|
+
if (this.readMode === "strong") {
|
|
2091
|
+
await this.syncUnlocked();
|
|
2092
|
+
}
|
|
2093
|
+
return fn(this.tree);
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
async appendMutationAndApplyUnlocked(evaluate) {
|
|
2097
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt += 1) {
|
|
2098
|
+
await this.syncUnlocked();
|
|
2099
|
+
const mutation = evaluate(this.tree);
|
|
2100
|
+
if (mutation === null) {
|
|
2101
|
+
return null;
|
|
2102
|
+
}
|
|
2103
|
+
const expectedVersion = this.currentVersion;
|
|
2104
|
+
const mutations = this.initSeen ? [mutation] : [{ type: "init", configFingerprint: this.configFingerprint }, mutation];
|
|
2105
|
+
const appendResult = await this.store.append(expectedVersion, mutations);
|
|
2106
|
+
assertAppendVersionContract(expectedVersion, appendResult);
|
|
2107
|
+
if (appendResult.applied) {
|
|
2108
|
+
for (const m of mutations) {
|
|
2109
|
+
if (m === mutation) break;
|
|
2110
|
+
this.applyMutationLocal(m);
|
|
2111
|
+
}
|
|
2112
|
+
const localResult = this.applyMutationLocal(mutation);
|
|
2113
|
+
this.currentVersion = appendResult.version;
|
|
2114
|
+
return localResult;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
throw new BTreeConcurrencyError(
|
|
2118
|
+
`Mutation failed after ${String(this.maxRetries)} retries.`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
async put(key, value) {
|
|
2122
|
+
return this.runExclusive(async () => {
|
|
2123
|
+
return this.appendMutationAndApplyUnlocked(
|
|
2124
|
+
(tree) => {
|
|
2125
|
+
if (this.duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
2126
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2127
|
+
}
|
|
2128
|
+
return { type: "put", key, value };
|
|
2129
|
+
}
|
|
2130
|
+
);
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
async remove(key) {
|
|
2134
|
+
return this.runExclusive(async () => {
|
|
2135
|
+
return this.appendMutationAndApplyUnlocked(
|
|
2136
|
+
(tree) => {
|
|
2137
|
+
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
2138
|
+
}
|
|
2139
|
+
);
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
async removeById(entryId) {
|
|
2143
|
+
return this.runExclusive(async () => {
|
|
2144
|
+
return this.appendMutationAndApplyUnlocked(
|
|
2145
|
+
(tree) => {
|
|
2146
|
+
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
2147
|
+
}
|
|
2148
|
+
);
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
async updateById(entryId, value) {
|
|
2152
|
+
return this.runExclusive(async () => {
|
|
2153
|
+
return this.appendMutationAndApplyUnlocked(
|
|
2154
|
+
(tree) => {
|
|
2155
|
+
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
2156
|
+
}
|
|
2157
|
+
);
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
async popFirst() {
|
|
2161
|
+
return this.runExclusive(async () => {
|
|
2162
|
+
return this.appendMutationAndApplyUnlocked((tree) => {
|
|
2163
|
+
return tree.peekFirst() !== null ? { type: "popFirst" } : null;
|
|
2164
|
+
});
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
async get(key) {
|
|
2168
|
+
return this.readOp((tree) => tree.get(key));
|
|
2169
|
+
}
|
|
2170
|
+
async hasKey(key) {
|
|
2171
|
+
return this.readOp((tree) => tree.hasKey(key));
|
|
2172
|
+
}
|
|
2173
|
+
async findFirst(key) {
|
|
2174
|
+
return this.readOp((tree) => tree.findFirst(key));
|
|
2175
|
+
}
|
|
2176
|
+
async findLast(key) {
|
|
2177
|
+
return this.readOp((tree) => tree.findLast(key));
|
|
2178
|
+
}
|
|
2179
|
+
async range(startKey, endKey, options) {
|
|
2180
|
+
return this.readOp((tree) => tree.range(startKey, endKey, options));
|
|
2181
|
+
}
|
|
2182
|
+
async snapshot() {
|
|
2183
|
+
return this.readOp((tree) => tree.snapshot());
|
|
2184
|
+
}
|
|
2185
|
+
async size() {
|
|
2186
|
+
return this.readOp((tree) => tree.size());
|
|
2187
|
+
}
|
|
2188
|
+
async assertInvariants() {
|
|
2189
|
+
await this.readOp((tree) => tree.assertInvariants());
|
|
2190
|
+
}
|
|
2191
|
+
async getStats() {
|
|
2192
|
+
return this.readOp((tree) => tree.getStats());
|
|
2193
|
+
}
|
|
2194
|
+
async peekFirst() {
|
|
2195
|
+
return this.readOp((tree) => tree.peekFirst());
|
|
2196
|
+
}
|
|
2197
|
+
async peekLast() {
|
|
2198
|
+
return this.readOp((tree) => tree.peekLast());
|
|
2199
|
+
}
|
|
2200
|
+
async popLast() {
|
|
2201
|
+
return this.runExclusive(async () => {
|
|
2202
|
+
return this.appendMutationAndApplyUnlocked((tree) => {
|
|
2203
|
+
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
2204
|
+
});
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
async peekById(entryId) {
|
|
2208
|
+
return this.readOp((tree) => tree.peekById(entryId));
|
|
2209
|
+
}
|
|
2210
|
+
async count(startKey, endKey, options) {
|
|
2211
|
+
return this.readOp((tree) => tree.count(startKey, endKey, options));
|
|
2212
|
+
}
|
|
2213
|
+
async nextHigherKey(key) {
|
|
2214
|
+
return this.readOp((tree) => tree.nextHigherKey(key));
|
|
2215
|
+
}
|
|
2216
|
+
async nextLowerKey(key) {
|
|
2217
|
+
return this.readOp((tree) => tree.nextLowerKey(key));
|
|
2218
|
+
}
|
|
2219
|
+
async getPairOrNextLower(key) {
|
|
2220
|
+
return this.readOp((tree) => tree.getPairOrNextLower(key));
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2224
|
+
0 && (module.exports = {
|
|
2225
|
+
BTreeConcurrencyError,
|
|
2226
|
+
BTreeInvariantError,
|
|
2227
|
+
BTreeValidationError,
|
|
2228
|
+
ConcurrentInMemoryBTree,
|
|
2229
|
+
InMemoryBTree
|
|
2230
|
+
});
|