@bizjournals/js-storage 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +15 -0
- package/README.md +96 -0
- package/jest.config.cjs +3 -0
- package/lib/abstract/__tests__/storage.clear.spec.js +0 -5
- package/lib/abstract/__tests__/storage.constructor.spec.js +1 -7
- package/lib/abstract/__tests__/storage.getitem.spec.js +16 -7
- package/lib/abstract/__tests__/storage.hasitem.spec.js +6 -0
- package/lib/abstract/__tests__/storage.issupported.spec.js +1 -3
- package/lib/abstract/__tests__/storage.parser.spec.js +7 -3
- package/lib/abstract/__tests__/storage.removeitem.spec.js +12 -21
- package/lib/abstract/__tests__/storage.setitem.spec.js +4 -31
- package/lib/abstract/__tests__/storage.translator.spec.js +0 -1
- package/lib/abstract/index.js +75 -107
- package/package.json +6 -6
- package/lib/abstract/__tests__/storage.resetattempts.spec.js +0 -20
- /package/{babel.config.js → babel.config.cjs} +0 -0
package/.gitlab-ci.yml
ADDED
package/README.md
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Storage
|
|
2
|
+
This bundle has helper classes to interact with browser storage options. At present we
|
|
3
|
+
support `localstorage` and `sessionstorage`. These share a common abstract parent.
|
|
4
|
+
|
|
5
|
+
Storage references are critical to the implementation of our architecture. A reference
|
|
6
|
+
is a named key that contains your data in the selected storage type.
|
|
7
|
+
|
|
8
|
+
## Installing
|
|
9
|
+
|
|
10
|
+
Using npm:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @bizjournals/vue-core
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Using yarn:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
yarn add @bizjournals/vue-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Configuring your environment:**
|
|
23
|
+
|
|
24
|
+
You will need to configure an `NPM_TOKEN` within your project to include these
|
|
25
|
+
private modules. The best idea is to use an existing project as an example.
|
|
26
|
+
|
|
27
|
+
_For simple setups the .gitlab-ci.yml file within this project as a guide to accessing
|
|
28
|
+
project variables via the .npmrc file_
|
|
29
|
+
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { StorageReference, LocalStorage, SessionStorage } from "@bizjournals/js-storage";
|
|
34
|
+
|
|
35
|
+
// This will setup a reference that is namespaced to bizj
|
|
36
|
+
// The full key in localstorage will be bizj.mykey
|
|
37
|
+
// This key will have an expiration partner that by default will be one hour
|
|
38
|
+
const something = new StorageReference('mykey', new LocalStorage());
|
|
39
|
+
|
|
40
|
+
something.saveAs({
|
|
41
|
+
my: 'data'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// local storage has a configurable API as well, and this can be used in storage
|
|
45
|
+
// reference objects too.
|
|
46
|
+
const local = new LocalStorage({
|
|
47
|
+
// by changing the namespace what was bizj by default will now be notbizj
|
|
48
|
+
// Example: notbizj.mykey
|
|
49
|
+
namespace: 'notbizj',
|
|
50
|
+
expires: 3600 // one hour
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// session storage uses the same api as local storage
|
|
55
|
+
const somethingElse = new StorageReference('difkey', new SessionStorage({
|
|
56
|
+
namespace: 'woa'
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const somethingElsesData = somethingElse.obtain();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Storage Reference
|
|
63
|
+
A storage reference is a keyed object that exists within either local or session storage.
|
|
64
|
+
It may be prefixed with a namespace since of the respective storage container.
|
|
65
|
+
|
|
66
|
+
### API
|
|
67
|
+
|
|
68
|
+
**constructor**(cacheKey : String, storageSystem : StorageAbstract)
|
|
69
|
+
**obtain**()
|
|
70
|
+
**bust**()
|
|
71
|
+
**saveAs**(data : Mixed)
|
|
72
|
+
|
|
73
|
+
## Storage Abstract
|
|
74
|
+
The parent class for both local and session storage creates the API used by each of them.
|
|
75
|
+
|
|
76
|
+
### API
|
|
77
|
+
|
|
78
|
+
**constructor**(options : Object : { namespace: String, expires: Number })
|
|
79
|
+
**getItem**(key: String)
|
|
80
|
+
**hasItem**(key : String)
|
|
81
|
+
**setItem**(key : String, value : Mixed, expires = true : Boolean)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### Reference
|
|
85
|
+
|
|
86
|
+
#### getItem
|
|
87
|
+
Gets an item with a specified key. The namespace set in the construction of the object will
|
|
88
|
+
be pre-fixed to the beginning of the string, by default this is `bizj`.
|
|
89
|
+
|
|
90
|
+
#### hasItem
|
|
91
|
+
This checks to see if the item is available. If it is it'll cache it, so that if you need to
|
|
92
|
+
get the item later you don't need to worry about additional reads.
|
|
93
|
+
|
|
94
|
+
#### setItem
|
|
95
|
+
Sets the item in storage and updates the item in cache. It will automatically prefix itself
|
|
96
|
+
with the namespace, by default `bizj`.
|
package/jest.config.cjs
ADDED
|
@@ -14,11 +14,6 @@ describe('core:class >> storageabstract:clear', () => {
|
|
|
14
14
|
}).not.toThrow();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it('always returns `this`', () => {
|
|
18
|
-
const test = storageAbstract.clear();
|
|
19
|
-
expect(test).toEqual(storageAbstract);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
17
|
it('runs the clear method on the storage system when defined', () => {
|
|
23
18
|
storageAbstract.storage = {
|
|
24
19
|
clear: jest.fn()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import StorageAbstract from '../';
|
|
2
2
|
import { DEFAULT_EXPIRATION, DEFAULT_NAMESPACE } from '../constants';
|
|
3
|
-
import { NAMESPACE_MUST_BE_STRING, EXPIRATION_MUST_BE_POSITIVE_INTEGER } from '../exceptions';
|
|
4
3
|
|
|
5
4
|
describe('core:class >> storageabstract:constructor', () => {
|
|
6
5
|
it('defaults to constant as it\'s namespace when none provided', () => {
|
|
@@ -25,7 +24,7 @@ describe('core:class >> storageabstract:constructor', () => {
|
|
|
25
24
|
for (const namespace of namespaces) {
|
|
26
25
|
expect(() => {
|
|
27
26
|
new StorageAbstract({ namespace });
|
|
28
|
-
}).toThrow(
|
|
27
|
+
}).toThrow();
|
|
29
28
|
}
|
|
30
29
|
});
|
|
31
30
|
|
|
@@ -40,11 +39,6 @@ describe('core:class >> storageabstract:constructor', () => {
|
|
|
40
39
|
expect(storageAbstract.expires).toEqual(expires);
|
|
41
40
|
});
|
|
42
41
|
|
|
43
|
-
it('defaults the number of attempts to zero', () => {
|
|
44
|
-
const storageAbstract = new StorageAbstract();
|
|
45
|
-
expect(storageAbstract.attempts).toEqual(0);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
42
|
it('sets the storage to a default of null', () => {
|
|
49
43
|
const storageAbstract = new StorageAbstract();
|
|
50
44
|
expect(storageAbstract.storage).toEqual({});
|
|
@@ -19,19 +19,20 @@ describe('core:class >> storageabstract:getitem', () => {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
storageClassMock.storage = mockStorage;
|
|
22
|
+
StorageAbstract.supported = undefined;
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
it('returns
|
|
25
|
+
it('returns undefined when it is not supported', () => {
|
|
25
26
|
storageClassMock.isSupported = jest.fn().mockImplementation(() => false);
|
|
26
27
|
|
|
27
|
-
expect(storageClassMock.getItem()).toEqual(
|
|
28
|
+
expect(storageClassMock.getItem()).toEqual(undefined);
|
|
28
29
|
expect(storageClassMock.namespaceKey).not.toHaveBeenCalled();
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
it('returns
|
|
32
|
+
it('returns undefined when the item is expired', () => {
|
|
32
33
|
storageClassMock.isItemExpired = jest.fn().mockImplementation(() => true);
|
|
33
34
|
|
|
34
|
-
expect(storageClassMock.getItem()).toEqual(
|
|
35
|
+
expect(storageClassMock.getItem()).toEqual(undefined);
|
|
35
36
|
expect(storageClassMock.namespaceKey).not.toHaveBeenCalled();
|
|
36
37
|
});
|
|
37
38
|
|
|
@@ -64,8 +65,16 @@ describe('core:class >> storageabstract:getitem', () => {
|
|
|
64
65
|
expect(storageClassMock.parser).toHaveBeenCalledWith(data);
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
it('
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
it('does not call the `parser` method with the data fetched from suspend or getItem', () => {
|
|
69
|
+
const data = { hey: 'you' };
|
|
70
|
+
|
|
71
|
+
const localStorage = {
|
|
72
|
+
getItem: jest.fn().mockImplementation(() => data)
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
storageClassMock.storage = localStorage;
|
|
76
|
+
|
|
77
|
+
storageClassMock.getItem(key, false);
|
|
78
|
+
expect(storageClassMock.parser).not.toHaveBeenCalled();
|
|
70
79
|
});
|
|
71
80
|
});
|
|
@@ -16,4 +16,10 @@ describe('core:class >> storageabstract:hasitem', () => {
|
|
|
16
16
|
|
|
17
17
|
expect(storageClassMock.getItem).toHaveBeenCalledTimes(1);
|
|
18
18
|
});
|
|
19
|
+
|
|
20
|
+
it('returns false when the obtained item is undefined', () => {
|
|
21
|
+
storageClassMock.getItem = jest.fn().mockImplementation(() => undefined);
|
|
22
|
+
|
|
23
|
+
expect(storageClassMock.hasItem(key)).toBeFalsy();
|
|
24
|
+
});
|
|
19
25
|
});
|
|
@@ -9,10 +9,8 @@ describe('core:class >> storageabstract:issupported', () => {
|
|
|
9
9
|
mockStorage.setItem.mockClear();
|
|
10
10
|
mockStorage.removeItem.mockClear();
|
|
11
11
|
|
|
12
|
-
if (mockStorage._supported) {
|
|
13
|
-
delete mockStorage._supported;
|
|
14
|
-
}
|
|
15
12
|
storageAbstract.storage = mockStorage;
|
|
13
|
+
StorageAbstract.supported = undefined;
|
|
16
14
|
});
|
|
17
15
|
|
|
18
16
|
it('returns false when the storage system is defaulted', () => {
|
|
@@ -27,10 +27,14 @@ describe('core:class >> storageabstract:parser', () => {
|
|
|
27
27
|
expect(JSON.parse).toHaveBeenCalledTimes(1);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it('
|
|
30
|
+
it('returns the provided string if it could not be JSON parsed', () => {
|
|
31
31
|
JSON.parse = copyParse;
|
|
32
|
+
let response;
|
|
33
|
+
|
|
32
34
|
expect(() => {
|
|
33
|
-
storageAbstract.parser('wowowow');
|
|
34
|
-
}).toThrow();
|
|
35
|
+
response = storageAbstract.parser('wowowow');
|
|
36
|
+
}).not.toThrow();
|
|
37
|
+
|
|
38
|
+
expect(response).toEqual('wowowow');
|
|
35
39
|
});
|
|
36
40
|
});
|
|
@@ -19,28 +19,22 @@ describe('core:class >> storageabstract:removeitem', () => {
|
|
|
19
19
|
removeItem: jest.fn()
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
storageClassMock.attempts = 0;
|
|
23
22
|
storageClassMock.storage = mockStorage;
|
|
23
|
+
StorageAbstract.supported = undefined;
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('
|
|
26
|
+
it('halts when it is not supported', () => {
|
|
27
27
|
storageClassMock.isSupported = jest.fn().mockImplementation(() => false);
|
|
28
|
-
storageClassMock.removeItem(key);
|
|
29
|
-
|
|
30
|
-
expect(storageClassMock.resetAttempts).toHaveBeenCalledTimes(1);
|
|
31
|
-
expect(storageClassMock.storage.removeItem).not.toHaveBeenCalled();
|
|
32
|
-
});
|
|
33
28
|
|
|
34
|
-
it('resets the attempts and halts when attempts are >= 2', () => {
|
|
35
|
-
storageClassMock.attempts = 2;
|
|
36
29
|
storageClassMock.removeItem(key);
|
|
37
30
|
|
|
38
|
-
expect(storageClassMock.
|
|
31
|
+
expect(storageClassMock.isSupported).toHaveBeenCalledTimes(1);
|
|
39
32
|
expect(storageClassMock.storage.removeItem).not.toHaveBeenCalled();
|
|
40
|
-
|
|
41
33
|
});
|
|
42
34
|
|
|
43
|
-
it('
|
|
35
|
+
it('falls through when encountering an exception', () => {
|
|
36
|
+
StorageAbstract.supported = true;
|
|
37
|
+
|
|
44
38
|
const badStorage = {
|
|
45
39
|
removeItem: jest.fn().mockImplementation(() => {
|
|
46
40
|
throw new Error('testing code');
|
|
@@ -48,23 +42,20 @@ describe('core:class >> storageabstract:removeitem', () => {
|
|
|
48
42
|
};
|
|
49
43
|
|
|
50
44
|
storageClassMock.storage = badStorage;
|
|
51
|
-
storageClassMock.removeItem();
|
|
52
45
|
|
|
53
|
-
expect(
|
|
46
|
+
expect(() => {
|
|
47
|
+
storageClassMock.removeItem();
|
|
48
|
+
}).not.toThrow();
|
|
49
|
+
|
|
50
|
+
expect(storageClassMock.storage.removeItem).toHaveBeenCalledTimes(1);
|
|
54
51
|
});
|
|
55
52
|
|
|
56
53
|
it('removes both the item at namespace and expiration keys', () => {
|
|
54
|
+
StorageAbstract.supported = true;
|
|
57
55
|
storageClassMock.removeItem();
|
|
58
56
|
|
|
59
57
|
expect(storageClassMock.namespaceKey).toHaveBeenCalledTimes(1);
|
|
60
58
|
expect(storageClassMock.expirationKey).toHaveBeenCalledTimes(1);
|
|
61
59
|
expect(storageClassMock.storage.removeItem).toHaveBeenCalledTimes(2);
|
|
62
60
|
});
|
|
63
|
-
|
|
64
|
-
it('resets attempts to zero on successful return', () => {
|
|
65
|
-
storageClassMock.removeItem();
|
|
66
|
-
|
|
67
|
-
expect(storageClassMock.storage.removeItem).toHaveBeenCalledTimes(2);
|
|
68
|
-
expect(storageClassMock.resetAttempts).toHaveBeenCalledTimes(1);
|
|
69
|
-
});
|
|
70
61
|
});
|
|
@@ -26,22 +26,16 @@ describe('core:class >> storageabstract:setitem', () => {
|
|
|
26
26
|
|
|
27
27
|
storageClassMock.attempts = 0;
|
|
28
28
|
storageClassMock.storage = mockStorage;
|
|
29
|
+
StorageAbstract.supported = undefined;
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
it('resets the attempts and returns when it is not supported without continuing', () => {
|
|
32
33
|
storageClassMock.isSupported = jest.fn().mockImplementation(() => false);
|
|
33
34
|
storageClassMock.setItem();
|
|
34
35
|
|
|
35
|
-
expect(storageClassMock.
|
|
36
|
-
expect(storageClassMock.namespaceKey).not.toHaveBeenCalled();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('resets the attempts and returns when attempts is >= 2 without continuing', () => {
|
|
40
|
-
storageClassMock.attempts = 3;
|
|
41
|
-
storageClassMock.setItem();
|
|
42
|
-
|
|
43
|
-
expect(storageClassMock.resetAttempts).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(storageClassMock.isSupported).toHaveBeenCalledTimes(1);
|
|
44
37
|
expect(storageClassMock.namespaceKey).not.toHaveBeenCalled();
|
|
38
|
+
expect(mockStorage.setItem).not.toHaveBeenCalled();
|
|
45
39
|
});
|
|
46
40
|
|
|
47
41
|
it('uses a dynamically generated namespaceKey to set the item', () => {
|
|
@@ -59,7 +53,7 @@ describe('core:class >> storageabstract:setitem', () => {
|
|
|
59
53
|
expect(storageClassMock.storage.setItem).not.toHaveBeenCalled();
|
|
60
54
|
});
|
|
61
55
|
|
|
62
|
-
it('calls to set the item one time when not
|
|
56
|
+
it('calls to set the item one time when not expiring', () => {
|
|
63
57
|
const data = { it: true };
|
|
64
58
|
|
|
65
59
|
storageClassMock.setItem(key, data, false);
|
|
@@ -77,25 +71,4 @@ describe('core:class >> storageabstract:setitem', () => {
|
|
|
77
71
|
expect(storageClassMock.storage.setItem).toHaveBeenCalledTimes(2);
|
|
78
72
|
expect(storageClassMock.expirationKey).toHaveBeenCalledTimes(1);
|
|
79
73
|
});
|
|
80
|
-
|
|
81
|
-
it('calls to cache the data with the key', () => {
|
|
82
|
-
const data = { it: true };
|
|
83
|
-
|
|
84
|
-
storageClassMock.setItem(key, data);
|
|
85
|
-
expect(storageClassMock.cache).toHaveBeenCalledTimes(1);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('calls clear when there is an error and attempts up to 2 times', () => {
|
|
89
|
-
storageClassMock.translator = jest.fn().mockImplementation(() => {
|
|
90
|
-
throw new Error('testing script error');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
storageClassMock.setItem(key, { data: true });
|
|
94
|
-
expect(storageClassMock.translator).toHaveBeenCalledTimes(2);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('calls to reset attempts to zero once completed', () => {
|
|
98
|
-
storageClassMock.setItem(key, { data: true });
|
|
99
|
-
expect(storageClassMock.resetAttempts).toHaveBeenCalledTimes(1);
|
|
100
|
-
});
|
|
101
74
|
});
|
package/lib/abstract/index.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import { empty } from "@bizjournals/js-utilities";
|
|
2
1
|
import { DEFAULT_EXPIRATION, DEFAULT_NAMESPACE, EXPIRES_KEY_SUFFIX } from "./constants";
|
|
3
2
|
import { KEY_IS_REQUIRED_TO_FETCH_DATA } from "./exceptions";
|
|
4
3
|
|
|
5
|
-
// module scoped caching object
|
|
6
|
-
// does share data between session / local / any other
|
|
7
|
-
// If this becomes necessary attach it to the storage object
|
|
8
|
-
const cache = {};
|
|
9
|
-
|
|
10
4
|
/*
|
|
11
5
|
|--------------------------------------------------------------------------
|
|
12
6
|
| Storage Abstract / Contract
|
|
@@ -18,6 +12,13 @@ const cache = {};
|
|
|
18
12
|
|
|
|
19
13
|
*/
|
|
20
14
|
export default class StorageAbstract {
|
|
15
|
+
/**
|
|
16
|
+
* Extensions to this abstraction share an enabled state.
|
|
17
|
+
*
|
|
18
|
+
* @type {boolean}
|
|
19
|
+
*/
|
|
20
|
+
static supported;
|
|
21
|
+
|
|
21
22
|
/**
|
|
22
23
|
* Constructor takes in options for namespace and expiration.
|
|
23
24
|
* Session storage is the default storage type.
|
|
@@ -25,41 +26,36 @@ export default class StorageAbstract {
|
|
|
25
26
|
* @param {object} options
|
|
26
27
|
* @constructor
|
|
27
28
|
*/
|
|
28
|
-
constructor(options = {}) {
|
|
29
|
+
constructor (options = {}) {
|
|
29
30
|
this.namespace = options.namespace || DEFAULT_NAMESPACE;
|
|
30
31
|
this.expires = options.expires || DEFAULT_EXPIRATION;
|
|
31
|
-
this.attempts = 0;
|
|
32
32
|
|
|
33
33
|
this.namespace = this.namespace.toLowerCase();
|
|
34
34
|
|
|
35
|
-
cache[this.namespace] = cache[this.namespace] || {};
|
|
36
|
-
|
|
37
35
|
this.storage = {};
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
|
-
* Gets an item from storage
|
|
39
|
+
* Gets an item from storage and runs the parser unless explicitly
|
|
40
|
+
* disabled in the method request.
|
|
42
41
|
*
|
|
43
|
-
* @param {
|
|
44
|
-
* @
|
|
42
|
+
* @param {String} key
|
|
43
|
+
* @param {Boolean} parse
|
|
44
|
+
* @returns {(Array|Number|Object|String|Null)}
|
|
45
45
|
*/
|
|
46
|
-
getItem(key) {
|
|
47
|
-
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let response = null;
|
|
46
|
+
getItem (key, parse = true) {
|
|
47
|
+
let response;
|
|
52
48
|
|
|
53
|
-
if (
|
|
54
|
-
return
|
|
49
|
+
if (!this.isSupported() || this.isItemExpired(key)) {
|
|
50
|
+
return response;
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
try {
|
|
58
|
-
|
|
54
|
+
response = this.storage.getItem(this.namespaceKey(key));
|
|
59
55
|
|
|
60
|
-
response
|
|
56
|
+
if (response === null) return undefined;
|
|
61
57
|
|
|
62
|
-
this.
|
|
58
|
+
response = parse ? this.parser(response) : response;
|
|
63
59
|
} catch (e) {
|
|
64
60
|
// fall through
|
|
65
61
|
}
|
|
@@ -71,10 +67,18 @@ export default class StorageAbstract {
|
|
|
71
67
|
* Parses the data returned from the storage system
|
|
72
68
|
*
|
|
73
69
|
* @param data
|
|
74
|
-
* @returns {
|
|
70
|
+
* @returns {*}
|
|
75
71
|
*/
|
|
76
|
-
parser(data) {
|
|
77
|
-
|
|
72
|
+
parser (data) {
|
|
73
|
+
try {
|
|
74
|
+
if (typeof data === "string") {
|
|
75
|
+
data = JSON.parse(data);
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// fall through
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return data;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
@@ -83,8 +87,8 @@ export default class StorageAbstract {
|
|
|
83
87
|
* @param value
|
|
84
88
|
* @returns {any}
|
|
85
89
|
*/
|
|
86
|
-
translator(value) {
|
|
87
|
-
return typeof value ===
|
|
90
|
+
translator (value) {
|
|
91
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
/**
|
|
@@ -94,9 +98,9 @@ export default class StorageAbstract {
|
|
|
94
98
|
* @param {(array|object|number|string)} value
|
|
95
99
|
* @param {boolean} expires
|
|
96
100
|
*/
|
|
97
|
-
setItem(key, value, expires = true) {
|
|
98
|
-
if (!this.isSupported()
|
|
99
|
-
return
|
|
101
|
+
setItem (key, value, expires = true) {
|
|
102
|
+
if (!this.isSupported()) {
|
|
103
|
+
return;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
const generatedKey = this.namespaceKey(key);
|
|
@@ -105,16 +109,14 @@ export default class StorageAbstract {
|
|
|
105
109
|
this.storage.setItem(generatedKey, this.translator(value));
|
|
106
110
|
|
|
107
111
|
if (expires) {
|
|
108
|
-
this.storage.setItem(
|
|
112
|
+
this.storage.setItem(
|
|
113
|
+
this.expirationKey(key),
|
|
114
|
+
String(this.timestamp(this.expires))
|
|
115
|
+
);
|
|
109
116
|
}
|
|
110
|
-
|
|
111
|
-
this.cache(key, value);
|
|
112
117
|
} catch (e) {
|
|
113
|
-
|
|
114
|
-
this.setItem(key, value, expires);
|
|
118
|
+
// fall through
|
|
115
119
|
}
|
|
116
|
-
|
|
117
|
-
return this.resetAttempts();
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
/**
|
|
@@ -122,48 +124,28 @@ export default class StorageAbstract {
|
|
|
122
124
|
*
|
|
123
125
|
* @param {string} key
|
|
124
126
|
*/
|
|
125
|
-
removeItem(key) {
|
|
126
|
-
if (!this.isSupported()
|
|
127
|
-
return
|
|
127
|
+
removeItem (key) {
|
|
128
|
+
if (!this.isSupported()) {
|
|
129
|
+
return;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
try {
|
|
131
133
|
this.storage.removeItem(this.namespaceKey(key));
|
|
132
134
|
this.storage.removeItem(this.expirationKey(key));
|
|
133
|
-
|
|
134
|
-
this.free(key);
|
|
135
135
|
} catch (e) {
|
|
136
|
-
|
|
137
|
-
this.removeItem(key);
|
|
136
|
+
// fall through
|
|
138
137
|
}
|
|
139
|
-
|
|
140
|
-
return this.resetAttempts();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Resets the number of attempt operations to zero.
|
|
145
|
-
*
|
|
146
|
-
* @returns {StorageAbstract}
|
|
147
|
-
*/
|
|
148
|
-
resetAttempts() {
|
|
149
|
-
this.attempts = 0;
|
|
150
|
-
|
|
151
|
-
return this;
|
|
152
138
|
}
|
|
153
139
|
|
|
154
140
|
/**
|
|
155
141
|
* Clears all items from storage.
|
|
156
142
|
*/
|
|
157
|
-
clear() {
|
|
143
|
+
clear () {
|
|
158
144
|
try {
|
|
159
145
|
this.storage.clear();
|
|
160
|
-
|
|
161
|
-
cache[this.namespace] = {};
|
|
162
146
|
} catch (e) {
|
|
163
147
|
// fall through
|
|
164
148
|
}
|
|
165
|
-
|
|
166
|
-
return this;
|
|
167
149
|
}
|
|
168
150
|
|
|
169
151
|
/**
|
|
@@ -172,8 +154,8 @@ export default class StorageAbstract {
|
|
|
172
154
|
* @param {string} key
|
|
173
155
|
* @returns {boolean}
|
|
174
156
|
*/
|
|
175
|
-
hasItem(key) {
|
|
176
|
-
return
|
|
157
|
+
hasItem (key) {
|
|
158
|
+
return this.getItem(key, false) !== undefined;
|
|
177
159
|
}
|
|
178
160
|
|
|
179
161
|
/**
|
|
@@ -182,7 +164,7 @@ export default class StorageAbstract {
|
|
|
182
164
|
* @param {number} offset
|
|
183
165
|
* @returns {number}
|
|
184
166
|
*/
|
|
185
|
-
timestamp(offset = 0) {
|
|
167
|
+
timestamp (offset = 0) {
|
|
186
168
|
return Math.ceil(Date.now() / 1000) + offset;
|
|
187
169
|
}
|
|
188
170
|
|
|
@@ -192,16 +174,16 @@ export default class StorageAbstract {
|
|
|
192
174
|
* @param {string} key
|
|
193
175
|
* @returns {string}
|
|
194
176
|
*/
|
|
195
|
-
namespaceKey(key) {
|
|
177
|
+
namespaceKey (key) {
|
|
196
178
|
if (typeof key !== "string") {
|
|
197
179
|
throw new Error(KEY_IS_REQUIRED_TO_FETCH_DATA);
|
|
198
180
|
}
|
|
199
181
|
|
|
200
|
-
if (key.indexOf(`${this.namespace}.`) === 0) {
|
|
182
|
+
if (key.indexOf(`${ this.namespace }.`) === 0) {
|
|
201
183
|
key = key.slice(this.namespace.length + 1);
|
|
202
184
|
}
|
|
203
185
|
|
|
204
|
-
return `${this.namespace}.${key.toLowerCase()}`;
|
|
186
|
+
return `${ this.namespace }.${ key.toLowerCase() }`;
|
|
205
187
|
}
|
|
206
188
|
|
|
207
189
|
/**
|
|
@@ -209,8 +191,8 @@ export default class StorageAbstract {
|
|
|
209
191
|
*
|
|
210
192
|
* @returns {string}
|
|
211
193
|
*/
|
|
212
|
-
expirationKey(key) {
|
|
213
|
-
return `${this.namespaceKey(key)}.${EXPIRES_KEY_SUFFIX}`;
|
|
194
|
+
expirationKey (key) {
|
|
195
|
+
return `${ this.namespaceKey(key) }.${ EXPIRES_KEY_SUFFIX }`;
|
|
214
196
|
}
|
|
215
197
|
|
|
216
198
|
/**
|
|
@@ -219,7 +201,7 @@ export default class StorageAbstract {
|
|
|
219
201
|
* @param {string} key
|
|
220
202
|
* @returns {boolean}
|
|
221
203
|
*/
|
|
222
|
-
isItemExpired(key) {
|
|
204
|
+
isItemExpired (key) {
|
|
223
205
|
if (!this.isSupported()) {
|
|
224
206
|
return false;
|
|
225
207
|
}
|
|
@@ -242,52 +224,38 @@ export default class StorageAbstract {
|
|
|
242
224
|
return false;
|
|
243
225
|
}
|
|
244
226
|
|
|
245
|
-
/**
|
|
246
|
-
* Stores the data in memory.
|
|
247
|
-
*
|
|
248
|
-
* @param key
|
|
249
|
-
* @param value
|
|
250
|
-
*/
|
|
251
|
-
cache(key, value) {
|
|
252
|
-
cache[this.namespace][key] = value;
|
|
253
|
-
|
|
254
|
-
return this;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Frees the data from memory.
|
|
259
|
-
*
|
|
260
|
-
* @param key
|
|
261
|
-
* @param value
|
|
262
|
-
*/
|
|
263
|
-
free(key) {
|
|
264
|
-
if (cache[this.namespace][key]) {
|
|
265
|
-
delete cache[this.namespace][key];
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return this;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
227
|
/**
|
|
272
228
|
* Checks to see if the storage system is supported
|
|
273
229
|
*
|
|
274
230
|
* @returns {boolean}
|
|
275
231
|
*/
|
|
276
|
-
isSupported() {
|
|
277
|
-
if (typeof
|
|
278
|
-
return
|
|
232
|
+
isSupported () {
|
|
233
|
+
if (typeof StorageAbstract.supported === "boolean") {
|
|
234
|
+
return StorageAbstract.supported;
|
|
279
235
|
}
|
|
280
236
|
|
|
281
|
-
const test =
|
|
237
|
+
const test = 'biz.storage.test';
|
|
282
238
|
|
|
283
239
|
try {
|
|
284
|
-
this.storage.setItem(test,
|
|
240
|
+
this.storage.setItem(test, '1');
|
|
285
241
|
this.storage.removeItem(test);
|
|
286
|
-
|
|
242
|
+
|
|
243
|
+
StorageAbstract.supported = true;
|
|
287
244
|
} catch (e) {
|
|
288
|
-
|
|
245
|
+
StorageAbstract.supported = e instanceof DOMException &&
|
|
246
|
+
(
|
|
247
|
+
// everything except Firefox
|
|
248
|
+
e.code === 22 ||
|
|
249
|
+
// Firefox
|
|
250
|
+
e.code === 1014 ||
|
|
251
|
+
// test name field too, because code might not be present
|
|
252
|
+
// everything except Firefox
|
|
253
|
+
e.name === 'QuotaExceededError' ||
|
|
254
|
+
// Firefox
|
|
255
|
+
e.name === 'NS_ERROR_DOM_QUOTA_REACHED'
|
|
256
|
+
) && (this.storage && this.storage.length !== 0);
|
|
289
257
|
}
|
|
290
258
|
|
|
291
|
-
return
|
|
259
|
+
return StorageAbstract.supported;
|
|
292
260
|
}
|
|
293
261
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bizjournals/js-storage",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "ACBJ javascript storage classes",
|
|
5
5
|
"repository": {
|
|
6
6
|
"git": "https://gitlab.bizjournals.com/bizjournals/js-storage"
|
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"sideEffects": false,
|
|
11
11
|
"scripts": {
|
|
12
|
-
"test": "jest
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"preversion": "npm test",
|
|
14
|
+
"postversion": "git push && git push --tags && npm publish"
|
|
13
15
|
},
|
|
14
16
|
"keywords": [],
|
|
15
17
|
"author": "DPD",
|
|
16
18
|
"license": "UNLICENSED",
|
|
17
19
|
"devDependencies": {
|
|
18
|
-
"@babel/preset-env": "^7.
|
|
20
|
+
"@babel/preset-env": "^7.10.3",
|
|
19
21
|
"babel-jest": "^25.4.0",
|
|
20
22
|
"jest": "^25.4.0"
|
|
21
23
|
},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"@bizjournals/js-utilities": "0.0.1"
|
|
24
|
-
}
|
|
24
|
+
"dependencies": {}
|
|
25
25
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import StorageAbstract from '../';
|
|
2
|
-
|
|
3
|
-
describe('core:class >> storageabstract:resetattempts', () => {
|
|
4
|
-
let storageAbstract;
|
|
5
|
-
|
|
6
|
-
beforeAll(() => {
|
|
7
|
-
storageAbstract = new StorageAbstract();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('resets the attempts to 0 when called', () => {
|
|
11
|
-
storageAbstract.attempts = 2;
|
|
12
|
-
|
|
13
|
-
storageAbstract.resetAttempts();
|
|
14
|
-
expect(storageAbstract.attempts).toEqual(0);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('returns `this`', () => {
|
|
18
|
-
expect(storageAbstract.resetAttempts()).toEqual(storageAbstract);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
File without changes
|