1
1
import { type Database , type RangeOptions } from 'lmdb' ;
2
2
3
3
import { type Key , type Range } from '../interfaces/common.js' ;
4
- import { type AztecAsyncMultiMap , type AztecMultiMap } from '../interfaces/map.js' ;
4
+ import { type AztecAsyncMultiMap , type AztecMapWithSize , type AztecMultiMap } from '../interfaces/map.js' ;
5
5
6
6
/** The slot where a key-value entry would be stored */
7
7
type MapValueSlot < K extends Key | Buffer > = [ 'map' , string , 'slot' , K ] ;
@@ -13,8 +13,8 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
13
13
protected db : Database < [ K , V ] , MapValueSlot < K > > ;
14
14
protected name : string ;
15
15
16
- # startSentinel: MapValueSlot < Buffer > ;
17
- # endSentinel: MapValueSlot < Buffer > ;
16
+ protected startSentinel : MapValueSlot < Buffer > ;
17
+ protected endSentinel : MapValueSlot < Buffer > ;
18
18
19
19
constructor ( rootDb : Database , mapName : string ) {
20
20
this . name = mapName ;
@@ -23,24 +23,24 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
23
23
// sentinels are used to define the start and end of the map
24
24
// with LMDB's key encoding, no _primitive value_ can be "less than" an empty buffer or greater than Byte 255
25
25
// these will be used later to answer range queries
26
- this . # startSentinel = [ 'map' , this . name , 'slot' , Buffer . from ( [ ] ) ] ;
27
- this . # endSentinel = [ 'map' , this . name , 'slot' , Buffer . from ( [ 255 ] ) ] ;
26
+ this . startSentinel = [ 'map' , this . name , 'slot' , Buffer . from ( [ ] ) ] ;
27
+ this . endSentinel = [ 'map' , this . name , 'slot' , Buffer . from ( [ 255 ] ) ] ;
28
28
}
29
29
30
30
close ( ) : Promise < void > {
31
31
return this . db . close ( ) ;
32
32
}
33
33
34
34
get ( key : K ) : V | undefined {
35
- return this . db . get ( this . # slot( key ) ) ?. [ 1 ] ;
35
+ return this . db . get ( this . slot ( key ) ) ?. [ 1 ] ;
36
36
}
37
37
38
38
getAsync ( key : K ) : Promise < V | undefined > {
39
39
return Promise . resolve ( this . get ( key ) ) ;
40
40
}
41
41
42
42
* getValues ( key : K ) : IterableIterator < V > {
43
- const values = this . db . getValues ( this . # slot( key ) ) ;
43
+ const values = this . db . getValues ( this . slot ( key ) ) ;
44
44
for ( const value of values ) {
45
45
yield value ?. [ 1 ] ;
46
46
}
@@ -53,38 +53,38 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
53
53
}
54
54
55
55
has ( key : K ) : boolean {
56
- return this . db . doesExist ( this . # slot( key ) ) ;
56
+ return this . db . doesExist ( this . slot ( key ) ) ;
57
57
}
58
58
59
59
hasAsync ( key : K ) : Promise < boolean > {
60
60
return Promise . resolve ( this . has ( key ) ) ;
61
61
}
62
62
63
63
async set ( key : K , val : V ) : Promise < void > {
64
- await this . db . put ( this . # slot( key ) , [ key , val ] ) ;
64
+ await this . db . put ( this . slot ( key ) , [ key , val ] ) ;
65
65
}
66
66
67
67
swap ( key : K , fn : ( val : V | undefined ) => V ) : Promise < void > {
68
68
return this . db . childTransaction ( ( ) => {
69
- const slot = this . # slot( key ) ;
69
+ const slot = this . slot ( key ) ;
70
70
const entry = this . db . get ( slot ) ;
71
71
void this . db . put ( slot , [ key , fn ( entry ?. [ 1 ] ) ] ) ;
72
72
} ) ;
73
73
}
74
74
75
75
setIfNotExists ( key : K , val : V ) : Promise < boolean > {
76
- const slot = this . # slot( key ) ;
76
+ const slot = this . slot ( key ) ;
77
77
return this . db . ifNoExists ( slot , ( ) => {
78
78
void this . db . put ( slot , [ key , val ] ) ;
79
79
} ) ;
80
80
}
81
81
82
82
async delete ( key : K ) : Promise < void > {
83
- await this . db . remove ( this . # slot( key ) ) ;
83
+ await this . db . remove ( this . slot ( key ) ) ;
84
84
}
85
85
86
86
async deleteValue ( key : K , val : V ) : Promise < void > {
87
- await this . db . remove ( this . # slot( key ) , [ key , val ] ) ;
87
+ await this . db . remove ( this . slot ( key ) , [ key , val ] ) ;
88
88
}
89
89
90
90
* entries ( range : Range < K > = { } ) : IterableIterator < [ K , V ] > {
@@ -93,19 +93,19 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
93
93
// in that case, we need to swap the start and end sentinels
94
94
const start = reverse
95
95
? range . end
96
- ? this . # slot( range . end )
97
- : this . # endSentinel
96
+ ? this . slot ( range . end )
97
+ : this . endSentinel
98
98
: range . start
99
- ? this . # slot( range . start )
100
- : this . # startSentinel;
99
+ ? this . slot ( range . start )
100
+ : this . startSentinel ;
101
101
102
102
const end = reverse
103
103
? range . start
104
- ? this . # slot( range . start )
105
- : this . # startSentinel
104
+ ? this . slot ( range . start )
105
+ : this . startSentinel
106
106
: range . end
107
- ? this . # slot( range . end )
108
- : this . # endSentinel;
107
+ ? this . slot ( range . end )
108
+ : this . endSentinel ;
109
109
110
110
const lmdbRange : RangeOptions = {
111
111
start,
@@ -153,7 +153,82 @@ export class LmdbAztecMap<K extends Key, V> implements AztecMultiMap<K, V>, Azte
153
153
}
154
154
}
155
155
156
- # slot( key : K ) : MapValueSlot < K > {
156
+ protected slot ( key : K ) : MapValueSlot < K > {
157
157
return [ 'map' , this . name , 'slot' , key ] ;
158
158
}
159
+
160
+ async clear ( ) : Promise < void > {
161
+ const lmdbRange : RangeOptions = {
162
+ start : this . startSentinel ,
163
+ end : this . endSentinel ,
164
+ } ;
165
+
166
+ const iterator = this . db . getRange ( lmdbRange ) ;
167
+
168
+ for ( const { key } of iterator ) {
169
+ await this . db . remove ( key ) ;
170
+ }
171
+ }
172
+ }
173
+
174
+ export class LmdbAztecMapWithSize < K extends Key , V >
175
+ extends LmdbAztecMap < K , V >
176
+ implements AztecMapWithSize < K , V > , AztecAsyncMultiMap < K , V >
177
+ {
178
+ #sizeCache?: number ;
179
+
180
+ constructor ( rootDb : Database , mapName : string ) {
181
+ super ( rootDb , mapName ) ;
182
+ }
183
+
184
+ override async set ( key : K , val : V ) : Promise < void > {
185
+ await this . db . childTransaction ( ( ) => {
186
+ const exists = this . db . doesExist ( this . slot ( key ) ) ;
187
+ this . db . putSync ( this . slot ( key ) , [ key , val ] , {
188
+ appendDup : true ,
189
+ } ) ;
190
+ if ( ! exists ) {
191
+ this . #sizeCache = undefined ; // Invalidate cache
192
+ }
193
+ } ) ;
194
+ }
195
+
196
+ override async delete ( key : K ) : Promise < void > {
197
+ await this . db . childTransaction ( async ( ) => {
198
+ const exists = this . db . doesExist ( this . slot ( key ) ) ;
199
+ if ( exists ) {
200
+ await this . db . remove ( this . slot ( key ) ) ;
201
+ this . #sizeCache = undefined ; // Invalidate cache
202
+ }
203
+ } ) ;
204
+ }
205
+
206
+ override async deleteValue ( key : K , val : V ) : Promise < void > {
207
+ await this . db . childTransaction ( async ( ) => {
208
+ const exists = this . db . doesExist ( this . slot ( key ) ) ;
209
+ if ( exists ) {
210
+ await this . db . remove ( this . slot ( key ) , [ key , val ] ) ;
211
+ this . #sizeCache = undefined ; // Invalidate cache
212
+ }
213
+ } ) ;
214
+ }
215
+
216
+ /**
217
+ * Gets the size of the map by counting entries.
218
+ * @returns The number of entries in the map
219
+ */
220
+ size ( ) : number {
221
+ if ( this . #sizeCache === undefined ) {
222
+ this . #sizeCache = this . db . getCount ( {
223
+ start : this . startSentinel ,
224
+ end : this . endSentinel ,
225
+ } ) ;
226
+ }
227
+ return this . #sizeCache;
228
+ }
229
+
230
+ // Reset cache on clear/drop operations
231
+ clearCache ( ) {
232
+ this . #sizeCache = undefined ;
233
+ }
159
234
}
0 commit comments