@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,65 @@
1
+ /** Stores currently synced L2 tips and unfinalized block hashes. */ export class L2TipsStore {
2
+ l2TipsStore;
3
+ l2BlockHashesStore;
4
+ constructor(store, namespace){
5
+ this.l2TipsStore = store.openMap([
6
+ namespace,
7
+ 'l2_tips'
8
+ ].join('_'));
9
+ this.l2BlockHashesStore = store.openMap([
10
+ namespace,
11
+ 'l2_block_hashes'
12
+ ].join('_'));
13
+ }
14
+ getL2BlockHash(number) {
15
+ return this.l2BlockHashesStore.getAsync(number);
16
+ }
17
+ async getL2Tips() {
18
+ return {
19
+ latest: await this.getL2Tip('latest'),
20
+ finalized: await this.getL2Tip('finalized'),
21
+ proven: await this.getL2Tip('proven')
22
+ };
23
+ }
24
+ async getL2Tip(tag) {
25
+ const blockNumber = await this.l2TipsStore.getAsync(tag);
26
+ if (blockNumber === undefined || blockNumber === 0) {
27
+ return {
28
+ number: 0,
29
+ hash: undefined
30
+ };
31
+ }
32
+ const blockHash = await this.l2BlockHashesStore.getAsync(blockNumber);
33
+ if (!blockHash) {
34
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
35
+ }
36
+ return {
37
+ number: blockNumber,
38
+ hash: blockHash
39
+ };
40
+ }
41
+ async handleBlockStreamEvent(event) {
42
+ switch(event.type){
43
+ case 'blocks-added':
44
+ for (const block of event.blocks){
45
+ await this.l2BlockHashesStore.set(block.number, (await block.header.hash()).toString());
46
+ }
47
+ await this.l2TipsStore.set('latest', event.blocks.at(-1).number);
48
+ break;
49
+ case 'chain-pruned':
50
+ await this.l2TipsStore.set('latest', event.blockNumber);
51
+ break;
52
+ case 'chain-proven':
53
+ await this.l2TipsStore.set('proven', event.blockNumber);
54
+ break;
55
+ case 'chain-finalized':
56
+ await this.l2TipsStore.set('finalized', event.blockNumber);
57
+ for await (const key of this.l2BlockHashesStore.keysAsync({
58
+ end: event.blockNumber
59
+ })){
60
+ await this.l2BlockHashesStore.delete(key);
61
+ }
62
+ break;
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,12 @@
1
+ import type { EthAddress } from '@aztec/foundation/eth-address';
2
+ import type { Logger } from '@aztec/foundation/log';
3
+ import type { AztecAsyncKVStore, AztecKVStore } from './interfaces/store.js';
4
+ /**
5
+ * Clears the store if the rollup address does not match the one stored in the database.
6
+ * This is to prevent data from being accidentally shared between different rollup instances.
7
+ * @param store - The store to check
8
+ * @param rollupAddress - The ETH address of the rollup contract
9
+ * @returns A promise that resolves when the store is cleared, or rejects if the rollup address does not match
10
+ */
11
+ export declare function initStoreForRollup<T extends AztecKVStore | AztecAsyncKVStore>(store: T, rollupAddress: EthAddress, log?: Logger): Promise<T>;
12
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG7E;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,iBAAiB,EACjF,KAAK,EAAE,CAAC,EACR,aAAa,EAAE,UAAU,EACzB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
package/dest/utils.js ADDED
@@ -0,0 +1,24 @@
1
+ import { isSyncStore } from './interfaces/utils.js';
2
+ /**
3
+ * Clears the store if the rollup address does not match the one stored in the database.
4
+ * This is to prevent data from being accidentally shared between different rollup instances.
5
+ * @param store - The store to check
6
+ * @param rollupAddress - The ETH address of the rollup contract
7
+ * @returns A promise that resolves when the store is cleared, or rejects if the rollup address does not match
8
+ */ export async function initStoreForRollup(store, rollupAddress, log) {
9
+ if (!rollupAddress) {
10
+ throw new Error('Rollup address is required');
11
+ }
12
+ const rollupAddressValue = store.openSingleton('rollupAddress');
13
+ const rollupAddressString = rollupAddress.toString();
14
+ const storedRollupAddressString = isSyncStore(store) ? rollupAddressValue.get() : await rollupAddressValue.getAsync();
15
+ if (typeof storedRollupAddressString !== 'undefined' && storedRollupAddressString !== rollupAddressString) {
16
+ log?.warn(`Rollup address mismatch. Clearing entire database...`, {
17
+ expected: rollupAddressString,
18
+ found: storedRollupAddressString
19
+ });
20
+ await store.clear();
21
+ }
22
+ await rollupAddressValue.set(rollupAddressString);
23
+ return store;
24
+ }
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@aztec/kv-store",
3
+ "version": "0.0.0-test.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./dest/interfaces/index.js",
7
+ "./lmdb": "./dest/lmdb/index.js",
8
+ "./lmdb-v2": "./dest/lmdb-v2/index.js",
9
+ "./indexeddb": "./dest/indexeddb/index.js",
10
+ "./stores": "./dest/stores/index.js",
11
+ "./config": "./dest/config.js"
12
+ },
13
+ "scripts": {
14
+ "build": "yarn clean && tsc -b",
15
+ "build:dev": "tsc -b --watch",
16
+ "clean": "rm -rf ./dest .tsbuildinfo",
17
+ "formatting": "run -T prettier --check ./src && run -T eslint ./src",
18
+ "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
19
+ "test:node": "NODE_NO_WARNINGS=1 mocha --config ./.mocharc.json",
20
+ "test:browser": "wtr --config ./web-test-runner.config.mjs",
21
+ "test": "yarn test:node && yarn test:browser && true"
22
+ },
23
+ "inherits": [
24
+ "../package.common.json",
25
+ "./package.local.json"
26
+ ],
27
+ "dependencies": {
28
+ "@aztec/ethereum": "0.0.0-test.0",
29
+ "@aztec/foundation": "0.0.0-test.0",
30
+ "@aztec/native": "0.0.0-test.0",
31
+ "@aztec/stdlib": "0.0.0-test.0",
32
+ "idb": "^8.0.0",
33
+ "lmdb": "^3.2.0",
34
+ "msgpackr": "^1.11.2",
35
+ "ordered-binary": "^1.5.3"
36
+ },
37
+ "devDependencies": {
38
+ "@jest/globals": "^29.5.0",
39
+ "@types/chai": "^5.0.1",
40
+ "@types/chai-as-promised": "^8.0.1",
41
+ "@types/jest": "^29.5.0",
42
+ "@types/mocha": "^10.0.10",
43
+ "@types/mocha-each": "^2.0.4",
44
+ "@types/node": "^18.7.23",
45
+ "@types/sinon": "^17.0.3",
46
+ "@web/dev-server-esbuild": "^1.0.3",
47
+ "@web/test-runner": "^0.19.0",
48
+ "@web/test-runner-playwright": "^0.11.0",
49
+ "chai": "^5.1.2",
50
+ "chai-as-promised": "^8.0.1",
51
+ "jest": "^29.5.0",
52
+ "mocha": "^10.8.2",
53
+ "mocha-each": "^2.0.1",
54
+ "sinon": "^19.0.2",
55
+ "ts-node": "^10.9.1",
56
+ "typescript": "^5.0.4"
57
+ },
58
+ "files": [
59
+ "dest",
60
+ "src",
61
+ "!*.test.*"
62
+ ],
63
+ "engines": {
64
+ "node": ">=18"
65
+ },
66
+ "jest": {
67
+ "extensionsToTreatAsEsm": [
68
+ ".ts"
69
+ ],
70
+ "transform": {
71
+ "^.+\\.tsx?$": [
72
+ "@swc/jest",
73
+ {
74
+ "jsc": {
75
+ "parser": {
76
+ "syntax": "typescript",
77
+ "decorators": true
78
+ },
79
+ "transform": {
80
+ "decoratorVersion": "2022-03"
81
+ }
82
+ }
83
+ }
84
+ ]
85
+ },
86
+ "moduleNameMapper": {
87
+ "^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
88
+ },
89
+ "reporters": [
90
+ "default"
91
+ ],
92
+ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
93
+ "rootDir": "./src",
94
+ "testTimeout": 120000,
95
+ "setupFiles": [
96
+ "../../foundation/src/jest/setup.mjs"
97
+ ]
98
+ }
99
+ }
package/src/config.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { l1ContractAddressesMapping } from '@aztec/ethereum/l1-contract-addresses';
2
+ import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
3
+ import type { EthAddress } from '@aztec/foundation/eth-address';
4
+
5
+ export type DataStoreConfig = {
6
+ dataDirectory: string | undefined;
7
+ dataStoreMapSizeKB: number;
8
+ l1Contracts?: { rollupAddress: EthAddress };
9
+ };
10
+
11
+ export const dataConfigMappings: ConfigMappingsType<DataStoreConfig> = {
12
+ dataDirectory: {
13
+ env: 'DATA_DIRECTORY',
14
+ description: 'Optional dir to store data. If omitted will store in memory.',
15
+ },
16
+ dataStoreMapSizeKB: {
17
+ env: 'DATA_STORE_MAP_SIZE_KB',
18
+ description: 'DB mapping size to be applied to all key/value stores',
19
+ ...numberConfigHelper(128 * 1_024 * 1_024), // Defaulted to 128 GB
20
+ },
21
+ l1Contracts: {
22
+ description: 'The deployed L1 contract addresses',
23
+ nested: l1ContractAddressesMapping,
24
+ },
25
+ };
26
+
27
+ /**
28
+ * Returns the archiver configuration from the environment variables.
29
+ * Note: If an environment variable is not set, the default value is used.
30
+ * @returns The archiver configuration.
31
+ */
32
+ export function getDataConfigFromEnv(): DataStoreConfig {
33
+ return getConfigFromMappings<DataStoreConfig>(dataConfigMappings);
34
+ }
@@ -0,0 +1,118 @@
1
+ import type { IDBPDatabase, IDBPObjectStore } from 'idb';
2
+
3
+ import type { AztecAsyncArray } from '../interfaces/array.js';
4
+ import type { AztecIDBSchema } from './store.js';
5
+
6
+ /**
7
+ * A persistent array backed by IndexedDB.
8
+ */
9
+ export class IndexedDBAztecArray<T> implements AztecAsyncArray<T> {
10
+ #_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
11
+ #rootDB: IDBPDatabase<AztecIDBSchema>;
12
+ #container: string;
13
+ #name: string;
14
+
15
+ constructor(rootDB: IDBPDatabase<AztecIDBSchema>, name: string) {
16
+ this.#rootDB = rootDB;
17
+ this.#name = name;
18
+ this.#container = `array:${this.#name}`;
19
+ }
20
+
21
+ set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
22
+ this.#_db = db;
23
+ }
24
+
25
+ get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
26
+ return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
27
+ }
28
+
29
+ async lengthAsync(): Promise<number> {
30
+ return (
31
+ (await this.db
32
+ .index('key')
33
+ .count(IDBKeyRange.bound([this.#container, this.#name], [this.#container, this.#name]))) ?? 0
34
+ );
35
+ }
36
+
37
+ async push(...vals: T[]): Promise<number> {
38
+ let length = await this.lengthAsync();
39
+ for (const val of vals) {
40
+ await this.db.put({
41
+ value: val,
42
+ container: this.#container,
43
+ key: this.#name,
44
+ keyCount: length + 1,
45
+ slot: this.#slot(length),
46
+ });
47
+ length += 1;
48
+ }
49
+ return length;
50
+ }
51
+
52
+ async pop(): Promise<T | undefined> {
53
+ const length = await this.lengthAsync();
54
+ if (length === 0) {
55
+ return undefined;
56
+ }
57
+
58
+ const slot = this.#slot(length - 1);
59
+ const data = await this.db.get(slot);
60
+ await this.db.delete(slot);
61
+
62
+ return data?.value;
63
+ }
64
+
65
+ async atAsync(index: number): Promise<T | undefined> {
66
+ const length = await this.lengthAsync();
67
+
68
+ if (index < 0) {
69
+ index = length + index;
70
+ }
71
+
72
+ const data = await this.db.get(this.#slot(index));
73
+ return data?.value;
74
+ }
75
+
76
+ async setAt(index: number, val: T): Promise<boolean> {
77
+ const length = await this.lengthAsync();
78
+
79
+ if (index < 0) {
80
+ index = length + index;
81
+ }
82
+
83
+ if (index < 0 || index >= length) {
84
+ return Promise.resolve(false);
85
+ }
86
+
87
+ await this.db.put({
88
+ value: val,
89
+ container: this.#container,
90
+ key: this.#name,
91
+ keyCount: index + 1,
92
+ slot: this.#slot(index),
93
+ });
94
+ return true;
95
+ }
96
+
97
+ async *entriesAsync(): AsyncIterableIterator<[number, T]> {
98
+ const index = this.db.index('key');
99
+ const rangeQuery = IDBKeyRange.bound([this.#container, this.#name], [this.#container, this.#name]);
100
+ for await (const cursor of index.iterate(rangeQuery)) {
101
+ yield [cursor.value.keyCount - 1, cursor.value.value] as [number, T];
102
+ }
103
+ }
104
+
105
+ async *valuesAsync(): AsyncIterableIterator<T> {
106
+ for await (const [_, value] of this.entriesAsync()) {
107
+ yield value;
108
+ }
109
+ }
110
+
111
+ [Symbol.asyncIterator](): AsyncIterableIterator<T> {
112
+ return this.valuesAsync();
113
+ }
114
+
115
+ #slot(index: number): string {
116
+ return `array:${this.#name}:slot:${index}`;
117
+ }
118
+ }
@@ -0,0 +1,29 @@
1
+ import { type Logger, createLogger } from '@aztec/foundation/log';
2
+
3
+ import type { DataStoreConfig } from '../config.js';
4
+ import { initStoreForRollup } from '../utils.js';
5
+ import { AztecIndexedDBStore } from './store.js';
6
+
7
+ export { AztecIndexedDBStore } from './store.js';
8
+
9
+ export async function createStore(name: string, config: DataStoreConfig, log: Logger = createLogger('kv-store')) {
10
+ let { dataDirectory } = config;
11
+ if (typeof dataDirectory !== 'undefined') {
12
+ dataDirectory = `${dataDirectory}/${name}`;
13
+ }
14
+
15
+ log.info(
16
+ dataDirectory
17
+ ? `Creating ${name} data store at directory ${dataDirectory} with map size ${config.dataStoreMapSizeKB} KB`
18
+ : `Creating ${name} ephemeral data store with map size ${config.dataStoreMapSizeKB} KB`,
19
+ );
20
+ const store = await AztecIndexedDBStore.open(createLogger('kv-store:indexeddb'), dataDirectory ?? '', false);
21
+ if (config.l1Contracts?.rollupAddress) {
22
+ return initStoreForRollup(store, config.l1Contracts.rollupAddress, log);
23
+ }
24
+ return store;
25
+ }
26
+
27
+ export function openTmpStore(ephemeral: boolean = false): Promise<AztecIndexedDBStore> {
28
+ return AztecIndexedDBStore.open(createLogger('kv-store:indexeddb'), undefined, ephemeral);
29
+ }
@@ -0,0 +1,142 @@
1
+ import type { IDBPDatabase, IDBPObjectStore } from 'idb';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncMultiMap } from '../interfaces/map.js';
5
+ import type { AztecIDBSchema } from './store.js';
6
+
7
+ /**
8
+ * A map backed by IndexedDB.
9
+ */
10
+ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K, V> {
11
+ protected name: string;
12
+ #container: string;
13
+
14
+ #_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
15
+ #rootDB: IDBPDatabase<AztecIDBSchema>;
16
+
17
+ constructor(rootDB: IDBPDatabase<AztecIDBSchema>, mapName: string) {
18
+ this.name = mapName;
19
+ this.#container = `map:${mapName}`;
20
+ this.#rootDB = rootDB;
21
+ }
22
+
23
+ set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
24
+ this.#_db = db;
25
+ }
26
+
27
+ get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
28
+ return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
29
+ }
30
+
31
+ async getAsync(key: K): Promise<V | undefined> {
32
+ const data = await this.db.get(this.#slot(key));
33
+ return data?.value as V;
34
+ }
35
+
36
+ async *getValuesAsync(key: K): AsyncIterableIterator<V> {
37
+ const index = this.db.index('keyCount');
38
+ const rangeQuery = IDBKeyRange.bound(
39
+ [this.#container, this.#normalizeKey(key), 0],
40
+ [this.#container, this.#normalizeKey(key), Number.MAX_SAFE_INTEGER],
41
+ false,
42
+ false,
43
+ );
44
+ for await (const cursor of index.iterate(rangeQuery)) {
45
+ yield cursor.value.value as V;
46
+ }
47
+ }
48
+
49
+ async hasAsync(key: K): Promise<boolean> {
50
+ const result = (await this.getAsync(key)) !== undefined;
51
+ return result;
52
+ }
53
+
54
+ async set(key: K, val: V): Promise<void> {
55
+ const count = await this.db
56
+ .index('key')
57
+ .count(IDBKeyRange.bound([this.#container, this.#normalizeKey(key)], [this.#container, this.#normalizeKey(key)]));
58
+ await this.db.put({
59
+ value: val,
60
+ container: this.#container,
61
+ key: this.#normalizeKey(key),
62
+ keyCount: count + 1,
63
+ slot: this.#slot(key, count),
64
+ });
65
+ }
66
+
67
+ swap(_key: K, _fn: (val: V | undefined) => V): Promise<void> {
68
+ throw new Error('Not implemented');
69
+ }
70
+
71
+ async setIfNotExists(key: K, val: V): Promise<boolean> {
72
+ if (!(await this.hasAsync(key))) {
73
+ await this.set(key, val);
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ async delete(key: K): Promise<void> {
80
+ await this.db.delete(this.#slot(key));
81
+ }
82
+
83
+ async deleteValue(key: K, val: V): Promise<void> {
84
+ const index = this.db.index('keyCount');
85
+ const rangeQuery = IDBKeyRange.bound(
86
+ [this.#container, this.#normalizeKey(key), 0],
87
+ [this.#container, this.#normalizeKey(key), Number.MAX_SAFE_INTEGER],
88
+ false,
89
+ false,
90
+ );
91
+ for await (const cursor of index.iterate(rangeQuery)) {
92
+ if (JSON.stringify(cursor.value.value) === JSON.stringify(val)) {
93
+ await cursor.delete();
94
+ return;
95
+ }
96
+ }
97
+ }
98
+
99
+ async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<[K, V]> {
100
+ const index = this.db.index('key');
101
+ const rangeQuery = IDBKeyRange.bound(
102
+ [this.#container, range.start ?? ''],
103
+ [this.#container, range.end ?? '\uffff'],
104
+ !!range.reverse,
105
+ !range.reverse,
106
+ );
107
+ let count = 0;
108
+ for await (const cursor of index.iterate(rangeQuery, range.reverse ? 'prev' : 'next')) {
109
+ if (range.limit && count >= range.limit) {
110
+ return;
111
+ }
112
+ yield [cursor.value.key, cursor.value.value] as [K, V];
113
+ count++;
114
+ }
115
+ }
116
+
117
+ async *valuesAsync(range: Range<K> = {}): AsyncIterableIterator<V> {
118
+ for await (const [_, value] of this.entriesAsync(range)) {
119
+ yield value;
120
+ }
121
+ }
122
+
123
+ async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
124
+ for await (const [key, _] of this.entriesAsync(range)) {
125
+ yield this.#denormalizeKey(key as string);
126
+ }
127
+ }
128
+
129
+ #denormalizeKey(key: string): K {
130
+ const denormalizedKey = (key as string).split(',').map(part => (isNaN(parseInt(part)) ? part : parseInt(part)));
131
+ return (denormalizedKey.length > 1 ? denormalizedKey : key) as K;
132
+ }
133
+
134
+ #normalizeKey(key: K): string {
135
+ const arrayKey = Array.isArray(key) ? key : [key];
136
+ return arrayKey.join(',');
137
+ }
138
+
139
+ #slot(key: K, index: number = 0): string {
140
+ return `map:${this.name}:slot:${this.#normalizeKey(key)}:${index}`;
141
+ }
142
+ }
@@ -0,0 +1,37 @@
1
+ import type { IDBPDatabase, IDBPObjectStore } from 'idb';
2
+
3
+ import type { Key, Range } from '../interfaces/common.js';
4
+ import type { AztecAsyncSet } from '../interfaces/set.js';
5
+ import { IndexedDBAztecMap } from './map.js';
6
+ import type { AztecIDBSchema } from './store.js';
7
+
8
+ /**
9
+ * A set backed by IndexedDB.
10
+ */
11
+ export class IndexedDBAztecSet<K extends Key> implements AztecAsyncSet<K> {
12
+ private map: IndexedDBAztecMap<K, boolean>;
13
+
14
+ constructor(rootDb: IDBPDatabase<AztecIDBSchema>, mapName: string) {
15
+ this.map = new IndexedDBAztecMap(rootDb, mapName);
16
+ }
17
+
18
+ set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
19
+ this.map.db = db;
20
+ }
21
+
22
+ hasAsync(key: K): Promise<boolean> {
23
+ return this.map.hasAsync(key);
24
+ }
25
+
26
+ add(key: K): Promise<void> {
27
+ return this.map.set(key, true);
28
+ }
29
+
30
+ delete(key: K): Promise<void> {
31
+ return this.map.delete(key);
32
+ }
33
+
34
+ async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
35
+ yield* this.map.keysAsync(range);
36
+ }
37
+ }
@@ -0,0 +1,49 @@
1
+ import type { IDBPDatabase, IDBPObjectStore } from 'idb';
2
+
3
+ import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
4
+ import type { AztecIDBSchema } from './store.js';
5
+
6
+ /**
7
+ * Stores a single value in IndexedDB.
8
+ */
9
+ export class IndexedDBAztecSingleton<T> implements AztecAsyncSingleton<T> {
10
+ #_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
11
+ #rootDB: IDBPDatabase<AztecIDBSchema>;
12
+ #container: string;
13
+ #slot: string;
14
+
15
+ constructor(rootDB: IDBPDatabase<AztecIDBSchema>, name: string) {
16
+ this.#rootDB = rootDB;
17
+ this.#container = `singleton:${name}`;
18
+ this.#slot = `singleton:${name}:value`;
19
+ }
20
+
21
+ set db(db: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> | undefined) {
22
+ this.#_db = db;
23
+ }
24
+
25
+ get db(): IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'> {
26
+ return this.#_db ? this.#_db : this.#rootDB.transaction('data', 'readwrite').store;
27
+ }
28
+
29
+ async getAsync(): Promise<T | undefined> {
30
+ const data = await this.db.get(this.#slot);
31
+ return data?.value as T;
32
+ }
33
+
34
+ async set(val: T): Promise<boolean> {
35
+ const result = await this.db.put({
36
+ container: this.#container,
37
+ slot: this.#slot,
38
+ key: this.#slot,
39
+ keyCount: 1,
40
+ value: val,
41
+ });
42
+ return result !== undefined;
43
+ }
44
+
45
+ async delete(): Promise<boolean> {
46
+ await this.db.delete(this.#slot);
47
+ return true;
48
+ }
49
+ }