@aztec/kv-store 0.81.0 → 0.82.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 (68) hide show
  1. package/dest/config.d.ts.map +1 -1
  2. package/dest/config.js +3 -1
  3. package/dest/indexeddb/map.d.ts +5 -4
  4. package/dest/indexeddb/map.d.ts.map +1 -1
  5. package/dest/indexeddb/map.js +13 -53
  6. package/dest/indexeddb/multi_map.d.ts +12 -0
  7. package/dest/indexeddb/multi_map.d.ts.map +1 -0
  8. package/dest/indexeddb/multi_map.js +54 -0
  9. package/dest/indexeddb/store.d.ts +2 -1
  10. package/dest/indexeddb/store.d.ts.map +1 -1
  11. package/dest/indexeddb/store.js +2 -1
  12. package/dest/interfaces/index.d.ts +1 -0
  13. package/dest/interfaces/index.d.ts.map +1 -1
  14. package/dest/interfaces/index.js +1 -0
  15. package/dest/interfaces/map.d.ts +0 -46
  16. package/dest/interfaces/map.d.ts.map +1 -1
  17. package/dest/interfaces/map.js +1 -1
  18. package/dest/interfaces/map_test_suite.d.ts.map +1 -1
  19. package/dest/interfaces/map_test_suite.js +6 -20
  20. package/dest/interfaces/multi_map.d.ts +35 -0
  21. package/dest/interfaces/multi_map.d.ts.map +1 -0
  22. package/dest/interfaces/multi_map.js +3 -0
  23. package/dest/interfaces/multi_map_test_suite.d.ts +3 -0
  24. package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -0
  25. package/dest/interfaces/multi_map_test_suite.js +151 -0
  26. package/dest/interfaces/store.d.ts +2 -13
  27. package/dest/interfaces/store.d.ts.map +1 -1
  28. package/dest/lmdb/map.d.ts +2 -18
  29. package/dest/lmdb/map.d.ts.map +1 -1
  30. package/dest/lmdb/map.js +0 -81
  31. package/dest/lmdb/multi_map.d.ts +12 -0
  32. package/dest/lmdb/multi_map.d.ts.map +1 -0
  33. package/dest/lmdb/multi_map.js +29 -0
  34. package/dest/lmdb/store.d.ts +2 -13
  35. package/dest/lmdb/store.d.ts.map +1 -1
  36. package/dest/lmdb/store.js +3 -16
  37. package/dest/lmdb-v2/array.d.ts.map +1 -1
  38. package/dest/lmdb-v2/array.js +1 -0
  39. package/dest/lmdb-v2/map.d.ts +1 -43
  40. package/dest/lmdb-v2/map.d.ts.map +1 -1
  41. package/dest/lmdb-v2/map.js +1 -100
  42. package/dest/lmdb-v2/multi_map.d.ts +46 -0
  43. package/dest/lmdb-v2/multi_map.d.ts.map +1 -0
  44. package/dest/lmdb-v2/multi_map.js +103 -0
  45. package/dest/lmdb-v2/singleton.d.ts.map +1 -1
  46. package/dest/lmdb-v2/singleton.js +1 -0
  47. package/dest/lmdb-v2/store.d.ts +2 -1
  48. package/dest/lmdb-v2/store.d.ts.map +1 -1
  49. package/dest/lmdb-v2/store.js +5 -1
  50. package/package.json +5 -5
  51. package/src/config.ts +3 -1
  52. package/src/indexeddb/map.ts +15 -47
  53. package/src/indexeddb/multi_map.ts +53 -0
  54. package/src/indexeddb/store.ts +9 -3
  55. package/src/interfaces/index.ts +1 -0
  56. package/src/interfaces/map.ts +0 -52
  57. package/src/interfaces/map_test_suite.ts +18 -33
  58. package/src/interfaces/multi_map.ts +38 -0
  59. package/src/interfaces/multi_map_test_suite.ts +143 -0
  60. package/src/interfaces/store.ts +2 -22
  61. package/src/lmdb/map.ts +2 -88
  62. package/src/lmdb/multi_map.ts +35 -0
  63. package/src/lmdb/store.ts +5 -27
  64. package/src/lmdb-v2/array.ts +1 -0
  65. package/src/lmdb-v2/map.ts +2 -120
  66. package/src/lmdb-v2/multi_map.ts +126 -0
  67. package/src/lmdb-v2/singleton.ts +1 -0
  68. package/src/lmdb-v2/store.ts +7 -2
