@aztec/kv-store 0.71.0 → 0.73.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 (64) hide show
  1. package/dest/config.js +3 -3
  2. package/dest/indexeddb/store.d.ts +4 -7
  3. package/dest/indexeddb/store.d.ts.map +1 -1
  4. package/dest/indexeddb/store.js +5 -2
  5. package/dest/interfaces/common.d.ts +6 -1
  6. package/dest/interfaces/common.d.ts.map +1 -1
  7. package/dest/interfaces/index.d.ts +1 -1
  8. package/dest/interfaces/index.d.ts.map +1 -1
  9. package/dest/interfaces/map.d.ts +0 -6
  10. package/dest/interfaces/map.d.ts.map +1 -1
  11. package/dest/interfaces/map_test_suite.d.ts.map +1 -1
  12. package/dest/interfaces/map_test_suite.js +1 -12
  13. package/dest/interfaces/store.d.ts +11 -11
  14. package/dest/interfaces/store.d.ts.map +1 -1
  15. package/dest/lmdb/store.d.ts +2 -6
  16. package/dest/lmdb/store.d.ts.map +1 -1
  17. package/dest/lmdb/store.js +3 -3
  18. package/dest/lmdb-v2/factory.d.ts +7 -0
  19. package/dest/lmdb-v2/factory.d.ts.map +1 -0
  20. package/dest/lmdb-v2/factory.js +56 -0
  21. package/dest/lmdb-v2/index.d.ts +3 -0
  22. package/dest/lmdb-v2/index.d.ts.map +1 -0
  23. package/dest/lmdb-v2/index.js +3 -0
  24. package/dest/lmdb-v2/map.d.ts +86 -0
  25. package/dest/lmdb-v2/map.d.ts.map +1 -0
  26. package/dest/lmdb-v2/map.js +196 -0
  27. package/dest/lmdb-v2/message.d.ts +112 -0
  28. package/dest/lmdb-v2/message.d.ts.map +1 -0
  29. package/dest/lmdb-v2/message.js +19 -0
  30. package/dest/lmdb-v2/read_transaction.d.ts +14 -0
  31. package/dest/lmdb-v2/read_transaction.d.ts.map +1 -0
  32. package/dest/lmdb-v2/read_transaction.js +89 -0
  33. package/dest/lmdb-v2/singleton.d.ts +12 -0
  34. package/dest/lmdb-v2/singleton.d.ts.map +1 -0
  35. package/dest/lmdb-v2/singleton.js +29 -0
  36. package/dest/lmdb-v2/store.d.ts +41 -0
  37. package/dest/lmdb-v2/store.d.ts.map +1 -0
  38. package/dest/lmdb-v2/store.js +156 -0
  39. package/dest/lmdb-v2/utils.d.ts +19 -0
  40. package/dest/lmdb-v2/utils.d.ts.map +1 -0
  41. package/dest/lmdb-v2/utils.js +126 -0
  42. package/dest/lmdb-v2/write_transaction.d.ts +19 -0
  43. package/dest/lmdb-v2/write_transaction.d.ts.map +1 -0
  44. package/dest/lmdb-v2/write_transaction.js +234 -0
  45. package/dest/stores/l2_tips_store.js +2 -2
  46. package/package.json +14 -6
  47. package/src/config.ts +2 -2
  48. package/src/indexeddb/store.ts +8 -4
  49. package/src/interfaces/common.ts +3 -1
  50. package/src/interfaces/index.ts +1 -1
  51. package/src/interfaces/map.ts +0 -7
  52. package/src/interfaces/map_test_suite.ts +1 -16
  53. package/src/interfaces/store.ts +13 -3
  54. package/src/lmdb/store.ts +4 -4
  55. package/src/lmdb-v2/factory.ts +79 -0
  56. package/src/lmdb-v2/index.ts +2 -0
  57. package/src/lmdb-v2/map.ts +233 -0
  58. package/src/lmdb-v2/message.ts +146 -0
  59. package/src/lmdb-v2/read_transaction.ts +116 -0
  60. package/src/lmdb-v2/singleton.ts +34 -0
  61. package/src/lmdb-v2/store.ts +210 -0
  62. package/src/lmdb-v2/utils.ts +150 -0
  63. package/src/lmdb-v2/write_transaction.ts +314 -0
  64. package/src/stores/l2_tips_store.ts +1 -1
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@aztec/kv-store",
3
- "version": "0.71.0",
3
+ "version": "0.73.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/interfaces/index.js",
7
7
  "./lmdb": "./dest/lmdb/index.js",
