@@ -43,41 +43,88 @@ const str2ab = (str) => {
43
43
return buf
44
44
}
45
45
46
- const queryIt = async function * ( q , store , location ) {
47
- const range = q . prefix ? self . IDBKeyRange . bound ( str2ab ( q . prefix ) , str2ab ( q . prefix + '\xFF' ) , false , true ) : undefined
48
- let cursor = await store . transaction ( location ) . store . openCursor ( range )
49
- let limit = 0
46
+ class IdbDatastore extends Adapter {
47
+ constructor ( location , options = { } ) {
48
+ super ( )
50
49
51
- if ( cursor && q . offset && q . offset > 0 ) {
52
- cursor = await cursor . advance ( q . offset )
50
+ this . store = null
51
+ this . options = options
52
+ this . location = options . prefix + location
53
+ this . version = options . version || 1
53
54
}
54
55
55
- while ( cursor ) {
56
- // limit
57
- if ( q . limit !== undefined && q . limit === limit ) {
58
- return
56
+ _getStore ( ) {
57
+ if ( this . store === null ) {
58
+ throw new Error ( 'Datastore needs to be opened.' )
59
59
}
60
- limit ++
61
60
62
- const key = new Key ( Buffer . from ( cursor . key ) )
63
- if ( q . keysOnly ) {
64
- yield { key }
65
- } else {
66
- const value = Buffer . from ( cursor . value )
67
- yield { key, value }
61
+ if ( ! this . _tx ) {
62
+ let cleanup
63
+
64
+ // idb gives us an `tx.done` promise, but awaiting on it then doing other
65
+ // work can add tasks to the microtask queue which extends the life of
66
+ // the transaction which may not be what the caller intended.
67
+ const done = new Promise ( resolve => {
68
+ cleanup = ( ) => {
69
+ // make sure we don't accidentally reuse the 'finished' transaction
70
+ this . _tx = null
71
+
72
+ // resolve on the next iteration of the event loop to ensure that
73
+ // we are actually, really done, the microtask queue has been emptied
74
+ // and the transaction has been auto-committed
75
+ setImmediate ( ( ) => {
76
+ resolve ( )
77
+ } )
78
+ }
79
+ } )
80
+
81
+ const tx = this . store . transaction ( this . location , 'readwrite' )
82
+ tx . oncomplete = cleanup
83
+ tx . onerror = cleanup
84
+ tx . onabort = cleanup
85
+
86
+ this . _tx = {
87
+ tx,
88
+ done
89
+ }
68
90
}
69
- cursor = await cursor . continue ( )
91
+
92
+ // we only operate on one object store so the tx.store property is set
93
+ return this . _tx . tx . store
70
94
}
71
- }
72
95
73
- class IdbDatastore extends Adapter {
74
- constructor ( location , options = { } ) {
75
- super ( )
96
+ async * _queryIt ( q ) {
97
+ if ( this . _tx ) {
98
+ await this . _tx . done
99
+ }
76
100
77
- this . store = null
78
- this . options = options
79
- this . location = options . prefix + location
80
- this . version = options . version || 1
101
+ const range = q . prefix ? self . IDBKeyRange . bound ( str2ab ( q . prefix ) , str2ab ( q . prefix + '\xFF' ) , false , true ) : undefined
102
+ const store = this . _getStore ( )
103
+ let cursor = await store . openCursor ( range )
104
+ let limit = 0
105
+
106
+ if ( cursor && q . offset && q . offset > 0 ) {
107
+ cursor = await cursor . advance ( q . offset )
108
+ }
109
+
110
+ while ( cursor ) {
111
+ // limit
112
+ if ( q . limit !== undefined && q . limit === limit ) {
113
+ return
114
+ }
115
+ limit ++
116
+
117
+ const key = new Key ( Buffer . from ( cursor . key ) )
118
+ if ( q . keysOnly ) {
119
+ yield { key }
120
+ } else {
121
+ const value = Buffer . from ( cursor . value )
122
+ yield { key, value }
123
+ }
124
+ cursor = await cursor . continue ( )
125
+ }
126
+
127
+ await this . _tx . done
81
128
}
82
129
83
130
async open ( ) {
@@ -98,23 +145,17 @@ class IdbDatastore extends Adapter {
98
145
}
99
146
100
147
async put ( key , val ) {
101
- if ( this . store === null ) {
102
- throw new Error ( 'Datastore needs to be opened.' )
103
- }
104
148
try {
105
- await this . store . put ( this . location , val , key . toBuffer ( ) )
149
+ await this . _getStore ( ) . put ( val , key . toBuffer ( ) )
106
150
} catch ( err ) {
107
151
throw Errors . dbWriteFailedError ( err )
108
152
}
109
153
}
110
154
111
155
async get ( key ) {
112
- if ( this . store === null ) {
113
- throw new Error ( 'Datastore needs to be opened.' )
114
- }
115
156
let value
116
157
try {
117
- value = await this . store . get ( this . location , key . toBuffer ( ) )
158
+ value = await this . _getStore ( ) . get ( key . toBuffer ( ) )
118
159
} catch ( err ) {
119
160
throw Errors . dbWriteFailedError ( err )
120
161
}
@@ -127,24 +168,19 @@ class IdbDatastore extends Adapter {
127
168
}
128
169
129
170
async has ( key ) {
130
- if ( this . store === null ) {
131
- throw new Error ( 'Datastore needs to be opened.' )
132
- }
133
171
try {
134
- await this . get ( key )
172
+ const res = await this . _getStore ( ) . getKey ( key . toBuffer ( ) )
173
+
174
+ return Boolean ( res )
135
175
} catch ( err ) {
136
176
if ( err . code === 'ERR_NOT_FOUND' ) return false
137
177
throw err
138
178
}
139
- return true
140
179
}
141
180
142
181
async delete ( key ) {
143
- if ( this . store === null ) {
144
- throw new Error ( 'Datastore needs to be opened.' )
145
- }
146
182
try {
147
- await this . store . delete ( this . location , key . toBuffer ( ) )
183
+ await this . _getStore ( ) . delete ( key . toBuffer ( ) )
148
184
} catch ( err ) {
149
185
throw Errors . dbDeleteFailedError ( err )
150
186
}
@@ -162,23 +198,20 @@ class IdbDatastore extends Adapter {
162
198
dels . push ( key . toBuffer ( ) )
163
199
} ,
164
200
commit : async ( ) => {
165
- if ( this . store === null ) {
166
- throw new Error ( 'Datastore needs to be opened.' )
201
+ if ( this . _tx ) {
202
+ await this . _tx . done
167
203
}
168
- const tx = this . store . transaction ( this . location , 'readwrite' )
169
- const store = tx . store
204
+
205
+ const store = this . _getStore ( )
170
206
await Promise . all ( puts . map ( p => store . put ( p [ 1 ] , p [ 0 ] ) ) )
171
207
await Promise . all ( dels . map ( p => store . delete ( p ) ) )
172
- await tx . done
208
+ await this . _tx . done
173
209
}
174
210
}
175
211
}
176
212
177
213
query ( q ) {
178
- if ( this . store === null ) {
179
- throw new Error ( 'Datastore needs to be opened.' )
180
- }
181
- let it = queryIt ( q , this . store , this . location )
214
+ let it = this . _queryIt ( q )
182
215
183
216
if ( Array . isArray ( q . filters ) ) {
184
217
it = q . filters . reduce ( ( it , f ) => filter ( it , f ) , it )
@@ -191,12 +224,15 @@ class IdbDatastore extends Adapter {
191
224
return it
192
225
}
193
226
194
- close ( ) {
195
- if ( this . store === null ) {
196
- throw new Error ( 'Datastore needs to be opened.' )
227
+ async close ( ) {
228
+ if ( this . _tx ) {
229
+ await this . _tx . done
230
+ }
231
+
232
+ if ( this . store ) {
233
+ this . store . close ( )
234
+ this . store = null
197
235
}
198
- this . store . close ( )
199
- this . store = null
200
236
}
201
237
202
238
destroy ( ) {
0 commit comments