@@ -0,0 +1,143 @@
1
+ import { toArray } from '@aztec/foundation/iterable';
2
+
3
+ import { expect } from 'chai';
4
+
5
+ import type { Key, Range } from './common.js';
6
+ import type { AztecAsyncMultiMap, AztecMultiMap } from './multi_map.js';
7
+ import type { AztecAsyncKVStore, AztecKVStore } from './store.js';
8
+ import { isSyncStore } from './utils.js';
9
+
10
+ export function describeAztecMultiMap(
11
+ testName: string,
12
+ getStore: () => AztecKVStore | Promise<AztecAsyncKVStore>,
13
+ forceAsync: boolean = false,
14
+ ) {
15
+ describe(testName, () => {
16
+ let store: AztecKVStore | AztecAsyncKVStore;
17
+ let multiMap: AztecMultiMap<Key, string> | AztecAsyncMultiMap<Key, string>;
18
+
19
+ beforeEach(async () => {
20
+ store = await getStore();
21
+ multiMap = store.openMultiMap<string, string>('test');
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await store.delete();
26
+ });
27
+
28
+ async function get(key: Key, sut: AztecAsyncMultiMap<any, any> | AztecMultiMap<any, any> = multiMap) {
29
+ return isSyncStore(store) && !forceAsync
30
+ ? (sut as AztecMultiMap<any, any>).get(key)
31
+ : await (sut as AztecAsyncMultiMap<any, any>).getAsync(key);
32
+ }
33
+
34
+ async function entries() {
35
+ return isSyncStore(store) && !forceAsync
36
+ ? await toArray((multiMap as AztecMultiMap<any, any>).entries())
37
+ : await toArray((multiMap as AztecAsyncMultiMap<any, any>).entriesAsync());
38
+ }
39
+
40
+ async function values() {
41
+ return isSyncStore(store) && !forceAsync
42
+ ? await toArray((multiMap as AztecMultiMap<any, any>).values())
43
+ : await toArray((multiMap as AztecAsyncMultiMap<any, any>).valuesAsync());
44
+ }
45
+
46
+ async function keys(range?: Range<Key>, sut: AztecAsyncMultiMap<any, any> | AztecMultiMap<any, any> = multiMap) {
47
+ return isSyncStore(store) && !forceAsync
48
+ ? await toArray((sut as AztecMultiMap<any, any>).keys(range))
49
+ : await toArray((sut as AztecAsyncMultiMap<any, any>).keysAsync(range));
50
+ }
51
+
52
+ async function getValues(key: Key) {
53
+ return isSyncStore(store) && !forceAsync
54
+ ? await toArray((multiMap as AztecMultiMap<any, any>).getValues(key))
55
+ : await toArray((multiMap as AztecAsyncMultiMap<any, any>).getValuesAsync(key));
56
+ }
57
+
58
+ it('should be able to set and get values', async () => {
59
+ await multiMap.set('foo', 'bar');
60
+ await multiMap.set('baz', 'qux');
61
+
62
+ expect(await get('foo')).to.equal('bar');
63
+ expect(await get('baz')).to.equal('qux');
64
+ expect(await get('quux')).to.equal(undefined);
65
+ });
66
+
67
+ it('should be able to set values if they do not exist', async () => {
68
+ expect(await multiMap.setIfNotExists('foo', 'bar')).to.equal(true);
69
+ expect(await multiMap.setIfNotExists('foo', 'baz')).to.equal(false);
70
+
71
+ expect(await get('foo')).to.equal('bar');
72
+ });
73
+
74
+ it('should be able to delete values', async () => {
75
+ await multiMap.set('foo', 'bar');
76
+ await multiMap.set('baz', 'qux');
77
+
78
+ await multiMap.delete('foo');
79
+
80
+ expect(await get('foo')).to.equal(undefined);
81
+ expect(await get('baz')).to.equal('qux');
82
+ });
83
+
84
+ it('should be able to iterate over entries when there are no keys', async () => {
85
+ expect(await entries()).to.deep.equal([]);
86
+ });
87
+
88
+ it('should be able to iterate over entries', async () => {
89
+ await multiMap.set('foo', 'bar');
90
+ await multiMap.set('baz', 'qux');
91
+
92
+ expect(await entries()).to.deep.equal([
93
+ ['baz', 'qux'],
94
+ ['foo', 'bar'],
95
+ ]);
96
+ });
97
+
98
+ it('should be able to iterate over values', async () => {
99
+ await multiMap.set('foo', 'bar');
100
+ await multiMap.set('baz', 'quux');
101
+
102
+ expect(await values()).to.deep.equal(['quux', 'bar']);
103
+ });
104
+
105
+ it('should be able to iterate over keys', async () => {
106
+ await multiMap.set('foo', 'bar');
107
+ await multiMap.set('baz', 'qux');
108
+
109
+ expect(await keys()).to.deep.equal(['baz', 'foo']);
110
+ });
111
+
112
+ it('should be able to get multiple values for a single key', async () => {
113
+ await multiMap.set('foo', 'bar');
114
+ await multiMap.set('foo', 'baz');
115
+
116
+ expect(await getValues('foo')).to.deep.equal(['bar', 'baz']);
117
+ });
118
+
119
+ it('should be able to delete individual values for a single key', async () => {
120
+ await multiMap.set('foo', 'bar');
121
+ await multiMap.set('foo', 'baz');
122
+
123
+ await multiMap.deleteValue('foo', 'bar');
124
+
125
+ expect(await getValues('foo')).to.deep.equal(['baz']);
126
+ });
127
+
128
+ it('supports range queries', async () => {
129
+ await multiMap.set('a', 'a');
130
+ await multiMap.set('b', 'b');
131
+ await multiMap.set('c', 'c');
132
+ await multiMap.set('d', 'd');
133
+
134
+ expect(await keys({ start: 'b', end: 'c' })).to.deep.equal(['b']);
135
+ expect(await keys({ start: 'b' })).to.deep.equal(['b', 'c', 'd']);
136
+ expect(await keys({ end: 'c' })).to.deep.equal(['a', 'b']);
137
+ expect(await keys({ start: 'b', end: 'c', reverse: true })).to.deep.equal(['c']);
138
+ expect(await keys({ start: 'b', limit: 1 })).to.deep.equal(['b']);
139
+ expect(await keys({ start: 'b', reverse: true })).to.deep.equal(['d', 'c']);
140
+ expect(await keys({ end: 'b', reverse: true })).to.deep.equal(['b', 'a']);
141
+ });
142
+ });
143
+ }
@@ -1,14 +1,8 @@
1
1
  import type { AztecArray, AztecAsyncArray } from './array.js';
