@frostpillar/frostpillar-btree 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-JA.md +39 -6
- package/README.md +39 -6
- package/dist/InMemoryBTree.d.ts +33 -0
- package/dist/btree/autoScale.d.ts +1 -0
- package/dist/btree/node-ops.d.ts +15 -0
- package/dist/btree/rangeQuery.d.ts +1 -0
- package/dist/btree/serialization.d.ts +0 -4
- package/dist/btree/types.d.ts +12 -26
- package/dist/{chunk-ZA3EQNDI.js → chunk-OWHENPGJ.js} +259 -212
- package/dist/concurrency/ConcurrentInMemoryBTree.d.ts +20 -16
- package/dist/concurrency/helpers.d.ts +9 -2
- package/dist/concurrency/types.d.ts +14 -1
- package/dist/concurrency/writeOps.d.ts +48 -0
- package/dist/core.cjs +259 -212
- package/dist/core.js +1 -1
- package/dist/errors.d.ts +3 -0
- package/dist/frostpillar-btree-core.min.js +1 -1
- package/dist/frostpillar-btree.min.js +1 -1
- package/dist/index.cjs +543 -278
- package/dist/index.js +285 -67
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_MAX_LEAF_ENTRIES,
|
|
7
7
|
InMemoryBTree,
|
|
8
8
|
computeAutoScaleTier
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-OWHENPGJ.js";
|
|
10
10
|
|
|
11
11
|
// src/concurrency/helpers.ts
|
|
12
12
|
var DEFAULT_MAX_RETRIES = 16;
|
|
@@ -30,6 +30,81 @@ var assertNeverMutation = (mutation) => {
|
|
|
30
30
|
`Unsupported mutation type from shared store: ${String(unknownMutation.type)}`
|
|
31
31
|
);
|
|
32
32
|
};
|
|
33
|
+
var validatePutManyEntries = (entries) => {
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (typeof entry !== "object" || entry === null || !("key" in entry) || !("value" in entry)) {
|
|
36
|
+
throw new BTreeConcurrencyError("Malformed putMany mutation: each entry must have key and value.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var validateInitMutation = (m, expectedConfigFingerprint) => {
|
|
41
|
+
if (typeof m.configFingerprint !== "string") {
|
|
42
|
+
throw new BTreeConcurrencyError("Malformed init mutation: missing configFingerprint.");
|
|
43
|
+
}
|
|
44
|
+
if (expectedConfigFingerprint !== void 0 && m.configFingerprint !== expectedConfigFingerprint) {
|
|
45
|
+
throw new BTreeConcurrencyError(
|
|
46
|
+
"Config mismatch: store peers must share identical tree config."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var validateSingleMutation = (m, expectedConfigFingerprint) => {
|
|
51
|
+
switch (m.type) {
|
|
52
|
+
case "init":
|
|
53
|
+
validateInitMutation(m, expectedConfigFingerprint);
|
|
54
|
+
break;
|
|
55
|
+
case "put":
|
|
56
|
+
if (!("key" in m) || !("value" in m)) {
|
|
57
|
+
throw new BTreeConcurrencyError("Malformed put mutation: missing key or value.");
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case "remove":
|
|
61
|
+
if (!("key" in m)) {
|
|
62
|
+
throw new BTreeConcurrencyError("Malformed remove mutation: missing key.");
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case "removeById":
|
|
66
|
+
if (!("entryId" in m)) {
|
|
67
|
+
throw new BTreeConcurrencyError("Malformed removeById mutation: missing entryId.");
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case "updateById":
|
|
71
|
+
if (!("entryId" in m) || !("value" in m)) {
|
|
72
|
+
throw new BTreeConcurrencyError("Malformed updateById mutation: missing entryId or value.");
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case "popFirst":
|
|
76
|
+
case "popLast":
|
|
77
|
+
break;
|
|
78
|
+
case "putMany":
|
|
79
|
+
if (!("entries" in m) || !Array.isArray(m.entries)) {
|
|
80
|
+
throw new BTreeConcurrencyError("Malformed putMany mutation: missing entries array.");
|
|
81
|
+
}
|
|
82
|
+
validatePutManyEntries(m.entries);
|
|
83
|
+
break;
|
|
84
|
+
case "deleteRange":
|
|
85
|
+
if (!("startKey" in m) || !("endKey" in m)) {
|
|
86
|
+
throw new BTreeConcurrencyError("Malformed deleteRange mutation: missing startKey or endKey.");
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case "clear":
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
throw new BTreeConcurrencyError(
|
|
93
|
+
`Unsupported mutation type from shared store: ${String(m.type)}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var validateMutationBatch = (mutations, expectedConfigFingerprint) => {
|
|
98
|
+
for (const mutation of mutations) {
|
|
99
|
+
if (typeof mutation !== "object" || mutation === null) {
|
|
100
|
+
throw new BTreeConcurrencyError("Malformed mutation: expected an object.");
|
|
101
|
+
}
|
|
102
|
+
validateSingleMutation(
|
|
103
|
+
mutation,
|
|
104
|
+
expectedConfigFingerprint
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
33
108
|
var normalizeMaxRetries = (value) => {
|
|
34
109
|
if (value === void 0) {
|
|
35
110
|
return DEFAULT_MAX_RETRIES;
|
|
@@ -88,10 +163,110 @@ function assertAppendVersionContract(expectedVersion, appendResult) {
|
|
|
88
163
|
}
|
|
89
164
|
}
|
|
90
165
|
|
|
166
|
+
// src/concurrency/writeOps.ts
|
|
167
|
+
var applyMutationLocal = (tree, mutation, onInit) => {
|
|
168
|
+
switch (mutation.type) {
|
|
169
|
+
case "init":
|
|
170
|
+
onInit();
|
|
171
|
+
return null;
|
|
172
|
+
case "put":
|
|
173
|
+
return tree.put(mutation.key, mutation.value);
|
|
174
|
+
case "putMany":
|
|
175
|
+
return tree.putMany(mutation.entries);
|
|
176
|
+
case "remove":
|
|
177
|
+
return tree.remove(mutation.key);
|
|
178
|
+
case "removeById":
|
|
179
|
+
return tree.removeById(mutation.entryId);
|
|
180
|
+
case "updateById":
|
|
181
|
+
return tree.updateById(mutation.entryId, mutation.value);
|
|
182
|
+
case "popFirst":
|
|
183
|
+
return tree.popFirst();
|
|
184
|
+
case "popLast":
|
|
185
|
+
return tree.popLast();
|
|
186
|
+
case "deleteRange":
|
|
187
|
+
return tree.deleteRange(mutation.startKey, mutation.endKey, mutation.options);
|
|
188
|
+
case "clear":
|
|
189
|
+
tree.clear();
|
|
190
|
+
return null;
|
|
191
|
+
default:
|
|
192
|
+
return assertNeverMutation(mutation);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var createPutEvaluator = (duplicateKeys, key, value) => {
|
|
196
|
+
return (tree) => {
|
|
197
|
+
if (duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
198
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
199
|
+
}
|
|
200
|
+
return { type: "put", key, value };
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
var createRemoveEvaluator = (key) => {
|
|
204
|
+
return (tree) => {
|
|
205
|
+
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
var createRemoveByIdEvaluator = (entryId) => {
|
|
209
|
+
return (tree) => {
|
|
210
|
+
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
var createUpdateByIdEvaluator = (entryId, value) => {
|
|
214
|
+
return (tree) => {
|
|
215
|
+
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
var createPopFirstEvaluator = () => {
|
|
219
|
+
return (tree) => {
|
|
220
|
+
return tree.peekFirst() !== null ? { type: "popFirst" } : null;
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
var createPopLastEvaluator = () => {
|
|
224
|
+
return (tree) => {
|
|
225
|
+
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
var createPutManyEvaluator = (entries, duplicateKeys, compareKeys) => {
|
|
229
|
+
return (tree) => {
|
|
230
|
+
const strictlyAscending = duplicateKeys !== "allow";
|
|
231
|
+
for (let i = 1; i < entries.length; i += 1) {
|
|
232
|
+
const cmp = compareKeys(entries[i - 1].key, entries[i].key);
|
|
233
|
+
if (cmp > 0) {
|
|
234
|
+
throw new BTreeValidationError("putMany: entries not in ascending order.");
|
|
235
|
+
}
|
|
236
|
+
if (strictlyAscending && cmp === 0) {
|
|
237
|
+
throw new BTreeValidationError(
|
|
238
|
+
duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (duplicateKeys === "reject") {
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
if (tree.hasKey(entry.key)) {
|
|
245
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { type: "putMany", entries };
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
var createDeleteRangeEvaluator = (startKey, endKey, options) => {
|
|
253
|
+
return (tree) => {
|
|
254
|
+
const count = tree.count(startKey, endKey, options);
|
|
255
|
+
if (count === 0) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
return { type: "deleteRange", startKey, endKey, options };
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
var createClearEvaluator = () => {
|
|
262
|
+
return () => ({ type: "clear" });
|
|
263
|
+
};
|
|
264
|
+
|
|
91
265
|
// src/concurrency/ConcurrentInMemoryBTree.ts
|
|
92
266
|
var ConcurrentInMemoryBTree = class {
|
|
93
267
|
constructor(config) {
|
|
94
268
|
this.store = config.store;
|
|
269
|
+
this.compareKeys = config.compareKeys;
|
|
95
270
|
this.maxRetries = normalizeMaxRetries(config.maxRetries);
|
|
96
271
|
this.maxSyncMutationsPerBatch = normalizeMaxSyncMutationsPerBatch(
|
|
97
272
|
config.maxSyncMutationsPerBatch
|
|
@@ -110,6 +285,7 @@ var ConcurrentInMemoryBTree = class {
|
|
|
110
285
|
this.currentVersion = 0n;
|
|
111
286
|
this.operationQueue = Promise.resolve();
|
|
112
287
|
this.initSeen = false;
|
|
288
|
+
this.corrupted = false;
|
|
113
289
|
}
|
|
114
290
|
async sync() {
|
|
115
291
|
await this.runExclusive(async () => {
|
|
@@ -132,39 +308,31 @@ var ConcurrentInMemoryBTree = class {
|
|
|
132
308
|
if (log.version <= this.currentVersion) {
|
|
133
309
|
return;
|
|
134
310
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return null;
|
|
150
|
-
case "put":
|
|
151
|
-
return this.tree.put(mutation.key, mutation.value);
|
|
152
|
-
case "remove":
|
|
153
|
-
return this.tree.remove(mutation.key);
|
|
154
|
-
case "removeById":
|
|
155
|
-
return this.tree.removeById(mutation.entryId);
|
|
156
|
-
case "updateById":
|
|
157
|
-
return this.tree.updateById(mutation.entryId, mutation.value);
|
|
158
|
-
case "popFirst":
|
|
159
|
-
return this.tree.popFirst();
|
|
160
|
-
case "popLast":
|
|
161
|
-
return this.tree.popLast();
|
|
162
|
-
default:
|
|
163
|
-
return assertNeverMutation(mutation);
|
|
311
|
+
validateMutationBatch(log.mutations, this.configFingerprint);
|
|
312
|
+
try {
|
|
313
|
+
for (const mutation of log.mutations) {
|
|
314
|
+
applyMutationLocal(this.tree, mutation, () => {
|
|
315
|
+
this.initSeen = true;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
this.currentVersion = log.version;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
this.corrupted = true;
|
|
321
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
322
|
+
throw new BTreeConcurrencyError(
|
|
323
|
+
`Replay failure: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
|
|
324
|
+
);
|
|
164
325
|
}
|
|
165
326
|
}
|
|
166
327
|
runExclusive(operation) {
|
|
167
|
-
const run = async () =>
|
|
328
|
+
const run = async () => {
|
|
329
|
+
if (this.corrupted) {
|
|
330
|
+
throw new BTreeConcurrencyError(
|
|
331
|
+
"Instance is permanently corrupted. Discard and create a new instance."
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return operation();
|
|
335
|
+
};
|
|
168
336
|
const result = this.operationQueue.then(run, run);
|
|
169
337
|
this.operationQueue = result.then(
|
|
170
338
|
() => void 0,
|
|
@@ -192,13 +360,29 @@ var ConcurrentInMemoryBTree = class {
|
|
|
192
360
|
const appendResult = await this.store.append(expectedVersion, mutations);
|
|
193
361
|
assertAppendVersionContract(expectedVersion, appendResult);
|
|
194
362
|
if (appendResult.applied) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
363
|
+
try {
|
|
364
|
+
for (const m of mutations) {
|
|
365
|
+
if (m === mutation) break;
|
|
366
|
+
applyMutationLocal(this.tree, m, () => {
|
|
367
|
+
this.initSeen = true;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const localResult = applyMutationLocal(
|
|
371
|
+
this.tree,
|
|
372
|
+
mutation,
|
|
373
|
+
() => {
|
|
374
|
+
this.initSeen = true;
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
this.currentVersion = appendResult.version;
|
|
378
|
+
return localResult;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
this.corrupted = true;
|
|
381
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
382
|
+
throw new BTreeConcurrencyError(
|
|
383
|
+
`Local apply failure after successful append: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
|
|
384
|
+
);
|
|
198
385
|
}
|
|
199
|
-
const localResult = this.applyMutationLocal(mutation);
|
|
200
|
-
this.currentVersion = appendResult.version;
|
|
201
|
-
return localResult;
|
|
202
386
|
}
|
|
203
387
|
}
|
|
204
388
|
throw new BTreeConcurrencyError(
|
|
@@ -208,47 +392,56 @@ var ConcurrentInMemoryBTree = class {
|
|
|
208
392
|
async put(key, value) {
|
|
209
393
|
return this.runExclusive(async () => {
|
|
210
394
|
return this.appendMutationAndApplyUnlocked(
|
|
211
|
-
(
|
|
212
|
-
if (this.duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
213
|
-
throw new BTreeValidationError("Duplicate key rejected.");
|
|
214
|
-
}
|
|
215
|
-
return { type: "put", key, value };
|
|
216
|
-
}
|
|
395
|
+
createPutEvaluator(this.duplicateKeys, key, value)
|
|
217
396
|
);
|
|
218
397
|
});
|
|
219
398
|
}
|
|
220
399
|
async remove(key) {
|
|
221
400
|
return this.runExclusive(async () => {
|
|
222
|
-
return this.appendMutationAndApplyUnlocked(
|
|
223
|
-
(tree) => {
|
|
224
|
-
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
225
|
-
}
|
|
226
|
-
);
|
|
401
|
+
return this.appendMutationAndApplyUnlocked(createRemoveEvaluator(key));
|
|
227
402
|
});
|
|
228
403
|
}
|
|
229
404
|
async removeById(entryId) {
|
|
230
405
|
return this.runExclusive(async () => {
|
|
231
|
-
return this.appendMutationAndApplyUnlocked(
|
|
232
|
-
(tree) => {
|
|
233
|
-
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
234
|
-
}
|
|
235
|
-
);
|
|
406
|
+
return this.appendMutationAndApplyUnlocked(createRemoveByIdEvaluator(entryId));
|
|
236
407
|
});
|
|
237
408
|
}
|
|
238
409
|
async updateById(entryId, value) {
|
|
410
|
+
return this.runExclusive(async () => {
|
|
411
|
+
return this.appendMutationAndApplyUnlocked(createUpdateByIdEvaluator(entryId, value));
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
async popFirst() {
|
|
415
|
+
return this.runExclusive(async () => {
|
|
416
|
+
return this.appendMutationAndApplyUnlocked(createPopFirstEvaluator());
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
async popLast() {
|
|
420
|
+
return this.runExclusive(async () => {
|
|
421
|
+
return this.appendMutationAndApplyUnlocked(createPopLastEvaluator());
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async putMany(entries) {
|
|
425
|
+
if (entries.length === 0) {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
239
428
|
return this.runExclusive(async () => {
|
|
240
429
|
return this.appendMutationAndApplyUnlocked(
|
|
241
|
-
(
|
|
242
|
-
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
243
|
-
}
|
|
430
|
+
createPutManyEvaluator(entries, this.duplicateKeys, this.compareKeys)
|
|
244
431
|
);
|
|
245
432
|
});
|
|
246
433
|
}
|
|
247
|
-
async
|
|
434
|
+
async deleteRange(startKey, endKey, options) {
|
|
248
435
|
return this.runExclusive(async () => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
436
|
+
const result = await this.appendMutationAndApplyUnlocked(
|
|
437
|
+
createDeleteRangeEvaluator(startKey, endKey, options)
|
|
438
|
+
);
|
|
439
|
+
return result ?? 0;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
async clear() {
|
|
443
|
+
await this.runExclusive(async () => {
|
|
444
|
+
await this.appendMutationAndApplyUnlocked(createClearEvaluator());
|
|
252
445
|
});
|
|
253
446
|
}
|
|
254
447
|
async get(key) {
|
|
@@ -284,13 +477,6 @@ var ConcurrentInMemoryBTree = class {
|
|
|
284
477
|
async peekLast() {
|
|
285
478
|
return this.readOp((tree) => tree.peekLast());
|
|
286
479
|
}
|
|
287
|
-
async popLast() {
|
|
288
|
-
return this.runExclusive(async () => {
|
|
289
|
-
return this.appendMutationAndApplyUnlocked((tree) => {
|
|
290
|
-
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
480
|
async peekById(entryId) {
|
|
295
481
|
return this.readOp((tree) => tree.peekById(entryId));
|
|
296
482
|
}
|
|
@@ -306,6 +492,38 @@ var ConcurrentInMemoryBTree = class {
|
|
|
306
492
|
async getPairOrNextLower(key) {
|
|
307
493
|
return this.readOp((tree) => tree.getPairOrNextLower(key));
|
|
308
494
|
}
|
|
495
|
+
async entries() {
|
|
496
|
+
return this.readOp((tree) => Array.from(tree.entries()));
|
|
497
|
+
}
|
|
498
|
+
async entriesReversed() {
|
|
499
|
+
return this.readOp((tree) => Array.from(tree.entriesReversed()));
|
|
500
|
+
}
|
|
501
|
+
async keys() {
|
|
502
|
+
return this.readOp((tree) => Array.from(tree.keys()));
|
|
503
|
+
}
|
|
504
|
+
async values() {
|
|
505
|
+
return this.readOp((tree) => Array.from(tree.values()));
|
|
506
|
+
}
|
|
507
|
+
async forEach(callback) {
|
|
508
|
+
await this.readOp((tree) => {
|
|
509
|
+
tree.forEach(callback);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async *[Symbol.asyncIterator]() {
|
|
513
|
+
const all = await this.entries();
|
|
514
|
+
for (const entry of all) {
|
|
515
|
+
yield entry;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
async clone() {
|
|
519
|
+
return this.readOp((tree) => tree.clone());
|
|
520
|
+
}
|
|
521
|
+
async toJSON() {
|
|
522
|
+
return this.readOp((tree) => tree.toJSON());
|
|
523
|
+
}
|
|
524
|
+
static fromJSON(json, compareKeys) {
|
|
525
|
+
return InMemoryBTree.fromJSON(json, compareKeys);
|
|
526
|
+
}
|
|
309
527
|
};
|
|
310
528
|
export {
|
|
311
529
|
BTreeConcurrencyError,
|
package/package.json
CHANGED