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

Commit 04491d3

Browse files
committed
Drop support of key & value types other than string and Buffer
1 parent 69645a4 commit 04491d3

16 files changed

+124
-732
lines changed

README.md

Lines changed: 22 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# level-js
22

3-
> An [`abstract-leveldown`][abstract-leveldown] compliant store on top of [IndexedDB][indexeddb], which is in turn implemented on top of [LevelDB][leveldb] which brings this whole shebang full circle.
3+
> An [`abstract-leveldown`][abstract-leveldown] compliant store on top of [IndexedDB][indexeddb], which is ~~in turn implemented on top of [LevelDB][leveldb] which brings this whole shebang full circle~~.
44
55
[![level badge][level-badge]][awesome]
66
[![npm](https://img.shields.io/npm/v/level-js.svg?label=&logo=npm)](https://www.npmjs.com/package/level-js)
@@ -35,13 +35,11 @@ Here are the goals of `level-js`:
3535

3636
- Store large amounts of data in modern browsers
3737
- Pass the full [`abstract-leveldown`][abstract-leveldown] test suite
38-
- Support [`Buffer`][buffer] keys and values
39-
- Support all key types of IndexedDB Second Edition
40-
- Support all value types of the [structured clone algorithm][structured-clone-algorithm] except for `null` and `undefined`
38+
- Support string and [`Buffer`][buffer] keys and values
4139
- Be as fast as possible
42-
- Sync with [multilevel](https://github.com/juliangruber/multilevel) over ASCII or binary transports.
40+
- ~~Sync with [multilevel](https://github.com/juliangruber/multilevel) over ASCII or binary transports.~~
4341

44-
Being `abstract-leveldown` compliant means you can use many of the [Level modules][awesome] on top of this library. For some demos of it working, see [**@brycebaril**](https://github.com/brycebaril)'s presentation [Path of the NodeBases Jedi](http://brycebaril.github.io/nodebase_jedi/#/vanilla).
42+
Being `abstract-leveldown` compliant means you can use many of the [Level modules][awesome] on top of this library.
4543

4644
## Example
4745

@@ -80,90 +78,63 @@ const value = await db.get('hello')
8078

8179
## Type Support
8280

83-
Unlike [`leveldown`][leveldown], `level-js` does not stringify keys or values. This means that in addition to strings and Buffers you can store almost any JavaScript type without the need for [`encoding-down`][encoding-down].
81+
~~Unlike [`leveldown`][leveldown], `level-js` does not stringify keys or values. This means that in addition to strings and Buffers you can store almost any JavaScript type without the need for [`encoding-down`][encoding-down].~~
8482

8583
### Values
8684

87-
All value types of the [structured clone algorithm][structured-clone-algorithm] are supported except for `null` and `undefined`. Depending on the environment, this includes:
85+
~~All value types of the [structured clone algorithm][structured-clone-algorithm] are supported except for `null` and `undefined`. Depending on the environment, this includes:~~
8886

89-
- Number, including `NaN`, `Infinity` and `-Infinity`
90-
- String, Boolean, Date, RegExp, Array, Object
87+
- ~~Number, including `NaN`, `Infinity` and `-Infinity`~~
88+
- String, ~~Boolean, Date, RegExp, Array, Object~~
9189
- ArrayBuffer or a view thereof (typed arrays);
92-
- Map, Set, Blob, File, FileList, ImageData (limited support).
90+
- ~~Map, Set, Blob, File, FileList, ImageData (limited support).~~
9391

9492
In addition `level-js` stores [`Buffer`][buffer] values without transformation. This works in all target environments because `Buffer` is a subclass of `Uint8Array`, meaning such values can be passed to `IndexedDB` as-is.
9593

96-
When getting or iterating binary values, regardless of whether they were stored as a `Buffer`, `ArrayBuffer` or a view thereof, values will return as a `Buffer`. This behavior can be disabled, in which case `ArrayBuffer` returns as `ArrayBuffer`, typed arrays return as typed arrays and `Buffer` returns as `Uint8Array`:
94+
When getting or iterating binary values, regardless of whether they were stored as a `Buffer`, `ArrayBuffer` or a view thereof, values will return as a `Buffer`. This behavior can be disabled, in which case ~~`ArrayBuffer` returns as `ArrayBuffer`, typed arrays return as typed arrays and `Buffer` returns as `Uint8Array`~~:
9795

9896
```js
9997
db.get('key', { asBuffer: false })
10098
db.iterator({ valueAsBuffer: false })
10199
```
102100

103-
If the environment does not support a type, it will throw an error which `level-js` catches and passes to the callbacks of `put` or `batch`. For example, IE does not support typed array values. At the time of writing, Chrome is the only browser that supports all types listed above.
101+
~~If the environment does not support a type, it will throw an error which `level-js` catches and passes to the callbacks of `put` or `batch`. For example, IE does not support typed array values. At the time of writing, Chrome is the only browser that supports all types listed above.~~
104102

105103
### Keys
106104

107-
All key types of IndexedDB Second Edition are supported. Depending on the environment, this includes:
105+
~~All key types of IndexedDB Second Edition are supported. Depending on the environment, this includes:~~
108106

109-
- Number, including `Infinity` and `-Infinity`, but not `NaN`
110-
- Date, except invalid (`NaN`)
107+
- ~~Number, including `Infinity` and `-Infinity`, but not `NaN`~~
108+
- ~~Date, except invalid (`NaN`)~~
111109
- String
112110
- ArrayBuffer or a view thereof (typed arrays);
113-
- Array, except cyclical, empty and sparse arrays. Elements must be valid types themselves.
111+
- ~~Array, except cyclical, empty and sparse arrays. Elements must be valid types themselves.~~
114112

115-
In addition you can use [`Buffer`][buffer] keys, giving `level-js` the same power as implementations like `leveldown` and `memdown`. When iterating binary keys, regardless of whether they were stored as `Buffer`, `ArrayBuffer` or a view thereof, keys will return as a `Buffer`. This behavior can be disabled, in which case binary keys will always return as `ArrayBuffer`:
113+
In addition you can use [`Buffer`][buffer] keys, giving `level-js` the same power as implementations like `leveldown` and `memdown`. When iterating binary keys, regardless of whether they were stored as `Buffer`, `ArrayBuffer` or a view thereof, keys will return as a `Buffer`. This behavior can be disabled, in which case ~~binary keys will always return as `ArrayBuffer`~~:
116114

117115
```js
118116
db.iterator({ keyAsBuffer: false })
119117
```
120118

121119
Note that this behavior is slightly different from values due to the way that IndexedDB works. IndexedDB stores binary _values_ using the structured clone algorithm, which preserves views, but it stores binary _keys_ as an array of octets, so that it is able to compare and sort differently typed keys.
122120

123-
If the environment does not support a type, it will throw an error which `level-js` catches and passes to the callbacks of `get`, `put`, `del`, `batch` or an iterator. Exceptions are:
121+
~~If the environment does not support a type, it will throw an error which `level-js` catches and passes to the callbacks of `get`, `put`, `del`, `batch` or an iterator~~. Exceptions are:
124122

125123
- `null` and `undefined`: rejected early by `abstract-leveldown`
126-
- Binary and array keys: if not supported by the environment, `level-js` falls back to `String(key)`.
124+
- Binary keys: if not supported by the environment, `level-js` falls back to `String(key)`. If (one of) your target environments does not support binary keys (like IE11), it is recommended to set the `bufferKeys` option of the constructor to `false` and then only use string keys. Otherwise, you risk losing data once the environment does land support for binary keys.
127125

128126
### Normalization
129127

130128
If you desire normalization for keys and values (e.g. to stringify numbers), wrap `level-js` with [`encoding-down`][encoding-down]. Alternatively install [`level`][level] which conveniently bundles [`levelup`][levelup], `level-js` and `encoding-down`. Such an approach is also recommended if you want to achieve universal (isomorphic) behavior or to smooth over type differences between browsers. For example, you could have [`leveldown`][leveldown] in a backend and `level-js` in the frontend. The `level` package does exactly that.
131129

132-
Another reason you might want to use `encoding-down` is that the structured clone algorithm, while rich in types, can be slower than `JSON.stringify`.
130+
~~Another reason you might want to use `encoding-down` is that the structured clone algorithm, while rich in types, can be slower than `JSON.stringify`.~~
133131

134132
### Sort Order
135133

136-
Unless `level-js` is wrapped with [`encoding-down`][encoding-down], IndexedDB will sort your keys in the following order:
134+
~~Unless `level-js` is wrapped with [`encoding-down`][encoding-down], IndexedDB will sort your keys in the following order:~~
137135

138-
1. number (numeric)
139-
2. date (numeric, by epoch offset)
140136
3. binary (bitwise)
141137
4. string (lexicographic)
142-
5. array (componentwise).
143-
144-
You can take advantage of this fact with `levelup` streams. For example, if your keys are dates, you can select everything greater than a specific date (let's be happy and ignore timezones for a moment):
145-
146-
```js
147-
const db = levelup(leveljs('time-db'))
148-
149-
db.createReadStream({ gt: new Date('2019-01-01') })
150-
.pipe(..)
151-
```
152-
153-
Or if your keys are arrays, you can do things like:
154-
155-
```js
156-
const db = levelup(leveljs('books-db'))
157-
158-
await db.put(['Roald Dahl', 'Charlie and the Chocolate Factory'], {})
159-
await db.put(['Roald Dahl', 'Fantastic Mr Fox'], {})
160-
161-
// Select all books by Roald Dahl
162-
db.createReadStream({ gt: ['Roald Dahl'], lt: ['Roald Dahl', '\xff'] })
163-
.pipe(..)
164-
```
165-
166-
To achieve this on other `abstract-leveldown` implementations, wrap them with [`encoding-down`][encoding-down] and [`charwise`][charwise] (or similar).
167138

168139
#### Known Browser Issues
169140

@@ -173,7 +144,7 @@ IE11 and Edge yield incorrect results for `{ gte: '' }` if the database contains
173144

174145
For interoperability it is recommended to use `Buffer` as your binary type. While we recognize that Node.js core modules are moving towards supporting `ArrayBuffer` and views thereof, `Buffer` remains the primary binary type in the Level ecosystem.
175146

176-
That said: if you want to `put()` an `ArrayBuffer` you can! Just know that it will come back as a `Buffer` by default. If you want to `get()` or iterate stored `ArrayBuffer` data as an `ArrayBuffer`, you have a few options. Without `encoding-down`:
147+
That said: if you want to `put()` an `ArrayBuffer` you can! Just know that it will come back as a `Buffer` by default. ~~If you want to `get()` or iterate stored `ArrayBuffer` data as an `ArrayBuffer`, you have a few options~~. Without `encoding-down`:
177148

178149
```js
179150
const db = levelup(leveljs('mydb'))
@@ -220,6 +191,7 @@ The optional `options` argument may contain:
220191

221192
- `prefix` _(string, default: `'level-js-'`)_: Prefix for `IDBDatabase` name.
222193
- `version` _(string | number, default: `1`)_: The version to open the database with.
194+
- `bufferKeys`: ..
223195

224196
See [`IDBFactory#open`](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) for more details.
225197

@@ -276,8 +248,6 @@ To sustain [`Level`](https://github.com/Level) and its activities, become a back
276248

277249
[abstract-leveldown]: https://github.com/Level/abstract-leveldown
278250

279-
[charwise]: https://github.com/dominictarr/charwise
280-
281251
[levelup]: https://github.com/Level/levelup
282252

283253
[leveldown]: https://github.com/Level/leveldown

index.js

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ module.exports = Level
77
var AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
88
var inherits = require('inherits')
99
var Iterator = require('./iterator')
10-
var mixedToBuffer = require('./util/mixed-to-buffer')
10+
var serialize = require('./util/serialize')
11+
var deserialize = require('./util/deserialize')
1112
var setImmediate = require('./util/immediate')
1213
var support = require('./util/support')
1314

@@ -25,13 +26,24 @@ function Level (location, opts) {
2526
this.location = location
2627
this.prefix = opts.prefix || DEFAULT_PREFIX
2728
this.version = parseInt(opts.version || 1, 10)
29+
30+
// Experimental, do not externally rely on this object yet.
31+
// See Level/community#42.
32+
this.supports = {
33+
bufferKeys: opts.bufferKeys !== false && Level.binaryKeys,
34+
permanence: true,
35+
snapshots: true, // For now (#86)
36+
seek: false, // #178
37+
clear: false, // #175
38+
createIfMissing: false,
39+
errorIfExists: false
40+
}
2841
}
2942

3043
inherits(Level, AbstractLevelDOWN)
3144

3245
// Detect binary and array key support (IndexedDB Second Edition)
3346
Level.binaryKeys = support.binaryKeys(indexedDB)
34-
Level.arrayKeys = support.arrayKeys(indexedDB)
3547

3648
Level.prototype._open = function (options, callback) {
3749
var req = indexedDB.open(this.prefix + this.location, this.version)
@@ -93,11 +105,7 @@ Level.prototype._get = function (key, options, callback) {
93105
return callback(new Error('NotFound'))
94106
}
95107

96-
if (options.asBuffer) {
97-
value = mixedToBuffer(value)
98-
}
99-
100-
callback(null, value)
108+
callback(null, deserialize(value, options.asBuffer))
101109
})
102110
}
103111

@@ -131,27 +139,12 @@ Level.prototype._put = function (key, value, options, callback) {
131139
this.await(req, callback)
132140
}
133141

134-
// Valid key types in IndexedDB Second Edition:
135-
//
136-
// - Number, except NaN. Includes Infinity and -Infinity
137-
// - Date, except invalid (NaN)
138-
// - String
139-
// - ArrayBuffer or a view thereof (typed arrays). In level-js we also support
140-
// Buffer (which is an Uint8Array) (and the primary binary type of Level).
141-
// - Array, except cyclical and empty (e.g. Array(10)). Elements must be valid
142-
// types themselves.
143142
Level.prototype._serializeKey = function (key) {
144-
if (Buffer.isBuffer(key)) {
145-
return Level.binaryKeys ? key : key.toString()
146-
} else if (Array.isArray(key)) {
147-
return Level.arrayKeys ? key.map(this._serializeKey, this) : String(key)
148-
} else {
149-
return key
150-
}
143+
return serialize(key, this.supports.bufferKeys)
151144
}
152145

153146
Level.prototype._serializeValue = function (value) {
154-
return value
147+
return serialize(value, true)
155148
}
156149

157150
Level.prototype._iterator = function (options) {

iterator.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
var inherits = require('inherits')
66
var AbstractIterator = require('abstract-leveldown').AbstractIterator
77
var ltgt = require('ltgt')
8-
var mixedToBuffer = require('./util/mixed-to-buffer')
8+
var deserialize = require('./util/deserialize')
99
var setImmediate = require('./util/immediate')
1010
var noop = function () {}
1111

@@ -23,6 +23,8 @@ function Iterator (db, location, options) {
2323
this._error = null
2424
this._transaction = null
2525

26+
this._keys = options.keys
27+
this._values = options.values
2628
this._keyAsBuffer = options.keyAsBuffer
2729
this._valueAsBuffer = options.valueAsBuffer
2830

@@ -126,8 +128,17 @@ Iterator.prototype._next = function (callback) {
126128
var key = this._cache.shift()
127129
var value = this._cache.shift()
128130

129-
if (this._keyAsBuffer) key = mixedToBuffer(key)
130-
if (this._valueAsBuffer) value = mixedToBuffer(value)
131+
if (this._keys && key !== undefined) {
132+
key = deserialize(key, this._keyAsBuffer)
133+
} else {
134+
key = undefined
135+
}
136+
137+
if (this._values && value !== undefined) {
138+
value = deserialize(value, this._valueAsBuffer)
139+
} else {
140+
value = undefined
141+
}
131142

132143
setImmediate(function () {
133144
callback(null, key, value)

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929
"abstract-leveldown": "~6.0.1",
3030
"immediate": "~3.2.3",
3131
"inherits": "^2.0.3",
32-
"ltgt": "^2.1.2",
33-
"typedarray-to-buffer": "~3.1.5"
32+
"ltgt": "^2.1.2"
3433
},
3534
"devDependencies": {
3635
"airtap": "^2.0.0",

test/custom-test.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ module.exports = function (leveljs, test, testCommon) {
6363
})
6464
})
6565

66-
test('put Buffer value, get Uint8Array value', function (t) {
66+
// TODO: get with { asBuffer: false } should now return a string
67+
test.skip('put Buffer value, get Uint8Array value', function (t) {
6768
var level = testCommon.factory()
6869
level.open(function (err) {
6970
t.notOk(err, 'no error')
@@ -96,7 +97,8 @@ module.exports = function (leveljs, test, testCommon) {
9697
})
9798
})
9899

99-
test('put Uint8Array value, get Uint8Array value', function (t) {
100+
// TODO: get with { asBuffer: false } should now return a string
101+
test.skip('put Uint8Array value, get Uint8Array value', function (t) {
100102
var level = testCommon.factory()
101103
level.open(function (err) {
102104
t.notOk(err, 'no error')
@@ -129,7 +131,8 @@ module.exports = function (leveljs, test, testCommon) {
129131
})
130132
})
131133

132-
test('put ArrayBuffer value, get ArrayBuffer value', function (t) {
134+
// TODO: get with { asBuffer: false } should now return a string
135+
test.skip('put ArrayBuffer value, get ArrayBuffer value', function (t) {
133136
var level = testCommon.factory()
134137
level.open(function (err) {
135138
t.notOk(err, 'no error')
@@ -145,6 +148,9 @@ module.exports = function (leveljs, test, testCommon) {
145148
})
146149
})
147150

151+
// TODO: test putting+gettting:
152+
// Buffer/Uint8Array/ArrayBuffer/string key with keyAsBuffer true/false
153+
148154
// This should be covered by abstract-leveldown tests, but that's
149155
// prevented by process.browser checks (Level/abstract-leveldown#121).
150156
// This test is adapted from memdown.
@@ -194,8 +200,8 @@ module.exports = function (leveljs, test, testCommon) {
194200
t.ifError(err, 'no open error')
195201

196202
db.batch([
197-
{ type: 'put', key: Buffer.from([0]), value: 0 },
198-
{ type: 'put', key: Buffer.from([1]), value: 1 }
203+
{ type: 'put', key: Buffer.from([0]), value: '0' },
204+
{ type: 'put', key: Buffer.from([1]), value: '1' }
199205
], function (err) {
200206
t.ifError(err, 'no batch error')
201207

@@ -204,8 +210,8 @@ module.exports = function (leveljs, test, testCommon) {
204210
t.ifError(err, 'no iterator error')
205211

206212
t.same(entries, [
207-
{ key: Buffer.from([0]), value: 0 },
208-
{ key: Buffer.from([1]), value: 1 }
213+
{ key: Buffer.from([0]), value: '0' },
214+
{ key: Buffer.from([1]), value: '1' }
209215
], 'keys are Buffers')
210216

211217
db.close(function (err) {

test/index.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,3 @@ suite(testCommon)
2828

2929
// Additional tests for this implementation
3030
require('./custom-test')(leveljs, test, testCommon)
31-
require('./structured-clone-test')(leveljs, test, testCommon)
32-
require('./key-type-test')(leveljs, test, testCommon)
33-
require('./key-type-illegal-test')(leveljs, test, testCommon)
34-
require('./native-order-test')(leveljs, test, testCommon)

0 commit comments

Comments
 (0)