2
2
  import type { Key, StoreSize } from './common.js';
3
3
  import type { AztecAsyncCounter, AztecCounter } from './counter.js';
4
- import type {
5
- AztecAsyncMap,
6
- AztecAsyncMultiMap,
7
- AztecMap,
8
- AztecMapWithSize,
9
- AztecMultiMap,
10
- AztecMultiMapWithSize,
11
- } from './map.js';
4
+ import type { AztecAsyncMap, AztecMap } from './map.js';
5
+ import type { AztecAsyncMultiMap, AztecMultiMap } from './multi_map.js';
12
6
  import type { AztecAsyncSet, AztecSet } from './set.js';
13
7
  import type { AztecAsyncSingleton, AztecSingleton } from './singleton.js';
14
8
 
@@ -36,20 +30,6 @@ export interface AztecKVStore {
36
30
  */
37
31
  openMultiMap<K extends Key, V>(name: string): AztecMultiMap<K, V>;
38
32
 
39
- /**
40
- * Creates a new multi-map with size.
41
- * @param name - The name of the multi-map
42
- * @returns The multi-map
43
- */
44
- openMultiMapWithSize<K extends Key, V>(name: string): AztecMultiMapWithSize<K, V>;
45
-
46
- /**
47
- * Creates a new map with size.
48
- * @param name - The name of the map
49
- * @returns The map
50
- */
51
- openMapWithSize<K extends Key, V>(name: string): AztecMapWithSize<K, V>;
52
-
53
33
  /**
54
34
  * Creates a new array.
55
35
  * @param name - The name of the array
package/src/lmdb/map.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Database, RangeOptions } from 'lmdb';
2
2
 
3
3
  import type { Key, Range } from '../interfaces/common.js';
4
- import type { AztecAsyncMultiMap, AztecMapWithSize, AztecMultiMap } from '../interfaces/map.js';
4
+ import type { AztecAsyncMap, AztecMap } from '../interfaces/map.js';
5
5
 
6
6
  /** The slot where a key-value entry would be stored */
7
7
  type MapValueSlot<K extends Key | Buffer> = ['map', string, 'slot', K];
@@ -9,7 +9,7 @@ type MapValueSlot<K extends Key | Buffer> = ['map', string, 'slot', K];
9
9
  /**
10
10
  * A map backed by LMDB.
11
11
  */
12
- export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, AztecAsyncMultiMap<K, V> {
12
+ export class LmdbAztecMap<K extends Key, V> implements AztecMap<K, V>, AztecAsyncMap<K, V> {
13
13
  protected db: Database<[K, V], MapValueSlot<K>>;
14
14
  protected name: string;
15
15
 
@@ -39,26 +39,6 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
39
39
  return Promise.resolve(this.get(key));
40
40
  }
41
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
42
  has(key: K): boolean {
63
43
  return this.db.doesExist(this.slot(key));
64
44
  }
@@ -90,10 +70,6 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
90
70
  await this.db.remove(this.slot(key));
91
71
  }
92
72
 
93
- async deleteValue(key: K, val: V): Promise<void> {
94
- await this.db.remove(this.slot(key), [key, val]);
95
- }
96
-
97
73
  *entries(range: Range<K> = {}): IterableIterator<[K, V]> {
98
74
  const transaction = this.db.useReadTransaction();
99
75
 
@@ -184,65 +160,3 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
184
160
  }
185
161
  }
