@esportsplus/web-storage 0.3.5 → 0.4.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/build/constants.d.ts +3 -1
- package/build/constants.js +2 -0
- package/build/drivers/memory.d.ts +16 -0
- package/build/drivers/memory.js +64 -0
- package/build/drivers/sessionstorage.d.ts +19 -0
- package/build/drivers/sessionstorage.js +94 -0
- package/build/index.d.ts +12 -2
- package/build/index.js +271 -31
- package/build/types.d.ts +16 -2
- package/package.json +6 -2
- package/src/constants.ts +3 -1
- package/src/drivers/memory.ts +92 -0
- package/src/drivers/sessionstorage.ts +131 -0
- package/src/index.ts +420 -39
- package/src/types.ts +23 -2
- package/storage/feature-research.md +173 -0
- package/tests/drivers/indexeddb.ts +297 -0
- package/tests/drivers/localstorage.ts +290 -0
- package/tests/drivers/memory.ts +257 -0
- package/tests/drivers/sessionstorage.ts +290 -0
- package/tests/index.ts +1462 -0
- package/vitest.config.ts +16 -0
package/src/index.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { decrypt, encrypt } from '@esportsplus/utilities';
|
|
2
|
-
import type { Driver, Filter, Options } from './types';
|
|
2
|
+
import type { Driver, Filter, GlobalCallback, KeyCallback, MigrationFn, Options, SetOptions, TTLEnvelope } from './types';
|
|
3
3
|
import { DriverType } from './constants';
|
|
4
4
|
import { IndexedDBDriver } from '~/drivers/indexeddb';
|
|
5
5
|
import { LocalStorageDriver } from '~/drivers/localstorage';
|
|
6
|
+
import { MemoryDriver } from '~/drivers/memory';
|
|
7
|
+
import { SessionStorageDriver } from '~/drivers/sessionstorage';
|
|
6
8
|
|
|
7
9
|
|
|
10
|
+
const VERSION_KEY = '__version__';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function isEnvelope<V>(value: unknown): value is TTLEnvelope<V> {
|
|
14
|
+
return value !== null
|
|
15
|
+
&& typeof value === 'object'
|
|
16
|
+
&& '__e' in (value as Record<string, unknown>)
|
|
17
|
+
&& '__v' in (value as Record<string, unknown>);
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
async function deserialize<V>(value: unknown, secret: string | null): Promise<V | undefined> {
|
|
9
21
|
if (value === undefined || value === null) {
|
|
10
22
|
return undefined;
|
|
@@ -35,125 +47,418 @@ async function serialize<V>(value: V, secret: string | null): Promise<string | V
|
|
|
35
47
|
return value;
|
|
36
48
|
}
|
|
37
49
|
|
|
50
|
+
function notify<T>(
|
|
51
|
+
globals: Set<GlobalCallback<T>>,
|
|
52
|
+
listeners: Map<keyof T, Set<KeyCallback<T>>>,
|
|
53
|
+
key: keyof T,
|
|
54
|
+
newValue: T[keyof T] | undefined,
|
|
55
|
+
oldValue: T[keyof T] | undefined
|
|
56
|
+
): void {
|
|
57
|
+
let set = listeners.get(key);
|
|
58
|
+
|
|
59
|
+
if (set) {
|
|
60
|
+
for (let cb of set) {
|
|
61
|
+
cb(newValue, oldValue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let cb of globals) {
|
|
66
|
+
cb(key, newValue, oldValue);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function unwrap<V>(value: unknown): { expired: boolean; hasTTL: boolean; value: V } {
|
|
71
|
+
if (isEnvelope<V>(value)) {
|
|
72
|
+
return {
|
|
73
|
+
expired: Date.now() > value.__e,
|
|
74
|
+
hasTTL: true,
|
|
75
|
+
value: value.__v
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { expired: false, hasTTL: false, value: value as V };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function migrate<T>(driver: Driver<T>, migrations: Record<number, MigrationFn>, version: number): Promise<void> {
|
|
83
|
+
let raw = await driver.get(VERSION_KEY as keyof T),
|
|
84
|
+
stored = typeof raw === 'number' ? raw : 0;
|
|
85
|
+
|
|
86
|
+
if (stored >= version) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let keys = Object.keys(migrations).map(Number).filter((v) => v > stored && v <= version).sort((a, b) => a - b);
|
|
91
|
+
|
|
92
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
93
|
+
let all = await driver.all(),
|
|
94
|
+
data: Record<string, unknown> = {};
|
|
95
|
+
|
|
96
|
+
for (let key in all) {
|
|
97
|
+
if (key !== VERSION_KEY) {
|
|
98
|
+
data[key] = all[key as keyof T];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let transformed = await migrations[keys[i]]({ all: () => Promise.resolve(data) });
|
|
103
|
+
|
|
104
|
+
await driver.clear();
|
|
105
|
+
|
|
106
|
+
let entries: [keyof T, T[keyof T]][] = [];
|
|
107
|
+
|
|
108
|
+
for (let key in transformed) {
|
|
109
|
+
entries.push([key as keyof T, transformed[key] as T[keyof T]]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (entries.length > 0) {
|
|
113
|
+
await driver.replace(entries);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await driver.set(VERSION_KEY as keyof T, version as T[keyof T]);
|
|
118
|
+
}
|
|
119
|
+
|
|
38
120
|
|
|
39
121
|
class Local<T> {
|
|
40
122
|
|
|
41
123
|
private driver: Driver<T>;
|
|
42
124
|
|
|
125
|
+
private globals: Set<GlobalCallback<T>>;
|
|
126
|
+
|
|
127
|
+
private listeners: Map<keyof T, Set<KeyCallback<T>>>;
|
|
128
|
+
|
|
129
|
+
private ready: Promise<void>;
|
|
130
|
+
|
|
43
131
|
private secret: string | null;
|
|
44
132
|
|
|
133
|
+
private version: number;
|
|
134
|
+
|
|
45
135
|
|
|
46
136
|
constructor(options: Options, secret?: string) {
|
|
137
|
+
this.globals = new Set();
|
|
138
|
+
this.listeners = new Map();
|
|
47
139
|
this.secret = secret || null;
|
|
48
140
|
|
|
49
|
-
let { name, version = 1 } = options;
|
|
141
|
+
let { migrations, name, version = 1 } = options;
|
|
142
|
+
|
|
143
|
+
this.version = version;
|
|
50
144
|
|
|
51
145
|
if (options.driver === DriverType.LocalStorage) {
|
|
52
146
|
this.driver = new LocalStorageDriver<T>(name, version);
|
|
53
147
|
}
|
|
148
|
+
else if (options.driver === DriverType.Memory) {
|
|
149
|
+
this.driver = new MemoryDriver<T>(name, version);
|
|
150
|
+
}
|
|
151
|
+
else if (options.driver === DriverType.SessionStorage) {
|
|
152
|
+
this.driver = new SessionStorageDriver<T>(name, version);
|
|
153
|
+
}
|
|
54
154
|
else {
|
|
55
155
|
this.driver = new IndexedDBDriver<T>(name, version);
|
|
56
156
|
}
|
|
157
|
+
|
|
158
|
+
if (migrations) {
|
|
159
|
+
this.ready = migrate(this.driver, migrations, version);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.ready = Promise.resolve();
|
|
163
|
+
}
|
|
57
164
|
}
|
|
58
165
|
|
|
59
166
|
|
|
60
167
|
async all(): Promise<T> {
|
|
61
|
-
|
|
168
|
+
await this.ready;
|
|
169
|
+
|
|
170
|
+
let expired: (keyof T)[] = [],
|
|
171
|
+
raw = await this.driver.all(),
|
|
62
172
|
result = {} as T;
|
|
63
173
|
|
|
64
174
|
for (let key in raw) {
|
|
65
|
-
|
|
175
|
+
if (key === VERSION_KEY) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw[key], this.secret),
|
|
180
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
66
181
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
182
|
+
if (deserialized === undefined) {
|
|
183
|
+
continue;
|
|
69
184
|
}
|
|
185
|
+
|
|
186
|
+
if (unwrapped.expired) {
|
|
187
|
+
expired.push(key as keyof T);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
(result as Record<string, unknown>)[key] = unwrapped.value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (expired.length > 0) {
|
|
195
|
+
this.driver.delete(expired);
|
|
70
196
|
}
|
|
71
197
|
|
|
72
198
|
return result;
|
|
73
199
|
}
|
|
74
200
|
|
|
75
201
|
async clear(): Promise<void> {
|
|
76
|
-
|
|
202
|
+
await this.ready;
|
|
203
|
+
|
|
204
|
+
let allData = await this.all(),
|
|
205
|
+
keys = Object.keys(allData as Record<string, unknown>) as (keyof T)[];
|
|
206
|
+
|
|
207
|
+
await this.driver.clear();
|
|
208
|
+
await this.driver.set(VERSION_KEY as keyof T, this.version as T[keyof T]);
|
|
209
|
+
|
|
210
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
211
|
+
notify(this.globals, this.listeners, keys[i], undefined, allData[keys[i]]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async cleanup(): Promise<void> {
|
|
216
|
+
await this.ready;
|
|
217
|
+
|
|
218
|
+
let expired: (keyof T)[] = [],
|
|
219
|
+
oldValues = new Map<keyof T, T[keyof T]>();
|
|
220
|
+
|
|
221
|
+
await this.driver.map(async (raw, key) => {
|
|
222
|
+
if (key as string === VERSION_KEY) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret),
|
|
227
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
228
|
+
|
|
229
|
+
if (deserialized !== undefined && unwrapped.expired) {
|
|
230
|
+
expired.push(key);
|
|
231
|
+
oldValues.set(key, unwrapped.value);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (expired.length > 0) {
|
|
236
|
+
await this.driver.delete(expired);
|
|
237
|
+
|
|
238
|
+
for (let i = 0, n = expired.length; i < n; i++) {
|
|
239
|
+
notify(this.globals, this.listeners, expired[i], undefined, oldValues.get(expired[i]));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
77
242
|
}
|
|
78
243
|
|
|
79
244
|
async count(): Promise<number> {
|
|
80
|
-
|
|
245
|
+
await this.ready;
|
|
246
|
+
|
|
247
|
+
let total = await this.driver.count(),
|
|
248
|
+
raw = await this.driver.get(VERSION_KEY as keyof T);
|
|
249
|
+
|
|
250
|
+
return raw !== undefined ? total - 1 : total;
|
|
81
251
|
}
|
|
82
252
|
|
|
83
253
|
async delete(...keys: (keyof T)[]): Promise<void> {
|
|
84
|
-
|
|
254
|
+
await this.ready;
|
|
255
|
+
|
|
256
|
+
let oldValues = new Map<keyof T, T[keyof T] | undefined>();
|
|
257
|
+
|
|
258
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
259
|
+
let raw = await this.driver.get(keys[i]),
|
|
260
|
+
deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret),
|
|
261
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
262
|
+
|
|
263
|
+
oldValues.set(keys[i], deserialized === undefined ? undefined : unwrapped.value);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await this.driver.delete(keys);
|
|
267
|
+
|
|
268
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
269
|
+
notify(this.globals, this.listeners, keys[i], undefined, oldValues.get(keys[i]));
|
|
270
|
+
}
|
|
85
271
|
}
|
|
86
272
|
|
|
87
273
|
async filter(fn: Filter<T>): Promise<T> {
|
|
88
|
-
|
|
274
|
+
await this.ready;
|
|
275
|
+
|
|
276
|
+
let expired: (keyof T)[] = [],
|
|
277
|
+
i = 0,
|
|
89
278
|
result = {} as T,
|
|
90
279
|
stop = () => { stopped = true; },
|
|
91
280
|
stopped = false;
|
|
92
281
|
|
|
93
282
|
await this.driver.map(async (raw, key) => {
|
|
94
|
-
if (stopped) {
|
|
283
|
+
if (stopped || key as string === VERSION_KEY) {
|
|
95
284
|
return;
|
|
96
285
|
}
|
|
97
286
|
|
|
98
|
-
let
|
|
287
|
+
let deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret),
|
|
288
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
289
|
+
|
|
290
|
+
if (deserialized === undefined) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
99
293
|
|
|
100
|
-
if (
|
|
294
|
+
if (unwrapped.expired) {
|
|
295
|
+
expired.push(key);
|
|
101
296
|
return;
|
|
102
297
|
}
|
|
103
298
|
|
|
104
|
-
if (await fn({ i: i++, key, stop, value })) {
|
|
105
|
-
result[key] = value;
|
|
299
|
+
if (await fn({ i: i++, key, stop, value: unwrapped.value })) {
|
|
300
|
+
result[key] = unwrapped.value;
|
|
106
301
|
}
|
|
107
302
|
});
|
|
108
303
|
|
|
304
|
+
if (expired.length > 0) {
|
|
305
|
+
this.driver.delete(expired);
|
|
306
|
+
}
|
|
307
|
+
|
|
109
308
|
return result;
|
|
110
309
|
}
|
|
111
310
|
|
|
112
|
-
async get(key: keyof T): Promise<T[keyof T] | undefined
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
311
|
+
async get(key: keyof T): Promise<T[keyof T] | undefined>;
|
|
312
|
+
async get(key: keyof T, factory: () => T[keyof T] | Promise<T[keyof T]>): Promise<T[keyof T]>;
|
|
313
|
+
async get(key: keyof T, factory?: () => T[keyof T] | Promise<T[keyof T]>): Promise<T[keyof T] | undefined> {
|
|
314
|
+
await this.ready;
|
|
315
|
+
|
|
316
|
+
let deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(
|
|
317
|
+
await this.driver.get(key),
|
|
318
|
+
this.secret
|
|
319
|
+
),
|
|
320
|
+
missing = false,
|
|
321
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
322
|
+
|
|
323
|
+
if (deserialized === undefined) {
|
|
324
|
+
missing = true;
|
|
325
|
+
}
|
|
326
|
+
else if (unwrapped.expired) {
|
|
327
|
+
this.driver.delete([key]);
|
|
328
|
+
missing = true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (missing) {
|
|
332
|
+
if (factory) {
|
|
333
|
+
let value = await factory();
|
|
334
|
+
|
|
335
|
+
this.set(key, value);
|
|
336
|
+
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return unwrapped.value;
|
|
117
344
|
}
|
|
118
345
|
|
|
119
346
|
async keys(): Promise<(keyof T)[]> {
|
|
120
|
-
|
|
347
|
+
await this.ready;
|
|
348
|
+
|
|
349
|
+
let all = await this.driver.keys();
|
|
350
|
+
|
|
351
|
+
return all.filter((k) => k as string !== VERSION_KEY);
|
|
121
352
|
}
|
|
122
353
|
|
|
123
|
-
length(): Promise<number> {
|
|
124
|
-
|
|
354
|
+
async length(): Promise<number> {
|
|
355
|
+
await this.ready;
|
|
356
|
+
|
|
357
|
+
return this.count();
|
|
125
358
|
}
|
|
126
359
|
|
|
127
|
-
map(fn: (value: T[keyof T], key: keyof T, i: number) => void | Promise<void>): Promise<void> {
|
|
128
|
-
|
|
129
|
-
|
|
360
|
+
async map(fn: (value: T[keyof T], key: keyof T, i: number) => void | Promise<void>): Promise<void> {
|
|
361
|
+
await this.ready;
|
|
362
|
+
|
|
363
|
+
let expired: (keyof T)[] = [],
|
|
364
|
+
j = 0;
|
|
365
|
+
|
|
366
|
+
await this.driver.map(async (raw, key) => {
|
|
367
|
+
if (key as string === VERSION_KEY) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret),
|
|
372
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
130
373
|
|
|
131
|
-
if (
|
|
132
|
-
|
|
374
|
+
if (deserialized === undefined) {
|
|
375
|
+
return;
|
|
133
376
|
}
|
|
377
|
+
|
|
378
|
+
if (unwrapped.expired) {
|
|
379
|
+
expired.push(key);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await fn(unwrapped.value, key, j++);
|
|
134
384
|
});
|
|
385
|
+
|
|
386
|
+
if (expired.length > 0) {
|
|
387
|
+
this.driver.delete(expired);
|
|
388
|
+
}
|
|
135
389
|
}
|
|
136
390
|
|
|
137
391
|
async only(...keys: (keyof T)[]): Promise<T> {
|
|
138
|
-
|
|
392
|
+
await this.ready;
|
|
393
|
+
|
|
394
|
+
let expired: (keyof T)[] = [],
|
|
395
|
+
raw = await this.driver.only(keys),
|
|
139
396
|
result = {} as T;
|
|
140
397
|
|
|
141
398
|
for (let [key, value] of raw) {
|
|
142
|
-
let deserialized = await deserialize<T[keyof T]
|
|
399
|
+
let deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(value, this.secret),
|
|
400
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
143
401
|
|
|
144
|
-
if (deserialized
|
|
145
|
-
|
|
402
|
+
if (deserialized === undefined) {
|
|
403
|
+
continue;
|
|
146
404
|
}
|
|
405
|
+
|
|
406
|
+
if (unwrapped.expired) {
|
|
407
|
+
expired.push(key);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
result[key] = unwrapped.value;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (expired.length > 0) {
|
|
415
|
+
this.driver.delete(expired);
|
|
147
416
|
}
|
|
148
417
|
|
|
149
418
|
return result;
|
|
150
419
|
}
|
|
151
420
|
|
|
421
|
+
async persist(key: keyof T): Promise<boolean> {
|
|
422
|
+
await this.ready;
|
|
423
|
+
|
|
424
|
+
let raw = await this.driver.get(key),
|
|
425
|
+
deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret);
|
|
426
|
+
|
|
427
|
+
if (deserialized === undefined) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
432
|
+
|
|
433
|
+
if (unwrapped.expired) {
|
|
434
|
+
this.driver.delete([key]);
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!unwrapped.hasTTL) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return this.driver.set(
|
|
443
|
+
key,
|
|
444
|
+
await serialize(unwrapped.value, this.secret) as T[keyof T]
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
152
448
|
async replace(values: Partial<T>): Promise<string[]> {
|
|
449
|
+
await this.ready;
|
|
450
|
+
|
|
153
451
|
let entries: [keyof T, unknown][] = [],
|
|
154
|
-
failed: string[] = []
|
|
452
|
+
failed: string[] = [],
|
|
453
|
+
oldValues = new Map<keyof T, T[keyof T] | undefined>();
|
|
155
454
|
|
|
156
455
|
for (let key in values) {
|
|
456
|
+
let raw = await this.driver.get(key),
|
|
457
|
+
deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret),
|
|
458
|
+
unwrapped = unwrap<T[keyof T]>(deserialized);
|
|
459
|
+
|
|
460
|
+
oldValues.set(key, deserialized === undefined ? undefined : unwrapped.value);
|
|
461
|
+
|
|
157
462
|
try {
|
|
158
463
|
entries.push([
|
|
159
464
|
key,
|
|
@@ -169,20 +474,96 @@ class Local<T> {
|
|
|
169
474
|
await this.driver.replace(entries as [keyof T, T[keyof T]][]);
|
|
170
475
|
}
|
|
171
476
|
|
|
477
|
+
for (let i = 0, n = entries.length; i < n; i++) {
|
|
478
|
+
let key = entries[i][0];
|
|
479
|
+
|
|
480
|
+
notify(this.globals, this.listeners, key, values[key] as T[keyof T], oldValues.get(key));
|
|
481
|
+
}
|
|
482
|
+
|
|
172
483
|
return failed;
|
|
173
484
|
}
|
|
174
485
|
|
|
175
|
-
async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
|
|
486
|
+
async set(key: keyof T, value: T[keyof T], options?: SetOptions): Promise<boolean> {
|
|
487
|
+
await this.ready;
|
|
488
|
+
|
|
176
489
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
490
|
+
let oldRaw = await this.driver.get(key),
|
|
491
|
+
oldDeserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(oldRaw, this.secret),
|
|
492
|
+
oldUnwrapped = unwrap<T[keyof T]>(oldDeserialized),
|
|
493
|
+
oldValue = oldDeserialized === undefined ? undefined : oldUnwrapped.value,
|
|
494
|
+
stored: T[keyof T] | string;
|
|
495
|
+
|
|
496
|
+
if (options?.ttl != null && options.ttl > 0) {
|
|
497
|
+
let envelope: TTLEnvelope<T[keyof T]> = {
|
|
498
|
+
__e: Date.now() + options.ttl,
|
|
499
|
+
__v: value
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
stored = await serialize(envelope, this.secret) as T[keyof T];
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
stored = await serialize(value, this.secret) as T[keyof T];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let result = await this.driver.set(key, stored as T[keyof T]);
|
|
509
|
+
|
|
510
|
+
notify(this.globals, this.listeners, key, value, oldValue);
|
|
511
|
+
|
|
512
|
+
return result;
|
|
181
513
|
}
|
|
182
514
|
catch {
|
|
183
515
|
return false;
|
|
184
516
|
}
|
|
185
517
|
}
|
|
518
|
+
|
|
519
|
+
subscribe(callback: GlobalCallback<T>): () => void;
|
|
520
|
+
subscribe<K extends keyof T>(key: K, callback: KeyCallback<T, K>): () => void;
|
|
521
|
+
subscribe<K extends keyof T>(keyOrCallback: K | GlobalCallback<T>, callback?: KeyCallback<T, K>): () => void {
|
|
522
|
+
if (typeof keyOrCallback === 'function') {
|
|
523
|
+
let cb = keyOrCallback as GlobalCallback<T>;
|
|
524
|
+
|
|
525
|
+
this.globals.add(cb);
|
|
526
|
+
|
|
527
|
+
return () => { this.globals.delete(cb); };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let cb = callback as KeyCallback<T, K>,
|
|
531
|
+
key = keyOrCallback as K,
|
|
532
|
+
set = this.listeners.get(key);
|
|
533
|
+
|
|
534
|
+
if (!set) {
|
|
535
|
+
set = new Set();
|
|
536
|
+
this.listeners.set(key, set);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
set.add(cb as KeyCallback<T>);
|
|
540
|
+
|
|
541
|
+
return () => { set.delete(cb as KeyCallback<T>); };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async ttl(key: keyof T): Promise<number> {
|
|
545
|
+
await this.ready;
|
|
546
|
+
|
|
547
|
+
let raw = await this.driver.get(key),
|
|
548
|
+
deserialized = await deserialize<T[keyof T] | TTLEnvelope<T[keyof T]>>(raw, this.secret);
|
|
549
|
+
|
|
550
|
+
if (deserialized === undefined) {
|
|
551
|
+
return -1;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (!isEnvelope(deserialized)) {
|
|
555
|
+
return -1;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
let remaining = deserialized.__e - Date.now();
|
|
559
|
+
|
|
560
|
+
if (remaining <= 0) {
|
|
561
|
+
this.driver.delete([key]);
|
|
562
|
+
return -1;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return remaining;
|
|
566
|
+
}
|
|
186
567
|
}
|
|
187
568
|
|
|
188
569
|
|
|
@@ -190,4 +571,4 @@ export default <T>(options: Options, secret?: string) => {
|
|
|
190
571
|
return new Local<T>(options, secret);
|
|
191
572
|
};
|
|
192
573
|
export { DriverType } from './constants';
|
|
193
|
-
export type { Local };
|
|
574
|
+
export type { Local };
|
package/src/types.ts
CHANGED
|
@@ -16,11 +16,32 @@ interface Driver<T> {
|
|
|
16
16
|
|
|
17
17
|
type Filter<T> = (data: { i: number; key: keyof T; stop: VoidFunction; value: T[keyof T] }) => boolean | Promise<boolean>;
|
|
18
18
|
|
|
19
|
+
type MigrationContext = {
|
|
20
|
+
all(): Promise<Record<string, unknown>>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type MigrationFn = (old: MigrationContext) => Promise<Record<string, unknown>>;
|
|
24
|
+
|
|
19
25
|
type Options = {
|
|
20
|
-
driver?: DriverType.IndexedDB | DriverType.LocalStorage;
|
|
26
|
+
driver?: DriverType.IndexedDB | DriverType.LocalStorage | DriverType.Memory | DriverType.SessionStorage;
|
|
27
|
+
migrations?: Record<number, MigrationFn>;
|
|
21
28
|
name: string;
|
|
22
29
|
version: number;
|
|
23
30
|
};
|
|
24
31
|
|
|
32
|
+
type SetOptions = {
|
|
33
|
+
ttl?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type TTLEnvelope<V> = {
|
|
37
|
+
__e: number;
|
|
38
|
+
__v: V;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
type GlobalCallback<T> = (key: keyof T, newValue: T[keyof T] | undefined, oldValue: T[keyof T] | undefined) => void;
|
|
43
|
+
|
|
44
|
+
type KeyCallback<T, K extends keyof T = keyof T> = (newValue: T[K] | undefined, oldValue: T[K] | undefined) => void;
|
|
45
|
+
|
|
25
46
|
|
|
26
|
-
export type { Driver, Filter, Options };
|
|
47
|
+
export type { Driver, Filter, GlobalCallback, KeyCallback, MigrationContext, MigrationFn, Options, SetOptions, TTLEnvelope };
|