UNPKG

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