├── .babelrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .moccarc ├── .travis.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── package.json └── src ├── __tests__ └── index-test.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "modern-node/0.12", "stage-3" ] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [package.json] 12 | indent_size = 2 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artefacts 2 | /build/ 3 | 4 | # npm 5 | /node_modules/ 6 | /npm-debug.log 7 | /redux-storage-*.tgz 8 | 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /.moccarc: -------------------------------------------------------------------------------- 1 | --globals localStorage 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5.5" 4 | - "5.4" 5 | - "5.3" 6 | - "5.2" 7 | - "5.1" 8 | - "5.0" 9 | - "4.2" 10 | - "4.1" 11 | - "0.12" 12 | - "0.10" 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Contento 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = ./node_modules/.bin 2 | NPM = npm --loglevel=error 3 | 4 | # 5 | # INSTALL 6 | # 7 | 8 | install: node_modules/ 9 | 10 | node_modules/: package.json 11 | echo "> Installing ..." 12 | $(NPM) --ignore-scripts install > /dev/null 13 | touch node_modules/ 14 | 15 | # 16 | # CLEAN 17 | # 18 | 19 | clean: 20 | echo "> Cleaning ..." 21 | rm -rf build/ 22 | 23 | mrproper: clean 24 | echo "> Cleaning deep ..." 25 | rm -rf node_modules/ 26 | 27 | # 28 | # BUILD 29 | # 30 | 31 | build: clean install 32 | echo "> Building ..." 33 | $(BIN)/babel src/ --out-dir build/ 34 | 35 | build-watch: clean install 36 | echo "> Building forever ..." 37 | $(BIN)/babel src/ --out-dir build/ --watch 38 | 39 | # 40 | # TEST 41 | # 42 | 43 | lint: install 44 | echo "> Linting ..." 45 | $(BIN)/eslint src/ 46 | 47 | test: install 48 | echo "> Testing ..." 49 | $(BIN)/mocca 50 | 51 | test-watch: install 52 | echo "> Testing forever ..." 53 | $(BIN)/mocca --watch 54 | 55 | # 56 | # PUBLISH 57 | # 58 | 59 | _publish : NODE_ENV ?= production 60 | _publish: lint test build 61 | 62 | publish-fix: _publish 63 | $(BIN)/release-it --increment patch 64 | 65 | publish-feature: _publish 66 | $(BIN)/release-it --increment minor 67 | 68 | publish-breaking: _publish 69 | $(BIN)/release-it --increment major 70 | 71 | # 72 | # MAKEFILE 73 | # 74 | 75 | .PHONY: \ 76 | install \ 77 | clean mrproper \ 78 | build build-watch \ 79 | lint test test-watch \ 80 | publish-fix publish-feature publish-breaking 81 | 82 | .SILENT: 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [redux-storage-engine-localstorage][] 2 | 3 | [![build](https://travis-ci.org/michaelcontento/redux-storage-engine-localstorage.svg?branch=master)](https://travis-ci.org/michaelcontento/redux-storage-engine-localstorage) 4 | [![dependencies](https://david-dm.org/michaelcontento/redux-storage-engine-localstorage.svg)](https://david-dm.org/michaelcontento/redux-storage-engine-localstorage) 5 | [![devDependencies](https://david-dm.org/michaelcontento/redux-storage-engine-localstorage/dev-status.svg)](https://david-dm.org/michaelcontento/redux-storage-engine-localstorage#info=devDependencies) 6 | 7 | [![license](https://img.shields.io/npm/l/redux-storage-engine-localstorage.svg?style=flat-square)](https://www.npmjs.com/package/redux-storage-engine-localstorage) 8 | [![npm version](https://img.shields.io/npm/v/redux-storage-engine-localstorage.svg?style=flat-square)](https://www.npmjs.com/package/redux-storage-engine-localstorage) 9 | [![npm downloads](https://img.shields.io/npm/dm/redux-storage-engine-localstorage.svg?style=flat-square)](https://www.npmjs.com/package/redux-storage-engine-localstorage) 10 | 11 | `window.localStorage` based engine for [redux-storage][]. 12 | 13 | # Moved to the react-stack organisation 14 | 15 | My focus has left the node / react ecosystem and this module has got a new home 16 | over at [react-stack](https://github.com/react-stack/redux-storage-engine-localstorage)! 17 | 18 | ## Installation 19 | 20 | npm install --save redux-storage-engine-localstorage 21 | 22 | ## Usage 23 | 24 | Stores everything inside `window.localStorage`. 25 | 26 | ```js 27 | import createEngine from 'redux-storage-engine-localstorage'; 28 | const engine = createEngine('my-save-key'); 29 | ``` 30 | 31 | You can customize the saving and loading process by providing a [`replacer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) and/or a [`reviver`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter). 32 | 33 | ```js 34 | import createEngine from 'redux-storage-engine-localstorage'; 35 | 36 | function replacer (key, value) { 37 | if (typeof value === 'string') { 38 | return 'foo'; 39 | } 40 | return value; 41 | } 42 | 43 | function reviver (key, value) { 44 | if (key === 'foo') { 45 | return 'bar'; 46 | } 47 | return value; 48 | }); 49 | 50 | const engine = createEngine('my-save-key', replacer, reviver); 51 | ``` 52 | 53 | **Warning**: `localStorage` does not expose a async API and every save/load 54 | operation will block the JS thread! 55 | 56 | **Warning**: Some browsers like IE<=11 does not support Promises! If you don't 57 | want to polyfill the whole Promise API, [redux-storage-engine-localstoragefakepromise][] 58 | might be a possible solution for you. 59 | 60 | ## License 61 | 62 | The MIT License (MIT) 63 | 64 | Copyright (c) 2015 Michael Contento 65 | 66 | Permission is hereby granted, free of charge, to any person obtaining a copy of 67 | this software and associated documentation files (the "Software"), to deal in 68 | the Software without restriction, including without limitation the rights to 69 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 70 | the Software, and to permit persons to whom the Software is furnished to do so, 71 | subject to the following conditions: 72 | 73 | The above copyright notice and this permission notice shall be included in all 74 | copies or substantial portions of the Software. 75 | 76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 77 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 78 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 79 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 80 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 81 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 82 | 83 | [redux-storage]: https://github.com/michaelcontento/redux-storage 84 | [redux-storage-engine-localstorage]: https://github.com/michaelcontento/redux-storage-engine-localstorage 85 | [redux-storage-engine-localstoragefakepromise]: https://github.com/michaelcontento/redux-storage-engine-localstoragefakepromise 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-storage-engine-localstorage", 3 | "version": "1.1.2", 4 | "description": "window.localStorage engine for redux-storage", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "test": "make lint test build" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/michaelcontento/redux-storage-engine-localstorage.git" 12 | }, 13 | "homepage": "https://github.com/michaelcontento/redux-storage-engine-localstorage", 14 | "keywords": [ 15 | "redux", 16 | "redux-storage", 17 | "redux-storage-engine", 18 | "localStorage" 19 | ], 20 | "author": "Michael Contento ", 21 | "files": [ 22 | "build/", 23 | "src", 24 | "!**/__tests__/**" 25 | ], 26 | "eslintConfig": { 27 | "extends": "michaelcontento" 28 | }, 29 | "license": "MIT", 30 | "devDependencies": { 31 | "babel-cli": "^6.4.0", 32 | "babel-core": "^6.4.0", 33 | "babel-polyfill": "^6.3.14", 34 | "babel-preset-modern-node": "^2.1.0", 35 | "babel-preset-stage-3": "^6.5.0", 36 | "eslint": "^1.10.3", 37 | "eslint-config-michaelcontento": "^1.1.1", 38 | "eslint-plugin-mocha-only": "0.0.3", 39 | "mocca": "^1.0.3", 40 | "release-it": "^2.3.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/__tests__/index-test.js: -------------------------------------------------------------------------------- 1 | import createEngine from '../'; 2 | 3 | describe('engine', () => { 4 | beforeEach(() => { 5 | global.localStorage = { 6 | getItem: sinon.stub().returns(null), 7 | setItem: sinon.stub() 8 | }; 9 | }); 10 | 11 | afterEach(() => { 12 | delete global.localStorage; 13 | }); 14 | 15 | describe('load', () => { 16 | it('should load via getItem', async () => { 17 | localStorage.getItem.returns('{"a":1}'); 18 | 19 | const engine = createEngine('key'); 20 | const result = await engine.load(); 21 | 22 | localStorage.getItem.should.have.been.called; 23 | result.should.deep.equal({ a: 1 }); 24 | }); 25 | 26 | it('should load with the given key', async () => { 27 | const engine = createEngine('key'); 28 | await engine.load(); 29 | 30 | localStorage.getItem.should.have.been.calledWith('key'); 31 | }); 32 | 33 | it('should fallback to empty dict', async () => { 34 | const engine = createEngine('key'); 35 | const result = await engine.load(); 36 | 37 | localStorage.getItem.should.have.been.called; 38 | result.should.be.a.dict; 39 | result.should.be.empty; 40 | }); 41 | 42 | it('should reject when localStorage is not present', () => { 43 | delete global.localStorage; 44 | 45 | const engine = createEngine('key'); 46 | return engine.load() 47 | .should.eventually.be.rejectedWith('localStorage is not defined'); 48 | }); 49 | 50 | it('should reject if json cannot be loaded', async () => { 51 | localStorage.getItem.returns('{"a'); 52 | const engine = createEngine('key'); 53 | 54 | return engine.load() 55 | .should.eventually.be.rejectedWith('Unexpected token a'); 56 | }); 57 | 58 | it('should use a provided reviver', async () => { 59 | const reviver = sinon.stub(); 60 | const engine = createEngine('key', null, reviver); 61 | 62 | await engine.load(); 63 | 64 | reviver.should.have.been.called; 65 | }); 66 | }); 67 | 68 | describe('save', () => { 69 | it('should asve via setItem', async () => { 70 | const engine = createEngine('key'); 71 | await engine.save({}); 72 | 73 | localStorage.setItem 74 | .should.have.been.called; 75 | }); 76 | 77 | it('should load with the given key', async () => { 78 | const engine = createEngine('key'); 79 | await engine.save({}); 80 | 81 | localStorage.setItem 82 | .should.have.been.calledWith('key'); 83 | }); 84 | 85 | it('should save the passed state as json', async () => { 86 | const engine = createEngine('key'); 87 | await engine.save({ a: 1 }); 88 | 89 | localStorage.setItem 90 | .should.have.been.calledWith(sinon.match.any, '{"a":1}'); 91 | }); 92 | 93 | it('should reject when localStorage is not present', () => { 94 | delete global.localStorage; 95 | 96 | const engine = createEngine('key'); 97 | return engine.save({}) 98 | .should.eventually.be.rejectedWith('localStorage is not defined'); 99 | }); 100 | 101 | it('should reject if json cannot be serialized', async () => { 102 | const engine = createEngine('key'); 103 | const a = {}; 104 | a.a = a; 105 | 106 | return engine.save(a) 107 | .should.eventually.be.rejectedWith('Converting circular structure to JSON'); 108 | }); 109 | 110 | it('should use a provided replacer', async () => { 111 | const replacer = sinon.stub(); 112 | const engine = createEngine('key', replacer); 113 | 114 | await engine.save({}); 115 | 116 | replacer.should.have.been.called; 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | function rejectWithMessage(error) { 2 | return Promise.reject(error.message); 3 | } 4 | 5 | export default (key, replacer, reviver) => ({ 6 | load() { 7 | return new Promise((resolve) => { 8 | const jsonState = localStorage.getItem(key); 9 | resolve(JSON.parse(jsonState, reviver) || {}); 10 | }).catch(rejectWithMessage); 11 | }, 12 | 13 | save(state) { 14 | return new Promise((resolve) => { 15 | const jsonState = JSON.stringify(state, replacer); 16 | localStorage.setItem(key, jsonState); 17 | resolve(); 18 | }).catch(rejectWithMessage); 19 | } 20 | }); 21 | --------------------------------------------------------------------------------