UNPKG

7.75 kBJavaScriptView Raw
1'use strict'
2
3const EventEmitter = require('events').EventEmitter
4const inherits = require('util').inherits
5const DeferredLevelDOWN = require('deferred-leveldown')
6const IteratorStream = require('level-iterator-stream')
7const Batch = require('./batch')
8const errors = require('level-errors')
9const supports = require('level-supports')
10const assert = require('assert')
11const catering = require('catering')
12const getCallback = require('./common').getCallback
13const getOptions = require('./common').getOptions
14
15// TODO: after we drop node 10, also use queueMicrotask() in node
16const nextTick = require('./next-tick')
17
18const WriteError = errors.WriteError
19const ReadError = errors.ReadError
20const NotFoundError = errors.NotFoundError
21const OpenError = errors.OpenError
22const InitializationError = errors.InitializationError
23
24// Possible AbstractLevelDOWN#status values:
25// - 'new' - newly created, not opened or closed
26// - 'opening' - waiting for the database to be opened, post open()
27// - 'open' - successfully opened the database, available for use
28// - 'closing' - waiting for the database to be closed, post close()
29// - 'closed' - database has been successfully closed, should not be
30// used except for another open() operation
31
32function LevelUP (db, options, callback) {
33 if (!(this instanceof LevelUP)) {
34 return new LevelUP(db, options, callback)
35 }
36
37 let error
38
39 EventEmitter.call(this)
40 this.setMaxListeners(Infinity)
41
42 if (typeof options === 'function') {
43 callback = options
44 options = {}
45 }
46
47 options = options || {}
48
49 if (!db || typeof db !== 'object') {
50 error = new InitializationError('First argument must be an abstract-leveldown compliant store')
51 if (typeof callback === 'function') {
52 return nextTick(callback, error)
53 }
54 throw error
55 }
56
57 assert.strictEqual(typeof db.status, 'string', '.status required, old abstract-leveldown')
58
59 this.options = getOptions(options)
60 this._db = db
61 this.db = new DeferredLevelDOWN(db)
62 this.open(callback || ((err) => {
63 if (err) this.emit('error', err)
64 }))
65
66 // Create manifest based on deferred-leveldown's
67 this.supports = supports(this.db.supports, {
68 status: false,
69 deferredOpen: true,
70 openCallback: true,
71 promises: true,
72 streams: true
73 })
74
75 // Experimental: enrich levelup interface
76 for (const method of Object.keys(this.supports.additionalMethods)) {
77 if (this[method] != null) continue
78
79 // Don't do this.db[method].bind() because this.db is dynamic.
80 this[method] = function (...args) {
81 return this.db[method](...args)
82 }
83 }
84}
85
86LevelUP.prototype.emit = EventEmitter.prototype.emit
87LevelUP.prototype.once = EventEmitter.prototype.once
88inherits(LevelUP, EventEmitter)
89
90LevelUP.prototype.open = function (opts, callback) {
91 if (typeof opts === 'function') {
92 callback = opts
93 opts = null
94 }
95
96 callback = catering.fromCallback(callback)
97
98 if (!opts) {
99 opts = this.options
100 }
101
102 if (this.isOpen()) {
103 nextTick(callback, null, this)
104 return callback.promise
105 }
106
107 if (this._isOpening()) {
108 this.once('open', () => { callback(null, this) })
109 return callback.promise
110 }
111
112 this.emit('opening')
113
114 this.db.open(opts, (err) => {
115 if (err) {
116 return callback(new OpenError(err))
117 }
118 this.db = this._db
119 callback(null, this)
120 this.emit('open')
121 this.emit('ready')
122 })
123
124 return callback.promise
125}
126
127LevelUP.prototype.close = function (callback) {
128 callback = catering.fromCallback(callback)
129
130 if (this.isOpen()) {
131 this.db.close((err, ...rest) => {
132 this.emit('closed')
133 callback(err, ...rest)
134 })
135 this.emit('closing')
136 this.db = new DeferredLevelDOWN(this._db)
137 } else if (this.isClosed()) {
138 nextTick(callback)
139 } else if (this.db.status === 'closing') {
140 this.once('closed', callback)
141 } else if (this._isOpening()) {
142 this.once('open', () => {
143 this.close(callback)
144 })
145 }
146
147 return callback.promise
148}
149
150LevelUP.prototype.isOpen = function () {
151 return this.db.status === 'open'
152}
153
154LevelUP.prototype._isOpening = function () {
155 return this.db.status === 'opening'
156}
157
158LevelUP.prototype.isClosed = function () {
159 return (/^clos|new/).test(this.db.status)
160}
161
162LevelUP.prototype.get = function (key, options, callback) {
163 callback = getCallback(options, callback)
164 callback = catering.fromCallback(callback)
165
166 if (maybeError(this, callback)) {
167 return callback.promise
168 }
169
170 options = getOptions(options)
171
172 this.db.get(key, options, function (err, value) {
173 if (err) {
174 if ((/notfound/i).test(err) || err.notFound) {
175 err = new NotFoundError('Key not found in database [' + key + ']', err)
176 } else {
177 err = new ReadError(err)
178 }
179 return callback(err)
180 }
181 callback(null, value)
182 })
183
184 return callback.promise
185}
186
187LevelUP.prototype.put = function (key, value, options, callback) {
188 callback = getCallback(options, callback)
189 callback = catering.fromCallback(callback)
190
191 if (maybeError(this, callback)) {
192 return callback.promise
193 }
194
195 options = getOptions(options)
196
197 this.db.put(key, value, options, (err) => {
198 if (err) {
199 return callback(new WriteError(err))
200 }
201 this.emit('put', key, value)
202 callback()
203 })
204
205 return callback.promise
206}
207
208LevelUP.prototype.del = function (key, options, callback) {
209 callback = getCallback(options, callback)
210 callback = catering.fromCallback(callback)
211
212 if (maybeError(this, callback)) {
213 return callback.promise
214 }
215
216 options = getOptions(options)
217
218 this.db.del(key, options, (err) => {
219 if (err) {
220 return callback(new WriteError(err))
221 }
222 this.emit('del', key)
223 callback()
224 })
225
226 return callback.promise
227}
228
229LevelUP.prototype.batch = function (arr, options, callback) {
230 if (!arguments.length) {
231 return new Batch(this)
232 }
233
234 if (typeof arr === 'function') callback = arr
235 else callback = getCallback(options, callback)
236
237 callback = catering.fromCallback(callback)
238
239 if (maybeError(this, callback)) {
240 return callback.promise
241 }
242
243 options = getOptions(options)
244
245 this.db.batch(arr, options, (err) => {
246 if (err) {
247 return callback(new WriteError(err))
248 }
249 this.emit('batch', arr)
250 callback()
251 })
252
253 return callback.promise
254}
255
256LevelUP.prototype.iterator = function (options) {
257 return this.db.iterator(options)
258}
259
260LevelUP.prototype.clear = function (options, callback) {
261 callback = getCallback(options, callback)
262 options = getOptions(options)
263 callback = catering.fromCallback(callback)
264
265 if (maybeError(this, callback)) {
266 return callback.promise
267 }
268
269 this.db.clear(options, (err) => {
270 if (err) {
271 return callback(new WriteError(err))
272 }
273 this.emit('clear', options)
274 callback()
275 })
276
277 return callback.promise
278}
279
280LevelUP.prototype.readStream =
281LevelUP.prototype.createReadStream = function (options) {
282 options = Object.assign({ keys: true, values: true }, options)
283 if (typeof options.limit !== 'number') { options.limit = -1 }
284 return new IteratorStream(this.db.iterator(options), options)
285}
286
287LevelUP.prototype.keyStream =
288LevelUP.prototype.createKeyStream = function (options) {
289 return this.createReadStream(Object.assign({}, options, { keys: true, values: false }))
290}
291
292LevelUP.prototype.valueStream =
293LevelUP.prototype.createValueStream = function (options) {
294 return this.createReadStream(Object.assign({}, options, { keys: false, values: true }))
295}
296
297LevelUP.prototype.toString = function () {
298 return 'LevelUP'
299}
300
301LevelUP.prototype.type = 'levelup'
302
303function maybeError (db, callback) {
304 if (!db._isOpening() && !db.isOpen()) {
305 nextTick(callback, new ReadError('Database is not open'))
306 return true
307 }
308}
309
310LevelUP.errors = errors
311module.exports = LevelUP