@@ -4,137 +4,192 @@ const { AbstractIterator } = require('abstract-level')
44const createKeyRange = require ( './util/key-range' )
55const deserialize = require ( './util/deserialize' )
66
7- const noop = function ( ) { }
8- const kCount = Symbol ( 'count' )
9- const kCallback = Symbol ( 'callback' )
107const kCache = Symbol ( 'cache' )
11- const kCompleted = Symbol ( 'completed' )
12- const kAborted = Symbol ( 'aborted' )
13- const kError = Symbol ( 'error' )
14- const kKeys = Symbol ( 'keys' )
15- const kValues = Symbol ( 'values' )
16- const kOnItem = Symbol ( 'onItem' )
17- const kOnAbort = Symbol ( 'onAbort' )
18- const kOnComplete = Symbol ( 'onComplete' )
19- const kMaybeNext = Symbol ( 'maybeNext' )
8+ const kFinished = Symbol ( 'finished' )
9+ const kOptions = Symbol ( 'options' )
10+ const kPosition = Symbol ( 'position' )
11+ const kLocation = Symbol ( 'location' )
12+ const emptyOptions = { }
2013
2114class Iterator extends AbstractIterator {
2215 constructor ( db , location , options ) {
2316 super ( db , options )
2417
25- this [ kCount ] = 0
26- this [ kCallback ] = null
2718 this [ kCache ] = [ ]
28- this [ kCompleted ] = false
29- this [ kAborted ] = false
30- this [ kError ] = null
31- this [ kKeys ] = options . keys
32- this [ kValues ] = options . values
33-
34- if ( this . limit === 0 ) {
35- this [ kCompleted ] = true
36- return
19+ this [ kFinished ] = this . limit === 0
20+ this [ kOptions ] = options
21+ this [ kPosition ] = undefined
22+ this [ kLocation ] = location
23+ }
24+
25+ // Note: if called by _all() then size can be Infinity. This is an internal
26+ // detail; by design AbstractIterator.nextv() does not support Infinity.
27+ _nextv ( size , options , callback ) {
28+ if ( this [ kFinished ] ) {
29+ return this . nextTick ( callback , null , [ ] )
30+ } else if ( this [ kCache ] . length > 0 ) {
31+ // TODO: mixing next and nextv is not covered by test suite
32+ size = Math . min ( size , this [ kCache ] . length )
33+ return this . nextTick ( callback , null , this [ kCache ] . splice ( 0 , size ) )
34+ }
35+
36+ // Adjust range by what we already visited
37+ if ( this [ kPosition ] !== undefined ) {
38+ if ( this [ kOptions ] . reverse ) {
39+ this [ kOptions ] . lt = this [ kPosition ]
40+ this [ kOptions ] . lte = undefined
41+ } else {
42+ this [ kOptions ] . gt = this [ kPosition ]
43+ this [ kOptions ] . gte = undefined
44+ }
3745 }
3846
3947 let keyRange
4048
4149 try {
42- keyRange = createKeyRange ( options )
43- } catch ( e ) {
50+ keyRange = createKeyRange ( this [ kOptions ] )
51+ } catch ( _ ) {
4452 // The lower key is greater than the upper key.
4553 // IndexedDB throws an error, but we'll just return 0 results.
46- this [ kCompleted ] = true
47- return
54+ this [ kFinished ] = true
55+ return this . nextTick ( callback , null , [ ] )
4856 }
4957
50- const transaction = db . db . transaction ( [ location ] , 'readonly' )
51- const store = transaction . objectStore ( location )
52- const req = store . openCursor ( keyRange , options . reverse ? 'prev' : 'next' )
58+ const transaction = this . db . db . transaction ( [ this [ kLocation ] ] , 'readonly' )
59+ const store = transaction . objectStore ( this [ kLocation ] )
60+ const entries = [ ]
5361
54- req . onsuccess = ( ev ) => {
55- const cursor = ev . target . result
56- if ( cursor ) this [ kOnItem ] ( cursor )
57- }
62+ if ( ! this [ kOptions ] . reverse ) {
63+ let keys
64+ let values
5865
59- // If an error occurs (on the request), the transaction will abort.
60- transaction . onabort = ( ) => {
61- this [ kOnAbort ] ( transaction . error || new Error ( 'aborted by user' ) )
62- }
66+ const complete = ( ) => {
67+ // Wait for both requests to complete
68+ if ( keys === undefined || values === undefined ) return
6369
64- transaction . oncomplete = ( ) => {
65- this [ kOnComplete ] ( )
66- }
67- }
70+ const length = Math . max ( keys . length , values . length )
6871
69- [ kOnItem ] ( cursor ) {
70- this [ kCache ] . push ( cursor . key , cursor . value )
71-
72- if ( ++ this [ kCount ] < this . limit ) {
73- cursor . continue ( )
74- }
72+ if ( length === 0 || size === Infinity ) {
73+ this [ kFinished ] = true
74+ } else {
75+ this [ kPosition ] = keys [ length - 1 ]
76+ }
7577
76- this [ kMaybeNext ] ( )
77- }
78+ // Resize
79+ entries . length = length
7880
79- [ kOnAbort ] ( err ) {
80- this [ kAborted ] = true
81- this [ kError ] = err
82- this [ kMaybeNext ] ( )
83- }
81+ // Merge keys and values
82+ for ( let i = 0 ; i < length ; i ++ ) {
83+ const key = keys [ i ]
84+ const value = values [ i ]
8485
85- [ kOnComplete ] ( ) {
86- this [ kCompleted ] = true
87- this [ kMaybeNext ] ( )
88- }
86+ entries [ i ] = [
87+ this [ kOptions ] . keys && key !== undefined ? deserialize ( key ) : undefined ,
88+ this [ kOptions ] . values && value !== undefined ? deserialize ( value ) : undefined
89+ ]
90+ }
8991
90- [ kMaybeNext ] ( ) {
91- if ( this [ kCallback ] ) {
92- this . _next ( this [ kCallback ] )
93- this [ kCallback ] = null
94- }
95- }
96-
97- _next ( callback ) {
98- if ( this [ kAborted ] ) {
99- const err = this [ kError ]
100- this [ kError ] = null
101- this . nextTick ( callback , err )
102- } else if ( this [ kCache ] . length > 0 ) {
103- let key = this [ kCache ] . shift ( )
104- let value = this [ kCache ] . shift ( )
92+ maybeCommit ( transaction )
93+ }
10594
106- if ( this [ kKeys ] && key !== undefined ) {
107- key = deserialize ( key )
95+ // If keys were not requested and size is Infinity, we don't have to keep
96+ // track of position and can thus skip getting keys.
97+ if ( this [ kOptions ] . keys || size < Infinity ) {
98+ store . getAllKeys ( keyRange , size < Infinity ? size : undefined ) . onsuccess = ( ev ) => {
99+ keys = ev . target . result
100+ complete ( )
101+ }
108102 } else {
109- key = undefined
103+ keys = [ ]
104+ this . nextTick ( complete )
110105 }
111106
112- if ( this [ kValues ] && value !== undefined ) {
113- value = deserialize ( value )
107+ if ( this [ kOptions ] . values ) {
108+ store . getAll ( keyRange , size < Infinity ? size : undefined ) . onsuccess = ( ev ) => {
109+ values = ev . target . result
110+ complete ( )
111+ }
114112 } else {
115- value = undefined
113+ values = [ ]
114+ this . nextTick ( complete )
115+ }
116+ } else {
117+ // Can't use getAll() in reverse, so use a slower cursor that yields one item at a time
118+ store . openCursor ( keyRange , 'prev' ) . onsuccess = ( ev ) => {
119+ const cursor = ev . target . result
120+
121+ if ( cursor ) {
122+ const { key, value } = cursor
123+ this [ kPosition ] = key
124+
125+ entries . push ( [
126+ this [ kOptions ] . keys && key !== undefined ? deserialize ( key ) : undefined ,
127+ this [ kOptions ] . values && value !== undefined ? deserialize ( value ) : undefined
128+ ] )
129+
130+ if ( entries . length < size ) {
131+ cursor . continue ( )
132+ } else {
133+ maybeCommit ( transaction )
134+ }
135+ } else {
136+ this [ kFinished ] = true
137+ }
116138 }
139+ }
140+
141+ // If an error occurs (on the request), the transaction will abort.
142+ transaction . onabort = ( ) => {
143+ callback ( transaction . error || new Error ( 'aborted by user' ) )
144+ callback = null
145+ }
146+
147+ transaction . oncomplete = ( ) => {
148+ callback ( null , entries )
149+ callback = null
150+ }
151+ }
117152
153+ _next ( callback ) {
154+ if ( this [ kCache ] . length > 0 ) {
155+ const [ key , value ] = this [ kCache ] . shift ( )
118156 this . nextTick ( callback , null , key , value )
119- } else if ( this [ kCompleted ] ) {
157+ } else if ( this [ kFinished ] ) {
120158 this . nextTick ( callback )
121159 } else {
122- this [ kCallback ] = callback
160+ // TODO: use 1 if this is the first _next() call (see classic-level)
161+ const size = Math . min ( 100 , this . limit - this . count )
162+
163+ this . _nextv ( size , emptyOptions , ( err , entries ) => {
164+ if ( err ) return callback ( err )
165+ this [ kCache ] = entries
166+ this . _next ( callback )
167+ } )
123168 }
124169 }
125170
126- _close ( callback ) {
127- if ( this [ kAborted ] || this [ kCompleted ] ) {
128- return this . nextTick ( callback )
171+ _all ( options , callback ) {
172+ // TODO: mixing next and all is not covered by test suite
173+ const cache = this [ kCache ] . splice ( 0 , this [ kCache ] . length )
174+ const size = this . limit - this . count - cache . length
175+
176+ if ( size <= 0 ) {
177+ return this . nextTick ( callback , null , cache )
129178 }
130179
131- // Don't advance the cursor anymore, and the transaction will complete
132- // on its own in the next tick. This approach is much cleaner than calling
133- // transaction.abort() with its unpredictable event order.
134- this [ kOnItem ] = noop
135- this [ kOnAbort ] = callback
136- this [ kOnComplete ] = callback
180+ this . _nextv ( size , emptyOptions , ( err , entries ) => {
181+ if ( err ) return callback ( err )
182+ if ( cache . length > 0 ) entries = cache . concat ( entries )
183+ callback ( null , entries )
184+ } )
137185 }
138186}
139187
140188exports . Iterator = Iterator
189+
190+ function maybeCommit ( transaction ) {
191+ // Commit (meaning close) now instead of waiting for auto-commit
192+ if ( typeof transaction . commit === 'function' ) {
193+ transaction . commit ( )
194+ }
195+ }
0 commit comments