8
+ "./lmdb-v2": "./dest/lmdb-v2/index.js",
8
9
  "./indexeddb": "./dest/indexeddb/index.js",
9
10
  "./stores": "./dest/stores/index.js",
10
11
  "./config": "./dest/config.js"
@@ -12,23 +13,28 @@
12
13
  "scripts": {
13
14
  "build": "yarn clean && tsc -b",
14
15
  "build:dev": "tsc -b --watch",
16
+ "clean:cpp": "rm -rf $(git rev-parse --show-toplevel)/barretenberg/cpp/build-pic",
15
17
  "clean": "rm -rf ./dest .tsbuildinfo",
16
18
  "formatting": "run -T prettier --check ./src && run -T eslint ./src",
17
19
  "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
18
20
  "test:node": "NODE_NO_WARNINGS=1 mocha --config ./.mocharc.json --reporter dot",
19
21
  "test:browser": "wtr --config ./web-test-runner.config.mjs",
20
- "test": "yarn test:node && yarn test:browser && true"
22
+ "test": "yarn test:node && yarn test:browser && true",
23
+ "generate": "mkdir -p build && cp -v ../../barretenberg/cpp/build-pic/lib/nodejs_module.node build"
21
24
  },
22
25
  "inherits": [
23
26
  "../package.common.json",
24
27
  "./package.local.json"
25
28
  ],
26
29
  "dependencies": {
27
- "@aztec/circuit-types": "0.71.0",
28
- "@aztec/ethereum": "0.71.0",
29
- "@aztec/foundation": "0.71.0",
30
+ "@aztec/circuit-types": "0.73.0",
31
+ "@aztec/ethereum": "0.73.0",
32
+ "@aztec/foundation": "0.73.0",
33
+ "@aztec/native": "0.73.0",
30
34
  "idb": "^8.0.0",
31
- "lmdb": "^3.2.0"
35
+ "lmdb": "^3.2.0",
36
+ "msgpackr": "^1.11.2",
37
+ "ordered-binary": "^1.5.3"
32
38
  },
33
39
  "devDependencies": {
34
40
  "@aztec/circuits.js": "workspace:^",
@@ -39,6 +45,7 @@
39
45
  "@types/mocha": "^10.0.10",
40
46
  "@types/mocha-each": "^2.0.4",
41
47
  "@types/node": "^18.7.23",
48
+ "@types/sinon": "^17.0.3",
42
49
  "@web/dev-server-esbuild": "^1.0.3",
43
50
  "@web/test-runner": "^0.19.0",
44
51
  "@web/test-runner-playwright": "^0.11.0",
@@ -47,6 +54,7 @@
47
54
  "jest": "^29.5.0",
48
55
  "mocha": "^10.8.2",
49
56
  "mocha-each": "^2.0.1",
57
+ "sinon": "^19.0.2",
50
58
  "ts-node": "^10.9.1",
51
59
  "typescript": "^5.0.4"
52
60
  },
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { l1ContractAddressesMapping } from '@aztec/ethereum';
1
+ import { l1ContractAddressesMapping } from '@aztec/ethereum/l1-contract-addresses';
2
2
  import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
3
3
  import { type EthAddress } from '@aztec/foundation/eth-address';
4
4
 
@@ -20,7 +20,7 @@ export const dataConfigMappings: ConfigMappingsType<DataStoreConfig> = {
20
20
  },
21
21
  l1Contracts: {
22
22
  description: 'The deployed L1 contract addresses',
23
- defaultValue: l1ContractAddressesMapping,
23
+ nested: l1ContractAddressesMapping,
24
24
  },
25
25
  };
26
26
 
@@ -3,7 +3,7 @@ import { type Logger } from '@aztec/foundation/log';
3
3
  import { type DBSchema, type IDBPDatabase, deleteDB, openDB } from 'idb';
4
4
 
5
5
  import { type AztecAsyncArray } from '../interfaces/array.js';
6
- import { type Key } from '../interfaces/common.js';
6
+ import { type Key, type StoreSize } from '../interfaces/common.js';
7
7
  import { type AztecAsyncCounter } from '../interfaces/counter.js';
8
8
  import { type AztecAsyncMap, type AztecAsyncMultiMap } from '../interfaces/map.js';
9
9
  import { type AztecAsyncSet } from '../interfaces/set.js';
@@ -124,7 +124,7 @@ export class AztecIndexedDBStore implements AztecAsyncKVStore {
124
124
  return multimap;
125
125
  }
126
126
 
