@aztec/kv-store 0.66.0 → 0.67.1-devnet
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/dest/indexeddb/array.d.ts +21 -0
- package/dest/indexeddb/array.d.ts.map +1 -0
- package/dest/indexeddb/array.js +96 -0
- package/dest/indexeddb/index.d.ts +7 -0
- package/dest/indexeddb/index.d.ts.map +1 -0
- package/dest/indexeddb/index.js +22 -0
- package/dest/indexeddb/map.d.ts +26 -0
- package/dest/indexeddb/map.d.ts.map +1 -0
- package/dest/indexeddb/map.js +104 -0
- package/dest/indexeddb/set.d.ts +17 -0
- package/dest/indexeddb/set.d.ts.map +1 -0
- package/dest/indexeddb/set.js +25 -0
- package/dest/indexeddb/singleton.d.ts +16 -0
- package/dest/indexeddb/singleton.d.ts.map +1 -0
- package/dest/indexeddb/singleton.js +42 -0
- package/dest/indexeddb/store.d.ts +100 -0
- package/dest/indexeddb/store.d.ts.map +1 -0
- package/dest/indexeddb/store.js +157 -0
- package/dest/interfaces/array.d.ts +43 -11
- package/dest/interfaces/array.d.ts.map +1 -1
- 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 +100 -0
- package/dest/interfaces/counter.d.ts +21 -1
- package/dest/interfaces/counter.d.ts.map +1 -1
- package/dest/interfaces/map.d.ts +80 -12
- package/dest/interfaces/map.d.ts.map +1 -1
- 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 +117 -0
- package/dest/interfaces/set.d.ts +23 -7
- package/dest/interfaces/set.d.ts.map +1 -1
- 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 +59 -0
- package/dest/interfaces/singleton.d.ts +14 -5
- package/dest/interfaces/singleton.d.ts.map +1 -1
- 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 +33 -0
- package/dest/interfaces/store.d.ts +83 -8
- package/dest/interfaces/store.d.ts.map +1 -1
- package/dest/interfaces/store_test_suite.d.ts +3 -0
- package/dest/interfaces/store_test_suite.d.ts.map +1 -0
- package/dest/interfaces/store_test_suite.js +40 -0
- package/dest/interfaces/utils.d.ts +16 -0
- package/dest/interfaces/utils.d.ts.map +1 -0
- package/dest/interfaces/utils.js +19 -0
- package/dest/lmdb/array.d.ts +7 -2
- package/dest/lmdb/array.d.ts.map +1 -1
- package/dest/lmdb/array.js +20 -1
- package/dest/lmdb/counter.d.ts +5 -2
- package/dest/lmdb/counter.d.ts.map +1 -1
- package/dest/lmdb/counter.js +10 -1
- package/dest/lmdb/index.d.ts +10 -0
- package/dest/lmdb/index.d.ts.map +1 -1
- package/dest/lmdb/index.js +28 -1
- package/dest/lmdb/map.d.ts +25 -3
- package/dest/lmdb/map.d.ts.map +1 -1
- package/dest/lmdb/map.js +112 -26
- package/dest/lmdb/set.d.ts +4 -2
- package/dest/lmdb/set.d.ts.map +1 -1
- package/dest/lmdb/set.js +9 -1
- package/dest/lmdb/singleton.d.ts +3 -2
- package/dest/lmdb/singleton.d.ts.map +1 -1
- package/dest/lmdb/singleton.js +4 -1
- package/dest/lmdb/store.d.ts +35 -15
- package/dest/lmdb/store.d.ts.map +1 -1
- package/dest/lmdb/store.js +47 -27
- package/dest/stores/l2_tips_store.d.ts +2 -2
- package/dest/stores/l2_tips_store.d.ts.map +1 -1
- package/dest/stores/l2_tips_store.js +13 -13
- package/dest/utils.d.ts +8 -7
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +6 -29
- package/package.json +54 -37
- package/src/indexeddb/array.ts +118 -0
- package/src/indexeddb/index.ts +29 -0
- package/src/indexeddb/map.ts +142 -0
- package/src/indexeddb/set.ts +37 -0
- package/src/indexeddb/singleton.ts +49 -0
- package/src/indexeddb/store.ts +193 -0
- package/src/interfaces/array.ts +48 -12
- package/src/interfaces/array_test_suite.ts +130 -0
- package/src/interfaces/counter.ts +23 -1
- package/src/interfaces/map.ts +90 -14
- package/src/interfaces/map_test_suite.ts +158 -0
- package/src/interfaces/set.ts +25 -8
- package/src/interfaces/set_test_suite.ts +81 -0
- package/src/interfaces/singleton.ts +14 -6
- package/src/interfaces/singleton_test_suite.ts +46 -0
- package/src/interfaces/store.ts +99 -8
- package/src/interfaces/store_test_suite.ts +56 -0
- package/src/interfaces/utils.ts +21 -0
- package/src/lmdb/array.ts +26 -2
- package/src/lmdb/counter.ts +14 -2
- package/src/lmdb/index.ts +36 -0
- package/src/lmdb/map.ts +130 -23
- package/src/lmdb/set.ts +12 -2
- package/src/lmdb/singleton.ts +6 -2
- package/src/lmdb/store.ts +73 -43
- package/src/stores/l2_tips_store.ts +17 -17
- package/src/utils.ts +8 -37
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type IDBPDatabase, type IDBPObjectStore } from 'idb';
|
|
2
|
+
|
|
3
|
+
import { type AztecAsyncArray } from '../interfaces/array.js';
|
|
4
|
+
import { type AztecIDBSchema } from './store.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A persistent array backed by IndexedDB.
|
|
8
|
+
*/
|
|
9
|
+
export class IndexedDBAztecArray<T> implements AztecAsyncArray<T> {
|
|
10
|
+
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
11
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
12
|
+
#container: string;
|
|
13
|
+
#name: string;
|
|
14
|
+
|
|
15
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, name: string) {
|
|
16
|
+
this.#rootDB = rootDB;
|
|
17
|
+
this.#name = name;
|
|
18
|
+
this.#container = `array:${this.#name}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
|
|
22
|
+
this.#_db = db;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
|
|
26
|
+
return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async lengthAsync(): Promise<number> {
|
|
30
|
+
return (
|
|
31
|
+
(await this.db
|
|
32
|
+
.index('key')
|
|
33
|
+
.count(IDBKeyRange.bound([this.#container, this.#name], [this.#container, this.#name]))) ?? 0
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async push(...vals: T[]): Promise<number> {
|
|
38
|
+
let length = await this.lengthAsync();
|
|
39
|
+
for (const val of vals) {
|
|
40
|
+
await this.db.put({
|
|
41
|
+
value: val,
|
|
42
|
+
container: this.#container,
|
|
43
|
+
key: this.#name,
|
|
44
|
+
keyCount: length + 1,
|
|
45
|
+
slot: this.#slot(length),
|
|
46
|
+
});
|
|
47
|
+
length += 1;
|
|
48
|
+
}
|
|
49
|
+
return length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async pop(): Promise<T | undefined> {
|
|
53
|
+
const length = await this.lengthAsync();
|
|
54
|
+
if (length === 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const slot = this.#slot(length - 1);
|
|
59
|
+
const data = await this.db.get(slot);
|
|
60
|
+
await this.db.delete(slot);
|
|
61
|
+
|
|
62
|
+
return data?.value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async atAsync(index: number): Promise<T | undefined> {
|
|
66
|
+
const length = await this.lengthAsync();
|
|
67
|
+
|
|
68
|
+
if (index < 0) {
|
|
69
|
+
index = length + index;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const data = await this.db.get(this.#slot(index));
|
|
73
|
+
return data?.value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async setAt(index: number, val: T): Promise<boolean> {
|
|
77
|
+
const length = await this.lengthAsync();
|
|
78
|
+
|
|
79
|
+
if (index < 0) {
|
|
80
|
+
index = length + index;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (index < 0 || index >= length) {
|
|
84
|
+
return Promise.resolve(false);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await this.db.put({
|
|
88
|
+
value: val,
|
|
89
|
+
container: this.#container,
|
|
90
|
+
key: this.#name,
|
|
91
|
+
keyCount: index + 1,
|
|
92
|
+
slot: this.#slot(index),
|
|
93
|
+
});
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async *entriesAsync(): AsyncIterableIterator<[number, T]> {
|
|
98
|
+
const index = this.db.index('key');
|
|
99
|
+
const rangeQuery = IDBKeyRange.bound([this.#container, this.#name], [this.#container, this.#name]);
|
|
100
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
101
|
+
yield [cursor.value.keyCount - 1, cursor.value.value] as [number, T];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async *valuesAsync(): AsyncIterableIterator<T> {
|
|
106
|
+
for await (const [_, value] of this.entriesAsync()) {
|
|
107
|
+
yield value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<T> {
|
|
112
|
+
return this.valuesAsync();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#slot(index: number): string {
|
|
116
|
+
return `array:${this.#name}:slot:${index}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { type DataStoreConfig } from '../config.js';
|
|
4
|
+
import { initStoreForRollup } from '../utils.js';
|
|
5
|
+
import { AztecIndexedDBStore } from './store.js';
|
|
6
|
+
|
|
7
|
+
export { AztecIndexedDBStore } from './store.js';
|
|
8
|
+
|
|
9
|
+
export async function createStore(name: string, config: DataStoreConfig, log: Logger = createLogger('kv-store')) {
|
|
10
|
+
let { dataDirectory } = config;
|
|
11
|
+
if (typeof dataDirectory !== 'undefined') {
|
|
12
|
+
dataDirectory = `${dataDirectory}/${name}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
log.info(
|
|
16
|
+
dataDirectory
|
|
17
|
+
? `Creating ${name} data store at directory ${dataDirectory} with map size ${config.dataStoreMapSizeKB} KB`
|
|
18
|
+
: `Creating ${name} ephemeral data store with map size ${config.dataStoreMapSizeKB} KB`,
|
|
19
|
+
);
|
|
20
|
+
const store = await AztecIndexedDBStore.open(createLogger('kv-store:indexeddb'), dataDirectory ?? '', false);
|
|
21
|
+
if (config.l1Contracts?.rollupAddress) {
|
|
22
|
+
return initStoreForRollup(store, config.l1Contracts.rollupAddress, log);
|
|
23
|
+
}
|
|
24
|
+
return store;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function openTmpStore(ephemeral: boolean = false): Promise<AztecIndexedDBStore> {
|
|
28
|
+
return AztecIndexedDBStore.open(createLogger('kv-store:indexeddb'), undefined, ephemeral);
|
|
29
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type IDBPDatabase, type IDBPObjectStore } from 'idb';
|
|
2
|
+
|
|
3
|
+
import { type Key, type Range } from '../interfaces/common.js';
|
|
4
|
+
import { type AztecAsyncMultiMap } from '../interfaces/map.js';
|
|
5
|
+
import { type AztecIDBSchema } from './store.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A map backed by IndexedDB.
|
|
9
|
+
*/
|
|
10
|
+
export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K, V> {
|
|
11
|
+
protected name: string;
|
|
12
|
+
#container: string;
|
|
13
|
+
|
|
14
|
+
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
15
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
16
|
+
|
|
17
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, mapName: string) {
|
|
18
|
+
this.name = mapName;
|
|
19
|
+
this.#container = `map:${mapName}`;
|
|
20
|
+
this.#rootDB = rootDB;
|
|
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(key: K): Promise<V | undefined> {
|
|
32
|
+
const data = await this.db.get(this.#slot(key));
|
|
33
|
+
return data?.value as V;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async *getValuesAsync(key: K): AsyncIterableIterator<V> {
|
|
37
|
+
const index = this.db.index('keyCount');
|
|
38
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
39
|
+
[this.#container, this.#normalizeKey(key), 0],
|
|
40
|
+
[this.#container, this.#normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
41
|
+
false,
|
|
42
|
+
false,
|
|
43
|
+
);
|
|
44
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
45
|
+
yield cursor.value.value as V;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async hasAsync(key: K): Promise<boolean> {
|
|
50
|
+
const result = (await this.getAsync(key)) !== undefined;
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async set(key: K, val: V): Promise<void> {
|
|
55
|
+
const count = await this.db
|
|
56
|
+
.index('key')
|
|
57
|
+
.count(IDBKeyRange.bound([this.#container, this.#normalizeKey(key)], [this.#container, this.#normalizeKey(key)]));
|
|
58
|
+
await this.db.put({
|
|
59
|
+
value: val,
|
|
60
|
+
container: this.#container,
|
|
61
|
+
key: this.#normalizeKey(key),
|
|
62
|
+
keyCount: count + 1,
|
|
63
|
+
slot: this.#slot(key, count),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
swap(_key: K, _fn: (val: V | undefined) => V): Promise<void> {
|
|
68
|
+
throw new Error('Not implemented');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async setIfNotExists(key: K, val: V): Promise<boolean> {
|
|
72
|
+
if (!(await this.hasAsync(key))) {
|
|
73
|
+
await this.set(key, val);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async delete(key: K): Promise<void> {
|
|
80
|
+
await this.db.delete(this.#slot(key));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async deleteValue(key: K, val: V): Promise<void> {
|
|
84
|
+
const index = this.db.index('keyCount');
|
|
85
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
86
|
+
[this.#container, this.#normalizeKey(key), 0],
|
|
87
|
+
[this.#container, this.#normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
88
|
+
false,
|
|
89
|
+
false,
|
|
90
|
+
);
|
|
91
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
92
|
+
if (JSON.stringify(cursor.value.value) === JSON.stringify(val)) {
|
|
93
|
+
await cursor.delete();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<[K, V]> {
|
|
100
|
+
const index = this.db.index('key');
|
|
101
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
102
|
+
[this.#container, range.start ?? ''],
|
|
103
|
+
[this.#container, range.end ?? '\uffff'],
|
|
104
|
+
!!range.reverse,
|
|
105
|
+
!range.reverse,
|
|
106
|
+
);
|
|
107
|
+
let count = 0;
|
|
108
|
+
for await (const cursor of index.iterate(rangeQuery, range.reverse ? 'prev' : 'next')) {
|
|
109
|
+
if (range.limit && count >= range.limit) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
yield [cursor.value.key, cursor.value.value] as [K, V];
|
|
113
|
+
count++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async *valuesAsync(range: Range<K> = {}): AsyncIterableIterator<V> {
|
|
118
|
+
for await (const [_, value] of this.entriesAsync(range)) {
|
|
119
|
+
yield value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
|
|
124
|
+
for await (const [key, _] of this.entriesAsync(range)) {
|
|
125
|
+
yield this.#denormalizeKey(key as string);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#denormalizeKey(key: string): K {
|
|
130
|
+
const denormalizedKey = (key as string).split(',').map(part => (isNaN(parseInt(part)) ? part : parseInt(part)));
|
|
131
|
+
return (denormalizedKey.length > 1 ? denormalizedKey : key) as K;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#normalizeKey(key: K): string {
|
|
135
|
+
const arrayKey = Array.isArray(key) ? key : [key];
|
|
136
|
+
return arrayKey.join(',');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#slot(key: K, index: number = 0): string {
|
|
140
|
+
return `map:${this.name}:slot:${this.#normalizeKey(key)}:${index}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type IDBPDatabase, type IDBPObjectStore } from 'idb';
|
|
2
|
+
|
|
3
|
+
import { type Key, type 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,49 @@
|
|
|
1
|
+
import { type IDBPDatabase, type IDBPObjectStore } from 'idb';
|
|
2
|
+
|
|
3
|
+
import { type AztecAsyncSingleton } from '../interfaces/singleton.js';
|
|
4
|
+
import { type AztecIDBSchema } from './store.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Stores a single value in IndexedDB.
|
|
8
|
+
*/
|
|
9
|
+
export class IndexedDBAztecSingleton<T> implements AztecAsyncSingleton<T> {
|
|
10
|
+
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
11
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
12
|
+
#container: string;
|
|
13
|
+
#slot: string;
|
|
14
|
+
|
|
15
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, name: string) {
|
|
16
|
+
this.#rootDB = rootDB;
|
|
17
|
+
this.#container = `singleton:${name}`;
|
|
18
|
+
this.#slot = `singleton:${name}:value`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
|
|
22
|
+
this.#_db = db;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
|
|
26
|
+
return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getAsync(): Promise<T | undefined> {
|
|
30
|
+
const data = await this.db.get(this.#slot);
|
|
31
|
+
return data?.value as T;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async set(val: T): Promise<boolean> {
|
|
35
|
+
const result = await this.db.put({
|
|
36
|
+
container: this.#container,
|
|
37
|
+
slot: this.#slot,
|
|
38
|
+
key: this.#slot,
|
|
39
|
+
keyCount: 1,
|
|
40
|
+
value: val,
|
|
41
|
+
});
|
|
42
|
+
return result !== undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async delete(): Promise<boolean> {
|
|
46
|
+
await this.db.delete(this.#slot);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { type DBSchema, type IDBPDatabase, deleteDB, openDB } from 'idb';
|
|
4
|
+
|
|
5
|
+
import { type AztecAsyncArray } from '../interfaces/array.js';
|
|
6
|
+
import { type Key } from '../interfaces/common.js';
|
|
7
|
+
import { type AztecAsyncCounter } from '../interfaces/counter.js';
|
|
8
|
+
import { type AztecAsyncMap, type AztecAsyncMultiMap } from '../interfaces/map.js';
|
|
9
|
+
import { type AztecAsyncSet } from '../interfaces/set.js';
|
|
10
|
+
import { type AztecAsyncSingleton } from '../interfaces/singleton.js';
|
|
11
|
+
import { type AztecAsyncKVStore } from '../interfaces/store.js';
|
|
12
|
+
import { IndexedDBAztecArray } from './array.js';
|
|
13
|
+
import { IndexedDBAztecMap } from './map.js';
|
|
14
|
+
import { IndexedDBAztecSet } from './set.js';
|
|
15
|
+
import { IndexedDBAztecSingleton } from './singleton.js';
|
|
16
|
+
|
|
17
|
+
export type StoredData<V> = { value: V; container: string; key: string; keyCount: number; slot: string };
|
|
18
|
+
|
|
19
|
+
export interface AztecIDBSchema extends DBSchema {
|
|
20
|
+
data: {
|
|
21
|
+
value: StoredData<any>;
|
|
22
|
+
key: string;
|
|
23
|
+
indexes: { container: string; key: string; keyCount: number };
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A key-value store backed by IndexedDB.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export class AztecIndexedDBStore implements AztecAsyncKVStore {
|
|
32
|
+
#log: Logger;
|
|
33
|
+
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
34
|
+
#name: string;
|
|
35
|
+
|
|
36
|
+
#containers = new Set<
|
|
37
|
+
IndexedDBAztecArray<any> | IndexedDBAztecMap<any, any> | IndexedDBAztecSet<any> | IndexedDBAztecSingleton<any>
|
|
38
|
+
>();
|
|
39
|
+
|
|
40
|
+
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, public readonly isEphemeral: boolean, log: Logger, name: string) {
|
|
41
|
+
this.#rootDB = rootDB;
|
|
42
|
+
this.#log = log;
|
|
43
|
+
this.#name = name;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new AztecKVStore backed by IndexedDB. The path to the database is optional. If not provided,
|
|
47
|
+
* the database will be stored in a temporary location and be deleted when the process exists.
|
|
48
|
+
*
|
|
49
|
+
*
|
|
50
|
+
* @param path - A path on the disk to store the database. Optional
|
|
51
|
+
* @param ephemeral - true if the store should only exist in memory and not automatically be flushed to disk. Optional
|
|
52
|
+
* @param log - A logger to use. Optional
|
|
53
|
+
* @returns The store
|
|
54
|
+
*/
|
|
55
|
+
static async open(log: Logger, name?: string, ephemeral: boolean = false): Promise<AztecIndexedDBStore> {
|
|
56
|
+
name = name && !ephemeral ? name : self.crypto.getRandomValues(new Uint8Array(16)).join('');
|
|
57
|
+
log.debug(`Opening IndexedDB ${ephemeral ? 'temp ' : ''}database with name ${name}`);
|
|
58
|
+
const rootDB = await openDB<AztecIDBSchema>(name, 1, {
|
|
59
|
+
upgrade(db) {
|
|
60
|
+
const objectStore = db.createObjectStore('data', { keyPath: 'slot' });
|
|
61
|
+
|
|
62
|
+
objectStore.createIndex('key', ['container', 'key'], { unique: false });
|
|
63
|
+
objectStore.createIndex('keyCount', ['container', 'key', 'keyCount'], { unique: false });
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const kvStore = new AztecIndexedDBStore(rootDB, ephemeral, log, name);
|
|
68
|
+
return kvStore;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Forks the current DB into a new DB by backing it up to a temporary location and opening a new indexedb.
|
|
73
|
+
* @returns A new AztecIndexedDBStore.
|
|
74
|
+
*/
|
|
75
|
+
async fork(): Promise<AztecAsyncKVStore> {
|
|
76
|
+
const forkedStore = await AztecIndexedDBStore.open(this.#log, undefined, true);
|
|
77
|
+
this.#log.verbose(`Forking store to ${forkedStore.#name}`);
|
|
78
|
+
|
|
79
|
+
// Copy old data to new store
|
|
80
|
+
const oldData = this.#rootDB.transaction('data').store;
|
|
81
|
+
const dataToWrite = [];
|
|
82
|
+
for await (const cursor of oldData.iterate()) {
|
|
83
|
+
dataToWrite.push(cursor.value);
|
|
84
|
+
}
|
|
85
|
+
const tx = forkedStore.#rootDB.transaction('data', 'readwrite').store;
|
|
86
|
+
for (const data of dataToWrite) {
|
|
87
|
+
await tx.add(data);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.#log.debug(`Forked store at ${forkedStore.#name} opened successfully`);
|
|
91
|
+
return forkedStore;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates a new AztecMap in the store.
|
|
96
|
+
* @param name - Name of the map
|
|
97
|
+
* @returns A new AztecMap
|
|
98
|
+
*/
|
|
99
|
+
openMap<K extends Key, V>(name: string): AztecAsyncMap<K, V> {
|
|
100
|
+
const map = new IndexedDBAztecMap<K, V>(this.#rootDB, name);
|
|
101
|
+
this.#containers.add(map);
|
|
102
|
+
return map;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a new AztecSet in the store.
|
|
107
|
+
* @param name - Name of the set
|
|
108
|
+
* @returns A new AztecSet
|
|
109
|
+
*/
|
|
110
|
+
openSet<K extends Key>(name: string): AztecAsyncSet<K> {
|
|
111
|
+
const set = new IndexedDBAztecSet<K>(this.#rootDB, name);
|
|
112
|
+
this.#containers.add(set);
|
|
113
|
+
return set;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates a new AztecMultiMap in the store. A multi-map stores multiple values for a single key automatically.
|
|
118
|
+
* @param name - Name of the map
|
|
119
|
+
* @returns A new AztecMultiMap
|
|
120
|
+
*/
|
|
121
|
+
openMultiMap<K extends Key, V>(name: string): AztecAsyncMultiMap<K, V> {
|
|
122
|
+
const multimap = new IndexedDBAztecMap<K, V>(this.#rootDB, name);
|
|
123
|
+
this.#containers.add(multimap);
|
|
124
|
+
return multimap;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
openCounter<K extends Key | Array<string | number>>(_name: string): AztecAsyncCounter<K> {
|
|
128
|
+
throw new Error('Method not implemented.');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates a new AztecArray in the store.
|
|
133
|
+
* @param name - Name of the array
|
|
134
|
+
* @returns A new AztecArray
|
|
135
|
+
*/
|
|
136
|
+
openArray<T>(name: string): AztecAsyncArray<T> {
|
|
137
|
+
const array = new IndexedDBAztecArray<T>(this.#rootDB, name);
|
|
138
|
+
this.#containers.add(array);
|
|
139
|
+
return array;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates a new AztecSingleton in the store.
|
|
144
|
+
* @param name - Name of the singleton
|
|
145
|
+
* @returns A new AztecSingleton
|
|
146
|
+
*/
|
|
147
|
+
openSingleton<T>(name: string): AztecAsyncSingleton<T> {
|
|
148
|
+
const singleton = new IndexedDBAztecSingleton<T>(this.#rootDB, name);
|
|
149
|
+
this.#containers.add(singleton);
|
|
150
|
+
return singleton;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Runs a callback in a transaction.
|
|
155
|
+
* @param callback - Function to execute in a transaction
|
|
156
|
+
* @returns A promise that resolves to the return value of the callback
|
|
157
|
+
*/
|
|
158
|
+
async transactionAsync<T>(callback: () => Promise<T>): Promise<T> {
|
|
159
|
+
const tx = this.#rootDB.transaction('data', 'readwrite');
|
|
160
|
+
for (const container of this.#containers) {
|
|
161
|
+
container.db = tx.store;
|
|
162
|
+
}
|
|
163
|
+
// Avoid awaiting this promise so it doesn't get scheduled in the next microtask
|
|
164
|
+
// By then, the tx would be closed
|
|
165
|
+
const runningPromise = callback();
|
|
166
|
+
// Wait for the transaction to finish
|
|
167
|
+
await tx.done;
|
|
168
|
+
for (const container of this.#containers) {
|
|
169
|
+
container.db = undefined;
|
|
170
|
+
}
|
|
171
|
+
// Return the result of the callback.
|
|
172
|
+
// Tx is guaranteed to already be closed, so the await doesn't hurt anything here
|
|
173
|
+
return await runningPromise;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Clears all entries in the store & sub DBs.
|
|
178
|
+
*/
|
|
179
|
+
async clear() {
|
|
180
|
+
await this.#rootDB.transaction('data', 'readwrite').store.clear();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Deletes this store and removes the database */
|
|
184
|
+
delete() {
|
|
185
|
+
this.#containers.clear();
|
|
186
|
+
this.#rootDB.close();
|
|
187
|
+
return deleteDB(this.#name);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
estimateSize(): { mappingSize: number; actualSize: number; numItems: number } {
|
|
191
|
+
return { mappingSize: 0, actualSize: 0, numItems: 0 };
|
|
192
|
+
}
|
|
193
|
+
}
|
package/src/interfaces/array.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An array backed by a persistent store. Can not have any holes in it.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The size of the array
|
|
7
|
-
*/
|
|
8
|
-
length: number;
|
|
9
|
-
|
|
4
|
+
interface BaseAztecArray<T> {
|
|
10
5
|
/**
|
|
11
6
|
* Pushes values to the end of the array
|
|
12
7
|
* @param vals - The values to push to the end of the array
|
|
@@ -20,6 +15,24 @@ export interface AztecArray<T> {
|
|
|
20
15
|
*/
|
|
21
16
|
pop(): Promise<T | undefined>;
|
|
22
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Updates the value at the given index. Index can be in the range [-length, length - 1).
|
|
20
|
+
* @param index - The index to set the value at
|
|
21
|
+
* @param val - The value to set
|
|
22
|
+
* @returns Whether the value was set
|
|
23
|
+
*/
|
|
24
|
+
setAt(index: number, val: T): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* An array backed by a persistent store. Can not have any holes in it.
|
|
29
|
+
*/
|
|
30
|
+
export interface AztecAsyncArray<T> extends BaseAztecArray<T> {
|
|
31
|
+
/**
|
|
32
|
+
* The size of the array
|
|
33
|
+
*/
|
|
34
|
+
lengthAsync(): Promise<number>;
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Gets the value at the given index. Index can be in the range [-length, length - 1).
|
|
25
38
|
* If the index is negative, it will be treated as an offset from the end of the array.
|
|
@@ -27,15 +40,38 @@ export interface AztecArray<T> {
|
|
|
27
40
|
* @param index - The index to get the value from
|
|
28
41
|
* @returns The value at the given index or undefined if the index is out of bounds
|
|
29
42
|
*/
|
|
30
|
-
|
|
43
|
+
atAsync(index: number): Promise<T | undefined>;
|
|
31
44
|
|
|
32
45
|
/**
|
|
33
|
-
*
|
|
34
|
-
* @param index - The index to set the value at
|
|
35
|
-
* @param val - The value to set
|
|
36
|
-
* @returns Whether the value was set
|
|
46
|
+
* Iterates over the array with indexes.
|
|
37
47
|
*/
|
|
38
|
-
|
|
48
|
+
entriesAsync(): AsyncIterableIterator<[number, T]>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Iterates over the array.
|
|
52
|
+
*/
|
|
53
|
+
valuesAsync(): AsyncIterableIterator<T>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Iterates over the array.
|
|
57
|
+
*/
|
|
58
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AztecArray<T> extends BaseAztecArray<T> {
|
|
62
|
+
/**
|
|
63
|
+
* The size of the array
|
|
64
|
+
*/
|
|
65
|
+
length: number;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets the value at the given index. Index can be in the range [-length, length - 1).
|
|
69
|
+
* If the index is negative, it will be treated as an offset from the end of the array.
|
|
70
|
+
*
|
|
71
|
+
* @param index - The index to get the value from
|
|
72
|
+
* @returns The value at the given index or undefined if the index is out of bounds
|
|
73
|
+
*/
|
|
74
|
+
at(index: number): T | undefined;
|
|
39
75
|
|
|
40
76
|
/**
|
|
41
77
|
* Iterates over the array with indexes.
|