@aztec/kv-store 0.0.0-test.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.
Files changed (166) hide show
  1. package/README.md +10 -0
  2. package/dest/config.d.ts +17 -0
  3. package/dest/config.d.ts.map +1 -0
  4. package/dest/config.js +24 -0
  5. package/dest/indexeddb/array.d.ts +21 -0
  6. package/dest/indexeddb/array.d.ts.map +1 -0
  7. package/dest/indexeddb/array.js +104 -0
  8. package/dest/indexeddb/index.d.ts +7 -0
  9. package/dest/indexeddb/index.d.ts.map +1 -0
  10. package/dest/indexeddb/index.js +19 -0
  11. package/dest/indexeddb/map.d.ts +26 -0
  12. package/dest/indexeddb/map.d.ts.map +1 -0
  13. package/dest/indexeddb/map.js +133 -0
  14. package/dest/indexeddb/set.d.ts +17 -0
  15. package/dest/indexeddb/set.d.ts.map +1 -0
  16. package/dest/indexeddb/set.js +24 -0
  17. package/dest/indexeddb/singleton.d.ts +16 -0
  18. package/dest/indexeddb/singleton.d.ts.map +1 -0
  19. package/dest/indexeddb/singleton.js +37 -0
  20. package/dest/indexeddb/store.d.ts +97 -0
  21. package/dest/indexeddb/store.d.ts.map +1 -0
  22. package/dest/indexeddb/store.js +164 -0
  23. package/dest/interfaces/array.d.ts +80 -0
  24. package/dest/interfaces/array.d.ts.map +1 -0
  25. package/dest/interfaces/array.js +3 -0
  26. package/dest/interfaces/array_test_suite.d.ts +3 -0
  27. package/dest/interfaces/array_test_suite.d.ts.map +1 -0
  28. package/dest/interfaces/array_test_suite.js +104 -0
  29. package/dest/interfaces/common.d.ts +23 -0
  30. package/dest/interfaces/common.d.ts.map +1 -0
  31. package/dest/interfaces/common.js +3 -0
  32. package/dest/interfaces/counter.d.ts +59 -0
  33. package/dest/interfaces/counter.d.ts.map +1 -0
  34. package/dest/interfaces/counter.js +1 -0
  35. package/dest/interfaces/index.d.ts +8 -0
  36. package/dest/interfaces/index.d.ts.map +1 -0
  37. package/dest/interfaces/index.js +6 -0
  38. package/dest/interfaces/map.d.ts +134 -0
  39. package/dest/interfaces/map.d.ts.map +1 -0
  40. package/dest/interfaces/map.js +3 -0
  41. package/dest/interfaces/map_test_suite.d.ts +3 -0
  42. package/dest/interfaces/map_test_suite.d.ts.map +1 -0
  43. package/dest/interfaces/map_test_suite.js +151 -0
  44. package/dest/interfaces/set.d.ts +44 -0
  45. package/dest/interfaces/set.d.ts.map +1 -0
  46. package/dest/interfaces/set.js +1 -0
  47. package/dest/interfaces/set_test_suite.d.ts +3 -0
  48. package/dest/interfaces/set_test_suite.d.ts.map +1 -0
  49. package/dest/interfaces/set_test_suite.js +96 -0
  50. package/dest/interfaces/singleton.d.ts +29 -0
  51. package/dest/interfaces/singleton.d.ts.map +1 -0
  52. package/dest/interfaces/singleton.js +4 -0
  53. package/dest/interfaces/singleton_test_suite.d.ts +3 -0
  54. package/dest/interfaces/singleton_test_suite.d.ts.map +1 -0
  55. package/dest/interfaces/singleton_test_suite.js +30 -0
  56. package/dest/interfaces/store.d.ts +145 -0
  57. package/dest/interfaces/store.d.ts.map +1 -0
  58. package/dest/interfaces/store.js +1 -0
  59. package/dest/interfaces/store_test_suite.d.ts +3 -0
  60. package/dest/interfaces/store_test_suite.d.ts.map +1 -0
  61. package/dest/interfaces/store_test_suite.js +37 -0
  62. package/dest/interfaces/utils.d.ts +16 -0
  63. package/dest/interfaces/utils.d.ts.map +1 -0
  64. package/dest/interfaces/utils.js +16 -0
  65. package/dest/lmdb/array.d.ts +23 -0
  66. package/dest/lmdb/array.d.ts.map +1 -0
  67. package/dest/lmdb/array.js +117 -0
  68. package/dest/lmdb/counter.d.ts +19 -0
  69. package/dest/lmdb/counter.d.ts.map +1 -0
  70. package/dest/lmdb/counter.js +50 -0
  71. package/dest/lmdb/index.d.ts +12 -0
  72. package/dest/lmdb/index.d.ts.map +1 -0
  73. package/dest/lmdb/index.js +25 -0
  74. package/dest/lmdb/map.d.ts +52 -0
  75. package/dest/lmdb/map.d.ts.map +1 -0
  76. package/dest/lmdb/map.js +222 -0
  77. package/dest/lmdb/set.d.ts +18 -0
  78. package/dest/lmdb/set.d.ts.map +1 -0
  79. package/dest/lmdb/set.js +32 -0
  80. package/dest/lmdb/singleton.d.ts +14 -0
  81. package/dest/lmdb/singleton.d.ts.map +1 -0
  82. package/dest/lmdb/singleton.js +26 -0
  83. package/dest/lmdb/store.d.ts +110 -0
  84. package/dest/lmdb/store.d.ts.map +1 -0
  85. package/dest/lmdb/store.js +216 -0
  86. package/dest/lmdb-v2/array.d.ts +18 -0
  87. package/dest/lmdb-v2/array.d.ts.map +1 -0
  88. package/dest/lmdb-v2/array.js +101 -0
  89. package/dest/lmdb-v2/factory.d.ts +12 -0
  90. package/dest/lmdb-v2/factory.d.ts.map +1 -0
  91. package/dest/lmdb-v2/factory.js +60 -0
  92. package/dest/lmdb-v2/index.d.ts +3 -0
  93. package/dest/lmdb-v2/index.d.ts.map +1 -0
  94. package/dest/lmdb-v2/index.js +2 -0
  95. package/dest/lmdb-v2/map.d.ts +86 -0
  96. package/dest/lmdb-v2/map.d.ts.map +1 -0
  97. package/dest/lmdb-v2/map.js +193 -0
  98. package/dest/lmdb-v2/message.d.ts +112 -0
  99. package/dest/lmdb-v2/message.d.ts.map +1 -0
  100. package/dest/lmdb-v2/message.js +18 -0
  101. package/dest/lmdb-v2/read_transaction.d.ts +14 -0
  102. package/dest/lmdb-v2/read_transaction.d.ts.map +1 -0
  103. package/dest/lmdb-v2/read_transaction.js +101 -0
  104. package/dest/lmdb-v2/singleton.d.ts +12 -0
  105. package/dest/lmdb-v2/singleton.d.ts.map +1 -0
  106. package/dest/lmdb-v2/singleton.js +31 -0
  107. package/dest/lmdb-v2/store.d.ts +43 -0
  108. package/dest/lmdb-v2/store.d.ts.map +1 -0
  109. package/dest/lmdb-v2/store.js +181 -0
  110. package/dest/lmdb-v2/utils.d.ts +19 -0
  111. package/dest/lmdb-v2/utils.d.ts.map +1 -0
  112. package/dest/lmdb-v2/utils.js +126 -0
  113. package/dest/lmdb-v2/write_transaction.d.ts +19 -0
  114. package/dest/lmdb-v2/write_transaction.d.ts.map +1 -0
  115. package/dest/lmdb-v2/write_transaction.js +254 -0
  116. package/dest/stores/index.d.ts +2 -0
  117. package/dest/stores/index.d.ts.map +1 -0
  118. package/dest/stores/index.js +1 -0
  119. package/dest/stores/l2_tips_store.d.ts +13 -0
  120. package/dest/stores/l2_tips_store.d.ts.map +1 -0
  121. package/dest/stores/l2_tips_store.js +65 -0
  122. package/dest/utils.d.ts +12 -0
  123. package/dest/utils.d.ts.map +1 -0
  124. package/dest/utils.js +24 -0
  125. package/package.json +99 -0
  126. package/src/config.ts +34 -0
  127. package/src/indexeddb/array.ts +118 -0
  128. package/src/indexeddb/index.ts +29 -0
  129. package/src/indexeddb/map.ts +142 -0
  130. package/src/indexeddb/set.ts +37 -0
  131. package/src/indexeddb/singleton.ts +49 -0
  132. package/src/indexeddb/store.ts +197 -0
  133. package/src/interfaces/array.ts +90 -0
  134. package/src/interfaces/array_test_suite.ts +130 -0
  135. package/src/interfaces/common.ts +20 -0
  136. package/src/interfaces/counter.ts +65 -0
  137. package/src/interfaces/index.ts +7 -0
  138. package/src/interfaces/map.ts +151 -0
  139. package/src/interfaces/map_test_suite.ts +143 -0
  140. package/src/interfaces/set.ts +48 -0
  141. package/src/interfaces/set_test_suite.ts +81 -0
  142. package/src/interfaces/singleton.ts +29 -0
  143. package/src/interfaces/singleton_test_suite.ts +46 -0
  144. package/src/interfaces/store.ts +177 -0
  145. package/src/interfaces/store_test_suite.ts +56 -0
  146. package/src/interfaces/utils.ts +21 -0
  147. package/src/lmdb/array.ts +139 -0
  148. package/src/lmdb/counter.ts +67 -0
  149. package/src/lmdb/index.ts +37 -0
  150. package/src/lmdb/map.ts +248 -0
  151. package/src/lmdb/set.ts +45 -0
  152. package/src/lmdb/singleton.ts +35 -0
  153. package/src/lmdb/store.ts +263 -0
  154. package/src/lmdb-v2/array.ts +115 -0
  155. package/src/lmdb-v2/factory.ts +98 -0
  156. package/src/lmdb-v2/index.ts +2 -0
  157. package/src/lmdb-v2/map.ts +233 -0
  158. package/src/lmdb-v2/message.ts +146 -0
  159. package/src/lmdb-v2/read_transaction.ts +116 -0
  160. package/src/lmdb-v2/singleton.ts +34 -0
  161. package/src/lmdb-v2/store.ts +237 -0
  162. package/src/lmdb-v2/utils.ts +150 -0
  163. package/src/lmdb-v2/write_transaction.ts +314 -0
  164. package/src/stores/index.ts +1 -0
  165. package/src/stores/l2_tips_store.ts +70 -0
  166. package/src/utils.ts +40 -0