186
162
  }
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,35 @@
1
+ import type { Key } from '../interfaces/common.js';
2
+ import type { AztecAsyncMultiMap, AztecMultiMap } from '../interfaces/multi_map.js';
3
+ import { LmdbAztecMap } from './map.js';
4
+
5
+ /**
6
+ * A map backed by LMDB.
7
+ */
8
+ export class LmdbAztecMultiMap<K extends Key, V>
9
+ extends LmdbAztecMap<K, V>
10
+ implements AztecMultiMap<K, V>, AztecAsyncMultiMap<K, V>
11
+ {
12
+ *getValues(key: K): IterableIterator<V> {
13
+ const transaction = this.db.useReadTransaction();
14
+ try {
15
+ const values = this.db.getValues(this.slot(key), {
16
+ transaction,
17
+ });
18
+ for (const value of values) {
19
+ yield value?.[1];
20
+ }
21
+ } finally {
22
+ transaction.done();
23
+ }
24
+ }
25
+
26
+ async *getValuesAsync(key: K): AsyncIterableIterator<V> {
27
+ for (const value of this.getValues(key)) {
28
+ yield value;
29
+ }
30
+ }
31
+
32
+ async deleteValue(key: K, val: V): Promise<void> {
33
+ await this.db.remove(this.slot(key), [key, val]);
34
+ }
35
+ }
package/src/lmdb/store.ts CHANGED
@@ -9,20 +9,15 @@ import { join } from 'path';
9
9
  import type { AztecArray, AztecAsyncArray } from '../interfaces/array.js';
10
10
  import type { Key, StoreSize } from '../interfaces/common.js';
11
11
  import type { AztecAsyncCounter, AztecCounter } from '../interfaces/counter.js';
12
- import type {
13
- AztecAsyncMap,
14
- AztecAsyncMultiMap,
15
- AztecMap,
16
- AztecMapWithSize,
17
- AztecMultiMap,
18
- AztecMultiMapWithSize,
19
- } from '../interfaces/map.js';
12
+ import type { AztecAsyncMap, AztecMap } from '../interfaces/map.js';
13
+ import type { AztecAsyncMultiMap, AztecMultiMap } from '../interfaces/multi_map.js';
20
14
  import type { AztecAsyncSet, AztecSet } from '../interfaces/set.js';
21
15
  import type { AztecAsyncSingleton, AztecSingleton } from '../interfaces/singleton.js';
22
16
  import type { AztecAsyncKVStore, AztecKVStore } from '../interfaces/store.js';
23
17
  import { LmdbAztecArray } from './array.js';
24
18
  import { LmdbAztecCounter } from './counter.js';
25
- import { LmdbAztecMap, LmdbAztecMapWithSize } from './map.js';
19
+ import { LmdbAztecMap } from './map.js';
20
+ import { LmdbAztecMultiMap } from './multi_map.js';
26
21
  import { LmdbAztecSet } from './set.js';
