@aztec/kv-store 0.80.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.
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +3 -1
- package/dest/indexeddb/map.d.ts +5 -4
- package/dest/indexeddb/map.d.ts.map +1 -1
- package/dest/indexeddb/map.js +13 -53
- 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 +54 -0
- package/dest/indexeddb/store.d.ts +2 -1
- package/dest/indexeddb/store.d.ts.map +1 -1
- package/dest/indexeddb/store.js +2 -1
- package/dest/interfaces/index.d.ts +1 -0
- package/dest/interfaces/index.d.ts.map +1 -1
- package/dest/interfaces/index.js +1 -0
- package/dest/interfaces/map.d.ts +0 -46
- 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 +6 -20
- 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 +151 -0
- package/dest/interfaces/store.d.ts +2 -13
- package/dest/interfaces/store.d.ts.map +1 -1
- package/dest/lmdb/map.d.ts +2 -18
- package/dest/lmdb/map.d.ts.map +1 -1
- package/dest/lmdb/map.js +0 -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 +2 -13
- package/dest/lmdb/store.d.ts.map +1 -1
- package/dest/lmdb/store.js +3 -16
- package/dest/lmdb-v2/array.d.ts.map +1 -1
- package/dest/lmdb-v2/array.js +1 -0
- package/dest/lmdb-v2/map.d.ts +1 -43
- package/dest/lmdb-v2/map.d.ts.map +1 -1
- package/dest/lmdb-v2/map.js +1 -100
- package/dest/lmdb-v2/multi_map.d.ts +46 -0
- package/dest/lmdb-v2/multi_map.d.ts.map +1 -0
- package/dest/lmdb-v2/multi_map.js +103 -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 +2 -1
- package/dest/lmdb-v2/store.d.ts.map +1 -1
- package/dest/lmdb-v2/store.js +5 -1
- package/dest/stores/l2_tips_store.d.ts.map +1 -1
- package/dest/stores/l2_tips_store.js +7 -4
- package/package.json +5 -5
- package/src/config.ts +3 -1
- package/src/indexeddb/map.ts +15 -47
- package/src/indexeddb/multi_map.ts +53 -0
- package/src/indexeddb/store.ts +9 -3
- package/src/interfaces/index.ts +1 -0
- package/src/interfaces/map.ts +0 -52
- package/src/interfaces/map_test_suite.ts +18 -33
- package/src/interfaces/multi_map.ts +38 -0
- package/src/interfaces/multi_map_test_suite.ts +143 -0
- package/src/interfaces/store.ts +2 -22
- package/src/lmdb/map.ts +2 -88
- package/src/lmdb/multi_map.ts +35 -0
- package/src/lmdb/store.ts +5 -27
- package/src/lmdb-v2/array.ts +1 -0
- package/src/lmdb-v2/map.ts +2 -120
- package/src/lmdb-v2/multi_map.ts +126 -0
- package/src/lmdb-v2/singleton.ts +1 -0
- package/src/lmdb-v2/store.ts +7 -2
- package/src/stores/l2_tips_store.ts +5 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Encoder } from 'msgpackr/pack';
|
|
2
|
+
import { execInReadTx, execInWriteTx } from './store.js';
|
|
3
|
+
import { deserializeKey, maxKey, minKey, serializeKey } from './utils.js';
|
|
4
|
+
export class LMDBMultiMap {
|
|
5
|
+
store;
|
|
6
|
+
prefix;
|
|
7
|
+
encoder;
|
|
8
|
+
constructor(store, name){
|
|
9
|
+
this.store = store;
|
|
10
|
+
this.encoder = new Encoder();
|
|
11
|
+
this.prefix = `multimap:${name}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sets the value at the given key.
|
|
15
|
+
* @param key - The key to set the value at
|
|
16
|
+
* @param val - The value to set
|
|
17
|
+
*/ set(key, val) {
|
|
18
|
+
return execInWriteTx(this.store, (tx)=>tx.setIndex(serializeKey(this.prefix, key), this.encoder.pack(val)));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sets the value at the given key if it does not already exist.
|
|
22
|
+
* @param key - The key to set the value at
|
|
23
|
+
* @param val - The value to set
|
|
24
|
+
*/ setIfNotExists(key, val) {
|
|
25
|
+
return execInWriteTx(this.store, async (tx)=>{
|
|
26
|
+
const exists = !!await this.getAsync(key);
|
|
27
|
+
if (!exists) {
|
|
28
|
+
await tx.setIndex(serializeKey(this.prefix, key), this.encoder.pack(val));
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Deletes the value at the given key.
|
|
36
|
+
* @param key - The key to delete the value at
|
|
37
|
+
*/ delete(key) {
|
|
38
|
+
return execInWriteTx(this.store, (tx)=>tx.removeIndex(serializeKey(this.prefix, key)));
|
|
39
|
+
}
|
|
40
|
+
getAsync(key) {
|
|
41
|
+
return execInReadTx(this.store, async (tx)=>{
|
|
42
|
+
const val = await tx.getIndex(serializeKey(this.prefix, key));
|
|
43
|
+
return val.length > 0 ? this.encoder.unpack(val[0]) : undefined;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
hasAsync(key) {
|
|
47
|
+
return execInReadTx(this.store, async (tx)=>(await tx.getIndex(serializeKey(this.prefix, key))).length > 0);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Iterates over the map's key-value entries in the key's natural order
|
|
51
|
+
* @param range - The range of keys to iterate over
|
|
52
|
+
*/ async *entriesAsync(range) {
|
|
53
|
+
const reverse = range?.reverse ?? false;
|
|
54
|
+
const startKey = range?.start ? serializeKey(this.prefix, range.start) : minKey(this.prefix);
|
|
55
|
+
const endKey = range?.end ? serializeKey(this.prefix, range.end) : reverse ? maxKey(this.prefix) : undefined;
|
|
56
|
+
let tx = this.store.getCurrentWriteTx();
|
|
57
|
+
const shouldClose = !tx;
|
|
58
|
+
tx ??= this.store.getReadTx();
|
|
59
|
+
try {
|
|
60
|
+
for await (const [key, vals] of tx.iterateIndex(reverse ? endKey : startKey, reverse ? startKey : endKey, reverse, range?.limit)){
|
|
61
|
+
const deserializedKey = deserializeKey(this.prefix, key);
|
|
62
|
+
if (!deserializedKey) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
for (const val of vals){
|
|
66
|
+
yield [
|
|
67
|
+
deserializedKey,
|
|
68
|
+
this.encoder.unpack(val)
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} finally{
|
|
73
|
+
if (shouldClose) {
|
|
74
|
+
tx.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Iterates over the map's values in the key's natural order
|
|
80
|
+
* @param range - The range of keys to iterate over
|
|
81
|
+
*/ async *valuesAsync(range) {
|
|
82
|
+
for await (const [_, value] of this.entriesAsync(range)){
|
|
83
|
+
yield value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Iterates over the map's keys in the key's natural order
|
|
88
|
+
* @param range - The range of keys to iterate over
|
|
89
|
+
*/ async *keysAsync(range) {
|
|
90
|
+
for await (const [key, _] of this.entriesAsync(range)){
|
|
91
|
+
yield key;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
deleteValue(key, val) {
|
|
95
|
+
return execInWriteTx(this.store, (tx)=>tx.removeIndex(serializeKey(this.prefix, key), this.encoder.pack(val)));
|
|
96
|
+
}
|
|
97
|
+
async *getValuesAsync(key) {
|
|
98
|
+
const values = await execInReadTx(this.store, (tx)=>tx.getIndex(serializeKey(this.prefix, key)));
|
|
99
|
+
for (const value of values){
|
|
100
|
+
yield this.encoder.unpack(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/lmdb-v2/singleton.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/lmdb-v2/singleton.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EAAE,KAAK,gBAAgB,EAA+B,MAAM,YAAY,CAAC;AAGhF,qBAAa,eAAe,CAAC,CAAC,CAAE,YAAW,mBAAmB,CAAC,CAAC,CAAC;IAGnD,OAAO,CAAC,KAAK;IAFzB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,OAAO,CAAiB;gBACZ,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM;IAIzD,QAAQ,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAOlC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAO7B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CAM3B"}
|
package/dest/lmdb-v2/store.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { type Logger } from '@aztec/foundation/log';
|
|
|
2
2
|
import type { AztecAsyncArray } from '../interfaces/array.js';
|
|
3
3
|
import type { Key, StoreSize } from '../interfaces/common.js';
|
|
4
4
|
import type { AztecAsyncCounter } from '../interfaces/counter.js';
|
|
5
|
-
import type { AztecAsyncMap
|
|
5
|
+
import type { AztecAsyncMap } from '../interfaces/map.js';
|
|
6
|
+
import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
|
|
6
7
|
import type { AztecAsyncSet } from '../interfaces/set.js';
|
|
7
8
|
import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
|
|
8
9
|
import type { AztecAsyncKVStore } from '../interfaces/store.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/lmdb-v2/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAOlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/lmdb-v2/store.ts"],"names":[],"mappings":"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"}
|
package/dest/lmdb-v2/store.js
CHANGED
|
@@ -3,10 +3,14 @@ import { Semaphore, SerialQueue } from '@aztec/foundation/queue';
|
|
|
3
3
|
import { MsgpackChannel, NativeLMDBStore } from '@aztec/native';
|
|
4
4
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
5
5
|
import { rm } from 'fs/promises';
|
|
6
|
+
// eslint-disable-next-line import/no-cycle
|
|
6
7
|
import { LMDBArray } from './array.js';
|
|
7
|
-
|
|
8
|
+
// eslint-disable-next-line import/no-cycle
|
|
9
|
+
import { LMDBMap } from './map.js';
|
|
8
10
|
import { Database, LMDBMessageType } from './message.js';
|
|
11
|
+
import { LMDBMultiMap } from './multi_map.js';
|
|
9
12
|
import { ReadTransaction } from './read_transaction.js';
|
|
13
|
+
// eslint-disable-next-line import/no-cycle
|
|
10
14
|
import { LMDBSingleValue } from './singleton.js';
|
|
11
15
|
import { WriteTransaction } from './write_transaction.js';
|
|
12
16
|
export class AztecLMDBStoreV2 {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"l2_tips_store.d.ts","sourceRoot":"","sources":["../../src/stores/l2_tips_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,yBAAyB,EACzB,8BAA8B,EAE9B,MAAM,EACP,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,oEAAoE;AACpE,qBAAa,WAAY,YAAW,yBAAyB,EAAE,8BAA8B;IAC3F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;IAChE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAgC;gBAEvD,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM;IAKhD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIrD,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;YAQ3B,QAAQ;IAaT,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"l2_tips_store.d.ts","sourceRoot":"","sources":["../../src/stores/l2_tips_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,yBAAyB,EACzB,8BAA8B,EAE9B,MAAM,EACP,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,oEAAoE;AACpE,qBAAa,WAAY,YAAW,yBAAyB,EAAE,8BAA8B;IAC3F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;IAChE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAgC;gBAEvD,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM;IAKhD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIrD,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;YAQ3B,QAAQ;IAaT,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAwB9E"}
|
|
@@ -41,11 +41,14 @@
|
|
|
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.header.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
53
|
await this.l2TipsStore.set('latest', event.blockNumber);
|
|
51
54
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/kv-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/interfaces/index.js",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"./package.local.json"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aztec/ethereum": "0.
|
|
29
|
-
"@aztec/foundation": "0.
|
|
30
|
-
"@aztec/native": "0.
|
|
31
|
-
"@aztec/stdlib": "0.
|
|
28
|
+
"@aztec/ethereum": "0.82.0",
|
|
29
|
+
"@aztec/foundation": "0.82.0",
|
|
30
|
+
"@aztec/native": "0.82.0",
|
|
31
|
+
"@aztec/stdlib": "0.82.0",
|
|
32
32
|
"idb": "^8.0.0",
|
|
33
33
|
"lmdb": "^3.2.0",
|
|
34
34
|
"msgpackr": "^1.11.2",
|
package/src/config.ts
CHANGED
|
@@ -20,7 +20,9 @@ export const dataConfigMappings: ConfigMappingsType<DataStoreConfig> = {
|
|
|
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/map.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
2
2
|
|
|
3
3
|
import type { Key, Range } from '../interfaces/common.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { AztecAsyncMap } from '../interfaces/map.js';
|
|
5
5
|
import type { AztecIDBSchema } from './store.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* A map backed by IndexedDB.
|
|
9
9
|
*/
|
|
10
|
-
export class IndexedDBAztecMap<K extends Key, V> implements
|
|
10
|
+
export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMap<K, V> {
|
|
11
11
|
protected name: string;
|
|
12
|
-
|
|
12
|
+
protected container: string;
|
|
13
13
|
|
|
14
14
|
#_db?: IDBPObjectStore<AztecIDBSchema, ['data'], 'data', 'readwrite'>;
|
|
15
15
|
#rootDB: IDBPDatabase<AztecIDBSchema>;
|
|
16
16
|
|
|
17
17
|
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, mapName: string) {
|
|
18
18
|
this.name = mapName;
|
|
19
|
-
this
|
|
19
|
+
this.container = `map:${mapName}`;
|
|
20
20
|
this.#rootDB = rootDB;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -29,38 +29,22 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async getAsync(key: K): Promise<V | undefined> {
|
|
32
|
-
const data = await this.db.get(this
|
|
32
|
+
const data = await this.db.get(this.slot(key));
|
|
33
33
|
return data?.value as V;
|
|
34
34
|
}
|
|
35
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
36
|
async hasAsync(key: K): Promise<boolean> {
|
|
50
37
|
const result = (await this.getAsync(key)) !== undefined;
|
|
51
38
|
return result;
|
|
52
39
|
}
|
|
53
40
|
|
|
54
41
|
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
42
|
await this.db.put({
|
|
59
43
|
value: val,
|
|
60
|
-
container: this
|
|
61
|
-
key: this
|
|
62
|
-
keyCount:
|
|
63
|
-
slot: this
|
|
44
|
+
container: this.container,
|
|
45
|
+
key: this.normalizeKey(key),
|
|
46
|
+
keyCount: 1,
|
|
47
|
+
slot: this.slot(key),
|
|
64
48
|
});
|
|
65
49
|
}
|
|
66
50
|
|
|
@@ -77,30 +61,14 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
77
61
|
}
|
|
78
62
|
|
|
79
63
|
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
|
-
}
|
|
64
|
+
await this.db.delete(this.slot(key));
|
|
97
65
|
}
|
|
98
66
|
|
|
99
67
|
async *entriesAsync(range: Range<K> = {}): AsyncIterableIterator<[K, V]> {
|
|
100
68
|
const index = this.db.index('key');
|
|
101
69
|
const rangeQuery = IDBKeyRange.bound(
|
|
102
|
-
[this
|
|
103
|
-
[this
|
|
70
|
+
[this.container, range.start ?? ''],
|
|
71
|
+
[this.container, range.end ?? '\uffff'],
|
|
104
72
|
!!range.reverse,
|
|
105
73
|
!range.reverse,
|
|
106
74
|
);
|
|
@@ -131,12 +99,12 @@ export class IndexedDBAztecMap<K extends Key, V> implements AztecAsyncMultiMap<K
|
|
|
131
99
|
return (denormalizedKey.length > 1 ? denormalizedKey : key) as K;
|
|
132
100
|
}
|
|
133
101
|
|
|
134
|
-
|
|
102
|
+
protected normalizeKey(key: K): string {
|
|
135
103
|
const arrayKey = Array.isArray(key) ? key : [key];
|
|
136
104
|
return arrayKey.join(',');
|
|
137
105
|
}
|
|
138
106
|
|
|
139
|
-
|
|
140
|
-
return `map:${this.name}:slot:${this
|
|
107
|
+
protected slot(key: K, index: number = 0): string {
|
|
108
|
+
return `map:${this.name}:slot:${this.normalizeKey(key)}:${index}`;
|
|
141
109
|
}
|
|
142
110
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Key } from '../interfaces/common.js';
|
|
2
|
+
import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
|
|
3
|
+
import { IndexedDBAztecMap } from './map.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A multi map backed by IndexedDB.
|
|
7
|
+
*/
|
|
8
|
+
export class IndexedDBAztecMultiMap<K extends Key, V>
|
|
9
|
+
extends IndexedDBAztecMap<K, V>
|
|
10
|
+
implements AztecAsyncMultiMap<K, V>
|
|
11
|
+
{
|
|
12
|
+
override async set(key: K, val: V): Promise<void> {
|
|
13
|
+
const count = await this.db
|
|
14
|
+
.index('key')
|
|
15
|
+
.count(IDBKeyRange.bound([this.container, this.normalizeKey(key)], [this.container, this.normalizeKey(key)]));
|
|
16
|
+
await this.db.put({
|
|
17
|
+
value: val,
|
|
18
|
+
container: this.container,
|
|
19
|
+
key: this.normalizeKey(key),
|
|
20
|
+
keyCount: count + 1,
|
|
21
|
+
slot: this.slot(key, count),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async *getValuesAsync(key: K): AsyncIterableIterator<V> {
|
|
26
|
+
const index = this.db.index('keyCount');
|
|
27
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
28
|
+
[this.container, this.normalizeKey(key), 0],
|
|
29
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
30
|
+
false,
|
|
31
|
+
false,
|
|
32
|
+
);
|
|
33
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
34
|
+
yield cursor.value.value as V;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async deleteValue(key: K, val: V): Promise<void> {
|
|
39
|
+
const index = this.db.index('keyCount');
|
|
40
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
41
|
+
[this.container, this.normalizeKey(key), 0],
|
|
42
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
43
|
+
false,
|
|
44
|
+
false,
|
|
45
|
+
);
|
|
46
|
+
for await (const cursor of index.iterate(rangeQuery)) {
|
|
47
|
+
if (JSON.stringify(cursor.value.value) === JSON.stringify(val)) {
|
|
48
|
+
await cursor.delete();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/indexeddb/store.ts
CHANGED
|
@@ -5,12 +5,14 @@ import { type DBSchema, type IDBPDatabase, deleteDB, openDB } from 'idb';
|
|
|
5
5
|
import type { AztecAsyncArray } from '../interfaces/array.js';
|
|
6
6
|
import type { Key, StoreSize } from '../interfaces/common.js';
|
|
7
7
|
import type { AztecAsyncCounter } from '../interfaces/counter.js';
|
|
8
|
-
import type { AztecAsyncMap
|
|
8
|
+
import type { AztecAsyncMap } from '../interfaces/map.js';
|
|
9
|
+
import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
|
|
9
10
|
import type { AztecAsyncSet } from '../interfaces/set.js';
|
|
10
11
|
import type { AztecAsyncSingleton } from '../interfaces/singleton.js';
|
|
11
12
|
import type { AztecAsyncKVStore } from '../interfaces/store.js';
|
|
12
13
|
import { IndexedDBAztecArray } from './array.js';
|
|
13
14
|
import { IndexedDBAztecMap } from './map.js';
|
|
15
|
+
import { IndexedDBAztecMultiMap } from './multi_map.js';
|
|
14
16
|
import { IndexedDBAztecSet } from './set.js';
|
|
15
17
|
import { IndexedDBAztecSingleton } from './singleton.js';
|
|
16
18
|
|
|
@@ -34,7 +36,11 @@ export class AztecIndexedDBStore implements AztecAsyncKVStore {
|
|
|
34
36
|
#name: string;
|
|
35
37
|
|
|
36
38
|
#containers = new Set<
|
|
37
|
-
|
|
39
|
+
| IndexedDBAztecArray<any>
|
|
40
|
+
| IndexedDBAztecMap<any, any>
|
|
41
|
+
| IndexedDBAztecMultiMap<any, any>
|
|
42
|
+
| IndexedDBAztecSet<any>
|
|
43
|
+
| IndexedDBAztecSingleton<any>
|
|
38
44
|
>();
|
|
39
45
|
|
|
40
46
|
constructor(rootDB: IDBPDatabase<AztecIDBSchema>, public readonly isEphemeral: boolean, log: Logger, name: string) {
|
|
@@ -119,7 +125,7 @@ export class AztecIndexedDBStore implements AztecAsyncKVStore {
|
|
|
119
125
|
* @returns A new AztecMultiMap
|
|
120
126
|
*/
|
|
121
127
|
openMultiMap<K extends Key, V>(name: string): AztecAsyncMultiMap<K, V> {
|
|
122
|
-
const multimap = new
|
|
128
|
+
const multimap = new IndexedDBAztecMultiMap<K, V>(this.#rootDB, name);
|
|
123
129
|
this.#containers.add(multimap);
|
|
124
130
|
return multimap;
|
|
125
131
|
}
|
package/src/interfaces/index.ts
CHANGED
package/src/interfaces/map.ts
CHANGED
|
@@ -62,40 +62,6 @@ export interface AztecMap<K extends Key, V> extends AztecBaseMap<K, V> {
|
|
|
62
62
|
clear(): Promise<void>;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export interface AztecMapWithSize<K extends Key, V> extends AztecMap<K, V> {
|
|
66
|
-
/**
|
|
67
|
-
* Gets the size of the map.
|
|
68
|
-
* @returns The size of the map
|
|
69
|
-
*/
|
|
70
|
-
size(): number;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* A map backed by a persistent store that can have multiple values for a single key.
|
|
75
|
-
*/
|
|
76
|
-
export interface AztecMultiMap<K extends Key, V> extends AztecMap<K, V> {
|
|
77
|
-
/**
|
|
78
|
-
* Gets all the values at the given key.
|
|
79
|
-
* @param key - The key to get the values from
|
|
80
|
-
*/
|
|
81
|
-
getValues(key: K): IterableIterator<V>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Deletes a specific value at the given key.
|
|
85
|
-
* @param key - The key to delete the value at
|
|
86
|
-
* @param val - The value to delete
|
|
87
|
-
*/
|
|
88
|
-
deleteValue(key: K, val: V): Promise<void>;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface AztecMultiMapWithSize<K extends Key, V> extends AztecMultiMap<K, V> {
|
|
92
|
-
/**
|
|
93
|
-
* Gets the size of the map.
|
|
94
|
-
* @returns The size of the map
|
|
95
|
-
*/
|
|
96
|
-
size(): number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
65
|
/**
|
|
100
66
|
* A map backed by a persistent store.
|
|
101
67
|
*/
|
|
@@ -131,21 +97,3 @@ export interface AztecAsyncMap<K extends Key, V> extends AztecBaseMap<K, V> {
|
|
|
131
97
|
*/
|
|
132
98
|
keysAsync(range?: Range<K>): AsyncIterableIterator<K>;
|
|
133
99
|
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* A map backed by a persistent store that can have multiple values for a single key.
|
|
137
|
-
*/
|
|
138
|
-
export interface AztecAsyncMultiMap<K extends Key, V> extends AztecAsyncMap<K, V> {
|
|
139
|
-
/**
|
|
140
|
-
* Gets all the values at the given key.
|
|
141
|
-
* @param key - The key to get the values from
|
|
142
|
-
*/
|
|
143
|
-
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Deletes a specific value at the given key.
|
|
147
|
-
* @param key - The key to delete the value at
|
|
148
|
-
* @param val - The value to delete
|
|
149
|
-
*/
|
|
150
|
-
deleteValue(key: K, val: V): Promise<void>;
|
|
151
|
-
}
|
|
@@ -3,7 +3,7 @@ import { toArray } from '@aztec/foundation/iterable';
|
|
|
3
3
|
import { expect } from 'chai';
|
|
4
4
|
|
|
5
5
|
import type { Key, Range } from './common.js';
|
|
6
|
-
import type { AztecAsyncMap,
|
|
6
|
+
import type { AztecAsyncMap, AztecMap } from './map.js';
|
|
7
7
|
import type { AztecAsyncKVStore, AztecKVStore } from './store.js';
|
|
8
8
|
import { isSyncStore } from './utils.js';
|
|
9
9
|
|
|
@@ -14,11 +14,11 @@ export function describeAztecMap(
|
|
|
14
14
|
) {
|
|
15
15
|
describe(testName, () => {
|
|
16
16
|
let store: AztecKVStore | AztecAsyncKVStore;
|
|
17
|
-
let map:
|
|
17
|
+
let map: AztecMap<Key, string> | AztecAsyncMap<Key, string>;
|
|
18
18
|
|
|
19
19
|
beforeEach(async () => {
|
|
20
20
|
store = await getStore();
|
|
21
|
-
map = store.
|
|
21
|
+
map = store.openMap<string, string>('test');
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
afterEach(async () => {
|
|
@@ -27,32 +27,26 @@ export function describeAztecMap(
|
|
|
27
27
|
|
|
28
28
|
async function get(key: Key, sut: AztecAsyncMap<any, any> | AztecMap<any, any> = map) {
|
|
29
29
|
return isSyncStore(store) && !forceAsync
|
|
30
|
-
? (sut as
|
|
31
|
-
: await (sut as
|
|
30
|
+
? (sut as AztecMap<any, any>).get(key)
|
|
31
|
+
: await (sut as AztecAsyncMap<any, any>).getAsync(key);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async function entries() {
|
|
35
35
|
return isSyncStore(store) && !forceAsync
|
|
36
|
-
? await toArray((map as
|
|
37
|
-
: await toArray((map as
|
|
36
|
+
? await toArray((map as AztecMap<any, any>).entries())
|
|
37
|
+
: await toArray((map as AztecAsyncMap<any, any>).entriesAsync());
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function values() {
|
|
41
41
|
return isSyncStore(store) && !forceAsync
|
|
42
|
-
? await toArray((map as
|
|
43
|
-
: await toArray((map as
|
|
42
|
+
? await toArray((map as AztecMap<any, any>).values())
|
|
43
|
+
: await toArray((map as AztecAsyncMap<any, any>).valuesAsync());
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async function keys(range?: Range<Key>, sut: AztecAsyncMap<any, any> | AztecMap<any, any> = map) {
|
|
47
47
|
return isSyncStore(store) && !forceAsync
|
|
48
|
-
? await toArray((sut as
|
|
49
|
-
: await toArray((sut as
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function getValues(key: Key) {
|
|
53
|
-
return isSyncStore(store) && !forceAsync
|
|
54
|
-
? await toArray((map as AztecMultiMap<any, any>).getValues(key))
|
|
55
|
-
: await toArray((map as AztecAsyncMultiMap<any, any>).getValuesAsync(key));
|
|
48
|
+
? await toArray((sut as AztecMap<any, any>).keys(range))
|
|
49
|
+
: await toArray((sut as AztecAsyncMap<any, any>).keysAsync(range));
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
it('should be able to set and get values', async () => {
|
|
@@ -64,6 +58,13 @@ export function describeAztecMap(
|
|
|
64
58
|
expect(await get('quux')).to.equal(undefined);
|
|
65
59
|
});
|
|
66
60
|
|
|
61
|
+
it('should be able to overwrite values', async () => {
|
|
62
|
+
await map.set('foo', 'bar');
|
|
63
|
+
await map.set('foo', 'baz');
|
|
64
|
+
|
|
65
|
+
expect(await get('foo')).to.equal('baz');
|
|
66
|
+
});
|
|
67
|
+
|
|
67
68
|
it('should be able to set values if they do not exist', async () => {
|
|
68
69
|
expect(await map.setIfNotExists('foo', 'bar')).to.equal(true);
|
|
69
70
|
expect(await map.setIfNotExists('foo', 'baz')).to.equal(false);
|
|
@@ -109,22 +110,6 @@ export function describeAztecMap(
|
|
|
109
110
|
expect(await keys()).to.deep.equal(['baz', 'foo']);
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
it('should be able to get multiple values for a single key', async () => {
|
|
113
|
-
await map.set('foo', 'bar');
|
|
114
|
-
await map.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 map.set('foo', 'bar');
|
|
121
|
-
await map.set('foo', 'baz');
|
|
122
|
-
|
|
123
|
-
await map.deleteValue('foo', 'bar');
|
|
124
|
-
|
|
125
|
-
expect(await getValues('foo')).to.deep.equal(['baz']);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
113
|
it('supports range queries', async () => {
|
|
129
114
|
await map.set('a', 'a');
|
|
130
115
|
await map.set('b', 'b');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Key } from './common.js';
|
|
2
|
+
import type { AztecAsyncMap, AztecMap } from './map.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A map backed by a persistent store that can have multiple values for a single key.
|
|
6
|
+
*/
|
|
7
|
+
export interface AztecMultiMap<K extends Key, V> extends AztecMap<K, V> {
|
|
8
|
+
/**
|
|
9
|
+
* Gets all the values at the given key.
|
|
10
|
+
* @param key - The key to get the values from
|
|
11
|
+
*/
|
|
12
|
+
getValues(key: K): IterableIterator<V>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Deletes a specific value at the given key.
|
|
16
|
+
* @param key - The key to delete the value at
|
|
17
|
+
* @param val - The value to delete
|
|
18
|
+
*/
|
|
19
|
+
deleteValue(key: K, val: V): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A map backed by a persistent store that can have multiple values for a single key.
|
|
24
|
+
*/
|
|
25
|
+
export interface AztecAsyncMultiMap<K extends Key, V> extends AztecAsyncMap<K, V> {
|
|
26
|
+
/**
|
|
27
|
+
* Gets all the values at the given key.
|
|
28
|
+
* @param key - The key to get the values from
|
|
29
|
+
*/
|
|
30
|
+
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Deletes a specific value at the given key.
|
|
34
|
+
* @param key - The key to delete the value at
|
|
35
|
+
* @param val - The value to delete
|
|
36
|
+
*/
|
|
37
|
+
deleteValue(key: K, val: V): Promise<void>;
|
|
38
|
+
}
|