@aztec-labs/kv-store 6.0.0-nightly.20260829
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.md +19 -0
- package/dest/bench/shared_map_bench.d.ts +19 -0
- package/dest/bench/shared_map_bench.d.ts.map +1 -0
- package/dest/bench/shared_map_bench.js +91 -0
- package/dest/deprecated/indexeddb/array.d.ts +22 -0
- package/dest/deprecated/indexeddb/array.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/array.js +121 -0
- package/dest/deprecated/indexeddb/index.d.ts +7 -0
- package/dest/deprecated/indexeddb/index.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/index.js +8 -0
- package/dest/deprecated/indexeddb/map.d.ts +38 -0
- package/dest/deprecated/indexeddb/map.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/map.js +121 -0
- package/dest/deprecated/indexeddb/multi_map.d.ts +14 -0
- package/dest/deprecated/indexeddb/multi_map.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/multi_map.js +108 -0
- package/dest/deprecated/indexeddb/set.d.ts +17 -0
- package/dest/deprecated/indexeddb/set.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/set.js +24 -0
- package/dest/deprecated/indexeddb/singleton.d.ts +17 -0
- package/dest/deprecated/indexeddb/singleton.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/singleton.js +41 -0
- package/dest/deprecated/indexeddb/store.d.ts +98 -0
- package/dest/deprecated/indexeddb/store.d.ts.map +1 -0
- package/dest/deprecated/indexeddb/store.js +171 -0
- package/dest/interfaces/array.d.ts +81 -0
- package/dest/interfaces/array.d.ts.map +1 -0
- package/dest/interfaces/array.js +1 -0
- package/dest/interfaces/array_test_suite.d.ts +3 -0
- package/dest/interfaces/array_test_suite.d.ts.map +1 -0
- package/dest/interfaces/array_test_suite.js +103 -0
- package/dest/interfaces/common.d.ts +25 -0
- package/dest/interfaces/common.d.ts.map +1 -0
- package/dest/interfaces/common.js +8 -0
- package/dest/interfaces/counter.d.ts +59 -0
- package/dest/interfaces/counter.d.ts.map +1 -0
- package/dest/interfaces/counter.js +1 -0
- package/dest/interfaces/index.d.ts +10 -0
- package/dest/interfaces/index.d.ts.map +1 -0
- package/dest/interfaces/index.js +8 -0
- package/dest/interfaces/map.d.ts +106 -0
- package/dest/interfaces/map.d.ts.map +1 -0
- package/dest/interfaces/map.js +3 -0
- package/dest/interfaces/map_test_suite.d.ts +3 -0
- package/dest/interfaces/map_test_suite.d.ts.map +1 -0
- package/dest/interfaces/map_test_suite.js +261 -0
- package/dest/interfaces/multi_map.d.ts +41 -0
- package/dest/interfaces/multi_map.d.ts.map +1 -0
- package/dest/interfaces/multi_map.js +3 -0
- package/dest/interfaces/multi_map_test_suite.d.ts +3 -0
- package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -0
- package/dest/interfaces/multi_map_test_suite.js +363 -0
- package/dest/interfaces/set.d.ts +44 -0
- package/dest/interfaces/set.d.ts.map +1 -0
- package/dest/interfaces/set.js +1 -0
- package/dest/interfaces/set_test_suite.d.ts +3 -0
- package/dest/interfaces/set_test_suite.d.ts.map +1 -0
- package/dest/interfaces/set_test_suite.js +95 -0
- package/dest/interfaces/singleton.d.ts +29 -0
- package/dest/interfaces/singleton.d.ts.map +1 -0
- package/dest/interfaces/singleton.js +4 -0
- package/dest/interfaces/singleton_test_suite.d.ts +3 -0
- package/dest/interfaces/singleton_test_suite.d.ts.map +1 -0
- package/dest/interfaces/singleton_test_suite.js +29 -0
- package/dest/interfaces/store.d.ts +120 -0
- package/dest/interfaces/store.d.ts.map +1 -0
- package/dest/interfaces/store.js +1 -0
- package/dest/interfaces/utils.d.ts +18 -0
- package/dest/interfaces/utils.d.ts.map +1 -0
- package/dest/interfaces/utils.js +18 -0
- package/dest/lmdb/array.d.ts +24 -0
- package/dest/lmdb/array.d.ts.map +1 -0
- package/dest/lmdb/array.js +119 -0
- package/dest/lmdb/counter.d.ts +19 -0
- package/dest/lmdb/counter.d.ts.map +1 -0
- package/dest/lmdb/counter.js +50 -0
- package/dest/lmdb/index.d.ts +12 -0
- package/dest/lmdb/index.d.ts.map +1 -0
- package/dest/lmdb/index.js +25 -0
- package/dest/lmdb/map.d.ts +40 -0
- package/dest/lmdb/map.d.ts.map +1 -0
- package/dest/lmdb/map.js +156 -0
- package/dest/lmdb/multi_map.d.ts +13 -0
- package/dest/lmdb/multi_map.d.ts.map +1 -0
- package/dest/lmdb/multi_map.js +44 -0
- package/dest/lmdb/set.d.ts +18 -0
- package/dest/lmdb/set.d.ts.map +1 -0
- package/dest/lmdb/set.js +32 -0
- package/dest/lmdb/singleton.d.ts +14 -0
- package/dest/lmdb/singleton.d.ts.map +1 -0
- package/dest/lmdb/singleton.js +26 -0
- package/dest/lmdb/store.d.ts +94 -0
- package/dest/lmdb/store.d.ts.map +1 -0
- package/dest/lmdb/store.js +200 -0
- package/dest/lmdb-v2/array.d.ts +19 -0
- package/dest/lmdb-v2/array.d.ts.map +1 -0
- package/dest/lmdb-v2/array.js +103 -0
- package/dest/lmdb-v2/factory.d.ts +35 -0
- package/dest/lmdb-v2/factory.d.ts.map +1 -0
- package/dest/lmdb-v2/factory.js +126 -0
- package/dest/lmdb-v2/index.d.ts +3 -0
- package/dest/lmdb-v2/index.d.ts.map +1 -0
- package/dest/lmdb-v2/index.js +2 -0
- package/dest/lmdb-v2/map.d.ts +53 -0
- package/dest/lmdb-v2/map.d.ts.map +1 -0
- package/dest/lmdb-v2/map.js +106 -0
- package/dest/lmdb-v2/message.d.ts +131 -0
- package/dest/lmdb-v2/message.d.ts.map +1 -0
- package/dest/lmdb-v2/message.js +20 -0
- package/dest/lmdb-v2/multi_map.d.ts +52 -0
- package/dest/lmdb-v2/multi_map.d.ts.map +1 -0
- package/dest/lmdb-v2/multi_map.js +123 -0
- package/dest/lmdb-v2/read_transaction.d.ts +16 -0
- package/dest/lmdb-v2/read_transaction.d.ts.map +1 -0
- package/dest/lmdb-v2/read_transaction.js +137 -0
- package/dest/lmdb-v2/set.d.ts +15 -0
- package/dest/lmdb-v2/set.d.ts.map +1 -0
- package/dest/lmdb-v2/set.js +23 -0
- package/dest/lmdb-v2/singleton.d.ts +12 -0
- package/dest/lmdb-v2/singleton.d.ts.map +1 -0
- package/dest/lmdb-v2/singleton.js +31 -0
- package/dest/lmdb-v2/store.d.ts +43 -0
- package/dest/lmdb-v2/store.d.ts.map +1 -0
- package/dest/lmdb-v2/store.js +171 -0
- package/dest/lmdb-v2/tx-helpers.d.ts +6 -0
- package/dest/lmdb-v2/tx-helpers.d.ts.map +1 -0
- package/dest/lmdb-v2/tx-helpers.js +21 -0
- package/dest/lmdb-v2/utils.d.ts +8 -0
- package/dest/lmdb-v2/utils.d.ts.map +1 -0
- package/dest/lmdb-v2/utils.js +32 -0
- package/dest/lmdb-v2/write_transaction.d.ts +17 -0
- package/dest/lmdb-v2/write_transaction.d.ts.map +1 -0
- package/dest/lmdb-v2/write_transaction.js +258 -0
- package/dest/sqlite-opfs/array.d.ts +21 -0
- package/dest/sqlite-opfs/array.d.ts.map +1 -0
- package/dest/sqlite-opfs/array.js +128 -0
- package/dest/sqlite-opfs/errors.d.ts +54 -0
- package/dest/sqlite-opfs/errors.d.ts.map +1 -0
- package/dest/sqlite-opfs/errors.js +77 -0
- package/dest/sqlite-opfs/index.d.ts +14 -0
- package/dest/sqlite-opfs/index.d.ts.map +1 -0
- package/dest/sqlite-opfs/index.js +16 -0
- package/dest/sqlite-opfs/internal/ordered-binary-browser.d.ts +32 -0
- package/dest/sqlite-opfs/internal/ordered-binary-browser.d.ts.map +1 -0
- package/dest/sqlite-opfs/internal/ordered-binary-browser.js +448 -0
- package/dest/sqlite-opfs/manage.d.ts +22 -0
- package/dest/sqlite-opfs/manage.d.ts.map +1 -0
- package/dest/sqlite-opfs/manage.js +38 -0
- package/dest/sqlite-opfs/map.d.ts +35 -0
- package/dest/sqlite-opfs/map.d.ts.map +1 -0
- package/dest/sqlite-opfs/map.js +163 -0
- package/dest/sqlite-opfs/messages.d.ts +66 -0
- package/dest/sqlite-opfs/messages.d.ts.map +1 -0
- package/dest/sqlite-opfs/messages.js +5 -0
- package/dest/sqlite-opfs/multi_map.d.ts +16 -0
- package/dest/sqlite-opfs/multi_map.d.ts.map +1 -0
- package/dest/sqlite-opfs/multi_map.js +67 -0
- package/dest/sqlite-opfs/pool_integrity.d.ts +20 -0
- package/dest/sqlite-opfs/pool_integrity.d.ts.map +1 -0
- package/dest/sqlite-opfs/pool_integrity.js +282 -0
- package/dest/sqlite-opfs/pool_lock.d.ts +8 -0
- package/dest/sqlite-opfs/pool_lock.d.ts.map +1 -0
- package/dest/sqlite-opfs/pool_lock.js +47 -0
- package/dest/sqlite-opfs/set.d.ts +13 -0
- package/dest/sqlite-opfs/set.d.ts.map +1 -0
- package/dest/sqlite-opfs/set.js +19 -0
- package/dest/sqlite-opfs/singleton.d.ts +13 -0
- package/dest/sqlite-opfs/singleton.d.ts.map +1 -0
- package/dest/sqlite-opfs/singleton.js +48 -0
- package/dest/sqlite-opfs/store.d.ts +84 -0
- package/dest/sqlite-opfs/store.d.ts.map +1 -0
- package/dest/sqlite-opfs/store.js +318 -0
- package/dest/sqlite-opfs/worker.d.ts +2 -0
- package/dest/sqlite-opfs/worker.d.ts.map +1 -0
- package/dest/sqlite-opfs/worker.js +275 -0
- package/dest/stores/index.d.ts +3 -0
- package/dest/stores/index.d.ts.map +1 -0
- package/dest/stores/index.js +2 -0
- package/dest/stores/l2_tips_store.d.ts +23 -0
- package/dest/stores/l2_tips_store.d.ts.map +1 -0
- package/dest/stores/l2_tips_store.js +64 -0
- package/dest/utils.d.ts +15 -0
- package/dest/utils.d.ts.map +1 -0
- package/dest/utils.js +59 -0
- package/package.json +108 -0
- package/src/bench/shared_map_bench.ts +111 -0
- package/src/deprecated/indexeddb/array.ts +122 -0
- package/src/deprecated/indexeddb/index.ts +12 -0
- package/src/deprecated/indexeddb/map.ts +137 -0
- package/src/deprecated/indexeddb/multi_map.ts +106 -0
- package/src/deprecated/indexeddb/set.ts +37 -0
- package/src/deprecated/indexeddb/singleton.ts +52 -0
- package/src/deprecated/indexeddb/store.ts +214 -0
- package/src/interfaces/array.ts +92 -0
- package/src/interfaces/array_test_suite.ts +128 -0
- package/src/interfaces/common.ts +31 -0
- package/src/interfaces/counter.ts +65 -0
- package/src/interfaces/index.ts +9 -0
- package/src/interfaces/map.ts +117 -0
- package/src/interfaces/map_test_suite.ts +198 -0
- package/src/interfaces/multi_map.ts +45 -0
- package/src/interfaces/multi_map_test_suite.ts +372 -0
- package/src/interfaces/set.ts +48 -0
- package/src/interfaces/set_test_suite.ts +79 -0
- package/src/interfaces/singleton.ts +29 -0
- package/src/interfaces/singleton_test_suite.ts +44 -0
- package/src/interfaces/store.ts +142 -0
- package/src/interfaces/utils.ts +23 -0
- package/src/lmdb/array.ts +140 -0
- package/src/lmdb/counter.ts +67 -0
- package/src/lmdb/index.ts +41 -0
- package/src/lmdb/map.ts +177 -0
- package/src/lmdb/multi_map.ts +51 -0
- package/src/lmdb/set.ts +45 -0
- package/src/lmdb/singleton.ts +35 -0
- package/src/lmdb/store.ts +242 -0
- package/src/lmdb-v2/array.ts +120 -0
- package/src/lmdb-v2/factory.ts +184 -0
- package/src/lmdb-v2/index.ts +2 -0
- package/src/lmdb-v2/map.ts +136 -0
- package/src/lmdb-v2/message.ts +169 -0
- package/src/lmdb-v2/multi_map.ts +150 -0
- package/src/lmdb-v2/read_transaction.ts +156 -0
- package/src/lmdb-v2/set.ts +33 -0
- package/src/lmdb-v2/singleton.ts +38 -0
- package/src/lmdb-v2/store.ts +220 -0
- package/src/lmdb-v2/tx-helpers.ts +29 -0
- package/src/lmdb-v2/utils.ts +32 -0
- package/src/lmdb-v2/write_transaction.ts +315 -0
- package/src/sqlite-opfs/array.ts +124 -0
- package/src/sqlite-opfs/errors.ts +94 -0
- package/src/sqlite-opfs/index.ts +27 -0
- package/src/sqlite-opfs/internal/ordered-binary-browser.js +465 -0
- package/src/sqlite-opfs/manage.ts +46 -0
- package/src/sqlite-opfs/map.ts +163 -0
- package/src/sqlite-opfs/messages.ts +39 -0
- package/src/sqlite-opfs/multi_map.ts +74 -0
- package/src/sqlite-opfs/pool_integrity.ts +327 -0
- package/src/sqlite-opfs/pool_lock.ts +62 -0
- package/src/sqlite-opfs/set.ts +29 -0
- package/src/sqlite-opfs/singleton.ts +48 -0
- package/src/sqlite-opfs/store.ts +331 -0
- package/src/sqlite-opfs/worker.ts +259 -0
- package/src/stores/index.ts +3 -0
- package/src/stores/l2_tips_store.ts +67 -0
- package/src/utils.ts +98 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Logger } from '@aztec-labs/foundation/log';
|
|
2
|
+
import { Timer } from '@aztec-labs/foundation/timer';
|
|
3
|
+
|
|
4
|
+
import type { Key } from '../interfaces/common.js';
|
|
5
|
+
import type { AztecAsyncMap } from '../interfaces/map.js';
|
|
6
|
+
import type { AztecAsyncKVStore } from '../interfaces/store.js';
|
|
7
|
+
|
|
8
|
+
/** One benchmark measurement. */
|
|
9
|
+
export type BenchResult = {
|
|
10
|
+
/** Benchmark name (includes the backend prefix for disambiguation). */
|
|
11
|
+
name: string;
|
|
12
|
+
value: number;
|
|
13
|
+
unit: 'ms' | 'us';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type BenchReporter = (results: BenchResult[]) => void | Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Runs the standard Map benchmark suite against any `AztecAsyncKVStore` backend,
|
|
20
|
+
* populates `results`, and calls `reporter` in `afterAll`.
|
|
21
|
+
*
|
|
22
|
+
* Kept free of Node-only deps (`fs`, `path`) so the same runner works under
|
|
23
|
+
* vitest-browser for IndexedDB and SQLite-OPFS.
|
|
24
|
+
*/
|
|
25
|
+
export function describeAztecMapBench(
|
|
26
|
+
backendPrefix: string,
|
|
27
|
+
getStore: () => Promise<AztecAsyncKVStore>,
|
|
28
|
+
logger: Logger,
|
|
29
|
+
reporter: BenchReporter,
|
|
30
|
+
) {
|
|
31
|
+
describe(`${backendPrefix} Map benchmarks`, () => {
|
|
32
|
+
let store: AztecAsyncKVStore;
|
|
33
|
+
let map: AztecAsyncMap<Key, string>;
|
|
34
|
+
|
|
35
|
+
const results: BenchResult[] = [];
|
|
36
|
+
|
|
37
|
+
const generateKeyValuePairs = (count: number, offset = 0) => {
|
|
38
|
+
const keys = Array.from({ length: count }, (_, i) => `key-${i + offset}`);
|
|
39
|
+
const values = Array.from({ length: count }, (_, i) => `value-${i + offset}`);
|
|
40
|
+
return keys.map((key, i) => ({ key, value: values[i] }));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const record = (name: string, value: number, unit: BenchResult['unit']) => {
|
|
44
|
+
results.push({ name: `${backendPrefix}/Map/${name}`, value, unit });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
beforeEach(async () => {
|
|
48
|
+
store = await getStore();
|
|
49
|
+
map = store.openMap<Key, string>('test');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
await store.delete();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterAll(async () => {
|
|
57
|
+
const pretty = results.map(r => `${r.name}: ${r.value.toFixed(2)} ${r.unit}`).join('\n');
|
|
58
|
+
logger.info(`\n${pretty}\n`);
|
|
59
|
+
await reporter(results);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('adds individual values', async () => {
|
|
63
|
+
const pairs = generateKeyValuePairs(1000);
|
|
64
|
+
const timer = new Timer();
|
|
65
|
+
for (const pair of pairs) {
|
|
66
|
+
await map.set(pair.key, pair.value);
|
|
67
|
+
}
|
|
68
|
+
record('Individual insertion', timer.ms() / pairs.length, 'ms');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('adds batched values', async () => {
|
|
72
|
+
const batches = Array.from({ length: 100 }, (_, i) => generateKeyValuePairs(1000, i * 1000));
|
|
73
|
+
const timer = new Timer();
|
|
74
|
+
for (const batch of batches) {
|
|
75
|
+
await map.setMany(batch);
|
|
76
|
+
}
|
|
77
|
+
record(`Batch insertion of ${batches[0].length} items`, timer.ms() / batches.length, 'ms');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('reads individual values', async () => {
|
|
81
|
+
const pairs = generateKeyValuePairs(10000);
|
|
82
|
+
await map.setMany(pairs);
|
|
83
|
+
const timer = new Timer();
|
|
84
|
+
for (const pair of pairs) {
|
|
85
|
+
await map.getAsync(pair.key);
|
|
86
|
+
}
|
|
87
|
+
record('Individual read', (timer.ms() * 1000) / pairs.length, 'us');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('reads via a cursor', async () => {
|
|
91
|
+
const pairs = generateKeyValuePairs(10000);
|
|
92
|
+
await map.setMany(pairs);
|
|
93
|
+
const timer = new Timer();
|
|
94
|
+
for await (const _ of map.entriesAsync()) {
|
|
95
|
+
// consume
|
|
96
|
+
}
|
|
97
|
+
record(`Iterator per item read of ${pairs.length} items`, (timer.ms() * 1000) / pairs.length, 'us');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('reads the size of the map', async () => {
|
|
101
|
+
const numIterations = 1000;
|
|
102
|
+
const pairs = generateKeyValuePairs(10000);
|
|
103
|
+
await map.setMany(pairs);
|
|
104
|
+
const timer = new Timer();
|
|
105
|
+
for (let i = 0; i < numIterations; i++) {
|
|
106
|
+
await map.sizeAsync();
|
|
107
|
+
}
|
|
108
|
+
record(`Read size of ${pairs.length} items`, (timer.ms() * 1000) / numIterations, 'us');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
import { hash } from 'ohash';
|
|
3
|
+
|
|
4
|
+
import type { AztecAsyncArray } from '../../interfaces/array.js';
|
|
5
|
+
import type { Value } from '../../interfaces/common.js';
|
|
6
|
+
import type { AztecIDBSchema } from './store.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A persistent array backed by IndexedDB.
|
|
10
|
+
*/
|
|
11
|
+
export class IndexedDBAztecArray<T extends Value> implements AztecAsyncArray<T> {
|
|
12
|
+
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
13
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
14
|
+
#container: string;
|
|
15
|
+
#name: string;
|
|
16
|
+
|
|
17
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, name: string) {
|
|
18
|
+
this.#rootDB = rootDB;
|
|
19
|
+
this.#name = name;
|
|
20
|
+
this.#container = `array:${this.#name}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
|
|
24
|
+
this.#_db = db;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
|
|
28
|
+
return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async lengthAsync(): Promise<number> {
|
|
32
|
+
return (
|
|
33
|
+
(await this.db
|
|
34
|
+
.index('key')
|
|
35
|
+
.count(IDBKeyRange.bound([this.#container, [this.#name]], [this.#container, [this.#name]]))) ?? 0
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async push(...vals: T[]): Promise<number> {
|
|
40
|
+
let length = await this.lengthAsync();
|
|
41
|
+
for (const val of vals) {
|
|
42
|
+
await this.db.put({
|
|
43
|
+
value: val,
|
|
44
|
+
hash: hash(val),
|
|
45
|
+
container: this.#container,
|
|
46
|
+
key: [this.#name],
|
|
47
|
+
keyCount: length + 1,
|
|
48
|
+
slot: this.#slot(length),
|
|
49
|
+
});
|
|
50
|
+
length += 1;
|
|
51
|
+
}
|
|
52
|
+
return length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async pop(): Promise<T | undefined> {
|
|
56
|
+
const length = await this.lengthAsync();
|
|
57
|
+
if (length === 0) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const slot = this.#slot(length - 1);
|
|
62
|
+
const data = await this.db.get(slot);
|
|
63
|
+
await this.db.delete(slot);
|
|
64
|
+
|
|
65
|
+
return data?.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async atAsync(index: number): Promise<T | undefined> {
|
|
69
|
+
const length = await this.lengthAsync();
|
|
70
|
+
|
|
71
|
+
if (index < 0) {
|
|
72
|
+
index = length + index;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const data = await this.db.get(this.#slot(index));
|
|
76
|
+
return data?.value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async setAt(index: number, val: T): Promise<boolean> {
|
|
80
|
+
const length = await this.lengthAsync();
|
|
81
|
+
|
|
82
|
+
if (index < 0) {
|
|
83
|
+
index = length + index;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (index < 0 || index >= length) {
|
|
87
|
+
return Promise.resolve(false);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await this.db.put({
|
|
91
|
+
value: val,
|
|
92
|
+
hash: hash(val),
|
|
93
|
+
container: this.#container,
|
|
94
|
+
key: [this.#name],
|
|
95
|
+
keyCount: index + 1,
|
|
96
|
+
slot: this.#slot(index),
|
|
97
|
+
});
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async *entriesAsync(): AsyncIterableIterator<[number, T]> {
|
|
102
|
+
const index = this.db.index('key');
|
|
103
|
+
const rangeQuery = IDBKeyRange.bound([this.#container, [this.#name]], [this.#container, [this.#name]]);
|
|
104
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
105
|
+
yield [cursor.value.keyCount - 1, cursor.value.value] as [number, T];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async *valuesAsync(): AsyncIterableIterator<T> {
|
|
110
|
+
for await (const [_, value] of this.entriesAsync()) {
|
|
111
|
+
yield value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<T> {
|
|
116
|
+
return this.valuesAsync();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#slot(index: number): string {
|
|
120
|
+
return `array:${this.#name}:slot:${index}`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createLogger } from '@aztec-labs/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { AztecIndexedDBStore } from './store.js';
|
|
4
|
+
|
|
5
|
+
export { AztecIndexedDBStore } from './store.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated The IndexedDB backend is being retired. Use `@aztec-labs/kv-store/sqlite-opfs` instead.
|
|
9
|
+
*/
|
|
10
|
+
export function openTmpStore(ephemeral: boolean = false): Promise<AztecIndexedDBStore> {
|
|
11
|
+
return AztecIndexedDBStore.open(createLogger('kv-store:indexeddb'), undefined, ephemeral);
|
|
12
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
import { hash } from 'ohash';
|
|
3
|
+
|
|
4
|
+
import type { Key, Range, Value } from '../../interfaces/common.js';
|
|
5
|
+
import type { AztecAsyncMap } from '../../interfaces/map.js';
|
|
6
|
+
import type { AztecIDBSchema } from './store.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A map backed by IndexedDB.
|
|
10
|
+
*/
|
|
11
|
+
export class IndexedDBAztecMap<K extends Key, V extends Value> implements AztecAsyncMap<K, V> {
|
|
12
|
+
protected name: string;
|
|
13
|
+
protected container: string;
|
|
14
|
+
|
|
15
|
+
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
16
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
17
|
+
|
|
18
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, mapName: string) {
|
|
19
|
+
this.name = mapName;
|
|
20
|
+
this.container = `map:${mapName}`;
|
|
21
|
+
this.#rootDB = rootDB;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
|
|
25
|
+
this.#_db = db;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
|
|
29
|
+
return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getAsync(key: K): Promise<V | undefined> {
|
|
33
|
+
const data = await this.db.get(this.slot(key));
|
|
34
|
+
return data ? this.restoreBuffers(data.value as V) : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async hasAsync(key: K): Promise<boolean> {
|
|
38
|
+
const result = (await this.getAsync(key)) !== undefined;
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async sizeAsync(): Promise<number> {
|
|
43
|
+
const index = this.db.index('key');
|
|
44
|
+
const rangeQuery = IDBKeyRange.bound([this.container], [this.container + '\uffff'], true, true);
|
|
45
|
+
return await index.count(rangeQuery);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async set(key: K, val: V): Promise<void> {
|
|
49
|
+
await this.db.put({
|
|
50
|
+
value: val,
|
|
51
|
+
hash: hash(val),
|
|
52
|
+
container: this.container,
|
|
53
|
+
key: this.normalizeKey(key),
|
|
54
|
+
keyCount: 1,
|
|
55
|
+
slot: this.slot(key),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async setMany(entries: { key: K; value: V }[]): Promise<void> {
|
|
60
|
+
for (const { key, value } of entries) {
|
|
61
|
+
await this.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
swap(_key: K, _fn: (val: V | undefined) => V): Promise<void> {
|
|
66
|
+
throw new Error('Not implemented');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async setIfNotExists(key: K, val: V): Promise<boolean> {
|
|
70
|
+
if (!(await this.hasAsync(key))) {
|
|
71
|
+
await this.set(key, val);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async delete(key: K): Promise<void> {
|
|
78
|
+
await this.db.delete(this.slot(key));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<[K, V]> {
|
|
82
|
+
const index = this.db.index('key');
|
|
83
|
+
const startKey = range.start ? this.normalizeKey(range.start) : [];
|
|
84
|
+
const endKey = range.end ? this.normalizeKey(range.end) : ['\uffff'];
|
|
85
|
+
|
|
86
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
87
|
+
[this.container, startKey],
|
|
88
|
+
[this.container, endKey],
|
|
89
|
+
!!range.reverse,
|
|
90
|
+
!range.reverse,
|
|
91
|
+
);
|
|
92
|
+
let count = 0;
|
|
93
|
+
for await (const cursor of index.iterate(rangeQuery, range.reverse ? 'prev' : 'next')) {
|
|
94
|
+
if (range.limit && count >= range.limit) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
yield [this.#denormalizeKey(cursor.value.key), this.restoreBuffers(cursor.value.value as V)] as [K, V];
|
|
98
|
+
count++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async *valuesAsync(range: Range<K> = {}): AsyncIterableIterator<V> {
|
|
103
|
+
for await (const [_, value] of this.entriesAsync(range)) {
|
|
104
|
+
yield value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
|
|
109
|
+
for await (const [key, _] of this.entriesAsync(range)) {
|
|
110
|
+
yield key;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* IndexedDB's structured clone downcasts Buffer (a Node.js subclass of Uint8Array) to plain Uint8Array.
|
|
116
|
+
* This breaks .toString() behavior: Buffer.toString() returns UTF-8, Uint8Array.toString() returns
|
|
117
|
+
* comma-separated decimal bytes. We restore Buffer identity on read so callers get consistent behavior.
|
|
118
|
+
*/
|
|
119
|
+
protected restoreBuffers(val: V): V {
|
|
120
|
+
if (val instanceof Uint8Array && !Buffer.isBuffer(val)) {
|
|
121
|
+
return Buffer.from(val) as V;
|
|
122
|
+
}
|
|
123
|
+
return val;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#denormalizeKey(key: (string | number | Uint8Array)[]): K {
|
|
127
|
+
return (key.length > 1 ? key : key[0]) as K;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected normalizeKey(key: K): (string | number | Uint8Array)[] {
|
|
131
|
+
return Array.isArray(key) ? key : [key];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected slot(key: K, index: number = 0): string {
|
|
135
|
+
return `map:${this.name}:slot:${this.normalizeKey(key)}:${index}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { first } from '@aztec-labs/foundation/iterable';
|
|
2
|
+
import { hash } from 'ohash';
|
|
3
|
+
|
|
4
|
+
import type { Key, Value } from '../../interfaces/common.js';
|
|
5
|
+
import type { AztecAsyncMultiMap } from '../../interfaces/multi_map.js';
|
|
6
|
+
import { IndexedDBAztecMap } from './map.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A multi map backed by IndexedDB.
|
|
10
|
+
*/
|
|
11
|
+
export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
12
|
+
extends IndexedDBAztecMap<K, V>
|
|
13
|
+
implements AztecAsyncMultiMap<K, V>
|
|
14
|
+
{
|
|
15
|
+
override async set(key: K, val: V): Promise<void> {
|
|
16
|
+
// Inserting repeated values is a no-op
|
|
17
|
+
const exists = !!(await this.db
|
|
18
|
+
.index('hash')
|
|
19
|
+
.get(
|
|
20
|
+
IDBKeyRange.bound(
|
|
21
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
22
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
23
|
+
),
|
|
24
|
+
));
|
|
25
|
+
if (exists) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Get the maximum keyCount for the given key
|
|
29
|
+
// In order to support sparse multimaps, we cannot rely
|
|
30
|
+
// on just counting the number of entries for the key, since we would repeat slots
|
|
31
|
+
// if we delete an entry
|
|
32
|
+
// set -> container:key:0 (keyCount = 1)
|
|
33
|
+
// set -> container:key:1 (keyCount = 2)
|
|
34
|
+
// delete -> container:key:0 (keyCount = 1)
|
|
35
|
+
// set -> container:key:1 <--- already exists!
|
|
36
|
+
// Instead, we iterate in reverse order to get the last inserted entry
|
|
37
|
+
const index = this.db.index('keyCount');
|
|
38
|
+
const rangeQuery = IDBKeyRange.upperBound([this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER]);
|
|
39
|
+
const maxEntry = await first(index.iterate(rangeQuery, 'prevunique'));
|
|
40
|
+
const count = maxEntry?.value?.keyCount ?? 0;
|
|
41
|
+
await this.db.put({
|
|
42
|
+
value: val,
|
|
43
|
+
hash: hash(val),
|
|
44
|
+
container: this.container,
|
|
45
|
+
key: this.normalizeKey(key),
|
|
46
|
+
keyCount: count + 1,
|
|
47
|
+
slot: this.slot(key, count),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async *getValuesAsync(key: K): AsyncIterableIterator<V> {
|
|
52
|
+
// Iterate over the whole range of keyCount for the given key
|
|
53
|
+
const index = this.db.index('keyCount');
|
|
54
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
55
|
+
[this.container, this.normalizeKey(key), 0],
|
|
56
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
57
|
+
false,
|
|
58
|
+
false,
|
|
59
|
+
);
|
|
60
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
61
|
+
yield this.restoreBuffers(cursor.value.value as V);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getValueCountAsync(key: K): Promise<number> {
|
|
66
|
+
// Count entries over the keyCount index range for this key
|
|
67
|
+
const index = this.db.index('keyCount');
|
|
68
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
69
|
+
[this.container, this.normalizeKey(key), 0],
|
|
70
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
71
|
+
false,
|
|
72
|
+
false,
|
|
73
|
+
);
|
|
74
|
+
return index.count(rangeQuery);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async deleteValue(key: K, val: V): Promise<void> {
|
|
78
|
+
// Since we know the value, we can hash it and directly query the "hash" index
|
|
79
|
+
// to avoid having to iterate over all the values
|
|
80
|
+
const fullKey = await this.db
|
|
81
|
+
.index('hash')
|
|
82
|
+
.getKey(
|
|
83
|
+
IDBKeyRange.bound(
|
|
84
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
85
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
if (fullKey) {
|
|
89
|
+
await this.db.delete(fullKey);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override async delete(key: K): Promise<void> {
|
|
94
|
+
const index = this.db.index('keyCount');
|
|
95
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
96
|
+
[this.container, this.normalizeKey(key), 0],
|
|
97
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
98
|
+
false,
|
|
99
|
+
false,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
103
|
+
await cursor.delete();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
|
|
3
|
+
import type { Key, Range } from '../../interfaces/common.js';
|
|
4
|
+
import type { AztecAsyncSet } from '../../interfaces/set.js';
|
|
5
|
+
import { IndexedDBAztecMap } from './map.js';
|
|
6
|
+
import type { AztecIDBSchema } from './store.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A set backed by IndexedDB.
|
|
10
|
+
*/
|
|
11
|
+
export class IndexedDBAztecSet<K extends Key> implements AztecAsyncSet<K> {
|
|
12
|
+
private map: IndexedDBAztecMap<K, boolean>;
|
|
13
|
+
|
|
14
|
+
constructor(rootDb: IDBPDatabase<AztecIDBSchema>, mapName: string) {
|
|
15
|
+
this.map = new IndexedDBAztecMap(rootDb, mapName);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
|
|
19
|
+
this.map.db = db;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hasAsync(key: K): Promise<boolean> {
|
|
23
|
+
return this.map.hasAsync(key);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
add(key: K): Promise<void> {
|
|
27
|
+
return this.map.set(key, true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
delete(key: K): Promise<void> {
|
|
31
|
+
return this.map.delete(key);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
|
|
35
|
+
yield* this.map.keysAsync(range);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
import { hash } from 'ohash';
|
|
3
|
+
|
|
4
|
+
import type { Value } from '../../interfaces/common.js';
|
|
5
|
+
import type { AztecAsyncSingleton } from '../../interfaces/singleton.js';
|
|
6
|
+
import type { AztecIDBSchema } from './store.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stores a single value in IndexedDB.
|
|
10
|
+
*/
|
|
11
|
+
export class IndexedDBAztecSingleton<T extends Value> implements AztecAsyncSingleton<T> {
|
|
12
|
+
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
13
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
14
|
+
#container: string;
|
|
15
|
+
#slot: string;
|
|
16
|
+
|
|
17
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, name: string) {
|
|
18
|
+
this.#rootDB = rootDB;
|
|
19
|
+
this.#container = `singleton:${name}`;
|
|
20
|
+
this.#slot = `singleton:${name}:value`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
|
|
24
|
+
this.#_db = db;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
|
|
28
|
+
return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getAsync(): Promise<T | undefined> {
|
|
32
|
+
const data = await this.db.get(this.#slot);
|
|
33
|
+
return data?.value as T;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async set(val: T): Promise<boolean> {
|
|
37
|
+
const result = await this.db.put({
|
|
38
|
+
container: this.#container,
|
|
39
|
+
slot: this.#slot,
|
|
40
|
+
key: [this.#slot],
|
|
41
|
+
keyCount: 1,
|
|
42
|
+
value: val,
|
|
43
|
+
hash: hash(val),
|
|
44
|
+
});
|
|
45
|
+
return result !== undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async delete(): Promise<boolean> {
|
|
49
|
+
await this.db.delete(this.#slot);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|