27
22
  import { LmdbAztecSingleton } from './singleton.js';
28
23
 
@@ -119,29 +114,12 @@ export class AztecLmdbStore implements AztecKVStore, AztecAsyncKVStore {
119
114
  * @returns A new AztecMultiMap
120
115
  */
121
116
  openMultiMap<K extends Key, V>(name: string): AztecMultiMap<K, V> & AztecAsyncMultiMap<K, V> {
122
- return new LmdbAztecMap(this.#multiMapData, name);
117
+ return new LmdbAztecMultiMap(this.#multiMapData, name);
123
118
  }
124
119
 
125
120
  openCounter<K extends Key>(name: string): AztecCounter<K> & AztecAsyncCounter<K> {
126
121
  return new LmdbAztecCounter(this.#data, name);
127
122
  }
128
- /**
129
- * Creates a new AztecMultiMapWithSize in the store. A multi-map with size stores multiple values for a single key automatically.
130
- * @param name - Name of the map
131
- * @returns A new AztecMultiMapWithSize
132
- */
133
- openMultiMapWithSize<K extends Key, V>(name: string): AztecMultiMapWithSize<K, V> {
134
- return new LmdbAztecMapWithSize(this.#multiMapData, name);
135
- }
136
-
137
- /**
138
- * Creates a new AztecMapWithSize in the store.
139
- * @param name - Name of the map
140
- * @returns A new AztecMapWithSize
141
- */
142
- openMapWithSize<K extends Key, V>(name: string): AztecMapWithSize<K, V> {
143
- return new LmdbAztecMapWithSize(this.#data, name);
144
- }
145
123
 
146
124
  /**
147
125
  * Creates a new AztecArray in the store.
@@ -3,6 +3,7 @@ import { Encoder } from 'msgpackr/pack';
3
3
  import type { AztecAsyncArray } from '../interfaces/array.js';
4
4
  import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
5
5
  import type { ReadTransaction } from './read_transaction.js';
6
+ // eslint-disable-next-line import/no-cycle
6
7
  import { AztecLMDBStoreV2, execInReadTx, execInWriteTx } from './store.js';
7
8
  import { deserializeKey, serializeKey } from './utils.js';
8
9
 
@@ -1,8 +1,9 @@
1
1
  import { Encoder } from 'msgpackr';
2
2
 
3
3
  import type { Key, Range } from '../interfaces/common.js';
4
- import type { AztecAsyncMap, AztecAsyncMultiMap } from '../interfaces/map.js';
4
+ import type { AztecAsyncMap } from '../interfaces/map.js';
5
5
  import type { ReadTransaction } from './read_transaction.js';
6
+ // eslint-disable-next-line import/no-cycle
6
7
  import { type AztecLMDBStoreV2, execInReadTx, execInWriteTx } from './store.js';
7
8
  import { deserializeKey, maxKey, minKey, serializeKey } from './utils.js';
8
9
 
@@ -112,122 +113,3 @@ export class LMDBMap<K extends Key, V> implements AztecAsyncMap<K, V> {
112
113
  }
113
114
  }
114
115
  }
115
-
116
- export class LMDBMultiMap<K extends Key, V> implements AztecAsyncMultiMap<K, V> {
117
- private prefix: string;
118
- private encoder = new Encoder();
119
- constructor(private store: AztecLMDBStoreV2, name: string) {
120
- this.prefix = `multimap:${name}`;
121
- }
122
-
123
- /**
124
- * Sets the value at the given key.
125
- * @param key - The key to set the value at
126
- * @param val - The value to set
127
- */
128
- set(key: K, val: V): Promise<void> {
129
- return execInWriteTx(this.store, tx => tx.setIndex(serializeKey(this.prefix, key), this.encoder.pack(val)));
130
- }
131
-
132
- /**
133
- * Sets the value at the given key if it does not already exist.
134
- * @param key - The key to set the value at
135
- * @param val - The value to set
136
- */
137
- setIfNotExists(key: K, val: V): Promise<boolean> {
138
- return execInWriteTx(this.store, async tx => {
139
- const exists = !!(await this.getAsync(key));
140
- if (!exists) {
141
- await tx.setIndex(serializeKey(this.prefix, key), this.encoder.pack(val));
142
- return true;
143
- }
144
- return false;
145
- });
146
- }
147
-
148
- /**
149
- * Deletes the value at the given key.
150
- * @param key - The key to delete the value at
151
- */
152
- delete(key: K): Promise<void> {
153
- return execInWriteTx(this.store, tx => tx.removeIndex(serializeKey(this.prefix, key)));
154
- }
155
-
156
- getAsync(key: K): Promise<V | undefined> {
157
- return execInReadTx(this.store, async tx => {
158
- const val = await tx.getIndex(serializeKey(this.prefix, key));
159
- return val.length > 0 ? this.encoder.unpack(val[0]) : undefined;
160
- });
161
- }
162
-
163
- hasAsync(key: K): Promise<boolean> {
164
- return execInReadTx(this.store, async tx => (await tx.getIndex(serializeKey(this.prefix, key))).length > 0);
165
- }
166
-
167
- /**
168
- * Iterates over the map's key-value entries in the key's natural order
169
- * @param range - The range of keys to iterate over
170
- */
171
- async *entriesAsync(range?: Range<K>): AsyncIterableIterator<[K, V]> {
172
- const reverse = range?.reverse ?? false;
173
- const startKey = range?.start ? serializeKey(this.prefix, range.start) : minKey(this.prefix);
174
- const endKey = range?.end ? serializeKey(this.prefix, range.end) : reverse ? maxKey(this.prefix) : undefined;
175
-
176
- let tx: ReadTransaction | undefined = this.store.getCurrentWriteTx();
177
- const shouldClose = !tx;
178
- tx ??= this.store.getReadTx();
179
-
180
- try {
181
- for await (const [key, vals] of tx.iterateIndex(
182
- reverse ? endKey! : startKey,
183
- reverse ? startKey : endKey,
184
- reverse,
185
- range?.limit,
186
- )) {
187
- const deserializedKey = deserializeKey<K>(this.prefix, key);
188
- if (!deserializedKey) {
189
- break;
190
- }
191
-
192
- for (const val of vals) {
193
- yield [deserializedKey, this.encoder.unpack(val)];
194
- }
195
- }
196
- } finally {
197
- if (shouldClose) {
198
- tx.close();
199
- }
200
- }
201
- }
202
-
203
- /**
204
- * Iterates over the map's values in the key's natural order
205
- * @param range - The range of keys to iterate over
206
- */
207
- async *valuesAsync(range?: Range<K>): AsyncIterableIterator<V> {
208
- for await (const [_, value] of this.entriesAsync(range)) {
209
- yield value;
210
- }
211
- }
212
-
213
- /**
214
- * Iterates over the map's keys in the key's natural order
215
- * @param range - The range of keys to iterate over
216
- */
217
- async *keysAsync(range?: Range<K>): AsyncIterableIterator<K> {
218
- for await (const [key, _] of this.entriesAsync(range)) {
219
- yield key;
220
- }
221
- }
222
-
223
- deleteValue(key: K, val: V | undefined): Promise<void> {
224
- return execInWriteTx(this.store, tx => tx.removeIndex(serializeKey(this.prefix, key), this.encoder.pack(val)));
225
- }
226
-
227
- async *getValuesAsync(key: K): AsyncIterableIterator<V> {
228
- const values = await execInReadTx(this.store, tx => tx.getIndex(serializeKey(this.prefix, key)));
229
- for (const value of values) {
230
- yield this.encoder.unpack(value);
231
- }
232
- }
233
- }
@@ -0,0 +1,126 @@
1
+ import { Encoder } from 'msgpackr/pack';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
5
+ import type { ReadTransaction } from './read_transaction.js';
6
+ import { type AztecLMDBStoreV2, execInReadTx, execInWriteTx } from './store.js';
7
+ import { deserializeKey, maxKey, minKey, serializeKey } from './utils.js';
8
+
9
+ export class LMDBMultiMap<K extends Key, V> implements AztecAsyncMultiMap<K, V> {
10
+ private prefix: string;
11
+ private encoder = new Encoder();
12
+ constructor(private store: AztecLMDBStoreV2, name: string) {
13
+ this.prefix = `multimap:${name}`;
14
+ }
15
+
16
+ /**
17
+ * Sets the value at the given key.
18
+ * @param key - The key to set the value at
19
+ * @param val - The value to set
20
+ */
21
+ set(key: K, val: V): Promise<void> {
22
+ return execInWriteTx(this.store, tx => tx.setIndex(serializeKey(this.prefix, key), this.encoder.pack(val)));
23
+ }
24
+
25
+ /**
26
+ * Sets the value at the given key if it does not already exist.
27
+ * @param key - The key to set the value at
28
+ * @param val - The value to set
29
+ */
30
+ setIfNotExists(key: K, val: V): Promise<boolean> {
31
+ return execInWriteTx(this.store, async tx => {
32
+ const exists = !!(await this.getAsync(key));
33
+ if (!exists) {
34
+ await tx.setIndex(serializeKey(this.prefix, key), this.encoder.pack(val));
35
+ return true;
36
+ }
37
+ return false;
38
+ });
39
+ }
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<void> {
46
+ return execInWriteTx(this.store, tx => tx.removeIndex(serializeKey(this.prefix, key)));
47
+ }
48
+
49
+ getAsync(key: K): Promise<V | undefined> {
50
+ return execInReadTx(this.store, async tx => {
51
+ const val = await tx.getIndex(serializeKey(this.prefix, key));
52
+ return val.length > 0 ? this.encoder.unpack(val[0]) : undefined;
53
+ });
54
+ }
55
+
56
+ hasAsync(key: K): Promise<boolean> {
57
+ return execInReadTx(this.store, async tx => (await tx.getIndex(serializeKey(this.prefix, key))).length > 0);
58
+ }
59
+
60
+ /**
61
+ * Iterates over the map's key-value entries in the key's natural order
62
+ * @param range - The range of keys to iterate over
63
+ */
64
+ async *entriesAsync(range?: Range<K>): AsyncIterableIterator<[K, V]> {
65
+ const reverse = range?.reverse ?? false;
66
+ const startKey = range?.start ? serializeKey(this.prefix, range.start) : minKey(this.prefix);
67
+ const endKey = range?.end ? serializeKey(this.prefix, range.end) : reverse ? maxKey(this.prefix) : undefined;
68
+
69
+ let tx: ReadTransaction | undefined = this.store.getCurrentWriteTx();
70
+ const shouldClose = !tx;
71
+ tx ??= this.store.getReadTx();
72
+
73
+ try {
74
+ for await (const [key, vals] of tx.iterateIndex(
75
+ reverse ? endKey! : startKey,
76
+ reverse ? startKey : endKey,
77
+ reverse,
78
+ range?.limit,
79
+ )) {
80
+ const deserializedKey = deserializeKey<K>(this.prefix, key);
81
+ if (!deserializedKey) {
82
+ break;
83
+ }
84
+
85
+ for (const val of vals) {
86
+ yield [deserializedKey, this.encoder.unpack(val)];
87
+ }
88
+ }
89
+ } finally {
90
+ if (shouldClose) {
91
+ tx.close();
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Iterates over the map's values in the key's natural order
98
+ * @param range - The range of keys to iterate over
99
+ */
100
+ async *valuesAsync(range?: Range<K>): AsyncIterableIterator<V> {
101
+ for await (const [_, value] of this.entriesAsync(range)) {
102
+ yield value;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Iterates over the map's keys in the key's natural order
108
+ * @param range - The range of keys to iterate over
109
+ */
110
+ async *keysAsync(range?: Range<K>): AsyncIterableIterator<K> {
111
+ for await (const [key, _] of this.entriesAsync(range)) {
112
+ yield key;
113
+ }
114
+ }
115
+
116
+ deleteValue(key: K, val: V | undefined): Promise<void> {
117
+ return execInWriteTx(this.store, tx => tx.removeIndex(serializeKey(this.prefix, key), this.encoder.pack(val)));
118
+ }
119
+
120
+ async *getValuesAsync(key: K): AsyncIterableIterator<V> {
121
+ const values = await execInReadTx(this.store, tx => tx.getIndex(serializeKey(this.prefix, key)));
122
+ for (const value of values) {
123
+ yield this.encoder.unpack(value);
124
+ }
125
+ }
126
+ }
@@ -1,6 +1,7 @@
1
1
  import { Encoder } from 'msgpackr';
2
2
 
3
3
  import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
4
+ // eslint-disable-next-line import/no-cycle
4
5
  import { type AztecLMDBStoreV2, execInReadTx, execInWriteTx } from './store.js';
5
6
  import { serializeKey } from './utils.js';
6
7