@esportsplus/web-storage 0.1.44 → 0.3.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.
@@ -0,0 +1,183 @@
1
+ import type { Driver } from '~/types';
2
+
3
+
4
+ let connections = new Map<string, Promise<IDBDatabase>>();
5
+
6
+
7
+ function connect(name: string, version: number): Promise<IDBDatabase> {
8
+ let key = `${name}:${version}`;
9
+
10
+ if (!connections.has(key)) {
11
+ connections.set(key, new Promise((resolve, reject) => {
12
+ let request = indexedDB.open(name, version);
13
+
14
+ request.onupgradeneeded = () => {
15
+ let db = request.result;
16
+
17
+ if (!db.objectStoreNames.contains(name)) {
18
+ db.createObjectStore(name);
19
+ }
20
+ };
21
+
22
+ request.onerror = () => reject(request.error);
23
+ request.onsuccess = () => resolve(request.result);
24
+ }));
25
+ }
26
+
27
+ return connections.get(key)!;
28
+ }
29
+
30
+ function promisify<T>(request: IDBRequest<T>): Promise<T> {
31
+ return new Promise((resolve, reject) => {
32
+ request.onerror = () => reject(request.error);
33
+ request.onsuccess = () => resolve(request.result);
34
+ });
35
+ }
36
+
37
+
38
+ class IndexedDBDriver<T> implements Driver<T> {
39
+
40
+ private connection: Promise<IDBDatabase>;
41
+
42
+ private name: string;
43
+
44
+
45
+ constructor(name: string, version: number) {
46
+ this.connection = connect(name, version);
47
+ this.name = name;
48
+ }
49
+
50
+
51
+ async all(): Promise<T> {
52
+ let db = await this.connection,
53
+ store = db.transaction(this.name, 'readonly').objectStore(this.name),
54
+ [keys, values] = await Promise.all([
55
+ promisify(store.getAllKeys()),
56
+ promisify(store.getAll())
57
+ ]),
58
+ result = {} as T;
59
+
60
+ for (let i = 0, n = keys.length; i < n; i++) {
61
+ result[keys[i] as keyof T] = values[i];
62
+ }
63
+
64
+ return result;
65
+ }
66
+
67
+ async clear(): Promise<void> {
68
+ let db = await this.connection,
69
+ store = db.transaction(this.name, 'readwrite').objectStore(this.name);
70
+
71
+ await promisify(store.clear());
72
+ }
73
+
74
+ async count(): Promise<number> {
75
+ let db = await this.connection,
76
+ store = db.transaction(this.name, 'readonly').objectStore(this.name);
77
+
78
+ return promisify(store.count());
79
+ }
80
+
81
+ async delete(keys: (keyof T)[]): Promise<void> {
82
+ let db = await this.connection,
83
+ tx = db.transaction(this.name, 'readwrite'),
84
+ store = tx.objectStore(this.name);
85
+
86
+ for (let i = 0, n = keys.length; i < n; i++) {
87
+ store.delete(keys[i] as IDBValidKey);
88
+ }
89
+
90
+ return new Promise((resolve, reject) => {
91
+ tx.oncomplete = () => resolve();
92
+ tx.onerror = () => reject(tx.error);
93
+ });
94
+ }
95
+
96
+ async get(key: keyof T): Promise<T[keyof T] | undefined> {
97
+ let db = await this.connection,
98
+ store = db.transaction(this.name, 'readonly').objectStore(this.name);
99
+
100
+ return promisify(store.get(key as IDBValidKey));
101
+ }
102
+
103
+ async keys(): Promise<(keyof T)[]> {
104
+ let db = await this.connection,
105
+ store = db.transaction(this.name, 'readonly').objectStore(this.name);
106
+
107
+ return promisify(store.getAllKeys()) as Promise<(keyof T)[]>;
108
+ }
109
+
110
+ async map(fn: (value: T[keyof T], key: keyof T, i: number) => void | Promise<void>): Promise<void> {
111
+ let db = await this.connection,
112
+ store = db.transaction(this.name, 'readonly').objectStore(this.name);
113
+
114
+ return new Promise((resolve, reject) => {
115
+ let cursor = store.openCursor(),
116
+ i = 0;
117
+
118
+ cursor.onerror = () => reject(cursor.error);
119
+ cursor.onsuccess = async () => {
120
+ let c = cursor.result;
121
+
122
+ if (c) {
123
+ await fn(c.value, c.key as keyof T, i++);
124
+ c.continue();
125
+ }
126
+ else {
127
+ resolve();
128
+ }
129
+ };
130
+ });
131
+ }
132
+
133
+ async only(keys: (keyof T)[]): Promise<Map<keyof T, T[keyof T]>> {
134
+ let db = await this.connection,
135
+ results = new Map<keyof T, T[keyof T]>(),
136
+ tx = db.transaction(this.name, 'readonly'),
137
+ store = tx.objectStore(this.name);
138
+
139
+ await Promise.all(
140
+ keys.map((key) =>
141
+ promisify(store.get(key as IDBValidKey)).then((value) => {
142
+ if (value !== undefined) {
143
+ results.set(key, value);
144
+ }
145
+ })
146
+ )
147
+ );
148
+
149
+ return results;
150
+ }
151
+
152
+ async replace(entries: [keyof T, T[keyof T]][]): Promise<void> {
153
+ let db = await this.connection,
154
+ tx = db.transaction(this.name, 'readwrite'),
155
+ store = tx.objectStore(this.name);
156
+
157
+ for (let i = 0; i < entries.length; i++) {
158
+ store.put(entries[i][1], entries[i][0] as IDBValidKey);
159
+ }
160
+
161
+ return new Promise((resolve, reject) => {
162
+ tx.oncomplete = () => resolve();
163
+ tx.onerror = () => reject(tx.error);
164
+ });
165
+ }
166
+
167
+ async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
168
+ try {
169
+ let db = await this.connection,
170
+ store = db.transaction(this.name, 'readwrite').objectStore(this.name);
171
+
172
+ await promisify(store.put(value, key as IDBValidKey));
173
+
174
+ return true;
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ }
181
+
182
+
183
+ export { IndexedDBDriver };
@@ -0,0 +1,131 @@
1
+ import type { Driver } from '~/types';
2
+
3
+
4
+ class LocalStorageDriver<T> implements Driver<T> {
5
+
6
+ private prefix: string;
7
+
8
+
9
+ constructor(name: string, version: number) {
10
+ this.prefix = `${name}:${version}:`;
11
+ }
12
+
13
+
14
+ private getKeys(): string[] {
15
+ let keys: string[] = [];
16
+
17
+ for (let i = 0, n = localStorage.length; i < n; i++) {
18
+ let key = localStorage.key(i);
19
+
20
+ if (key && key.startsWith(this.prefix)) {
21
+ keys.push(key.slice(this.prefix.length));
22
+ }
23
+ }
24
+
25
+ return keys;
26
+ }
27
+
28
+ private key(key: keyof T): string {
29
+ return this.prefix + String(key);
30
+ }
31
+
32
+ private parse(value: string | null): unknown {
33
+ if (value === null) {
34
+ return undefined;
35
+ }
36
+
37
+ try {
38
+ return JSON.parse(value);
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+
45
+
46
+ async all(): Promise<T> {
47
+ let keys = this.getKeys(),
48
+ result = {} as T;
49
+
50
+ for (let i = 0, n = keys.length; i < n; i++) {
51
+ let value = this.parse(localStorage.getItem(this.prefix + keys[i]));
52
+
53
+ if (value !== undefined) {
54
+ result[keys[i] as keyof T] = value as T[keyof T];
55
+ }
56
+ }
57
+
58
+ return result;
59
+ }
60
+
61
+ async clear(): Promise<void> {
62
+ let keys = this.getKeys();
63
+
64
+ for (let i = 0, n = keys.length; i < n; i++) {
65
+ localStorage.removeItem(this.prefix + keys[i]);
66
+ }
67
+ }
68
+
69
+ async count(): Promise<number> {
70
+ return this.getKeys().length;
71
+ }
72
+
73
+ async delete(keys: (keyof T)[]): Promise<void> {
74
+ for (let i = 0, n = keys.length; i < n; i++) {
75
+ localStorage.removeItem(this.key(keys[i]));
76
+ }
77
+ }
78
+
79
+ async get(key: keyof T): Promise<T[keyof T] | undefined> {
80
+ return this.parse(localStorage.getItem(this.key(key))) as T[keyof T] | undefined;
81
+ }
82
+
83
+ async keys(): Promise<(keyof T)[]> {
84
+ return this.getKeys() as (keyof T)[];
85
+ }
86
+
87
+ async map(fn: (value: T[keyof T], key: keyof T, i: number) => void | Promise<void>): Promise<void> {
88
+ let keys = this.getKeys();
89
+
90
+ for (let i = 0, n = keys.length; i < n; i++) {
91
+ let value = this.parse(localStorage.getItem(this.prefix + keys[i]));
92
+
93
+ if (value !== undefined) {
94
+ await fn(value as T[keyof T], keys[i] as keyof T, i);
95
+ }
96
+ }
97
+ }
98
+
99
+ async only(keys: (keyof T)[]): Promise<Map<keyof T, T[keyof T]>> {
100
+ let results = new Map<keyof T, T[keyof T]>();
101
+
102
+ for (let i = 0, n = keys.length; i < n; i++) {
103
+ let value = this.parse(localStorage.getItem(this.key(keys[i])));
104
+
105
+ if (value !== undefined) {
106
+ results.set(keys[i], value as T[keyof T]);
107
+ }
108
+ }
109
+
110
+ return results;
111
+ }
112
+
113
+ async replace(entries: [keyof T, T[keyof T]][]): Promise<void> {
114
+ for (let i = 0, n = entries.length; i < n; i++) {
115
+ localStorage.setItem(this.key(entries[i][0]), JSON.stringify(entries[i][1]));
116
+ }
117
+ }
118
+
119
+ async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
120
+ try {
121
+ localStorage.setItem(this.key(key), JSON.stringify(value));
122
+ return true;
123
+ }
124
+ catch {
125
+ return false;
126
+ }
127
+ }
128
+ }
129
+
130
+
131
+ export { LocalStorageDriver };
package/src/index.ts CHANGED
@@ -1 +1,192 @@
1
- export { default as local } from './local';
1
+ import { decrypt, encrypt } from '@esportsplus/crypto';
2
+ import type { Driver, Filter, Options } from './types';
3
+ import { DriverType } from './constants';
4
+ import { IndexedDBDriver } from '~/drivers/indexeddb';
5
+ import { LocalStorageDriver } from '~/drivers/localstorage';
6
+
7
+
8
+ async function deserialize<V>(value: unknown, secret: string | null): Promise<V | undefined> {
9
+ if (value === undefined || value === null) {
10
+ return undefined;
11
+ }
12
+
13
+ if (secret && typeof value === 'string') {
14
+ try {
15
+ value = await decrypt(value, secret);
16
+ value = JSON.parse(value as string);
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+
23
+ return value as V;
24
+ }
25
+
26
+ async function serialize<V>(value: V, secret: string | null): Promise<string | V> {
27
+ if (value === undefined || value === null) {
28
+ return value;
29
+ }
30
+
31
+ if (secret) {
32
+ return encrypt(JSON.stringify(value), secret);
33
+ }
34
+
35
+ return value;
36
+ }
37
+
38
+
39
+ class Local<T> {
40
+
41
+ private driver: Driver<T>;
42
+
43
+ private secret: string | null;
44
+
45
+
46
+ constructor(options: Options, secret?: string) {
47
+ this.secret = secret || null;
48
+
49
+ let { name, version = 1 } = options;
50
+
51
+ if (options.driver === DriverType.LocalStorage) {
52
+ this.driver = new LocalStorageDriver<T>(name, version);
53
+ }
54
+ else {
55
+ this.driver = new IndexedDBDriver<T>(name, version);
56
+ }
57
+ }
58
+
59
+
60
+ async all(): Promise<T> {
61
+ let raw = await this.driver.all(),
62
+ result = {} as T;
63
+
64
+ for (let key in raw) {
65
+ let value = await deserialize<T[keyof T]>(raw[key], this.secret);
66
+
67
+ if (value !== undefined) {
68
+ (result as Record<string, unknown>)[key] = value;
69
+ }
70
+ }
71
+
72
+ return result;
73
+ }
74
+
75
+ async clear(): Promise<void> {
76
+ return this.driver.clear();
77
+ }
78
+
79
+ async count(): Promise<number> {
80
+ return this.driver.count();
81
+ }
82
+
83
+ async delete(...keys: (keyof T)[]): Promise<void> {
84
+ return this.driver.delete(keys);
85
+ }
86
+
87
+ async filter(fn: Filter<T>): Promise<T> {
88
+ let i = 0,
89
+ result = {} as T,
90
+ stop = () => { stopped = true; },
91
+ stopped = false;
92
+
93
+ await this.driver.map(async (raw, key) => {
94
+ if (stopped) {
95
+ return;
96
+ }
97
+
98
+ let value = await deserialize<T[keyof T]>(raw, this.secret);
99
+
100
+ if (value === undefined) {
101
+ return;
102
+ }
103
+
104
+ if (await fn({ i: i++, key, stop, value })) {
105
+ result[key] = value;
106
+ }
107
+ });
108
+
109
+ return result;
110
+ }
111
+
112
+ async get(key: keyof T): Promise<T[keyof T] | undefined> {
113
+ return deserialize<T[keyof T]>(
114
+ await this.driver.get(key),
115
+ this.secret
116
+ );
117
+ }
118
+
119
+ async keys(): Promise<(keyof T)[]> {
120
+ return this.driver.keys();
121
+ }
122
+
123
+ length(): Promise<number> {
124
+ return this.driver.count();
125
+ }
126
+
127
+ map(fn: (value: T[keyof T], key: keyof T, i: number) => void | Promise<void>): Promise<void> {
128
+ return this.driver.map(async (raw, key, i) => {
129
+ let value = await deserialize<T[keyof T]>(raw, this.secret);
130
+
131
+ if (value !== undefined) {
132
+ await fn(value, key, i);
133
+ }
134
+ });
135
+ }
136
+
137
+ async only(...keys: (keyof T)[]): Promise<T> {
138
+ let raw = await this.driver.only(keys),
139
+ result = {} as T;
140
+
141
+ for (let [key, value] of raw) {
142
+ let deserialized = await deserialize<T[keyof T]>(value, this.secret);
143
+
144
+ if (deserialized !== undefined) {
145
+ result[key] = deserialized;
146
+ }
147
+ }
148
+
149
+ return result;
150
+ }
151
+
152
+ async replace(values: Partial<T>): Promise<string[]> {
153
+ let entries: [keyof T, unknown][] = [],
154
+ failed: string[] = [];
155
+
156
+ for (let key in values) {
157
+ try {
158
+ entries.push([
159
+ key,
160
+ await serialize(values[key], this.secret)
161
+ ]);
162
+ }
163
+ catch {
164
+ failed.push(key);
165
+ }
166
+ }
167
+
168
+ if (entries.length > 0) {
169
+ await this.driver.replace(entries as [keyof T, T[keyof T]][]);
170
+ }
171
+
172
+ return failed;
173
+ }
174
+
175
+ async set(key: keyof T, value: T[keyof T]): Promise<boolean> {
176
+ try {
177
+ return this.driver.set(
178
+ key,
179
+ await serialize(value, this.secret) as T[keyof T]
180
+ );
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ }
186
+ }
187
+
188
+
189
+ export default <T>(options: Options, secret?: string) => {
190
+ return new Local<T>(options, secret);
191
+ };
192
+ export { DriverType } from './constants';
package/src/types.ts CHANGED
@@ -1,23 +1,26 @@
1
- import localforage from 'localforage';
1
+ import { DriverType } from './constants';
2
2
 
3
3
 
4
- enum Driver {
5
- IndexedDB,
6
- LocalStorage
7
- };
8
-
9
- type Filter<T> = (data: { i: number, key: keyof T, stop: VoidFunction, value: T[keyof T] }) => boolean | Promise<boolean>;
4
+ interface Driver<T> {
5
+ all(): Promise<T>;
6
+ clear(): Promise<void>;
7
+ count(): Promise<number>;
8
+ delete(keys: (keyof T)[]): Promise<void>;
9
+ get(key: keyof T): Promise<T[keyof T] | undefined>;
10
+ keys(): Promise<(keyof T)[]>;
11
+ map(fn: (value: T[keyof T], key: keyof T, i: number) => void | Promise<void>): Promise<void>;
12
+ only(keys: (keyof T)[]): Promise<Map<keyof T, T[keyof T]>>;
13
+ replace(entries: [keyof T, T[keyof T]][]): Promise<void>;
14
+ set(key: keyof T, value: T[keyof T]): Promise<boolean>;
15
+ }
10
16
 
11
- type LocalForage = typeof localforage;
17
+ type Filter<T> = (data: { i: number; key: keyof T; stop: VoidFunction; value: T[keyof T] }) => boolean | Promise<boolean>;
12
18
 
13
19
  type Options = {
14
- description?: string;
15
- driver?: string | string[];
20
+ driver?: DriverType.IndexedDB | DriverType.LocalStorage;
16
21
  name: string;
17
- size?: number;
18
- version?: number;
22
+ version: number;
19
23
  };
20
24
 
21
25
 
22
- export { Driver };
23
- export type { Filter, LocalForage, Options };
26
+ export type { Driver, Filter, Options };
package/build/local.d.ts DELETED
@@ -1,19 +0,0 @@
1
- import { Filter, LocalForage, Options } from './types.js';
2
- declare class Local<T> {
3
- instance: LocalForage;
4
- iterate: LocalForage['iterate'];
5
- keys: LocalForage['keys'];
6
- length: LocalForage['length'];
7
- secret: null | string;
8
- constructor(options: Options, secret?: string);
9
- all(): Promise<T>;
10
- clear(): Promise<void>;
11
- delete(...keys: (keyof T)[]): Promise<void>;
12
- filter(fn: Filter<T>): Promise<T>;
13
- get(key: keyof T): Promise<unknown>;
14
- only(...keys: (keyof T)[]): Promise<T>;
15
- replace(values: T): Promise<string[]>;
16
- set(key: keyof T, value: T[keyof T]): Promise<boolean>;
17
- }
18
- declare const _default: <T>(options: Options, secret?: string) => Local<T>;
19
- export default _default;
package/build/local.js DELETED
@@ -1,119 +0,0 @@
1
- import { Driver } from './types.js';
2
- import { decrypt, encrypt } from '@esportsplus/crypto';
3
- import localforage from 'localforage';
4
- async function deserialize(value, secret = null) {
5
- if (secret && typeof value === 'string') {
6
- value = await decrypt(value, secret);
7
- }
8
- if (typeof value === 'string') {
9
- value = JSON.parse(value);
10
- }
11
- return value;
12
- }
13
- async function serialize(value, secret = null) {
14
- if (value === null || value === undefined) {
15
- return undefined;
16
- }
17
- value = JSON.stringify(value);
18
- if (secret) {
19
- value = await encrypt(value, secret);
20
- }
21
- return value;
22
- }
23
- class Local {
24
- instance;
25
- iterate;
26
- keys;
27
- length;
28
- secret = null;
29
- constructor(options, secret) {
30
- switch ((options.driver || Driver.IndexedDB)) {
31
- case Driver.LocalStorage:
32
- options.driver = localforage.LOCALSTORAGE;
33
- break;
34
- default:
35
- options.driver = localforage.INDEXEDDB;
36
- break;
37
- }
38
- this.instance = localforage.createInstance(Object.assign(options, { storeName: options.name }));
39
- this.iterate = this.instance.iterate;
40
- this.keys = this.instance.keys;
41
- this.length = this.instance.length;
42
- if (secret) {
43
- this.secret = secret;
44
- }
45
- }
46
- async all() {
47
- let stack = [], values = {};
48
- await this.instance.iterate((v, k) => {
49
- stack.push(deserialize(v, this.secret)
50
- .then((value) => {
51
- if (value === undefined) {
52
- return;
53
- }
54
- values[k] = value;
55
- })
56
- .catch(() => { }));
57
- });
58
- await Promise.allSettled(stack);
59
- return values;
60
- }
61
- async clear() {
62
- await this.instance.clear();
63
- }
64
- async delete(...keys) {
65
- let stack = [];
66
- for (let i = 0, n = keys.length; i < n; i++) {
67
- stack.push(this.instance.removeItem(keys[i]));
68
- }
69
- await Promise.allSettled(stack);
70
- }
71
- async filter(fn) {
72
- let stop = () => {
73
- stopped = true;
74
- }, stopped = false, values = {};
75
- await this.instance.iterate(async (v, k, i) => {
76
- let key = k, value = await deserialize(v, this.secret).catch(() => undefined);
77
- if (value === undefined) {
78
- return;
79
- }
80
- if (await fn({ i, key, stop, value })) {
81
- values[key] = value;
82
- }
83
- if (stopped) {
84
- return true;
85
- }
86
- });
87
- return values;
88
- }
89
- async get(key) {
90
- return await deserialize(await this.instance.getItem(key), this.secret).catch(() => undefined);
91
- }
92
- async only(...keys) {
93
- return await this.filter(({ key }) => keys.includes(key));
94
- }
95
- async replace(values) {
96
- let failed = [], stack = [];
97
- for (let key in values) {
98
- stack.push(this.set(key, values[key])
99
- .then((ok) => {
100
- if (ok) {
101
- return;
102
- }
103
- failed.push(key);
104
- }));
105
- }
106
- await Promise.allSettled(stack);
107
- return failed;
108
- }
109
- async set(key, value) {
110
- let ok = true;
111
- await this.instance.setItem(key, await serialize(value, this.secret)
112
- .catch(() => {
113
- ok = false;
114
- return undefined;
115
- }));
116
- return ok;
117
- }
118
- }
119
- export default (options, secret) => new Local(options, secret);