@esportsplus/web-storage 0.4.0 → 0.5.1

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { decrypt, encrypt } from '@esportsplus/utilities';
1
+ import { encryption } 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';
@@ -11,14 +11,13 @@ function isEnvelope(value) {
11
11
  && '__e' in value
12
12
  && '__v' in value;
13
13
  }
14
- async function deserialize(value, secret) {
14
+ async function deserialize(value, cipher) {
15
15
  if (value === undefined || value === null) {
16
16
  return undefined;
17
17
  }
18
- if (secret && typeof value === 'string') {
18
+ if (cipher && typeof value === 'string') {
19
19
  try {
20
- value = await decrypt(value, secret);
21
- value = JSON.parse(value);
20
+ value = await cipher.decrypt(value);
22
21
  }
23
22
  catch {
24
23
  return undefined;
@@ -26,12 +25,12 @@ async function deserialize(value, secret) {
26
25
  }
27
26
  return value;
28
27
  }
29
- async function serialize(value, secret) {
28
+ async function serialize(value, cipher) {
30
29
  if (value === undefined || value === null) {
31
30
  return value;
32
31
  }
33
- if (secret) {
34
- return encrypt(JSON.stringify(value), secret);
32
+ if (cipher) {
33
+ return cipher.encrypt(value);
35
34
  }
36
35
  return value;
37
36
  }
@@ -70,11 +69,11 @@ async function migrate(driver, migrations, version) {
70
69
  }
71
70
  }
72
71
  let transformed = await migrations[keys[i]]({ all: () => Promise.resolve(data) });
73
- await driver.clear();
74
72
  let entries = [];
75
73
  for (let key in transformed) {
76
74
  entries.push([key, transformed[key]]);
77
75
  }
76
+ await driver.clear();
78
77
  if (entries.length > 0) {
79
78
  await driver.replace(entries);
80
79
  }
@@ -82,16 +81,16 @@ async function migrate(driver, migrations, version) {
82
81
  await driver.set(VERSION_KEY, version);
83
82
  }
84
83
  class Local {
84
+ cipher;
85
85
  driver;
86
86
  globals;
87
87
  listeners;
88
88
  ready;
89
- secret;
90
89
  version;
91
90
  constructor(options, secret) {
91
+ this.cipher = null;
92
92
  this.globals = new Set();
93
93
  this.listeners = new Map();
94
- this.secret = secret || null;
95
94
  let { migrations, name, version = 1 } = options;
96
95
  this.version = version;
97
96
  if (options.driver === DriverType.LocalStorage) {
@@ -106,11 +105,14 @@ class Local {
106
105
  else {
107
106
  this.driver = new IndexedDBDriver(name, version);
108
107
  }
108
+ let init = secret
109
+ ? encryption(secret).then((c) => { this.cipher = c; })
110
+ : Promise.resolve();
109
111
  if (migrations) {
110
- this.ready = migrate(this.driver, migrations, version);
112
+ this.ready = init.then(() => migrate(this.driver, migrations, version));
111
113
  }
112
114
  else {
113
- this.ready = Promise.resolve();
115
+ this.ready = init;
114
116
  }
115
117
  }
116
118
  async all() {
@@ -120,7 +122,7 @@ class Local {
120
122
  if (key === VERSION_KEY) {
121
123
  continue;
122
124
  }
123
- let deserialized = await deserialize(raw[key], this.secret), unwrapped = unwrap(deserialized);
125
+ let deserialized = await deserialize(raw[key], this.cipher), unwrapped = unwrap(deserialized);
124
126
  if (deserialized === undefined) {
125
127
  continue;
126
128
  }
@@ -131,7 +133,7 @@ class Local {
131
133
  result[key] = unwrapped.value;
132
134
  }
133
135
  if (expired.length > 0) {
134
- this.driver.delete(expired);
136
+ await this.driver.delete(expired);
135
137
  }
136
138
  return result;
137
139
  }
@@ -151,7 +153,7 @@ class Local {
151
153
  if (key === VERSION_KEY) {
152
154
  return;
153
155
  }
154
- let deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
156
+ let deserialized = await deserialize(raw, this.cipher), unwrapped = unwrap(deserialized);
155
157
  if (deserialized !== undefined && unwrapped.expired) {
156
158
  expired.push(key);
157
159
  oldValues.set(key, unwrapped.value);
@@ -166,15 +168,32 @@ class Local {
166
168
  }
167
169
  async count() {
168
170
  await this.ready;
169
- let total = await this.driver.count(), raw = await this.driver.get(VERSION_KEY);
170
- return raw !== undefined ? total - 1 : total;
171
+ let expired = [], total = 0;
172
+ await this.driver.map(async (raw, key) => {
173
+ if (key === VERSION_KEY) {
174
+ return;
175
+ }
176
+ let deserialized = await deserialize(raw, this.cipher), unwrapped = unwrap(deserialized);
177
+ if (deserialized === undefined) {
178
+ return;
179
+ }
180
+ if (unwrapped.expired) {
181
+ expired.push(key);
182
+ return;
183
+ }
184
+ total++;
185
+ });
186
+ if (expired.length > 0) {
187
+ await this.driver.delete(expired);
188
+ }
189
+ return total;
171
190
  }
172
191
  async delete(...keys) {
173
192
  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);
193
+ let oldValues = new Map(), raw = await this.driver.only(keys);
194
+ for (let [key, value] of raw) {
195
+ let deserialized = await deserialize(value, this.cipher), unwrapped = unwrap(deserialized);
196
+ oldValues.set(key, deserialized === undefined ? undefined : unwrapped.value);
178
197
  }
179
198
  await this.driver.delete(keys);
180
199
  for (let i = 0, n = keys.length; i < n; i++) {
@@ -188,7 +207,7 @@ class Local {
188
207
  if (stopped || key === VERSION_KEY) {
189
208
  return;
190
209
  }
191
- let deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
210
+ let deserialized = await deserialize(raw, this.cipher), unwrapped = unwrap(deserialized);
192
211
  if (deserialized === undefined) {
193
212
  return;
194
213
  }
@@ -201,18 +220,18 @@ class Local {
201
220
  }
202
221
  });
203
222
  if (expired.length > 0) {
204
- this.driver.delete(expired);
223
+ await this.driver.delete(expired);
205
224
  }
206
225
  return result;
207
226
  }
208
227
  async get(key, factory) {
209
228
  await this.ready;
210
- let deserialized = await deserialize(await this.driver.get(key), this.secret), missing = false, unwrapped = unwrap(deserialized);
229
+ let deserialized = await deserialize(await this.driver.get(key), this.cipher), missing = false, unwrapped = unwrap(deserialized);
211
230
  if (deserialized === undefined) {
212
231
  missing = true;
213
232
  }
214
233
  else if (unwrapped.expired) {
215
- this.driver.delete([key]);
234
+ await this.driver.delete([key]);
216
235
  missing = true;
217
236
  }
218
237
  if (missing) {
@@ -227,8 +246,25 @@ class Local {
227
246
  }
228
247
  async keys() {
229
248
  await this.ready;
230
- let all = await this.driver.keys();
231
- return all.filter((k) => k !== VERSION_KEY);
249
+ let expired = [], result = [];
250
+ await this.driver.map(async (raw, key) => {
251
+ if (key === VERSION_KEY) {
252
+ return;
253
+ }
254
+ let deserialized = await deserialize(raw, this.cipher), unwrapped = unwrap(deserialized);
255
+ if (deserialized === undefined) {
256
+ return;
257
+ }
258
+ if (unwrapped.expired) {
259
+ expired.push(key);
260
+ return;
261
+ }
262
+ result.push(key);
263
+ });
264
+ if (expired.length > 0) {
265
+ await this.driver.delete(expired);
266
+ }
267
+ return result;
232
268
  }
233
269
  async length() {
234
270
  await this.ready;
@@ -241,7 +277,7 @@ class Local {
241
277
  if (key === VERSION_KEY) {
242
278
  return;
243
279
  }
244
- let deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
280
+ let deserialized = await deserialize(raw, this.cipher), unwrapped = unwrap(deserialized);
245
281
  if (deserialized === undefined) {
246
282
  return;
247
283
  }
@@ -252,14 +288,14 @@ class Local {
252
288
  await fn(unwrapped.value, key, j++);
253
289
  });
254
290
  if (expired.length > 0) {
255
- this.driver.delete(expired);
291
+ await this.driver.delete(expired);
256
292
  }
257
293
  }
258
294
  async only(...keys) {
259
295
  await this.ready;
260
296
  let expired = [], raw = await this.driver.only(keys), result = {};
261
297
  for (let [key, value] of raw) {
262
- let deserialized = await deserialize(value, this.secret), unwrapped = unwrap(deserialized);
298
+ let deserialized = await deserialize(value, this.cipher), unwrapped = unwrap(deserialized);
263
299
  if (deserialized === undefined) {
264
300
  continue;
265
301
  }
@@ -270,36 +306,38 @@ class Local {
270
306
  result[key] = unwrapped.value;
271
307
  }
272
308
  if (expired.length > 0) {
273
- this.driver.delete(expired);
309
+ await this.driver.delete(expired);
274
310
  }
275
311
  return result;
276
312
  }
277
313
  async persist(key) {
278
314
  await this.ready;
279
- let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.secret);
315
+ let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.cipher);
280
316
  if (deserialized === undefined) {
281
317
  return false;
282
318
  }
283
319
  let unwrapped = unwrap(deserialized);
284
320
  if (unwrapped.expired) {
285
- this.driver.delete([key]);
321
+ await this.driver.delete([key]);
286
322
  return false;
287
323
  }
288
324
  if (!unwrapped.hasTTL) {
289
325
  return true;
290
326
  }
291
- return this.driver.set(key, await serialize(unwrapped.value, this.secret));
327
+ return this.driver.set(key, await serialize(unwrapped.value, this.cipher));
292
328
  }
293
329
  async replace(values) {
294
330
  await this.ready;
295
- let entries = [], failed = [], oldValues = new Map();
296
- for (let key in values) {
297
- let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.secret), unwrapped = unwrap(deserialized);
331
+ let entries = [], failed = [], fetchKeys = Object.keys(values), oldValues = new Map(), raw = await this.driver.only(fetchKeys);
332
+ for (let key of fetchKeys) {
333
+ let value = raw.get(key), deserialized = value !== undefined
334
+ ? await deserialize(value, this.cipher)
335
+ : undefined, unwrapped = unwrap(deserialized);
298
336
  oldValues.set(key, deserialized === undefined ? undefined : unwrapped.value);
299
337
  try {
300
338
  entries.push([
301
339
  key,
302
- await serialize(values[key], this.secret)
340
+ await serialize(values[key], this.cipher)
303
341
  ]);
304
342
  }
305
343
  catch {
@@ -318,16 +356,16 @@ class Local {
318
356
  async set(key, value, options) {
319
357
  await this.ready;
320
358
  try {
321
- let oldRaw = await this.driver.get(key), oldDeserialized = await deserialize(oldRaw, this.secret), oldUnwrapped = unwrap(oldDeserialized), oldValue = oldDeserialized === undefined ? undefined : oldUnwrapped.value, stored;
359
+ let oldRaw = await this.driver.get(key), oldDeserialized = await deserialize(oldRaw, this.cipher), oldUnwrapped = unwrap(oldDeserialized), oldValue = oldDeserialized === undefined ? undefined : oldUnwrapped.value, stored;
322
360
  if (options?.ttl != null && options.ttl > 0) {
323
361
  let envelope = {
324
362
  __e: Date.now() + options.ttl,
325
363
  __v: value
326
364
  };
327
- stored = await serialize(envelope, this.secret);
365
+ stored = await serialize(envelope, this.cipher);
328
366
  }
329
367
  else {
330
- stored = await serialize(value, this.secret);
368
+ stored = await serialize(value, this.cipher);
331
369
  }
332
370
  let result = await this.driver.set(key, stored);
333
371
  notify(this.globals, this.listeners, key, value, oldValue);
@@ -353,7 +391,7 @@ class Local {
353
391
  }
354
392
  async ttl(key) {
355
393
  await this.ready;
356
- let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.secret);
394
+ let raw = await this.driver.get(key), deserialized = await deserialize(raw, this.cipher);
357
395
  if (deserialized === undefined) {
358
396
  return -1;
359
397
  }
@@ -362,7 +400,7 @@ class Local {
362
400
  }
363
401
  let remaining = deserialized.__e - Date.now();
364
402
  if (remaining <= 0) {
365
- this.driver.delete([key]);
403
+ await this.driver.delete([key]);
366
404
  return -1;
367
405
  }
368
406
  return remaining;
package/build/lz.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare const compress: (input: string) => string;
2
+ declare const decompress: (compressed: string) => string;
3
+ export { compress, decompress };
package/build/lz.js ADDED
@@ -0,0 +1,138 @@
1
+ let MAX_DECOMPRESSED_SIZE = 10_485_760;
2
+ function emitLiteral(ctx, ch) {
3
+ let code = ch.charCodeAt(0);
4
+ if (code < 256) {
5
+ writeBits(ctx, ctx.numBits, 0);
6
+ writeBits(ctx, 8, code);
7
+ }
8
+ else {
9
+ writeBits(ctx, ctx.numBits, 1);
10
+ writeBits(ctx, 16, code);
11
+ }
12
+ }
13
+ function readBits(ctx, n) {
14
+ let result = 0;
15
+ for (let i = 0; i < n; i++) {
16
+ if (ctx.bitPos > 15) {
17
+ if (ctx.pos >= ctx.compressed.length) {
18
+ throw new Error('LZ: unexpected end of compressed data');
19
+ }
20
+ ctx.currentValue = ctx.compressed.charCodeAt(ctx.pos++) - 1;
21
+ ctx.bitPos = 0;
22
+ }
23
+ result = (result << 1) | ((ctx.currentValue >> (15 - ctx.bitPos)) & 1);
24
+ ctx.bitPos++;
25
+ }
26
+ return result;
27
+ }
28
+ function writeBits(ctx, n, value) {
29
+ for (let i = n - 1; i >= 0; i--) {
30
+ ctx.buffer = (ctx.buffer << 1) | ((value >> i) & 1);
31
+ ctx.bitsInBuffer++;
32
+ if (ctx.bitsInBuffer === 16) {
33
+ ctx.output.push(ctx.buffer + 1);
34
+ ctx.buffer = 0;
35
+ ctx.bitsInBuffer = 0;
36
+ }
37
+ }
38
+ }
39
+ const compress = (input) => {
40
+ if (!input) {
41
+ return '';
42
+ }
43
+ let ctx = { bitsInBuffer: 0, buffer: 0, numBits: 2, output: [] }, dictSize = 3, dictionary = new Map(), w = '';
44
+ for (let i = 0, n = input.length; i < n; i++) {
45
+ let c = input[i], wc = w + c;
46
+ if (dictionary.has(wc)) {
47
+ w = wc;
48
+ continue;
49
+ }
50
+ if (w.length > 0) {
51
+ if (dictionary.has(w)) {
52
+ writeBits(ctx, ctx.numBits, dictionary.get(w));
53
+ }
54
+ else {
55
+ emitLiteral(ctx, w);
56
+ }
57
+ dictionary.set(wc, dictSize++);
58
+ if (dictSize > (1 << ctx.numBits)) {
59
+ ctx.numBits++;
60
+ }
61
+ }
62
+ w = c;
63
+ }
64
+ if (w.length > 0) {
65
+ if (dictionary.has(w)) {
66
+ writeBits(ctx, ctx.numBits, dictionary.get(w));
67
+ }
68
+ else {
69
+ emitLiteral(ctx, w);
70
+ }
71
+ }
72
+ dictSize++;
73
+ if (dictSize > (1 << ctx.numBits)) {
74
+ ctx.numBits++;
75
+ }
76
+ writeBits(ctx, ctx.numBits, 2);
77
+ if (ctx.bitsInBuffer > 0) {
78
+ ctx.output.push(((ctx.buffer << (16 - ctx.bitsInBuffer)) & 0xFFFF) + 1);
79
+ }
80
+ ctx.output.push((ctx.bitsInBuffer === 0 ? 16 : ctx.bitsInBuffer) + 1);
81
+ return String.fromCharCode(...ctx.output);
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 parts = [entry], totalLength = entry.length, 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
+ parts.push(entry);
130
+ totalLength += entry.length;
131
+ if (totalLength > MAX_DECOMPRESSED_SIZE) {
132
+ throw new Error('LZ: decompressed output exceeds size limit');
133
+ }
134
+ w = entry;
135
+ }
136
+ return parts.join('');
137
+ };
138
+ export { compress, decompress };
package/build/types.d.ts CHANGED
@@ -36,4 +36,4 @@ type TTLEnvelope<V> = {
36
36
  };
37
37
  type GlobalCallback<T> = (key: keyof T, newValue: T[keyof T] | undefined, oldValue: T[keyof T] | undefined) => void;
38
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 };
39
+ export type { Driver, Filter, GlobalCallback, KeyCallback, MigrationFn, Options, SetOptions, TTLEnvelope };
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
- "@esportsplus/utilities": "^0.26.0"
4
+ "@esportsplus/utilities": "^0.28.0"
5
5
  },
6
6
  "description": "Web storage utility",
7
7
  "devDependencies": {
8
- "@esportsplus/typescript": "^0.9.2",
8
+ "@esportsplus/typescript": "^0.29.1",
9
9
  "fake-indexeddb": "^6.2.5",
10
- "happy-dom": "^20.8.8",
11
- "vitest": "^4.1.1"
10
+ "happy-dom": "^20.8.9",
11
+ "vitest": "^4.1.4"
12
12
  },
13
13
  "main": "build/index.js",
14
14
  "name": "@esportsplus/web-storage",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "type": "module",
21
21
  "types": "build/index.d.ts",
22
- "version": "0.4.0",
22
+ "version": "0.5.1",
23
23
  "scripts": {
24
24
  "build": "tsc && tsc-alias",
25
25
  "test": "vitest run",
@@ -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(),
@@ -67,7 +78,15 @@ class LocalStorageDriver<T> implements Driver<T> {
67
78
  }
68
79
 
69
80
  async count(): Promise<number> {
70
- return this.getKeys().length;
81
+ let count = 0;
82
+
83
+ for (let i = 0, n = localStorage.length; i < n; i++) {
84
+ if (localStorage.key(i)?.startsWith(this.prefix)) {
85
+ count++;
86
+ }
87
+ }
88
+
89
+ return count;
71
90
  }
72
91
 
73
92
  async delete(keys: (keyof T)[]): Promise<void> {
@@ -112,13 +131,13 @@ class LocalStorageDriver<T> implements Driver<T> {
112
131
 
113
132
  async replace(entries: [keyof T, T[keyof T]][]): Promise<void> {
114
133
  for (let i = 0, n = entries.length; i < n; i++) {
115
- localStorage.setItem(this.key(entries[i][0]), JSON.stringify(entries[i][1]));
134
+ localStorage.setItem(this.key(entries[i][0]), this.serialize(entries[i][1]));
116
135
  }
117
136
  }
118
137
 
119
138
  async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
120
139
  try {
121
- localStorage.setItem(this.key(key), JSON.stringify(value));
140
+ localStorage.setItem(this.key(key), this.serialize(value));
122
141
  return true;
123
142
  }
124
143
  catch {
@@ -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 SessionStorageDriver<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 SessionStorageDriver<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(),
@@ -67,7 +78,15 @@ class SessionStorageDriver<T> implements Driver<T> {
67
78
  }
68
79
 
69
80
  async count(): Promise<number> {
70
- return this.getKeys().length;
81
+ let count = 0;
82
+
83
+ for (let i = 0, n = sessionStorage.length; i < n; i++) {
84
+ if (sessionStorage.key(i)?.startsWith(this.prefix)) {
85
+ count++;
86
+ }
87
+ }
88
+
89
+ return count;
71
90
  }
72
91
 
73
92
  async delete(keys: (keyof T)[]): Promise<void> {
@@ -112,13 +131,13 @@ class SessionStorageDriver<T> implements Driver<T> {
112
131
 
113
132
  async replace(entries: [keyof T, T[keyof T]][]): Promise<void> {
114
133
  for (let i = 0, n = entries.length; i < n; i++) {
115
- sessionStorage.setItem(this.key(entries[i][0]), JSON.stringify(entries[i][1]));
134
+ sessionStorage.setItem(this.key(entries[i][0]), this.serialize(entries[i][1]));
116
135
  }
117
136
  }
118
137
 
119
138
  async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
120
139
  try {
121
- sessionStorage.setItem(this.key(key), JSON.stringify(value));
140
+ sessionStorage.setItem(this.key(key), this.serialize(value));
122
141
  return true;
123
142
  }
124
143
  catch {