@aztec/kv-store 3.0.0-nightly.20251114 → 3.0.0-nightly.20251115
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/indexeddb/multi_map.d.ts +1 -0
- package/dest/indexeddb/multi_map.d.ts.map +1 -1
- package/dest/indexeddb/multi_map.js +14 -0
- package/dest/interfaces/multi_map.d.ts +6 -0
- package/dest/interfaces/multi_map.d.ts.map +1 -1
- package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -1
- package/dest/interfaces/multi_map_test_suite.js +94 -0
- package/dest/lmdb/multi_map.d.ts +1 -0
- package/dest/lmdb/multi_map.d.ts.map +1 -1
- package/dest/lmdb/multi_map.js +15 -0
- package/dest/lmdb-v2/multi_map.d.ts +1 -0
- package/dest/lmdb-v2/multi_map.d.ts.map +1 -1
- package/dest/lmdb-v2/multi_map.js +11 -0
- package/package.json +5 -5
- package/src/indexeddb/multi_map.ts +12 -0
- package/src/interfaces/multi_map.ts +7 -0
- package/src/interfaces/multi_map_test_suite.ts +100 -0
- package/src/lmdb/multi_map.ts +16 -0
- package/src/lmdb-v2/multi_map.ts +9 -0
|
@@ -7,6 +7,7 @@ import { IndexedDBAztecMap } from './map.js';
|
|
|
7
7
|
export declare class IndexedDBAztecMultiMap<K extends Key, V extends Value> extends IndexedDBAztecMap<K, V> implements AztecAsyncMultiMap<K, V> {
|
|
8
8
|
set(key: K, val: V): Promise<void>;
|
|
9
9
|
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
10
|
+
getValueCountAsync(key: K): Promise<number>;
|
|
10
11
|
deleteValue(key: K, val: V): Promise<void>;
|
|
11
12
|
}
|
|
12
13
|
//# sourceMappingURL=multi_map.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/indexeddb/multi_map.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C;;GAEG;AACH,qBAAa,sBAAsB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAChE,SAAQ,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAC9B,YAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpB,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC1C,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/indexeddb/multi_map.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C;;GAEG;AACH,qBAAa,sBAAsB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAChE,SAAQ,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAC9B,YAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpB,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC1C,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;IAcvD,kBAAkB,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAYrC,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAejD"}
|
|
@@ -59,6 +59,20 @@ import { IndexedDBAztecMap } from './map.js';
|
|
|
59
59
|
yield cursor.value.value;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
getValueCountAsync(key) {
|
|
63
|
+
// Count entries over the keyCount index range for this key
|
|
64
|
+
const index = this.db.index('keyCount');
|
|
65
|
+
const rangeQuery = IDBKeyRange.bound([
|
|
66
|
+
this.container,
|
|
67
|
+
this.normalizeKey(key),
|
|
68
|
+
0
|
|
69
|
+
], [
|
|
70
|
+
this.container,
|
|
71
|
+
this.normalizeKey(key),
|
|
72
|
+
Number.MAX_SAFE_INTEGER
|
|
73
|
+
], false, false);
|
|
74
|
+
return index.count(rangeQuery);
|
|
75
|
+
}
|
|
62
76
|
async deleteValue(key, val) {
|
|
63
77
|
// Since we know the value, we can hash it and directly query the "hash" index
|
|
64
78
|
// to avoid having to iterate over all the values
|
|
@@ -25,6 +25,12 @@ export interface AztecAsyncMultiMap<K extends Key, V extends Value> extends Azte
|
|
|
25
25
|
* @param key - The key to get the values from
|
|
26
26
|
*/
|
|
27
27
|
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
28
|
+
/**
|
|
29
|
+
* Gets the number of values at the given key.
|
|
30
|
+
* @param key - The key to get the number of values from
|
|
31
|
+
* @returns The number of values at the given key
|
|
32
|
+
*/
|
|
33
|
+
getValueCountAsync(key: K): Promise<number>;
|
|
28
34
|
/**
|
|
29
35
|
* Deletes a specific value at the given key.
|
|
30
36
|
* @param key - The key to delete the value at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/interfaces/multi_map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAAE,SAAQ,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IACnF;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAEvC;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAAE,SAAQ,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7F;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAEjD;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C"}
|
|
1
|
+
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/interfaces/multi_map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAAE,SAAQ,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IACnF;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAEvC;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAAE,SAAQ,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7F;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAEjD;;;;OAIG;IACH,kBAAkB,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5C;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_map_test_suite.d.ts","sourceRoot":"","sources":["../../src/interfaces/multi_map_test_suite.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGlE,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,EACzD,UAAU,GAAE,OAAe,
|
|
1
|
+
{"version":3,"file":"multi_map_test_suite.d.ts","sourceRoot":"","sources":["../../src/interfaces/multi_map_test_suite.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGlE,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,EACzD,UAAU,GAAE,OAAe,QAyU5B"}
|
|
@@ -30,6 +30,9 @@ export function describeAztecMultiMap(testName, getStore, forceAsync = false) {
|
|
|
30
30
|
async function getValues(key) {
|
|
31
31
|
return isSyncStore(store) && !forceAsync ? await toArray(multiMap.getValues(key)) : await toArray(multiMap.getValuesAsync(key));
|
|
32
32
|
}
|
|
33
|
+
async function getValueCount(key, sut = multiMap) {
|
|
34
|
+
return await sut.getValueCountAsync(key);
|
|
35
|
+
}
|
|
33
36
|
it('should be able to set and get values', async ()=>{
|
|
34
37
|
await multiMap.set('foo', 'bar');
|
|
35
38
|
await multiMap.set('baz', 'qux');
|
|
@@ -241,5 +244,96 @@ export function describeAztecMultiMap(testName, getStore, forceAsync = false) {
|
|
|
241
244
|
'a'
|
|
242
245
|
]);
|
|
243
246
|
});
|
|
247
|
+
it('returns 0 for missing key', async ()=>{
|
|
248
|
+
expect(await getValueCount('missing')).to.equal(0);
|
|
249
|
+
});
|
|
250
|
+
it('counts a single value', async ()=>{
|
|
251
|
+
await multiMap.set('foo', 'bar');
|
|
252
|
+
expect(await getValueCount('foo')).to.equal(1);
|
|
253
|
+
});
|
|
254
|
+
it('counts multiple distinct values for same key', async ()=>{
|
|
255
|
+
await multiMap.set('foo', 'bar');
|
|
256
|
+
await multiMap.set('foo', 'baz');
|
|
257
|
+
await multiMap.set('foo', 'qux');
|
|
258
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
259
|
+
});
|
|
260
|
+
it('does not increase count for duplicate inserts', async ()=>{
|
|
261
|
+
await multiMap.set('foo', 'bar');
|
|
262
|
+
await multiMap.set('foo', 'bar');
|
|
263
|
+
await multiMap.set('foo', 'baz');
|
|
264
|
+
await multiMap.set('foo', 'baz');
|
|
265
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
266
|
+
expect(await getValues('foo')).to.have.members([
|
|
267
|
+
'bar',
|
|
268
|
+
'baz'
|
|
269
|
+
]);
|
|
270
|
+
});
|
|
271
|
+
it('decrements when deleting a single value', async ()=>{
|
|
272
|
+
await multiMap.set('foo', '1');
|
|
273
|
+
await multiMap.set('foo', '2');
|
|
274
|
+
await multiMap.set('foo', '3');
|
|
275
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
276
|
+
await multiMap.deleteValue('foo', '2');
|
|
277
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
278
|
+
expect(await getValues('foo')).to.have.members([
|
|
279
|
+
'1',
|
|
280
|
+
'3'
|
|
281
|
+
]);
|
|
282
|
+
});
|
|
283
|
+
it('does not change count when deleting a non-existent value', async ()=>{
|
|
284
|
+
await multiMap.set('foo', '1');
|
|
285
|
+
await multiMap.set('foo', '3');
|
|
286
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
287
|
+
await multiMap.deleteValue('foo', '2');
|
|
288
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
289
|
+
});
|
|
290
|
+
it('clears all values when deleting a key', async ()=>{
|
|
291
|
+
await multiMap.set('foo', 'bar');
|
|
292
|
+
await multiMap.set('foo', 'baz');
|
|
293
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
294
|
+
await multiMap.delete('foo');
|
|
295
|
+
expect(await getValueCount('foo')).to.equal(0);
|
|
296
|
+
expect(await getValues('foo')).to.deep.equal([]);
|
|
297
|
+
});
|
|
298
|
+
it('count equals enumerated values length', async ()=>{
|
|
299
|
+
await multiMap.set('foo', '1');
|
|
300
|
+
await multiMap.set('foo', '2');
|
|
301
|
+
const vals = await getValues('foo');
|
|
302
|
+
expect(await getValueCount('foo')).to.equal(vals.length);
|
|
303
|
+
});
|
|
304
|
+
it('sum of per-key counts equals total size', async ()=>{
|
|
305
|
+
await multiMap.set('foo', '1');
|
|
306
|
+
await multiMap.set('foo', '2');
|
|
307
|
+
await multiMap.set('bar', '3');
|
|
308
|
+
await multiMap.set('bar', '4');
|
|
309
|
+
await multiMap.set('baz', '5');
|
|
310
|
+
const allKeys = await keys();
|
|
311
|
+
const uniqueKeys = Array.from(new Set(allKeys));
|
|
312
|
+
const counts = await Promise.all(uniqueKeys.map((k)=>getValueCount(k)));
|
|
313
|
+
const sum = counts.reduce((s, n)=>s + n, 0);
|
|
314
|
+
expect(sum).to.equal(await size());
|
|
315
|
+
});
|
|
316
|
+
it('supports sparse slots: delete middle, reinsert new, count remains correct', async ()=>{
|
|
317
|
+
await multiMap.set('foo', '1');
|
|
318
|
+
await multiMap.set('foo', '2');
|
|
319
|
+
await multiMap.set('foo', '3');
|
|
320
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
321
|
+
await multiMap.deleteValue('foo', '2');
|
|
322
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
323
|
+
await multiMap.set('foo', '4');
|
|
324
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
325
|
+
expect(await getValues('foo')).to.have.members([
|
|
326
|
+
'1',
|
|
327
|
+
'3',
|
|
328
|
+
'4'
|
|
329
|
+
]);
|
|
330
|
+
});
|
|
331
|
+
it('multiple keys are independent', async ()=>{
|
|
332
|
+
await multiMap.set('foo', '1');
|
|
333
|
+
await multiMap.set('foo', '2');
|
|
334
|
+
await multiMap.set('bar', '3');
|
|
335
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
336
|
+
expect(await getValueCount('bar')).to.equal(1);
|
|
337
|
+
});
|
|
244
338
|
});
|
|
245
339
|
}
|
package/dest/lmdb/multi_map.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { LmdbAztecMap } from './map.js';
|
|
|
7
7
|
export declare class LmdbAztecMultiMap<K extends Key, V extends Value> extends LmdbAztecMap<K, V> implements AztecMultiMap<K, V>, AztecAsyncMultiMap<K, V> {
|
|
8
8
|
getValues(key: K): IterableIterator<V>;
|
|
9
9
|
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
10
|
+
getValueCountAsync(key: K): Promise<number>;
|
|
10
11
|
deleteValue(key: K, val: V): Promise<void>;
|
|
11
12
|
}
|
|
12
13
|
//# sourceMappingURL=multi_map.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/lmdb/multi_map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC;;GAEG;AACH,qBAAa,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAC3D,SAAQ,YAAY,CAAC,CAAC,EAAE,CAAC,CACzB,YAAW,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAchC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/lmdb/multi_map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC;;GAEG;AACH,qBAAa,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAC3D,SAAQ,YAAY,CAAC,CAAC,EAAE,CAAC,CACzB,YAAW,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAchC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;IAMvD,kBAAkB,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBrC,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAGjD"}
|
package/dest/lmdb/multi_map.js
CHANGED
|
@@ -20,6 +20,21 @@ import { LmdbAztecMap } from './map.js';
|
|
|
20
20
|
yield value;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
getValueCountAsync(key) {
|
|
24
|
+
const transaction = this.db.useReadTransaction();
|
|
25
|
+
try {
|
|
26
|
+
const values = this.db.getValues(this.slot(key), {
|
|
27
|
+
transaction
|
|
28
|
+
});
|
|
29
|
+
let count = 0;
|
|
30
|
+
for (const _ of values){
|
|
31
|
+
count++;
|
|
32
|
+
}
|
|
33
|
+
return Promise.resolve(count);
|
|
34
|
+
} finally{
|
|
35
|
+
transaction.done();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
23
38
|
async deleteValue(key, val) {
|
|
24
39
|
await this.db.remove(this.slot(key), [
|
|
25
40
|
key,
|
|
@@ -47,5 +47,6 @@ export declare class LMDBMultiMap<K extends Key, V extends Value> implements Azt
|
|
|
47
47
|
keysAsync(range?: Range<K>): AsyncIterableIterator<K>;
|
|
48
48
|
deleteValue(key: K, val: V | undefined): Promise<void>;
|
|
49
49
|
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
50
|
+
getValueCountAsync(key: K): Promise<number>;
|
|
50
51
|
}
|
|
51
52
|
//# sourceMappingURL=multi_map.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/lmdb-v2/multi_map.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"multi_map.d.ts","sourceRoot":"","sources":["../../src/lmdb-v2/multi_map.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,KAAK,gBAAgB,EAA+B,MAAM,YAAY,CAAC;AAGhF,qBAAa,YAAY,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAAE,YAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAIzF,OAAO,CAAC,KAAK;IAHf,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAiB;gBAEtB,KAAK,EAAE,gBAAgB,EAC/B,IAAI,EAAE,MAAM;IAKd;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7D;;;;OAIG;IACH,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhD;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7B,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAOxC,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlC,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5B;;;OAGG;IACI,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAgCpE;;;OAGG;IACI,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;IAM9D;;;OAGG;IACI,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;IAM5D,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;IAOvD,kBAAkB,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;CAM5C"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Encoder } from 'msgpackr/pack';
|
|
2
|
+
import { MAXIMUM_KEY, toBufferKey } from 'ordered-binary';
|
|
3
|
+
// eslint-disable-next-line import/no-cycle
|
|
2
4
|
import { execInReadTx, execInWriteTx } from './store.js';
|
|
3
5
|
import { deserializeKey, maxKey, minKey, serializeKey } from './utils.js';
|
|
4
6
|
export class LMDBMultiMap {
|
|
@@ -110,4 +112,13 @@ export class LMDBMultiMap {
|
|
|
110
112
|
yield this.encoder.unpack(value);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
115
|
+
getValueCountAsync(key) {
|
|
116
|
+
const startKey = serializeKey(this.prefix, key);
|
|
117
|
+
const endKey = toBufferKey([
|
|
118
|
+
this.prefix,
|
|
119
|
+
key,
|
|
120
|
+
MAXIMUM_KEY
|
|
121
|
+
]);
|
|
122
|
+
return execInReadTx(this.store, (tx)=>tx.countEntriesIndex(startKey, endKey, false));
|
|
123
|
+
}
|
|
113
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/kv-store",
|
|
3
|
-
"version": "3.0.0-nightly.
|
|
3
|
+
"version": "3.0.0-nightly.20251115",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/interfaces/index.js",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"./package.local.json"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@aztec/ethereum": "3.0.0-nightly.
|
|
28
|
-
"@aztec/foundation": "3.0.0-nightly.
|
|
29
|
-
"@aztec/native": "3.0.0-nightly.
|
|
30
|
-
"@aztec/stdlib": "3.0.0-nightly.
|
|
27
|
+
"@aztec/ethereum": "3.0.0-nightly.20251115",
|
|
28
|
+
"@aztec/foundation": "3.0.0-nightly.20251115",
|
|
29
|
+
"@aztec/native": "3.0.0-nightly.20251115",
|
|
30
|
+
"@aztec/stdlib": "3.0.0-nightly.20251115",
|
|
31
31
|
"idb": "^8.0.0",
|
|
32
32
|
"lmdb": "^3.2.0",
|
|
33
33
|
"msgpackr": "^1.11.2",
|
|
@@ -61,6 +61,18 @@ export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
getValueCountAsync(key: K): Promise<number> {
|
|
65
|
+
// Count entries over the keyCount index range for this key
|
|
66
|
+
const index = this.db.index('keyCount');
|
|
67
|
+
const rangeQuery = IDBKeyRange.bound(
|
|
68
|
+
[this.container, this.normalizeKey(key), 0],
|
|
69
|
+
[this.container, this.normalizeKey(key), Number.MAX_SAFE_INTEGER],
|
|
70
|
+
false,
|
|
71
|
+
false,
|
|
72
|
+
);
|
|
73
|
+
return index.count(rangeQuery);
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
async deleteValue(key: K, val: V): Promise<void> {
|
|
65
77
|
// Since we know the value, we can hash it and directly query the "hash" index
|
|
66
78
|
// to avoid having to iterate over all the values
|
|
@@ -29,6 +29,13 @@ export interface AztecAsyncMultiMap<K extends Key, V extends Value> extends Azte
|
|
|
29
29
|
*/
|
|
30
30
|
getValuesAsync(key: K): AsyncIterableIterator<V>;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Gets the number of values at the given key.
|
|
34
|
+
* @param key - The key to get the number of values from
|
|
35
|
+
* @returns The number of values at the given key
|
|
36
|
+
*/
|
|
37
|
+
getValueCountAsync(key: K): Promise<number>;
|
|
38
|
+
|
|
32
39
|
/**
|
|
33
40
|
* Deletes a specific value at the given key.
|
|
34
41
|
* @param key - The key to delete the value at
|
|
@@ -61,6 +61,13 @@ export function describeAztecMultiMap(
|
|
|
61
61
|
: await toArray((multiMap as AztecAsyncMultiMap<any, any>).getValuesAsync(key));
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
async function getValueCount(
|
|
65
|
+
key: Key,
|
|
66
|
+
sut: AztecAsyncMultiMap<any, any> | AztecMultiMap<any, any> = multiMap,
|
|
67
|
+
): Promise<number> {
|
|
68
|
+
return await (sut as AztecAsyncMultiMap<any, any>).getValueCountAsync(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
it('should be able to set and get values', async () => {
|
|
65
72
|
await multiMap.set('foo', 'bar');
|
|
66
73
|
await multiMap.set('baz', 'qux');
|
|
@@ -238,5 +245,98 @@ export function describeAztecMultiMap(
|
|
|
238
245
|
expect(await keys({ start: 'b', reverse: true })).to.deep.equal(['d', 'c']);
|
|
239
246
|
expect(await keys({ end: 'b', reverse: true })).to.deep.equal(['b', 'a']);
|
|
240
247
|
});
|
|
248
|
+
|
|
249
|
+
it('returns 0 for missing key', async () => {
|
|
250
|
+
expect(await getValueCount('missing')).to.equal(0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('counts a single value', async () => {
|
|
254
|
+
await multiMap.set('foo', 'bar');
|
|
255
|
+
expect(await getValueCount('foo')).to.equal(1);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('counts multiple distinct values for same key', async () => {
|
|
259
|
+
await multiMap.set('foo', 'bar');
|
|
260
|
+
await multiMap.set('foo', 'baz');
|
|
261
|
+
await multiMap.set('foo', 'qux');
|
|
262
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('does not increase count for duplicate inserts', async () => {
|
|
266
|
+
await multiMap.set('foo', 'bar');
|
|
267
|
+
await multiMap.set('foo', 'bar');
|
|
268
|
+
await multiMap.set('foo', 'baz');
|
|
269
|
+
await multiMap.set('foo', 'baz');
|
|
270
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
271
|
+
expect(await getValues('foo')).to.have.members(['bar', 'baz']);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('decrements when deleting a single value', async () => {
|
|
275
|
+
await multiMap.set('foo', '1');
|
|
276
|
+
await multiMap.set('foo', '2');
|
|
277
|
+
await multiMap.set('foo', '3');
|
|
278
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
279
|
+
await multiMap.deleteValue('foo', '2');
|
|
280
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
281
|
+
expect(await getValues('foo')).to.have.members(['1', '3']);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('does not change count when deleting a non-existent value', async () => {
|
|
285
|
+
await multiMap.set('foo', '1');
|
|
286
|
+
await multiMap.set('foo', '3');
|
|
287
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
288
|
+
await multiMap.deleteValue('foo', '2');
|
|
289
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('clears all values when deleting a key', async () => {
|
|
293
|
+
await multiMap.set('foo', 'bar');
|
|
294
|
+
await multiMap.set('foo', 'baz');
|
|
295
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
296
|
+
await multiMap.delete('foo');
|
|
297
|
+
expect(await getValueCount('foo')).to.equal(0);
|
|
298
|
+
expect(await getValues('foo')).to.deep.equal([]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('count equals enumerated values length', async () => {
|
|
302
|
+
await multiMap.set('foo', '1');
|
|
303
|
+
await multiMap.set('foo', '2');
|
|
304
|
+
const vals = await getValues('foo');
|
|
305
|
+
expect(await getValueCount('foo')).to.equal(vals.length);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('sum of per-key counts equals total size', async () => {
|
|
309
|
+
await multiMap.set('foo', '1');
|
|
310
|
+
await multiMap.set('foo', '2');
|
|
311
|
+
await multiMap.set('bar', '3');
|
|
312
|
+
await multiMap.set('bar', '4');
|
|
313
|
+
await multiMap.set('baz', '5');
|
|
314
|
+
|
|
315
|
+
const allKeys = await keys();
|
|
316
|
+
const uniqueKeys = Array.from(new Set(allKeys));
|
|
317
|
+
const counts = await Promise.all(uniqueKeys.map(k => getValueCount(k)));
|
|
318
|
+
const sum = counts.reduce((s, n) => s + n, 0);
|
|
319
|
+
expect(sum).to.equal(await size());
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('supports sparse slots: delete middle, reinsert new, count remains correct', async () => {
|
|
323
|
+
await multiMap.set('foo', '1');
|
|
324
|
+
await multiMap.set('foo', '2');
|
|
325
|
+
await multiMap.set('foo', '3');
|
|
326
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
327
|
+
await multiMap.deleteValue('foo', '2');
|
|
328
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
329
|
+
await multiMap.set('foo', '4');
|
|
330
|
+
expect(await getValueCount('foo')).to.equal(3);
|
|
331
|
+
expect(await getValues('foo')).to.have.members(['1', '3', '4']);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('multiple keys are independent', async () => {
|
|
335
|
+
await multiMap.set('foo', '1');
|
|
336
|
+
await multiMap.set('foo', '2');
|
|
337
|
+
await multiMap.set('bar', '3');
|
|
338
|
+
expect(await getValueCount('foo')).to.equal(2);
|
|
339
|
+
expect(await getValueCount('bar')).to.equal(1);
|
|
340
|
+
});
|
|
241
341
|
});
|
|
242
342
|
}
|
package/src/lmdb/multi_map.ts
CHANGED
|
@@ -29,6 +29,22 @@ export class LmdbAztecMultiMap<K extends Key, V extends Value>
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
getValueCountAsync(key: K): Promise<number> {
|
|
33
|
+
const transaction = this.db.useReadTransaction();
|
|
34
|
+
try {
|
|
35
|
+
const values = this.db.getValues(this.slot(key), {
|
|
36
|
+
transaction,
|
|
37
|
+
});
|
|
38
|
+
let count = 0;
|
|
39
|
+
for (const _ of values) {
|
|
40
|
+
count++;
|
|
41
|
+
}
|
|
42
|
+
return Promise.resolve(count);
|
|
43
|
+
} finally {
|
|
44
|
+
transaction.done();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
async deleteValue(key: K, val: V): Promise<void> {
|
|
33
49
|
await this.db.remove(this.slot(key), [key, val]);
|
|
34
50
|
}
|
package/src/lmdb-v2/multi_map.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Encoder } from 'msgpackr/pack';
|
|
2
|
+
import { MAXIMUM_KEY, toBufferKey } from 'ordered-binary';
|
|
2
3
|
|
|
3
4
|
import type { Key, Range, Value } from '../interfaces/common.js';
|
|
4
5
|
import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
|
|
5
6
|
import type { ReadTransaction } from './read_transaction.js';
|
|
7
|
+
// eslint-disable-next-line import/no-cycle
|
|
6
8
|
import { type AztecLMDBStoreV2, execInReadTx, execInWriteTx } from './store.js';
|
|
7
9
|
import { deserializeKey, maxKey, minKey, serializeKey } from './utils.js';
|
|
8
10
|
|
|
@@ -138,4 +140,11 @@ export class LMDBMultiMap<K extends Key, V extends Value> implements AztecAsyncM
|
|
|
138
140
|
yield this.encoder.unpack(value);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
143
|
+
|
|
144
|
+
getValueCountAsync(key: K): Promise<number> {
|
|
145
|
+
const startKey = serializeKey(this.prefix, key);
|
|
146
|
+
const endKey = toBufferKey([this.prefix, key, MAXIMUM_KEY]);
|
|
147
|
+
|
|
148
|
+
return execInReadTx(this.store, tx => tx.countEntriesIndex(startKey, endKey, false));
|
|
149
|
+
}
|
|
141
150
|
}
|