Skip to content
This repository was archived by the owner on Dec 2, 2024. It is now read-only.

Unwrap #88

Merged
merged 7 commits into from
May 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 81 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
module.exports = Level

var IDB = require('idb-wrapper')
var AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
var util = require('util')
var Iterator = require('./iterator')
var xtend = require('xtend')
var toBuffer = require('typedarray-to-buffer')

function Level(location) {
if (!(this instanceof Level)) return new Level(location)
var DEFAULT_PREFIX = 'level-js-'

function Level (location, opts) {
if (!(this instanceof Level)) return new Level(location, opts)
AbstractLevelDOWN.call(this, location)
this.IDBOptions = {}
this.location = location
opts = opts || {}

this.prefix = opts.prefix || DEFAULT_PREFIX
this.version = parseInt(opts.version || 1, 10)
}

util.inherits(Level, AbstractLevelDOWN)
Expand All @@ -30,28 +32,51 @@ Level.binaryKeys = (function () {
}
})()

Level.prototype._open = function(options, callback) {
Level.prototype._open = function (options, callback) {
var req = indexedDB.open(this.prefix + this.location, this.version)
var self = this

var idbOpts = {
storeName: this.location,
autoIncrement: false,
keyPath: null,
onStoreReady: function () {
callback && callback(null, self.idb)
},
onError: function(err) {
callback && callback(err)
req.onerror = function () {
callback(req.error || new Error('unknown error'))
}

req.onsuccess = function () {
self.db = req.result
callback()
}

req.onupgradeneeded = function (ev) {
var db = ev.target.result

if (!db.objectStoreNames.contains(self.location)) {
db.createObjectStore(self.location)
}
}
}

Level.prototype.store = function (mode) {
var transaction = this.db.transaction([this.location], mode)
return transaction.objectStore(this.location)
}

Level.prototype.await = function (request, callback) {
var transaction = request.transaction

// Take advantage of the fact that a non-canceled request error aborts
// the transaction. I.e. no need to listen for "request.onerror".
transaction.onabort = function () {
callback(transaction.error || new Error('aborted by user'))
}

xtend(idbOpts, options)
this.IDBOptions = idbOpts
this.idb = new IDB(idbOpts)
transaction.oncomplete = function () {
callback(null, request.result)
}
}

Level.prototype._get = function (key, options, callback) {
this.idb.get(key, function (value) {
this.await(this.store('readonly').get(key), function (err, value) {
if (err) return callback(err)

if (value === undefined) {
// 'NotFound' error, consistent with LevelDOWN API
return callback(new Error('NotFound'))
Expand All @@ -62,16 +87,16 @@ Level.prototype._get = function (key, options, callback) {
else value = Buffer.from(String(value))
}

return callback(null, value, key)
}, callback)
callback(null, value)
})
}

Level.prototype._del = function(id, options, callback) {
this.idb.remove(id, callback, callback)
Level.prototype._del = function(key, options, callback) {
this.await(this.store('readwrite').delete(key), callback)
}

Level.prototype._put = function (key, value, options, callback) {
this.idb.put(key, value, function() { callback() }, callback)
this.await(this.store('readwrite').put(value, key), callback)
}

// Valid key types in IndexedDB Second Edition:
Expand Down Expand Up @@ -100,50 +125,52 @@ Level.prototype._serializeValue = function (value) {
}

Level.prototype._iterator = function (options) {
return new Iterator(this.idb, options)
return new Iterator(this.db, this.location, options)
}

Level.prototype._batch = function (array, options, callback) {
var op
var i
var k
var copiedOp
var currentOp
var modified = []

if (array.length === 0) return setTimeout(callback, 0)

for (i = 0; i < array.length; i++) {
copiedOp = {}
currentOp = array[i]
modified[i] = copiedOp

for (k in currentOp) {
if (k === 'type' && currentOp[k] == 'del') {
copiedOp[k] = 'remove'
} else {
copiedOp[k] = currentOp[k]
}
Level.prototype._batch = function (operations, options, callback) {
if (operations.length === 0) return setTimeout(callback, 0)

var store = this.store('readwrite')
var transaction = store.transaction
var index = 0

transaction.onabort = function () {
callback(transaction.error || new Error('aborted by user'))
}

transaction.oncomplete = function () {
callback()
}

// Wait for a request to complete before making the next, saving CPU.
function loop () {
var op = operations[index++]
var key = op.key
var req = op.type === 'del' ? store.delete(key) : store.put(op.value, key)

if (index < operations.length) {
req.onsuccess = loop
}
}

return this.idb.batch(modified, function(){ callback() }, callback)
loop()
}

Level.prototype._close = function (callback) {
this.idb.db.close()
this.db.close()
callback()
}

Level.destroy = function (db, callback) {
if (typeof db === 'object') {
var prefix = db.IDBOptions.storePrefix || 'IDBWrapper-'
var dbname = db.location
var prefix = db.prefix || DEFAULT_PREFIX
var location = db.location
} else {
var prefix = 'IDBWrapper-'
var dbname = db
prefix = DEFAULT_PREFIX
location = db
}
var request = indexedDB.deleteDatabase(prefix + dbname)
var request = indexedDB.deleteDatabase(prefix + location)
request.onsuccess = function() {
callback()
}
Expand Down
74 changes: 38 additions & 36 deletions iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,15 @@ function mixedToBuffer (value) {

module.exports = Iterator

function Iterator (db, options) {
// TODO: in later abstract-leveldown, options is always an object.
if (!options) options = {}
function Iterator (db, location, options) {
AbstractIterator.call(this, db)

this._order = options.reverse ? 'DESC': 'ASC'
this._limit = options.limit
this._count = 0
this._callback = null
this._cache = []
this._completed = false
this.transaction = null
this._transaction = null

this._keyAsBuffer = options.keyAsBuffer
this._valueAsBuffer = options.valueAsBuffer
Expand All @@ -33,51 +30,59 @@ function Iterator (db, options) {
return
}

var lower = ltgt.lowerBound(options)
var upper = ltgt.upperBound(options)

try {
this._keyRange = lower || upper ? this.db.makeKeyRange({
lower: lower,
upper: upper,
excludeLower: ltgt.lowerBoundExclusive(options),
excludeUpper: ltgt.upperBoundExclusive(options)
}) : null
var keyRange = this.createKeyRange(options)
} catch (e) {
// The lower key is greater than the upper key.
// IndexedDB throws an error, but we'll just return 0 results.
this._completed = true
return
}

this.createIterator()
this.createIterator(location, keyRange, options.reverse)
}

util.inherits(Iterator, AbstractIterator)

Iterator.prototype.createIterator = function() {
Iterator.prototype.createKeyRange = function (options) {
var lower = ltgt.lowerBound(options)
var upper = ltgt.upperBound(options)
var lowerOpen = ltgt.lowerBoundExclusive(options)
var upperOpen = ltgt.upperBoundExclusive(options)

if (lower !== undefined && upper !== undefined) {
return IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen)
} else if (lower !== undefined) {
return IDBKeyRange.lowerBound(lower, lowerOpen)
} else if (upper !== undefined) {
return IDBKeyRange.upperBound(upper, upperOpen)
} else {
return null
}
}

Iterator.prototype.createIterator = function (location, keyRange, reverse) {
var self = this
var transaction = this.db.transaction([location], 'readonly')
var store = transaction.objectStore(location)
var req = store.openCursor(keyRange, reverse ? 'prev' : 'next')

self.transaction = self.db.iterate(function () {
self.onItem.apply(self, arguments)
}, {
keyRange: self._keyRange,
autoContinue: false,
order: self._order,
req.onsuccess = function (ev) {
var cursor = ev.target.result
if (cursor) self.onItem(cursor)
}

// If an error occurs, the transaction will abort and we can
// get the error from "transaction.error".
onError: noop
})
this._transaction = transaction

// Override IDBWrapper's event handlers for a simpler flow.
self.transaction.oncomplete = self.transaction.onabort = function () {
// If an error occurs, the transaction will abort and we can
// get the error from "transaction.error".
transaction.oncomplete = transaction.onabort = function () {
self.onComplete()
}
}

Iterator.prototype.onItem = function (value, cursor, cursorTransaction) {
this._cache.push(cursor.key, value)
Iterator.prototype.onItem = function (cursor) {
this._cache.push(cursor.key, cursor.value)

if (this._limit <= 0 || ++this._count < this._limit) {
cursor['continue']()
Expand All @@ -100,11 +105,8 @@ Iterator.prototype.maybeNext = function () {

// TODO: use setImmediate (see memdown)
Iterator.prototype._next = function (callback) {
// TODO: can remove this after upgrading abstract-leveldown
if (!callback) throw new Error('next() requires a callback argument')

if (this.transaction !== null && this.transaction.error !== null) {
var err = this.transaction.error
if (this._transaction !== null && this._transaction.error !== null) {
var err = this._transaction.error

setTimeout(function() {
callback(err)
Expand Down Expand Up @@ -133,7 +135,7 @@ Iterator.prototype._end = function (callback) {
return
}

var transaction = this.transaction
var transaction = this._transaction

// Don't advance the cursor anymore, and the transaction will complete
// on its own in the next tick. This approach is much cleaner than calling
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
},
"dependencies": {
"abstract-leveldown": "~5.0.0",
"idb-wrapper": "^1.5.0",
"ltgt": "^2.1.2",
"typedarray-to-buffer": "~3.1.5",
"xtend": "~4.0.1"
"typedarray-to-buffer": "~3.1.5"
}
}
2 changes: 0 additions & 2 deletions test/custom-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ module.exports.all = function(leveljs, tape, testCommon) {
})
})

// TODO: this should be supported without raw: true. Which is possible once
// we upgrade abstract-leveldown (which only tests strings and Buffers now).
tape('store NaN value', function(t) {
var level = leveljs(testCommon.location())
level.open(function(err) {
Expand Down
2 changes: 1 addition & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var leveljs = require('../')
var testCommon = require('./testCommon')

// load IndexedDBShim in the tests
require('./idb-shim.js')()
// require('./idb-shim.js')()

/*** compatibility with basic LevelDOWN API ***/
require('abstract-leveldown/abstract/leveldown-test').args(leveljs, tape, testCommon)
Expand Down