Skip to content

Commit

Permalink
Merge pull request #23 from smithki/development
Browse files Browse the repository at this point in the history
Release v1.0.0
  • Loading branch information
smithki authored Oct 1, 2019
2 parents d1b55f7 + 9705460 commit 10c91cc
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 8 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "storage-proxy",
"version": "0.11.3",
"version": "1.0.0",
"description": "Use web storage (localStorage/sessionStorage) just like plain objects using ES6 Proxies.",
"author": "Ian K Smith <[email protected]>",
"license": "MIT",
Expand Down Expand Up @@ -36,6 +36,7 @@
"devDependencies": {
"@ikscodes/tslint-config": "^5.3.1",
"alsatian": "^2.4.0",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"microbundle": "^0.8.4",
"mock-browser": "^0.92.14",
Expand Down
42 changes: 37 additions & 5 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import onChange from 'on-change';
const namespaceSymbol = Symbol('namespaceSymbol');
const isStorageProxy = Symbol('isStorageProxy');
const storageTargetSymbol = Symbol('storageTargetSymbol');
const defaultValuesSymbol = Symbol('defaultValuesSymbol');
const storageProxyIntegrityKey = '__storageProxyIntegrity';

/** Web storage targets: `localStorage` and `sessionStorage`. */
Expand All @@ -20,6 +21,7 @@ export type StorageProxy<TStorageDefinitions> = Partial<TStorageDefinitions> & {
readonly [namespaceSymbol]: string;
readonly [isStorageProxy]: true;
readonly [storageTargetSymbol]: StorageTarget;
readonly [defaultValuesSymbol]: Partial<TStorageDefinitions>;
[storageProxyIntegrityKey]: string;
};

Expand All @@ -32,10 +34,23 @@ export type StorageProxy<TStorageDefinitions> = Partial<TStorageDefinitions> & {
*
* @return Returns true if value is undefined, else false.
*/
export function isUndefined(value: any): value is undefined {
function isUndefined(value: any): value is undefined {
return value === undefined;
}

/**
* Asserts that the given argument is a valid `StorageProxy` object, otherwise
* raising an error.
*
* @param value - Any value to test for validity as a `StorageProxy` object.
*/
function enforceStorageProxy(value?: any) {
// Argument must be a `StorageProxy` object.
if (!value || !value[isStorageProxy]) {
throw new TypeError('[storage-proxy] Supplied argument is not a `StorageProxy` object.');
}
}

/**
* Initializes the web storage interface. If no storage exists, we save an empty
* object.
Expand Down Expand Up @@ -103,6 +118,8 @@ function createProxy<TStorageDefinitions extends any>(
storageProxy[key] = value;
}
}

storageProxy[defaultValuesSymbol] = defaults;
}

return storageProxy;
Expand Down Expand Up @@ -156,10 +173,7 @@ export const StorageProxy = {
* @return `boolean` indicating whether the cache integrity is sound.
*/
verifyCache<TStorageProxy extends StorageProxy<any>>(storageProxy: TStorageProxy, seed: string) {
// Argument must be a `StorageProxy` object.
if (!storageProxy[isStorageProxy]) {
throw new Error('[storage-proxy] Provided argument is not a `StorageProxy` object.');
}
enforceStorageProxy(storageProxy);

// Get a seed from the raw web storage data and decode it.
const data = getDataFromStorage(storageProxy[namespaceSymbol], storageProxy[storageTargetSymbol]);
Expand All @@ -182,8 +196,26 @@ export const StorageProxy = {
* @param storageProxy - The storage proxy object to clear.
*/
clearStorage<TStorageProxy extends StorageProxy<any>>(storageProxy: TStorageProxy) {
enforceStorageProxy(storageProxy);

for (const key of Object.keys(storageProxy)) {
delete storageProxy[key];
}
},

/**
* Restores the default values given to `StorageProxy.createLocalStorage()`
* and `StorageProxy.createSessionStorage()`. However, unlike when the
* `StorageProxy` was initially created, this function privelages the default
* values _over_ what is currently in `WebStorage`.
*
* @param storageProxy - The storage proxy object to restore to a default state.
*/
restoreDefaults<TStorageProxy extends StorageProxy<any>>(storageProxy: TStorageProxy) {
enforceStorageProxy(storageProxy);

for (const [key, value] of Object.entries(storageProxy[defaultValuesSymbol])) {
(storageProxy as any)[key] = value;
}
},
};
3 changes: 3 additions & 0 deletions test/src/mocks/browser.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import atob from 'atob';
import btoa from 'btoa';
import { mocks } from 'mock-browser';

Expand All @@ -10,6 +11,8 @@ const mb = new mocks.MockBrowser();
(global as any).window = mb.getWindow();
(global as any).btoa = btoa;
(global as any).window.btoa = btoa;
(global as any).atob = atob;
(global as any).window.atob = atob;
(global as any).location = mb.getLocation();
(global as any).navigator = mb.getNavigator();
(global as any).history = mb.getHistory();
Expand Down
1 change: 1 addition & 0 deletions test/src/shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
declare module 'mock-browser';
declare module 'btoa';
declare module 'atob';
46 changes: 44 additions & 2 deletions test/src/storage-proxy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const testStr = 'hello world';
const testObj = { monty: 'python', numbers: [1, 2, 3] };
const testArr = [1, 2, 3];

const storageProxyIntegrityKey = '__storageProxyIntegrity';

interface TestStorage {
bar: string;
baz: typeof testObj;
Expand Down Expand Up @@ -43,8 +45,8 @@ function getItem(storageTarget: StorageTarget, path: string) {

@TestFixture('StorageProxy Tests')
export class StorageProxyTestFixture {
lStore: Partial<TestStorage>;
sStore: Partial<TestStorage>;
lStore: StorageProxy<TestStorage>;
sStore: StorageProxy<TestStorage>;

@SetupFixture
public setupFixture() {
Expand Down Expand Up @@ -79,6 +81,12 @@ export class StorageProxyTestFixture {
Expect(this.lStore.alreadySetDefault).toEqual(999);
}

@Test('Restoring defaults privelages default values over `WebStorage` values.')
public restoreDefaultsTest() {
StorageProxy.restoreDefaults(this.lStore);
Expect(this.lStore.alreadySetDefault).toEqual(123);
}

@Test('Set `localStorage` key')
public setLocalStorageKeyTest() {
this.lStore.fizz = 123;
Expand All @@ -105,6 +113,21 @@ export class StorageProxyTestFixture {
Expect(data).toEqual(testStr);
}

@Test('Verify caching helper works')
public verifyCacheTest() {
Expect(getItem(StorageTarget.Local, storageProxyIntegrityKey)).not.toBeDefined();

const shouldBeTrue = StorageProxy.verifyCache(this.lStore, 'very seedy');
Expect(getItem(StorageTarget.Local, storageProxyIntegrityKey)).toBeDefined();
Expect(shouldBeTrue).toBeTruthy();

const shouldAlsoBeTrue = StorageProxy.verifyCache(this.lStore, 'very seedy');
Expect(shouldAlsoBeTrue).toBeTruthy();

const shouldBeFalse = StorageProxy.verifyCache(this.lStore, 'even seedier');
Expect(shouldBeFalse).not.toBeTruthy();
}

@Test('Validate `Array.prototype.push`')
public arrayPushTest() {
this.lStore.baz!.numbers.push(4, 5, 6);
Expand Down Expand Up @@ -174,4 +197,23 @@ export class StorageProxyTestFixture {
Expect(getItem(StorageTarget.Local, 'baz.numbers')).toEqual(expected);
Expect(getItem(StorageTarget.Local, 'arrayValue')).toEqual(expected);
}

@Test('Clearing `StorageProxy` removes all keys from both `WebStorage` and the local object')
public clearStorageTest() {
StorageProxy.clearStorage(this.lStore);

Expect(this.lStore.alreadySetDefault).toBeNull();
Expect(this.lStore.arrayValue).toBeNull();
Expect(this.lStore.bar).toBeNull();
Expect(this.lStore.baz).toBeNull();
Expect(this.lStore.defaults).toBeNull();
Expect(this.lStore.fizz).toBeNull();

Expect(getItem(StorageTarget.Local, 'alreadySetDefault')).not.toBeDefined();
Expect(getItem(StorageTarget.Local, 'arrayValue')).not.toBeDefined();
Expect(getItem(StorageTarget.Local, 'bar')).not.toBeDefined();
Expect(getItem(StorageTarget.Local, 'baz')).not.toBeDefined();
Expect(getItem(StorageTarget.Local, 'defaults')).not.toBeDefined();
Expect(getItem(StorageTarget.Local, 'fizz')).not.toBeDefined();
}
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,11 @@ asyncro@^3.0.0:
resolved "https://registry.yarnpkg.com/asyncro/-/asyncro-3.0.0.tgz#3c7a732e263bc4a42499042f48d7d858e9c0134e"
integrity sha512-nEnWYfrBmA3taTiuiOoZYmgJ/CNrSoQLeLs29SeLcPu60yaw/mHDBHV0iOZ051fTvsTHxpCY+gXibqT9wbQYfg==

atob@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==

autoprefixer@^6.3.1:
version "6.7.7"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
Expand Down

0 comments on commit 10c91cc

Please sign in to comment.