├── bin └── nedb ├── .travis.yml ├── terminal.gif ├── .eslintrc ├── .gitignore ├── test ├── .eslintrc └── datastore.test.js ├── .editorconfig ├── lib ├── repl │ ├── commands.js │ ├── events.js │ ├── evaluator.js │ ├── writer.js │ └── index.js ├── cli.js └── datastore.js ├── package.json ├── LICENSE └── README.md /bin/nedb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli'); 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '14.0' 5 | -------------------------------------------------------------------------------- /terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikolvs/nedb-repl/HEAD/terminal.gif -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "no-console": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # ESLint 7 | .eslintcache 8 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /lib/repl/commands.js: -------------------------------------------------------------------------------- 1 | const nedbRepl = require('./'); 2 | const chalk = require('chalk'); 3 | 4 | const Commands = { 5 | open: { 6 | help: 'Open file to persist data', 7 | action(filename) { 8 | nedbRepl.setDatastore(this.context, filename).then(() => { 9 | console.log(filename 10 | ? `Opened file ${chalk.bold(filename)}` 11 | : 'Using in-memory only datastore'); 12 | 13 | this.displayPrompt(); 14 | }); 15 | }, 16 | }, 17 | }; 18 | 19 | module.exports = Commands; 20 | -------------------------------------------------------------------------------- /lib/repl/events.js: -------------------------------------------------------------------------------- 1 | const nedbRepl = require('./'); 2 | const chalk = require('chalk'); 3 | const R = require('ramda'); 4 | 5 | const Events = { 6 | reset(context) { 7 | nedbRepl.setVM(context, this.options.filename, this.options).then(() => { 8 | const datastoreName = R.defaultTo('in-memory only', this.options.filename); 9 | console.log(`Current datastore: ${chalk.bold(datastoreName)}`); 10 | this.displayPrompt(); 11 | }); 12 | }, 13 | 14 | exit() { 15 | this.context.watcher.close(); 16 | process.exit(0); 17 | }, 18 | }; 19 | 20 | module.exports = Events; 21 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | const meow = require('meow'); 2 | const pkg = require('../package.json'); 3 | const nedbRepl = require('./repl'); 4 | 5 | const CLI_HELP = ` 6 | USAGE 7 | $ nedb [filename] 8 | 9 | OPTIONS 10 | -t, --timeout Set REPL timeout in seconds. Defaults to 3 11 | -h, --help Displays this 12 | -v, --version Displays the REPL and NeDB versions 13 | `; 14 | 15 | const cli = meow({ 16 | help: CLI_HELP, 17 | version: `NeDB v${pkg.dependencies.nedb} - REPL v${pkg.version}`, 18 | }, { 19 | alias: { 20 | t: 'timeout', 21 | h: 'help', 22 | v: 'version', 23 | }, 24 | }); 25 | 26 | const filename = cli.input[0]; 27 | nedbRepl(filename, cli.flags).catch(console.error); 28 | -------------------------------------------------------------------------------- /lib/repl/evaluator.js: -------------------------------------------------------------------------------- 1 | const repl = require('repl'); 2 | const R = require('ramda'); 3 | 4 | const RECOVERABLE_ERRORS = [ 5 | 'Unexpected token', 6 | 'Unexpected end of input', 7 | 'missing ) after argument list', 8 | 'Unterminated template literal', 9 | 'Missing } in template expression', 10 | ]; 11 | 12 | const isCursor = x => x && x.query && x.execFn; 13 | const isRecoverable = (err) => { 14 | if (err.name !== 'SyntaxError') return false; 15 | return R.contains(err.message, RECOVERABLE_ERRORS); 16 | }; 17 | 18 | const evaluator = async function evaluator(cmd, context, filename, callback) { 19 | try { 20 | const result = context.vm.run(cmd); 21 | const output = isCursor(result) ? result.exec() : result; 22 | 23 | return callback(null, await output); 24 | } catch (err) { 25 | if (isRecoverable(err)) { 26 | return callback(new repl.Recoverable(err)); 27 | } 28 | 29 | return callback(null, Error(err)); 30 | } 31 | }; 32 | 33 | module.exports = evaluator; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nedb-repl", 3 | "version": "1.2.3", 4 | "description": "The command-line tool for NeDB", 5 | "license": "MIT", 6 | "repository": "nikolvs/nedb-repl", 7 | "author": "Nikolas Silva ", 8 | "engines": { 9 | "node": ">= 14.0.0" 10 | }, 11 | "bin": { 12 | "nedb": "bin/nedb" 13 | }, 14 | "scripts": { 15 | "test": "eslint . && ava" 16 | }, 17 | "keywords": [ 18 | "nedb", 19 | "repl", 20 | "cli", 21 | "terminal", 22 | "database", 23 | "nosql" 24 | ], 25 | "dependencies": { 26 | "cardinal": "^2.1.1", 27 | "chalk": "^4.1.1", 28 | "circular-json": "^0.5.9", 29 | "gaze": "^1.1.3", 30 | "meow": "^9.0.0", 31 | "nedb": "1.8.0", 32 | "ramda": "^0.27.1", 33 | "vm2": "^3.1.0" 34 | }, 35 | "devDependencies": { 36 | "ava": "^0.23.0", 37 | "babel-eslint": "^8.0.1", 38 | "eslint": "^4.10.0", 39 | "eslint-config-airbnb": "^16.1.0", 40 | "eslint-plugin-import": "^2.0.0", 41 | "eslint-plugin-jsx-a11y": "^6.0.2", 42 | "eslint-plugin-react": "^7.4.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/datastore.js: -------------------------------------------------------------------------------- 1 | /* eslint no-underscore-dangle: 2 | ["error", { "allow": ["_exec", "_insert", "_update", "_remove"] }] */ 3 | 4 | const Cursor = require('nedb/lib/cursor'); 5 | const Datastore = require('nedb'); 6 | const { promisify } = require('util'); 7 | 8 | Cursor.prototype._exec = promisify(Cursor.prototype._exec); 9 | Cursor.prototype.exec = function exec(...args) { 10 | return this._exec(...args); 11 | }; 12 | 13 | Datastore.prototype._insert = promisify(Datastore.prototype._insert); 14 | Datastore.prototype.insert = function insert(...args) { 15 | return this._insert(...args); 16 | }; 17 | 18 | Datastore.prototype._update = promisify(Datastore.prototype._update); 19 | Datastore.prototype.update = function update(...args) { 20 | return this._update(...args); 21 | }; 22 | 23 | Datastore.prototype._remove = promisify(Datastore.prototype._remove); 24 | Datastore.prototype.remove = function remove(...args) { 25 | return this._remove(...args); 26 | }; 27 | 28 | module.exports = filename => new Promise((resolve, reject) => { 29 | const ds = new Datastore({ 30 | filename, 31 | autoload: true, 32 | onload: err => (err ? reject(err) : resolve(ds)), 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nikolas Silva (nikolas.com.br) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/repl/writer.js: -------------------------------------------------------------------------------- 1 | const cardinal = require('cardinal'); 2 | const chalk = require('chalk'); 3 | const CircularJSON = require('circular-json'); 4 | const R = require('ramda'); 5 | const util = require('util'); 6 | 7 | const isError = R.is(Error); 8 | const isObject = R.is(Object); 9 | const isFunction = R.is(Function); 10 | 11 | const writer = function writer(data) { 12 | if (isError(data)) { 13 | const output = data.message; 14 | return chalk.red(output); 15 | } 16 | 17 | if (!isObject(data)) { 18 | const output = String(data); 19 | return cardinal.highlight(output); 20 | } 21 | 22 | if (isFunction(data)) { 23 | const output = util.inspect(data); 24 | return cardinal.highlight(output); 25 | } 26 | 27 | const isObjectArray = data.length && isObject(data[0]); 28 | if (!Array.isArray(data) || !isObjectArray) { 29 | const output = CircularJSON.stringify(data); 30 | return cardinal.highlight(output); 31 | } 32 | 33 | const output = data.reduce((buf, item, idx) => { 34 | const str = CircularJSON.stringify(item); 35 | return `${buf + str}${(idx === (data.length - 1) ? '' : ',\n')}`; 36 | }, ''); 37 | 38 | return cardinal.highlight(output); 39 | }; 40 | 41 | module.exports = writer; 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nedb-repl 2 | > The command-line tool for NeDB 3 | 4 | ![](terminal.gif) 5 | 6 | [![Build Status](https://travis-ci.org/nikolvs/nedb-repl.svg)](https://travis-ci.org/nikolvs/nedb-repl) 7 | [![Dependency Status](https://david-dm.org/nikolvs/nedb-repl.svg)](https://david-dm.org/nikolvs/nedb-repl) 8 | [![devDependency Status](https://david-dm.org/nikolvs/nedb-repl/dev-status.svg)](https://david-dm.org/nikolvs/nedb-repl#info=devDependencies) 9 | 10 | This is an interactive interface to query and update data, like MongoDB Shell, but for NeDB. 11 | 12 | ## Install 13 | ```bash 14 | npm install -g nedb-repl 15 | ``` 16 | 17 | ## Usage 18 | To open a datastore file, use: 19 | ```bash 20 | $ nedb foo.db 21 | ``` 22 | 23 | Inside the REPL, the datastore namespace is attached to the `db` global property. 24 | 25 | You can display the datastore you're using by typing: 26 | ```bash 27 | nedb> db.filename 28 | foo.db 29 | ``` 30 | 31 | To perform queries and other operations, you can use the well-known NeDB datastore methods without the callback param. See [NeDB API](https://github.com/louischatriot/nedb#api). 32 | ```bash 33 | nedb> db.insert([ { a: 1 }, { a: 2 } ]) 34 | {"a":1,"_id":"Kkui4fblZ5kqkmc8"}, 35 | {"a":2,"_id":"9ptV45vIEbBparvA"} 36 | nedb> db.find({ a: 1 }) 37 | {"a":1,"_id":"Kkui4fblZ5kqkmc8"} 38 | nedb> db.count() 39 | 2 40 | ``` 41 | 42 | You can change the datastore you're using with the `.open` command: 43 | ```bash 44 | nedb> .open bar.db 45 | Opened file bar.db 46 | ``` 47 | If the filename is not specified, the datastore is set to in-memory only. 48 | 49 | You can see other REPL commands by typing `.help`. 50 | 51 | ## Changelog 52 | 53 | ### 1.2.0 54 | - Automatically execute cursors (no need to put `.exec()` in the final of each query) 55 | 56 | ### 1.1.0 57 | - Automatically reload datastore when it changes 58 | 59 | ## License 60 | MIT © [Nikolas Silva](http://nikolas.com.br) 61 | -------------------------------------------------------------------------------- /test/datastore.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import datastore from '../lib/datastore'; 3 | 4 | const data = [ 5 | { a: 1 }, 6 | { a: 2 }, 7 | { a: 3 }, 8 | ]; 9 | 10 | let db; 11 | test.before(async () => { 12 | db = await datastore(); 13 | }); 14 | 15 | test.serial('should insert a document', async (t) => { 16 | const doc = await db.insert(data[0]); 17 | 18 | t.is(doc.a, data[0].a); 19 | t.true('_id' in doc); 20 | }); 21 | 22 | test.serial('should insert two documents', async (t) => { 23 | const arr = data.slice(1); 24 | const docs = await db.insert(arr); 25 | 26 | docs.forEach((doc, index) => { 27 | t.is(doc.a, arr[index].a); 28 | t.true('_id' in doc); 29 | }); 30 | }); 31 | 32 | test.serial('should find all documents', async (t) => { 33 | const docs = await db.find({}).exec(); 34 | 35 | if (docs.length !== data.length) { 36 | t.fail(); 37 | } 38 | 39 | docs.forEach((doc) => { 40 | const i = data.find(d => d.a === doc.a); 41 | t.not(i, null); 42 | }); 43 | }); 44 | 45 | test.serial('should find all documents keeping only the `a` field', async (t) => { 46 | const docs = await db 47 | .find({}, { a: 1, _id: 0 }) 48 | .sort({ a: 1 }) 49 | .exec(); 50 | 51 | t.deepEqual(docs, data); 52 | }); 53 | 54 | test.serial('should find one document', async (t) => { 55 | const doc = await db.findOne({ a: 3 }).exec(); 56 | t.is(doc.a, data[2].a); 57 | }); 58 | 59 | test.serial('should count documents', async (t) => { 60 | const count = await db.count({}).exec(); 61 | t.is(count, data.length); 62 | }); 63 | 64 | test.serial('should update a document', async (t) => { 65 | const countUpdates = await db.update({ a: 1 }, { a: 0 }); 66 | t.is(countUpdates, 1); 67 | }); 68 | 69 | test.serial('should update documents w/ `a` field greater than 1', async (t) => { 70 | const countUpdates = await db.update( 71 | { a: { $gt: 1 } }, 72 | { a: 4 }, 73 | { multi: true }, 74 | ); 75 | 76 | t.is(countUpdates, data.filter(d => d.a > 1).length); 77 | }); 78 | 79 | test.serial('should remove a document', async (t) => { 80 | const countRemoved = await db.remove({ a: 0 }); 81 | t.is(countRemoved, 1); 82 | }); 83 | 84 | test.serial('should remove all documents', async (t) => { 85 | const countRemoved = await db.remove({}, { multi: true }); 86 | t.is(countRemoved, data.length - 1); 87 | }); 88 | -------------------------------------------------------------------------------- /lib/repl/index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-underscore-dangle: ["error", { "allow": ["_context"] }] */ 2 | /* eslint no-param-reassign: ["error", { "props": false }] */ 3 | 4 | const Commands = require('./commands'); 5 | const Events = require('./events'); 6 | const datastore = require('../datastore'); 7 | const evaluator = require('./evaluator'); 8 | const pkg = require('../../package.json'); 9 | const writer = require('./writer'); 10 | 11 | const chalk = require('chalk'); 12 | const repl = require('repl'); 13 | const R = require('ramda'); 14 | const { Gaze } = require('gaze'); 15 | const { VM } = require('vm2'); 16 | 17 | const REPL_PROMPT = 'nedb> '; 18 | const VM_DEFAULT_TIMEOUT = 3; 19 | 20 | const displayIntro = (filename) => { 21 | const datastoreName = R.defaultTo('in-memory only', filename); 22 | const openHint = filename ? '' : 23 | `Use ${chalk.underline('.open FILENAME')} to reopen on a persistent datastore.`; 24 | const intro = ` 25 | NeDB v${pkg.dependencies.nedb} - REPL v${pkg.version} 26 | Enter ${chalk.underline('.help')} for usage hints. 27 | Connected to ${chalk.bold(datastoreName)} datastore. 28 | ${openHint} 29 | `.replace(/(\n\s+|\n\n)/g, '\n'); 30 | 31 | console.log(intro); 32 | }; 33 | 34 | exports.setDatastore = async (context, filename) => { 35 | if (context.watcher) { 36 | context.watcher.close(); 37 | } 38 | 39 | const ds = await datastore(filename); 40 | Object.defineProperty(context.vm._context, 'db', { 41 | configurable: true, 42 | enumarable: true, 43 | value: ds, 44 | }); 45 | 46 | context.watcher = new Gaze(filename); 47 | context.watcher.on('changed', () => this.setDatastore(context, filename)); 48 | }; 49 | 50 | exports.setVM = (context, filename, options = {}) => { 51 | context.vm = new VM({ 52 | timeout: R.defaultTo(VM_DEFAULT_TIMEOUT, options.timeout) * 1000, 53 | }); 54 | 55 | return this.setDatastore(context, filename); 56 | }; 57 | 58 | module.exports = async (filename, options = {}) => { 59 | displayIntro(filename); 60 | const nedbRepl = repl.start({ 61 | writer, 62 | eval: evaluator, 63 | prompt: REPL_PROMPT, 64 | }); 65 | 66 | R.mapObjIndexed((cmd, keyword) => { 67 | nedbRepl.defineCommand(keyword, cmd); 68 | }, Commands); 69 | 70 | nedbRepl.options = options; 71 | nedbRepl.options.filename = filename; 72 | 73 | nedbRepl.on('reset', Events.reset); 74 | nedbRepl.on('exit', Events.exit); 75 | 76 | await this.setVM(nedbRepl.context, filename, options); 77 | return nedbRepl; 78 | }; 79 | --------------------------------------------------------------------------------