@aztec/kv-store 0.0.0-test.1 → 0.0.1-commit.b655e406
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.
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -3
- package/dest/indexeddb/array.d.ts +2 -1
- package/dest/indexeddb/array.d.ts.map +1 -1
- package/dest/indexeddb/array.js +3 -0
- package/dest/indexeddb/index.js +1 -1
- package/dest/indexeddb/map.d.ts +11 -5
- package/dest/indexeddb/map.d.ts.map +1 -1
- package/dest/indexeddb/map.js +38 -60
- package/dest/indexeddb/multi_map.d.ts +12 -0
- package/dest/indexeddb/multi_map.d.ts.map +1 -0
- package/dest/indexeddb/multi_map.js +78 -0
- package/dest/indexeddb/singleton.d.ts +2 -1
- package/dest/indexeddb/singleton.d.ts.map +1 -1
- package/dest/indexeddb/singleton.js +3 -1
- package/dest/indexeddb/store.d.ts +12 -13
- package/dest/indexeddb/store.d.ts.map +1 -1
- package/dest/indexeddb/store.js +45 -42
- package/dest/interfaces/array.d.ts +4 -3
- package/dest/interfaces/array.d.ts.map +1 -1
- package/dest/interfaces/array.js +1 -3
- package/dest/interfaces/common.d.ts +10 -8
- package/dest/interfaces/common.d.ts.map +1 -1
- package/dest/interfaces/common.js +8 -3
- package/dest/interfaces/index.d.ts +3 -1
- package/dest/interfaces/index.d.ts.map +1 -1
- package/dest/interfaces/index.js +2 -0
- package/dest/interfaces/map.d.ts +20 -48
- package/dest/interfaces/map.d.ts.map +1 -1
- package/dest/interfaces/map.js +1 -1
- package/dest/interfaces/map_test_suite.d.ts.map +1 -1
- package/dest/interfaces/map_test_suite.js +135 -70
- package/dest/interfaces/multi_map.d.ts +35 -0
- package/dest/interfaces/multi_map.d.ts.map +1 -0
- package/dest/interfaces/multi_map.js +3 -0
- package/dest/interfaces/multi_map_test_suite.d.ts +3 -0
- package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -0
- package/dest/interfaces/multi_map_test_suite.js +245 -0
- package/dest/interfaces/store.d.ts +17 -42
- package/dest/interfaces/store.d.ts.map +1 -1
- package/dest/interfaces/utils.d.ts +1 -0
- package/dest/interfaces/utils.d.ts.map +1 -1
- package/dest/interfaces/utils.js +2 -1
- package/dest/lmdb/array.d.ts +2 -1
- package/dest/lmdb/array.d.ts.map +1 -1
- package/dest/lmdb/index.js +2 -2
- package/dest/lmdb/map.d.ts +10 -22
- package/dest/lmdb/map.d.ts.map +1 -1
- package/dest/lmdb/map.js +15 -81
- package/dest/lmdb/multi_map.d.ts +12 -0
- package/dest/lmdb/multi_map.d.ts.map +1 -0
- package/dest/lmdb/multi_map.js +29 -0
- package/dest/lmdb/store.d.ts +7 -22
- package/dest/lmdb/store.d.ts.map +1 -1
- package/dest/lmdb/store.js +11 -31
- package/dest/lmdb-v2/array.d.ts +2 -1
- package/dest/lmdb-v2/array.d.ts.map +1 -1
- package/dest/lmdb-v2/array.js +1 -0
- package/dest/lmdb-v2/factory.d.ts +1 -1
- package/dest/lmdb-v2/factory.d.ts.map +1 -1
- package/dest/lmdb-v2/factory.js +16 -6
- package/dest/lmdb-v2/map.d.ts +10 -43
- package/dest/lmdb-v2/map.d.ts.map +1 -1
- package/dest/lmdb-v2/map.js +17 -103
- package/dest/lmdb-v2/message.d.ts +23 -4
- package/dest/lmdb-v2/message.d.ts.map +1 -1
- package/dest/lmdb-v2/message.js +6 -4
- package/dest/lmdb-v2/multi_map.d.ts +51 -0
- package/dest/lmdb-v2/multi_map.d.ts.map +1 -0
- package/dest/lmdb-v2/multi_map.js +113 -0
- package/dest/lmdb-v2/read_transaction.d.ts +2 -0
- package/dest/lmdb-v2/read_transaction.d.ts.map +1 -1
- package/dest/lmdb-v2/read_transaction.js +34 -0
- package/dest/lmdb-v2/set.d.ts +15 -0
- package/dest/lmdb-v2/set.d.ts.map +1 -0
- package/dest/lmdb-v2/set.js +23 -0
- package/dest/lmdb-v2/singleton.d.ts.map +1 -1
- package/dest/lmdb-v2/singleton.js +1 -0
- package/dest/lmdb-v2/store.d.ts +9 -8
- package/dest/lmdb-v2/store.d.ts.map +1 -1
- package/dest/lmdb-v2/store.js +19 -7
- package/dest/lmdb-v2/utils.d.ts +2 -4
- package/dest/lmdb-v2/utils.d.ts.map +1 -1
- package/dest/lmdb-v2/write_transaction.d.ts +2 -4
- package/dest/lmdb-v2/write_transaction.d.ts.map +1 -1
- package/dest/stores/index.d.ts +1 -0
- package/dest/stores/index.d.ts.map +1 -1
- package/dest/stores/index.js +1 -0
- package/dest/stores/l2_tips_store.d.ts +2 -1
- package/dest/stores/l2_tips_store.d.ts.map +1 -1
- package/dest/stores/l2_tips_store.js +18 -9
- package/package.json +18 -14
- package/src/config.ts +6 -4
- package/src/indexeddb/array.ts +5 -1
- package/src/indexeddb/index.ts +2 -2
- package/src/indexeddb/map.ts +35 -53
- package/src/indexeddb/multi_map.ts +79 -0
- package/src/indexeddb/singleton.ts +4 -1
- package/src/indexeddb/store.ts +66 -56
- package/src/interfaces/array.ts +5 -3
- package/src/interfaces/common.ts +20 -9
- package/src/interfaces/index.ts +3 -1
- package/src/interfaces/map.ts +19 -53
- package/src/interfaces/map_test_suite.ts +73 -44
- package/src/interfaces/multi_map.ts +38 -0
- package/src/interfaces/multi_map_test_suite.ts +242 -0
- package/src/interfaces/store.ts +18 -53
- package/src/interfaces/utils.ts +1 -0
- package/src/lmdb/array.ts +2 -1
- package/src/lmdb/index.ts +3 -3
- package/src/lmdb/map.ts +23 -94
- package/src/lmdb/multi_map.ts +35 -0
- package/src/lmdb/store.ts +23 -47
- package/src/lmdb-v2/array.ts +7 -2
- package/src/lmdb-v2/factory.ts +17 -10
- package/src/lmdb-v2/map.ts +29 -126
- package/src/lmdb-v2/message.ts +23 -0
- package/src/lmdb-v2/multi_map.ts +141 -0
- package/src/lmdb-v2/read_transaction.ts +40 -0
- package/src/lmdb-v2/set.ts +33 -0
- package/src/lmdb-v2/singleton.ts +5 -1
- package/src/lmdb-v2/store.ts +22 -14
- package/src/lmdb-v2/write_transaction.ts +2 -2
- package/src/stores/index.ts +2 -0
- package/src/stores/l2_tips_store.ts +18 -9
- package/dest/interfaces/store_test_suite.d.ts +0 -3
- package/dest/interfaces/store_test_suite.d.ts.map +0 -1
- package/dest/interfaces/store_test_suite.js +0 -37
- package/src/interfaces/store_test_suite.ts +0 -56
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Stores currently synced L2 tips and unfinalized block hashes. */ export class
|
|
1
|
+
/** Stores currently synced L2 tips and unfinalized block hashes. */ export class L2TipsKVStore {
|
|
2
2
|
l2TipsStore;
|
|
3
3
|
l2BlockHashesStore;
|
|
4
4
|
constructor(store, namespace){
|
|
@@ -41,25 +41,34 @@
|
|
|
41
41
|
async handleBlockStreamEvent(event) {
|
|
42
42
|
switch(event.type){
|
|
43
43
|
case 'blocks-added':
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
{
|
|
45
|
+
const blocks = event.blocks.map((b)=>b.block);
|
|
46
|
+
for (const block of blocks){
|
|
47
|
+
await this.l2BlockHashesStore.set(block.number, (await block.hash()).toString());
|
|
48
|
+
}
|
|
49
|
+
await this.l2TipsStore.set('latest', blocks.at(-1).number);
|
|
50
|
+
break;
|
|
46
51
|
}
|
|
47
|
-
await this.l2TipsStore.set('latest', event.blocks.at(-1).number);
|
|
48
|
-
break;
|
|
49
52
|
case 'chain-pruned':
|
|
50
|
-
await this.
|
|
53
|
+
await this.saveTag('latest', event.block);
|
|
51
54
|
break;
|
|
52
55
|
case 'chain-proven':
|
|
53
|
-
await this.
|
|
56
|
+
await this.saveTag('proven', event.block);
|
|
54
57
|
break;
|
|
55
58
|
case 'chain-finalized':
|
|
56
|
-
await this.
|
|
59
|
+
await this.saveTag('finalized', event.block);
|
|
57
60
|
for await (const key of this.l2BlockHashesStore.keysAsync({
|
|
58
|
-
end: event.
|
|
61
|
+
end: event.block.number
|
|
59
62
|
})){
|
|
60
63
|
await this.l2BlockHashesStore.delete(key);
|
|
61
64
|
}
|
|
62
65
|
break;
|
|
63
66
|
}
|
|
64
67
|
}
|
|
68
|
+
async saveTag(name, block) {
|
|
69
|
+
await this.l2TipsStore.set(name, block.number);
|
|
70
|
+
if (block.hash) {
|
|
71
|
+
await this.l2BlockHashesStore.set(block.number, block.hash);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
65
74
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/kv-store",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1-commit.b655e406",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/interfaces/index.js",
|
|
@@ -14,46 +14,46 @@
|
|
|
14
14
|
"build": "yarn clean && tsc -b",
|
|
15
15
|
"build:dev": "tsc -b --watch",
|
|
16
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
17
|
"test:node": "NODE_NO_WARNINGS=1 mocha --config ./.mocharc.json",
|
|
20
18
|
"test:browser": "wtr --config ./web-test-runner.config.mjs",
|
|
21
|
-
"test": "yarn test:node
|
|
19
|
+
"test": "yarn test:node",
|
|
20
|
+
"test:jest": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
22
21
|
},
|
|
23
22
|
"inherits": [
|
|
24
23
|
"../package.common.json",
|
|
25
24
|
"./package.local.json"
|
|
26
25
|
],
|
|
27
26
|
"dependencies": {
|
|
28
|
-
"@aztec/ethereum": "0.0.
|
|
29
|
-
"@aztec/foundation": "0.0.
|
|
30
|
-
"@aztec/native": "0.0.
|
|
31
|
-
"@aztec/stdlib": "0.0.
|
|
27
|
+
"@aztec/ethereum": "0.0.1-commit.b655e406",
|
|
28
|
+
"@aztec/foundation": "0.0.1-commit.b655e406",
|
|
29
|
+
"@aztec/native": "0.0.1-commit.b655e406",
|
|
30
|
+
"@aztec/stdlib": "0.0.1-commit.b655e406",
|
|
32
31
|
"idb": "^8.0.0",
|
|
33
32
|
"lmdb": "^3.2.0",
|
|
34
33
|
"msgpackr": "^1.11.2",
|
|
34
|
+
"ohash": "^2.0.11",
|
|
35
35
|
"ordered-binary": "^1.5.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@jest/globals": "^
|
|
38
|
+
"@jest/globals": "^30.0.0",
|
|
39
39
|
"@types/chai": "^5.0.1",
|
|
40
40
|
"@types/chai-as-promised": "^8.0.1",
|
|
41
|
-
"@types/jest": "^
|
|
41
|
+
"@types/jest": "^30.0.0",
|
|
42
42
|
"@types/mocha": "^10.0.10",
|
|
43
43
|
"@types/mocha-each": "^2.0.4",
|
|
44
|
-
"@types/node": "^
|
|
44
|
+
"@types/node": "^22.15.17",
|
|
45
45
|
"@types/sinon": "^17.0.3",
|
|
46
46
|
"@web/dev-server-esbuild": "^1.0.3",
|
|
47
47
|
"@web/test-runner": "^0.19.0",
|
|
48
48
|
"@web/test-runner-playwright": "^0.11.0",
|
|
49
49
|
"chai": "^5.1.2",
|
|
50
50
|
"chai-as-promised": "^8.0.1",
|
|
51
|
-
"jest": "^
|
|
51
|
+
"jest": "^30.0.0",
|
|
52
52
|
"mocha": "^10.8.2",
|
|
53
53
|
"mocha-each": "^2.0.1",
|
|
54
54
|
"sinon": "^19.0.2",
|
|
55
55
|
"ts-node": "^10.9.1",
|
|
56
|
-
"typescript": "^5.
|
|
56
|
+
"typescript": "^5.3.3"
|
|
57
57
|
},
|
|
58
58
|
"files": [
|
|
59
59
|
"dest",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"!*.test.*"
|
|
62
62
|
],
|
|
63
63
|
"engines": {
|
|
64
|
-
"node": ">=
|
|
64
|
+
"node": ">=20.10"
|
|
65
65
|
},
|
|
66
66
|
"jest": {
|
|
67
67
|
"extensionsToTreatAsEsm": [
|
|
@@ -94,6 +94,10 @@
|
|
|
94
94
|
"testTimeout": 120000,
|
|
95
95
|
"setupFiles": [
|
|
96
96
|
"../../foundation/src/jest/setup.mjs"
|
|
97
|
+
],
|
|
98
|
+
"testEnvironment": "../../foundation/src/jest/env.mjs",
|
|
99
|
+
"setupFilesAfterEnv": [
|
|
100
|
+
"../../foundation/src/jest/setupAfterEnv.mjs"
|
|
97
101
|
]
|
|
98
102
|
}
|
|
99
103
|
}
|
package/src/config.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
4
4
|
|
|
5
5
|
export type DataStoreConfig = {
|
|
6
6
|
dataDirectory: string | undefined;
|
|
7
|
-
|
|
7
|
+
dataStoreMapSizeKb: number;
|
|
8
8
|
l1Contracts?: { rollupAddress: EthAddress };
|
|
9
9
|
};
|
|
10
10
|
|
|
@@ -13,14 +13,16 @@ export const dataConfigMappings: ConfigMappingsType<DataStoreConfig> = {
|
|
|
13
13
|
env: 'DATA_DIRECTORY',
|
|
14
14
|
description: 'Optional dir to store data. If omitted will store in memory.',
|
|
15
15
|
},
|
|
16
|
-
|
|
16
|
+
dataStoreMapSizeKb: {
|
|
17
17
|
env: 'DATA_STORE_MAP_SIZE_KB',
|
|
18
|
-
description: '
|
|
18
|
+
description: 'The maximum possible size of a data store DB in KB. Can be overridden by component-specific options.',
|
|
19
19
|
...numberConfigHelper(128 * 1_024 * 1_024), // Defaulted to 128 GB
|
|
20
20
|
},
|
|
21
21
|
l1Contracts: {
|
|
22
22
|
description: 'The deployed L1 contract addresses',
|
|
23
|
-
nested:
|
|
23
|
+
nested: {
|
|
24
|
+
rollupAddress: l1ContractAddressesMapping.rollupAddress,
|
|
25
|
+
},
|
|
24
26
|
},
|
|
25
27
|
};
|
|
26
28
|
|
package/src/indexeddb/array.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
import { hash } from 'ohash';
|
|
2
3
|
|
|
3
4
|
import type { AztecAsyncArray } from '../interfaces/array.js';
|
|
5
|
+
import type { Value } from '../interfaces/common.js';
|
|
4
6
|
import type { AztecIDBSchema } from './store.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* A persistent array backed by IndexedDB.
|
|
8
10
|
*/
|
|
9
|
-
export class IndexedDBAztecArray<T> implements AztecAsyncArray<T> {
|
|
11
|
+
export class IndexedDBAztecArray<T extends Value> implements AztecAsyncArray<T> {
|
|
10
12
|
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
11
13
|
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
12
14
|
#container: string;
|
|
@@ -39,6 +41,7 @@ export class IndexedDBAztecArray<T> implements AztecAsyncArray<T> {
|
|
|
39
41
|
for (const val of vals) {
|
|
40
42
|
await this.db.put({
|
|
41
43
|
value: val,
|
|
44
|
+
hash: hash(val),
|
|
42
45
|
container: this.#container,
|
|
43
46
|
key: this.#name,
|
|
44
47
|
keyCount: length + 1,
|
|
@@ -86,6 +89,7 @@ export class IndexedDBAztecArray<T> implements AztecAsyncArray<T> {
|
|
|
86
89
|
|
|
87
90
|
await this.db.put({
|
|
88
91
|
value: val,
|
|
92
|
+
hash: hash(val),
|
|
89
93
|
container: this.#container,
|
|
90
94
|
key: this.#name,
|
|
91
95
|
keyCount: index + 1,
|
package/src/indexeddb/index.ts
CHANGED
|
@@ -14,8 +14,8 @@ export async function createStore(name: string, config: DataStoreConfig, log: Lo
|
|
|
14
14
|
|
|
15
15
|
log.info(
|
|
16
16
|
dataDirectory
|
|
17
|
-
? `Creating ${name} data store at directory ${dataDirectory} with map size ${config.
|
|
18
|
-
: `Creating ${name} ephemeral data store with map size ${config.
|
|
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
19
|
);
|
|
20
20
|
const store = await AztecIndexedDBStore.open(createLogger('kv-store:indexeddb'), dataDirectory ?? '', false);
|
|
21
21
|
if (config.l1Contracts?.rollupAddress) {
|
package/src/indexeddb/map.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
import { hash } from 'ohash';
|
|
2
3
|
|
|
3
|
-
import type { Key, Range } from '../interfaces/common.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { Key, Range, Value } from '../interfaces/common.js';
|
|
5
|
+
import type { AztecAsyncMap } from '../interfaces/map.js';
|
|
5
6
|
import type { AztecIDBSchema } from './store.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* A map backed by IndexedDB.
|
|
9
10
|
*/
|
|
10
|
-
export class IndexedDBAztecMap<K extends Key, V> implements
|
|
11
|
+
export class IndexedDBAztecMap<K extends Key, V extends Value> implements AztecAsyncMap<K, V> {
|
|
11
12
|
protected name: string;
|
|
12
|
-
|
|
13
|
+
protected container: string;
|
|
13
14
|
|
|
14
15
|
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
15
16
|
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
16
17
|
|
|
17
18
|
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, mapName: string) {
|
|
18
19
|
this.name = mapName;
|
|
19
|
-
this
|
|
20
|
+
this.container = `map:${mapName}`;
|
|
20
21
|
this.#rootDB = rootDB;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -29,41 +30,38 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async getAsync(key: K): Promise<V | undefined> {
|
|
32
|
-
const data = await this.db.get(this
|
|
33
|
+
const data = await this.db.get(this.slot(key));
|
|
33
34
|
return data?.value as V;
|
|
34
35
|
}
|
|
35
36
|
|
|
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
37
|
async hasAsync(key: K): Promise<boolean> {
|
|
50
38
|
const result = (await this.getAsync(key)) !== undefined;
|
|
51
39
|
return result;
|
|
52
40
|
}
|
|
53
41
|
|
|
42
|
+
async sizeAsync(): Promise<number> {
|
|
43
|
+
const index = this.db.index('key');
|
|
44
|
+
const rangeQuery = IDBKeyRange.bound([this.container, ''], [this.container, '\uffff']);
|
|
45
|
+
return await index.count(rangeQuery);
|
|
46
|
+
}
|
|
47
|
+
|
|
54
48
|
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
49
|
await this.db.put({
|
|
59
50
|
value: val,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
hash: hash(val),
|
|
52
|
+
container: this.container,
|
|
53
|
+
key: this.normalizeKey(key),
|
|
54
|
+
keyCount: 1,
|
|
55
|
+
slot: this.slot(key),
|
|
64
56
|
});
|
|
65
57
|
}
|
|
66
58
|
|
|
59
|
+
async setMany(entries: { key: K; value: V }[]): Promise<void> {
|
|
60
|
+
for (const { key, value } of entries) {
|
|
61
|
+
await this.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
67
65
|
swap(_key: K, _fn: (val: V | undefined) => V): Promise<void> {
|
|
68
66
|
throw new Error('Not implemented');
|
|
69
67
|
}
|
|
@@ -77,30 +75,14 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
async delete(key: K): Promise<void> {
|
|
80
|
-
await this.db.delete(this
|
|
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
|
-
}
|
|
78
|
+
await this.db.delete(this.slot(key));
|
|
97
79
|
}
|
|
98
80
|
|
|
99
81
|
async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<[K, V]> {
|
|
100
82
|
const index = this.db.index('key');
|
|
101
83
|
const rangeQuery = IDBKeyRange.bound(
|
|
102
|
-
[this
|
|
103
|
-
[this
|
|
84
|
+
[this.container, range.start ? this.normalizeKey(range.start) : ''],
|
|
85
|
+
[this.container, range.end ? this.normalizeKey(range.end) : '\uffff'],
|
|
104
86
|
!!range.reverse,
|
|
105
87
|
!range.reverse,
|
|
106
88
|
);
|
|
@@ -109,7 +91,7 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
109
91
|
if (range.limit && count >= range.limit) {
|
|
110
92
|
return;
|
|
111
93
|
}
|
|
112
|
-
yield [cursor.value.key, cursor.value.value] as [K, V];
|
|
94
|
+
yield [this.#denormalizeKey(cursor.value.key), cursor.value.value] as [K, V];
|
|
113
95
|
count++;
|
|
114
96
|
}
|
|
115
97
|
}
|
|
@@ -122,21 +104,21 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
122
104
|
|
|
123
105
|
async *keysAsync(range: Range<K> = {}): AsyncIterableIterator<K> {
|
|
124
106
|
for await (const [key, _] of this.entriesAsync(range)) {
|
|
125
|
-
yield
|
|
107
|
+
yield key;
|
|
126
108
|
}
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
#denormalizeKey(key: string): K {
|
|
130
|
-
const denormalizedKey =
|
|
131
|
-
return (denormalizedKey.length > 1 ? denormalizedKey :
|
|
112
|
+
const denormalizedKey = key.split(',').map(part => (part.startsWith('n_') ? Number(part.slice(2)) : part));
|
|
113
|
+
return (denormalizedKey.length > 1 ? denormalizedKey : denormalizedKey[0]) as K;
|
|
132
114
|
}
|
|
133
115
|
|
|
134
|
-
|
|
116
|
+
protected normalizeKey(key: K): string {
|
|
135
117
|
const arrayKey = Array.isArray(key) ? key : [key];
|
|
136
|
-
return arrayKey.join(',');
|
|
118
|
+
return (arrayKey as K[]).map((element: K) => (typeof element === 'number' ? `n_${element}` : element)).join(',');
|
|
137
119
|
}
|
|
138
120
|
|
|
139
|
-
|
|
140
|
-
return `map:${this.name}:slot:${this
|
|
121
|
+
protected slot(key: K, index: number = 0): string {
|
|
122
|
+
return `map:${this.name}:slot:${this.normalizeKey(key)}:${index}`;
|
|
141
123
|
}
|
|
142
124
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { hash } from 'ohash';
|
|
2
|
+
|
|
3
|
+
import type { Key, Value } from '../interfaces/common.js';
|
|
4
|
+
import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
|
|
5
|
+
import { IndexedDBAztecMap } from './map.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A multi map backed by IndexedDB.
|
|
9
|
+
*/
|
|
10
|
+
export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
11
|
+
extends IndexedDBAztecMap<K, V>
|
|
12
|
+
implements AztecAsyncMultiMap<K, V>
|
|
13
|
+
{
|
|
14
|
+
override async set(key: K, val: V): Promise<void> {
|
|
15
|
+
// Inserting repeated values is a no-op
|
|
16
|
+
const exists = !!(await this.db
|
|
17
|
+
.index('hash')
|
|
18
|
+
.get(
|
|
19
|
+
IDBKeyRange.bound(
|
|
20
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
21
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
22
|
+
),
|
|
23
|
+
));
|
|
24
|
+
if (exists) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Get the maximum keyCount for the given key
|
|
28
|
+
// In order to support sparse multimaps, we cannot rely
|
|
29
|
+
// on just counting the number of entries for the key, since we would repeat slots
|
|
30
|
+
// if we delete an entry
|
|
31
|
+
// set -> container:key:0 (keyCount = 1)
|
|
32
|
+
// set -> container:key:1 (keyCount = 2)
|
|
33
|
+
// delete -> container:key:0 (keyCount = 1)
|
|
34
|
+
// set -> container:key:1 <--- already exists!
|
|
35
|
+
// Instead, we iterate in reverse order to get the last inserted entry
|
|
36
|
+
const index = this.db.index('keyCount');
|
|
37
|
+
const rangeQuery = IDBKeyRange.upperBound([this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER]);
|
|
38
|
+
const maxEntry = (await index.iterate(rangeQuery, 'prevunique').next()).value;
|
|
39
|
+
const count = maxEntry?.value?.keyCount ?? 0;
|
|
40
|
+
await this.db.put({
|
|
41
|
+
value: val,
|
|
42
|
+
hash: hash(val),
|
|
43
|
+
container: this.container,
|
|
44
|
+
key: this.normalizeKey(key),
|
|
45
|
+
keyCount: count + 1,
|
|
46
|
+
slot: this.slot(key, count),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async *getValuesAsync(key: K): AsyncIterableIterator<V> {
|
|
51
|
+
// Iterate over the whole range of keyCount for the given key
|
|
52
|
+
const index = this.db.index('keyCount');
|
|
53
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
54
|
+
[this.container, this.normalizeKey(key), 0],
|
|
55
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
56
|
+
false,
|
|
57
|
+
false,
|
|
58
|
+
);
|
|
59
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
60
|
+
yield cursor.value.value as V;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async deleteValue(key: K, val: V): Promise<void> {
|
|
65
|
+
// Since we know the value, we can hash it and directly query the "hash" index
|
|
66
|
+
// to avoid having to iterate over all the values
|
|
67
|
+
const fullKey = await this.db
|
|
68
|
+
.index('hash')
|
|
69
|
+
.getKey(
|
|
70
|
+
IDBKeyRange.bound(
|
|
71
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
72
|
+
[this.container, this.normalizeKey(key), hash(val)],
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
if (fullKey) {
|
|
76
|
+
await this.db.delete(fullKey);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
|
+
import { hash } from 'ohash';
|
|
2
3
|
|
|
4
|
+
import type { Value } from '../interfaces/common.js';
|
|
3
5
|
import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
|
|
4
6
|
import type { AztecIDBSchema } from './store.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Stores a single value in IndexedDB.
|
|
8
10
|
*/
|
|
9
|
-
export class IndexedDBAztecSingleton<T> implements AztecAsyncSingleton<T> {
|
|
11
|
+
export class IndexedDBAztecSingleton<T extends Value> implements AztecAsyncSingleton<T> {
|
|
10
12
|
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
11
13
|
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
12
14
|
#container: string;
|
|
@@ -38,6 +40,7 @@ export class IndexedDBAztecSingleton<T> implements AztecAsyncSingleton<T> {
|
|
|
38
40
|
key: this.#slot,
|
|
39
41
|
keyCount: 1,
|
|
40
42
|
value: val,
|
|
43
|
+
hash: hash(val),
|
|
41
44
|
});
|
|
42
45
|
return result !== undefined;
|
|
43
46
|
}
|