-
Notifications
You must be signed in to change notification settings - Fork 0
/
storage.js
180 lines (148 loc) · 5.51 KB
/
storage.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import DefaultSerializer from './serializer.js';
const JsonStorage = function JsonStorage(options) {
// initializes internal store
// you can just pass storeGet and storeSet methods, or only store if it has `setItem` and `getItem` methods
this._initStorage(options.store, options.storeGet, options.storeSet);
// by default store is sync, but if its not, then you can pass async: true
// this will indicate that store's get item returns promise and should be processed in async manner
this.isAsync = options.async === true;
// by default store should be a string store, but if its not
// you can pass stringStore: false for removing serialization/deserialization of stored values
this.isStringStore = options.stringStore !== false;
//if your store does not support expiration mechanic you may use this option - expire: true
//mostly developed for browser local and session storages.
this.useExpiration = options.expire === true;
this.expireKeyPrefix = '_xexp:'; // - will set expiration as `'_xexp:[yourkey]': expirationValue`
// by default every object will be wrapped for processing with custom serializators
// but you can turn this option off by passing wrap: false
this.wrap = options.wrap !== false;
// by default this option is turned off, but when it enabled it will allow you to pass objects which have circular dependencies inside
// this option works only when `wrap` is set to true
// you will get an error if there is a circular dependency inside and this option is disabled
this.circularDependency = options.circularDependency === true;
this._initSerializer(options);
}
// here is a runtime fail on prototype asign, thats why we should ignore next line until this will not fixed in instanbul
/* istanbul ignore next */
JsonStorage.prototype = {
Serializer: DefaultSerializer,
// sets item to a store
setItem(key, value, options) {
let json = this._createStoreItem(value, options);
let text = this.convertToString(json);
this.setItemExpiration(key, options);
let res = this.setItemToStore(key, text);
return res;
},
setItemToStore(key, value) {
return this._store.setItem(key, value);
},
setItemExpiration(key, options = {}) {
if (!this.useExpiration || !options.expiresAt) {
return;
}
let expiresAt = options.expiresAt;
let expKey = this.expireKeyPrefix + key;
this.setItemToStore(expKey, expiresAt);
},
// gets item from store
getItem() {
if (this.isAsync) {
return this.getItemAsync.apply(this, arguments);
} else {
return this.getItemSync.apply(this, arguments);
}
},
getItemSync(key, options) {
let expired = this.checkExpireSync(key, options);
if (expired) {
return;
}
let text = this.getItemFromStore(key);
let json = this.parseStringValue(text);
return this._parseStoredItem(json, options);
},
async getItemAsync(key, options) {
let expired = await this.checkExpireAsync(key, options);
if (expired) {
return;
}
let text = await this.getItemFromStore(key);
let json = this.parseStringValue(text);
return this._parseStoredItem(json, options);
},
checkExpireSync(key, options = {}) {
if (!this.useExpiration || options.expired === false) {
return;
}
let expKey = this.expireKeyPrefix + key;
let expired = this.getItemSync(expKey, { expired: false });
return expired && expired < Date.now();
},
async checkExpireAsync(key, options = {}) {
if (!this.useExpiration || options.expired === false) {
return;
}
let expKey = this.expireKeyPrefix + key;
let expired = await this.getItemAsync(expKey, { expired: false });
return expired && expired < Date.now();
},
getItemFromStore(key) {
return this._store.getItem(key);
},
//#region helpers
parseStringValue(text) {
if (text == null) return;
if (!this.isStringStore) {
return text;
}
return JSON.parse(text);
},
convertToString(storeItem) {
if (!this.isStringStore) {
return storeItem;
}
return JSON.stringify(storeItem);
},
_createStoreItem(value, options = {}) {
if (options.wrap == null) { options.wrap = this.wrap; }
if (options.supportCircularDependency == null) { options.supportCircularDependency = this.circularDependency; }
return this.serializer.toJSON(value, options);
},
_parseStoredItem(json, options = {}) {
if (options.unwrap == null) { options.unwrap = this.wrap; }
return this.serializer.toObject(json, options);
},
_initSerializer(options) {
if (options.serializer instanceof this.Serializer) {
this.serializer = options.serializer;
} else {
let seropts = options.serializerOptions || {};
if (seropts.wrap == null) {
seropts.wrap = options.wrap;
}
if (seropts.unwrap == null) {
seropts.unwrap = !options.wrap;
}
if (seropts.supportCircularDependency == null) {
seropts.supportCircularDependency = options.circularDependency;
}
let Serializer = options.Serializer || this.Serializer;
this.serializer = new Serializer(seropts);
}
},
_initStorage(store, getItem, setItem) {
if (store && store.getItem && store.setItem) {
this._store = store;
} else if (getItem && setItem) {
this._store = {
getItem,
setItem
}
} else {
throw new Error('You must provide a store which has `getItem` and `setItem` methods or you should provide `storeGet` and `storeSet` methods in options');
}
},
//#region helpers
}
export default JsonStorage;