@aztec/kv-store 0.86.0-nightly.20250501 → 0.86.0-nightly.20250502
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.map +1 -1
- package/dest/indexeddb/multi_map.js +20 -6
- package/dest/indexeddb/store.d.ts.map +1 -1
- package/dest/indexeddb/store.js +5 -1
- package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -1
- package/dest/interfaces/multi_map_test_suite.js +66 -2
- package/package.json +5 -5
- package/src/indexeddb/multi_map.ts +17 -3
- package/src/indexeddb/store.ts +5 -1
- package/src/interfaces/multi_map_test_suite.ts +63 -2
|
@@ -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;
|
|
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;IAcjD,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAejD"}
|
|
@@ -4,6 +4,7 @@ import { IndexedDBAztecMap } from './map.js';
|
|
|
4
4
|
* A multi map backed by IndexedDB.
|
|
5
5
|
*/ export class IndexedDBAztecMultiMap extends IndexedDBAztecMap {
|
|
6
6
|
async set(key, val) {
|
|
7
|
+
// Inserting repeated values is a no-op
|
|
7
8
|
const exists = !!await this.db.index('hash').get(IDBKeyRange.bound([
|
|
8
9
|
this.container,
|
|
9
10
|
this.normalizeKey(key),
|
|
@@ -16,13 +17,23 @@ import { IndexedDBAztecMap } from './map.js';
|
|
|
16
17
|
if (exists) {
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
// Get the maximum keyCount for the given key
|
|
21
|
+
// In order to support sparse multimaps, we cannot rely
|
|
22
|
+
// on just counting the number of entries for the key, since we would repeat slots
|
|
23
|
+
// if we delete an entry
|
|
24
|
+
// set -> container:key:0 (keyCount = 1)
|
|
25
|
+
// set -> container:key:1 (keyCount = 2)
|
|
26
|
+
// delete -> container:key:0 (keyCount = 1)
|
|
27
|
+
// set -> container:key:1 <--- already exists!
|
|
28
|
+
// Instead, we iterate in reverse order to get the last inserted entry
|
|
29
|
+
const index = this.db.index('keyCount');
|
|
30
|
+
const rangeQuery = IDBKeyRange.upperBound([
|
|
23
31
|
this.container,
|
|
24
|
-
this.normalizeKey(key)
|
|
25
|
-
|
|
32
|
+
this.normalizeKey(key),
|
|
33
|
+
Number.MAX_SAFE_INTEGER
|
|
34
|
+
]);
|
|
35
|
+
const maxEntry = (await index.iterate(rangeQuery, 'prevunique').next()).value;
|
|
36
|
+
const count = maxEntry?.value?.keyCount ?? 0;
|
|
26
37
|
await this.db.put({
|
|
27
38
|
value: val,
|
|
28
39
|
hash: hash(val),
|
|
@@ -33,6 +44,7 @@ import { IndexedDBAztecMap } from './map.js';
|
|
|
33
44
|
});
|
|
34
45
|
}
|
|
35
46
|
async *getValuesAsync(key) {
|
|
47
|
+
// Iterate over the whole range of keyCount for the given key
|
|
36
48
|
const index = this.db.index('keyCount');
|
|
37
49
|
const rangeQuery = IDBKeyRange.bound([
|
|
38
50
|
this.container,
|
|
@@ -48,6 +60,8 @@ import { IndexedDBAztecMap } from './map.js';
|
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
async deleteValue(key, val) {
|
|
63
|
+
// Since we know the value, we can hash it and directly query the "hash" index
|
|
64
|
+
// to avoid having to iterate over all the values
|
|
51
65
|
const fullKey = await this.db.index('hash').getKey(IDBKeyRange.bound([
|
|
52
66
|
this.container,
|
|
53
67
|
this.normalizeKey(key),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/indexeddb/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAoB,MAAM,KAAK,CAAC;AAEzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAOhE,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,KAAK,IAAI;IACxC,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,WAAW,cAAe,SAAQ,QAAQ;IAC9C,IAAI,EAAE;QACJ,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;QACvB,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KAC7E,CAAC;CACH;AAED;;GAEG;AAEH,qBAAa,mBAAoB,YAAW,iBAAiB;;aAaO,WAAW,EAAE,OAAO;gBAA1E,MAAM,EAAE,YAAY,CAAC,cAAc,CAAC,EAAkB,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAKjH;;;;;;;;;OASG;WACU,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,GAAE,OAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/indexeddb/store.ts"],"names":[],"mappings":"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"}
|
package/dest/indexeddb/store.js
CHANGED
|
@@ -42,13 +42,17 @@ import { IndexedDBAztecSingleton } from './singleton.js';
|
|
|
42
42
|
], {
|
|
43
43
|
unique: false
|
|
44
44
|
});
|
|
45
|
+
// Keep count of the maximum number of keys ever inserted in the container
|
|
46
|
+
// This allows unique slots for repeated keys, which is useful for multi-maps
|
|
45
47
|
objectStore.createIndex('keyCount', [
|
|
46
48
|
'container',
|
|
47
49
|
'key',
|
|
48
50
|
'keyCount'
|
|
49
51
|
], {
|
|
50
|
-
unique:
|
|
52
|
+
unique: true
|
|
51
53
|
});
|
|
54
|
+
// Keep an index on the pair key-hash for a given container, allowing us to efficiently
|
|
55
|
+
// delete unique values from multi-maps
|
|
52
56
|
objectStore.createIndex('hash', [
|
|
53
57
|
'container',
|
|
54
58
|
'key',
|
|
@@ -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,QAsM5B"}
|
|
@@ -95,11 +95,75 @@ export function describeAztecMultiMap(testName, getStore, forceAsync = false) {
|
|
|
95
95
|
]);
|
|
96
96
|
});
|
|
97
97
|
it('should be able to delete individual values for a single key', async ()=>{
|
|
98
|
+
await multiMap.set('foo', '1');
|
|
99
|
+
await multiMap.set('foo', '2');
|
|
100
|
+
await multiMap.set('foo', '3');
|
|
101
|
+
await multiMap.deleteValue('foo', '2');
|
|
102
|
+
expect(await getValues('foo')).to.deep.equal([
|
|
103
|
+
'1',
|
|
104
|
+
'3'
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
it('should be able to delete the last and first values for a key', async ()=>{
|
|
108
|
+
await multiMap.set('foo', '1');
|
|
109
|
+
await multiMap.set('foo', '2');
|
|
110
|
+
await multiMap.set('foo', '3');
|
|
111
|
+
await multiMap.deleteValue('foo', '1');
|
|
112
|
+
expect(await getValues('foo')).to.deep.equal([
|
|
113
|
+
'2',
|
|
114
|
+
'3'
|
|
115
|
+
]);
|
|
116
|
+
await multiMap.deleteValue('foo', '3');
|
|
117
|
+
expect(await getValues('foo')).to.deep.equal([
|
|
118
|
+
'2'
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
it('should be able to fully clear a key', async ()=>{
|
|
122
|
+
await multiMap.set('foo', '1');
|
|
123
|
+
await multiMap.set('foo', '2');
|
|
124
|
+
await multiMap.set('foo', '3');
|
|
125
|
+
await multiMap.deleteValue('foo', '1');
|
|
126
|
+
await multiMap.deleteValue('foo', '3');
|
|
127
|
+
await multiMap.deleteValue('foo', '2');
|
|
128
|
+
expect(await getValues('foo')).to.deep.equal([]);
|
|
129
|
+
});
|
|
130
|
+
it('should be able to insert after deletion', async ()=>{
|
|
131
|
+
await multiMap.set('foo', '1');
|
|
132
|
+
await multiMap.set('foo', '2');
|
|
133
|
+
await multiMap.set('foo', '3');
|
|
134
|
+
await multiMap.deleteValue('foo', '2');
|
|
98
135
|
await multiMap.set('foo', 'bar');
|
|
99
|
-
await
|
|
136
|
+
expect(await getValues('foo')).to.deep.equal([
|
|
137
|
+
'1',
|
|
138
|
+
'3',
|
|
139
|
+
'bar'
|
|
140
|
+
]);
|
|
141
|
+
// Delete the just-added entry
|
|
100
142
|
await multiMap.deleteValue('foo', 'bar');
|
|
101
143
|
expect(await getValues('foo')).to.deep.equal([
|
|
102
|
-
'
|
|
144
|
+
'1',
|
|
145
|
+
'3'
|
|
146
|
+
]);
|
|
147
|
+
// Reinsert the initially deleted key
|
|
148
|
+
await multiMap.set('foo', '2');
|
|
149
|
+
// LMDB and IndexedDB behave differently here, the former ordering by value and the latter by insertion. This is
|
|
150
|
+
// fine because there is no expectation for values in a multimap to be ordered.
|
|
151
|
+
const values = (await getValues('foo')).sort((a, b)=>a.localeCompare(b));
|
|
152
|
+
expect(values).to.deep.equal([
|
|
153
|
+
'1',
|
|
154
|
+
'2',
|
|
155
|
+
'3'
|
|
156
|
+
]);
|
|
157
|
+
// Fully clear the key
|
|
158
|
+
await multiMap.deleteValue('foo', '1');
|
|
159
|
+
await multiMap.deleteValue('foo', '3');
|
|
160
|
+
await multiMap.deleteValue('foo', '2');
|
|
161
|
+
// Insert some more
|
|
162
|
+
await multiMap.set('foo', 'baz');
|
|
163
|
+
await multiMap.set('foo', 'qux');
|
|
164
|
+
expect(await getValues('foo')).to.deep.equal([
|
|
165
|
+
'baz',
|
|
166
|
+
'qux'
|
|
103
167
|
]);
|
|
104
168
|
});
|
|
105
169
|
it('supports range queries', async ()=>{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/kv-store",
|
|
3
|
-
"version": "0.86.0-nightly.
|
|
3
|
+
"version": "0.86.0-nightly.20250502",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/interfaces/index.js",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"./package.local.json"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@aztec/ethereum": "0.86.0-nightly.
|
|
27
|
-
"@aztec/foundation": "0.86.0-nightly.
|
|
28
|
-
"@aztec/native": "0.86.0-nightly.
|
|
29
|
-
"@aztec/stdlib": "0.86.0-nightly.
|
|
26
|
+
"@aztec/ethereum": "0.86.0-nightly.20250502",
|
|
27
|
+
"@aztec/foundation": "0.86.0-nightly.20250502",
|
|
28
|
+
"@aztec/native": "0.86.0-nightly.20250502",
|
|
29
|
+
"@aztec/stdlib": "0.86.0-nightly.20250502",
|
|
30
30
|
"idb": "^8.0.0",
|
|
31
31
|
"lmdb": "^3.2.0",
|
|
32
32
|
"msgpackr": "^1.11.2",
|
|
@@ -12,6 +12,7 @@ export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
|
12
12
|
implements AztecAsyncMultiMap<K, V>
|
|
13
13
|
{
|
|
14
14
|
override async set(key: K, val: V): Promise<void> {
|
|
15
|
+
// Inserting repeated values is a no-op
|
|
15
16
|
const exists = !!(await this.db
|
|
16
17
|
.index('hash')
|
|
17
18
|
.get(
|
|
@@ -23,9 +24,19 @@ export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
|
23
24
|
if (exists) {
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
29
40
|
await this.db.put({
|
|
30
41
|
value: val,
|
|
31
42
|
hash: hash(val),
|
|
@@ -37,6 +48,7 @@ export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
async *getValuesAsync(key: K): AsyncIterableIterator<V> {
|
|
51
|
+
// Iterate over the whole range of keyCount for the given key
|
|
40
52
|
const index = this.db.index('keyCount');
|
|
41
53
|
const rangeQuery = IDBKeyRange.bound(
|
|
42
54
|
[this.container, this.normalizeKey(key), 0],
|
|
@@ -50,6 +62,8 @@ export class IndexedDBAztecMultiMap<K extends Key, V extends Value>
|
|
|
50
62
|
}
|
|
51
63
|
|
|
52
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
|
|
53
67
|
const fullKey = await this.db
|
|
54
68
|
.index('hash')
|
|
55
69
|
.getKey(
|
package/src/indexeddb/store.ts
CHANGED
|
@@ -73,7 +73,11 @@ export class AztecIndexedDBStore implements AztecAsyncKVStore {
|
|
|
73
73
|
const objectStore = db.createObjectStore('data', { keyPath: 'slot' });
|
|
74
74
|
|
|
75
75
|
objectStore.createIndex('key', ['container', 'key'], { unique: false });
|
|
76
|
-
|
|
76
|
+
// Keep count of the maximum number of keys ever inserted in the container
|
|
77
|
+
// This allows unique slots for repeated keys, which is useful for multi-maps
|
|
78
|
+
objectStore.createIndex('keyCount', ['container', 'key', 'keyCount'], { unique: true });
|
|
79
|
+
// Keep an index on the pair key-hash for a given container, allowing us to efficiently
|
|
80
|
+
// delete unique values from multi-maps
|
|
77
81
|
objectStore.createIndex('hash', ['container', 'key', 'hash'], { unique: true });
|
|
78
82
|
},
|
|
79
83
|
});
|
|
@@ -124,12 +124,73 @@ export function describeAztecMultiMap(
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
it('should be able to delete individual values for a single key', async () => {
|
|
127
|
+
await multiMap.set('foo', '1');
|
|
128
|
+
await multiMap.set('foo', '2');
|
|
129
|
+
await multiMap.set('foo', '3');
|
|
130
|
+
|
|
131
|
+
await multiMap.deleteValue('foo', '2');
|
|
132
|
+
|
|
133
|
+
expect(await getValues('foo')).to.deep.equal(['1', '3']);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should be able to delete the last and first values for a key', async () => {
|
|
137
|
+
await multiMap.set('foo', '1');
|
|
138
|
+
await multiMap.set('foo', '2');
|
|
139
|
+
await multiMap.set('foo', '3');
|
|
140
|
+
|
|
141
|
+
await multiMap.deleteValue('foo', '1');
|
|
142
|
+
|
|
143
|
+
expect(await getValues('foo')).to.deep.equal(['2', '3']);
|
|
144
|
+
|
|
145
|
+
await multiMap.deleteValue('foo', '3');
|
|
146
|
+
|
|
147
|
+
expect(await getValues('foo')).to.deep.equal(['2']);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should be able to fully clear a key', async () => {
|
|
151
|
+
await multiMap.set('foo', '1');
|
|
152
|
+
await multiMap.set('foo', '2');
|
|
153
|
+
await multiMap.set('foo', '3');
|
|
154
|
+
|
|
155
|
+
await multiMap.deleteValue('foo', '1');
|
|
156
|
+
await multiMap.deleteValue('foo', '3');
|
|
157
|
+
await multiMap.deleteValue('foo', '2');
|
|
158
|
+
|
|
159
|
+
expect(await getValues('foo')).to.deep.equal([]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should be able to insert after deletion', async () => {
|
|
163
|
+
await multiMap.set('foo', '1');
|
|
164
|
+
await multiMap.set('foo', '2');
|
|
165
|
+
await multiMap.set('foo', '3');
|
|
166
|
+
|
|
167
|
+
await multiMap.deleteValue('foo', '2');
|
|
127
168
|
await multiMap.set('foo', 'bar');
|
|
128
|
-
await multiMap.set('foo', 'baz');
|
|
129
169
|
|
|
170
|
+
expect(await getValues('foo')).to.deep.equal(['1', '3', 'bar']);
|
|
171
|
+
|
|
172
|
+
// Delete the just-added entry
|
|
130
173
|
await multiMap.deleteValue('foo', 'bar');
|
|
131
174
|
|
|
132
|
-
expect(await getValues('foo')).to.deep.equal(['
|
|
175
|
+
expect(await getValues('foo')).to.deep.equal(['1', '3']);
|
|
176
|
+
|
|
177
|
+
// Reinsert the initially deleted key
|
|
178
|
+
await multiMap.set('foo', '2');
|
|
179
|
+
|
|
180
|
+
// LMDB and IndexedDB behave differently here, the former ordering by value and the latter by insertion. This is
|
|
181
|
+
// fine because there is no expectation for values in a multimap to be ordered.
|
|
182
|
+
const values = (await getValues('foo')).sort((a, b) => a.localeCompare(b));
|
|
183
|
+
expect(values).to.deep.equal(['1', '2', '3']);
|
|
184
|
+
|
|
185
|
+
// Fully clear the key
|
|
186
|
+
await multiMap.deleteValue('foo', '1');
|
|
187
|
+
await multiMap.deleteValue('foo', '3');
|
|
188
|
+
await multiMap.deleteValue('foo', '2');
|
|
189
|
+
|
|
190
|
+
// Insert some more
|
|
191
|
+
await multiMap.set('foo', 'baz');
|
|
192
|
+
await multiMap.set('foo', 'qux');
|
|
193
|
+
expect(await getValues('foo')).to.deep.equal(['baz', 'qux']);
|
|
133
194
|
});
|
|
134
195
|
|
|
135
196
|
it('supports range queries', async () => {
|