127
- openCounter<K extends Key | Array<string | number>>(_name: string): AztecAsyncCounter<K> {
127
+ openCounter<K extends Key>(_name: string): AztecAsyncCounter<K> {
128
128
  throw new Error('Method not implemented.');
129
129
  }
130
130
 
@@ -187,7 +187,11 @@ export class AztecIndexedDBStore implements AztecAsyncKVStore {
187
187
  return deleteDB(this.#name);
188
188
  }
189
189
 
190
- estimateSize(): { mappingSize: number; actualSize: number; numItems: number } {
191
- return { mappingSize: 0, actualSize: 0, numItems: 0 };
190
+ estimateSize(): Promise<StoreSize> {
191
+ return Promise.resolve({ mappingSize: 0, actualSize: 0, numItems: 0 });
192
+ }
193
+
194
+ close(): Promise<void> {
195
+ return Promise.resolve();
192
196
  }
193
197
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The key type for use with the kv-store
3
3
  */
4
- export type Key = string | number | Array<string | number>;
4
+ export type Key = string | number;
5
5
 
6
6
  /**
7
7
  * A range of keys to iterate over.
@@ -16,3 +16,5 @@ export type Range<K extends Key = Key> = {
16
16
  /** The maximum number of items to iterate over */
17
17
  limit?: number;
18
18
  };
19
+
20
+ export type StoreSize = { mappingSize: number; actualSize: number; numItems: number };
@@ -4,4 +4,4 @@ export * from './counter.js';
4
4
  export * from './singleton.js';
5
5
  export * from './store.js';
6
6
  export * from './set.js';
7
- export { Range } from './common.js';
7
+ export { Range, StoreSize } from './common.js';
@@ -11,13 +11,6 @@ interface AztecBaseMap<K extends Key, V> {
11
11
  */
12
12
  set(key: K, val: V): Promise<void>;
13
13
 
14
- /**
15
- * Atomically swap the value at the given key
16
- * @param key - The key to swap the value at
17
- * @param fn - The function to swap the value with
18
- */
19
- swap(key: K, fn: (val: V | undefined) => V): Promise<void>;
20
-
21
14
  /**
22
15
  * Sets the value at the given key if it does not already exist.
23
16
  * @param key - The key to set the value at
@@ -18,7 +18,7 @@ export function describeAztecMap(
18
18
 
19
19
  beforeEach(async () => {
20
20
  store = await getStore();
21
- map = store.openMultiMap<string | [number, string], string>('test');
21
+ map = store.openMultiMap<string, string>('test');
22
22
  });
23
23
 
24
24
  afterEach(async () => {
@@ -125,21 +125,6 @@ export function describeAztecMap(
125
125
  expect(await getValues('foo')).to.deep.equal(['baz']);
126
126
  });
127
127
 
128
- it('supports tuple keys', async () => {
129
- // Use a new map because key structure has changed
130
- const tupleMap = store.openMap<[number, string], string>('test-tuple');
131
-
132
- await tupleMap.set([5, 'bar'], 'val');
133
- await tupleMap.set([0, 'foo'], 'val');
134
-
135
- expect(await keys(undefined, tupleMap)).to.deep.equal([
136
- [0, 'foo'],
137
- [5, 'bar'],
138
- ]);
139
-
140
- expect(await get([5, 'bar'], tupleMap)).to.equal('val');
141
- });
142
-
143
128
  it('supports range queries', async () => {
144
129
  await map.set('a', 'a');
145
130
  await map.set('b', 'b');
@@ -1,5 +1,5 @@
1
1
  import { type AztecArray, type AztecAsyncArray } from './array.js';
2
- import { type Key } from './common.js';
2
+ import { type Key, type StoreSize } from './common.js';
3
3
  import { type AztecAsyncCounter, type AztecCounter } from './counter.js';
4
4
  import {
5
5
  type AztecAsyncMap,
@@ -94,7 +94,12 @@ export interface AztecKVStore {
94
94
  /**
95
95
  * Estimates the size of the store in bytes.
96
96
  */
97
- estimateSize(): { mappingSize: number; actualSize: number; numItems: number };
97
+ estimateSize(): Promise<StoreSize>;
98
+
99
+ /**
100
+ * Closes the store
101
+ */
102
+ close(): Promise<void>;
98
103
  }
99
104
 
100
105
  export interface AztecAsyncKVStore {
@@ -163,5 +168,10 @@ export interface AztecAsyncKVStore {
163
168
  /**
164
169
  * Estimates the size of the store in bytes.
165
170
  */
166
- estimateSize(): { mappingSize: number; actualSize: number; numItems: number };
171
+ estimateSize(): Promise<StoreSize>;
172
+
173
+ /**
174
+ * Closes the store
175
+ */
176
+ close(): Promise<void>;
167
177
  }
package/src/lmdb/store.ts CHANGED
@@ -7,7 +7,7 @@ import { tmpdir } from 'os';
7
7
  import { join } from 'path';
8
8
 
9
9
  import { type AztecArray, type AztecAsyncArray } from '../interfaces/array.js';
10
- import { type Key } from '../interfaces/common.js';
10
+ import { type Key, type StoreSize } from '../interfaces/common.js';
11
11
  import { type AztecAsyncCounter, type AztecCounter } from '../interfaces/counter.js';
12
12
  import {
13
13
  type AztecAsyncMap,
@@ -216,7 +216,7 @@ export class AztecLmdbStore implements AztecKVStore, AztecAsyncKVStore {
216
216
  }
217
217
  }
218
218
 
219
- estimateSize(): { mappingSize: number; actualSize: number; numItems: number } {
219
+ estimateSize(): Promise<StoreSize> {
220
220
  const stats = this.#rootDb.getStats();
221
221
  // The 'mapSize' is the total amount of virtual address space allocated to the DB (effectively the maximum possible size)
222
222
  // http://www.lmdb.tech/doc/group__mdb.html#a4bde3c8b676457342cba2fe27aed5fbd
@@ -226,11 +226,11 @@ export class AztecLmdbStore implements AztecKVStore, AztecAsyncKVStore {
226
226
  }
227
227
  const dataResult = this.estimateSubDBSize(this.#data);
228
228
  const multiResult = this.estimateSubDBSize(this.#multiMapData);
229
- return {
229
+ return Promise.resolve({
230
230
  mappingSize: mapSize,
231
231
  actualSize: dataResult.actualSize + multiResult.actualSize,
232
232
  numItems: dataResult.numItems + multiResult.numItems,
233
- };
233
+ });
234
234
  }
235
235
 
236
236
  private estimateSubDBSize(db: Database<unknown, Key>): { actualSize: number; numItems: number } {
@@ -0,0 +1,79 @@
1
+ import { EthAddress } from '@aztec/circuits.js';
2
+ import { type Logger, createLogger } from '@aztec/foundation/log';
3
+
4
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises';
5
+ import { tmpdir } from 'os';
6
+ import { join } from 'path';
7
+
8
+ import { type DataStoreConfig } from '../config.js';
9
+ import { AztecLMDBStoreV2 } from './store.js';
10
+
11
+ const ROLLUP_ADDRESS_FILE = 'rollup_address';
12
+ const MAX_READERS = 16;
13
+
14
+ export async function createStore(
15
+ name: string,
16
+ config: DataStoreConfig,
17
+ log: Logger = createLogger('kv-store:lmdb-v2:' + name),
18
+ ): Promise<AztecLMDBStoreV2> {
19
+ const { dataDirectory, l1Contracts } = config;
20
+
21
+ let store: AztecLMDBStoreV2;
22
+ if (typeof dataDirectory !== 'undefined') {
23
+ const subDir = join(dataDirectory, name);
24
+ await mkdir(subDir, { recursive: true });
25
+
26
+ if (l1Contracts) {
27
+ const { rollupAddress } = l1Contracts;
28
+ const localRollupAddress = await readFile(join(subDir, ROLLUP_ADDRESS_FILE), 'utf-8')
29
+ .then(EthAddress.fromString)
30
+ .catch(() => EthAddress.ZERO);
31
+
32
+ if (!localRollupAddress.equals(rollupAddress)) {
33
+ if (!localRollupAddress.isZero()) {
34
+ log.warn(`Rollup address mismatch. Clearing entire database...`, {
35
+ expected: rollupAddress,
36
+ found: localRollupAddress,
37
+ });
38
+
39
+ await rm(subDir, { recursive: true, force: true });
40
+ await mkdir(subDir, { recursive: true });
41
+ }
42
+
43
+ await writeFile(join(subDir, ROLLUP_ADDRESS_FILE), rollupAddress.toString());
44
+ }
45
+ }
46
+
47
+ log.info(
48
+ `Creating ${name} data store at directory ${subDir} with map size ${config.dataStoreMapSizeKB} KB (LMDB v2)`,
49
+ );
50
+ store = await AztecLMDBStoreV2.new(subDir, config.dataStoreMapSizeKB, MAX_READERS, () => Promise.resolve(), log);
51
+ } else {
52
+ store = await openTmpStore(name, true, config.dataStoreMapSizeKB, MAX_READERS, log);
53
+ }
54
+
55
+ return store;
56
+ }
57
+
58
+ export async function openTmpStore(
59
+ name: string,
60
+ ephemeral: boolean = true,
61
+ dbMapSizeKb = 10 * 1_024 * 1_024, // 10GB
62
+ maxReaders = MAX_READERS,
63
+ log: Logger = createLogger('kv-store:lmdb-v2:' + name),
64
+ ): Promise<AztecLMDBStoreV2> {
65
+ const dataDir = await mkdtemp(join(tmpdir(), name + '-'));
66
+ log.debug(`Created temporary data store at: ${dataDir} with size: ${dbMapSizeKb} KB (LMDB v2)`);
67
+
68
+ // pass a cleanup callback because process.on('beforeExit', cleanup) does not work under Jest
69
+ const cleanup = async () => {
70
+ if (ephemeral) {
71
+ await rm(dataDir, { recursive: true, force: true });
72
+ log.debug(`Deleted temporary data store: ${dataDir}`);
73
+ } else {
74
+ log.debug(`Leaving temporary data store: ${dataDir}`);
75
+ }
76
+ };
77
+
78
+ return AztecLMDBStoreV2.new(dataDir, dbMapSizeKb, maxReaders, cleanup, log);
79
+ }
@@ -0,0 +1,2 @@
1
+ export * from './store.js';
2
+ export * from './factory.js';
@@ -0,0 +1,233 @@
1
+ import { Encoder } from 'msgpackr';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncMap, AztecAsyncMultiMap } from '../interfaces/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 LMDBMap<K extends Key, V> implements AztecAsyncMap<K, V> {
10
+ private prefix: string;
11
+ private encoder = new Encoder();
12
+
13
+ constructor(private store: AztecLMDBStoreV2, name: string) {
14
+ this.prefix = `map:${name}`;
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.set(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 strKey = serializeKey(this.prefix, key);
33
+ const exists = !!(await tx.get(strKey));
34
+ if (!exists) {
35
+ await tx.set(strKey, this.encoder.pack(val));
36
+ return true;
37
+ }
38
+ return false;
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Deletes the value at the given key.
44
+ * @param key - The key to delete the value at
45
+ */
46
+ delete(key: K): Promise<void> {
47
+ return execInWriteTx(this.store, tx => tx.remove(serializeKey(this.prefix, key)));
48
+ }
49
+
50
+ getAsync(key: K): Promise<V | undefined> {
51
+ return execInReadTx(this.store, async tx => {
52
+ const val = await tx.get(serializeKey(this.prefix, key));
53
+ return val ? this.encoder.unpack(val) : undefined;
54
+ });
55
+ }
56
+
57
+ hasAsync(key: K): Promise<boolean> {
58
+ return execInReadTx(this.store, async tx => !!(await tx.get(serializeKey(this.prefix, key))));
59
+ }
60
+
61
+ /**
62
+ * Iterates over the map's key-value entries in the key's natural order
63
+ * @param range - The range of keys to iterate over
64
+ */
65
+ async *entriesAsync(range?: Range<K>): AsyncIterableIterator<[K, V]> {
66
+ const reverse = range?.reverse ?? false;
67
+ const startKey = range?.start ? serializeKey(this.prefix, range.start) : minKey(this.prefix);
68
+
69
+ const endKey = range?.end ? serializeKey(this.prefix, range.end) : reverse ? maxKey(this.prefix) : undefined;
70
+
71
+ let tx: ReadTransaction | undefined = this.store.getCurrentWriteTx();
72
+ const shouldClose = !tx;
73
+ tx ??= this.store.getReadTx();
74
+
75
+ try {
76
+ for await (const [key, val] of tx.iterate(
77
+ reverse ? endKey! : startKey,
78
+ reverse ? startKey : endKey,
79
+ reverse,
80
+ range?.limit,
81
+ )) {
82
+ const deserializedKey = deserializeKey<K>(this.prefix, key);
83
+ if (!deserializedKey) {
84
+ break;
85
+ }
86
+ yield [deserializedKey, this.encoder.unpack(val)];
87
+ }
88
+ } finally {
89
+ if (shouldClose) {
90
+ tx.close();
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Iterates over the map's values in the key's natural order
97
+ * @param range - The range of keys to iterate over
98
+ */
99
+ async *valuesAsync(range?: Range<K>): AsyncIterableIterator<V> {
100
+ for await (const [_, value] of this.entriesAsync(range)) {
101
+ yield value;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Iterates over the map's keys in the key's natural order
107
+ * @param range - The range of keys to iterate over
108
+ */
109
+ async *keysAsync(range?: Range<K>): AsyncIterableIterator<K> {
110
+ for await (const [key, _] of this.entriesAsync(range)) {
111
+ yield key;
112
+ }
113
+ }
114
+ }
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
+ }