ngrx-store-localstorage
Advanced tools
Comparing version 0.1.7 to 0.1.8
export declare const dateReviver: (key: string, value: any) => any; | ||
export declare const rehydrateApplicationState: (keys: any[], storage: Storage) => any; | ||
export declare const syncStateUpdate: (state: any, keys: any[], storage: Storage, removeOnUndefined: boolean) => void; | ||
export declare const rehydrateApplicationState: (keys: any[], storage: Storage, storageKeySerializer: (key: string) => string) => any; | ||
export declare const syncStateUpdate: (state: any, keys: any[], storage: Storage, storageKeySerializer: (key: string) => string, removeOnUndefined: boolean) => void; | ||
export declare const localStorageSync: (config: LocalStorageConfig) => (reducer: any) => (state: any, action: any) => any; | ||
@@ -11,2 +11,3 @@ export declare const localStorageSyncAndClean: (keys: any[], rehydrate?: boolean, removeOnUndefined?: boolean) => (reducer: any) => any; | ||
removeOnUndefined?: boolean; | ||
storageKeySerializer?: (key: string) => string; | ||
} |
@@ -26,3 +26,3 @@ "use strict"; | ||
}; | ||
exports.rehydrateApplicationState = function (keys, storage) { | ||
exports.rehydrateApplicationState = function (keys, storage, storageKeySerializer) { | ||
return keys.reduce(function (acc, curr) { | ||
@@ -63,3 +63,3 @@ var key = curr; | ||
} | ||
var stateSlice = storage.getItem(key); | ||
var stateSlice = storage.getItem(storageKeySerializer(key)); | ||
if (stateSlice) { | ||
@@ -77,3 +77,3 @@ // Use provided decrypt function | ||
}; | ||
exports.syncStateUpdate = function (state, keys, storage, removeOnUndefined) { | ||
exports.syncStateUpdate = function (state, keys, storage, storageKeySerializer, removeOnUndefined) { | ||
keys.forEach(function (key) { | ||
@@ -132,3 +132,3 @@ var stateSlice = state[key]; | ||
} | ||
storage.setItem(key, typeof stateSlice === 'string' ? stateSlice : JSON.stringify(stateSlice, replacer, space)); | ||
storage.setItem(storageKeySerializer(key), typeof stateSlice === 'string' ? stateSlice : JSON.stringify(stateSlice, replacer, space)); | ||
} | ||
@@ -141,3 +141,3 @@ catch (e) { | ||
try { | ||
storage.removeItem(key); | ||
storage.removeItem(storageKeySerializer(key)); | ||
} | ||
@@ -154,4 +154,7 @@ catch (e) { | ||
} | ||
if (config.storageKeySerializer === undefined) { | ||
config.storageKeySerializer = function (key) { return key; }; | ||
} | ||
var stateKeys = validateStateKeys(config.keys); | ||
var rehydratedState = config.rehydrate ? exports.rehydrateApplicationState(stateKeys, config.storage) : undefined; | ||
var rehydratedState = config.rehydrate ? exports.rehydrateApplicationState(stateKeys, config.storage, config.storageKeySerializer) : undefined; | ||
return function (state, action) { | ||
@@ -167,3 +170,3 @@ if (state === void 0) { state = rehydratedState; } | ||
var nextState = reducer(state, action); | ||
exports.syncStateUpdate(nextState, stateKeys, config.storage, config.removeOnUndefined); | ||
exports.syncStateUpdate(nextState, stateKeys, config.storage, config.storageKeySerializer, config.removeOnUndefined); | ||
return nextState; | ||
@@ -170,0 +173,0 @@ }; |
{ | ||
"name": "ngrx-store-localstorage", | ||
"version": "0.1.7", | ||
"version": "0.1.8", | ||
"description": "State and local storage syncing for @ngrx/store", | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -13,3 +13,3 @@ # ngrx-store-localstorage | ||
2. Invoke the `localStorageSync` function after `combineReducers`, this receives a `LocalStorageConfig` object and assigns the property `keys` the slices of state you would like to keep synced with local storage. | ||
3. Optionally specify in the `LocalStorageConfig` whether to rehydrate this state from local storage as `initialState` on application bootstrap with the `rehydrateState` property. | ||
3. Optionally specify in the `LocalStorageConfig` whether to rehydrate this state from local storage as `initialState` on application bootstrap with the `rehydrate` property. | ||
4. Invoke composed function with application reducers as an argument to `StoreModule.provideStore`. | ||
@@ -45,3 +45,3 @@ ```ts | ||
### **LocalStorageConfig** | ||
An interface that holds the needed configuration attributes to bootstrap `localStorageSync`. The following are properties which compose the `LocalStorageConfig`: | ||
An interface defining the configuration attributes to bootstrap `localStorageSync`. The following are properties which compose `LocalStorageConfig`: | ||
* `keys` (required) State keys to sync with local storage. The keys can be defined in two different formats: | ||
@@ -70,5 +70,7 @@ * `string[]`: Array of strings representing the state (reducer) keys. Full state will be synced (e.g. `localStorageSync({keys: ['todos']})`). | ||
* `rehydrateState` (optional) `boolean`: Pull initial state from local storage on startup, this will default to `false`. | ||
* `rehydrate` (optional) `boolean`: Pull initial state from local storage on startup, this will default to `false`. | ||
* `storage` (optional) `Storage`: Specify an object that conforms to the Storage interface to use, this will default to `localStorage`. | ||
* `removeOnUndefined` (optional) `boolean`: Specify if the state is removed from the storage when the new value is undefined, this will default to `false`. | ||
* `storageKeySerializer` (optional) `(key: string) => string`: Сustom serialize function for storage keys, used to avoid Storage conflicts. | ||
Usage: `localStorageSync({keys: ['todos', 'visibilityFilter'], storageKeySerializer: (key) => 'cool_' + key, ... })`. In this example `Storage` will use keys `cool_todos` and `cool_visibilityFilter` keys to store `todos` and `visibilityFilter` slices of state). The key itself is used by default - `(key) => key`. | ||
@@ -75,0 +77,0 @@ --- |
declare var beforeEachProviders, it, describe, expect, inject; | ||
require('es6-shim'); | ||
require('reflect-metadata'); | ||
import { syncStateUpdate, rehydrateApplicationState, dateReviver } from '../src/index'; | ||
@@ -8,3 +7,3 @@ import * as CryptoJS from 'crypto-js'; | ||
// Very simple classes to test serialization options. They cover string, number, date, and nested classes | ||
// The top level class has static functions to help test reviver, replacer, serialize and deserialize | ||
// The top level class has static functions to help test reviver, replacer, serialize and deserialize | ||
class TypeB { | ||
@@ -69,6 +68,6 @@ constructor(public afield: string) { } | ||
public clear(): void { throw 'Not Implemented'; } | ||
public getItem(key: string): any { | ||
return this[key]; | ||
public getItem(key: string): string | null { | ||
return this[key] ? this[key] : null; | ||
} | ||
key(index: number): string { throw 'Not Implemented'; } | ||
key(index: number): string | null { throw 'Not Implemented'; } | ||
removeItem(key: string): void { this[key] = undefined; } | ||
@@ -82,2 +81,5 @@ setItem(key: string, data: string): void { | ||
function mockStorageKeySerializer(key) { return key; } | ||
describe('ngrxLocalStorage', () => { | ||
@@ -113,7 +115,8 @@ let t1 = new TypeA( | ||
// Since we're not specifiying anything for rehydration, the roundtrip | ||
// loses type information... | ||
// loses type information... | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
syncStateUpdate(initialState, ['state'], s, false); | ||
syncStateUpdate(initialState, ['state'], s, skr, false); | ||
@@ -123,3 +126,3 @@ let raw = s.getItem('state'); | ||
let finalState: any = rehydrateApplicationState(['state'], s); | ||
let finalState: any = rehydrateApplicationState(['state'], s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(initialStateJson); | ||
@@ -133,11 +136,12 @@ | ||
// Use the filter by field option to round-trip an object while | ||
// filtering out the anumber and adate filed | ||
// filtering out the anumber and adate filed | ||
// Since we're not specifiying anything for rehydration, the roundtrip | ||
// loses type information... | ||
// loses type information... | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { state: t1 }; | ||
let keys = [{ state: ['astring', 'aclass'] }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
@@ -147,3 +151,3 @@ let raw = s.getItem('state'); | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(JSON.stringify({ state: t1Filtered })); | ||
@@ -159,8 +163,9 @@ | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { state: t1 }; | ||
let keys = [{ state: TypeA.reviver }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(JSON.stringify(initialState)); | ||
@@ -175,8 +180,9 @@ expect(finalState.state instanceof TypeA).toBeTruthy(); | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { state: t1 }; | ||
let keys = [{ state: { reviver: TypeA.reviver } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(JSON.stringify(initialState)); | ||
@@ -189,9 +195,10 @@ expect(finalState.state instanceof TypeA).toBeTruthy(); | ||
// Use the filter by field option to round-trip an object while | ||
// filtering out the anumber and adate filed | ||
// filtering out the anumber and adate filed | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { filtered: t1 }; | ||
let keys = [{ filtered: { filter: ['astring', 'aclass'] } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
@@ -201,3 +208,3 @@ let raw = s.getItem('filtered'); | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(JSON.stringify({ filtered: t1Filtered })); | ||
@@ -215,8 +222,9 @@ | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { replacer: t1 }; | ||
let keys = [{ replacer: { reviver: TypeA.replacer } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(JSON.stringify({ replacer: t1Filtered })); | ||
@@ -234,6 +242,7 @@ | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { replacer: t1 }; | ||
let keys = [{ replacer: { replacer: ['astring', 'adate', 'anumber'], space: 2 } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
@@ -245,3 +254,3 @@ // We want to validate the space parameter, but don't want to trip up on OS specific newlines, so filter the newlines out and | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
@@ -258,8 +267,9 @@ expect(JSON.stringify(finalState)).toEqual('{"replacer":{"astring":"Testing","adate":"1968-11-16T12:30:00.000Z","anumber":3.14159}}'); | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { state: t1 }; | ||
let keys = [{ state: { serialize: TypeA.serialize, deserialize: TypeA.deserialize } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
let finalState: any = rehydrateApplicationState(keys, s); | ||
let finalState: any = rehydrateApplicationState(keys, s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(initialStateJson); | ||
@@ -273,3 +283,4 @@ expect(finalState.state instanceof TypeA).toBeTruthy(); | ||
let s = new MockStorage(); | ||
syncStateUpdate(initialState, ['state'], s, true); | ||
let skr = mockStorageKeySerializer; | ||
syncStateUpdate(initialState, ['state'], s, skr, true); | ||
@@ -281,3 +292,3 @@ // do update | ||
// ensure that it's erased | ||
syncStateUpdate(undefinedState, ['state'], s, true); | ||
syncStateUpdate(undefinedState, ['state'], s, skr, true); | ||
raw = s.getItem('state'); | ||
@@ -290,3 +301,4 @@ expect(raw).toBeFalsy(); | ||
let s = new MockStorage(); | ||
syncStateUpdate(initialState, ['state'], s, false); | ||
let skr = mockStorageKeySerializer; | ||
syncStateUpdate(initialState, ['state'], s, skr, false); | ||
@@ -298,3 +310,3 @@ // do update | ||
// test update doesn't erase when it's undefined | ||
syncStateUpdate(undefinedState, ['state'], s, false); | ||
syncStateUpdate(undefinedState, ['state'], s, skr, false); | ||
raw = s.getItem('state'); | ||
@@ -306,12 +318,13 @@ expect(raw).toEqual(t1Json); | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { state: t1 }; | ||
let keys = [{ state: { encrypt: TypeC.encrypt, decrypt: TypeC.decrypt } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
// Decript stored value and compare with the on-memory state | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
// Decript stored value and compare with the on-memory state | ||
let raw = s.getItem('state'); | ||
expect(TypeC.decrypt(raw)).toEqual(JSON.stringify(initialState.state)); | ||
// Retrieve the stored state with the rehydrateApplicationState function and | ||
let storedState = rehydrateApplicationState(keys, s); | ||
// Retrieve the stored state with the rehydrateApplicationState function and | ||
let storedState = rehydrateApplicationState(keys, s, skr); | ||
expect(initialStateJson).toEqual(JSON.stringify(storedState)); | ||
@@ -322,2 +335,3 @@ }); | ||
let s = new MockStorage(); | ||
let skr = mockStorageKeySerializer; | ||
let initialState = { state: t1 }; | ||
@@ -327,4 +341,4 @@ let keys; | ||
syncStateUpdate(initialState, keys, s, false); | ||
// Stored value must not be encripted due to decrypt function is not present, so must be equal to the on-memory state | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
// Stored value must not be encripted due to decrypt function is not present, so must be equal to the on-memory state | ||
let raw = s.getItem('state'); | ||
@@ -335,6 +349,23 @@ expect(raw).toEqual(JSON.stringify(initialState.state)); | ||
keys = [{ state: { decrypt: TypeC.decrypt } }]; | ||
syncStateUpdate(initialState, keys, s, false); | ||
syncStateUpdate(initialState, keys, s, skr, false); | ||
raw = s.getItem('state'); | ||
expect(raw).toEqual(JSON.stringify(initialState.state)); | ||
}); | ||
it('storageKeySerializer', () => { | ||
// This tests that storage key serializer are working. | ||
let s = new MockStorage(); | ||
let skr = (key) => `this_key` + key; | ||
console.log(skr('a')); | ||
syncStateUpdate(initialState, ['state'], s, skr, false); | ||
let raw = s.getItem('1232342'); | ||
expect(raw).toBeNull(); | ||
let finalState: any = rehydrateApplicationState(['state'], s, skr); | ||
expect(JSON.stringify(finalState)).toEqual(initialStateJson); | ||
expect(t1 instanceof TypeA).toBeTruthy(); | ||
expect(finalState.simple instanceof TypeA).toBeFalsy(); | ||
}); | ||
}); |
@@ -17,3 +17,3 @@ const INIT_ACTION = '@ngrx/store/init'; | ||
if (typeof key === 'object') { | ||
attr = Object.keys(key)[0]; | ||
attr = Object.keys(key)[0]; | ||
} | ||
@@ -31,3 +31,3 @@ | ||
export const rehydrateApplicationState = (keys: any[], storage: Storage) => { | ||
export const rehydrateApplicationState = (keys: any[], storage: Storage, storageKeySerializer: (key: string) => string) => { | ||
return keys.reduce((acc, curr) => { | ||
@@ -40,32 +40,32 @@ let key = curr; | ||
if (typeof key === 'object') { | ||
key = Object.keys(key)[0]; | ||
// use the custom reviver function | ||
if (typeof curr[key] === 'function') { | ||
reviver = curr[key]; | ||
} | ||
else { | ||
// use custom reviver function if available | ||
if (curr[key].reviver) { | ||
reviver = curr[key].reviver; | ||
} | ||
// use custom serialize function if available | ||
if (curr[key].deserialize) { | ||
deserialize = curr[key].deserialize; | ||
} | ||
} | ||
key = Object.keys(key)[0]; | ||
// use the custom reviver function | ||
if (typeof curr[key] === 'function') { | ||
reviver = curr[key]; | ||
} | ||
else { | ||
// use custom reviver function if available | ||
if (curr[key].reviver) { | ||
reviver = curr[key].reviver; | ||
} | ||
// use custom serialize function if available | ||
if (curr[key].deserialize) { | ||
deserialize = curr[key].deserialize; | ||
} | ||
} | ||
// Ensure that encrypt and decrypt functions are both presents | ||
if (curr[key].encrypt && curr[key].decrypt) { | ||
if (typeof (curr[key].encrypt) === 'function' && typeof (curr[key].decrypt) === 'function') { | ||
decrypt = curr[key].decrypt; | ||
} else { | ||
console.error(`Either encrypt or decrypt is not a function on '${curr[key]}' key object.`); | ||
} | ||
} else if (curr[key].encrypt || curr[key].decrypt) { | ||
// Let know that one of the encryption functions is not provided | ||
console.error(`Either encrypt or decrypt function is not present on '${curr[key]}' key object.`); | ||
} | ||
// Ensure that encrypt and decrypt functions are both presents | ||
if (curr[key].encrypt && curr[key].decrypt) { | ||
if (typeof (curr[key].encrypt) === 'function' && typeof (curr[key].decrypt) === 'function') { | ||
decrypt = curr[key].decrypt; | ||
} else { | ||
console.error(`Either encrypt or decrypt is not a function on '${curr[key]}' key object.`); | ||
} | ||
} else if (curr[key].encrypt || curr[key].decrypt) { | ||
// Let know that one of the encryption functions is not provided | ||
console.error(`Either encrypt or decrypt function is not present on '${curr[key]}' key object.`); | ||
} | ||
} | ||
let stateSlice = storage.getItem(key); | ||
let stateSlice = storage.getItem(storageKeySerializer(key)); | ||
if (stateSlice) { | ||
@@ -83,3 +83,3 @@ // Use provided decrypt function | ||
export const syncStateUpdate = (state: any, keys: any[], storage: Storage, removeOnUndefined: boolean) => { | ||
export const syncStateUpdate = (state: any, keys: any[], storage: Storage, storageKeySerializer: (key: string) => string, removeOnUndefined: boolean) => { | ||
keys.forEach(key => { | ||
@@ -139,3 +139,3 @@ | ||
if (typeof(stateSlice) !== 'undefined') { | ||
if (typeof (stateSlice) !== 'undefined') { | ||
try { | ||
@@ -146,13 +146,13 @@ if (encrypt) { | ||
} | ||
storage.setItem(key, typeof stateSlice === 'string' ? stateSlice : JSON.stringify(stateSlice, replacer, space)); | ||
storage.setItem(storageKeySerializer(key), typeof stateSlice === 'string' ? stateSlice : JSON.stringify(stateSlice, replacer, space)); | ||
} catch (e) { | ||
console.warn('Unable to save state to localStorage:', e); | ||
} | ||
} else if (typeof(stateSlice) === 'undefined' && removeOnUndefined) { | ||
} else if (typeof (stateSlice) === 'undefined' && removeOnUndefined) { | ||
try { | ||
storage.removeItem(key); | ||
storage.removeItem(storageKeySerializer(key)); | ||
} catch (e) { | ||
console.warn(`Exception on removing/cleaning undefined '${key}' state`, e); | ||
} | ||
} | ||
} | ||
}); | ||
@@ -167,4 +167,8 @@ }; | ||
if (config.storageKeySerializer === undefined) { | ||
config.storageKeySerializer = (key) => key; | ||
} | ||
const stateKeys = validateStateKeys(config.keys); | ||
const rehydratedState = config.rehydrate ? rehydrateApplicationState(stateKeys, config.storage) : undefined; | ||
const rehydratedState = config.rehydrate ? rehydrateApplicationState(stateKeys, config.storage, config.storageKeySerializer) : undefined; | ||
@@ -180,3 +184,3 @@ return function (state = rehydratedState, action: any) { | ||
const nextState = reducer(state, action); | ||
syncStateUpdate(nextState, stateKeys, config.storage, config.removeOnUndefined); | ||
syncStateUpdate(nextState, stateKeys, config.storage, config.storageKeySerializer, config.removeOnUndefined); | ||
return nextState; | ||
@@ -210,2 +214,3 @@ }; | ||
removeOnUndefined?: boolean; | ||
storageKeySerializer?: (key: string) => string; | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50975
756
106