xitdb 0.13.0 → 0.14.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.
Files changed (61) hide show
  1. package/README.md +16 -15
  2. package/dist/core-buffered-file.d.ts +2 -2
  3. package/dist/core-buffered-file.js +182 -0
  4. package/dist/core-file.d.ts +1 -1
  5. package/dist/core-file.js +105 -0
  6. package/dist/core-memory.d.ts +1 -1
  7. package/dist/core-memory.js +152 -0
  8. package/dist/core.js +1 -0
  9. package/dist/database.d.ts +174 -49
  10. package/dist/database.js +3047 -0
  11. package/dist/exceptions.d.ts +4 -0
  12. package/dist/exceptions.js +62 -0
  13. package/dist/hasher.js +49 -0
  14. package/dist/index.d.ts +30 -26
  15. package/dist/index.js +35 -3923
  16. package/dist/read-array-list.d.ts +3 -3
  17. package/dist/read-array-list.js +35 -0
  18. package/dist/read-counted-hash-map.d.ts +2 -2
  19. package/dist/read-counted-hash-map.js +19 -0
  20. package/dist/read-counted-hash-set.d.ts +2 -2
  21. package/dist/read-counted-hash-set.js +19 -0
  22. package/dist/read-cursor.d.ts +11 -5
  23. package/dist/read-cursor.js +577 -0
  24. package/dist/read-hash-map.d.ts +4 -4
  25. package/dist/read-hash-map.js +65 -0
  26. package/dist/read-hash-set.d.ts +4 -4
  27. package/dist/read-hash-set.js +47 -0
  28. package/dist/read-linked-array-list.d.ts +3 -3
  29. package/dist/read-linked-array-list.js +35 -0
  30. package/dist/read-sorted-map.d.ts +21 -0
  31. package/dist/read-sorted-map.js +73 -0
  32. package/dist/read-sorted-set.d.ts +19 -0
  33. package/dist/read-sorted-set.js +65 -0
  34. package/dist/slot-pointer.d.ts +1 -1
  35. package/dist/slot-pointer.js +11 -0
  36. package/dist/slot.d.ts +2 -2
  37. package/dist/slot.js +41 -0
  38. package/dist/slotted.d.ts +1 -1
  39. package/dist/slotted.js +1 -0
  40. package/dist/tag.d.ts +3 -1
  41. package/dist/tag.js +25 -0
  42. package/dist/write-array-list.d.ts +4 -4
  43. package/dist/write-array-list.js +42 -0
  44. package/dist/write-counted-hash-map.d.ts +2 -2
  45. package/dist/write-counted-hash-map.js +18 -0
  46. package/dist/write-counted-hash-set.d.ts +2 -2
  47. package/dist/write-counted-hash-set.js +18 -0
  48. package/dist/write-cursor.d.ts +5 -5
  49. package/dist/write-cursor.js +124 -0
  50. package/dist/write-hash-map.d.ts +4 -4
  51. package/dist/write-hash-map.js +90 -0
  52. package/dist/write-hash-set.d.ts +4 -4
  53. package/dist/write-hash-set.js +59 -0
  54. package/dist/write-linked-array-list.d.ts +4 -4
  55. package/dist/write-linked-array-list.js +52 -0
  56. package/dist/write-sorted-map.d.ts +12 -0
  57. package/dist/write-sorted-map.js +37 -0
  58. package/dist/write-sorted-set.d.ts +10 -0
  59. package/dist/write-sorted-set.js +29 -0
  60. package/dist/writeable-data.js +68 -0
  61. package/package.json +6 -6
