├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example.js ├── index.js ├── package.json └── test ├── idx.js ├── multilevel-object.js ├── multilevel.js └── safe-deep-access.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | sudo: false 5 | cache: 6 | directories: 7 | - node_modules 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # level-idx change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## 2.0.0 9 | 10 | * Update all deps 11 | * Update level-auto-index 12 | * Update level-hookdown 13 | * update Levelup 4.1.0 14 | 15 | ## 1.0.0 16 | 17 | * engage 18 | * Initial release 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 hypermodules 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # level-idx 2 | 3 | High Level leveldb indexing API using [level-auto-index](https://github.com/hypermodules/level-auto-index). 4 | 5 | ```bash 6 | npm install level-idx 7 | ``` 8 | 9 | [![level badge][level-badge]](https://github.com/level/awesome) 10 | [![npm][npm-image]][npm-url] 11 | [![Build Status](https://travis-ci.org/hypermodules/level-idx.svg?branch=master)](https://travis-ci.org/hypermodules/level-idx) 12 | [![dependencies Status](https://david-dm.org/hypermodules/level-idx/status.svg)](https://david-dm.org/hypermodules/level-idx) 13 | [![devDependencies Status](https://david-dm.org/hypermodules/level-idx/dev-status.svg)](https://david-dm.org/hypermodules/level-idx?type=dev) 14 | 15 | [level-badge]: https://camo.githubusercontent.com/1bd15320a5fad1db168bba8bcedb098735f82464/68747470733a2f2f6c6576656c6a732e6f72672f696d672f62616467652e737667 16 | [npm-image]: https://img.shields.io/npm/v/level-idx.svg 17 | [npm-url]: https://www.npmjs.com/package/level-idx 18 | 19 | ## Usage 20 | 21 | Index posts by title and body length, then query for them: 22 | 23 | ```js 24 | var Index = require('level-idx') 25 | var level = require('memdb') 26 | var sub = require('subleveldown') 27 | 28 | var db = level() 29 | var posts = sub(db, 'posts', {valueEncoding: 'json'}) 30 | var idx = sub(db, 'idx') 31 | 32 | Index(posts, idx) 33 | .by('Title', 'title') 34 | .by('Length', ['body.length', 'title']) 35 | .by('Author', ['author', 'title']) 36 | 37 | var post = { 38 | title: 'a title', 39 | body: 'lorem ipsum', 40 | author: 'julian' 41 | } 42 | 43 | posts.put('1337', post, function (err) { 44 | if (err) throw err 45 | 46 | posts.byTitle.get('a title', console.log) 47 | posts.byLength.get('11!a title', console.log) 48 | posts.byAuthor.get('julian!a title', console.log) 49 | }) 50 | ``` 51 | 52 | ## API 53 | 54 | ### Index(db, idb) 55 | 56 | Index `db` into `idb`. 57 | 58 | ### Index#by(name, props) 59 | 60 | Create an index called `name` and index by `props`. 61 | 62 | `props` should be a string or an array of strings that each name a property. 63 | Deep object access is enabled via 64 | [deep-access](https://github.com/juliangruber/deep-access). Use multiple 65 | properties if you can't guarantee the uniqueness of the first property's 66 | value. 67 | 68 | If a property doesn't exist, e.g. if you want to index by `body.length` but there is no key `body`, it will be ignored. 69 | 70 | ### Index.db 71 | 72 | The underlying `db`. 73 | 74 | ### Index.db.by{Name}.get(key[, opts], fn) 75 | ### Index.db.by{Name}.create{Key,Value,Read}Stream([opts]) 76 | 77 | See [level-auto-index](https://github.com/hypermodules/level-auto-index). 78 | 79 | ## Multilevel 80 | 81 | Populate `db.methods` with the manifests of each indexed db.`object`: 82 | 83 | ```js 84 | var index = require('level-idx'); 85 | var createManifest = require('level-manifest'); 86 | 87 | db.methods = {}; 88 | db.posts = index(db.sublevel('posts')) 89 | .by('Slug', ['slug']) 90 | .db; 91 | db.methods.posts = { 92 | type: 'object', 93 | methods: createManifest(db.posts).methods 94 | }; 95 | ``` 96 | 97 | ## See Also 98 | 99 | - [hypermodules/level-hookdown](https://github.com/hypermodules/level-hookdown) - Simple levelup hooks implemented using instance method override of arbitrary levelups. 100 | - [hypermodules/level-auto-index](https://github.com/hypermodules/level-auto-index) - Automatic secondary indexing for leveldb and subleveldown leveraging `level-hookdown`. 101 | 102 | This module is a port of [juliangruber/level-sec](https://github.com/juliangruber/level-sec) that works/uses [subleveldown](http://ghub.io/subleveldown) and [level-auto-index](http://ghub.io/level-auto-index). 103 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var Index = require('./') 2 | var level = require('memdb') 3 | var sub = require('subleveldown') 4 | 5 | var db = level() 6 | var posts = sub(db, 'posts', { valueEncoding: 'json' }) 7 | var idx = sub(db, 'idx') 8 | 9 | Index(posts, idx) 10 | .by('Title', 'title') 11 | .by('Length', ['body.length', 'title']) 12 | .by('Author', ['author', 'title']) 13 | 14 | var post = { 15 | title: 'a title', 16 | body: 'lorem ipsum', 17 | author: 'julian' 18 | } 19 | 20 | posts.put('1337', post, function (err) { 21 | if (err) throw err 22 | 23 | posts.byTitle.get('a title', console.log) 24 | posts.byLength.get('11!a title', console.log) 25 | posts.byAuthor.get('julian!a title', console.log) 26 | }) 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var AutoIndex = require('level-auto-index') 2 | var access = require('deep-access') 3 | var sub = require('subleveldown') 4 | 5 | module.exports = Idx 6 | 7 | function Idx (db, idb, opts) { 8 | if (!(this instanceof Idx)) return new Idx(db, idb, opts) 9 | if (!opts) opts = {} 10 | this.db = db 11 | this.db.methods = this.db.methods || {} 12 | this.idb = idb 13 | this.keyEncoding = opts.keyEncoding || 'utf8' 14 | this.keyFn = opts.keyEncoding && opts.keyEncoding.type === 'bytewise-core' 15 | ? keyFns.bytewise 16 | : keyFns.utf8 17 | } 18 | 19 | var keyFns = { 20 | utf8: function (segs) { return segs.join('!') }, 21 | bytewise: function (segs) { return segs } 22 | } 23 | 24 | Idx.prototype.by = function (name, props) { 25 | if (!Array.isArray(props)) props = [props] 26 | 27 | var self = this 28 | 29 | var autoIdx = AutoIndex( 30 | this.db, 31 | sub(this.idb, name, { 32 | valueEncoding: this.db.options.keyEncoding, 33 | keyEncoding: this.keyEncoding 34 | }), 35 | reducer 36 | ) 37 | 38 | function reducer (value) { 39 | var segs = [] 40 | props.forEach(function (prop) { 41 | try { 42 | var seg = access(value, prop) 43 | } catch (e) { 44 | return 45 | } 46 | segs.push(seg) 47 | }) 48 | return self.keyFn(segs) 49 | } 50 | 51 | this.db['by' + name] = autoIdx 52 | this.db.methods['by' + name] = { 53 | type: 'object', 54 | methods: autoIdx.manifest.methods 55 | } 56 | return this 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "level-idx", 3 | "version": "2.0.0", 4 | "description": "Another high-level API for creating secondary leveldb indexes", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "run-s test:*", 8 | "test:deps": "dependency-check ./package.json", 9 | "test:lint": "standard | snazzy", 10 | "test:tape": "tape test/* | tap-format-spec" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/hypermodules/level-idx.git" 15 | }, 16 | "keywords": [ 17 | "leveldb", 18 | "level", 19 | "index", 20 | "secondary", 21 | "automatic", 22 | "indexing" 23 | ], 24 | "author": "Bret Comnes", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/hypermodules/level-idx/issues" 28 | }, 29 | "homepage": "https://github.com/hypermodules/level-idx#readme", 30 | "dependencies": { 31 | "deep-access": "^0.1.1", 32 | "level-auto-index": "^2.0.0", 33 | "subleveldown": "^4.1.0" 34 | }, 35 | "devDependencies": { 36 | "@tap-format/spec": "^0.2.0", 37 | "bytewise": "^1.1.0", 38 | "dependency-check": "^3.4.1", 39 | "memdb": "^1.3.1", 40 | "multilevel": "^7.2.3", 41 | "npm-run-all": "^4.1.5", 42 | "snazzy": "^8.0.0", 43 | "standard": "^14.0.0", 44 | "tape": "^4.11.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/idx.js: -------------------------------------------------------------------------------- 1 | var MemDB = require('memdb') 2 | var Index = require('..') 3 | var sub = require('subleveldown') 4 | var test = require('tape') 5 | var bytewise = require('bytewise') 6 | 7 | test('idx', function (t) { 8 | t.plan(7) 9 | 10 | var db = MemDB() 11 | var posts = sub(db, 'posts', { valueEncoding: 'json' }) 12 | var idb = sub(db, 'index') 13 | 14 | Index(posts, idb) 15 | .by('Title', 'title') 16 | .by('Length', ['body.length']) 17 | .by('Author', ['author', 'title']) 18 | 19 | var post = { 20 | title: 'a title', 21 | body: 'lorem ipsum', 22 | author: 'julian' 23 | } 24 | 25 | posts.put('1337', post, function (err) { 26 | t.error(err) 27 | 28 | posts.byTitle.get('a title', onPost) 29 | posts.byLength.get('11', onPost) 30 | posts.byAuthor.get('julian!a title', onPost) 31 | 32 | function onPost (err, _post) { 33 | t.error(err) 34 | t.deepEqual(_post, post) 35 | } 36 | }) 37 | }) 38 | 39 | test('idx-bytewise', function (t) { 40 | t.plan(7) 41 | 42 | var db = MemDB('./db') 43 | var posts = sub(db, 'posts', { keyEncoding: bytewise, valueEncoding: 'json' }) 44 | var idb = sub(db, 'index') 45 | 46 | Index(posts, idb, { keyEncoding: bytewise }) 47 | .by('Title', 'title') 48 | .by('Length', ['body.length']) 49 | .by('Author', ['author', 'title']) 50 | 51 | var post = { 52 | title: 'a title', 53 | body: 'lorem ipsum', 54 | author: 'julian' 55 | } 56 | 57 | posts.put('1337', post, function (err) { 58 | t.error(err) 59 | 60 | posts.byTitle.get(['a title'], onPost) 61 | posts.byLength.get([11], onPost) 62 | posts.byAuthor.get(['julian', 'a title'], onPost) 63 | 64 | function onPost (err, _post) { 65 | t.error(err) 66 | t.deepEqual(_post, post) 67 | } 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/multilevel-object.js: -------------------------------------------------------------------------------- 1 | var MemDB = require('memdb') 2 | var Index = require('..') 3 | var sub = require('subleveldown') 4 | var test = require('tape') 5 | var multilevel = require('multilevel') 6 | var createManifest = require('level-manifest') 7 | 8 | test('multilevel object', function (t) { 9 | t.plan(7) 10 | 11 | var db = MemDB() 12 | db.posts = sub(db, 'posts', { valueEncoding: 'json' }) 13 | var idb = sub(db, 'index') 14 | 15 | Index(db.posts, idb) 16 | .by('Title', 'title') 17 | .by('Length', ['body.length']) 18 | .by('Author', ['author', 'title']) 19 | 20 | db.methods = db.methods || {} 21 | db.methods.posts = { 22 | type: 'object', 23 | methods: createManifest(db.posts).methods 24 | } 25 | var manifest = createManifest(db) 26 | 27 | var server = multilevel.server(db) 28 | var client = multilevel.client(manifest) 29 | 30 | server.pipe(client.createRpcStream()).pipe(server) 31 | 32 | var post = { 33 | title: 'a title', 34 | body: 'lorem ipsum', 35 | author: 'julian' 36 | } 37 | 38 | client.posts.put('1337', post, function (err) { 39 | t.error(err) 40 | 41 | client.posts.byTitle.get('a title', onPost) 42 | client.posts.byLength.get('11', onPost) 43 | client.posts.byAuthor.get('julian!a title', onPost) 44 | 45 | function onPost (err, _post) { 46 | t.error(err) 47 | t.deepEqual(_post, post) 48 | } 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/multilevel.js: -------------------------------------------------------------------------------- 1 | var MemDB = require('memdb') 2 | var Index = require('..') 3 | var sub = require('subleveldown') 4 | var test = require('tape') 5 | var multilevel = require('multilevel') 6 | 7 | test('multilevel', function (t) { 8 | t.plan(7) 9 | 10 | var db = MemDB() 11 | var posts = sub(db, 'posts', { valueEncoding: 'json' }) 12 | var idb = sub(db, 'index') 13 | 14 | Index(posts, idb) 15 | .by('Title', 'title') 16 | .by('Length', ['body.length']) 17 | .by('Author', ['author', 'title']) 18 | 19 | var server = multilevel.server(posts) 20 | var client = multilevel.client(posts) 21 | 22 | server.pipe(client.createRpcStream()).pipe(server) 23 | 24 | var post = { 25 | title: 'a title', 26 | body: 'lorem ipsum', 27 | author: 'julian' 28 | } 29 | 30 | client.put('1337', post, function (err) { 31 | t.error(err) 32 | client.byTitle.get('a title', onPost) 33 | client.byLength.get('11', onPost) 34 | client.byAuthor.get('julian!a title', onPost) 35 | 36 | function onPost (err, _post) { 37 | t.error(err) 38 | t.deepEqual(_post, post) 39 | } 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/safe-deep-access.js: -------------------------------------------------------------------------------- 1 | var MemDB = require('memdb') 2 | var Index = require('..') 3 | var sub = require('subleveldown') 4 | var test = require('tape') 5 | 6 | test('safe-deep-access', function (t) { 7 | t.plan(5) 8 | 9 | var db = MemDB() 10 | var posts = sub(db, 'posts', { valueEncoding: 'json' }) 11 | var idb = sub(db, 'index') 12 | 13 | Index(posts, idb) 14 | .by('Title', 'title') 15 | .by('Length', ['body.length']) 16 | .by('Author', ['author', 'title']) 17 | 18 | var post = { 19 | title: 'a title', 20 | author: 'julian' 21 | } 22 | 23 | posts.put('1337', post, function (err) { 24 | t.error(err) 25 | 26 | posts.byTitle.get('a title', onPost) 27 | posts.byAuthor.get('julian!a title', onPost) 28 | 29 | function onPost (err, _post) { 30 | t.error(err) 31 | t.deepEqual(_post, post) 32 | } 33 | }) 34 | }) 35 | --------------------------------------------------------------------------------