From e744ac4b908ace40cb2b3e1ea17ad16f4c198bb6 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Thu, 24 May 2018 22:07:18 +0200 Subject: [PATCH 1/7] unwrap get, del, put --- index.js | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index ac7a7fa..3cf0b34 100644 --- a/index.js +++ b/index.js @@ -50,8 +50,31 @@ Level.prototype._open = function(options, callback) { this.idb = new IDB(idbOpts) } +Level.prototype.store = function (mode) { + var storeName = this.idb.storeName + var transaction = this.idb.db.transaction([storeName], mode) + + return transaction.objectStore(storeName) +} + +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')) + } + + 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')) @@ -62,16 +85,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: From 97d2f356b36ae4908b4cf653fb1739e130dab4a4 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Thu, 24 May 2018 22:08:16 +0200 Subject: [PATCH 2/7] unwrap batch --- index.js | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 3cf0b34..67fbf8b 100644 --- a/index.js +++ b/index.js @@ -126,31 +126,33 @@ Level.prototype._iterator = function (options) { return new Iterator(this.idb, 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) { From 3e26ae5ce2917e75f766ba9fc297b11913f75102 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Thu, 24 May 2018 23:50:24 +0200 Subject: [PATCH 3/7] unwrap iterator --- index.js | 2 +- iterator.js | 71 ++++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/index.js b/index.js index 67fbf8b..886df91 100644 --- a/index.js +++ b/index.js @@ -123,7 +123,7 @@ Level.prototype._serializeValue = function (value) { } Level.prototype._iterator = function (options) { - return new Iterator(this.idb, options) + return new Iterator(this.idb.db, this.idb.storeName, options) } Level.prototype._batch = function (operations, options, callback) { diff --git a/iterator.js b/iterator.js index e164fb2..b8bc17d 100644 --- a/iterator.js +++ b/iterator.js @@ -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, storeName, 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 @@ -33,16 +30,8 @@ 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. @@ -50,34 +39,50 @@ function Iterator (db, options) { return } - this.createIterator() + this.createIterator(storeName, 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 (storeName, keyRange, reverse) { var self = this + var transaction = this.db.transaction([storeName], 'readonly') + var store = transaction.objectStore(storeName) + 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']() @@ -103,8 +108,8 @@ 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) @@ -133,7 +138,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 From 644ee86e182fc49528369c54fe1e366a147b1a5f Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Thu, 24 May 2018 23:56:23 +0200 Subject: [PATCH 4/7] remove leftover callback check --- iterator.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/iterator.js b/iterator.js index b8bc17d..b29c8dc 100644 --- a/iterator.js +++ b/iterator.js @@ -105,9 +105,6 @@ 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 From 17e1dc0dd811596fe4516789a0a0d25c30d623e4 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Thu, 24 May 2018 23:56:44 +0200 Subject: [PATCH 5/7] remove leftover comment --- test/custom-tests.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/custom-tests.js b/test/custom-tests.js index 234b42c..9067a6f 100644 --- a/test/custom-tests.js +++ b/test/custom-tests.js @@ -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) { From 7f9bc0b00a174420b82abbf324d037e5136a37c6 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Thu, 24 May 2018 23:58:59 +0200 Subject: [PATCH 6/7] test without shim --- test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index 77893e2..c761a9f 100644 --- a/test/test.js +++ b/test/test.js @@ -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) From 8a8a5bda4c53391daedc3f502cdb02bde57eea2e Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Fri, 25 May 2018 15:41:42 +0200 Subject: [PATCH 7/7] unwrap open and close, remove idb-wrapper --- index.js | 64 +++++++++++++++++++++++++++------------------------- iterator.js | 10 ++++---- package.json | 4 +--- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/index.js b/index.js index 886df91..c4de9da 100644 --- a/index.js +++ b/index.js @@ -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) @@ -30,31 +32,31 @@ 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() } - xtend(idbOpts, options) - this.IDBOptions = idbOpts - this.idb = new IDB(idbOpts) + 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 storeName = this.idb.storeName - var transaction = this.idb.db.transaction([storeName], mode) - - return transaction.objectStore(storeName) + var transaction = this.db.transaction([this.location], mode) + return transaction.objectStore(this.location) } Level.prototype.await = function (request, callback) { @@ -123,7 +125,7 @@ Level.prototype._serializeValue = function (value) { } Level.prototype._iterator = function (options) { - return new Iterator(this.idb.db, this.idb.storeName, options) + return new Iterator(this.db, this.location, options) } Level.prototype._batch = function (operations, options, callback) { @@ -156,19 +158,19 @@ Level.prototype._batch = function (operations, options, callback) { } 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() } diff --git a/iterator.js b/iterator.js index b29c8dc..b06eff6 100644 --- a/iterator.js +++ b/iterator.js @@ -12,7 +12,7 @@ function mixedToBuffer (value) { module.exports = Iterator -function Iterator (db, storeName, options) { +function Iterator (db, location, options) { AbstractIterator.call(this, db) this._limit = options.limit @@ -39,7 +39,7 @@ function Iterator (db, storeName, options) { return } - this.createIterator(storeName, keyRange, options.reverse) + this.createIterator(location, keyRange, options.reverse) } util.inherits(Iterator, AbstractIterator) @@ -61,10 +61,10 @@ Iterator.prototype.createKeyRange = function (options) { } } -Iterator.prototype.createIterator = function (storeName, keyRange, reverse) { +Iterator.prototype.createIterator = function (location, keyRange, reverse) { var self = this - var transaction = this.db.transaction([storeName], 'readonly') - var store = transaction.objectStore(storeName) + var transaction = this.db.transaction([location], 'readonly') + var store = transaction.objectStore(location) var req = store.openCursor(keyRange, reverse ? 'prev' : 'next') req.onsuccess = function (ev) { diff --git a/package.json b/package.json index 8adbb5f..7597335 100644 --- a/package.json +++ b/package.json @@ -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" } }