-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathiterator.js
121 lines (98 loc) · 3.27 KB
/
iterator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
'use strict'
const { AbstractIterator } = require('abstract-level')
const binding = require('./binding')
const kContext = Symbol('context')
const kCache = Symbol('cache')
const kFirst = Symbol('first')
const kPosition = Symbol('position')
const kState = Symbol('state')
const kSignal = Symbol('signal')
const kAbort = Symbol('abort')
const empty = []
// Bit fields
const STATE_ENDED = 1
// Does not implement _all() because the default implementation
// of abstract-level falls back to nextv(1000) and using all()
// on more entries than that probably isn't a realistic use case,
// so it'll typically just make one nextv(1000) call and there's
// no performance gain in overriding _all().
class Iterator extends AbstractIterator {
constructor (db, context, options) {
super(db, options)
this[kState] = new Uint8Array(1)
this[kContext] = binding.iterator_init(context, this[kState], options)
this[kFirst] = true
this[kCache] = empty
this[kPosition] = 0
this[kAbort] = this[kAbort].bind(this)
// TODO: consider exposing iterator.signal in abstract-level
if (options.signal != null) {
this[kSignal] = options.signal
this[kSignal].addEventListener('abort', this[kAbort], { once: true })
} else {
this[kSignal] = null
}
}
_seek (target, options) {
this[kFirst] = true
this[kCache] = empty
this[kState][0] &= ~STATE_ENDED // Unset
this[kPosition] = 0
binding.iterator_seek(this[kContext], target)
}
async _next () {
if (this[kPosition] < this[kCache].length) {
return this[kCache][this[kPosition]++]
}
// Avoid iterator_nextv() call if end was already reached
if ((this[kState][0] & STATE_ENDED) !== 0) {
return undefined
}
if (this[kFirst]) {
// It's common to only want one entry initially or after a seek()
this[kFirst] = false
this[kCache] = await binding.iterator_nextv(this[kContext], 1)
this[kPosition] = 0
} else {
// Limit the size of the cache to prevent starving the event loop
// while we're recursively nexting.
this[kCache] = await binding.iterator_nextv(this[kContext], 1000)
this[kPosition] = 0
}
if (this[kPosition] < this[kCache].length) {
return this[kCache][this[kPosition]++]
}
}
async _nextv (size, options) {
this[kFirst] = false
// If next() was called then empty the cache first
if (this[kPosition] < this[kCache].length) {
const length = Math.min(size, this[kCache].length - this[kPosition])
const chunk = this[kCache].slice(this[kPosition], this[kPosition] + length)
this[kPosition] += length
return chunk
}
// Avoid iterator_nextv() call if end was already reached
if ((this[kState][0] & STATE_ENDED) !== 0) {
return []
}
return binding.iterator_nextv(this[kContext], size)
}
async _close () {
this[kCache] = empty
if (this[kSignal] !== null) {
this[kSignal].removeEventListener('abort', this[kAbort])
this[kSignal] = null
}
return binding.iterator_close(this[kContext])
}
[kAbort] () {
this[kSignal] = null
binding.iterator_abort(this[kContext])
}
// Undocumented, exposed for tests only
get cached () {
return this[kCache].length - this[kPosition]
}
}
exports.Iterator = Iterator