@esportsplus/web-storage 0.3.5 → 0.5.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/README.md +248 -0
- package/build/constants.d.ts +3 -1
- package/build/constants.js +2 -0
- package/build/drivers/localstorage.d.ts +1 -0
- package/build/drivers/localstorage.js +10 -2
- package/build/drivers/memory.d.ts +16 -0
- package/build/drivers/memory.js +64 -0
- package/build/drivers/sessionstorage.d.ts +20 -0
- package/build/drivers/sessionstorage.js +102 -0
- package/build/index.d.ts +12 -2
- package/build/index.js +271 -31
- package/build/lz.d.ts +3 -0
- package/build/lz.js +134 -0
- package/build/types.d.ts +16 -2
- package/package.json +6 -2
- package/src/constants.ts +3 -1
- package/src/drivers/localstorage.ts +13 -2
- package/src/drivers/memory.ts +92 -0
- package/src/drivers/sessionstorage.ts +142 -0
- package/src/index.ts +420 -39
- package/src/lz.ts +192 -0
- package/src/types.ts +23 -2
- package/storage/test-audit-web-storage.md +74 -0
- package/tests/drivers/indexeddb.ts +297 -0
- package/tests/drivers/localstorage.ts +376 -0
- package/tests/drivers/memory.ts +257 -0
- package/tests/drivers/sessionstorage.ts +375 -0
- package/tests/index.ts +1871 -0
- package/tests/lz.ts +324 -0
- package/vitest.config.ts +16 -0
package/build/index.js
CHANGED
|
@@ -2,6 +2,15 @@ import { decrypt, encrypt } from '@esportsplus/utilities';
|
|
|
2
2
|
import { DriverType } from './constants.js';
|
|
3
3
|
import { IndexedDBDriver } from './drivers/indexeddb.js';
|
|
4
4
|
import { LocalStorageDriver } from './drivers/localstorage.js';
|
|
5
|
+
import { MemoryDriver } from './drivers/memory.js';
|
|
6
|
+
import { SessionStorageDriver } from './drivers/sessionstorage.js';
|
|
7
|
+
const VERSION_KEY = '__version__';
|
|
8
|
+
function isEnvelope(value) {
|
|
9
|
+
return value !== null
|
|
10
|
+
&& typeof value === 'object'
|
|
11
|
+
&& '__e' in value
|
|
12
|
+
&& '__v' in value;
|
|
13
|
+
}
|
|
5
14
|
async function deserialize(value, secret) {
|
|
6
15
|
if (value === undefined || value === null) {
|
|
7
16
|
return undefined;
|
|
@@ -26,84 +35,267 @@ async function serialize(value, secret) {
|
|
|
26
35
|
}
|
|
27
36
|
return value;
|
|
28
37
|
}
|
|
38
|
+
function notify(globals, listeners, key, newValue, oldValue) {
|
|
39
|
+
let set = listeners.get(key);
|
|
40
|
+
if (set) {
|
|
41
|
+
for (let cb of set) {
|
|
42
|
+
cb(newValue, oldValue);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (let cb of globals) {
|
|
46
|
+
cb(key, newValue, oldValue);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function unwrap(value) {
|
|
50
|
+
if (isEnvelope(value)) {
|
|
51
|
+
return {
|
|
52
|
+
expired: Date.now() > value.__e,
|
|
53
|
+
hasTTL: true,
|
|
54
|
+
value: value.__v
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return { expired: false, hasTTL: false, value: value };
|
|
58
|
+
}
|
|
59
|
+
async function migrate(driver, migrations, version) {
|
|
60
|
+
let raw = await driver.get(VERSION_KEY), stored = typeof raw === 'number' ? raw : 0;
|
|
61
|
+
if (stored >= version) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let keys = Object.keys(migrations).map(Number).filter((v) => v > stored && v <= version).sort((a, b) => a - b);
|
|
65
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
66
|
+
let all = await driver.all(), data = {};
|
|
67
|
+
for (let key in all) {
|
|
68
|
+
if (key !== VERSION_KEY) {
|
|
69
|
+
data[key] = all[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let transformed = await migrations[keys[i]]({ all: () => Promise.resolve(data) });
|
|
73
|
+
await driver.clear();
|
|
74
|
+
let entries = [];
|
|
75
|
+
for (let key in transformed) {
|
|
76
|
+
entries.push([key, transformed[key]]);
|
|
77
|
+
}
|
|
78
|
+
if (entries.length > 0) {
|
|
79
|
+
await driver.replace(entries);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
await driver.set(VERSION_KEY, version);
|
|
83
|
+
}
|
|
29
84
|
class Local {
|
|
30
85
|
driver;
|
|
86
|
+
globals;
|
|
87
|
+
listeners;
|
|
88
|
+
ready;
|
|
31
89
|
secret;
|
|
90
|
+
version;
|
|
32
91
|
constructor(options, secret) {
|
|
92
|
+
this.globals = new Set();
|
|
93
|
+
this.listeners = new Map();
|
|
33
94
|
this.secret = secret || null;
|
|
34
|
-
let { name, version = 1 } = options;
|
|
95
|
+
let { migrations, name, version = 1 } = options;
|
|
96
|
+
this.version = version;
|
|
35
97
|
if (options.driver === DriverType.LocalStorage) {
|
|
36
98
|
this.driver = new LocalStorageDriver(name, version);
|
|
37
99
|
}
|
|
100
|
+
else if (options.driver === DriverType.Memory) {
|
|
101
|
+
this.driver = new MemoryDriver(name, version);
|
|
102
|
+
}
|
|
103
|
+
else if (options.driver === DriverType.SessionStorage) {
|
|
104
|
+
this.driver = new SessionStorageDriver(name, version);
|
|
105
|
+
}
|
|
38
106
|
else {
|
|
39
107
|
this.driver = new IndexedDBDriver(name, version);
|
|
40
108
|
}
|
|
109
|
+
if (migrations) {
|
|
110
|
+
this.ready = migrate(this.driver, migrations, version);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.ready = Promise.resolve();
|
|
114
|
+
}
|
|
41
115
|
}
|
|
42
116
|
async all() {
|
|
43
|
-
|
|
117
|
+
await this.ready;
|
|
118
|
+
let expired = [], raw = await this.driver.all(), result = {};
|
|
44
119
|
for (let key in raw) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
120
|
+
if (key === VERSION_KEY) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
let deserialized = await deserialize(raw[key], this.secret), unwrapped = unwrap(deserialized);
|
|
124
|
+
if (deserialized === undefined) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (unwrapped.expired) {
|
|
128
|
+
expired.push(key);
|
|
129
|
+
continue;
|
|
48
130
|
}
|
|
131
|
+
result[key] = unwrapped.value;
|
|
132
|
+
}
|
|
133
|
+
if (expired.length > 0) {
|
|
134
|
+
this.driver.delete(expired);
|
|
49
135
|
}
|
|
50
136
|
return result;
|
|
51
137
|
}
|
|
52
138
|
async clear() {
|
|
53
|
-
|
|
139
|
+
await this.ready;
|
|
140
|
+
let allData = await this.all(), keys = Object.keys(allData);
|
|
141
|
+
await this.driver.clear();
|
|
142
|
+
await this.driver.set(VERSION_KEY, this.version);
|
|
143
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
144
|
+
notify(this.globals, this.listeners, keys[i], undefined, allData[keys[i]]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async cleanup() {
|
|
148
|
+
await this.ready;
|
|
149
|
+
let expired = [], oldValues = new Map();
|
|
150
|
+
await this.driver.map(async (raw, key) => {
|
|
151
|
+
if (key === VERSION_KEY) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
|
|
155
|
+
if (deserialized !== undefined && unwrapped.expired) {
|
|
156
|
+
expired.push(key);
|
|
157
|
+
oldValues.set(key, unwrapped.value);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
if (expired.length > 0) {
|
|
161
|
+
await this.driver.delete(expired);
|
|
162
|
+
for (let i = 0, n = expired.length; i < n; i++) {
|
|
163
|
+
notify(this.globals, this.listeners, expired[i], undefined, oldValues.get(expired[i]));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
54
166
|
}
|
|
55
167
|
async count() {
|
|
56
|
-
|
|
168
|
+
await this.ready;
|
|
169
|
+
let total = await this.driver.count(), raw = await this.driver.get(VERSION_KEY);
|
|
170
|
+
return raw !== undefined ? total - 1 : total;
|
|
57
171
|
}
|
|
58
172
|
async delete(...keys) {
|
|
59
|
-
|
|
173
|
+
await this.ready;
|
|
174
|
+
let oldValues = new Map();
|
|
175
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
176
|
+
let raw = await this.driver.get(keys[i]), deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
|
|
177
|
+
oldValues.set(keys[i], deserialized === undefined ? undefined : unwrapped.value);
|
|
178
|
+
}
|
|
179
|
+
await this.driver.delete(keys);
|
|
180
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
181
|
+
notify(this.globals, this.listeners, keys[i], undefined, oldValues.get(keys[i]));
|
|
182
|
+
}
|
|
60
183
|
}
|
|
61
184
|
async filter(fn) {
|
|
62
|
-
|
|
185
|
+
await this.ready;
|
|
186
|
+
let expired = [], i = 0, result = {}, stop = () => { stopped = true; }, stopped = false;
|
|
63
187
|
await this.driver.map(async (raw, key) => {
|
|
64
|
-
if (stopped) {
|
|
188
|
+
if (stopped || key === VERSION_KEY) {
|
|
65
189
|
return;
|
|
66
190
|
}
|
|
67
|
-
let
|
|
68
|
-
if (
|
|
191
|
+
let deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
|
|
192
|
+
if (deserialized === undefined) {
|
|
69
193
|
return;
|
|
70
194
|
}
|
|
71
|
-
if (
|
|
72
|
-
|
|
195
|
+
if (unwrapped.expired) {
|
|
196
|
+
expired.push(key);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (await fn({ i: i++, key, stop, value: unwrapped.value })) {
|
|
200
|
+
result[key] = unwrapped.value;
|
|
73
201
|
}
|
|
74
202
|
});
|
|
203
|
+
if (expired.length > 0) {
|
|
204
|
+
this.driver.delete(expired);
|
|
205
|
+
}
|
|
75
206
|
return result;
|
|
76
207
|
}
|
|
77
|
-
async get(key) {
|
|
78
|
-
|
|
208
|
+
async get(key, factory) {
|
|
209
|
+
await this.ready;
|
|
210
|
+
let deserialized = await deserialize(await this.driver.get(key), this.secret), missing = false, unwrapped = unwrap(deserialized);
|
|
211
|
+
if (deserialized === undefined) {
|
|
212
|
+
missing = true;
|
|
213
|
+
}
|
|
214
|
+
else if (unwrapped.expired) {
|
|
215
|
+
this.driver.delete([key]);
|
|
216
|
+
missing = true;
|
|
217
|
+
}
|
|
218
|
+
if (missing) {
|
|
219
|
+
if (factory) {
|
|
220
|
+
let value = await factory();
|
|
221
|
+
this.set(key, value);
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
return unwrapped.value;
|
|
79
227
|
}
|
|
80
228
|
async keys() {
|
|
81
|
-
|
|
229
|
+
await this.ready;
|
|
230
|
+
let all = await this.driver.keys();
|
|
231
|
+
return all.filter((k) => k !== VERSION_KEY);
|
|
82
232
|
}
|
|
83
|
-
length() {
|
|
84
|
-
|
|
233
|
+
async length() {
|
|
234
|
+
await this.ready;
|
|
235
|
+
return this.count();
|
|
85
236
|
}
|
|
86
|
-
map(fn) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
237
|
+
async map(fn) {
|
|
238
|
+
await this.ready;
|
|
239
|
+
let expired = [], j = 0;
|
|
240
|
+
await this.driver.map(async (raw, key) => {
|
|
241
|
+
if (key === VERSION_KEY) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
let deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
|
|
245
|
+
if (deserialized === undefined) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (unwrapped.expired) {
|
|
249
|
+
expired.push(key);
|
|
250
|
+
return;
|
|
91
251
|
}
|
|
252
|
+
await fn(unwrapped.value, key, j++);
|
|
92
253
|
});
|
|
254
|
+
if (expired.length > 0) {
|
|
255
|
+
this.driver.delete(expired);
|
|
256
|
+
}
|
|
93
257
|
}
|
|
94
258
|
async only(...keys) {
|
|
95
|
-
|
|
259
|
+
await this.ready;
|
|
260
|
+
let expired = [], raw = await this.driver.only(keys), result = {};
|
|
96
261
|
for (let [key, value] of raw) {
|
|
97
|
-
let deserialized = await deserialize(value, this.secret);
|
|
98
|
-
if (deserialized
|
|
99
|
-
|
|
262
|
+
let deserialized = await deserialize(value, this.secret), unwrapped = unwrap(deserialized);
|
|
263
|
+
if (deserialized === undefined) {
|
|
264
|
+
continue;
|
|
100
265
|
}
|
|
266
|
+
if (unwrapped.expired) {
|
|
267
|
+
expired.push(key);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
result[key] = unwrapped.value;
|
|
271
|
+
}
|
|
272
|
+
if (expired.length > 0) {
|
|
273
|
+
this.driver.delete(expired);
|
|
101
274
|
}
|
|
102
275
|
return result;
|
|
103
276
|
}
|
|
277
|
+
async persist(key) {
|
|
278
|
+
await this.ready;
|
|
279
|
+
let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.secret);
|
|
280
|
+
if (deserialized === undefined) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
let unwrapped = unwrap(deserialized);
|
|
284
|
+
if (unwrapped.expired) {
|
|
285
|
+
this.driver.delete([key]);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
if (!unwrapped.hasTTL) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
return this.driver.set(key, await serialize(unwrapped.value, this.secret));
|
|
292
|
+
}
|
|
104
293
|
async replace(values) {
|
|
105
|
-
|
|
294
|
+
await this.ready;
|
|
295
|
+
let entries = [], failed = [], oldValues = new Map();
|
|
106
296
|
for (let key in values) {
|
|
297
|
+
let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
|
|
298
|
+
oldValues.set(key, deserialized === undefined ? undefined : unwrapped.value);
|
|
107
299
|
try {
|
|
108
300
|
entries.push([
|
|
109
301
|
key,
|
|
@@ -117,16 +309,64 @@ class Local {
|
|
|
117
309
|
if (entries.length > 0) {
|
|
118
310
|
await this.driver.replace(entries);
|
|
119
311
|
}
|
|
312
|
+
for (let i = 0, n = entries.length; i < n; i++) {
|
|
313
|
+
let key = entries[i][0];
|
|
314
|
+
notify(this.globals, this.listeners, key, values[key], oldValues.get(key));
|
|
315
|
+
}
|
|
120
316
|
return failed;
|
|
121
317
|
}
|
|
122
|
-
async set(key, value) {
|
|
318
|
+
async set(key, value, options) {
|
|
319
|
+
await this.ready;
|
|
123
320
|
try {
|
|
124
|
-
|
|
321
|
+
let oldRaw = await this.driver.get(key), oldDeserialized = await deserialize(oldRaw, this.secret), oldUnwrapped = unwrap(oldDeserialized), oldValue = oldDeserialized === undefined ? undefined : oldUnwrapped.value, stored;
|
|
322
|
+
if (options?.ttl != null && options.ttl > 0) {
|
|
323
|
+
let envelope = {
|
|
324
|
+
__e: Date.now() + options.ttl,
|
|
325
|
+
__v: value
|
|
326
|
+
};
|
|
327
|
+
stored = await serialize(envelope, this.secret);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
stored = await serialize(value, this.secret);
|
|
331
|
+
}
|
|
332
|
+
let result = await this.driver.set(key, stored);
|
|
333
|
+
notify(this.globals, this.listeners, key, value, oldValue);
|
|
334
|
+
return result;
|
|
125
335
|
}
|
|
126
336
|
catch {
|
|
127
337
|
return false;
|
|
128
338
|
}
|
|
129
339
|
}
|
|
340
|
+
subscribe(keyOrCallback, callback) {
|
|
341
|
+
if (typeof keyOrCallback === 'function') {
|
|
342
|
+
let cb = keyOrCallback;
|
|
343
|
+
this.globals.add(cb);
|
|
344
|
+
return () => { this.globals.delete(cb); };
|
|
345
|
+
}
|
|
346
|
+
let cb = callback, key = keyOrCallback, set = this.listeners.get(key);
|
|
347
|
+
if (!set) {
|
|
348
|
+
set = new Set();
|
|
349
|
+
this.listeners.set(key, set);
|
|
350
|
+
}
|
|
351
|
+
set.add(cb);
|
|
352
|
+
return () => { set.delete(cb); };
|
|
353
|
+
}
|
|
354
|
+
async ttl(key) {
|
|
355
|
+
await this.ready;
|
|
356
|
+
let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.secret);
|
|
357
|
+
if (deserialized === undefined) {
|
|
358
|
+
return -1;
|
|
359
|
+
}
|
|
360
|
+
if (!isEnvelope(deserialized)) {
|
|
361
|
+
return -1;
|
|
362
|
+
}
|
|
363
|
+
let remaining = deserialized.__e - Date.now();
|
|
364
|
+
if (remaining <= 0) {
|
|
365
|
+
this.driver.delete([key]);
|
|
366
|
+
return -1;
|
|
367
|
+
}
|
|
368
|
+
return remaining;
|
|
369
|
+
}
|
|
130
370
|
}
|
|
131
371
|
export default (options, secret) => {
|
|
132
372
|
return new Local(options, secret);
|
package/build/lz.d.ts
ADDED
package/build/lz.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
function emitLiteral(ctx, ch) {
|
|
2
|
+
let code = ch.charCodeAt(0);
|
|
3
|
+
if (code < 256) {
|
|
4
|
+
writeBits(ctx, ctx.numBits, 0);
|
|
5
|
+
writeBits(ctx, 8, code);
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
writeBits(ctx, ctx.numBits, 1);
|
|
9
|
+
writeBits(ctx, 16, code);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function readBits(ctx, n) {
|
|
13
|
+
let result = 0;
|
|
14
|
+
for (let i = 0; i < n; i++) {
|
|
15
|
+
if (ctx.bitPos > 15) {
|
|
16
|
+
ctx.currentValue = ctx.compressed.charCodeAt(ctx.pos++) - 1;
|
|
17
|
+
ctx.bitPos = 0;
|
|
18
|
+
}
|
|
19
|
+
result = (result << 1) | ((ctx.currentValue >> (15 - ctx.bitPos)) & 1);
|
|
20
|
+
ctx.bitPos++;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
function writeBits(ctx, n, value) {
|
|
25
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
26
|
+
ctx.buffer = (ctx.buffer << 1) | ((value >> i) & 1);
|
|
27
|
+
ctx.bitsInBuffer++;
|
|
28
|
+
if (ctx.bitsInBuffer === 16) {
|
|
29
|
+
ctx.output.push(ctx.buffer + 1);
|
|
30
|
+
ctx.buffer = 0;
|
|
31
|
+
ctx.bitsInBuffer = 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const compress = (input) => {
|
|
36
|
+
if (!input) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
let ctx = { bitsInBuffer: 0, buffer: 0, numBits: 2, output: [] }, dictSize = 3, dictionary = new Map(), w = '';
|
|
40
|
+
for (let i = 0, n = input.length; i < n; i++) {
|
|
41
|
+
let c = input[i], wc = w + c;
|
|
42
|
+
if (dictionary.has(wc)) {
|
|
43
|
+
w = wc;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (w.length > 0) {
|
|
47
|
+
if (dictionary.has(w)) {
|
|
48
|
+
writeBits(ctx, ctx.numBits, dictionary.get(w));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
emitLiteral(ctx, w);
|
|
52
|
+
}
|
|
53
|
+
dictionary.set(wc, dictSize++);
|
|
54
|
+
if (dictSize > (1 << ctx.numBits)) {
|
|
55
|
+
ctx.numBits++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
w = c;
|
|
59
|
+
}
|
|
60
|
+
if (w.length > 0) {
|
|
61
|
+
if (dictionary.has(w)) {
|
|
62
|
+
writeBits(ctx, ctx.numBits, dictionary.get(w));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
emitLiteral(ctx, w);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
dictSize++;
|
|
69
|
+
if (dictSize > (1 << ctx.numBits)) {
|
|
70
|
+
ctx.numBits++;
|
|
71
|
+
}
|
|
72
|
+
writeBits(ctx, ctx.numBits, 2);
|
|
73
|
+
if (ctx.bitsInBuffer > 0) {
|
|
74
|
+
ctx.output.push(((ctx.buffer << (16 - ctx.bitsInBuffer)) & 0xFFFF) + 1);
|
|
75
|
+
}
|
|
76
|
+
ctx.output.push((ctx.bitsInBuffer === 0 ? 16 : ctx.bitsInBuffer) + 1);
|
|
77
|
+
let chars = [];
|
|
78
|
+
for (let i = 0, n = ctx.output.length; i < n; i++) {
|
|
79
|
+
chars.push(String.fromCharCode(ctx.output[i]));
|
|
80
|
+
}
|
|
81
|
+
return chars.join('');
|
|
82
|
+
};
|
|
83
|
+
const decompress = (compressed) => {
|
|
84
|
+
if (!compressed) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
let ctx = { bitPos: 16, compressed: '', currentValue: 0, pos: 0 }, dictSize = 3, dictionary = [], numBits = 2;
|
|
88
|
+
ctx.compressed = compressed.substring(0, compressed.length - 1);
|
|
89
|
+
let code = readBits(ctx, numBits), entry;
|
|
90
|
+
if (code === 0) {
|
|
91
|
+
entry = String.fromCharCode(readBits(ctx, 8));
|
|
92
|
+
}
|
|
93
|
+
else if (code === 1) {
|
|
94
|
+
entry = String.fromCharCode(readBits(ctx, 16));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
let result = [entry], w = entry;
|
|
100
|
+
while (true) {
|
|
101
|
+
let slotIdx = dictionary.length;
|
|
102
|
+
dictionary.push('');
|
|
103
|
+
dictSize++;
|
|
104
|
+
if (dictSize > (1 << numBits)) {
|
|
105
|
+
numBits++;
|
|
106
|
+
}
|
|
107
|
+
code = readBits(ctx, numBits);
|
|
108
|
+
if (code === 2) {
|
|
109
|
+
dictionary.pop();
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
let slotCode = slotIdx + 3;
|
|
113
|
+
if (code === 0) {
|
|
114
|
+
entry = String.fromCharCode(readBits(ctx, 8));
|
|
115
|
+
}
|
|
116
|
+
else if (code === 1) {
|
|
117
|
+
entry = String.fromCharCode(readBits(ctx, 16));
|
|
118
|
+
}
|
|
119
|
+
else if (code === slotCode) {
|
|
120
|
+
entry = w + w[0];
|
|
121
|
+
}
|
|
122
|
+
else if (code >= 3 && code < slotCode) {
|
|
123
|
+
entry = dictionary[code - 3];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
throw new Error('LZ: invalid decompression code');
|
|
127
|
+
}
|
|
128
|
+
dictionary[slotIdx] = w + entry[0];
|
|
129
|
+
result.push(entry);
|
|
130
|
+
w = entry;
|
|
131
|
+
}
|
|
132
|
+
return result.join('');
|
|
133
|
+
};
|
|
134
|
+
export { compress, decompress };
|
package/build/types.d.ts
CHANGED
|
@@ -17,9 +17,23 @@ type Filter<T> = (data: {
|
|
|
17
17
|
stop: VoidFunction;
|
|
18
18
|
value: T[keyof T];
|
|
19
19
|
}) => boolean | Promise<boolean>;
|
|
20
|
+
type MigrationContext = {
|
|
21
|
+
all(): Promise<Record<string, unknown>>;
|
|
22
|
+
};
|
|
23
|
+
type MigrationFn = (old: MigrationContext) => Promise<Record<string, unknown>>;
|
|
20
24
|
type Options = {
|
|
21
|
-
driver?: DriverType.IndexedDB | DriverType.LocalStorage;
|
|
25
|
+
driver?: DriverType.IndexedDB | DriverType.LocalStorage | DriverType.Memory | DriverType.SessionStorage;
|
|
26
|
+
migrations?: Record<number, MigrationFn>;
|
|
22
27
|
name: string;
|
|
23
28
|
version: number;
|
|
24
29
|
};
|
|
25
|
-
|
|
30
|
+
type SetOptions = {
|
|
31
|
+
ttl?: number;
|
|
32
|
+
};
|
|
33
|
+
type TTLEnvelope<V> = {
|
|
34
|
+
__e: number;
|
|
35
|
+
__v: V;
|
|
36
|
+
};
|
|
37
|
+
type GlobalCallback<T> = (key: keyof T, newValue: T[keyof T] | undefined, oldValue: T[keyof T] | undefined) => void;
|
|
38
|
+
type KeyCallback<T, K extends keyof T = keyof T> = (newValue: T[K] | undefined, oldValue: T[K] | undefined) => void;
|
|
39
|
+
export type { Driver, Filter, GlobalCallback, KeyCallback, MigrationContext, MigrationFn, Options, SetOptions, TTLEnvelope };
|
package/package.json
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
},
|
|
6
6
|
"description": "Web storage utility",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@esportsplus/typescript": "^0.9.2"
|
|
8
|
+
"@esportsplus/typescript": "^0.9.2",
|
|
9
|
+
"fake-indexeddb": "^6.2.5",
|
|
10
|
+
"happy-dom": "^20.8.8",
|
|
11
|
+
"vitest": "^4.1.1"
|
|
9
12
|
},
|
|
10
13
|
"main": "build/index.js",
|
|
11
14
|
"name": "@esportsplus/web-storage",
|
|
@@ -16,9 +19,10 @@
|
|
|
16
19
|
},
|
|
17
20
|
"type": "module",
|
|
18
21
|
"types": "build/index.d.ts",
|
|
19
|
-
"version": "0.
|
|
22
|
+
"version": "0.5.0",
|
|
20
23
|
"scripts": {
|
|
21
24
|
"build": "tsc && tsc-alias",
|
|
25
|
+
"test": "vitest run",
|
|
22
26
|
"-": "-"
|
|
23
27
|
}
|
|
24
28
|
}
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compress, decompress } from '~/lz';
|
|
1
2
|
import type { Driver } from '~/types';
|
|
2
3
|
|
|
3
4
|
|
|
@@ -35,6 +36,10 @@ class LocalStorageDriver<T> implements Driver<T> {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
try {
|
|
39
|
+
if (value.charCodeAt(0) === 1) {
|
|
40
|
+
return JSON.parse(decompress(value.slice(1)));
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
return JSON.parse(value);
|
|
39
44
|
}
|
|
40
45
|
catch {
|
|
@@ -42,6 +47,12 @@ class LocalStorageDriver<T> implements Driver<T> {
|
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
private serialize(value: T[keyof T]): string {
|
|
51
|
+
let json = JSON.stringify(value);
|
|
52
|
+
|
|
53
|
+
return json.length >= 100 ? '\x01' + compress(json) : json;
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
|
|
46
57
|
async all(): Promise<T> {
|
|
47
58
|
let keys = this.getKeys(),
|
|
@@ -112,13 +123,13 @@ class LocalStorageDriver<T> implements Driver<T> {
|
|
|
112
123
|
|
|
113
124
|
async replace(entries: [keyof T, T[keyof T]][]): Promise<void> {
|
|
114
125
|
for (let i = 0, n = entries.length; i < n; i++) {
|
|
115
|
-
localStorage.setItem(this.key(entries[i][0]),
|
|
126
|
+
localStorage.setItem(this.key(entries[i][0]), this.serialize(entries[i][1]));
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
|
|
120
131
|
try {
|
|
121
|
-
localStorage.setItem(this.key(key),
|
|
132
|
+
localStorage.setItem(this.key(key), this.serialize(value));
|
|
122
133
|
return true;
|
|
123
134
|
}
|
|
124
135
|
catch {
|