@aztec/kv-store 0.23.0 → 0.26.1
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/lmdb/store.js +2 -2
- package/package.json +2 -2
- package/src/interfaces/array.ts +54 -0
- package/src/interfaces/common.ts +18 -0
- package/src/interfaces/counter.ts +43 -0
- package/src/interfaces/index.ts +6 -0
- package/src/interfaces/map.ts +82 -0
- package/src/interfaces/singleton.ts +21 -0
- package/src/interfaces/store.ts +53 -0
- package/src/lmdb/array.ts +109 -0
- package/src/lmdb/counter.ts +57 -0
- package/src/lmdb/index.ts +1 -0
- package/src/lmdb/map.ts +129 -0
- package/src/lmdb/singleton.ts +31 -0
- package/src/lmdb/store.ts +112 -0
- package/src/utils.ts +41 -0
package/dest/lmdb/store.js
CHANGED
|
@@ -21,7 +21,7 @@ export class AztecLmdbStore {
|
|
|
21
21
|
keyEncoding: 'ordered-binary',
|
|
22
22
|
}), "f");
|
|
23
23
|
__classPrivateFieldSet(this, _AztecLmdbStore_multiMapData, rootDb.openDB('data_dup_sort', {
|
|
24
|
-
encoding: '
|
|
24
|
+
encoding: 'ordered-binary',
|
|
25
25
|
keyEncoding: 'ordered-binary',
|
|
26
26
|
dupSort: true,
|
|
27
27
|
}), "f");
|
|
@@ -94,4 +94,4 @@ export class AztecLmdbStore {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
_AztecLmdbStore_rootDb = new WeakMap(), _AztecLmdbStore_data = new WeakMap(), _AztecLmdbStore_multiMapData = new WeakMap();
|
|
97
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
97
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbG1kYi9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRTFELE9BQU8sRUFBK0IsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBT3pELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDNUMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDeEMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFcEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUt6QixZQUFZLE1BQW9CO1FBSmhDLHlDQUFzQjtRQUN0Qix1Q0FBOEI7UUFDOUIsK0NBQXNDO1FBR3BDLHVCQUFBLElBQUksMEJBQVcsTUFBTSxNQUFBLENBQUM7UUFFdEIsbUNBQW1DO1FBQ25DLHVCQUFBLElBQUksd0JBQVMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7WUFDakMsUUFBUSxFQUFFLFNBQVM7WUFDbkIsV0FBVyxFQUFFLGdCQUFnQjtTQUM5QixDQUFDLE1BQUEsQ0FBQztRQUVILHVCQUFBLElBQUksZ0NBQWlCLE1BQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFO1lBQ2xELFFBQVEsRUFBRSxnQkFBZ0I7WUFDMUIsV0FBVyxFQUFFLGdCQUFnQjtZQUM3QixPQUFPLEVBQUUsSUFBSTtTQUNkLENBQUMsTUFBQSxDQUFDO0lBQ0wsQ0FBQztJQUVEOzs7Ozs7Ozs7OztPQVdHO0lBQ0gsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFhLEVBQUUsR0FBRyxHQUFHLGlCQUFpQixDQUFDLHFCQUFxQixDQUFDO1FBQ3ZFLEdBQUcsQ0FBQyxJQUFJLENBQUMsNEJBQTRCLElBQUksSUFBSSxvQkFBb0IsRUFBRSxDQUFDLENBQUM7UUFDckUsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUM5QixPQUFPLElBQUksY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3BDLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsT0FBTyxDQUErQixJQUFZO1FBQ2hELE9BQU8sSUFBSSxZQUFZLENBQUMsdUJBQUEsSUFBSSw0QkFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzVDLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsWUFBWSxDQUErQixJQUFZO1FBQ3JELE9BQU8sSUFBSSxZQUFZLENBQUMsdUJBQUEsSUFBSSxvQ0FBYyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3BELENBQUM7SUFFRCxXQUFXLENBQXFELElBQVk7UUFDMUUsT0FBTyxJQUFJLGdCQUFnQixDQUFDLHVCQUFBLElBQUksNEJBQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNoRCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILFNBQVMsQ0FBSSxJQUFZO1FBQ3ZCLE9BQU8sSUFBSSxjQUFjLENBQUMsdUJBQUEsSUFBSSw0QkFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzlDLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsYUFBYSxDQUFJLElBQVk7UUFDM0IsT0FBTyxJQUFJLGtCQUFrQixDQUFDLHVCQUFBLElBQUksNEJBQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNsRCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILFdBQVcsQ0FBSSxRQUFpQjtRQUM5QixPQUFPLHVCQUFBLElBQUksOEJBQVEsQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLEtBQUs7UUFDVCxNQUFNLHVCQUFBLElBQUksOEJBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUNsQyxDQUFDO0NBQ0YifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/kv-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/interfaces/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"workerThreads": true
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@aztec/foundation": "0.
|
|
32
|
+
"@aztec/foundation": "0.26.1",
|
|
33
33
|
"lmdb": "^2.9.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An array backed by a persistent store. Can not have any holes in it.
|
|
3
|
+
*/
|
|
4
|
+
export interface AztecArray<T> {
|
|
5
|
+
/**
|
|
6
|
+
* The size of the array
|
|
7
|
+
*/
|
|
8
|
+
length: number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pushes values to the end of the array
|
|
12
|
+
* @param vals - The values to push to the end of the array
|
|
13
|
+
* @returns The new length of the array
|
|
14
|
+
*/
|
|
15
|
+
push(...vals: T[]): Promise<number>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pops a value from the end of the array.
|
|
19
|
+
* @returns The value that was popped, or undefined if the array was empty
|
|
20
|
+
*/
|
|
21
|
+
pop(): Promise<T | undefined>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets the value at the given index. Index can be in the range [-length, length - 1).
|
|
25
|
+
* If the index is negative, it will be treated as an offset from the end of the array.
|
|
26
|
+
*
|
|
27
|
+
* @param index - The index to get the value from
|
|
28
|
+
* @returns The value at the given index or undefined if the index is out of bounds
|
|
29
|
+
*/
|
|
30
|
+
at(index: number): T | undefined;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Updates the value at the given index. Index can be in the range [-length, length - 1).
|
|
34
|
+
* @param index - The index to set the value at
|
|
35
|
+
* @param val - The value to set
|
|
36
|
+
* @returns Whether the value was set
|
|
37
|
+
*/
|
|
38
|
+
setAt(index: number, val: T): Promise<boolean>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Iterates over the array with indexes.
|
|
42
|
+
*/
|
|
43
|
+
entries(): IterableIterator<[number, T]>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Iterates over the array.
|
|
47
|
+
*/
|
|
48
|
+
values(): IterableIterator<T>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Iterates over the array.
|
|
52
|
+
*/
|
|
53
|
+
[Symbol.iterator](): IterableIterator<T>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The key type for use with the kv-store
|
|
3
|
+
*/
|
|
4
|
+
export type Key = string | number | Array<string | number>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A range of keys to iterate over.
|
|
8
|
+
*/
|
|
9
|
+
export type Range<K extends Key = Key> = {
|
|
10
|
+
/** The key of the first item to include */
|
|
11
|
+
start?: K;
|
|
12
|
+
/** The key of the last item to include */
|
|
13
|
+
end?: K;
|
|
14
|
+
/** Whether to iterate in reverse */
|
|
15
|
+
reverse?: boolean;
|
|
16
|
+
/** The maximum number of items to iterate over */
|
|
17
|
+
limit?: number;
|
|
18
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Key, Range } from './common.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A map that counts how many times it sees a key. Once 0 is reached, that key is removed from the map.
|
|
5
|
+
* Iterating over the map will only return keys that have a count over 0.
|
|
6
|
+
*
|
|
7
|
+
* Keys are stored in sorted order
|
|
8
|
+
*/
|
|
9
|
+
export interface AztecCounter<K extends Key = Key> {
|
|
10
|
+
/**
|
|
11
|
+
* Resets the count of the given key to the given value.
|
|
12
|
+
* @param key - The key to reset
|
|
13
|
+
* @param value - The value to reset the key to
|
|
14
|
+
*/
|
|
15
|
+
set(key: K, value: number): Promise<boolean>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Updates the count of the given key by the given delta. This can be used to increment or decrement the count.
|
|
19
|
+
* Once a key's count reaches 0, it is removed from the map.
|
|
20
|
+
*
|
|
21
|
+
* @param key - The key to update
|
|
22
|
+
* @param delta - The amount to modify the key by
|
|
23
|
+
*/
|
|
24
|
+
update(key: K, delta: number): Promise<boolean>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets the current count.
|
|
28
|
+
* @param key - The key to get the count of
|
|
29
|
+
*/
|
|
30
|
+
get(key: K): number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns keys in the map in sorted order. Only returns keys that have been seen at least once.
|
|
34
|
+
* @param range - The range of keys to iterate over
|
|
35
|
+
*/
|
|
36
|
+
keys(range: Range<K>): IterableIterator<K>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns keys and their counts in the map sorted by the key. Only returns keys that have been seen at least once.
|
|
40
|
+
* @param range - The range of keys to iterate over
|
|
41
|
+
*/
|
|
42
|
+
entries(range: Range<K>): IterableIterator<[K, number]>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Key, Range } from './common.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A map backed by a persistent store.
|
|
5
|
+
*/
|
|
6
|
+
export interface AztecMap<K extends Key, V> {
|
|
7
|
+
/**
|
|
8
|
+
* Gets the value at the given key.
|
|
9
|
+
* @param key - The key to get the value from
|
|
10
|
+
*/
|
|
11
|
+
get(key: K): V | undefined;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if a key exists in the map.
|
|
15
|
+
* @param key - The key to check
|
|
16
|
+
* @returns True if the key exists, false otherwise
|
|
17
|
+
*/
|
|
18
|
+
has(key: K): boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sets the value at the given key.
|
|
22
|
+
* @param key - The key to set the value at
|
|
23
|
+
* @param val - The value to set
|
|
24
|
+
*/
|
|
25
|
+
set(key: K, val: V): Promise<boolean>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Atomically swap the value at the given key
|
|
29
|
+
* @param key - The key to swap the value at
|
|
30
|
+
* @param fn - The function to swap the value with
|
|
31
|
+
*/
|
|
32
|
+
swap(key: K, fn: (val: V | undefined) => V): Promise<boolean>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sets the value at the given key if it does not already exist.
|
|
36
|
+
* @param key - The key to set the value at
|
|
37
|
+
* @param val - The value to set
|
|
38
|
+
*/
|
|
39
|
+
setIfNotExists(key: K, val: V): Promise<boolean>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deletes the value at the given key.
|
|
43
|
+
* @param key - The key to delete the value at
|
|
44
|
+
*/
|
|
45
|
+
delete(key: K): Promise<boolean>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Iterates over the map's key-value entries in the key's natural order
|
|
49
|
+
* @param range - The range of keys to iterate over
|
|
50
|
+
*/
|
|
51
|
+
entries(range?: Range<K>): IterableIterator<[K, V]>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Iterates over the map's values in the key's natural order
|
|
55
|
+
* @param range - The range of keys to iterate over
|
|
56
|
+
*/
|
|
57
|
+
values(range?: Range<K>): IterableIterator<V>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Iterates over the map's keys in the key's natural order
|
|
61
|
+
* @param range - The range of keys to iterate over
|
|
62
|
+
*/
|
|
63
|
+
keys(range?: Range<K>): IterableIterator<K>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A map backed by a persistent store that can have multiple values for a single key.
|
|
68
|
+
*/
|
|
69
|
+
export interface AztecMultiMap<K extends Key, V> extends AztecMap<K, V> {
|
|
70
|
+
/**
|
|
71
|
+
* Gets all the values at the given key.
|
|
72
|
+
* @param key - The key to get the values from
|
|
73
|
+
*/
|
|
74
|
+
getValues(key: K): IterableIterator<V>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deletes a specific value at the given key.
|
|
78
|
+
* @param key - The key to delete the value at
|
|
79
|
+
* @param val - The value to delete
|
|
80
|
+
*/
|
|
81
|
+
deleteValue(key: K, val: V): Promise<void>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a singleton value in the database.
|
|
3
|
+
* Note: The singleton loses type info so it's recommended to serialize to buffer when storing it.
|
|
4
|
+
*/
|
|
5
|
+
export interface AztecSingleton<T> {
|
|
6
|
+
/**
|
|
7
|
+
* Gets the value.
|
|
8
|
+
*/
|
|
9
|
+
get(): T | undefined;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sets the value.
|
|
13
|
+
* @param val - The new value
|
|
14
|
+
*/
|
|
15
|
+
set(val: T): Promise<boolean>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Deletes the value.
|
|
19
|
+
*/
|
|
20
|
+
delete(): Promise<boolean>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { AztecArray } from './array.js';
|
|
2
|
+
import { Key } from './common.js';
|
|
3
|
+
import { AztecCounter } from './counter.js';
|
|
4
|
+
import { AztecMap, AztecMultiMap } from './map.js';
|
|
5
|
+
import { AztecSingleton } from './singleton.js';
|
|
6
|
+
|
|
7
|
+
/** A key-value store */
|
|
8
|
+
export interface AztecKVStore {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new map.
|
|
11
|
+
* @param name - The name of the map
|
|
12
|
+
* @returns The map
|
|
13
|
+
*/
|
|
14
|
+
openMap<K extends string | number, V>(name: string): AztecMap<K, V>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new multi-map.
|
|
18
|
+
* @param name - The name of the multi-map
|
|
19
|
+
* @returns The multi-map
|
|
20
|
+
*/
|
|
21
|
+
openMultiMap<K extends string | number, V>(name: string): AztecMultiMap<K, V>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new array.
|
|
25
|
+
* @param name - The name of the array
|
|
26
|
+
* @returns The array
|
|
27
|
+
*/
|
|
28
|
+
openArray<T>(name: string): AztecArray<T>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new singleton.
|
|
32
|
+
* @param name - The name of the singleton
|
|
33
|
+
* @returns The singleton
|
|
34
|
+
*/
|
|
35
|
+
openSingleton<T>(name: string): AztecSingleton<T>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new count map.
|
|
39
|
+
* @param name - name of the counter
|
|
40
|
+
*/
|
|
41
|
+
openCounter<K extends Key>(name: string): AztecCounter<K>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Starts a transaction. All calls to read/write data while in a transaction are queued and executed atomically.
|
|
45
|
+
* @param callback - The callback to execute in a transaction
|
|
46
|
+
*/
|
|
47
|
+
transaction<T extends Exclude<any, Promise<any>>>(callback: () => T): Promise<T>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Clears the store
|
|
51
|
+
*/
|
|
52
|
+
clear(): Promise<void>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Database, Key } from 'lmdb';
|
|
2
|
+
|
|
3
|
+
import { AztecArray } from '../interfaces/array.js';
|
|
4
|
+
import { LmdbAztecSingleton } from './singleton.js';
|
|
5
|
+
|
|
6
|
+
/** The shape of a key that stores a value in an array */
|
|
7
|
+
type ArrayIndexSlot = ['array', string, 'slot', number];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An persistent array backed by LMDB.
|
|
11
|
+
*/
|
|
12
|
+
export class LmdbAztecArray<T> implements AztecArray<T> {
|
|
13
|
+
#db: Database<T, ArrayIndexSlot>;
|
|
14
|
+
#name: string;
|
|
15
|
+
#length: LmdbAztecSingleton<number>;
|
|
16
|
+
|
|
17
|
+
constructor(db: Database<unknown, Key>, arrName: string) {
|
|
18
|
+
this.#name = arrName;
|
|
19
|
+
this.#length = new LmdbAztecSingleton(db, `${arrName}:meta:length`);
|
|
20
|
+
this.#db = db as Database<T, ArrayIndexSlot>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get length(): number {
|
|
24
|
+
return this.#length.get() ?? 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
push(...vals: T[]): Promise<number> {
|
|
28
|
+
return this.#db.childTransaction(() => {
|
|
29
|
+
let length = this.length;
|
|
30
|
+
for (const val of vals) {
|
|
31
|
+
void this.#db.put(this.#slot(length), val);
|
|
32
|
+
length += 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void this.#length.set(length);
|
|
36
|
+
|
|
37
|
+
return length;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pop(): Promise<T | undefined> {
|
|
42
|
+
return this.#db.childTransaction(() => {
|
|
43
|
+
const length = this.length;
|
|
44
|
+
if (length === 0) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const slot = this.#slot(length - 1);
|
|
49
|
+
const val = this.#db.get(slot) as T;
|
|
50
|
+
|
|
51
|
+
void this.#db.remove(slot);
|
|
52
|
+
void this.#length.set(length - 1);
|
|
53
|
+
|
|
54
|
+
return val;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
at(index: number): T | undefined {
|
|
59
|
+
if (index < 0) {
|
|
60
|
+
index = this.length + index;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// the Array API only accepts indexes in the range [-this.length, this.length)
|
|
64
|
+
// so if after normalizing the index is still out of range, return undefined
|
|
65
|
+
if (index < 0 || index >= this.length) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this.#db.get(this.#slot(index));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setAt(index: number, val: T): Promise<boolean> {
|
|
73
|
+
if (index < 0) {
|
|
74
|
+
index = this.length + index;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (index < 0 || index >= this.length) {
|
|
78
|
+
return Promise.resolve(false);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this.#db.put(this.#slot(index), val);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
*entries(): IterableIterator<[number, T]> {
|
|
85
|
+
const values = this.#db.getRange({
|
|
86
|
+
start: this.#slot(0),
|
|
87
|
+
limit: this.length,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
for (const { key, value } of values) {
|
|
91
|
+
const index = key[3];
|
|
92
|
+
yield [index, value];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
*values(): IterableIterator<T> {
|
|
97
|
+
for (const [_, value] of this.entries()) {
|
|
98
|
+
yield value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[Symbol.iterator](): IterableIterator<T> {
|
|
103
|
+
return this.values();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#slot(index: number): ArrayIndexSlot {
|
|
107
|
+
return ['array', this.#name, 'slot', index];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Key as BaseKey, Database } from 'lmdb';
|
|
2
|
+
|
|
3
|
+
import { Key, Range } from '../interfaces/common.js';
|
|
4
|
+
import { AztecCounter } from '../interfaces/counter.js';
|
|
5
|
+
import { LmdbAztecMap } from './map.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A counter implementation backed by LMDB
|
|
9
|
+
*/
|
|
10
|
+
export class LmdbAztecCounter<K extends Key> implements AztecCounter<K> {
|
|
11
|
+
#db: Database;
|
|
12
|
+
#name: string;
|
|
13
|
+
#map: LmdbAztecMap<K, number>;
|
|
14
|
+
|
|
15
|
+
constructor(db: Database<unknown, BaseKey>, name: string) {
|
|
16
|
+
this.#db = db;
|
|
17
|
+
this.#name = name;
|
|
18
|
+
this.#map = new LmdbAztecMap(db, name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set(key: K, value: number): Promise<boolean> {
|
|
22
|
+
return this.#map.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
update(key: K, delta = 1): Promise<boolean> {
|
|
26
|
+
return this.#db.childTransaction(() => {
|
|
27
|
+
const current = this.#map.get(key) ?? 0;
|
|
28
|
+
const next = current + delta;
|
|
29
|
+
|
|
30
|
+
if (next < 0) {
|
|
31
|
+
throw new Error(`Cannot update ${key} in counter ${this.#name} below zero`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (next === 0) {
|
|
35
|
+
void this.#map.delete(key);
|
|
36
|
+
} else {
|
|
37
|
+
// store the key inside the entry because LMDB might return an internal representation
|
|
38
|
+
// of the key when iterating over the database
|
|
39
|
+
void this.#map.set(key, next);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get(key: K): number {
|
|
47
|
+
return this.#map.get(key) ?? 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
entries(range: Range<K> = {}): IterableIterator<[K, number]> {
|
|
51
|
+
return this.#map.entries(range);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
keys(range: Range<K> = {}): IterableIterator<K> {
|
|
55
|
+
return this.#map.keys(range);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AztecLmdbStore } from './store.js';
|
package/src/lmdb/map.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Database, RangeOptions } from 'lmdb';
|
|
2
|
+
|
|
3
|
+
import { Key, Range } from '../interfaces/common.js';
|
|
4
|
+
import { AztecMultiMap } from '../interfaces/map.js';
|
|
5
|
+
|
|
6
|
+
/** The slot where a key-value entry would be stored */
|
|
7
|
+
type MapValueSlot<K extends Key | Buffer> = ['map', string, 'slot', K];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A map backed by LMDB.
|
|
11
|
+
*/
|
|
12
|
+
export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V> {
|
|
13
|
+
protected db: Database<[K, V], MapValueSlot<K>>;
|
|
14
|
+
protected name: string;
|
|
15
|
+
|
|
16
|
+
#startSentinel: MapValueSlot<Buffer>;
|
|
17
|
+
#endSentinel: MapValueSlot<Buffer>;
|
|
18
|
+
|
|
19
|
+
constructor(rootDb: Database, mapName: string) {
|
|
20
|
+
this.name = mapName;
|
|
21
|
+
this.db = rootDb as Database<[K, V], MapValueSlot<K>>;
|
|
22
|
+
|
|
23
|
+
// sentinels are used to define the start and end of the map
|
|
24
|
+
// with LMDB's key encoding, no _primitive value_ can be "less than" an empty buffer or greater than Byte 255
|
|
25
|
+
// these will be used later to answer range queries
|
|
26
|
+
this.#startSentinel = ['map', this.name, 'slot', Buffer.from([])];
|
|
27
|
+
this.#endSentinel = ['map', this.name, 'slot', Buffer.from([255])];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
close(): Promise<void> {
|
|
31
|
+
return this.db.close();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get(key: K): V | undefined {
|
|
35
|
+
return this.db.get(this.#slot(key))?.[1];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
*getValues(key: K): IterableIterator<V> {
|
|
39
|
+
const values = this.db.getValues(this.#slot(key));
|
|
40
|
+
for (const value of values) {
|
|
41
|
+
yield value?.[1];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
has(key: K): boolean {
|
|
46
|
+
return this.db.doesExist(this.#slot(key));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set(key: K, val: V): Promise<boolean> {
|
|
50
|
+
return this.db.put(this.#slot(key), [key, val]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
swap(key: K, fn: (val: V | undefined) => V): Promise<boolean> {
|
|
54
|
+
return this.db.childTransaction(() => {
|
|
55
|
+
const slot = this.#slot(key);
|
|
56
|
+
const entry = this.db.get(slot);
|
|
57
|
+
void this.db.put(slot, [key, fn(entry?.[1])]);
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setIfNotExists(key: K, val: V): Promise<boolean> {
|
|
64
|
+
const slot = this.#slot(key);
|
|
65
|
+
return this.db.ifNoExists(slot, () => {
|
|
66
|
+
void this.db.put(slot, [key, val]);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
delete(key: K): Promise<boolean> {
|
|
71
|
+
return this.db.remove(this.#slot(key));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async deleteValue(key: K, val: V): Promise<void> {
|
|
75
|
+
await this.db.remove(this.#slot(key), [key, val]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
*entries(range: Range<K> = {}): IterableIterator<[K, V]> {
|
|
79
|
+
const { reverse = false, limit } = range;
|
|
80
|
+
// LMDB has a quirk where it expects start > end when reverse=true
|
|
81
|
+
// in that case, we need to swap the start and end sentinels
|
|
82
|
+
const start = reverse
|
|
83
|
+
? range.end
|
|
84
|
+
? this.#slot(range.end)
|
|
85
|
+
: this.#endSentinel
|
|
86
|
+
: range.start
|
|
87
|
+
? this.#slot(range.start)
|
|
88
|
+
: this.#startSentinel;
|
|
89
|
+
|
|
90
|
+
const end = reverse
|
|
91
|
+
? range.start
|
|
92
|
+
? this.#slot(range.start)
|
|
93
|
+
: this.#startSentinel
|
|
94
|
+
: range.end
|
|
95
|
+
? this.#slot(range.end)
|
|
96
|
+
: this.#endSentinel;
|
|
97
|
+
|
|
98
|
+
const lmdbRange: RangeOptions = {
|
|
99
|
+
start,
|
|
100
|
+
end,
|
|
101
|
+
reverse,
|
|
102
|
+
limit,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const iterator = this.db.getRange(lmdbRange);
|
|
106
|
+
|
|
107
|
+
for (const {
|
|
108
|
+
value: [key, value],
|
|
109
|
+
} of iterator) {
|
|
110
|
+
yield [key, value];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
*values(range: Range<K> = {}): IterableIterator<V> {
|
|
115
|
+
for (const [_, value] of this.entries(range)) {
|
|
116
|
+
yield value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
*keys(range: Range<K> = {}): IterableIterator<K> {
|
|
121
|
+
for (const [key, _] of this.entries(range)) {
|
|
122
|
+
yield key;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#slot(key: K): MapValueSlot<K> {
|
|
127
|
+
return ['map', this.name, 'slot', key];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Database, Key } from 'lmdb';
|
|
2
|
+
|
|
3
|
+
import { AztecSingleton } from '../interfaces/singleton.js';
|
|
4
|
+
|
|
5
|
+
/** The slot where this singleton will store its value */
|
|
6
|
+
type ValueSlot = ['singleton', string, 'value'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stores a single value in LMDB.
|
|
10
|
+
*/
|
|
11
|
+
export class LmdbAztecSingleton<T> implements AztecSingleton<T> {
|
|
12
|
+
#db: Database<T, ValueSlot>;
|
|
13
|
+
#slot: ValueSlot;
|
|
14
|
+
|
|
15
|
+
constructor(db: Database<unknown, Key>, name: string) {
|
|
16
|
+
this.#db = db as Database<T, ValueSlot>;
|
|
17
|
+
this.#slot = ['singleton', name, 'value'];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(): T | undefined {
|
|
21
|
+
return this.#db.get(this.#slot);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set(val: T): Promise<boolean> {
|
|
25
|
+
return this.#db.put(this.#slot, val);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
delete(): Promise<boolean> {
|
|
29
|
+
return this.#db.remove(this.#slot);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { Database, Key, RootDatabase, open } from 'lmdb';
|
|
4
|
+
|
|
5
|
+
import { AztecArray } from '../interfaces/array.js';
|
|
6
|
+
import { AztecCounter } from '../interfaces/counter.js';
|
|
7
|
+
import { AztecMap, AztecMultiMap } from '../interfaces/map.js';
|
|
8
|
+
import { AztecSingleton } from '../interfaces/singleton.js';
|
|
9
|
+
import { AztecKVStore } from '../interfaces/store.js';
|
|
10
|
+
import { LmdbAztecArray } from './array.js';
|
|
11
|
+
import { LmdbAztecCounter } from './counter.js';
|
|
12
|
+
import { LmdbAztecMap } from './map.js';
|
|
13
|
+
import { LmdbAztecSingleton } from './singleton.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A key-value store backed by LMDB.
|
|
17
|
+
*/
|
|
18
|
+
export class AztecLmdbStore implements AztecKVStore {
|
|
19
|
+
#rootDb: RootDatabase;
|
|
20
|
+
#data: Database<unknown, Key>;
|
|
21
|
+
#multiMapData: Database<unknown, Key>;
|
|
22
|
+
|
|
23
|
+
constructor(rootDb: RootDatabase) {
|
|
24
|
+
this.#rootDb = rootDb;
|
|
25
|
+
|
|
26
|
+
// big bucket to store all the data
|
|
27
|
+
this.#data = rootDb.openDB('data', {
|
|
28
|
+
encoding: 'msgpack',
|
|
29
|
+
keyEncoding: 'ordered-binary',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.#multiMapData = rootDb.openDB('data_dup_sort', {
|
|
33
|
+
encoding: 'ordered-binary',
|
|
34
|
+
keyEncoding: 'ordered-binary',
|
|
35
|
+
dupSort: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a new AztecKVStore backed by LMDB. The path to the database is optional. If not provided,
|
|
41
|
+
* the database will be stored in a temporary location and be deleted when the process exists.
|
|
42
|
+
*
|
|
43
|
+
* The `rollupAddress` passed is checked against what is stored in the database. If they do not match,
|
|
44
|
+
* the database is cleared before returning the store. This way data is not accidentally shared between
|
|
45
|
+
* different rollup instances.
|
|
46
|
+
*
|
|
47
|
+
* @param path - A path on the disk to store the database. Optional
|
|
48
|
+
* @param log - A logger to use. Optional
|
|
49
|
+
* @returns The store
|
|
50
|
+
*/
|
|
51
|
+
static open(path?: string, log = createDebugLogger('aztec:kv-store:lmdb')): AztecLmdbStore {
|
|
52
|
+
log.info(`Opening LMDB database at ${path || 'temporary location'}`);
|
|
53
|
+
const rootDb = open({ path });
|
|
54
|
+
return new AztecLmdbStore(rootDb);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a new AztecMap in the store.
|
|
59
|
+
* @param name - Name of the map
|
|
60
|
+
* @returns A new AztecMap
|
|
61
|
+
*/
|
|
62
|
+
openMap<K extends string | number, V>(name: string): AztecMap<K, V> {
|
|
63
|
+
return new LmdbAztecMap(this.#data, name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a new AztecMultiMap in the store. A multi-map stores multiple values for a single key automatically.
|
|
68
|
+
* @param name - Name of the map
|
|
69
|
+
* @returns A new AztecMultiMap
|
|
70
|
+
*/
|
|
71
|
+
openMultiMap<K extends string | number, V>(name: string): AztecMultiMap<K, V> {
|
|
72
|
+
return new LmdbAztecMap(this.#multiMapData, name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
openCounter<K extends string | number | Array<string | number>>(name: string): AztecCounter<K> {
|
|
76
|
+
return new LmdbAztecCounter(this.#data, name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new AztecArray in the store.
|
|
81
|
+
* @param name - Name of the array
|
|
82
|
+
* @returns A new AztecArray
|
|
83
|
+
*/
|
|
84
|
+
openArray<T>(name: string): AztecArray<T> {
|
|
85
|
+
return new LmdbAztecArray(this.#data, name);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Creates a new AztecSingleton in the store.
|
|
90
|
+
* @param name - Name of the singleton
|
|
91
|
+
* @returns A new AztecSingleton
|
|
92
|
+
*/
|
|
93
|
+
openSingleton<T>(name: string): AztecSingleton<T> {
|
|
94
|
+
return new LmdbAztecSingleton(this.#data, name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Runs a callback in a transaction.
|
|
99
|
+
* @param callback - Function to execute in a transaction
|
|
100
|
+
* @returns A promise that resolves to the return value of the callback
|
|
101
|
+
*/
|
|
102
|
+
transaction<T>(callback: () => T): Promise<T> {
|
|
103
|
+
return this.#rootDb.transaction(callback);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Clears the store
|
|
108
|
+
*/
|
|
109
|
+
async clear() {
|
|
110
|
+
await this.#rootDb.clearAsync();
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
|
+
import { Logger } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
import { AztecKVStore } from './interfaces/store.js';
|
|
5
|
+
import { AztecLmdbStore } from './lmdb/store.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Clears the store if the rollup address does not match the one stored in the database.
|
|
9
|
+
* This is to prevent data from being accidentally shared between different rollup instances.
|
|
10
|
+
* @param store - The store to check
|
|
11
|
+
* @param rollupAddress - The ETH address of the rollup contract
|
|
12
|
+
* @returns A promise that resolves when the store is cleared, or rejects if the rollup address does not match
|
|
13
|
+
*/
|
|
14
|
+
export async function initStoreForRollup<T extends AztecKVStore>(
|
|
15
|
+
store: T,
|
|
16
|
+
rollupAddress: EthAddress,
|
|
17
|
+
log?: Logger,
|
|
18
|
+
): Promise<T> {
|
|
19
|
+
const rollupAddressValue = store.openSingleton<ReturnType<EthAddress['toString']>>('rollupAddress');
|
|
20
|
+
const rollupAddressString = rollupAddress.toString();
|
|
21
|
+
const storedRollupAddressString = rollupAddressValue.get();
|
|
22
|
+
|
|
23
|
+
if (typeof storedRollupAddressString !== 'undefined' && storedRollupAddressString !== rollupAddressString) {
|
|
24
|
+
log?.warn(
|
|
25
|
+
`Rollup address mismatch: expected ${rollupAddress}, found ${rollupAddressValue}. Clearing entire database...`,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
await store.clear();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await rollupAddressValue.set(rollupAddressString);
|
|
32
|
+
return store;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Opens a temporary store for testing purposes.
|
|
37
|
+
* @returns A new store
|
|
38
|
+
*/
|
|
39
|
+
export function openTmpStore(): AztecKVStore {
|
|
40
|
+
return AztecLmdbStore.open();
|
|
41
|
+
}
|