@@ -0,0 +1,3047 @@
1
+ import { Hasher } from './hasher.js';
2
+ import { tagValueOf } from './tag.js';
3
+ import { Slot } from './slot.js';
4
+ import { SlotPointer } from './slot-pointer.js';
5
+ import { InvalidDatabaseException, InvalidVersionException, InvalidHashSizeException, KeyNotFoundException, WriteNotAllowedException, UnexpectedTagException, CursorNotWriteableException, ExpectedTxStartException, KeyOffsetExceededException, PathPartMustBeAtEndException, InvalidTopLevelTypeException, ExpectedUnsignedLongException, UnreachableException, MaxShiftExceededException, InvalidBTreeNodeException, InvalidBTreeNodeKindException, } from './exceptions.js';
6
+ import { Bytes, Float, Int, Uint } from './writeable-data.js';
7
+ import { WriteCursor } from './write-cursor.js';
8
+ export const VERSION = 0;
9
+ export const MAGIC_NUMBER = new Uint8Array([0x78, 0x69, 0x74]); // 'xit'
10
+ export const BIT_COUNT = 4;
11
+ export const SLOT_COUNT = 1 << BIT_COUNT;
12
+ export const MASK = BigInt(SLOT_COUNT - 1);
13
+ export const INDEX_BLOCK_SIZE = Slot.LENGTH * SLOT_COUNT;
14
+ export const MAX_BRANCH_LENGTH = 16;
15
+ // b-tree (backs LinkedArrayList): nodes hold up to BTREE_SLOT_COUNT entries
16
+ export const BTREE_SLOT_COUNT = SLOT_COUNT; // max entries per leaf / children per branch
17
+ export const BTREE_SPLIT_COUNT = Math.floor((BTREE_SLOT_COUNT + 1) / 2); // left side of a split
18
+ // on-disk node block: [kind: u8][num: u8] followed by, for a leaf, BTREE_SLOT_COUNT
19
+ // value slots; for a branch, BTREE_SLOT_COUNT child slots then BTREE_SLOT_COUNT u64
20
+ // subtree counts
21
+ export const BTREE_NODE_HEADER_SIZE = 2;
22
+ export const BTREE_LEAF_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + Slot.LENGTH * BTREE_SLOT_COUNT;
23
+ export const BTREE_BRANCH_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + (Slot.LENGTH + 8) * BTREE_SLOT_COUNT;
24
+ // sorted_map / sorted_set node block: [kind: u8][num: u8] then, for a leaf,
25
+ // BTREE_SLOT_COUNT .kv_pair slots; for a branch, BTREE_SLOT_COUNT child slots, then
26
+ // BTREE_SLOT_COUNT separator slots, then BTREE_SLOT_COUNT u64 counts
27
+ export const SORTED_LEAF_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + Slot.LENGTH * BTREE_SLOT_COUNT;
28
+ export const SORTED_BRANCH_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + (Slot.LENGTH * 2 + 8) * BTREE_SLOT_COUNT;
29
+ export var WriteMode;
30
+ (function (WriteMode) {
31
+ WriteMode[WriteMode["READ_ONLY"] = 0] = "READ_ONLY";
32
+ WriteMode[WriteMode["READ_WRITE"] = 1] = "READ_WRITE";
33
+ })(WriteMode || (WriteMode = {}));
34
+ // Header
35
+ export class Header {
36
+ hashId;
37
+ hashSize;
38
+ version;
39
+ tag;
40
+ magicNumber;
41
+ static LENGTH = 12;
42
+ constructor(hashId, hashSize, version, tag, magicNumber) {
43
+ this.hashId = hashId;
44
+ this.hashSize = hashSize;
45
+ this.version = version;
46
+ this.tag = tag;
47
+ this.magicNumber = magicNumber;
48
+ }
49
+ toBytes() {
50
+ const buffer = new ArrayBuffer(Header.LENGTH);
51
+ const view = new DataView(buffer);
52
+ const arr = new Uint8Array(buffer);
53
+ arr.set(this.magicNumber, 0);
54
+ view.setUint8(3, this.tag);
55
+ view.setInt16(4, this.version, false);
56
+ view.setInt16(6, this.hashSize, false);
57
+ view.setInt32(8, this.hashId, false);
58
+ return arr;
59
+ }
60
+ static read(core) {
61
+ const reader = core.reader();
62
+ const magicNumber = new Uint8Array(3);
63
+ reader.readFully(magicNumber);
64
+ const tagByte = reader.readByte();
65
+ const tag = tagValueOf(tagByte & 0b0111_1111);
66
+ const version = reader.readShort();
67
+ const hashSize = reader.readShort();
68
+ const hashId = reader.readInt();
69
+ return new Header(hashId, hashSize, version, tag, magicNumber);
70
+ }
71
+ write(core) {
72
+ const writer = core.writer();
73
+ writer.write(this.toBytes());
74
+ }
75
+ validate() {
76
+ if (!arraysEqual(this.magicNumber, MAGIC_NUMBER)) {
77
+ throw new InvalidDatabaseException();
78
+ }
79
+ if (this.version > VERSION) {
80
+ throw new InvalidVersionException();
81
+ }
82
+ }
83
+ withTag(tag) {
84
+ return new Header(this.hashId, this.hashSize, this.version, tag, this.magicNumber);
85
+ }
86
+ }
87
+ // ArrayListHeader
88
+ export class ArrayListHeader {
89
+ ptr;
90
+ size;
91
+ static LENGTH = 16;
92
+ constructor(ptr, size) {
93
+ this.ptr = ptr;
94
+ this.size = size;
95
+ }
96
+ toBytes() {
97
+ const buffer = new ArrayBuffer(ArrayListHeader.LENGTH);
98
+ const view = new DataView(buffer);
99
+ view.setBigInt64(0, BigInt(this.size), false);
100
+ view.setBigInt64(8, BigInt(this.ptr), false);
101
+ return new Uint8Array(buffer);
102
+ }
103
+ static fromBytes(bytes) {
104
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
105
+ const size = Number(view.getBigInt64(0, false));
106
+ checkLong(size);
107
+ const ptr = Number(view.getBigInt64(8, false));
108
+ checkLong(ptr);
109
+ return new ArrayListHeader(ptr, size);
110
+ }
111
+ withPtr(ptr) {
112
+ return new ArrayListHeader(ptr, this.size);
113
+ }
114
+ }
115
+ // TopLevelArrayListHeader
116
+ export class TopLevelArrayListHeader {
117
+ fileSize;
118
+ parent;
119
+ static LENGTH = 8 + ArrayListHeader.LENGTH;
120
+ constructor(fileSize, parent) {
121
+ this.fileSize = fileSize;
122
+ this.parent = parent;
123
+ }
124
+ toBytes() {
125
+ const buffer = new ArrayBuffer(TopLevelArrayListHeader.LENGTH);
126
+ const view = new DataView(buffer);
127
+ const arr = new Uint8Array(buffer);
128
+ arr.set(this.parent.toBytes(), 0);
129
+ view.setBigInt64(ArrayListHeader.LENGTH, BigInt(this.fileSize), false);
130
+ return arr;
131
+ }
132
+ }
133
+ // BTreeHeader: a root pointer plus the element count (backs LinkedArrayList)
134
+ export class BTreeHeader {
135
+ rootPtr;
136
+ size;
137
+ static LENGTH = 16;
138
+ constructor(rootPtr, size) {
139
+ this.rootPtr = rootPtr;
140
+ this.size = size;
141
+ }
142
+ toBytes() {
143
+ const buffer = new ArrayBuffer(BTreeHeader.LENGTH);
144
+ const view = new DataView(buffer);
145
+ view.setBigInt64(0, BigInt(this.size), false);
146
+ view.setBigInt64(8, BigInt(this.rootPtr), false);
147
+ return new Uint8Array(buffer);
148
+ }
149
+ static fromBytes(bytes) {
150
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
151
+ const size = Number(view.getBigInt64(0, false));
152
+ checkLong(size);
153
+ const rootPtr = Number(view.getBigInt64(8, false));
154
+ checkLong(rootPtr);
155
+ return new BTreeHeader(rootPtr, size);
156
+ }
157
+ }
158
+ // KeyValuePair
159
+ export class KeyValuePair {
160
+ valueSlot;
161
+ keySlot;
162
+ hash;
163
+ constructor(valueSlot, keySlot, hash) {
164
+ this.valueSlot = valueSlot;
165
+ this.keySlot = keySlot;
166
+ this.hash = hash;
167
+ }
168
+ static length(hashSize) {
169
+ return hashSize + Slot.LENGTH * 2;
170
+ }
171
+ toBytes() {
172
+ const buffer = new Uint8Array(KeyValuePair.length(this.hash.length));
173
+ buffer.set(this.hash, 0);
174
+ buffer.set(this.keySlot.toBytes(), this.hash.length);
175
+ buffer.set(this.valueSlot.toBytes(), this.hash.length + Slot.LENGTH);
176
+ return buffer;
177
+ }
178
+ static fromBytes(bytes, hashSize) {
179
+ const hash = bytes.slice(0, hashSize);
180
+ const keySlotBytes = bytes.slice(hashSize, hashSize + Slot.LENGTH);
181
+ const keySlot = Slot.fromBytes(keySlotBytes);
182
+ const valueSlotBytes = bytes.slice(hashSize + Slot.LENGTH, hashSize + Slot.LENGTH * 2);
183
+ const valueSlot = Slot.fromBytes(valueSlotBytes);
184
+ return new KeyValuePair(valueSlot, keySlot, hash);
185
+ }
186
+ }
187
+ // sorted-by-position B-tree node. a leaf holds value slots; a branch holds child
188
+ // slots (.index) plus a per-child u64 subtree count.
189
+ export var BTreeNodeKind;
190
+ (function (BTreeNodeKind) {
191
+ BTreeNodeKind[BTreeNodeKind["LEAF"] = 0] = "LEAF";
192
+ BTreeNodeKind[BTreeNodeKind["BRANCH"] = 1] = "BRANCH";
193
+ })(BTreeNodeKind || (BTreeNodeKind = {}));
194
+ export class BTreeNode {
195
+ kind;
196
+ num;
197
+ values = new Array(BTREE_SLOT_COUNT); // leaf
198
+ children = new Array(BTREE_SLOT_COUNT); // branch
199
+ counts = new Array(BTREE_SLOT_COUNT).fill(0); // branch
200
+ constructor(kind, num) {
201
+ this.kind = kind;
202
+ this.num = num;
203
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
204
+ this.values[i] = new Slot();
205
+ this.children[i] = new Slot();
206
+ }
207
+ }
208
+ subtreeCount() {
209
+ if (this.kind === BTreeNodeKind.LEAF)
210
+ return this.num;
211
+ let total = 0;
212
+ for (let i = 0; i < this.num; i++)
213
+ total += this.counts[i];
214
+ return total;
215
+ }
216
+ }
217
+ // a node pointer plus the element count of its subtree (the right sibling of a split)
218
+ export class BTreeNodeRef {
219
+ nodePtr;
220
+ count;
221
+ constructor(nodePtr, count) {
222
+ this.nodePtr = nodePtr;
223
+ this.count = count;
224
+ }
225
+ }
226
+ export class BTreeInsertResult {
227
+ nodePtr;
228
+ count;
229
+ valuePosition;
230
+ split;
231
+ constructor(nodePtr, count, valuePosition, split) {
232
+ this.nodePtr = nodePtr;
233
+ this.count = count;
234
+ this.valuePosition = valuePosition;
235
+ this.split = split;
236
+ }
237
+ }
238
+ export class BTreeWriteSlot {
239
+ nodePtr;
240
+ valuePosition;
241
+ slot;
242
+ constructor(nodePtr, valuePosition, slot) {
243
+ this.nodePtr = nodePtr;
244
+ this.valuePosition = valuePosition;
245
+ this.slot = slot;
246
+ }
247
+ }
248
+ export class BTreeJoinResult {
249
+ nodePtr;
250
+ count;
251
+ split;
252
+ constructor(nodePtr, count, split) {
253
+ this.nodePtr = nodePtr;
254
+ this.count = count;
255
+ this.split = split;
256
+ }
257
+ }
258
+ export class BTreeSplitResult {
259
+ left;
260
+ right;
261
+ constructor(left, right) {
262
+ this.left = left;
263
+ this.right = right;
264
+ }
265
+ }
266
+ // sorted_map / sorted_set: a count-augmented B+tree keyed on arbitrary byte strings,
267
+ // ordered lexicographically. reuses the b-tree's capacity constants, persistence model
268
+ // (txStart reuse), KeyValuePair entries, and the BTreeHeader {rootPtr, size} header. a
269
+ // leaf holds .kv_pair entries in ascending key order; a branch holds child slots,
270
+ // separator slots (the smallest key in each child's subtree; separators[0] is an unused
271
+ // sentinel), and per-child subtree counts.
272
+ export class SortedNode {
273
+ kind;
274
+ num;
275
+ entries = new Array(BTREE_SLOT_COUNT); // leaf
276
+ children = new Array(BTREE_SLOT_COUNT); // branch
277
+ separators = new Array(BTREE_SLOT_COUNT); // branch
278
+ counts = new Array(BTREE_SLOT_COUNT).fill(0); // branch
279
+ constructor(kind, num) {
280
+ this.kind = kind;
281
+ this.num = num;
282
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
283
+ this.entries[i] = new Slot();
284
+ this.children[i] = new Slot();
285
+ this.separators[i] = new Slot();
286
+ }
287
+ }
288
+ subtreeCount() {
289
+ if (this.kind === BTreeNodeKind.LEAF)
290
+ return this.num;
291
+ let total = 0;
292
+ for (let i = 0; i < this.num; i++)
293
+ total += this.counts[i];
294
+ return total;
295
+ }
296
+ }
297
+ // the new right sibling produced when a node splits
298
+ export class SortedSplit {
299
+ nodePtr;
300
+ count;
301
+ separator;
302
+ constructor(nodePtr, count, separator) {
303
+ this.nodePtr = nodePtr;
304
+ this.count = count;
305
+ this.separator = separator;
306
+ }
307
+ }
308
+ // insert/replace result: where to write the value, whether a new entry was added (vs
309
+ // replacing), and the new right sibling if this node split
310
+ export class SortedInsertResult {
311
+ nodePtr;
312
+ count;
313
+ valuePosition;
314
+ added;
315
+ split;
316
+ constructor(nodePtr, count, valuePosition, added, split) {
317
+ this.nodePtr = nodePtr;
318
+ this.count = count;
319
+ this.valuePosition = valuePosition;
320
+ this.added = added;
321
+ this.split = split;
322
+ }
323
+ }
324
+ // remove result threaded back up the descent: the rewritten node and whether the key
325
+ // was found. separators are stable lower-bound boundaries (not exact mins), so
326
+ // deletions never refresh them; an emptied leaf is left in place.
327
+ export class SortedRemoveResult {
328
+ nodePtr;
329
+ found;
330
+ constructor(nodePtr, found) {
331
+ this.nodePtr = nodePtr;
332
+ this.found = found;
333
+ }
334
+ }
335
+ export class SortedSlot {
336
+ slot;
337
+ position;
338
+ constructor(slot, position) {
339
+ this.slot = slot;
340
+ this.position = position;
341
+ }
342
+ }
343
+ export class SortedEntry {
344
+ kvSlot;
345
+ keySlot;
346
+ valuePosition;
347
+ constructor(kvSlot, keySlot, valuePosition) {
348
+ this.kvSlot = kvSlot;
349
+ this.keySlot = keySlot;
350
+ this.valuePosition = valuePosition;
351
+ }
352
+ }
353
+ export class HashMapGetKVPair {
354
+ hash;
355
+ kind = 'kv_pair';
356
+ constructor(hash) {
357
+ this.hash = hash;
358
+ }
359
+ }
360
+ export class HashMapGetKey {
361
+ hash;
362
+ kind = 'key';
363
+ constructor(hash) {
364
+ this.hash = hash;
365
+ }
366
+ }
367
+ export class HashMapGetValue {
368
+ hash;
369
+ kind = 'value';
370
+ constructor(hash) {
371
+ this.hash = hash;
372
+ }
373
+ }
374
+ export class SortedMapGetKVPair {
375
+ key;
376
+ kind = 'kv_pair';
377
+ constructor(key) {
378
+ this.key = key;
379
+ }
380
+ }
381
+ export class SortedMapGetKey {
382
+ key;
383
+ kind = 'key';
384
+ constructor(key) {
385
+ this.key = key;
386
+ }
387
+ }
388
+ export class SortedMapGetValue {
389
+ key;
390
+ kind = 'value';
391
+ constructor(key) {
392
+ this.key = key;
393
+ }
394
+ }
395
+ // PathPart implementations
396
+ export class ArrayListInit {
397
+ kind = 'ArrayListInit';
398
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
399
+ if (writeMode === WriteMode.READ_ONLY)
400
+ throw new WriteNotAllowedException();
401
+ if (isTopLevel) {
402
+ const writer = db.core.writer();
403
+ if (db.header.tag === 0 /* Tag.NONE */) {
404
+ db.core.seek(Header.LENGTH);
405
+ const arrayListPtr = Header.LENGTH + TopLevelArrayListHeader.LENGTH;
406
+ writer.write(new TopLevelArrayListHeader(0, new ArrayListHeader(arrayListPtr, 0)).toBytes());
407
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
408
+ db.core.seek(0);
409
+ db.header = db.header.withTag(2 /* Tag.ARRAY_LIST */);
410
+ writer.write(db.header.toBytes());
411
+ }
412
+ const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(2 /* Tag.ARRAY_LIST */));
413
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
414
+ }
415
+ if (slotPtr.position === null)
416
+ throw new CursorNotWriteableException();
417
+ const position = slotPtr.position;
418
+ switch (slotPtr.slot.tag) {
419
+ case 0 /* Tag.NONE */: {
420
+ const writer = db.core.writer();
421
+ let arrayListStart = db.core.length();
422
+ db.core.seek(arrayListStart);
423
+ const arrayListPtr = arrayListStart + ArrayListHeader.LENGTH;
424
+ writer.write(new ArrayListHeader(arrayListPtr, 0).toBytes());
425
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
426
+ const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, 2 /* Tag.ARRAY_LIST */));
427
+ db.core.seek(position);
428
+ writer.write(nextSlotPtr.slot.toBytes());
429
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
430
+ }
431
+ case 2 /* Tag.ARRAY_LIST */: {
432
+ const reader = db.core.reader();
433
+ const writer = db.core.writer();
434
+ let arrayListStart = Number(slotPtr.slot.value);
435
+ if (db.txStart !== null) {
436
+ if (arrayListStart < db.txStart) {
437
+ db.core.seek(arrayListStart);
438
+ const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
439
+ reader.readFully(headerBytes);
440
+ const header = ArrayListHeader.fromBytes(headerBytes);
441
+ db.core.seek(header.ptr);
442
+ const arrayListIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
443
+ reader.readFully(arrayListIndexBlock);
444
+ arrayListStart = db.core.length();
445
+ db.core.seek(arrayListStart);
446
+ const nextArrayListPtr = arrayListStart + ArrayListHeader.LENGTH;
447
+ const newHeader = header.withPtr(nextArrayListPtr);
448
+ writer.write(newHeader.toBytes());
449
+ writer.write(arrayListIndexBlock);
450
+ }
451
+ }
452
+ else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
453
+ throw new ExpectedTxStartException();
454
+ }
455
+ const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, 2 /* Tag.ARRAY_LIST */));
456
+ db.core.seek(position);
457
+ writer.write(nextSlotPtr.slot.toBytes());
458
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
459
+ }
460
+ default:
461
+ throw new UnexpectedTagException();
462
+ }
463
+ }
464
+ }
465
+ export class ArrayListGet {
466
+ index;
467
+ kind = 'ArrayListGet';
468
+ constructor(index) {
469
+ this.index = index;
470
+ }
471
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
472
+ const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag;
473
+ switch (tag) {
474
+ case 0 /* Tag.NONE */:
475
+ throw new KeyNotFoundException();
476
+ case 2 /* Tag.ARRAY_LIST */:
477
+ break;
478
+ default:
479
+ throw new UnexpectedTagException();
480
+ }
481
+ const nextArrayListStart = Number(slotPtr.slot.value);
482
+ let index = this.index;
483
+ db.core.seek(nextArrayListStart);
484
+ const reader = db.core.reader();
485
+ const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
486
+ reader.readFully(headerBytes);
487
+ const header = ArrayListHeader.fromBytes(headerBytes);
488
+ if (index >= header.size || index < -header.size) {
489
+ throw new KeyNotFoundException();
490
+ }
491
+ const key = index < 0 ? header.size - Math.abs(index) : index;
492
+ const lastKey = header.size - 1;
493
+ const shift = lastKey < SLOT_COUNT ? 0 : Math.floor(Math.log(lastKey) / Math.log(SLOT_COUNT));
494
+ const finalSlotPtr = db.readArrayListSlot(header.ptr, key, shift, writeMode, isTopLevel);
495
+ return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr);
496
+ }
497
+ }
498
+ export class ArrayListAppend {
499
+ kind = 'ArrayListAppend';
500
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
501
+ if (writeMode === WriteMode.READ_ONLY)
502
+ throw new WriteNotAllowedException();
503
+ const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag;
504
+ if (tag !== 2 /* Tag.ARRAY_LIST */)
505
+ throw new UnexpectedTagException();
506
+ const reader = db.core.reader();
507
+ const nextArrayListStart = Number(slotPtr.slot.value);
508
+ db.core.seek(nextArrayListStart);
509
+ const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
510
+ reader.readFully(headerBytes);
511
+ const origHeader = ArrayListHeader.fromBytes(headerBytes);
512
+ const appendResult = db.readArrayListSlotAppend(origHeader, writeMode, isTopLevel);
513
+ const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, appendResult.slotPtr);
514
+ const writer = db.core.writer();
515
+ if (isTopLevel) {
516
+ db.core.flush();
517
+ const fileSize = db.core.length();
518
+ const header = new TopLevelArrayListHeader(fileSize, appendResult.header);
519
+ db.core.seek(nextArrayListStart);
520
+ writer.write(header.toBytes());
521
+ }
522
+ else {
523
+ db.core.seek(nextArrayListStart);
524
+ writer.write(appendResult.header.toBytes());
525
+ }
526
+ return finalSlotPtr;
527
+ }
528
+ }
529
+ export class ArrayListSlice {
530
+ size;
531
+ kind = 'ArrayListSlice';
532
+ constructor(size) {
533
+ this.size = size;
534
+ }
535
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
536
+ if (writeMode === WriteMode.READ_ONLY)
537
+ throw new WriteNotAllowedException();
538
+ if (slotPtr.slot.tag !== 2 /* Tag.ARRAY_LIST */)
539
+ throw new UnexpectedTagException();
540
+ const reader = db.core.reader();
541
+ const nextArrayListStart = Number(slotPtr.slot.value);
542
+ db.core.seek(nextArrayListStart);
543
+ const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
544
+ reader.readFully(headerBytes);
545
+ const origHeader = ArrayListHeader.fromBytes(headerBytes);
546
+ const sliceHeader = db.readArrayListSlice(origHeader, this.size);
547
+ const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
548
+ const writer = db.core.writer();
549
+ db.core.seek(nextArrayListStart);
550
+ writer.write(sliceHeader.toBytes());
551
+ return finalSlotPtr;
552
+ }
553
+ }
554
+ export class LinkedArrayListInit {
555
+ kind = 'LinkedArrayListInit';
556
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
557
+ if (writeMode === WriteMode.READ_ONLY)
558
+ throw new WriteNotAllowedException();
559
+ if (isTopLevel)
560
+ throw new InvalidTopLevelTypeException();
561
+ if (slotPtr.position === null)
562
+ throw new CursorNotWriteableException();
563
+ const position = slotPtr.position;
564
+ const writer = db.core.writer();
565
+ switch (slotPtr.slot.tag) {
566
+ case 0 /* Tag.NONE */: {
567
+ // create an empty tree: a single empty leaf plus a header
568
+ const rootPtr = db.writeBTreeNode(new BTreeNode(BTreeNodeKind.LEAF, 0));
569
+ const headerPtr = db.core.length();
570
+ db.core.seek(headerPtr);
571
+ writer.write(new BTreeHeader(rootPtr, 0).toBytes());
572
+ const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, 3 /* Tag.LINKED_ARRAY_LIST */));
573
+ db.core.seek(position);
574
+ writer.write(nextSlotPtr.slot.toBytes());
575
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
576
+ }
577
+ case 3 /* Tag.LINKED_ARRAY_LIST */: {
578
+ let headerPtr = Number(slotPtr.slot.value);
579
+ // copy the header into this transaction unless it was made in it, so past
580
+ // moments still pointing at the old header are unaffected. b-tree nodes are
581
+ // always appended, so only the header (updated in place by later operations
582
+ // in this tx) needs copying.
583
+ if (db.txStart !== null) {
584
+ if (headerPtr < db.txStart) {
585
+ const reader = db.core.reader();
586
+ db.core.seek(headerPtr);
587
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
588
+ reader.readFully(headerBytes);
589
+ headerPtr = db.core.length();
590
+ db.core.seek(headerPtr);
591
+ writer.write(headerBytes);
592
+ }
593
+ }
594
+ else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
595
+ throw new ExpectedTxStartException();
596
+ }
597
+ const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, 3 /* Tag.LINKED_ARRAY_LIST */));
598
+ db.core.seek(position);
599
+ writer.write(nextSlotPtr.slot.toBytes());
600
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
601
+ }
602
+ default:
603
+ throw new UnexpectedTagException();
604
+ }
605
+ }
606
+ }
607
+ export class LinkedArrayListGet {
608
+ index;
609
+ kind = 'LinkedArrayListGet';
610
+ constructor(index) {
611
+ this.index = index;
612
+ }
613
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
614
+ switch (slotPtr.slot.tag) {
615
+ case 0 /* Tag.NONE */:
616
+ throw new KeyNotFoundException();
617
+ case 3 /* Tag.LINKED_ARRAY_LIST */:
618
+ break;
619
+ default:
620
+ throw new UnexpectedTagException();
621
+ }
622
+ const index = this.index;
623
+ const headerPtr = Number(slotPtr.slot.value);
624
+ const reader = db.core.reader();
625
+ db.core.seek(headerPtr);
626
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
627
+ reader.readFully(headerBytes);
628
+ const header = BTreeHeader.fromBytes(headerBytes);
629
+ if (index >= header.size || index < -header.size) {
630
+ throw new KeyNotFoundException();
631
+ }
632
+ const rank = index < 0 ? header.size - Math.abs(index) : index;
633
+ if (writeMode === WriteMode.READ_ONLY) {
634
+ const finalSlotPtr = db.readBTreeSlot(header.rootPtr, rank);
635
+ return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr);
636
+ }
637
+ else {
638
+ // path-copy down to the value slot so the write is persistent
639
+ const writeSlot = db.btreeGetForWrite(header.rootPtr, rank);
640
+ const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, new SlotPointer(writeSlot.valuePosition, writeSlot.slot));
641
+ // the header only needs rewriting if the root actually moved (it stays put
642
+ // when the whole path was already this-transaction)
643
+ if (writeSlot.nodePtr !== header.rootPtr) {
644
+ const writer = db.core.writer();
645
+ db.core.seek(headerPtr);
646
+ writer.write(new BTreeHeader(writeSlot.nodePtr, header.size).toBytes());
647
+ }
648
+ return finalSlotPtr;
649
+ }
650
+ }
651
+ }
652
+ export class LinkedArrayListAppend {
653
+ kind = 'LinkedArrayListAppend';
654
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
655
+ if (writeMode === WriteMode.READ_ONLY)
656
+ throw new WriteNotAllowedException();
657
+ if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
658
+ throw new UnexpectedTagException();
659
+ const headerPtr = Number(slotPtr.slot.value);
660
+ const reader = db.core.reader();
661
+ db.core.seek(headerPtr);
662
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
663
+ reader.readFully(headerBytes);
664
+ const header = BTreeHeader.fromBytes(headerBytes);
665
+ const result = db.btreeInsert(header.rootPtr, header.size);
666
+ const newRootPtr = db.btreeGrowRoot(result);
667
+ // update the header before filling in the value, so that a failure in the
668
+ // rest of the path leaves the tree and header consistent
669
+ const writer = db.core.writer();
670
+ db.core.seek(headerPtr);
671
+ writer.write(new BTreeHeader(newRootPtr, header.size + 1).toBytes());
672
+ // fill in the value via the rest of the path
673
+ return db.readSlotPointer(writeMode, path, pathI + 1, new SlotPointer(result.valuePosition, new Slot()));
674
+ }
675
+ }
676
+ export class LinkedArrayListSlice {
677
+ offset;
678
+ size;
679
+ kind = 'LinkedArrayListSlice';
680
+ constructor(offset, size) {
681
+ this.offset = offset;
682
+ this.size = size;
683
+ }
684
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
685
+ if (writeMode === WriteMode.READ_ONLY)
686
+ throw new WriteNotAllowedException();
687
+ if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
688
+ throw new UnexpectedTagException();
689
+ const headerPtr = Number(slotPtr.slot.value);
690
+ const reader = db.core.reader();
691
+ db.core.seek(headerPtr);
692
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
693
+ reader.readFully(headerBytes);
694
+ const header = BTreeHeader.fromBytes(headerBytes);
695
+ // bounds-checked without overflow (offset + size could wrap)
696
+ if (this.offset > header.size || this.size > header.size - this.offset) {
697
+ throw new KeyNotFoundException();
698
+ }
699
+ // slice = drop [0, offset) then keep [0, size) of what's left
700
+ const afterOffset = db.btreeSplit(header.rootPtr, this.offset);
701
+ const sliced = db.btreeSplit(afterOffset.right, this.size);
702
+ const newRootPtr = sliced.left;
703
+ // update the header before recursing into the rest of the path, so that a
704
+ // failure there leaves the tree and header consistent
705
+ const writer = db.core.writer();
706
+ db.core.seek(headerPtr);
707
+ writer.write(new BTreeHeader(newRootPtr, this.size).toBytes());
708
+ return db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
709
+ }
710
+ }
711
+ export class LinkedArrayListConcat {
712
+ list;
713
+ kind = 'LinkedArrayListConcat';
714
+ constructor(list) {
715
+ this.list = list;
716
+ }
717
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
718
+ if (writeMode === WriteMode.READ_ONLY)
719
+ throw new WriteNotAllowedException();
720
+ if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
721
+ throw new UnexpectedTagException();
722
+ if (this.list.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
723
+ throw new UnexpectedTagException();
724
+ const headerPtr = Number(slotPtr.slot.value);
725
+ const reader = db.core.reader();
726
+ db.core.seek(headerPtr);
727
+ const headerBytesA = new Uint8Array(BTreeHeader.LENGTH);
728
+ reader.readFully(headerBytesA);
729
+ const headerA = BTreeHeader.fromBytes(headerBytesA);
730
+ db.core.seek(Number(this.list.value));
731
+ const headerBytesB = new Uint8Array(BTreeHeader.LENGTH);
732
+ reader.readFully(headerBytesB);
733
+ const headerB = BTreeHeader.fromBytes(headerBytesB);
734
+ // the join result shares subtrees with both operands (and the second operand
735
+ // stays live), so freeze everything created so far: later in-place mutations
736
+ // will then copy those nodes instead of overwriting a node that is still
737
+ // referenced elsewhere.
738
+ db.txStart = db.core.length();
739
+ const newRootPtr = db.btreeJoin(headerA.rootPtr, headerB.rootPtr);
740
+ // update the header before recursing into the rest of the path, so that a
741
+ // failure there leaves the tree and header consistent
742
+ const writer = db.core.writer();
743
+ db.core.seek(headerPtr);
744
+ writer.write(new BTreeHeader(newRootPtr, headerA.size + headerB.size).toBytes());
745
+ return db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
746
+ }
747
+ }
748
+ export class LinkedArrayListInsert {
749
+ index;
750
+ kind = 'LinkedArrayListInsert';
751
+ constructor(index) {
752
+ this.index = index;
753
+ }
754
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
755
+ if (writeMode === WriteMode.READ_ONLY)
756
+ throw new WriteNotAllowedException();
757
+ if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
758
+ throw new UnexpectedTagException();
759
+ const headerPtr = Number(slotPtr.slot.value);
760
+ const reader = db.core.reader();
761
+ db.core.seek(headerPtr);
762
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
763
+ reader.readFully(headerBytes);
764
+ const header = BTreeHeader.fromBytes(headerBytes);
765
+ const index = this.index;
766
+ if (index >= header.size || index < -header.size) {
767
+ throw new KeyNotFoundException();
768
+ }
769
+ const rank = index < 0 ? header.size - Math.abs(index) : index;
770
+ const result = db.btreeInsert(header.rootPtr, rank);
771
+ const newRootPtr = db.btreeGrowRoot(result);
772
+ // update the header before filling in the value, so that a failure in the
773
+ // rest of the path leaves the tree and header consistent
774
+ const writer = db.core.writer();
775
+ db.core.seek(headerPtr);
776
+ writer.write(new BTreeHeader(newRootPtr, header.size + 1).toBytes());
777
+ return db.readSlotPointer(writeMode, path, pathI + 1, new SlotPointer(result.valuePosition, new Slot()));
778
+ }
779
+ }
780
+ export class LinkedArrayListRemove {
781
+ index;
782
+ kind = 'LinkedArrayListRemove';
783
+ constructor(index) {
784
+ this.index = index;
785
+ }
786
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
787
+ if (writeMode === WriteMode.READ_ONLY)
788
+ throw new WriteNotAllowedException();
789
+ if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
790
+ throw new UnexpectedTagException();
791
+ const headerPtr = Number(slotPtr.slot.value);
792
+ const reader = db.core.reader();
793
+ db.core.seek(headerPtr);
794
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
795
+ reader.readFully(headerBytes);
796
+ const header = BTreeHeader.fromBytes(headerBytes);
797
+ const index = this.index;
798
+ if (index >= header.size || index < -header.size) {
799
+ throw new KeyNotFoundException();
800
+ }
801
+ const rank = index < 0 ? header.size - Math.abs(index) : index;
802
+ // remove = join the parts before and after the removed element
803
+ const before = db.btreeSplit(header.rootPtr, rank);
804
+ const after = db.btreeSplit(before.right, 1);
805
+ const newRootPtr = db.btreeJoin(before.left, after.right);
806
+ // update the header before recursing into the rest of the path, so that a
807
+ // failure there leaves the tree and header consistent
808
+ const writer = db.core.writer();
809
+ db.core.seek(headerPtr);
810
+ writer.write(new BTreeHeader(newRootPtr, header.size - 1).toBytes());
811
+ return db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
812
+ }
813
+ }
814
+ export class HashMapInit {
815
+ counted;
816
+ set;
817
+ kind = 'HashMapInit';
818
+ constructor(counted = false, set = false) {
819
+ this.counted = counted;
820
+ this.set = set;
821
+ }
822
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
823
+ if (writeMode === WriteMode.READ_ONLY)
824
+ throw new WriteNotAllowedException();
825
+ const tag = this.counted
826
+ ? (this.set ? 13 /* Tag.COUNTED_HASH_SET */ : 12 /* Tag.COUNTED_HASH_MAP */)
827
+ : (this.set ? 11 /* Tag.HASH_SET */ : 4 /* Tag.HASH_MAP */);
828
+ if (isTopLevel) {
829
+ const writer = db.core.writer();
830
+ if (db.header.tag === 0 /* Tag.NONE */) {
831
+ db.core.seek(Header.LENGTH);
832
+ if (this.counted) {
833
+ writer.writeLong(0);
834
+ }
835
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
836
+ db.core.seek(0);
837
+ db.header = db.header.withTag(tag);
838
+ writer.write(db.header.toBytes());
839
+ }
840
+ const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(tag));
841
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
842
+ }
843
+ if (slotPtr.position === null)
844
+ throw new CursorNotWriteableException();
845
+ const position = slotPtr.position;
846
+ switch (slotPtr.slot.tag) {
847
+ case 0 /* Tag.NONE */: {
848
+ const writer = db.core.writer();
849
+ const mapStart = db.core.length();
850
+ db.core.seek(mapStart);
851
+ if (this.counted) {
852
+ writer.writeLong(0);
853
+ }
854
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
855
+ const nextSlotPtr = new SlotPointer(position, new Slot(mapStart, tag));
856
+ db.core.seek(position);
857
+ writer.write(nextSlotPtr.slot.toBytes());
858
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
859
+ }
860
+ case 4 /* Tag.HASH_MAP */:
861
+ case 11 /* Tag.HASH_SET */:
862
+ case 12 /* Tag.COUNTED_HASH_MAP */:
863
+ case 13 /* Tag.COUNTED_HASH_SET */: {
864
+ if (this.counted) {
865
+ switch (slotPtr.slot.tag) {
866
+ case 12 /* Tag.COUNTED_HASH_MAP */:
867
+ case 13 /* Tag.COUNTED_HASH_SET */:
868
+ break;
869
+ default:
870
+ throw new UnexpectedTagException();
871
+ }
872
+ }
873
+ else {
874
+ switch (slotPtr.slot.tag) {
875
+ case 4 /* Tag.HASH_MAP */:
876
+ case 11 /* Tag.HASH_SET */:
877
+ break;
878
+ default:
879
+ throw new UnexpectedTagException();
880
+ }
881
+ }
882
+ const reader = db.core.reader();
883
+ const writer = db.core.writer();
884
+ let mapStart = Number(slotPtr.slot.value);
885
+ if (db.txStart !== null) {
886
+ if (mapStart < db.txStart) {
887
+ db.core.seek(mapStart);
888
+ let mapCountMaybe = null;
889
+ if (this.counted) {
890
+ mapCountMaybe = reader.readLong();
891
+ }
892
+ const mapIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
893
+ reader.readFully(mapIndexBlock);
894
+ mapStart = db.core.length();
895
+ db.core.seek(mapStart);
896
+ if (mapCountMaybe !== null) {
897
+ writer.writeLong(mapCountMaybe);
898
+ }
899
+ writer.write(mapIndexBlock);
900
+ }
901
+ }
902
+ else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
903
+ throw new ExpectedTxStartException();
904
+ }
905
+ const nextSlotPtr = new SlotPointer(position, new Slot(mapStart, tag));
906
+ db.core.seek(position);
907
+ writer.write(nextSlotPtr.slot.toBytes());
908
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
909
+ }
910
+ default:
911
+ throw new UnexpectedTagException();
912
+ }
913
+ }
914
+ }
915
+ export class HashMapGet {
916
+ target;
917
+ kind = 'HashMapGet';
918
+ constructor(target) {
919
+ this.target = target;
920
+ }
921
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
922
+ let counted = false;
923
+ switch (slotPtr.slot.tag) {
924
+ case 0 /* Tag.NONE */:
925
+ throw new KeyNotFoundException();
926
+ case 4 /* Tag.HASH_MAP */:
927
+ case 11 /* Tag.HASH_SET */:
928
+ break;
929
+ case 12 /* Tag.COUNTED_HASH_MAP */:
930
+ case 13 /* Tag.COUNTED_HASH_SET */:
931
+ counted = true;
932
+ break;
933
+ default:
934
+ throw new UnexpectedTagException();
935
+ }
936
+ const indexPos = counted ? Number(slotPtr.slot.value) + 8 : Number(slotPtr.slot.value);
937
+ const hash = db.checkHash(this.target);
938
+ const res = db.readMapSlot(indexPos, hash, 0, writeMode, isTopLevel, this.target);
939
+ if (writeMode === WriteMode.READ_WRITE && counted && res.isEmpty) {
940
+ const reader = db.core.reader();
941
+ const writer = db.core.writer();
942
+ db.core.seek(Number(slotPtr.slot.value));
943
+ const mapCount = reader.readLong();
944
+ db.core.seek(Number(slotPtr.slot.value));
945
+ writer.writeLong(mapCount + 1);
946
+ }
947
+ return db.readSlotPointer(writeMode, path, pathI + 1, res.slotPtr);
948
+ }
949
+ }
950
+ export class HashMapRemove {
951
+ hash;
952
+ kind = 'HashMapRemove';
953
+ constructor(hash) {
954
+ this.hash = hash;
955
+ }
956
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
957
+ if (writeMode === WriteMode.READ_ONLY)
958
+ throw new WriteNotAllowedException();
959
+ let counted = false;
960
+ switch (slotPtr.slot.tag) {
961
+ case 0 /* Tag.NONE */:
962
+ throw new KeyNotFoundException();
963
+ case 4 /* Tag.HASH_MAP */:
964
+ case 11 /* Tag.HASH_SET */:
965
+ break;
966
+ case 12 /* Tag.COUNTED_HASH_MAP */:
967
+ case 13 /* Tag.COUNTED_HASH_SET */:
968
+ counted = true;
969
+ break;
970
+ default:
971
+ throw new UnexpectedTagException();
972
+ }
973
+ const indexPos = counted ? Number(slotPtr.slot.value) + 8 : Number(slotPtr.slot.value);
974
+ const hash = db.checkHashBytes(this.hash);
975
+ let keyFound = true;
976
+ try {
977
+ db.removeMapSlot(indexPos, hash, 0, isTopLevel);
978
+ }
979
+ catch (e) {
980
+ if (e instanceof KeyNotFoundException) {
981
+ keyFound = false;
982
+ }
983
+ else {
984
+ throw e;
985
+ }
986
+ }
987
+ if (writeMode === WriteMode.READ_WRITE && counted && keyFound) {
988
+ const reader = db.core.reader();
989
+ const writer = db.core.writer();
990
+ db.core.seek(Number(slotPtr.slot.value));
991
+ const mapCount = reader.readLong();
992
+ db.core.seek(Number(slotPtr.slot.value));
993
+ writer.writeLong(mapCount - 1);
994
+ }
995
+ if (!keyFound)
996
+ throw new KeyNotFoundException();
997
+ return slotPtr;
998
+ }
999
+ }
1000
+ export class SortedMapInit {
1001
+ set;
1002
+ kind = 'SortedMapInit';
1003
+ constructor(set = false) {
1004
+ this.set = set;
1005
+ }
1006
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
1007
+ if (writeMode === WriteMode.READ_ONLY)
1008
+ throw new WriteNotAllowedException();
1009
+ if (isTopLevel)
1010
+ throw new InvalidTopLevelTypeException();
1011
+ if (slotPtr.position === null)
1012
+ throw new CursorNotWriteableException();
1013
+ const position = slotPtr.position;
1014
+ const tag = this.set ? 15 /* Tag.SORTED_SET */ : 14 /* Tag.SORTED_MAP */;
1015
+ const writer = db.core.writer();
1016
+ switch (slotPtr.slot.tag) {
1017
+ case 0 /* Tag.NONE */: {
1018
+ const rootPtr = db.writeSortedNode(new SortedNode(BTreeNodeKind.LEAF, 0));
1019
+ const headerPtr = db.core.length();
1020
+ db.core.seek(headerPtr);
1021
+ writer.write(new BTreeHeader(rootPtr, 0).toBytes());
1022
+ const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, tag));
1023
+ db.core.seek(position);
1024
+ writer.write(nextSlotPtr.slot.toBytes());
1025
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
1026
+ }
1027
+ case 14 /* Tag.SORTED_MAP */:
1028
+ case 15 /* Tag.SORTED_SET */: {
1029
+ if (slotPtr.slot.tag !== tag)
1030
+ throw new UnexpectedTagException();
1031
+ let headerPtr = Number(slotPtr.slot.value);
1032
+ // copy the header into this transaction unless it was made in it
1033
+ if (db.txStart !== null) {
1034
+ if (headerPtr < db.txStart) {
1035
+ const reader = db.core.reader();
1036
+ db.core.seek(headerPtr);
1037
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
1038
+ reader.readFully(headerBytes);
1039
+ headerPtr = db.core.length();
1040
+ db.core.seek(headerPtr);
1041
+ writer.write(headerBytes);
1042
+ }
1043
+ }
1044
+ else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
1045
+ throw new ExpectedTxStartException();
1046
+ }
1047
+ const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, tag));
1048
+ db.core.seek(position);
1049
+ writer.write(nextSlotPtr.slot.toBytes());
1050
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
1051
+ }
1052
+ default:
1053
+ throw new UnexpectedTagException();
1054
+ }
1055
+ }
1056
+ }
1057
+ export class SortedMapGet {
1058
+ target;
1059
+ kind = 'SortedMapGet';
1060
+ constructor(target) {
1061
+ this.target = target;
1062
+ }
1063
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
1064
+ switch (slotPtr.slot.tag) {
1065
+ case 0 /* Tag.NONE */:
1066
+ throw new KeyNotFoundException();
1067
+ case 14 /* Tag.SORTED_MAP */:
1068
+ case 15 /* Tag.SORTED_SET */:
1069
+ break;
1070
+ default:
1071
+ throw new UnexpectedTagException();
1072
+ }
1073
+ const key = this.target.key;
1074
+ const headerPtr = Number(slotPtr.slot.value);
1075
+ const reader = db.core.reader();
1076
+ db.core.seek(headerPtr);
1077
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
1078
+ reader.readFully(headerBytes);
1079
+ const header = BTreeHeader.fromBytes(headerBytes);
1080
+ if (writeMode === WriteMode.READ_ONLY) {
1081
+ const found = db.sortedGet(header.rootPtr, key);
1082
+ if (found === null)
1083
+ throw new KeyNotFoundException();
1084
+ const targetSlot = db.sortedTargetSlot(Number(found.slot.value), this.target);
1085
+ return db.readSlotPointer(writeMode, path, pathI + 1, targetSlot);
1086
+ }
1087
+ else {
1088
+ const result = db.sortedPut(header.rootPtr, key);
1089
+ const newRootPtr = db.sortedGrowRoot(result);
1090
+ // update the header before filling in the value, so that a failure in the
1091
+ // rest of the path leaves the tree and header consistent (the entry exists
1092
+ // with an empty value) rather than inserted-but-uncounted
1093
+ const writer = db.core.writer();
1094
+ db.core.seek(headerPtr);
1095
+ writer.write(new BTreeHeader(newRootPtr, header.size + (result.added ? 1 : 0)).toBytes());
1096
+ const kvPos = result.valuePosition - db.header.hashSize - Slot.LENGTH;
1097
+ const targetSlot = db.sortedTargetSlot(kvPos, this.target);
1098
+ return db.readSlotPointer(writeMode, path, pathI + 1, targetSlot);
1099
+ }
1100
+ }
1101
+ }
1102
+ export class SortedMapGetIndex {
1103
+ index;
1104
+ kind = 'SortedMapGetIndex';
1105
+ constructor(index) {
1106
+ this.index = index;
1107
+ }
1108
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
1109
+ if (writeMode === WriteMode.READ_WRITE)
1110
+ throw new WriteNotAllowedException();
1111
+ switch (slotPtr.slot.tag) {
1112
+ case 0 /* Tag.NONE */:
1113
+ throw new KeyNotFoundException();
1114
+ case 14 /* Tag.SORTED_MAP */:
1115
+ case 15 /* Tag.SORTED_SET */:
1116
+ break;
1117
+ default:
1118
+ throw new UnexpectedTagException();
1119
+ }
1120
+ const headerPtr = Number(slotPtr.slot.value);
1121
+ const reader = db.core.reader();
1122
+ db.core.seek(headerPtr);
1123
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
1124
+ reader.readFully(headerBytes);
1125
+ const header = BTreeHeader.fromBytes(headerBytes);
1126
+ const index = this.index;
1127
+ if (index >= header.size || index < -header.size) {
1128
+ throw new KeyNotFoundException();
1129
+ }
1130
+ const rank = index < 0 ? header.size - Math.abs(index) : index;
1131
+ const found = db.sortedGetByIndex(header.rootPtr, rank);
1132
+ // return the kv_pair entry so the caller can read key and value
1133
+ const targetSlot = new SlotPointer(found.position, found.slot);
1134
+ return db.readSlotPointer(writeMode, path, pathI + 1, targetSlot);
1135
+ }
1136
+ }
1137
+ export class SortedMapRemove {
1138
+ key;
1139
+ kind = 'SortedMapRemove';
1140
+ constructor(key) {
1141
+ this.key = key;
1142
+ }
1143
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
1144
+ if (writeMode === WriteMode.READ_ONLY)
1145
+ throw new WriteNotAllowedException();
1146
+ switch (slotPtr.slot.tag) {
1147
+ case 0 /* Tag.NONE */:
1148
+ throw new KeyNotFoundException();
1149
+ case 14 /* Tag.SORTED_MAP */:
1150
+ case 15 /* Tag.SORTED_SET */:
1151
+ break;
1152
+ default:
1153
+ throw new UnexpectedTagException();
1154
+ }
1155
+ const headerPtr = Number(slotPtr.slot.value);
1156
+ const reader = db.core.reader();
1157
+ db.core.seek(headerPtr);
1158
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
1159
+ reader.readFully(headerBytes);
1160
+ const header = BTreeHeader.fromBytes(headerBytes);
1161
+ const result = db.sortedRemove(header.rootPtr, this.key);
1162
+ if (!result.found)
1163
+ throw new KeyNotFoundException();
1164
+ const writer = db.core.writer();
1165
+ db.core.seek(headerPtr);
1166
+ writer.write(new BTreeHeader(result.nodePtr, header.size - 1).toBytes());
1167
+ return slotPtr;
1168
+ }
1169
+ }
1170
+ export class WriteData {
1171
+ data;
1172
+ kind = 'WriteData';
1173
+ constructor(data) {
1174
+ this.data = data;
1175
+ }
1176
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
1177
+ if (writeMode === WriteMode.READ_ONLY)
1178
+ throw new WriteNotAllowedException();
1179
+ if (slotPtr.position === null)
1180
+ throw new CursorNotWriteableException();
1181
+ const position = slotPtr.position;
1182
+ const writer = db.core.writer();
1183
+ const data = this.data;
1184
+ let slot;
1185
+ if (data === null) {
1186
+ slot = new Slot();
1187
+ }
1188
+ else if (data instanceof Slot) {
1189
+ slot = data;
1190
+ }
1191
+ else if (data instanceof Uint) {
1192
+ slot = new Slot(data.value, 8 /* Tag.UINT */);
1193
+ }
1194
+ else if (data instanceof Int) {
1195
+ slot = new Slot(data.value, 9 /* Tag.INT */);
1196
+ }
1197
+ else if (data instanceof Float) {
1198
+ const buffer = new ArrayBuffer(8);
1199
+ const view = new DataView(buffer);
1200
+ view.setFloat64(0, data.value, false);
1201
+ const longValue = view.getBigInt64(0, false);
1202
+ slot = new Slot(longValue, 10 /* Tag.FLOAT */);
1203
+ }
1204
+ else if (data instanceof Bytes) {
1205
+ if (data.isShort()) {
1206
+ const buffer = new Uint8Array(8);
1207
+ buffer.set(data.value, 0);
1208
+ if (data.formatTag !== null) {
1209
+ buffer.set(data.formatTag, 6);
1210
+ }
1211
+ const view = new DataView(buffer.buffer);
1212
+ // Read 8 bytes big-endian as BigInt for full precision
1213
+ const longValue = view.getBigInt64(0, false);
1214
+ slot = new Slot(longValue, 7 /* Tag.SHORT_BYTES */, data.formatTag !== null);
1215
+ }
1216
+ else {
1217
+ const nextCursor = new WriteCursor(slotPtr, db);
1218
+ const cursorWriter = nextCursor.writer();
1219
+ cursorWriter.formatTag = data.formatTag;
1220
+ cursorWriter.write(data.value);
1221
+ cursorWriter.finish();
1222
+ slot = cursorWriter.slot;
1223
+ }
1224
+ }
1225
+ else {
1226
+ throw new Error('Unknown data type');
1227
+ }
1228
+ if (slot.tag === 0 /* Tag.NONE */) {
1229
+ slot = slot.withFull(true);
1230
+ }
1231
+ db.core.seek(position);
1232
+ writer.write(slot.toBytes());
1233
+ const nextSlotPtr = new SlotPointer(slotPtr.position, slot);
1234
+ return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
1235
+ }
1236
+ }
1237
+ export class Context {
1238
+ fn;
1239
+ kind = 'Context';
1240
+ constructor(fn) {
1241
+ this.fn = fn;
1242
+ }
1243
+ readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
1244
+ if (writeMode === WriteMode.READ_ONLY)
1245
+ throw new WriteNotAllowedException();
1246
+ if (pathI !== path.length - 1)
1247
+ throw new PathPartMustBeAtEndException();
1248
+ const nextCursor = new WriteCursor(slotPtr, db);
1249
+ try {
1250
+ this.fn(nextCursor);
1251
+ }
1252
+ catch (e) {
1253
+ try {
1254
+ db.truncate();
1255
+ }
1256
+ catch (_) { }
1257
+ throw e;
1258
+ }
1259
+ return nextCursor.slotPtr;
1260
+ }
1261
+ }
1262
+ // HashMapGetResult
1263
+ class HashMapGetResult {
1264
+ slotPtr;
1265
+ isEmpty;
1266
+ constructor(slotPtr, isEmpty) {
1267
+ this.slotPtr = slotPtr;
1268
+ this.isEmpty = isEmpty;
1269
+ }
1270
+ }
1271
+ // ArrayListAppendResult
1272
+ class ArrayListAppendResult {
1273
+ header;
1274
+ slotPtr;
1275
+ constructor(header, slotPtr) {
1276
+ this.header = header;
1277
+ this.slotPtr = slotPtr;
1278
+ }
1279
+ }
1280
+ // Helper functions
1281
+ function arraysEqual(a, b) {
1282
+ if (a.length !== b.length)
1283
+ return false;
1284
+ for (let i = 0; i < a.length; i++) {
1285
+ if (a[i] !== b[i])
1286
+ return false;
1287
+ }
1288
+ return true;
1289
+ }
1290
+ function checkLong(n) {
1291
+ if (n < 0) {
1292
+ throw new ExpectedUnsignedLongException();
1293
+ }
1294
+ return n;
1295
+ }
1296
+ // lexicographic comparison of two byte strings (unsigned), returns <0, 0, or >0
1297
+ function compareBytesUnsigned(a, b) {
1298
+ const n = Math.min(a.length, b.length);
1299
+ for (let i = 0; i < n; i++) {
1300
+ if (a[i] !== b[i])
1301
+ return a[i] < b[i] ? -1 : 1;
1302
+ }
1303
+ return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
1304
+ }
1305
+ function bigIntShiftRight(value, bits) {
1306
+ let result = 0n;
1307
+ for (let i = 0; i < value.length; i++) {
1308
+ result = (result << 8n) | BigInt(value[i]);
1309
+ }
1310
+ return result >> BigInt(bits);
1311
+ }
1312
+ // Database class
1313
+ export class Database {
1314
+ core;
1315
+ hasher;
1316
+ header;
1317
+ txStart = null;
1318
+ constructor(core, hasher) {
1319
+ this.core = core;
1320
+ this.hasher = hasher;
1321
+ core.seek(0);
1322
+ if ((core.length()) === 0) {
1323
+ this.header = new Header(hasher.id, hasher.digestLength, VERSION, 0 /* Tag.NONE */, MAGIC_NUMBER);
1324
+ this.header.write(core);
1325
+ core.flush();
1326
+ }
1327
+ else {
1328
+ this.header = Header.read(core);
1329
+ this.header.validate();
1330
+ if (this.header.hashSize !== hasher.digestLength) {
1331
+ throw new InvalidHashSizeException();
1332
+ }
1333
+ this.truncate();
1334
+ }
1335
+ }
1336
+ rootCursor() {
1337
+ // if the header tag is none, try re-reading it.
1338
+ // this may be necessary if the database was initialized on a different thread.
1339
+ if (this.header.tag === 0 /* Tag.NONE */) {
1340
+ this.core.seek(0);
1341
+ this.header = Header.read(this.core);
1342
+ }
1343
+ return new WriteCursor(new SlotPointer(null, new Slot(Header.LENGTH, this.header.tag)), this);
1344
+ }
1345
+ freeze() {
1346
+ if (this.txStart !== null) {
1347
+ this.txStart = this.core.length();
1348
+ }
1349
+ else {
1350
+ throw new ExpectedTxStartException();
1351
+ }
1352
+ }
1353
+ compact(targetCore) {
1354
+ const offsetMap = new Map();
1355
+ const hasher = new Hasher(this.hasher.algorithm, this.header.hashId);
1356
+ const target = new Database(targetCore, hasher);
1357
+ if (this.header.tag === 0 /* Tag.NONE */)
1358
+ return target;
1359
+ if (this.header.tag !== 2 /* Tag.ARRAY_LIST */)
1360
+ throw new UnexpectedTagException();
1361
+ // read source's top-level ArrayListHeader
1362
+ this.core.seek(Header.LENGTH);
1363
+ const sourceReader = this.core.reader();
1364
+ const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
1365
+ sourceReader.readFully(headerBytes);
1366
+ const sourceHeader = ArrayListHeader.fromBytes(headerBytes);
1367
+ if (sourceHeader.size === 0)
1368
+ return target;
1369
+ // read the last moment's slot
1370
+ const lastKey = sourceHeader.size - 1;
1371
+ const shift = lastKey < SLOT_COUNT ? 0 : Math.floor(Math.log(lastKey) / Math.log(SLOT_COUNT));
1372
+ const lastSlotPtr = this.readArrayListSlot(sourceHeader.ptr, lastKey, shift, WriteMode.READ_ONLY, true);
1373
+ const momentSlot = lastSlotPtr.slot;
1374
+ // write TopLevelArrayListHeader + root index block to target
1375
+ const targetWriter = targetCore.writer();
1376
+ targetCore.seek(Header.LENGTH);
1377
+ const targetArrayListPtr = Header.LENGTH + TopLevelArrayListHeader.LENGTH;
1378
+ targetWriter.write(new TopLevelArrayListHeader(0, new ArrayListHeader(targetArrayListPtr, 1)).toBytes());
1379
+ targetWriter.write(new Uint8Array(INDEX_BLOCK_SIZE));
1380
+ // recursively remap the moment slot
1381
+ const remappedMoment = remapSlot(this.core, targetCore, this.header.hashSize, offsetMap, momentSlot);
1382
+ // write remapped moment slot into position 0 of target's root index block
1383
+ targetCore.seek(targetArrayListPtr);
1384
+ targetWriter.write(remappedMoment.toBytes());
1385
+ // update target's DatabaseHeader tag
1386
+ target.header = target.header.withTag(2 /* Tag.ARRAY_LIST */);
1387
+ targetCore.seek(0);
1388
+ target.header.write(targetCore);
1389
+ // flush, update file_size, flush again
1390
+ targetCore.flush();
1391
+ const fileSize = targetCore.length();
1392
+ targetCore.seek(Header.LENGTH + ArrayListHeader.LENGTH);
1393
+ targetWriter.writeLong(fileSize);
1394
+ targetCore.flush();
1395
+ return target;
1396
+ }
1397
+ truncate() {
1398
+ if (this.header.tag !== 2 /* Tag.ARRAY_LIST */)
1399
+ return;
1400
+ const rootCursor = this.rootCursor();
1401
+ const listSize = rootCursor.count();
1402
+ if (listSize === 0)
1403
+ return;
1404
+ this.core.seek(Header.LENGTH + ArrayListHeader.LENGTH);
1405
+ const reader = this.core.reader();
1406
+ const headerFileSize = reader.readLong();
1407
+ if (headerFileSize === 0)
1408
+ return;
1409
+ const fileSize = this.core.length();
1410
+ if (fileSize === headerFileSize)
1411
+ return;
1412
+ try {
1413
+ this.core.setLength(headerFileSize);
1414
+ }
1415
+ catch (_) { }
1416
+ }
1417
+ checkHashBytes(hash) {
1418
+ if (hash.length !== this.header.hashSize) {
1419
+ throw new InvalidHashSizeException();
1420
+ }
1421
+ return hash;
1422
+ }
1423
+ checkHash(target) {
1424
+ return this.checkHashBytes(target.hash);
1425
+ }
1426
+ readSlotPointer(writeMode, path, pathI, slotPtr) {
1427
+ if (pathI === path.length) {
1428
+ if (writeMode === WriteMode.READ_ONLY && slotPtr.slot.tag === 0 /* Tag.NONE */) {
1429
+ throw new KeyNotFoundException();
1430
+ }
1431
+ return slotPtr;
1432
+ }
1433
+ const part = path[pathI];
1434
+ const isTopLevel = slotPtr.slot.value === BigInt(Header.LENGTH);
1435
+ const isTxStart = isTopLevel && this.header.tag === 2 /* Tag.ARRAY_LIST */ && this.txStart === null;
1436
+ if (isTxStart) {
1437
+ this.txStart = this.core.length();
1438
+ }
1439
+ try {
1440
+ return part.readSlotPointer(this, isTopLevel, writeMode, path, pathI, slotPtr);
1441
+ }
1442
+ finally {
1443
+ if (isTxStart) {
1444
+ this.txStart = null;
1445
+ }
1446
+ }
1447
+ }
1448
+ // HashMap methods
1449
+ readMapSlot(indexPos, keyHash, keyOffset, writeMode, isTopLevel, target) {
1450
+ if (keyOffset > (this.header.hashSize * 8) / BIT_COUNT) {
1451
+ throw new KeyOffsetExceededException();
1452
+ }
1453
+ const reader = this.core.reader();
1454
+ const writer = this.core.writer();
1455
+ const i = Number(bigIntShiftRight(keyHash, keyOffset * BIT_COUNT) & MASK);
1456
+ const slotPos = indexPos + Slot.LENGTH * i;
1457
+ this.core.seek(slotPos);
1458
+ const slotBytes = new Uint8Array(Slot.LENGTH);
1459
+ reader.readFully(slotBytes);
1460
+ const slot = Slot.fromBytes(slotBytes);
1461
+ const ptr = Number(slot.value);
1462
+ switch (slot.tag) {
1463
+ case 0 /* Tag.NONE */: {
1464
+ switch (writeMode) {
1465
+ case WriteMode.READ_ONLY:
1466
+ throw new KeyNotFoundException();
1467
+ case WriteMode.READ_WRITE: {
1468
+ const hashPos = this.core.length();
1469
+ this.core.seek(hashPos);
1470
+ const keySlotPos = hashPos + this.header.hashSize;
1471
+ const valueSlotPos = keySlotPos + Slot.LENGTH;
1472
+ const kvPair = new KeyValuePair(new Slot(), new Slot(), keyHash);
1473
+ writer.write(kvPair.toBytes());
1474
+ const nextSlot = new Slot(hashPos, 5 /* Tag.KV_PAIR */);
1475
+ this.core.seek(slotPos);
1476
+ writer.write(nextSlot.toBytes());
1477
+ let nextSlotPtr;
1478
+ if (target.kind === 'kv_pair') {
1479
+ nextSlotPtr = new SlotPointer(slotPos, nextSlot);
1480
+ }
1481
+ else if (target.kind === 'key') {
1482
+ nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
1483
+ }
1484
+ else {
1485
+ nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
1486
+ }
1487
+ return new HashMapGetResult(nextSlotPtr, true);
1488
+ }
1489
+ default:
1490
+ throw new UnreachableException();
1491
+ }
1492
+ }
1493
+ case 1 /* Tag.INDEX */: {
1494
+ let nextPtr = ptr;
1495
+ if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1496
+ if (this.txStart !== null) {
1497
+ if (nextPtr < this.txStart) {
1498
+ this.core.seek(ptr);
1499
+ const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
1500
+ reader.readFully(indexBlock);
1501
+ nextPtr = this.core.length();
1502
+ this.core.seek(nextPtr);
1503
+ writer.write(indexBlock);
1504
+ this.core.seek(slotPos);
1505
+ writer.write(new Slot(nextPtr, 1 /* Tag.INDEX */).toBytes());
1506
+ }
1507
+ }
1508
+ else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
1509
+ throw new ExpectedTxStartException();
1510
+ }
1511
+ }
1512
+ return this.readMapSlot(nextPtr, keyHash, keyOffset + 1, writeMode, isTopLevel, target);
1513
+ }
1514
+ case 5 /* Tag.KV_PAIR */: {
1515
+ this.core.seek(ptr);
1516
+ const kvPairBytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
1517
+ reader.readFully(kvPairBytes);
1518
+ const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.header.hashSize);
1519
+ if (arraysEqual(kvPair.hash, keyHash)) {
1520
+ if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1521
+ if (this.txStart !== null) {
1522
+ if (ptr < this.txStart) {
1523
+ const hashPos = this.core.length();
1524
+ this.core.seek(hashPos);
1525
+ const keySlotPos = hashPos + this.header.hashSize;
1526
+ const valueSlotPos = keySlotPos + Slot.LENGTH;
1527
+ writer.write(kvPair.toBytes());
1528
+ const nextSlot = new Slot(hashPos, 5 /* Tag.KV_PAIR */);
1529
+ this.core.seek(slotPos);
1530
+ writer.write(nextSlot.toBytes());
1531
+ let nextSlotPtr;
1532
+ if (target.kind === 'kv_pair') {
1533
+ nextSlotPtr = new SlotPointer(slotPos, nextSlot);
1534
+ }
1535
+ else if (target.kind === 'key') {
1536
+ nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
1537
+ }
1538
+ else {
1539
+ nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
1540
+ }
1541
+ return new HashMapGetResult(nextSlotPtr, false);
1542
+ }
1543
+ }
1544
+ else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
1545
+ throw new ExpectedTxStartException();
1546
+ }
1547
+ }
1548
+ const keySlotPos = ptr + this.header.hashSize;
1549
+ const valueSlotPos = keySlotPos + Slot.LENGTH;
1550
+ let nextSlotPtr;
1551
+ if (target.kind === 'kv_pair') {
1552
+ nextSlotPtr = new SlotPointer(slotPos, slot);
1553
+ }
1554
+ else if (target.kind === 'key') {
1555
+ nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
1556
+ }
1557
+ else {
1558
+ nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
1559
+ }
1560
+ return new HashMapGetResult(nextSlotPtr, false);
1561
+ }
1562
+ else {
1563
+ switch (writeMode) {
1564
+ case WriteMode.READ_ONLY:
1565
+ throw new KeyNotFoundException();
1566
+ case WriteMode.READ_WRITE: {
1567
+ if (keyOffset + 1 >= (this.header.hashSize * 8) / BIT_COUNT) {
1568
+ throw new KeyOffsetExceededException();
1569
+ }
1570
+ const nextI = Number(bigIntShiftRight(kvPair.hash, (keyOffset + 1) * BIT_COUNT) & MASK);
1571
+ const nextIndexPos = this.core.length();
1572
+ this.core.seek(nextIndexPos);
1573
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
1574
+ this.core.seek(nextIndexPos + Slot.LENGTH * nextI);
1575
+ writer.write(slot.toBytes());
1576
+ const res = this.readMapSlot(nextIndexPos, keyHash, keyOffset + 1, writeMode, isTopLevel, target);
1577
+ this.core.seek(slotPos);
1578
+ writer.write(new Slot(nextIndexPos, 1 /* Tag.INDEX */).toBytes());
1579
+ return res;
1580
+ }
1581
+ default:
1582
+ throw new UnreachableException();
1583
+ }
1584
+ }
1585
+ }
1586
+ default:
1587
+ throw new UnexpectedTagException();
1588
+ }
1589
+ }
1590
+ removeMapSlot(indexPos, keyHash, keyOffset, isTopLevel) {
1591
+ if (keyOffset > (this.header.hashSize * 8) / BIT_COUNT) {
1592
+ throw new KeyOffsetExceededException();
1593
+ }
1594
+ const reader = this.core.reader();
1595
+ const writer = this.core.writer();
1596
+ const slotBlock = new Array(SLOT_COUNT);
1597
+ this.core.seek(indexPos);
1598
+ const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
1599
+ reader.readFully(indexBlock);
1600
+ for (let i = 0; i < SLOT_COUNT; i++) {
1601
+ const slotBytes = indexBlock.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
1602
+ slotBlock[i] = Slot.fromBytes(slotBytes);
1603
+ }
1604
+ const i = Number(bigIntShiftRight(keyHash, keyOffset * BIT_COUNT) & MASK);
1605
+ const slotPos = indexPos + Slot.LENGTH * i;
1606
+ const slot = slotBlock[i];
1607
+ let nextSlot;
1608
+ switch (slot.tag) {
1609
+ case 0 /* Tag.NONE */:
1610
+ throw new KeyNotFoundException();
1611
+ case 1 /* Tag.INDEX */:
1612
+ nextSlot = this.removeMapSlot(Number(slot.value), keyHash, keyOffset + 1, isTopLevel);
1613
+ break;
1614
+ case 5 /* Tag.KV_PAIR */: {
1615
+ this.core.seek(Number(slot.value));
1616
+ const kvPairBytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
1617
+ reader.readFully(kvPairBytes);
1618
+ const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.header.hashSize);
1619
+ if (arraysEqual(kvPair.hash, keyHash)) {
1620
+ nextSlot = new Slot();
1621
+ }
1622
+ else {
1623
+ throw new KeyNotFoundException();
1624
+ }
1625
+ break;
1626
+ }
1627
+ default:
1628
+ throw new UnexpectedTagException();
1629
+ }
1630
+ if (keyOffset === 0) {
1631
+ this.core.seek(slotPos);
1632
+ writer.write(nextSlot.toBytes());
1633
+ return new Slot(indexPos, 1 /* Tag.INDEX */);
1634
+ }
1635
+ let slotToReturnMaybe = new Slot();
1636
+ slotBlock[i] = nextSlot;
1637
+ for (const blockSlot of slotBlock) {
1638
+ if (blockSlot.tag === 0 /* Tag.NONE */)
1639
+ continue;
1640
+ if (slotToReturnMaybe !== null) {
1641
+ if (slotToReturnMaybe.tag !== 0 /* Tag.NONE */) {
1642
+ slotToReturnMaybe = null;
1643
+ break;
1644
+ }
1645
+ }
1646
+ slotToReturnMaybe = blockSlot;
1647
+ }
1648
+ if (slotToReturnMaybe !== null) {
1649
+ switch (slotToReturnMaybe.tag) {
1650
+ case 0 /* Tag.NONE */:
1651
+ case 5 /* Tag.KV_PAIR */:
1652
+ return slotToReturnMaybe;
1653
+ default:
1654
+ break;
1655
+ }
1656
+ }
1657
+ if (!isTopLevel) {
1658
+ if (this.txStart !== null) {
1659
+ if (indexPos < this.txStart) {
1660
+ const nextIndexPos = this.core.length();
1661
+ this.core.seek(nextIndexPos);
1662
+ writer.write(indexBlock);
1663
+ const nextSlotPos = nextIndexPos + Slot.LENGTH * i;
1664
+ this.core.seek(nextSlotPos);
1665
+ writer.write(nextSlot.toBytes());
1666
+ return new Slot(nextIndexPos, 1 /* Tag.INDEX */);
1667
+ }
1668
+ }
1669
+ else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
1670
+ throw new ExpectedTxStartException();
1671
+ }
1672
+ }
1673
+ this.core.seek(slotPos);
1674
+ writer.write(nextSlot.toBytes());
1675
+ return new Slot(indexPos, 1 /* Tag.INDEX */);
1676
+ }
1677
+ // ArrayList methods
1678
+ readArrayListSlotAppend(header, writeMode, isTopLevel) {
1679
+ const writer = this.core.writer();
1680
+ let indexPos = header.ptr;
1681
+ const key = header.size;
1682
+ const prevShift = key < SLOT_COUNT ? 0 : Math.floor(Math.log(key - 1) / Math.log(SLOT_COUNT));
1683
+ const nextShift = key < SLOT_COUNT ? 0 : Math.floor(Math.log(key) / Math.log(SLOT_COUNT));
1684
+ if (prevShift !== nextShift) {
1685
+ const nextIndexPos = this.core.length();
1686
+ this.core.seek(nextIndexPos);
1687
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
1688
+ this.core.seek(nextIndexPos);
1689
+ writer.write(new Slot(indexPos, 1 /* Tag.INDEX */).toBytes());
1690
+ indexPos = nextIndexPos;
1691
+ }
1692
+ const slotPtr = this.readArrayListSlot(indexPos, key, nextShift, writeMode, isTopLevel);
1693
+ return new ArrayListAppendResult(new ArrayListHeader(indexPos, header.size + 1), slotPtr);
1694
+ }
1695
+ readArrayListSlot(indexPos, key, shift, writeMode, isTopLevel) {
1696
+ if (shift >= MAX_BRANCH_LENGTH)
1697
+ throw new MaxShiftExceededException();
1698
+ const reader = this.core.reader();
1699
+ const i = (key >>> (shift * BIT_COUNT)) & (SLOT_COUNT - 1);
1700
+ const slotPos = indexPos + Slot.LENGTH * i;
1701
+ this.core.seek(slotPos);
1702
+ const slotBytes = new Uint8Array(Slot.LENGTH);
1703
+ reader.readFully(slotBytes);
1704
+ const slot = Slot.fromBytes(slotBytes);
1705
+ if (shift === 0) {
1706
+ return new SlotPointer(slotPos, slot);
1707
+ }
1708
+ const ptr = Number(slot.value);
1709
+ switch (slot.tag) {
1710
+ case 0 /* Tag.NONE */: {
1711
+ switch (writeMode) {
1712
+ case WriteMode.READ_ONLY:
1713
+ throw new KeyNotFoundException();
1714
+ case WriteMode.READ_WRITE: {
1715
+ const writer = this.core.writer();
1716
+ const nextIndexPos = this.core.length();
1717
+ this.core.seek(nextIndexPos);
1718
+ writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
1719
+ if (isTopLevel) {
1720
+ const fileSize = this.core.length();
1721
+ this.core.seek(Header.LENGTH + ArrayListHeader.LENGTH);
1722
+ writer.writeLong(fileSize);
1723
+ }
1724
+ this.core.seek(slotPos);
1725
+ writer.write(new Slot(nextIndexPos, 1 /* Tag.INDEX */).toBytes());
1726
+ return this.readArrayListSlot(nextIndexPos, key, shift - 1, writeMode, isTopLevel);
1727
+ }
1728
+ default:
1729
+ throw new UnreachableException();
1730
+ }
1731
+ }
1732
+ case 1 /* Tag.INDEX */: {
1733
+ let nextPtr = ptr;
1734
+ if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1735
+ if (this.txStart !== null) {
1736
+ if (nextPtr < this.txStart) {
1737
+ this.core.seek(ptr);
1738
+ const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
1739
+ reader.readFully(indexBlock);
1740
+ const writer = this.core.writer();
1741
+ nextPtr = this.core.length();
1742
+ this.core.seek(nextPtr);
1743
+ writer.write(indexBlock);
1744
+ this.core.seek(slotPos);
1745
+ writer.write(new Slot(nextPtr, 1 /* Tag.INDEX */).toBytes());
1746
+ }
1747
+ }
1748
+ else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
1749
+ throw new ExpectedTxStartException();
1750
+ }
1751
+ }
1752
+ return this.readArrayListSlot(nextPtr, key, shift - 1, writeMode, isTopLevel);
1753
+ }
1754
+ default:
1755
+ throw new UnexpectedTagException();
1756
+ }
1757
+ }
1758
+ readArrayListSlice(header, size) {
1759
+ const reader = this.core.reader();
1760
+ if (size > header.size || size < 0) {
1761
+ throw new KeyNotFoundException();
1762
+ }
1763
+ const prevShift = header.size < SLOT_COUNT + 1 ? 0 : Math.floor(Math.log(header.size - 1) / Math.log(SLOT_COUNT));
1764
+ const nextShift = size < SLOT_COUNT + 1 ? 0 : Math.floor(Math.log(size - 1) / Math.log(SLOT_COUNT));
1765
+ if (prevShift === nextShift) {
1766
+ return new ArrayListHeader(header.ptr, size);
1767
+ }
1768
+ else {
1769
+ let shift = prevShift;
1770
+ let indexPos = header.ptr;
1771
+ while (shift > nextShift) {
1772
+ this.core.seek(indexPos);
1773
+ const slotBytes = new Uint8Array(Slot.LENGTH);
1774
+ reader.readFully(slotBytes);
1775
+ const slot = Slot.fromBytes(slotBytes);
1776
+ shift -= 1;
1777
+ indexPos = Number(slot.value);
1778
+ }
1779
+ return new ArrayListHeader(indexPos, size);
1780
+ }
1781
+ }
1782
+ // linked_array_list (backed by a count-augmented B-tree)
1783
+ readBTreeNode(ptr) {
1784
+ this.core.seek(ptr);
1785
+ const reader = this.core.reader();
1786
+ const headerBytes = new Uint8Array(BTREE_NODE_HEADER_SIZE);
1787
+ reader.readFully(headerBytes);
1788
+ const kindInt = headerBytes[0];
1789
+ if (kindInt > BTreeNodeKind.BRANCH)
1790
+ throw new InvalidBTreeNodeKindException();
1791
+ const kind = kindInt;
1792
+ const num = headerBytes[1];
1793
+ if (num > BTREE_SLOT_COUNT)
1794
+ throw new InvalidBTreeNodeException();
1795
+ const node = new BTreeNode(kind, num);
1796
+ switch (kind) {
1797
+ case BTreeNodeKind.LEAF: {
1798
+ const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
1799
+ reader.readFully(body);
1800
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
1801
+ node.values[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
1802
+ }
1803
+ break;
1804
+ }
1805
+ case BTreeNodeKind.BRANCH: {
1806
+ const body = new Uint8Array((Slot.LENGTH + 8) * BTREE_SLOT_COUNT);
1807
+ reader.readFully(body);
1808
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
1809
+ node.children[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
1810
+ }
1811
+ const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
1812
+ const countsOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
1813
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
1814
+ node.counts[i] = Number(view.getBigInt64(countsOffset + i * 8, false));
1815
+ }
1816
+ break;
1817
+ }
1818
+ }
1819
+ return node;
1820
+ }
1821
+ // always writes the node as a block at ptr. b-tree mutations are persistent:
1822
+ // every node on the path from the root is rewritten, while untouched subtrees
1823
+ // are shared by pointer.
1824
+ writeBTreeNodeAt(node, ptr) {
1825
+ this.core.seek(ptr);
1826
+ const writer = this.core.writer();
1827
+ const bodySize = node.kind === BTreeNodeKind.LEAF ? BTREE_LEAF_BLOCK_SIZE : BTREE_BRANCH_BLOCK_SIZE;
1828
+ const buffer = new Uint8Array(bodySize);
1829
+ buffer[0] = node.kind;
1830
+ buffer[1] = node.num;
1831
+ let off = BTREE_NODE_HEADER_SIZE;
1832
+ switch (node.kind) {
1833
+ case BTreeNodeKind.LEAF:
1834
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
1835
+ buffer.set(node.values[i].toBytes(), off);
1836
+ off += Slot.LENGTH;
1837
+ }
1838
+ break;
1839
+ case BTreeNodeKind.BRANCH: {
1840
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
1841
+ buffer.set(node.children[i].toBytes(), off);
1842
+ off += Slot.LENGTH;
1843
+ }
1844
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1845
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
1846
+ view.setBigInt64(off, BigInt(node.counts[i]), false);
1847
+ off += 8;
1848
+ }
1849
+ break;
1850
+ }
1851
+ }
1852
+ writer.write(buffer);
1853
+ }
1854
+ // appends the node as a fresh block and returns its position
1855
+ writeBTreeNode(node) {
1856
+ const ptr = this.core.length();
1857
+ this.writeBTreeNodeAt(node, ptr);
1858
+ return ptr;
1859
+ }
1860
+ // a node is safe to mutate in place when it was created in the current transaction
1861
+ // (offset >= txStart), since no committed moment and no pre-concat sharing can
1862
+ // reference it. concat advances txStart (an implicit freeze) precisely so its
1863
+ // shared subtrees fall below it here. for an ephemeral (non-array-list) top level
1864
+ // there is no transaction, so everything is mutable in place until a concat first
1865
+ // sets txStart.
1866
+ btreeReusable(ptr) {
1867
+ if (this.txStart !== null)
1868
+ return ptr >= this.txStart;
1869
+ return this.header.tag !== 2 /* Tag.ARRAY_LIST */;
1870
+ }
1871
+ // write a new version of a node, reusing oldPtr's position in place if that node
1872
+ // belongs to this transaction, otherwise appending a copy
1873
+ btreeWriteNode(node, oldPtr) {
1874
+ if (this.btreeReusable(oldPtr)) {
1875
+ this.writeBTreeNodeAt(node, oldPtr);
1876
+ return oldPtr;
1877
+ }
1878
+ return this.writeBTreeNode(node);
1879
+ }
1880
+ btreeNewRoot() {
1881
+ return this.writeBTreeNode(new BTreeNode(BTreeNodeKind.LEAF, 0));
1882
+ }
1883
+ // descend to the value slot at the given rank (0-based), returning a pointer to it
1884
+ // (its file position and current slot).
1885
+ readBTreeSlot(rootPtr, rank) {
1886
+ let nodePtr = rootPtr;
1887
+ let rem = rank;
1888
+ while (true) {
1889
+ const node = this.readBTreeNode(nodePtr);
1890
+ if (node.kind === BTreeNodeKind.LEAF) {
1891
+ const position = nodePtr + BTREE_NODE_HEADER_SIZE + rem * Slot.LENGTH;
1892
+ return new SlotPointer(position, node.values[rem]);
1893
+ }
1894
+ else {
1895
+ let i = 0;
1896
+ while (i + 1 < node.num && rem >= node.counts[i]) {
1897
+ rem -= node.counts[i];
1898
+ i++;
1899
+ }
1900
+ nodePtr = Number(node.children[i].value);
1901
+ }
1902
+ }
1903
+ }
1904
+ // insert a placeholder slot at `rank` within the subtree at nodePtr, writing new
1905
+ // nodes along the path. the caller fills in the value at the returned valuePosition.
1906
+ btreeInsert(nodePtr, rank) {
1907
+ const node = this.readBTreeNode(nodePtr);
1908
+ switch (node.kind) {
1909
+ case BTreeNodeKind.LEAF: {
1910
+ // build the entries with a placeholder spliced in at `rank`. the placeholder
1911
+ // is a NONE slot marked full so that, if the caller never writes a value
1912
+ // (e.g. appendCursor), iteration still counts it as an element rather than
1913
+ // skipping it as padding.
1914
+ const r = rank;
1915
+ const vals = [];
1916
+ for (let k = 0; k < r; k++)
1917
+ vals.push(node.values[k]);
1918
+ vals.push(new Slot(0, 0 /* Tag.NONE */, true));
1919
+ for (let k = r; k < node.num; k++)
1920
+ vals.push(node.values[k]);
1921
+ const total = node.num + 1;
1922
+ if (total <= BTREE_SLOT_COUNT) {
1923
+ const leaf = new BTreeNode(BTreeNodeKind.LEAF, total);
1924
+ for (let k = 0; k < total; k++)
1925
+ leaf.values[k] = vals[k];
1926
+ const ptr = this.btreeWriteNode(leaf, nodePtr);
1927
+ return new BTreeInsertResult(ptr, total, ptr + BTREE_NODE_HEADER_SIZE + r * Slot.LENGTH, null);
1928
+ }
1929
+ // overflow: split into two leaves (reuse this node for the left half)
1930
+ const leftN = BTREE_SPLIT_COUNT;
1931
+ const rightN = total - leftN;
1932
+ const left = new BTreeNode(BTreeNodeKind.LEAF, leftN);
1933
+ for (let k = 0; k < leftN; k++)
1934
+ left.values[k] = vals[k];
1935
+ const right = new BTreeNode(BTreeNodeKind.LEAF, rightN);
1936
+ for (let k = 0; k < rightN; k++)
1937
+ right.values[k] = vals[leftN + k];
1938
+ const leftPtr = this.btreeWriteNode(left, nodePtr);
1939
+ const rightPtr = this.writeBTreeNode(right);
1940
+ const valuePosition = r < leftN
1941
+ ? leftPtr + BTREE_NODE_HEADER_SIZE + r * Slot.LENGTH
1942
+ : rightPtr + BTREE_NODE_HEADER_SIZE + (r - leftN) * Slot.LENGTH;
1943
+ return new BTreeInsertResult(leftPtr, leftN, valuePosition, new BTreeNodeRef(rightPtr, rightN));
1944
+ }
1945
+ case BTreeNodeKind.BRANCH: {
1946
+ // pick the child that contains `rank`
1947
+ let i = 0;
1948
+ let rem = rank;
1949
+ while (i + 1 < node.num && rem > node.counts[i]) {
1950
+ rem -= node.counts[i];
1951
+ i++;
1952
+ }
1953
+ const child = this.btreeInsert(Number(node.children[i].value), rem);
1954
+ // rebuild this branch with the (possibly split) child
1955
+ const children = [];
1956
+ const counts = [];
1957
+ for (let k = 0; k < node.num; k++) {
1958
+ children.push(node.children[k]);
1959
+ counts.push(node.counts[k]);
1960
+ }
1961
+ children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
1962
+ counts[i] = child.count;
1963
+ let total = node.num;
1964
+ if (child.split !== null) {
1965
+ children.splice(i + 1, 0, new Slot(child.split.nodePtr, 1 /* Tag.INDEX */));
1966
+ counts.splice(i + 1, 0, child.split.count);
1967
+ total = node.num + 1;
1968
+ }
1969
+ if (total <= BTREE_SLOT_COUNT) {
1970
+ const branch = new BTreeNode(BTreeNodeKind.BRANCH, total);
1971
+ for (let k = 0; k < total; k++) {
1972
+ branch.children[k] = children[k];
1973
+ branch.counts[k] = counts[k];
1974
+ }
1975
+ const ptr = this.btreeWriteNode(branch, nodePtr);
1976
+ return new BTreeInsertResult(ptr, branch.subtreeCount(), child.valuePosition, null);
1977
+ }
1978
+ // overflow: split into two branches (reuse this node for the left half)
1979
+ const leftN = BTREE_SPLIT_COUNT;
1980
+ const rightN = total - leftN;
1981
+ const left = new BTreeNode(BTreeNodeKind.BRANCH, leftN);
1982
+ for (let k = 0; k < leftN; k++) {
1983
+ left.children[k] = children[k];
1984
+ left.counts[k] = counts[k];
1985
+ }
1986
+ const right = new BTreeNode(BTreeNodeKind.BRANCH, rightN);
1987
+ for (let k = 0; k < rightN; k++) {
1988
+ right.children[k] = children[leftN + k];
1989
+ right.counts[k] = counts[leftN + k];
1990
+ }
1991
+ const leftPtr = this.btreeWriteNode(left, nodePtr);
1992
+ const rightPtr = this.writeBTreeNode(right);
1993
+ return new BTreeInsertResult(leftPtr, left.subtreeCount(), child.valuePosition, new BTreeNodeRef(rightPtr, right.subtreeCount()));
1994
+ }
1995
+ }
1996
+ throw new UnreachableException();
1997
+ }
1998
+ // turn an insert result into a root pointer, growing the tree a level if the old
1999
+ // root split (shares the root-building logic with btreeMakeRoot)
2000
+ btreeGrowRoot(result) {
2001
+ return this.btreeMakeRoot(new BTreeJoinResult(result.nodePtr, result.count, result.split));
2002
+ }
2003
+ // descend to the value slot at `rank` for writing, copy-on-writing only the nodes
2004
+ // that belong to a past transaction. the element count is unchanged, so when the
2005
+ // whole path is already this-transaction nothing is rewritten and the caller writes
2006
+ // straight into the existing leaf.
2007
+ btreeGetForWrite(nodePtr, rank) {
2008
+ const node = this.readBTreeNode(nodePtr);
2009
+ switch (node.kind) {
2010
+ case BTreeNodeKind.LEAF: {
2011
+ const newPtr = this.btreeReusable(nodePtr) ? nodePtr : this.writeBTreeNode(node);
2012
+ return new BTreeWriteSlot(newPtr, newPtr + BTREE_NODE_HEADER_SIZE + rank * Slot.LENGTH, node.values[rank]);
2013
+ }
2014
+ case BTreeNodeKind.BRANCH: {
2015
+ let i = 0;
2016
+ let rem = rank;
2017
+ while (i + 1 < node.num && rem >= node.counts[i]) {
2018
+ rem -= node.counts[i];
2019
+ i++;
2020
+ }
2021
+ const childPtr = Number(node.children[i].value);
2022
+ const child = this.btreeGetForWrite(childPtr, rem);
2023
+ // if the child stayed put, this branch is unchanged too
2024
+ if (child.nodePtr === childPtr) {
2025
+ return new BTreeWriteSlot(nodePtr, child.valuePosition, child.slot);
2026
+ }
2027
+ node.children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
2028
+ const newPtr = this.btreeWriteNode(node, nodePtr);
2029
+ return new BTreeWriteSlot(newPtr, child.valuePosition, child.slot);
2030
+ }
2031
+ }
2032
+ throw new UnreachableException();
2033
+ }
2034
+ // join (concat): a true O(log n), structure-sharing concatenation of two trees where
2035
+ // every element of `a` precedes every element of `b`. unlike the rebuild helpers
2036
+ // above, untouched subtrees are shared by pointer, so concatenating a list with
2037
+ // itself stays cheap.
2038
+ // height of a tree = number of branch levels above the leaves
2039
+ btreeHeight(rootPtr) {
2040
+ let ptr = rootPtr;
2041
+ let height = 0;
2042
+ while (true) {
2043
+ const node = this.readBTreeNode(ptr);
2044
+ if (node.kind === BTreeNodeKind.LEAF)
2045
+ return height;
2046
+ height++;
2047
+ ptr = Number(node.children[0].value);
2048
+ }
2049
+ }
2050
+ btreeMakeRoot(result) {
2051
+ if (result.split !== null) {
2052
+ const root = new BTreeNode(BTreeNodeKind.BRANCH, 2);
2053
+ root.children[0] = new Slot(result.nodePtr, 1 /* Tag.INDEX */);
2054
+ root.children[1] = new Slot(result.split.nodePtr, 1 /* Tag.INDEX */);
2055
+ root.counts[0] = result.count;
2056
+ root.counts[1] = result.split.count;
2057
+ return this.writeBTreeNode(root);
2058
+ }
2059
+ return result.nodePtr;
2060
+ }
2061
+ // write `vals` as one leaf, or split into two balanced leaves if it exceeds the node
2062
+ // capacity
2063
+ btreeAssembleLeaf(vals, total) {
2064
+ if (total <= BTREE_SLOT_COUNT) {
2065
+ const leaf = new BTreeNode(BTreeNodeKind.LEAF, total);
2066
+ for (let k = 0; k < total; k++)
2067
+ leaf.values[k] = vals[k];
2068
+ return new BTreeJoinResult(this.writeBTreeNode(leaf), total, null);
2069
+ }
2070
+ const leftN = Math.floor(total / 2);
2071
+ const left = new BTreeNode(BTreeNodeKind.LEAF, leftN);
2072
+ for (let k = 0; k < leftN; k++)
2073
+ left.values[k] = vals[k];
2074
+ const right = new BTreeNode(BTreeNodeKind.LEAF, total - leftN);
2075
+ for (let k = 0; k < total - leftN; k++)
2076
+ right.values[k] = vals[leftN + k];
2077
+ return new BTreeJoinResult(this.writeBTreeNode(left), leftN, new BTreeNodeRef(this.writeBTreeNode(right), total - leftN));
2078
+ }
2079
+ // write `children`/`counts` as one branch, or split into two balanced branches
2080
+ btreeAssembleBranch(children, counts, total) {
2081
+ if (total <= BTREE_SLOT_COUNT) {
2082
+ const branch = new BTreeNode(BTreeNodeKind.BRANCH, total);
2083
+ for (let k = 0; k < total; k++) {
2084
+ branch.children[k] = children[k];
2085
+ branch.counts[k] = counts[k];
2086
+ }
2087
+ return new BTreeJoinResult(this.writeBTreeNode(branch), branch.subtreeCount(), null);
2088
+ }
2089
+ const leftN = Math.floor(total / 2);
2090
+ const left = new BTreeNode(BTreeNodeKind.BRANCH, leftN);
2091
+ for (let k = 0; k < leftN; k++) {
2092
+ left.children[k] = children[k];
2093
+ left.counts[k] = counts[k];
2094
+ }
2095
+ const right = new BTreeNode(BTreeNodeKind.BRANCH, total - leftN);
2096
+ for (let k = 0; k < total - leftN; k++) {
2097
+ right.children[k] = children[leftN + k];
2098
+ right.counts[k] = counts[leftN + k];
2099
+ }
2100
+ return new BTreeJoinResult(this.writeBTreeNode(left), left.subtreeCount(), new BTreeNodeRef(this.writeBTreeNode(right), right.subtreeCount()));
2101
+ }
2102
+ // merge two nodes of equal height (a precedes b) into one or two nodes
2103
+ btreeMergeNodes(a, b) {
2104
+ switch (a.kind) {
2105
+ case BTreeNodeKind.LEAF: {
2106
+ const vals = [];
2107
+ for (let k = 0; k < a.num; k++)
2108
+ vals.push(a.values[k]);
2109
+ for (let k = 0; k < b.num; k++)
2110
+ vals.push(b.values[k]);
2111
+ return this.btreeAssembleLeaf(vals, a.num + b.num);
2112
+ }
2113
+ case BTreeNodeKind.BRANCH: {
2114
+ const children = [];
2115
+ const counts = [];
2116
+ for (let k = 0; k < a.num; k++) {
2117
+ children.push(a.children[k]);
2118
+ counts.push(a.counts[k]);
2119
+ }
2120
+ for (let k = 0; k < b.num; k++) {
2121
+ children.push(b.children[k]);
2122
+ counts.push(b.counts[k]);
2123
+ }
2124
+ return this.btreeAssembleBranch(children, counts, a.num + b.num);
2125
+ }
2126
+ }
2127
+ throw new UnreachableException();
2128
+ }
2129
+ // join b (shorter) into the rightmost spine of a (taller), at height hb
2130
+ btreeJoinRight(aPtr, ha, bPtr, hb) {
2131
+ const a = this.readBTreeNode(aPtr);
2132
+ const last = a.num - 1;
2133
+ const sub = ha - 1 === hb
2134
+ ? this.btreeMergeNodes(this.readBTreeNode(Number(a.children[last].value)), this.readBTreeNode(bPtr))
2135
+ : this.btreeJoinRight(Number(a.children[last].value), ha - 1, bPtr, hb);
2136
+ const children = [];
2137
+ const counts = [];
2138
+ for (let k = 0; k < a.num; k++) {
2139
+ children.push(a.children[k]);
2140
+ counts.push(a.counts[k]);
2141
+ }
2142
+ children[last] = new Slot(sub.nodePtr, 1 /* Tag.INDEX */);
2143
+ counts[last] = sub.count;
2144
+ let total = a.num;
2145
+ if (sub.split !== null) {
2146
+ children[total] = new Slot(sub.split.nodePtr, 1 /* Tag.INDEX */);
2147
+ counts[total] = sub.split.count;
2148
+ total += 1;
2149
+ }
2150
+ return this.btreeAssembleBranch(children, counts, total);
2151
+ }
2152
+ // join a (shorter) into the leftmost spine of b (taller), at height ha
2153
+ btreeJoinLeft(aPtr, ha, bPtr, hb) {
2154
+ const b = this.readBTreeNode(bPtr);
2155
+ const sub = hb - 1 === ha
2156
+ ? this.btreeMergeNodes(this.readBTreeNode(aPtr), this.readBTreeNode(Number(b.children[0].value)))
2157
+ : this.btreeJoinLeft(aPtr, ha, Number(b.children[0].value), hb - 1);
2158
+ const children = [];
2159
+ const counts = [];
2160
+ children[0] = new Slot(sub.nodePtr, 1 /* Tag.INDEX */);
2161
+ counts[0] = sub.count;
2162
+ let head = 1;
2163
+ if (sub.split !== null) {
2164
+ children[1] = new Slot(sub.split.nodePtr, 1 /* Tag.INDEX */);
2165
+ counts[1] = sub.split.count;
2166
+ head = 2;
2167
+ }
2168
+ for (let k = 0; k < b.num - 1; k++) {
2169
+ children[head + k] = b.children[1 + k];
2170
+ counts[head + k] = b.counts[1 + k];
2171
+ }
2172
+ return this.btreeAssembleBranch(children, counts, head + b.num - 1);
2173
+ }
2174
+ btreeJoin(rootA, rootB) {
2175
+ const ha = this.btreeHeight(rootA);
2176
+ const hb = this.btreeHeight(rootB);
2177
+ let result;
2178
+ if (ha === hb) {
2179
+ result = this.btreeMergeNodes(this.readBTreeNode(rootA), this.readBTreeNode(rootB));
2180
+ }
2181
+ else if (ha > hb) {
2182
+ result = this.btreeJoinRight(rootA, ha, rootB, hb);
2183
+ }
2184
+ else {
2185
+ result = this.btreeJoinLeft(rootA, ha, rootB, hb);
2186
+ }
2187
+ return this.btreeMakeRoot(result);
2188
+ }
2189
+ // split (used by slice and remove): a true O(log n), structure-sharing split of a
2190
+ // tree into [0, rank) and [rank, size). partial nodes along the path are reassembled
2191
+ // with join, so the result trees stay balanced.
2192
+ // build a tree from a run of sibling children (already height-h-1 subtrees): empty ->
2193
+ // a new empty leaf, one -> that child unwrapped, many -> a branch
2194
+ btreeSubtree(children, counts, start, len) {
2195
+ if (len === 0)
2196
+ return this.btreeNewRoot();
2197
+ if (len === 1)
2198
+ return Number(children[start].value);
2199
+ // len <= BTREE_SLOT_COUNT here, so this never splits
2200
+ const subChildren = [];
2201
+ const subCounts = [];
2202
+ for (let k = 0; k < len; k++) {
2203
+ subChildren.push(children[start + k]);
2204
+ subCounts.push(counts[start + k]);
2205
+ }
2206
+ return this.btreeAssembleBranch(subChildren, subCounts, len).nodePtr;
2207
+ }
2208
+ btreeSplit(rootPtr, rank) {
2209
+ const node = this.readBTreeNode(rootPtr);
2210
+ switch (node.kind) {
2211
+ case BTreeNodeKind.LEAF: {
2212
+ const r = rank;
2213
+ const left = new BTreeNode(BTreeNodeKind.LEAF, r);
2214
+ for (let k = 0; k < r; k++)
2215
+ left.values[k] = node.values[k];
2216
+ const right = new BTreeNode(BTreeNodeKind.LEAF, node.num - r);
2217
+ for (let k = 0; k < node.num - r; k++)
2218
+ right.values[k] = node.values[r + k];
2219
+ return new BTreeSplitResult(this.writeBTreeNode(left), this.writeBTreeNode(right));
2220
+ }
2221
+ case BTreeNodeKind.BRANCH: {
2222
+ let i = 0;
2223
+ let rem = rank;
2224
+ while (i + 1 < node.num && rem > node.counts[i]) {
2225
+ rem -= node.counts[i];
2226
+ i++;
2227
+ }
2228
+ const child = this.btreeSplit(Number(node.children[i].value), rem);
2229
+ const leftSub = this.btreeSubtree(node.children, node.counts, 0, i);
2230
+ const rightSub = this.btreeSubtree(node.children, node.counts, i + 1, node.num - (i + 1));
2231
+ return new BTreeSplitResult(this.btreeJoin(leftSub, child.left), this.btreeJoin(child.right, rightSub));
2232
+ }
2233
+ }
2234
+ throw new UnreachableException();
2235
+ }
2236
+ // sorted_map / sorted_set
2237
+ readSortedNode(ptr) {
2238
+ this.core.seek(ptr);
2239
+ const reader = this.core.reader();
2240
+ const headerBytes = new Uint8Array(BTREE_NODE_HEADER_SIZE);
2241
+ reader.readFully(headerBytes);
2242
+ const kindInt = headerBytes[0];
2243
+ if (kindInt > BTreeNodeKind.BRANCH)
2244
+ throw new InvalidBTreeNodeKindException();
2245
+ const kind = kindInt;
2246
+ const num = headerBytes[1];
2247
+ if (num > BTREE_SLOT_COUNT)
2248
+ throw new InvalidBTreeNodeException();
2249
+ const node = new SortedNode(kind, num);
2250
+ switch (kind) {
2251
+ case BTreeNodeKind.LEAF: {
2252
+ const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
2253
+ reader.readFully(body);
2254
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2255
+ node.entries[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
2256
+ }
2257
+ break;
2258
+ }
2259
+ case BTreeNodeKind.BRANCH: {
2260
+ const body = new Uint8Array((Slot.LENGTH * 2 + 8) * BTREE_SLOT_COUNT);
2261
+ reader.readFully(body);
2262
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2263
+ node.children[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
2264
+ }
2265
+ const sepOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
2266
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2267
+ node.separators[i] = Slot.fromBytes(body.slice(sepOffset + i * Slot.LENGTH, sepOffset + i * Slot.LENGTH + Slot.LENGTH));
2268
+ }
2269
+ const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
2270
+ const countsOffset = Slot.LENGTH * 2 * BTREE_SLOT_COUNT;
2271
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2272
+ node.counts[i] = Number(view.getBigInt64(countsOffset + i * 8, false));
2273
+ }
2274
+ break;
2275
+ }
2276
+ }
2277
+ return node;
2278
+ }
2279
+ writeSortedNodeAt(node, ptr) {
2280
+ this.core.seek(ptr);
2281
+ const writer = this.core.writer();
2282
+ const bodySize = node.kind === BTreeNodeKind.LEAF ? SORTED_LEAF_BLOCK_SIZE : SORTED_BRANCH_BLOCK_SIZE;
2283
+ const buffer = new Uint8Array(bodySize);
2284
+ buffer[0] = node.kind;
2285
+ buffer[1] = node.num;
2286
+ let off = BTREE_NODE_HEADER_SIZE;
2287
+ switch (node.kind) {
2288
+ case BTreeNodeKind.LEAF:
2289
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2290
+ buffer.set(node.entries[i].toBytes(), off);
2291
+ off += Slot.LENGTH;
2292
+ }
2293
+ break;
2294
+ case BTreeNodeKind.BRANCH: {
2295
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2296
+ buffer.set(node.children[i].toBytes(), off);
2297
+ off += Slot.LENGTH;
2298
+ }
2299
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2300
+ buffer.set(node.separators[i].toBytes(), off);
2301
+ off += Slot.LENGTH;
2302
+ }
2303
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
2304
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2305
+ view.setBigInt64(off, BigInt(node.counts[i]), false);
2306
+ off += 8;
2307
+ }
2308
+ break;
2309
+ }
2310
+ }
2311
+ writer.write(buffer);
2312
+ }
2313
+ writeSortedNode(node) {
2314
+ const ptr = this.core.length();
2315
+ this.writeSortedNodeAt(node, ptr);
2316
+ return ptr;
2317
+ }
2318
+ // reuse oldPtr's position in place when it belongs to this transaction
2319
+ // (mirrors btreeWriteNode / the txStart path-copying model)
2320
+ sortedWriteNode(node, oldPtr) {
2321
+ if (this.btreeReusable(oldPtr)) {
2322
+ this.writeSortedNodeAt(node, oldPtr);
2323
+ return oldPtr;
2324
+ }
2325
+ return this.writeSortedNode(node);
2326
+ }
2327
+ readKvPair(kvSlot) {
2328
+ if (kvSlot.tag !== 5 /* Tag.KV_PAIR */)
2329
+ throw new UnexpectedTagException();
2330
+ this.core.seek(Number(kvSlot.value));
2331
+ const reader = this.core.reader();
2332
+ const bytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
2333
+ reader.readFully(bytes);
2334
+ return KeyValuePair.fromBytes(bytes, this.header.hashSize);
2335
+ }
2336
+ // lexicographic comparison of the byte key stored at keySlot (a bytes or short_bytes
2337
+ // slot) against the in-memory target. returns <0, 0, or >0. streams external bytes so
2338
+ // keys of any length work without allocation.
2339
+ compareKey(keySlot, target) {
2340
+ switch (keySlot.tag) {
2341
+ case 7 /* Tag.SHORT_BYTES */: {
2342
+ const buf = new Uint8Array(8);
2343
+ new DataView(buf.buffer).setBigInt64(0, keySlot.value, false);
2344
+ const total = keySlot.full ? 6 : 8;
2345
+ let len = total;
2346
+ for (let i = 0; i < total; i++) {
2347
+ if (buf[i] === 0) {
2348
+ len = i;
2349
+ break;
2350
+ }
2351
+ }
2352
+ return compareBytesUnsigned(buf.subarray(0, len), target);
2353
+ }
2354
+ case 6 /* Tag.BYTES */: {
2355
+ const reader = this.core.reader();
2356
+ this.core.seek(Number(keySlot.value));
2357
+ const len = reader.readLong();
2358
+ let i = 0;
2359
+ while (i < len) {
2360
+ const n = Math.min(256, len - i);
2361
+ const chunk = new Uint8Array(n);
2362
+ reader.readFully(chunk);
2363
+ for (let j = 0; j < n; j++) {
2364
+ const ti = i + j;
2365
+ if (ti >= target.length)
2366
+ return 1; // stored has more, equal so far
2367
+ const b = chunk[j];
2368
+ const t = target[ti];
2369
+ if (b < t)
2370
+ return -1;
2371
+ if (b > t)
2372
+ return 1;
2373
+ }
2374
+ i += n;
2375
+ }
2376
+ return target.length > len ? -1 : 0;
2377
+ }
2378
+ default:
2379
+ throw new UnexpectedTagException();
2380
+ }
2381
+ }
2382
+ // descend by key to the matching leaf entry (the .kv_pair slot), or null
2383
+ sortedGet(rootPtr, key) {
2384
+ let nodePtr = rootPtr;
2385
+ while (true) {
2386
+ const node = this.readSortedNode(nodePtr);
2387
+ if (node.kind === BTreeNodeKind.LEAF) {
2388
+ for (let i = 0; i < node.num; i++) {
2389
+ const entry = node.entries[i];
2390
+ const kv = this.readKvPair(entry);
2391
+ const cmp = this.compareKey(kv.keySlot, key);
2392
+ if (cmp === 0)
2393
+ return new SortedSlot(entry, nodePtr + BTREE_NODE_HEADER_SIZE + i * Slot.LENGTH);
2394
+ if (cmp > 0)
2395
+ return null;
2396
+ }
2397
+ return null;
2398
+ }
2399
+ else {
2400
+ let i = 0;
2401
+ while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0)
2402
+ i++;
2403
+ nodePtr = Number(node.children[i].value);
2404
+ }
2405
+ }
2406
+ }
2407
+ // descend by rank to the leaf entry at the given 0-based index
2408
+ sortedGetByIndex(rootPtr, rank) {
2409
+ let nodePtr = rootPtr;
2410
+ let rem = rank;
2411
+ while (true) {
2412
+ const node = this.readSortedNode(nodePtr);
2413
+ if (node.kind === BTreeNodeKind.LEAF) {
2414
+ const i = rem;
2415
+ return new SortedSlot(node.entries[i], nodePtr + BTREE_NODE_HEADER_SIZE + i * Slot.LENGTH);
2416
+ }
2417
+ else {
2418
+ let i = 0;
2419
+ while (i + 1 < node.num && rem >= node.counts[i]) {
2420
+ rem -= node.counts[i];
2421
+ i++;
2422
+ }
2423
+ nodePtr = Number(node.children[i].value);
2424
+ }
2425
+ }
2426
+ }
2427
+ // number of keys strictly less than key (the inverse of getByIndex)
2428
+ sortedRank(rootPtr, key) {
2429
+ let nodePtr = rootPtr;
2430
+ let rank = 0;
2431
+ while (true) {
2432
+ const node = this.readSortedNode(nodePtr);
2433
+ if (node.kind === BTreeNodeKind.LEAF) {
2434
+ for (let i = 0; i < node.num; i++) {
2435
+ const kv = this.readKvPair(node.entries[i]);
2436
+ if (this.compareKey(kv.keySlot, key) < 0)
2437
+ rank += 1;
2438
+ else
2439
+ break;
2440
+ }
2441
+ return rank;
2442
+ }
2443
+ else {
2444
+ let i = 0;
2445
+ while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0) {
2446
+ rank += node.counts[i];
2447
+ i++;
2448
+ }
2449
+ nodePtr = Number(node.children[i].value);
2450
+ }
2451
+ }
2452
+ }
2453
+ // write a byte key as a short_bytes (inline, <=8 bytes, no interior zero) or external
2454
+ // bytes slot
2455
+ writeKey(key) {
2456
+ let hasZero = false;
2457
+ for (const b of key) {
2458
+ if (b === 0) {
2459
+ hasZero = true;
2460
+ break;
2461
+ }
2462
+ }
2463
+ if (key.length <= 8 && !hasZero) {
2464
+ const value = new Uint8Array(8);
2465
+ value.set(key, 0);
2466
+ const v = new DataView(value.buffer).getBigInt64(0, false);
2467
+ return new Slot(v, 7 /* Tag.SHORT_BYTES */);
2468
+ }
2469
+ const writer = this.core.writer();
2470
+ const pos = this.core.length();
2471
+ this.core.seek(pos);
2472
+ writer.writeLong(key.length);
2473
+ writer.write(key);
2474
+ return new Slot(pos, 6 /* Tag.BYTES */);
2475
+ }
2476
+ // materialize a new leaf entry: write the key bytes and a KeyValuePair with an empty
2477
+ // value (the caller fills it via valuePosition). the hash field is unused by sorted
2478
+ // maps (navigation is by key bytes), so it is left zero.
2479
+ sortedNewEntry(key) {
2480
+ const keySlot = this.writeKey(key);
2481
+ const writer = this.core.writer();
2482
+ const kvPos = this.core.length();
2483
+ const kvPair = new KeyValuePair(new Slot(), keySlot, new Uint8Array(this.header.hashSize));
2484
+ this.core.seek(kvPos);
2485
+ writer.write(kvPair.toBytes());
2486
+ return new SortedEntry(new Slot(kvPos, 5 /* Tag.KV_PAIR */), keySlot, kvPos + this.header.hashSize + Slot.LENGTH);
2487
+ }
2488
+ // insert key (or locate it for replacement) within the subtree at nodePtr,
2489
+ // path-copying nodes and maintaining separators + counts. the caller writes the value
2490
+ // at the returned valuePosition.
2491
+ sortedPut(nodePtr, key) {
2492
+ const node = this.readSortedNode(nodePtr);
2493
+ const writer = this.core.writer();
2494
+ switch (node.kind) {
2495
+ case BTreeNodeKind.LEAF: {
2496
+ // find the matching or insertion index
2497
+ let idx = node.num;
2498
+ let found = false;
2499
+ for (let i = 0; i < node.num; i++) {
2500
+ const kv = this.readKvPair(node.entries[i]);
2501
+ const cmp = this.compareKey(kv.keySlot, key);
2502
+ if (cmp === 0) {
2503
+ idx = i;
2504
+ found = true;
2505
+ break;
2506
+ }
2507
+ if (cmp > 0) {
2508
+ idx = i;
2509
+ break;
2510
+ }
2511
+ }
2512
+ if (found) {
2513
+ // replace: return a writable value slot, copy-on-writing the kv_pair if it
2514
+ // belongs to a past moment
2515
+ const leaf = node;
2516
+ const kvSlot = node.entries[idx];
2517
+ let valuePosition;
2518
+ if (this.btreeReusable(Number(kvSlot.value))) {
2519
+ valuePosition = Number(kvSlot.value) + this.header.hashSize + Slot.LENGTH;
2520
+ }
2521
+ else {
2522
+ const kv = this.readKvPair(kvSlot);
2523
+ const newKvPos = this.core.length();
2524
+ this.core.seek(newKvPos);
2525
+ writer.write(kv.toBytes());
2526
+ leaf.entries[idx] = new Slot(newKvPos, 5 /* Tag.KV_PAIR */);
2527
+ valuePosition = newKvPos + this.header.hashSize + Slot.LENGTH;
2528
+ }
2529
+ const ptr = this.sortedWriteNode(leaf, nodePtr);
2530
+ return new SortedInsertResult(ptr, node.num, valuePosition, false, null);
2531
+ }
2532
+ // insert a new entry at idx
2533
+ const entry = this.sortedNewEntry(key);
2534
+ const entries = [];
2535
+ for (let k = 0; k < idx; k++)
2536
+ entries.push(node.entries[k]);
2537
+ entries.push(entry.kvSlot);
2538
+ for (let k = idx; k < node.num; k++)
2539
+ entries.push(node.entries[k]);
2540
+ const total = node.num + 1;
2541
+ if (total <= BTREE_SLOT_COUNT) {
2542
+ const leaf = new SortedNode(BTreeNodeKind.LEAF, total);
2543
+ for (let k = 0; k < total; k++)
2544
+ leaf.entries[k] = entries[k];
2545
+ const ptr = this.sortedWriteNode(leaf, nodePtr);
2546
+ return new SortedInsertResult(ptr, total, entry.valuePosition, true, null);
2547
+ }
2548
+ // overflow: split into two leaves; the new sibling's separator is the key of its
2549
+ // first entry
2550
+ const leftN = BTREE_SPLIT_COUNT;
2551
+ const rightN = total - leftN;
2552
+ const left = new SortedNode(BTreeNodeKind.LEAF, leftN);
2553
+ for (let k = 0; k < leftN; k++)
2554
+ left.entries[k] = entries[k];
2555
+ const right = new SortedNode(BTreeNodeKind.LEAF, rightN);
2556
+ for (let k = 0; k < rightN; k++)
2557
+ right.entries[k] = entries[leftN + k];
2558
+ const separator = this.readKvPair(entries[leftN]).keySlot;
2559
+ const leftPtr = this.sortedWriteNode(left, nodePtr);
2560
+ const rightPtr = this.writeSortedNode(right);
2561
+ return new SortedInsertResult(leftPtr, leftN, entry.valuePosition, true, new SortedSplit(rightPtr, rightN, separator));
2562
+ }
2563
+ case BTreeNodeKind.BRANCH: {
2564
+ let i = 0;
2565
+ while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0)
2566
+ i++;
2567
+ const child = this.sortedPut(Number(node.children[i].value), key);
2568
+ const children = [];
2569
+ const separators = [];
2570
+ const counts = [];
2571
+ for (let k = 0; k < node.num; k++) {
2572
+ children.push(node.children[k]);
2573
+ separators.push(node.separators[k]);
2574
+ counts.push(node.counts[k]);
2575
+ }
2576
+ children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
2577
+ counts[i] = child.count;
2578
+ let total = node.num;
2579
+ if (child.split !== null) {
2580
+ children.splice(i + 1, 0, new Slot(child.split.nodePtr, 1 /* Tag.INDEX */));
2581
+ separators.splice(i + 1, 0, child.split.separator);
2582
+ counts.splice(i + 1, 0, child.split.count);
2583
+ total = node.num + 1;
2584
+ }
2585
+ if (total <= BTREE_SLOT_COUNT) {
2586
+ const branch = new SortedNode(BTreeNodeKind.BRANCH, total);
2587
+ for (let k = 0; k < total; k++) {
2588
+ branch.children[k] = children[k];
2589
+ branch.separators[k] = separators[k];
2590
+ branch.counts[k] = counts[k];
2591
+ }
2592
+ const ptr = this.sortedWriteNode(branch, nodePtr);
2593
+ return new SortedInsertResult(ptr, branch.subtreeCount(), child.valuePosition, child.added, null);
2594
+ }
2595
+ // overflow: split into two branches; the new sibling's separator is the smallest
2596
+ // key of its first child (separators[leftN] of the combined)
2597
+ const leftN = BTREE_SPLIT_COUNT;
2598
+ const rightN = total - leftN;
2599
+ const left = new SortedNode(BTreeNodeKind.BRANCH, leftN);
2600
+ for (let k = 0; k < leftN; k++) {
2601
+ left.children[k] = children[k];
2602
+ left.separators[k] = separators[k];
2603
+ left.counts[k] = counts[k];
2604
+ }
2605
+ const right = new SortedNode(BTreeNodeKind.BRANCH, rightN);
2606
+ for (let k = 0; k < rightN; k++) {
2607
+ right.children[k] = children[leftN + k];
2608
+ right.separators[k] = separators[leftN + k];
2609
+ right.counts[k] = counts[leftN + k];
2610
+ }
2611
+ const separator = separators[leftN];
2612
+ const leftPtr = this.sortedWriteNode(left, nodePtr);
2613
+ const rightPtr = this.writeSortedNode(right);
2614
+ return new SortedInsertResult(leftPtr, left.subtreeCount(), child.valuePosition, child.added, new SortedSplit(rightPtr, right.subtreeCount(), separator));
2615
+ }
2616
+ }
2617
+ throw new UnreachableException();
2618
+ }
2619
+ // remove key from the subtree at nodePtr, path-copying nodes and decrementing counts.
2620
+ // an emptied leaf is left in place (see SortedRemoveResult).
2621
+ sortedRemove(nodePtr, key) {
2622
+ const node = this.readSortedNode(nodePtr);
2623
+ switch (node.kind) {
2624
+ case BTreeNodeKind.LEAF: {
2625
+ let idx = node.num;
2626
+ let found = false;
2627
+ for (let i = 0; i < node.num; i++) {
2628
+ const kv = this.readKvPair(node.entries[i]);
2629
+ const cmp = this.compareKey(kv.keySlot, key);
2630
+ if (cmp === 0) {
2631
+ idx = i;
2632
+ found = true;
2633
+ break;
2634
+ }
2635
+ if (cmp > 0)
2636
+ break;
2637
+ }
2638
+ if (!found)
2639
+ return new SortedRemoveResult(nodePtr, false);
2640
+ const leaf = new SortedNode(BTreeNodeKind.LEAF, node.num - 1);
2641
+ for (let k = 0; k < idx; k++)
2642
+ leaf.entries[k] = node.entries[k];
2643
+ for (let k = idx; k < node.num - 1; k++)
2644
+ leaf.entries[k] = node.entries[k + 1];
2645
+ const ptr = this.sortedWriteNode(leaf, nodePtr);
2646
+ return new SortedRemoveResult(ptr, true);
2647
+ }
2648
+ case BTreeNodeKind.BRANCH: {
2649
+ let i = 0;
2650
+ while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0)
2651
+ i++;
2652
+ const child = this.sortedRemove(Number(node.children[i].value), key);
2653
+ if (!child.found)
2654
+ return new SortedRemoveResult(nodePtr, false);
2655
+ const branch = node;
2656
+ branch.children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
2657
+ branch.counts[i] -= 1;
2658
+ const ptr = this.sortedWriteNode(branch, nodePtr);
2659
+ return new SortedRemoveResult(ptr, true);
2660
+ }
2661
+ }
2662
+ throw new UnreachableException();
2663
+ }
2664
+ sortedGrowRoot(result) {
2665
+ if (result.split !== null) {
2666
+ const split = result.split;
2667
+ const root = new SortedNode(BTreeNodeKind.BRANCH, 2);
2668
+ root.children[0] = new Slot(result.nodePtr, 1 /* Tag.INDEX */);
2669
+ root.children[1] = new Slot(split.nodePtr, 1 /* Tag.INDEX */);
2670
+ root.separators[1] = split.separator; // separators[0] is an unused sentinel
2671
+ root.counts[0] = result.count;
2672
+ root.counts[1] = split.count;
2673
+ return this.writeSortedNode(root);
2674
+ }
2675
+ return result.nodePtr;
2676
+ }
2677
+ // turn a located/inserted kv_pair (at kvPos) into the slot for the requested target.
2678
+ // only the value is writeable (that is how put works); the key and the kv_pair pointer
2679
+ // are immutable, so they are returned with no writeable position.
2680
+ sortedTargetSlot(kvPos, target) {
2681
+ const kv = this.readKvPair(new Slot(kvPos, 5 /* Tag.KV_PAIR */));
2682
+ if (target instanceof SortedMapGetKVPair) {
2683
+ return new SlotPointer(null, new Slot(kvPos, 5 /* Tag.KV_PAIR */));
2684
+ }
2685
+ else if (target instanceof SortedMapGetKey) {
2686
+ return new SlotPointer(null, kv.keySlot);
2687
+ }
2688
+ else if (target instanceof SortedMapGetValue) {
2689
+ return new SlotPointer(kvPos + this.header.hashSize + Slot.LENGTH, kv.valueSlot);
2690
+ }
2691
+ else {
2692
+ throw new UnexpectedTagException();
2693
+ }
2694
+ }
2695
+ }
2696
+ // compaction helpers
2697
+ function remapSlot(sourceCore, targetCore, hashSize, offsetMap, slot) {
2698
+ switch (slot.tag) {
2699
+ case 0 /* Tag.NONE */:
2700
+ case 8 /* Tag.UINT */:
2701
+ case 9 /* Tag.INT */:
2702
+ case 10 /* Tag.FLOAT */:
2703
+ case 7 /* Tag.SHORT_BYTES */:
2704
+ return slot;
2705
+ case 6 /* Tag.BYTES */: {
2706
+ const mapped = offsetMap.get(Number(slot.value));
2707
+ if (mapped !== undefined)
2708
+ return new Slot(mapped, slot.tag, slot.full);
2709
+ const newOffset = remapBytes(sourceCore, targetCore, slot);
2710
+ offsetMap.set(Number(slot.value), newOffset);
2711
+ return new Slot(newOffset, slot.tag, slot.full);
2712
+ }
2713
+ case 1 /* Tag.INDEX */: {
2714
+ const mapped = offsetMap.get(Number(slot.value));
2715
+ if (mapped !== undefined)
2716
+ return new Slot(mapped, slot.tag, slot.full);
2717
+ const newOffset = remapIndex(sourceCore, targetCore, hashSize, offsetMap, slot);
2718
+ offsetMap.set(Number(slot.value), newOffset);
2719
+ return new Slot(newOffset, slot.tag, slot.full);
2720
+ }
2721
+ case 2 /* Tag.ARRAY_LIST */: {
2722
+ const mapped = offsetMap.get(Number(slot.value));
2723
+ if (mapped !== undefined)
2724
+ return new Slot(mapped, slot.tag, slot.full);
2725
+ const newOffset = remapArrayList(sourceCore, targetCore, hashSize, offsetMap, slot);
2726
+ offsetMap.set(Number(slot.value), newOffset);
2727
+ return new Slot(newOffset, slot.tag, slot.full);
2728
+ }
2729
+ case 3 /* Tag.LINKED_ARRAY_LIST */: {
2730
+ const mapped = offsetMap.get(Number(slot.value));
2731
+ if (mapped !== undefined)
2732
+ return new Slot(mapped, slot.tag, slot.full);
2733
+ const newOffset = remapBTree(sourceCore, targetCore, hashSize, offsetMap, slot);
2734
+ offsetMap.set(Number(slot.value), newOffset);
2735
+ return new Slot(newOffset, slot.tag, slot.full);
2736
+ }
2737
+ case 4 /* Tag.HASH_MAP */:
2738
+ case 11 /* Tag.HASH_SET */: {
2739
+ const mapped = offsetMap.get(Number(slot.value));
2740
+ if (mapped !== undefined)
2741
+ return new Slot(mapped, slot.tag, slot.full);
2742
+ const newOffset = remapHashMapOrSet(sourceCore, targetCore, hashSize, offsetMap, slot, false);
2743
+ offsetMap.set(Number(slot.value), newOffset);
2744
+ return new Slot(newOffset, slot.tag, slot.full);
2745
+ }
2746
+ case 12 /* Tag.COUNTED_HASH_MAP */:
2747
+ case 13 /* Tag.COUNTED_HASH_SET */: {
2748
+ const mapped = offsetMap.get(Number(slot.value));
2749
+ if (mapped !== undefined)
2750
+ return new Slot(mapped, slot.tag, slot.full);
2751
+ const newOffset = remapHashMapOrSet(sourceCore, targetCore, hashSize, offsetMap, slot, true);
2752
+ offsetMap.set(Number(slot.value), newOffset);
2753
+ return new Slot(newOffset, slot.tag, slot.full);
2754
+ }
2755
+ case 5 /* Tag.KV_PAIR */: {
2756
+ const mapped = offsetMap.get(Number(slot.value));
2757
+ if (mapped !== undefined)
2758
+ return new Slot(mapped, slot.tag, slot.full);
2759
+ const newOffset = remapKvPair(sourceCore, targetCore, hashSize, offsetMap, slot);
2760
+ offsetMap.set(Number(slot.value), newOffset);
2761
+ return new Slot(newOffset, slot.tag, slot.full);
2762
+ }
2763
+ case 14 /* Tag.SORTED_MAP */:
2764
+ case 15 /* Tag.SORTED_SET */: {
2765
+ const mapped = offsetMap.get(Number(slot.value));
2766
+ if (mapped !== undefined)
2767
+ return new Slot(mapped, slot.tag, slot.full);
2768
+ const newOffset = remapSortedMap(sourceCore, targetCore, hashSize, offsetMap, slot);
2769
+ offsetMap.set(Number(slot.value), newOffset);
2770
+ return new Slot(newOffset, slot.tag, slot.full);
2771
+ }
2772
+ default:
2773
+ throw new UnexpectedTagException();
2774
+ }
2775
+ }
2776
+ function remapBytes(sourceCore, targetCore, slot) {
2777
+ sourceCore.seek(Number(slot.value));
2778
+ const sourceReader = sourceCore.reader();
2779
+ const length = sourceReader.readLong();
2780
+ // total size: 8-byte length + bytes + optional 2-byte format_tag
2781
+ const formatTagSize = slot.full ? 2 : 0;
2782
+ const totalPayload = length + formatTagSize;
2783
+ const newOffset = targetCore.length();
2784
+ targetCore.seek(newOffset);
2785
+ const targetWriter = targetCore.writer();
2786
+ targetWriter.writeLong(length);
2787
+ // copy bytes in chunks
2788
+ let remaining = totalPayload;
2789
+ while (remaining > 0) {
2790
+ const chunk = Math.min(remaining, 4096);
2791
+ const buf = new Uint8Array(chunk);
2792
+ sourceReader.readFully(buf);
2793
+ targetWriter.write(buf);
2794
+ remaining -= chunk;
2795
+ }
2796
+ return newOffset;
2797
+ }
2798
+ function remapIndex(sourceCore, targetCore, hashSize, offsetMap, slot) {
2799
+ // read 144-byte block (16 slots)
2800
+ sourceCore.seek(Number(slot.value));
2801
+ const sourceReader = sourceCore.reader();
2802
+ const blockBytes = new Uint8Array(INDEX_BLOCK_SIZE);
2803
+ sourceReader.readFully(blockBytes);
2804
+ // remap each slot
2805
+ const remappedSlots = [];
2806
+ for (let i = 0; i < SLOT_COUNT; i++) {
2807
+ const slotBytes = blockBytes.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
2808
+ const childSlot = Slot.fromBytes(slotBytes);
2809
+ remappedSlots.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, childSlot));
2810
+ }
2811
+ // write remapped block to target
2812
+ const newOffset = targetCore.length();
2813
+ targetCore.seek(newOffset);
2814
+ const targetWriter = targetCore.writer();
2815
+ for (const s of remappedSlots) {
2816
+ targetWriter.write(s.toBytes());
2817
+ }
2818
+ return newOffset;
2819
+ }
2820
+ function remapArrayList(sourceCore, targetCore, hashSize, offsetMap, slot) {
2821
+ // read ArrayListHeader (16 bytes)
2822
+ sourceCore.seek(Number(slot.value));
2823
+ const sourceReader = sourceCore.reader();
2824
+ const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
2825
+ sourceReader.readFully(headerBytes);
2826
+ const header = ArrayListHeader.fromBytes(headerBytes);
2827
+ // remap root index block pointer via remapSlot as an .index slot
2828
+ const indexSlot = new Slot(header.ptr, 1 /* Tag.INDEX */);
2829
+ const remappedIndex = remapSlot(sourceCore, targetCore, hashSize, offsetMap, indexSlot);
2830
+ // write new ArrayListHeader with remapped ptr
2831
+ const newOffset = targetCore.length();
2832
+ targetCore.seek(newOffset);
2833
+ const targetWriter = targetCore.writer();
2834
+ targetWriter.write(new ArrayListHeader(Number(remappedIndex.value), header.size).toBytes());
2835
+ return newOffset;
2836
+ }
2837
+ function remapBTree(sourceCore, targetCore, hashSize, offsetMap, slot) {
2838
+ sourceCore.seek(Number(slot.value));
2839
+ const sourceReader = sourceCore.reader();
2840
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
2841
+ sourceReader.readFully(headerBytes);
2842
+ const header = BTreeHeader.fromBytes(headerBytes);
2843
+ const remappedRoot = remapBTreeNode(sourceCore, targetCore, hashSize, offsetMap, header.rootPtr);
2844
+ const newOffset = targetCore.length();
2845
+ targetCore.seek(newOffset);
2846
+ const targetWriter = targetCore.writer();
2847
+ targetWriter.write(new BTreeHeader(remappedRoot, header.size).toBytes());
2848
+ return newOffset;
2849
+ }
2850
+ function remapBTreeNode(sourceCore, targetCore, hashSize, offsetMap, nodeOffset) {
2851
+ // dedup check (subtrees are shared by pointer)
2852
+ const mapped = offsetMap.get(nodeOffset);
2853
+ if (mapped !== undefined)
2854
+ return mapped;
2855
+ sourceCore.seek(nodeOffset);
2856
+ const sourceReader = sourceCore.reader();
2857
+ const nodeHeader = new Uint8Array(BTREE_NODE_HEADER_SIZE);
2858
+ sourceReader.readFully(nodeHeader);
2859
+ const kindInt = nodeHeader[0];
2860
+ if (kindInt > BTreeNodeKind.BRANCH)
2861
+ throw new InvalidBTreeNodeKindException();
2862
+ const kind = kindInt;
2863
+ const num = nodeHeader[1];
2864
+ switch (kind) {
2865
+ case BTreeNodeKind.LEAF: {
2866
+ const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
2867
+ sourceReader.readFully(body);
2868
+ const slots = [];
2869
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2870
+ const valueSlot = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
2871
+ slots.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, valueSlot));
2872
+ }
2873
+ const newOffset = targetCore.length();
2874
+ targetCore.seek(newOffset);
2875
+ const targetWriter = targetCore.writer();
2876
+ targetWriter.write(new Uint8Array([kindInt, num]));
2877
+ for (const s of slots)
2878
+ targetWriter.write(s.toBytes());
2879
+ offsetMap.set(nodeOffset, newOffset);
2880
+ return newOffset;
2881
+ }
2882
+ case BTreeNodeKind.BRANCH: {
2883
+ const body = new Uint8Array((Slot.LENGTH + 8) * BTREE_SLOT_COUNT);
2884
+ sourceReader.readFully(body);
2885
+ const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
2886
+ const children = [];
2887
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2888
+ const child = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
2889
+ if (child.tag === 1 /* Tag.INDEX */) {
2890
+ const remappedPtr = remapBTreeNode(sourceCore, targetCore, hashSize, offsetMap, Number(child.value));
2891
+ children.push(new Slot(remappedPtr, 1 /* Tag.INDEX */, child.full));
2892
+ }
2893
+ else {
2894
+ children.push(child);
2895
+ }
2896
+ }
2897
+ const countsOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
2898
+ const counts = [];
2899
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2900
+ counts.push(Number(view.getBigInt64(countsOffset + i * 8, false)));
2901
+ }
2902
+ const newOffset = targetCore.length();
2903
+ targetCore.seek(newOffset);
2904
+ const targetWriter = targetCore.writer();
2905
+ targetWriter.write(new Uint8Array([kindInt, num]));
2906
+ for (const s of children)
2907
+ targetWriter.write(s.toBytes());
2908
+ for (const c of counts)
2909
+ targetWriter.writeLong(c);
2910
+ offsetMap.set(nodeOffset, newOffset);
2911
+ return newOffset;
2912
+ }
2913
+ }
2914
+ throw new UnreachableException();
2915
+ }
2916
+ function remapSortedMap(sourceCore, targetCore, hashSize, offsetMap, slot) {
2917
+ sourceCore.seek(Number(slot.value));
2918
+ const sourceReader = sourceCore.reader();
2919
+ const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
2920
+ sourceReader.readFully(headerBytes);
2921
+ const header = BTreeHeader.fromBytes(headerBytes);
2922
+ const remappedRoot = remapSortedMapNode(sourceCore, targetCore, hashSize, offsetMap, header.rootPtr);
2923
+ const newOffset = targetCore.length();
2924
+ targetCore.seek(newOffset);
2925
+ const targetWriter = targetCore.writer();
2926
+ targetWriter.write(new BTreeHeader(remappedRoot, header.size).toBytes());
2927
+ return newOffset;
2928
+ }
2929
+ function remapSortedMapNode(sourceCore, targetCore, hashSize, offsetMap, nodeOffset) {
2930
+ const mapped = offsetMap.get(nodeOffset);
2931
+ if (mapped !== undefined)
2932
+ return mapped;
2933
+ sourceCore.seek(nodeOffset);
2934
+ const sourceReader = sourceCore.reader();
2935
+ const nodeHeader = new Uint8Array(BTREE_NODE_HEADER_SIZE);
2936
+ sourceReader.readFully(nodeHeader);
2937
+ const kindInt = nodeHeader[0];
2938
+ if (kindInt > BTreeNodeKind.BRANCH)
2939
+ throw new InvalidBTreeNodeKindException();
2940
+ const kind = kindInt;
2941
+ const num = nodeHeader[1];
2942
+ switch (kind) {
2943
+ case BTreeNodeKind.LEAF: {
2944
+ const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
2945
+ sourceReader.readFully(body);
2946
+ const entries = [];
2947
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2948
+ const entry = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
2949
+ entries.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, entry));
2950
+ }
2951
+ const newOffset = targetCore.length();
2952
+ targetCore.seek(newOffset);
2953
+ const targetWriter = targetCore.writer();
2954
+ targetWriter.write(new Uint8Array([kindInt, num]));
2955
+ for (const s of entries)
2956
+ targetWriter.write(s.toBytes());
2957
+ offsetMap.set(nodeOffset, newOffset);
2958
+ return newOffset;
2959
+ }
2960
+ case BTreeNodeKind.BRANCH: {
2961
+ const body = new Uint8Array((Slot.LENGTH * 2 + 8) * BTREE_SLOT_COUNT);
2962
+ sourceReader.readFully(body);
2963
+ const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
2964
+ const children = [];
2965
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2966
+ const child = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
2967
+ if (child.tag === 1 /* Tag.INDEX */) {
2968
+ const remappedPtr = remapSortedMapNode(sourceCore, targetCore, hashSize, offsetMap, Number(child.value));
2969
+ children.push(new Slot(remappedPtr, 1 /* Tag.INDEX */, child.full));
2970
+ }
2971
+ else {
2972
+ children.push(child);
2973
+ }
2974
+ }
2975
+ const sepOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
2976
+ const separators = [];
2977
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2978
+ const sep = Slot.fromBytes(body.slice(sepOffset + i * Slot.LENGTH, sepOffset + i * Slot.LENGTH + Slot.LENGTH));
2979
+ separators.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, sep));
2980
+ }
2981
+ const countsOffset = Slot.LENGTH * 2 * BTREE_SLOT_COUNT;
2982
+ const counts = [];
2983
+ for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
2984
+ counts.push(Number(view.getBigInt64(countsOffset + i * 8, false)));
2985
+ }
2986
+ const newOffset = targetCore.length();
2987
+ targetCore.seek(newOffset);
2988
+ const targetWriter = targetCore.writer();
2989
+ targetWriter.write(new Uint8Array([kindInt, num]));
2990
+ for (const s of children)
2991
+ targetWriter.write(s.toBytes());
2992
+ for (const s of separators)
2993
+ targetWriter.write(s.toBytes());
2994
+ for (const c of counts)
2995
+ targetWriter.writeLong(c);
2996
+ offsetMap.set(nodeOffset, newOffset);
2997
+ return newOffset;
2998
+ }
2999
+ }
3000
+ throw new UnreachableException();
3001
+ }
3002
+ function remapHashMapOrSet(sourceCore, targetCore, hashSize, offsetMap, slot, counted) {
3003
+ sourceCore.seek(Number(slot.value));
3004
+ const sourceReader = sourceCore.reader();
3005
+ let countValue = -1;
3006
+ if (counted) {
3007
+ countValue = sourceReader.readLong();
3008
+ }
3009
+ // read 144-byte root index block
3010
+ const blockBytes = new Uint8Array(INDEX_BLOCK_SIZE);
3011
+ sourceReader.readFully(blockBytes);
3012
+ // remap each child slot in the block
3013
+ const remappedSlots = [];
3014
+ for (let i = 0; i < SLOT_COUNT; i++) {
3015
+ const slotBytes = blockBytes.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
3016
+ const childSlot = Slot.fromBytes(slotBytes);
3017
+ remappedSlots.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, childSlot));
3018
+ }
3019
+ // write [optional count][remapped block] contiguously to target
3020
+ const newOffset = targetCore.length();
3021
+ targetCore.seek(newOffset);
3022
+ const targetWriter = targetCore.writer();
3023
+ if (counted) {
3024
+ targetWriter.writeLong(countValue);
3025
+ }
3026
+ for (const s of remappedSlots) {
3027
+ targetWriter.write(s.toBytes());
3028
+ }
3029
+ return newOffset;
3030
+ }
3031
+ function remapKvPair(sourceCore, targetCore, hashSize, offsetMap, slot) {
3032
+ // read KeyValuePair
3033
+ sourceCore.seek(Number(slot.value));
3034
+ const sourceReader = sourceCore.reader();
3035
+ const kvPairBytes = new Uint8Array(KeyValuePair.length(hashSize));
3036
+ sourceReader.readFully(kvPairBytes);
3037
+ const kvPair = KeyValuePair.fromBytes(kvPairBytes, hashSize);
3038
+ // remap key_slot and value_slot
3039
+ const remappedKey = remapSlot(sourceCore, targetCore, hashSize, offsetMap, kvPair.keySlot);
3040
+ const remappedValue = remapSlot(sourceCore, targetCore, hashSize, offsetMap, kvPair.valueSlot);
3041
+ // write remapped KV pair (hash stays unchanged)
3042
+ const newOffset = targetCore.length();
3043
+ targetCore.seek(newOffset);
3044
+ const targetWriter = targetCore.writer();
3045
+ targetWriter.write(new KeyValuePair(remappedValue, remappedKey, kvPair.hash).toBytes());
3046
+ return newOffset;
3047
+ }