├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── __mocks__ └── aws-sdk.js ├── __tests__ └── main.js ├── package-lock.json ├── package.json ├── src └── main.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "6" 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['standard', 'prettier'], 3 | plugins: ['prettier'], 4 | rules: { 5 | 'prettier/prettier': [ 6 | 'error', 7 | { 8 | singleQuote: true, 9 | semi: false, 10 | }, 11 | ] 12 | }, 13 | env: { jest: true } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .DS_Store 61 | .idea 62 | lib 63 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | src 3 | __tests__ 4 | __mocks__ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "stable" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ezra Sharp 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 | # AWS S3 Adapter for LowDB 2 | 3 | [![Build Status](https://travis-ci.org/nicekiwi/lowdb-adapter-aws-s3.svg?branch=master)](https://travis-ci.org/nicekiwi/lowdb-adapter-aws-s3) [![npm version](https://badge.fury.io/js/lowdb-adapter-aws-s3.svg)](https://badge.fury.io/js/lowdb-adapter-aws-s3) 4 | 5 | This adapter allows you to create and use a lowDB source located on AWS S3 Storage. 6 | 7 | Supports Node.JS >= 4.0.0, Electron and the Browser. 8 | 9 | #### In active development, not Production ready. 10 | 11 | ## Why? 12 | 13 | Cause AWS is amazeballs.. and I can't afford a MongoDB server. :P 14 | 15 | ## Installation 16 | 17 | `npm i --save lowdb-adapter-aws-s3` 18 | 19 | ## Usage 20 | 21 | ``` 22 | // Grab the deps 23 | const lowDB = require('lowdb') 24 | const AwsAdapter = require('lowdb-adapter-aws-s3') 25 | 26 | // Init the adapter 27 | const adapter = new AwsAdapter() 28 | 29 | // Go hard! 30 | lowDB(adapter) 31 | 32 | // Defaults FTW 33 | .then(db => db.defaults({ posts: [], user: {} }).write()) 34 | 35 | // Push something awesome 36 | .then(db => db.get('posts').push({ id: 1, title: 'lowdb is awesome'}).write()) 37 | 38 | // Profit! 39 | .then(db => console.log('Victory!')) 40 | ``` 41 | 42 | ## Configuration 43 | 44 | `const adapter = new AwsAdapter('db.json', options)` 45 | 46 | #### Constructor Options 47 | 48 | The constructor uses the same options as lowDB itself, and can be passed `defaultValue`, `serialize` and `deserialize`. 49 | 50 | However, this module introduces a new paramater: `aws` which contains the options to connect and write to AWS S3. 51 | 52 | #### AWS Options 53 | 54 | | Param | Type | Default | Description | 55 | | --- | --- | --- | --- | 56 | | contentType | String | 'application/json' | The MimeType of the source file. | 57 | | bucketName | String | 'lowdb-data' | The name of the S3 bucket to write to. | 58 | | acl | String | 'private' | The AWS access control settings for the source file. | 59 | | cognitoCredentials | null | Object | The Object containing `CognitoIdentityCredentials` options (only required when using in the browser or Electron). | 60 | 61 | When using server-side AWS credentials should be set via the ENV, and will be picked up by AWS automattically. 62 | 63 | ``` 64 | AWS_ACCESS_KEY_ID = null 65 | AWS_SECRET_ACCESS_KEY = null 66 | ``` 67 | 68 | When using in the browser or Electron, you should (probably) be using an AWS [CognitoIdentityCredentials](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityCredentials.html#constructor-property) object, all you need to do is pass the options to the `cognitoCredentials` option in the `aws` options. 69 | 70 | The `AWS.CognitoIdentityCredentials` object will be automatically created for you from the options you pass to the `cognitoCredentials` paramater. Easy peasy. 71 | 72 | Example: 73 | 74 | ``` 75 | const adapter = new AwsAdapter('db.json', { 76 | aws: { 77 | cognitoCredentials: { 78 | IdentityPoolId: 'us-east-2:1699ebc0-7900-4099-b910-2df94f52a030, 79 | ... 80 | }, 81 | ... 82 | } 83 | }) 84 | ``` 85 | 86 | ## Tests 87 | 88 | `npm test` 89 | 90 | ## Caveat 91 | 92 | Obviously as read/write calls are made on-demand to AWS this is not a fast adapter, MongoDB would be a better choice for such Need for Speed. 93 | 94 | ## Future 95 | 96 | * Support bucket/file encryption 97 | * Some kind of in-memory caching to speed up read times 98 | 99 | ## Other Projects using Low-Adapter-AWS-S3 100 | 101 | * https://github.com/pharindoko/json-server-less-lambda 102 | -------------------------------------------------------------------------------- /__mocks__/aws-sdk.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict' 3 | 4 | function S3() { 5 | this.result = null 6 | this.bucket = null 7 | this.key = { 8 | path: null, 9 | data: null 10 | } 11 | } 12 | 13 | S3.prototype = { 14 | 15 | promise: function() { 16 | return this.result 17 | }, 18 | 19 | getObject: function(params) { 20 | this.result = new Promise((resolve, reject) => { 21 | if (params.Key !== this.key.path) reject({ code: 'NoSuchKey' }) 22 | else resolve({ Body: this.key.data }) 23 | }) 24 | 25 | return this 26 | }, 27 | 28 | putObject: function(params) { 29 | this.result = new Promise((resolve, reject) => { 30 | this.key.path = params.Key 31 | this.key.data = params.Body 32 | resolve() 33 | }) 34 | 35 | return this 36 | } 37 | } 38 | 39 | module.exports = S3 -------------------------------------------------------------------------------- /__tests__/main.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | jest.mock('aws-sdk/clients/s3') 4 | 5 | const sinon = require('sinon') 6 | const S3Adapter = require('../src/main') 7 | const obj = { a: 1 } 8 | 9 | describe('AWS S3 Adapter', () => { 10 | test('should read from aws', () => { 11 | let db = new S3Adapter() 12 | db.read().then(data => expect(data).toEqual({})) 13 | }) 14 | 15 | test('should write to aws', () => { 16 | let db = new S3Adapter() 17 | db 18 | .write(obj) 19 | .then(() => db.read()) 20 | .then(data => expect(data).toEqual(obj)) 21 | .catch(console.error) 22 | }) 23 | 24 | test('should support options', () => { 25 | const serialize = sinon.spy(JSON.stringify) 26 | const deserialize = sinon.spy(JSON.parse) 27 | const db = new S3Adapter('db.json', { 28 | serialize, 29 | deserialize 30 | }) 31 | 32 | db 33 | .write(obj) 34 | .then(() => db.read()) 35 | .then(data => { 36 | expect(serialize.calledWith(obj)).toBeTruthy() 37 | expect(deserialize.called).toBeTruthy() 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lowdb-adapter-aws-s3", 3 | "version": "1.1.2", 4 | "description": "Amazon S3 Adapter for LowDB.", 5 | "main": "./lib/main.js", 6 | "scripts": { 7 | "test": "jest && npm run lint", 8 | "lint": "eslint . --ignore-path .gitignore", 9 | "fix": "npm run lint -- --fix", 10 | "prepublishOnly": "npm run build && pkg-ok", 11 | "build": "npm run build:lib && npm run build:dist", 12 | "build:lib": "rimraf lib && babel src --out-dir lib", 13 | "build:dist": "rimraf dist && webpack && webpack -p" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/nicekiwi/lowdb-aws-s3-adapter.git" 18 | }, 19 | "author": "Ezra Sharp ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/nicekiwi/lowdb-aws-s3-adapter/issues" 23 | }, 24 | "homepage": "https://github.com/nicekiwi/lowdb-aws-s3-adapter#readme", 25 | "dependencies": { 26 | "aws-sdk": "^2.167.0" 27 | }, 28 | "devDependencies": { 29 | "@babel/preset-env": "^7.6.3", 30 | "babel-cli": "^6.2.0", 31 | "babel-eslint": "^8.0.0", 32 | "babel-jest": "^25.0.0", 33 | "babel-loader": "^7.1.1", 34 | "babel-polyfill": "^6.9.1", 35 | "babel-preset-env": "^1.6.0", 36 | "babel-register": "^6.9.0", 37 | "delay": "^2.0.0", 38 | "eslint": "^4.5.0", 39 | "eslint-config-prettier": "^2.3.0", 40 | "eslint-config-standard": "^10.2.1", 41 | "eslint-plugin-import": "^2.6.1", 42 | "eslint-plugin-node": "^5.1.0", 43 | "eslint-plugin-prettier": "^2.1.2", 44 | "eslint-plugin-promise": "^3.5.0", 45 | "eslint-plugin-standard": "^3.0.1", 46 | "husky": "^0.15.0-beta.15", 47 | "jest": "^25.0.0", 48 | "lodash-id": "^0.14.0", 49 | "mv": "^2.1.1", 50 | "pkg-ok": "^1.0.1", 51 | "prettier": "^1.5.2", 52 | "ramda": "^0.25.0", 53 | "regenerator-runtime": "^0.11.0", 54 | "rimraf": "^2.5.4", 55 | "sinon": "^4.0.0", 56 | "tempfile": "^2.0.0", 57 | "webpack": "^3.3.0" 58 | }, 59 | "engines": { 60 | "node": ">=4" 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "npm test" 65 | } 66 | }, 67 | "jest": { 68 | "verbose": true, 69 | "moduleNameMapper": { 70 | "aws-sdk/clients/s3": "/__mocks__/aws-sdk" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk/global') 2 | const S3_CLIENT = require('aws-sdk/clients/s3') 3 | const stringify = obj => JSON.stringify(obj, null, 2) 4 | 5 | module.exports = class { 6 | constructor( 7 | source = 'db.json', 8 | { 9 | defaultValue = {}, 10 | serialize = stringify, 11 | deserialize = JSON.parse, 12 | aws = {} 13 | } = {} 14 | ) { 15 | this.source = source 16 | this.defaultValue = defaultValue 17 | this.serialize = serialize 18 | this.deserialize = deserialize 19 | this.contentType = aws.contentType || 'application/json' 20 | this.cognitoCredentials = aws.cognitoCredentials || false 21 | this.bucketName = aws.bucketName || 'lowdb-data' 22 | this.acl = aws.acl || 'private' 23 | 24 | let options = { apiVersion: '2006-03-01' } 25 | 26 | if (this.cognitoCredentials) { 27 | options.credentials = new AWS.CognitoIdentityCredentials( 28 | this.cognitoCredentials 29 | ) 30 | } 31 | 32 | this.S3 = new S3_CLIENT(options) 33 | } 34 | 35 | read() { 36 | return new Promise((resolve, reject) => { 37 | this.S3.getObject({ Bucket: this.bucketName, Key: this.source }) 38 | .promise() 39 | .then(data => { 40 | resolve(this.deserialize(data.Body)) 41 | }) 42 | .catch(err => { 43 | if (err.code === 'NoSuchKey') { 44 | this.write(this.defaultValue) 45 | .then(() => resolve(this.defaultValue)) 46 | .catch(reject) 47 | } else { 48 | reject(err) 49 | } 50 | }) 51 | }) 52 | } 53 | 54 | write(data) { 55 | return this.S3.putObject({ 56 | Body: this.serialize(data), 57 | Bucket: this.bucketName, 58 | Key: this.source, 59 | ContentType: this.contentType, 60 | ACL: this.acl 61 | }).promise() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var pkg = require('./package.json') 4 | var banner = pkg.name + ' v' + pkg.version 5 | 6 | module.exports = { 7 | entry: './src/main.js', 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: process.argv.indexOf('-p') !== -1 ? '[name].min.js' : '[name].js', 11 | library: '[name]' 12 | }, 13 | externals: { 14 | lodash: '_' 15 | }, 16 | plugins: [new webpack.BannerPlugin(banner)], 17 | module: { 18 | loaders: [ 19 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } 20 | ] 21 | } 22 | } 23 | --------------------------------------------------------------------------------