@aztec/kv-store 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: 'msgpack',
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbG1kYi9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRTFELE9BQU8sRUFBK0IsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBT3pELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDNUMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDeEMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFcEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUt6QixZQUFZLE1BQW9CO1FBSmhDLHlDQUFzQjtRQUN0Qix1Q0FBOEI7UUFDOUIsK0NBQXNDO1FBR3BDLHVCQUFBLElBQUksMEJBQVcsTUFBTSxNQUFBLENBQUM7UUFFdEIsbUNBQW1DO1FBQ25DLHVCQUFBLElBQUksd0JBQVMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7WUFDakMsUUFBUSxFQUFFLFNBQVM7WUFDbkIsV0FBVyxFQUFFLGdCQUFnQjtTQUM5QixDQUFDLE1BQUEsQ0FBQztRQUVILHVCQUFBLElBQUksZ0NBQWlCLE1BQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFO1lBQ2xELFFBQVEsRUFBRSxTQUFTO1lBQ25CLFdBQVcsRUFBRSxnQkFBZ0I7WUFDN0IsT0FBTyxFQUFFLElBQUk7U0FDZCxDQUFDLE1BQUEsQ0FBQztJQUNMLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7T0FXRztJQUNILE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBYSxFQUFFLEdBQUcsR0FBRyxpQkFBaUIsQ0FBQyxxQkFBcUIsQ0FBQztRQUN2RSxHQUFHLENBQUMsSUFBSSxDQUFDLDRCQUE0QixJQUFJLElBQUksb0JBQW9CLEVBQUUsQ0FBQyxDQUFDO1FBQ3JFLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDOUIsT0FBTyxJQUFJLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNwQyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILE9BQU8sQ0FBK0IsSUFBWTtRQUNoRCxPQUFPLElBQUksWUFBWSxDQUFDLHVCQUFBLElBQUksNEJBQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILFlBQVksQ0FBK0IsSUFBWTtRQUNyRCxPQUFPLElBQUksWUFBWSxDQUFDLHVCQUFBLElBQUksb0NBQWMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNwRCxDQUFDO0lBRUQsV0FBVyxDQUFxRCxJQUFZO1FBQzFFLE9BQU8sSUFBSSxnQkFBZ0IsQ0FBQyx1QkFBQSxJQUFJLDRCQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxTQUFTLENBQUksSUFBWTtRQUN2QixPQUFPLElBQUksY0FBYyxDQUFDLHVCQUFBLElBQUksNEJBQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUM5QyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILGFBQWEsQ0FBSSxJQUFZO1FBQzNCLE9BQU8sSUFBSSxrQkFBa0IsQ0FBQyx1QkFBQSxJQUFJLDRCQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxXQUFXLENBQUksUUFBaUI7UUFDOUIsT0FBTyx1QkFBQSxJQUFJLDhCQUFRLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzVDLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxLQUFLO1FBQ1QsTUFBTSx1QkFBQSxJQUFJLDhCQUFRLENBQUMsVUFBVSxFQUFFLENBQUM7SUFDbEMsQ0FBQztDQUNGIn0=
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.23.0",
3
+ "version": "0.24.0",
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.23.0",
32
+ "@aztec/foundation": "0.24.0",
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,6 @@
1
+ export * from './array.js';
2
+ export * from './map.js';
3
+ export * from './counter.js';
4
+ export * from './singleton.js';
5
+ export * from './store.js';
6
+ export { Range } from './common.js';
@@ -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';
@@ -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
+ }