├── .gitignore ├── .travis.yml ├── package.json ├── example.js ├── LICENSE ├── test.js ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '8' 5 | - '7' 6 | - '6' -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lowdb-gh-adapter", 3 | "version": "1.1.0", 4 | "description": "Set your github repo as a backend for your lowdb instance", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node example.js", 8 | "test": "standard --verbose | snazzy && tape test.js | tap-summary" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "github-api": "^3.0.0", 15 | "nanoassert": "^1.1.0" 16 | }, 17 | "devDependencies": { 18 | "dotenv": "^4.0.0", 19 | "lowdb": "^1.0.0", 20 | "snazzy": "^7.0.0", 21 | "standard": "^10.0.3", 22 | "tap-summary": "^4.0.0", 23 | "tape": "^4.8.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | const low = require('lowdb') 4 | const GhStorage = require('.') 5 | 6 | const adapter = GhStorage({ 7 | file: 'db.json', 8 | repo: 'lowdb-gh-adapter', 9 | user: 'YerkoPalma', 10 | token: process.env.TOKEN, 11 | branch: 'db' 12 | }) 13 | 14 | low(adapter) 15 | .then(db => { 16 | // Set some defaults 17 | return db.defaults({ posts: [], user: {} }) 18 | .write() 19 | .then(() => { 20 | // Add a post 21 | db.get('posts') 22 | .push({ id: 1, title: 'lowdb is awesome' }) 23 | .write() 24 | .then(() => { 25 | // Set a user using Lodash shorthand syntax 26 | db.set('user.name', 'yerkopalma') 27 | .write().catch(console.error) 28 | }).catch(console.error) 29 | }).catch(console.error) 30 | }) 31 | .catch(err => console.error(err)) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Yerko Palma 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. -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | require('dotenv').config() 3 | const low = require('lowdb') 4 | const GhStorage = require('.') 5 | const Github = require('github-api') 6 | 7 | // before 8 | var repo 9 | const TEST_BRANCH = 'test' 10 | const DB_FILE = 'db.json' 11 | 12 | test('setup', t => { 13 | // ensure a test branch exists 14 | var github = new Github({ token: process.env.TOKEN }) 15 | repo = github.getRepo('YerkoPalma', 'lowdb-gh-adapter') 16 | repo.listBranches((err, results) => { 17 | if (err) t.fail() 18 | var branch = results.filter(b => b.name === TEST_BRANCH)[0] 19 | if (!branch) { 20 | repo.createBranch('master', TEST_BRANCH, () => t.end()) 21 | } else { 22 | t.end() 23 | } 24 | }) 25 | }) 26 | 27 | test('should throw when some option is missed', t => { 28 | t.throws(() => { 29 | const adapter = GhStorage() 30 | low(adapter) 31 | }) 32 | t.end() 33 | }) 34 | test('should require token or username and password', t => { 35 | t.throws(() => { 36 | const adapter = GhStorage({ 37 | file: DB_FILE, 38 | repo: 'lowdb-gh-adapter', 39 | user: 'YerkoPalma' 40 | }) 41 | low(adapter) 42 | }) 43 | t.throws(() => { 44 | const adapter = GhStorage({ 45 | file: DB_FILE, 46 | repo: 'lowdb-gh-adapter', 47 | user: 'YerkoPalma', 48 | username: 'YerkoPalma' 49 | }) 50 | low(adapter) 51 | }) 52 | t.end() 53 | }) 54 | test('should create file if not present', t => { 55 | const adapter = GhStorage({ 56 | file: DB_FILE, 57 | repo: 'lowdb-gh-adapter', 58 | user: 'YerkoPalma', 59 | token: process.env.TOKEN 60 | }) 61 | low(adapter) 62 | .then(db => { 63 | repo.getContents('master', DB_FILE, true, (err, content) => { 64 | if (err) { 65 | t.fail() 66 | t.end() 67 | } else { 68 | t.pass() 69 | t.end() 70 | } 71 | }) 72 | }) 73 | }) 74 | test('should accept branch option', t => { 75 | const adapter = GhStorage({ 76 | file: DB_FILE, 77 | repo: 'lowdb-gh-adapter', 78 | user: 'YerkoPalma', 79 | token: process.env.TOKEN, 80 | branch: TEST_BRANCH 81 | }) 82 | low(adapter) 83 | .then(db => { 84 | repo.getContents(TEST_BRANCH, DB_FILE, true, (err, content) => { 85 | if (err) { 86 | t.fail() 87 | t.end() 88 | } else { 89 | t.pass() 90 | t.end() 91 | } 92 | }) 93 | }) 94 | }) 95 | 96 | // after 97 | test.onFinish(() => { 98 | // delete db files 99 | repo.listBranches((err, branches) => { 100 | if (err) throw err 101 | branches.map(branch => { 102 | repo.deleteFile(branch.name, DB_FILE) 103 | }) 104 | }) 105 | }) 106 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var assert = require('nanoassert') 2 | var Github = require('github-api') 3 | 4 | function GhStorage (opts) { 5 | if (!(this instanceof GhStorage)) return new GhStorage(opts) 6 | // mandatory 7 | assert.ok(opts, 'GhStorage: Must provide an opts parameter') 8 | assert.ok(opts.file, 'GhStorage: `opts.file` must be specified') 9 | assert.ok(opts.repo, 'GhStorage: `opts.repo` must be specified') 10 | assert.ok(opts.token || (opts.username && opts.password), 'GhStorage: A token or username/password credentials must be set') 11 | assert.ok(opts.user, 'GhStorage: Github user and repo must be provided') 12 | this._file = opts.file 13 | this._repo = opts.repo 14 | this._user = opts.user 15 | this._name = opts.name 16 | this._mail = opts.mail 17 | 18 | // optional 19 | this._branch = opts.branch || 'master' 20 | var credentials = opts.token ? { token: opts.token } : { username: opts.username, password: opts.password } 21 | var github = new Github(credentials) 22 | this.repo = github.getRepo(this._user, this._repo) 23 | } 24 | 25 | GhStorage.prototype.read = function () { 26 | var content 27 | return this.repo.getContents(this._branch, this._file, true, (err, result, res) => { 28 | if (err && err.response.status !== 404) throw err 29 | content = result 30 | }) 31 | .then(response => { 32 | if (content) { 33 | return new Promise(resolve => resolve(content)) 34 | } else { 35 | var opts = this._name && this._mail 36 | ? { committer: { name: this._name, mail: this._mail } } 37 | : {} 38 | // if content is not present we must create the file 39 | return this.repo.writeFile(this._branch, this._file, '{}', '[skip ci] Update lowdb', opts, (err, result, res) => { 40 | if (err && err.response.status !== 404) throw err 41 | // if we get again a 404, then it is highly possible that the branch doesn't exist 42 | // so, attempt to create it 43 | if (err.response.status === 404) { 44 | // just return it, anyway we are going to send the default empty object 45 | return this.repo.createBranch('master', this._branch) 46 | } 47 | }).then(response => { 48 | return new Promise(resolve => resolve({})) 49 | }) 50 | } 51 | }) 52 | } 53 | 54 | GhStorage.prototype.write = function (data) { 55 | var content 56 | if (typeof data === 'object') data = JSON.stringify(data, null, 2) 57 | var opts = this._name && this._mail 58 | ? { committer: { name: this._name, mail: this._mail } } 59 | : {} 60 | return this.repo.writeFile(this._branch, this._file, data, '[skip ci] Update lowdb', opts, (err, result, res) => { 61 | if (err) return new Promise((resolve, reject) => reject(err)) 62 | content = result 63 | }).then(response => { 64 | return new Promise(resolve => resolve(content)) 65 | }) 66 | } 67 | 68 | module.exports = GhStorage 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lowdb-gh-adapter [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | Set your github repo as a backend for your [lowdb][lowdb] instance 6 | 7 | ## Usage 8 | 9 | ```js 10 | const low = require('lowdb') 11 | const GhStorage = require('lowdb-gh-adapter') 12 | 13 | const adapter = GhStorage({ 14 | file: 'db.json', 15 | repo: 'lowdb-gh-adapter', 16 | user: 'YerkoPalma', 17 | token: process.env.TOKEN 18 | }) 19 | 20 | low(adapter) 21 | .then(db => { 22 | // Set some defaults 23 | return db.defaults({ posts: [], user: {} }) 24 | .write() 25 | .then(() => { 26 | // Add a post 27 | db.get('posts') 28 | .push({ id: 1, title: 'lowdb is awesome'}) 29 | .write() 30 | .then(() => { 31 | // Set a user using Lodash shorthand syntax 32 | db.set('user.name', 'yerkopalma') 33 | .write() 34 | }) 35 | }) 36 | }) 37 | ``` 38 | 39 | ## API 40 | 41 | ### `const adapter = GhStorage(opts)` 42 | 43 | Create an asynchronous adapter object, and then use it like any other 44 | asynchronous adapter with lowdb. Most of the options are mandatory: 45 | 46 | - **`file`**: Required. A string indicating the path of the file where you want 47 | to store your data in your repo. `json` extension is not mandatory but it is 48 | recommended. If this file is not present in your repo, it will be automatically 49 | created. 50 | - **`repo`**: Required. A string for the github repo where the `file` is stored. 51 | - **`user`**: Required. The github username that owns the github repo. 52 | - **`token`**: The personal [access token for github][Github token]. You can 53 | [create tokens here][Create token], you only need the `repo` permission. If this 54 | option is not provided, then you must provide a `username` and `password` option 55 | for authentication. 56 | - **`username`**: A Github username who have access to the `repo` specified. 57 | Not needed if you provided a token, if you don't, you will also need a password 58 | for this username. 59 | - **`password`**: A Github password for the username provided. 60 | - **`branch`**: Optional. Pass a string with the name of the branch where you 61 | want to save your data. If the branch is not specified, it will default to master. 62 | If the provided branch doesn't exists in the repo it will attemp to create it as 63 | a clone of master branch. 64 | 65 | ## Disclaimer 66 | 67 | Please note that this is not recommended for big scale projects. As lowdb 68 | [limits section][limits] says, for bigger projects you should stay with solutions 69 | like MongoDB. 70 | 71 | Specially for this adapter, you should have a look to [Github Rate limit][rate limit] 72 | which has a quota of 5000 requests per hour. 73 | 74 | ## License 75 | 76 | [MIT](/LICENSE) 77 | 78 | [0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square 79 | [1]: https://nodejs.org/api/documentation.html#documentation_stability_index 80 | [2]: https://img.shields.io/npm/v/lowdb-gh-adapter.svg?style=flat-square 81 | [3]: https://npmjs.org/package/lowdb-gh-adapter 82 | [4]: https://img.shields.io/travis/YerkoPalma/lowdb-gh-adapter/master.svg?style=flat-square 83 | [5]: https://travis-ci.org/YerkoPalma/lowdb-gh-adapter 84 | [6]: https://img.shields.io/codecov/c/github/YerkoPalma/lowdb-gh-adapter/master.svg?style=flat-square 85 | [7]: https://codecov.io/github/YerkoPalma/lowdb-gh-adapter 86 | [8]: http://img.shields.io/npm/dm/lowdb-gh-adapter.svg?style=flat-square 87 | [9]: https://npmjs.org/package/lowdb-gh-adapter 88 | [10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square 89 | [11]: https://github.com/feross/standard 90 | [Github token]: https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ 91 | [Create token]: https://github.com/settings/tokens/new 92 | [limits]: https://github.com/typicode/lowdb#limits 93 | [lowdb]: https://github.com/typicode/lowdb 94 | [rate limit]: https://developer.github.com/v3/#rate-limiting --------------------------------------------------------------------------------