@@ -0,0 +1,139 @@
1
+ import type { Database, Key } from 'lmdb';
2
+
3
+ import type { AztecArray, AztecAsyncArray } 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>, AztecAsyncArray<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
+ lengthAsync(): Promise<number> {
28
+ return Promise.resolve(this.length);
29
+ }
30
+
31
+ push(...vals: T[]): Promise<number> {
32
+ return this.#db.childTransaction(() => {
33
+ let length = this.length;
34
+ for (const val of vals) {
35
+ void this.#db.put(this.#slot(length), val);
36
+ length += 1;
37
+ }
38
+
39
+ void this.#length.set(length);
40
+
41
+ return length;
42
+ });
43
+ }
44
+
45
+ pop(): Promise<T | undefined> {
46
+ return this.#db.childTransaction(() => {
47
+ const length = this.length;
48
+ if (length === 0) {
49
+ return undefined;
50
+ }
51
+
52
+ const slot = this.#slot(length - 1);
53
+ const val = this.#db.get(slot) as T;
54
+
55
+ void this.#db.remove(slot);
56
+ void this.#length.set(length - 1);
57
+
58
+ return val;
59
+ });
60
+ }
61
+
62
+ at(index: number): T | undefined {
63
+ if (index < 0) {
64
+ index = this.length + index;
65
+ }
66
+
67
+ // the Array API only accepts indexes in the range [-this.length, this.length)
68
+ // so if after normalizing the index is still out of range, return undefined
69
+ if (index < 0 || index >= this.length) {
70
+ return undefined;
71
+ }
72
+
73
+ return this.#db.get(this.#slot(index));
74
+ }
75
+
76
+ atAsync(index: number): Promise<T | undefined> {
77
+ return Promise.resolve(this.at(index));
78
+ }
79
+
80
+ setAt(index: number, val: T): Promise<boolean> {
81
+ if (index < 0) {
82
+ index = this.length + index;
83
+ }
84
+
85
+ if (index < 0 || index >= this.length) {
86
+ return Promise.resolve(false);
87
+ }
88
+
89
+ return this.#db.put(this.#slot(index), val);
90
+ }
91
+
92
+ *entries(): IterableIterator<[number, T]> {
93
+ const transaction = this.#db.useReadTransaction();
94
+ try {
95
+ const values = this.#db.getRange({
96
+ start: this.#slot(0),
97
+ limit: this.length,
98
+ transaction,
99
+ });
100
+
101
+ for (const { key, value } of values) {
102
+ const index = key[3];
103
+ yield [index, value];
104
+ }
105
+ } finally {
106
+ transaction.done();
107
+ }
108
+ }
109
+
110
+ async *entriesAsync(): AsyncIterableIterator<[number, T]> {
111
+ for (const [key, value] of this.entries()) {
112
+ yield [key, value];
113
+ }
114
+ }
115
+
116
+ *values(): IterableIterator<T> {
117
+ for (const [_, value] of this.entries()) {
118
+ yield value;
119
+ }
120
+ }
121
+
122
+ async *valuesAsync(): AsyncIterableIterator<T> {
123
+ for (const [_, value] of this.entries()) {
124
+ yield value;
125
+ }
126
+ }
127
+
128
+ [Symbol.iterator](): IterableIterator<T> {
129
+ return this.values();
130
+ }
131
+
132
+ [Symbol.asyncIterator](): AsyncIterableIterator<T> {
133
+ return this.valuesAsync();
134
+ }
135
+
136
+ #slot(index: number): ArrayIndexSlot {
137
+ return ['array', this.#name, 'slot', index];
138
+ }
139
+ }
@@ -0,0 +1,67 @@
1
+ import type { Key as BaseKey, Database } from 'lmdb';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncCounter, 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>, AztecAsyncCounter<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
+ async set(key: K, value: number): Promise<void> {
22
+ await this.#map.set(key, value);
23
+ }
24
+
25
+ update(key: K, delta = 1): Promise<void> {
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
+ }
43
+
44
+ get(key: K): number {
45
+ return this.#map.get(key) ?? 0;
46
+ }
47
+
48
+ getAsync(key: K): Promise<number> {
49
+ return Promise.resolve(this.get(key));
50
+ }
51
+
52
+ entries(range: Range<K> = {}): IterableIterator<[K, number]> {
53
+ return this.#map.entries(range);
54
+ }
55
+
56
+ async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<[K, number]> {
57
+ yield* this.entries(range);
58
+ }
59
+
60
+ keys(range: Range<K> = {}): IterableIterator<K> {
61
+ return this.#map.keys(range);
62
+ }
63
+
64
+ async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
65
+ yield* this.keys(range);
66
+ }
67
+ }
@@ -0,0 +1,37 @@
1
+ import { type Logger, createLogger } from '@aztec/foundation/log';
2
+
3
+ import { join } from 'path';
4
+
5
+ import type { DataStoreConfig } from '../config.js';
6
+ import { initStoreForRollup } from '../utils.js';
7
+ import { AztecLmdbStore } from './store.js';
8
+
9
+ export { AztecLmdbStore } from './store.js';
10
+
11
+ export function createStore(name: string, config: DataStoreConfig, log: Logger = createLogger('kv-store')) {
12
+ let { dataDirectory } = config;
13
+ if (typeof dataDirectory !== 'undefined') {
14
+ dataDirectory = join(dataDirectory, name);
15
+ }
16
+
17
+ log.info(
18
+ dataDirectory
19
+ ? `Creating ${name} data store at directory ${dataDirectory} with map size ${config.dataStoreMapSizeKB} KB`
20
+ : `Creating ${name} ephemeral data store with map size ${config.dataStoreMapSizeKB} KB`,
21
+ );
22
+
23
+ const store = AztecLmdbStore.open(dataDirectory, config.dataStoreMapSizeKB, false);
24
+ if (config.l1Contracts?.rollupAddress) {
25
+ return initStoreForRollup(store, config.l1Contracts.rollupAddress, log);
26
+ }
27
+ return store;
28
+ }
29
+ /**
30
+ * Opens a temporary store for testing purposes.
31
+ * @param ephemeral - true if the store should only exist in memory and not automatically be flushed to disk. Optional
32
+ * @returns A new store
33
+ */
34
+ export function openTmpStore(ephemeral: boolean = false): AztecLmdbStore {
35
+ const mapSize = 1024 * 1024 * 10; // 10 GB map size
36
+ return AztecLmdbStore.open(undefined, mapSize, ephemeral);
37
+ }
@@ -0,0 +1,248 @@
1
+ import type { Database, RangeOptions } from 'lmdb';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncMultiMap, AztecMapWithSize, 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>, AztecAsyncMultiMap<K, V> {
13
+ protected db: Database<[K, V], MapValueSlot<K>>;
14
+ protected name: string;
15
+
16
+ protected startSentinel: MapValueSlot<Buffer>;
17
+ protected 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
+ getAsync(key: K): Promise<V | undefined> {
39
+ return Promise.resolve(this.get(key));
40
+ }
41
+
42
+ *getValues(key: K): IterableIterator<V> {
43
+ const transaction = this.db.useReadTransaction();
44
+ try {
45
+ const values = this.db.getValues(this.slot(key), {
46
+ transaction,
47
+ });
48
+ for (const value of values) {
49
+ yield value?.[1];
50
+ }
51
+ } finally {
52
+ transaction.done();
53
+ }
54
+ }
55
+
56
+ async *getValuesAsync(key: K): AsyncIterableIterator<V> {
57
+ for (const value of this.getValues(key)) {
58
+ yield value;
59
+ }
60
+ }
61
+
62
+ has(key: K): boolean {
63
+ return this.db.doesExist(this.slot(key));
64
+ }
65
+
66
+ hasAsync(key: K): Promise<boolean> {
67
+ return Promise.resolve(this.has(key));
68
+ }
69
+
70
+ async set(key: K, val: V): Promise<void> {
71
+ await this.db.put(this.slot(key), [key, val]);
72
+ }
73
+
74
+ swap(key: K, fn: (val: V | undefined) => V): Promise<void> {
75
+ return this.db.childTransaction(() => {
76
+ const slot = this.slot(key);
77
+ const entry = this.db.get(slot);
78
+ void this.db.put(slot, [key, fn(entry?.[1])]);
79
+ });
80
+ }
81
+
82
+ setIfNotExists(key: K, val: V): Promise<boolean> {
83
+ const slot = this.slot(key);
84
+ return this.db.ifNoExists(slot, () => {
85
+ void this.db.put(slot, [key, val]);
86
+ });
87
+ }
88
+
89
+ async delete(key: K): Promise<void> {
90
+ await this.db.remove(this.slot(key));
91
+ }
92
+
93
+ async deleteValue(key: K, val: V): Promise<void> {
94
+ await this.db.remove(this.slot(key), [key, val]);
95
+ }
96
+
97
+ *entries(range: Range<K> = {}): IterableIterator<[K, V]> {
98
+ const transaction = this.db.useReadTransaction();
99
+
100
+ try {
101
+ const { reverse = false, limit } = range;
102
+ // LMDB has a quirk where it expects start > end when reverse=true
103
+ // in that case, we need to swap the start and end sentinels
104
+ const start = reverse
105
+ ? range.end
106
+ ? this.slot(range.end)
107
+ : this.endSentinel
108
+ : range.start
109
+ ? this.slot(range.start)
110
+ : this.startSentinel;
111
+
112
+ const end = reverse
113
+ ? range.start
114
+ ? this.slot(range.start)
115
+ : this.startSentinel
116
+ : range.end
117
+ ? this.slot(range.end)
118
+ : this.endSentinel;
119
+
120
+ const lmdbRange: RangeOptions = {
121
+ start,
122
+ end,
123
+ reverse,
124
+ limit,
125
+ transaction,
126
+ };
127
+
128
+ const iterator = this.db.getRange(lmdbRange);
129
+
130
+ for (const {
131
+ value: [key, value],
132
+ } of iterator) {
133
+ yield [key, value];
134
+ }
135
+ } finally {
136
+ transaction.done();
137
+ }
138
+ }
139
+
140
+ async *entriesAsync(range?: Range<K> | undefined): AsyncIterableIterator<[K, V]> {
141
+ for (const entry of this.entries(range)) {
142
+ yield entry;
143
+ }
144
+ }
145
+
146
+ *values(range: Range<K> = {}): IterableIterator<V> {
147
+ for (const [_, value] of this.entries(range)) {
148
+ yield value;
149
+ }
150
+ }
151
+
152
+ async *valuesAsync(range: Range<K> = {}): AsyncIterableIterator<V> {
153
+ for await (const [_, value] of this.entriesAsync(range)) {
154
+ yield value;
155
+ }
156
+ }
157
+
158
+ *keys(range: Range<K> = {}): IterableIterator<K> {
159
+ for (const [key, _] of this.entries(range)) {
160
+ yield key;
161
+ }
162
+ }
163
+
164
+ async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
165
+ for await (const [key, _] of this.entriesAsync(range)) {
166
+ yield key;
167
+ }
168
+ }
169
+
170
+ protected slot(key: K): MapValueSlot<K> {
171
+ return ['map', this.name, 'slot', key];
172
+ }
173
+
174
+ async clear(): Promise<void> {
175
+ const lmdbRange: RangeOptions = {
176
+ start: this.startSentinel,
177
+ end: this.endSentinel,
178
+ };
179
+
180
+ const iterator = this.db.getRange(lmdbRange);
181
+
182
+ for (const { key } of iterator) {
183
+ await this.db.remove(key);
184
+ }
185
+ }
186
+ }
187
+
188
+ export class LmdbAztecMapWithSize<K extends Key, V>
189
+ extends LmdbAztecMap<K, V>
190
+ implements AztecMapWithSize<K, V>, AztecAsyncMultiMap<K, V>
191
+ {
192
+ #sizeCache?: number;
193
+
194
+ constructor(rootDb: Database, mapName: string) {
195
+ super(rootDb, mapName);
196
+ }
197
+
198
+ override async set(key: K, val: V): Promise<void> {
199
+ await this.db.childTransaction(() => {
200
+ const exists = this.db.doesExist(this.slot(key));
201
+ this.db.putSync(this.slot(key), [key, val], {
202
+ appendDup: true,
203
+ });
204
+ if (!exists) {
205
+ this.#sizeCache = undefined; // Invalidate cache
206
+ }
207
+ });
208
+ }
209
+
210
+ override async delete(key: K): Promise<void> {
211
+ await this.db.childTransaction(async () => {
212
+ const exists = this.db.doesExist(this.slot(key));
213
+ if (exists) {
214
+ await this.db.remove(this.slot(key));
215
+ this.#sizeCache = undefined; // Invalidate cache
216
+ }
217
+ });
218
+ }
219
+
220
+ override async deleteValue(key: K, val: V): Promise<void> {
221
+ await this.db.childTransaction(async () => {
222
+ const exists = this.db.doesExist(this.slot(key));
223
+ if (exists) {
224
+ await this.db.remove(this.slot(key), [key, val]);
225
+ this.#sizeCache = undefined; // Invalidate cache
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Gets the size of the map by counting entries.
232
+ * @returns The number of entries in the map
233
+ */
234
+ size(): number {
235
+ if (this.#sizeCache === undefined) {
236
+ this.#sizeCache = this.db.getCount({
237
+ start: this.startSentinel,
238
+ end: this.endSentinel,
239
+ });
240
+ }
241
+ return this.#sizeCache;
242
+ }
243
+
244
+ // Reset cache on clear/drop operations
245
+ clearCache() {
246
+ this.#sizeCache = undefined;
247
+ }
248
+ }
@@ -0,0 +1,45 @@
1
+ import type { Database } from 'lmdb';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncSet, AztecSet } from '../interfaces/set.js';
5
+ import { LmdbAztecMap } from './map.js';
6
+
7
+ /**
8
+ * A set backed by LMDB.
9
+ */
10
+ export class LmdbAztecSet<K extends Key> implements AztecSet<K>, AztecAsyncSet<K> {
11
+ private map: LmdbAztecMap<K, boolean>;
12
+ constructor(rootDb: Database, mapName: string) {
13
+ this.map = new LmdbAztecMap(rootDb, mapName);
14
+ }
15
+
16
+ close(): Promise<void> {
17
+ return this.map.close();
18
+ }
19
+
20
+ has(key: K): boolean {
21
+ return this.map.has(key);
22
+ }
23
+
24
+ hasAsync(key: K): Promise<boolean> {
25
+ return Promise.resolve(this.has(key));
26
+ }
27
+
28
+ add(key: K): Promise<void> {
29
+ return this.map.set(key, true);
30
+ }
31
+
32
+ delete(key: K): Promise<void> {
33
+ return this.map.delete(key);
34
+ }
35
+
36
+ entries(range: Range<K> = {}): IterableIterator<K> {
37
+ return this.map.keys(range);
38
+ }
39
+
40
+ async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
41
+ for await (const key of this.map.keysAsync(range)) {
42
+ yield key;
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,35 @@
1
+ import type { Database, Key } from 'lmdb';
2
+
3
+ import type { AztecAsyncSingleton, 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>, AztecAsyncSingleton<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
+ getAsync(): Promise<T | undefined> {
25
+ return Promise.resolve(this.get());
26
+ }
27
+
28
+ set(val: T): Promise<boolean> {
29
+ return this.#db.put(this.#slot, val);
30
+ }
31
+
32
+ delete(): Promise<boolean> {
33
+ return this.#db.remove(this.#slot);
34
+ }
35
+ }