diff --git a/README.md b/README.md
index 5217757..75e91a4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# level
-> Fast & simple storage. A Node.js-style `LevelDB` wrapper.
+> Fast & simple storage. A Node.js-style `LevelDB` wrapper for Node.js, Electron and browsers.
[![level badge][level-badge]](https://github.com/level/awesome)
[](#backers)
@@ -14,19 +14,14 @@
[](https://standardjs.com)
[](https://www.npmjs.com/package/level)
-A convenience package that:
+## Table of Contents
-* exports a function that returns a [`levelup instance`](https://github.com/level/levelup#ctor) when invoked
-* bundles the current release of [`levelup`][levelup] and [`leveldown`][leveldown]
-* leverages encodings using [`encoding-down`][encoding-down]
-
-Use this package to avoid having to explicitly install `leveldown` when you just want plain old `LevelDB` from `levelup`.
-
-**Note** that `level` only supports Node.js. There is ongoing work to make it work in both Node.js and in the browser. In the meantime, we suggest you to use [`level-browserify`](https://github.com/level/level-browserify).
-
-**If you are upgrading:** please see [`UPGRADING.md`](UPGRADING.md).
+
+Click to expand
+* [Introduction](#introduction)
* [Usage](#usage)
+* [Supported Platforms](#supported-platforms)
* [API](#api)
* [Promise Support](#promise-support)
* [Events](#events)
@@ -36,14 +31,28 @@ Use this package to avoid having to explicitly install `leveldown` when you just
* [Sponsors](#sponsors)
* [License](#license)
+
+
+## Introduction
+
+This is a convenience package that:
+
+* exports a function that returns a [`levelup`](https://github.com/level/levelup#ctor) instance when invoked
+* bundles the current release of [`leveldown`][leveldown] and [`level-js`][level-js]
+* leverages encodings using [`encoding-down`][encoding-down].
+
+Use this package to avoid having to explicitly install `leveldown` and/or `level-js` when you just want to use `levelup`. It uses `leveldown` in Node.js or Electron and `level-js` in browsers (when bundled by [`browserify`](https://github.com/browserify/browserify) or similar).
+
## Usage
+**If you are upgrading:** please see [`UPGRADING.md`](UPGRADING.md).
+
```js
var level = require('level')
// 1) Create our database, supply location and options.
-// This will create or open the underlying LevelDB store.
-var db = level('./mydb')
+// This will create or open the underlying store.
+var db = level('my-db')
// 2) Put a key & value
db.put('name', 'Level', function (err) {
@@ -59,8 +68,18 @@ db.put('name', 'Level', function (err) {
})
```
+## Supported Platforms
+
+At the time of writing, `level` works in Node.js 8+ and Electron 3+ on Linux, Mac OS, Windows and FreeBSD, including any future Node.js and Electron release thanks to [N-API](https://nodejs.org/api/n-api.html), including ARM platforms like Raspberry Pi and Android, as well as in Chrome, Firefox, IE 11, Edge, Safari, iPhone and Chrome for Android. For details, see [Supported Platforms](https://github.com/Level/leveldown#supported-platforms) of `leveldown` and [Browser Support](https://github.com/Level/level-js#browser-support) of `level-js`.
+
+Though `leveldown` and `level-js` have subtle differences in type handling, `level` negates this by transparently wrapping both with [`encoding-down`](https://github.com/Level/encoding-down). If you want to support all possible runtime environments, stick to string keys with encodings like the default `utf8`. Binary values are supported across the board; in browsers that support [IndexedDB Second Edition](https://www.w3.org/TR/IndexedDB-2/) (like Chrome and Firefox) you can also use binary keys.
+
+If you want to use [Promises](#promise-support), you will need a polyfill like [`pinkie`](https://github.com/floatdrop/pinkie) in older browsers like IE.
+
## API
+For options specific to [`leveldown`][leveldown] and [`level-js`][level-js] ("underlying store" from here on out), please see their respective READMEs.
+
* [level()](#ctor)
* [db.open()](#open)
* [db.close()](#close)
@@ -75,17 +94,15 @@ db.put('name', 'Level', function (err) {
* [db.createKeyStream()](#createKeyStream)
* [db.createValueStream()](#createValueStream)
-See [`levelup`][levelup] and [`leveldown`][leveldown] for more details.
-
### `db = level(location[, options[, callback]])`
The main entry point for creating a new `levelup` instance.
-- `location` path to the underlying `LevelDB`.
+- `location` is a string pointing to the LevelDB location to be opened or in browsers, the name of the [`IDBDatabase`](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase) to be opened.
- `options` is passed on to the underlying store.
- `options.keyEncoding` and `options.valueEncoding` are passed to [`encoding-down`][encoding-down], default encoding is `'utf8'`
-Calling `level('./db')` will also open the underlying store. This is an asynchronous operation which will trigger your callback if you provide one. The callback should take the form `function (err, db) {}` where `db` is the `levelup` instance. If you don't provide a callback, any read & write operations are simply queued internally until the store is fully opened.
+Calling `level('my-db')` will also open the underlying store. This is an asynchronous operation which will trigger your callback if you provide one. The callback should take the form `function (err, db) {}` where `db` is the `levelup` instance. If you don't provide a callback, any read & write operations are simply queued internally until the store is fully opened.
This leads to two alternative ways of managing a `levelup` instance:
@@ -115,13 +132,15 @@ db.get('foo', function (err, value) {
The constructor function has a `.errors` property which provides access to the different error types from [`level-errors`](https://github.com/Level/errors#api). See example below on how to use it:
```js
-level('./db', { createIfMissing: false }, function (err, db) {
+level('my-db', { createIfMissing: false }, function (err, db) {
if (err instanceof level.errors.OpenError) {
console.log('failed to open database')
}
})
```
+Note that `createIfMissing` is an option specific to [`leveldown`][leveldown].
+
### `db.open([callback])`
Opens the underlying store. In general you should never need to call this method directly as it's automatically called by levelup().
@@ -142,7 +161,8 @@ If no callback is passed, a promise is returned.
### `db.put(key, value[, options][, callback])`
put() is the primary method for inserting data into the store. Both `key` and `value` can be of any type as far as `levelup` is concerned.
-`options` is passed on to the underlying store.
+- `options` is passed on to the underlying store
+- `options.keyEncoding` and `options.valueEncoding` are passed to [`encoding-down`][encoding-down], allowing you to override the key- and/or value encoding for this `put` operation.
If no callback is passed, a promise is returned.
@@ -165,7 +185,8 @@ db.get('foo', function (err, value) {
})
```
-`options` is passed on to the underlying store.
+- `options` is passed on to the underlying store.
+- `options.keyEncoding` and `options.valueEncoding` are passed to [`encoding-down`][encoding-down], allowing you to override the key- and/or value encoding for this `get` operation.
If no callback is passed, a promise is returned.
@@ -179,7 +200,8 @@ db.del('foo', function (err) {
});
```
-`options` is passed on to the underlying store.
+- `options` is passed on to the underlying store.
+- `options.keyEncoding` is passed to [`encoding-down`][encoding-down], allowing you to override the key encoding for this `del` operation.
If no callback is passed, a promise is returned.
@@ -189,8 +211,6 @@ If no callback is passed, a promise is returned.
Each operation is contained in an object having the following properties: `type`, `key`, `value`, where the *type* is either `'put'` or `'del'`. In the case of `'del'` the `value` property is ignored. Any entries with a `key` of `null` or `undefined` will cause an error to be returned on the `callback` and any `type: 'put'` entry with a `value` of `null` or `undefined` will return an error.
-If `key` and `value` are defined but `type` is not, it will default to `'put'`.
-
```js
var ops = [
{ type: 'del', key: 'father' },
@@ -206,7 +226,8 @@ db.batch(ops, function (err) {
})
```
-`options` is passed on to the underlying store.
+- `options` is passed on to the underlying store.
+- `options.keyEncoding` and `options.valueEncoding` are passed to [`encoding-down`][encoding-down], allowing you to override the key- and/or value encoding of operations in this batch.
If no callback is passed, a promise is returned.
@@ -244,10 +265,13 @@ Clear all queued operations on the current batch, any previous operations will b
The number of queued operations on the current batch.
-batch.write([callback])
+batch.write([options][, callback])
Commit the queued operations for this batch. All operations not *cleared* will be written to the underlying store atomically, that is, they will either all succeed or fail with no partial commits.
+- `options` is passed on to the underlying store.
+- `options.keyEncoding` and `options.valueEncoding` are not supported here.
+
If no callback is passed, a promise is returned.
@@ -266,8 +290,6 @@ A `levelup` instance can be in one of the following states:
### `db.isClosed()`
-*See isOpen()*
-
`isClosed()` will return `true` only when the state is "closing" *or* "closed", it can be useful for determining if read and write operations are permissible.
@@ -311,6 +333,8 @@ Legacy options:
* `end`: instead use `lte`
+Underlying stores may have additional options.
+
### `db.createKeyStream([options])`
@@ -353,7 +377,7 @@ db.createReadStream({ keys: false, values: true })
## Promise Support
-`level` ships with native `Promise` support out of the box.
+`level(up)` ships with native `Promise` support out of the box.
Each function taking a callback also can be used as a promise, if the callback is omitted. This applies for:
@@ -368,7 +392,7 @@ The only exception is the `level` constructor itself, which if no callback is pa
Example:
```js
-var db = level('./my-db')
+var db = level('my-db')
db.put('foo', 'bar')
.then(function () { return db.get('foo') })
@@ -380,7 +404,7 @@ Or using `async/await`:
```js
var main = async () => {
- var db = level('./my-db')
+ var db = level('my-db')
await db.put('foo', 'bar')
console.log(await db.get('foo'))
@@ -454,4 +478,5 @@ Copyright (c) 2012-present `level` [contributors](https://github.com/level/commu
[level-badge]: http://leveldb.org/img/badge.svg
[levelup]: https://github.com/level/levelup
[leveldown]: https://github.com/level/leveldown
+[level-js]: https://github.com/level/level-js
[encoding-down]: https://github.com/level/encoding-down
diff --git a/browser.js b/browser.js
new file mode 100644
index 0000000..5974f4d
--- /dev/null
+++ b/browser.js
@@ -0,0 +1 @@
+module.exports = require('level-packager')(require('level-js'))
diff --git a/package.json b/package.json
index be14d98..be6b154 100644
--- a/package.json
+++ b/package.json
@@ -17,20 +17,26 @@
"json"
],
"main": "level.js",
+ "browser": "browser.js",
"dependencies": {
+ "level-js": "^4.0.0",
"level-packager": "^5.0.0",
"leveldown": "^5.0.0-1",
"opencollective-postinstall": "^2.0.0"
},
"devDependencies": {
+ "airtap": "^2.0.1",
"coveralls": "^3.0.2",
"nyc": "^12.0.2",
+ "pinkie": "^2.0.4",
"standard": "^12.0.0",
"tape": "^4.2.2",
+ "uuid": "^3.3.2",
"verify-travis-appveyor": "^3.0.0"
},
"scripts": {
"test": "standard && nyc node test.js && verify-travis-appveyor",
+ "test-browser-local": "airtap --local test-browser.js",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"postinstall": "opencollective-postinstall || exit 0"
},
diff --git a/test-browser.js b/test-browser.js
new file mode 100644
index 0000000..dd281b9
--- /dev/null
+++ b/test-browser.js
@@ -0,0 +1,64 @@
+'use strict'
+
+// Promise polyfill for IE and others.
+if (typeof Promise !== 'function') {
+ global.Promise = require('pinkie')
+}
+
+var test = require('tape')
+var uuid = require('uuid/v4')
+var level = require('.')
+
+require('level-packager/abstract/base-test')(test, level)
+require('level-packager/abstract/db-values-test')(test, level)
+
+function factory (opts) {
+ return level(uuid(), opts)
+}
+
+test('level put', function (t) {
+ t.plan(4)
+
+ var db = factory()
+
+ db.put('name', 'level', function (err) {
+ t.ifError(err, 'no put error')
+
+ db.get('name', function (err, value) {
+ t.ifError(err, 'no get error')
+ t.is(value, 'level')
+
+ db.close(function (err) {
+ t.ifError(err, 'no close error')
+ })
+ })
+ })
+})
+
+test('level binary value', function (t) {
+ t.plan(9)
+
+ var db = factory({ valueEncoding: 'binary' })
+ var buf = Buffer.from('00ff', 'hex')
+
+ db.put('binary', buf, function (err) {
+ t.ifError(err, 'no put error')
+
+ db.get('binary', function (err, value) {
+ t.ifError(err, 'no get error')
+ t.ok(Buffer.isBuffer(value), 'is a buffer')
+ t.same(value, buf)
+
+ db.get('binary', { valueEncoding: 'id' }, function (err, value) {
+ t.ifError(err, 'no get error')
+ t.notOk(Buffer.isBuffer(value), 'is not a buffer')
+ t.ok(value instanceof Uint8Array, 'is a Uint8Array')
+ t.same(Buffer.from(value), buf)
+
+ db.close(function (err) {
+ t.ifError(err, 'no close error')
+ })
+ })
+ })
+ })
+})