@jeanharo98/typed-storage 0.1.7 → 0.1.8
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/dist/core/create-storage.d.ts +2 -0
- package/dist/core/create-storage.js +31 -0
- package/dist/core/memory-storage.d.ts +6 -0
- package/dist/core/memory-storage.js +14 -0
- package/dist/core/storage-signal.d.ts +2 -0
- package/dist/core/storage-signal.js +129 -0
- package/dist/features/heavy-storage/heavy-storage.d.ts +3 -0
- package/dist/features/heavy-storage/heavy-storage.js +50 -0
- package/dist/features/heavy-storage/heavy-storage.types.d.ts +16 -0
- package/dist/features/heavy-storage/heavy-storage.types.js +1 -0
- package/dist/features/heavy-storage/indexeddb-driver.d.ts +4 -0
- package/dist/features/heavy-storage/indexeddb-driver.js +40 -0
- package/dist/features/migrations.d.ts +1 -0
- package/dist/features/migrations.js +34 -0
- package/dist/features/xor.d.ts +3 -0
- package/dist/features/xor.js +14 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/src/{create-storage.ts → core/create-storage.ts} +6 -2
- package/src/{storage-signal.ts → core/storage-signal.ts} +8 -2
- package/src/features/heavy-storage/heavy-storage.ts +89 -0
- package/src/features/heavy-storage/heavy-storage.types.ts +21 -0
- package/src/features/heavy-storage/indexeddb-driver.ts +50 -0
- package/src/index.ts +2 -2
- package/src/heavy-storage.ts +0 -146
- /package/src/{memory-storage.ts → core/memory-storage.ts} +0 -0
- /package/src/{storage-signal.test.ts → core/storage-signal.test.ts} +0 -0
- /package/src/{heavy-storage.test.ts → features/heavy-storage/heavy-storage.test.ts} +0 -0
- /package/src/{migrations.test.ts → features/migrations.test.ts} +0 -0
- /package/src/{migrations.ts → features/migrations.ts} +0 -0
- /package/src/{xor.ts → features/xor.ts} +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createStorageSignal } from './storage-signal.js';
|
|
2
|
+
import { applyMigrations } from '../features/migrations.js';
|
|
3
|
+
function registerPrefix(prefix, sto) {
|
|
4
|
+
const registryKey = '__typed-storage__';
|
|
5
|
+
const existing = sto.getItem(registryKey);
|
|
6
|
+
const prefixes = existing ? JSON.parse(existing) : [];
|
|
7
|
+
if (prefix && !prefixes.includes(prefix)) {
|
|
8
|
+
prefixes.push(prefix);
|
|
9
|
+
sto.setItem(registryKey, JSON.stringify(prefixes));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function createStorage(schema, options) {
|
|
13
|
+
if (options?.version && options.migrations) {
|
|
14
|
+
const sto = options.storage === 'session' ? sessionStorage : localStorage;
|
|
15
|
+
const prefix = options.prefix ?? '';
|
|
16
|
+
applyMigrations(prefix, options.version, options.migrations, sto);
|
|
17
|
+
}
|
|
18
|
+
const sto = options?.storage === 'session' ? sessionStorage : localStorage;
|
|
19
|
+
registerPrefix(options?.prefix ?? '', sto);
|
|
20
|
+
const result = [];
|
|
21
|
+
let keys = Object.keys(schema);
|
|
22
|
+
for (let key of keys) {
|
|
23
|
+
result[key] = createStorageSignal(key, schema[key], options);
|
|
24
|
+
}
|
|
25
|
+
result.clear = () => {
|
|
26
|
+
for (let key of keys) {
|
|
27
|
+
result[key].reset();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import LZString from 'lz-string';
|
|
2
|
+
import { MemoryStorage } from "./memory-storage.js";
|
|
3
|
+
import { xorEncrypt, xorDecrypt } from '../features/xor.js';
|
|
4
|
+
function safeParseJSON(value, fallback) {
|
|
5
|
+
if (!value)
|
|
6
|
+
return { value: fallback };
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(value);
|
|
9
|
+
if (parsed && typeof parsed === 'object' && 'value' in parsed) {
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
return { value: JSON.parse(value) };
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.warn(`Error al parsear JSON de localStorage. Usando valor por defecto.`, error);
|
|
16
|
+
return { value: fallback };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function getStorage(type) {
|
|
20
|
+
try {
|
|
21
|
+
const sto = type === 'session' ? sessionStorage : localStorage;
|
|
22
|
+
sto.setItem('__typed_storage_test__', '1');
|
|
23
|
+
sto.removeItem('__typed_storage_test__');
|
|
24
|
+
return sto;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
console.warn('Storage no disponible, usando memoria como fallback');
|
|
28
|
+
return new MemoryStorage();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function createStorageSignal(key, initialValue, options) {
|
|
32
|
+
let sto = getStorage(options?.storage ?? 'local');
|
|
33
|
+
if (options?.prefix) {
|
|
34
|
+
key = `${options.prefix}:${key}`;
|
|
35
|
+
}
|
|
36
|
+
const rawData = sto.getItem(key);
|
|
37
|
+
let savedData = options?.compress && rawData
|
|
38
|
+
? LZString.decompress(rawData)
|
|
39
|
+
: rawData;
|
|
40
|
+
if (options?.encrypt && options?.secret && savedData) {
|
|
41
|
+
try {
|
|
42
|
+
savedData = xorDecrypt(savedData, options.secret);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
savedData = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let currentValue;
|
|
49
|
+
const listeners = [];
|
|
50
|
+
function notify(value) {
|
|
51
|
+
listeners.forEach(cb => cb(value));
|
|
52
|
+
}
|
|
53
|
+
const item = safeParseJSON(savedData, initialValue);
|
|
54
|
+
if (item.expiresAt === undefined) {
|
|
55
|
+
currentValue = !savedData ? initialValue : item.value;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (Date.now() <= item.expiresAt) {
|
|
59
|
+
currentValue = item.value;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
sto.removeItem(key);
|
|
63
|
+
currentValue = initialValue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const signalBase = function () {
|
|
67
|
+
return currentValue;
|
|
68
|
+
};
|
|
69
|
+
if (options?.sync) {
|
|
70
|
+
window.addEventListener('storage', (event) => {
|
|
71
|
+
if (event.key === key) {
|
|
72
|
+
if (event.newValue === null) {
|
|
73
|
+
notify(initialValue);
|
|
74
|
+
return currentValue = initialValue;
|
|
75
|
+
}
|
|
76
|
+
let rawNewValue = options?.compress
|
|
77
|
+
? LZString.decompress(event.newValue)
|
|
78
|
+
: event.newValue;
|
|
79
|
+
if (options?.encrypt && options?.secret) {
|
|
80
|
+
try {
|
|
81
|
+
rawNewValue = xorDecrypt(rawNewValue, options.secret);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
rawNewValue = '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const item = safeParseJSON(rawNewValue, initialValue);
|
|
88
|
+
notify(item.value);
|
|
89
|
+
return currentValue = item.value;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
signalBase.set = function (newValue) {
|
|
94
|
+
currentValue = newValue;
|
|
95
|
+
notify(currentValue);
|
|
96
|
+
const dataToStore = JSON.stringify({
|
|
97
|
+
value: newValue,
|
|
98
|
+
expiresAt: options?.ttl ? Date.now() + options.ttl : undefined
|
|
99
|
+
});
|
|
100
|
+
let finalData = options?.compress
|
|
101
|
+
? LZString.compress(dataToStore)
|
|
102
|
+
: dataToStore;
|
|
103
|
+
if (options?.encrypt && options?.secret) {
|
|
104
|
+
finalData = xorEncrypt(finalData, options.secret);
|
|
105
|
+
}
|
|
106
|
+
sto.setItem(key, finalData);
|
|
107
|
+
};
|
|
108
|
+
signalBase.reset = function () {
|
|
109
|
+
currentValue = initialValue;
|
|
110
|
+
notify(currentValue);
|
|
111
|
+
const dataToStore = JSON.stringify(initialValue);
|
|
112
|
+
const finalData = options?.compress
|
|
113
|
+
? LZString.compress(dataToStore)
|
|
114
|
+
: dataToStore;
|
|
115
|
+
sto.setItem(key, finalData);
|
|
116
|
+
};
|
|
117
|
+
signalBase.has = function () {
|
|
118
|
+
return !!sto.getItem(key);
|
|
119
|
+
};
|
|
120
|
+
signalBase.remove = function () {
|
|
121
|
+
sto.removeItem(key);
|
|
122
|
+
currentValue = initialValue;
|
|
123
|
+
notify(currentValue);
|
|
124
|
+
};
|
|
125
|
+
signalBase.onChange = function (callback) {
|
|
126
|
+
listeners.push(callback);
|
|
127
|
+
};
|
|
128
|
+
return signalBase;
|
|
129
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { HeavySignal, HeavyStorageOptions, HeavyStorageResult, HeavyStorageSchema } from "./heavy-storage.types";
|
|
2
|
+
export declare function createHeavySignal<T>(key: string, initialValue: T, options?: HeavyStorageOptions): HeavySignal<T>;
|
|
3
|
+
export declare function createHeavyStorage<T extends HeavyStorageSchema>(schema: T, options?: HeavyStorageOptions): HeavyStorageResult<T>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { dbDelete, dbGet, dbSet, openDB } from "./indexeddb-driver";
|
|
2
|
+
export function createHeavySignal(key, initialValue, options) {
|
|
3
|
+
const dbName = options?.dbName ?? 'typed-storage-heavy';
|
|
4
|
+
const listeners = [];
|
|
5
|
+
function notify(value) {
|
|
6
|
+
listeners.forEach(cb => cb(value));
|
|
7
|
+
}
|
|
8
|
+
const signal = {};
|
|
9
|
+
signal.get = async function () {
|
|
10
|
+
const db = await openDB(dbName);
|
|
11
|
+
const stored = await dbGet(db, key);
|
|
12
|
+
if (stored === undefined)
|
|
13
|
+
return initialValue;
|
|
14
|
+
if (stored.expiresAt && Date.now() > stored.expiresAt) {
|
|
15
|
+
await dbDelete(db, key);
|
|
16
|
+
return initialValue;
|
|
17
|
+
}
|
|
18
|
+
return stored.value ?? initialValue;
|
|
19
|
+
};
|
|
20
|
+
signal.set = async function (value) {
|
|
21
|
+
const db = await openDB(dbName);
|
|
22
|
+
await dbSet(db, key, {
|
|
23
|
+
value,
|
|
24
|
+
expiresAt: options?.ttl ? Date.now() + options.ttl : undefined
|
|
25
|
+
});
|
|
26
|
+
notify(value);
|
|
27
|
+
};
|
|
28
|
+
signal.remove = async function () {
|
|
29
|
+
const db = await openDB(dbName);
|
|
30
|
+
await dbDelete(db, key);
|
|
31
|
+
notify(initialValue);
|
|
32
|
+
};
|
|
33
|
+
signal.onChange = function (callback) {
|
|
34
|
+
listeners.push(callback);
|
|
35
|
+
};
|
|
36
|
+
return signal;
|
|
37
|
+
}
|
|
38
|
+
export function createHeavyStorage(schema, options) {
|
|
39
|
+
const result = {};
|
|
40
|
+
const keys = Object.keys(schema);
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
result[key] = createHeavySignal(key, schema[key], options);
|
|
43
|
+
}
|
|
44
|
+
result.clear = async () => {
|
|
45
|
+
for (const key of keys) {
|
|
46
|
+
await result[key].remove();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface HeavyStorageOptions {
|
|
2
|
+
dbName?: string;
|
|
3
|
+
ttl?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface HeavySignal<T> {
|
|
6
|
+
get(): Promise<T>;
|
|
7
|
+
set(value: T): Promise<void>;
|
|
8
|
+
remove(): Promise<void>;
|
|
9
|
+
onChange(callback: (value: T) => void): void;
|
|
10
|
+
}
|
|
11
|
+
export type HeavyStorageSchema = Record<string, any>;
|
|
12
|
+
export type HeavyStorageResult<T extends HeavyStorageSchema> = {
|
|
13
|
+
[K in keyof T]: HeavySignal<T[K]>;
|
|
14
|
+
} & {
|
|
15
|
+
clear(): Promise<void>;
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function openDB(dbName: string): Promise<IDBDatabase>;
|
|
2
|
+
export declare function dbGet(db: IDBDatabase, key: string): Promise<any>;
|
|
3
|
+
export declare function dbSet(db: IDBDatabase, key: string, value: any): Promise<void>;
|
|
4
|
+
export declare function dbDelete(db: IDBDatabase, key: string): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function openDB(dbName) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const request = indexedDB.open(dbName, 1);
|
|
4
|
+
request.onupgradeneeded = (event) => {
|
|
5
|
+
const db = event.target.result;
|
|
6
|
+
if (!db.objectStoreNames.contains('storage')) {
|
|
7
|
+
db.createObjectStore('storage');
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
request.onsuccess = () => resolve(request.result);
|
|
11
|
+
request.onerror = () => reject(request.error);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function dbGet(db, key) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const transaction = db.transaction('storage', 'readonly');
|
|
17
|
+
const store = transaction.objectStore('storage');
|
|
18
|
+
const request = store.get(key);
|
|
19
|
+
request.onsuccess = () => resolve(request.result);
|
|
20
|
+
request.onerror = () => reject(request.error);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function dbSet(db, key, value) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const transaction = db.transaction('storage', 'readwrite');
|
|
26
|
+
const store = transaction.objectStore('storage');
|
|
27
|
+
const request = store.put(value, key);
|
|
28
|
+
request.onsuccess = () => resolve();
|
|
29
|
+
request.onerror = () => reject(request.error);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function dbDelete(db, key) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const transaction = db.transaction('storage', 'readwrite');
|
|
35
|
+
const store = transaction.objectStore('storage');
|
|
36
|
+
const request = store.delete(key);
|
|
37
|
+
request.onsuccess = () => resolve();
|
|
38
|
+
request.onerror = () => reject(request.error);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function applyMigrations(prefix: string, currentVersion: number, migrations: Record<number, (data: any) => any>, storage: Storage): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function applyMigrations(prefix, currentVersion, migrations, storage) {
|
|
2
|
+
const versionKey = `${prefix}__version__`;
|
|
3
|
+
const savedVersion = storage.getItem(versionKey);
|
|
4
|
+
if (!savedVersion) {
|
|
5
|
+
storage.setItem(versionKey, String(currentVersion));
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
let version = parseInt(savedVersion);
|
|
9
|
+
if (version >= currentVersion)
|
|
10
|
+
return;
|
|
11
|
+
const currentData = {};
|
|
12
|
+
for (let i = 0; i < storage.length; i++) {
|
|
13
|
+
const key = storage.key(i);
|
|
14
|
+
if (key && key.startsWith(prefix) && key !== versionKey) {
|
|
15
|
+
const value = storage.getItem(key);
|
|
16
|
+
if (value) {
|
|
17
|
+
const cleanKey = key.replace(`${prefix}:`, '');
|
|
18
|
+
currentData[cleanKey] = JSON.parse(value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
while (version < currentVersion) {
|
|
23
|
+
const migration = migrations[version];
|
|
24
|
+
if (migration) {
|
|
25
|
+
const migrated = migration(currentData);
|
|
26
|
+
Object.assign(currentData, migrated);
|
|
27
|
+
}
|
|
28
|
+
version++;
|
|
29
|
+
}
|
|
30
|
+
for (const [key, value] of Object.entries(currentData)) {
|
|
31
|
+
storage.setItem(`${prefix}:${key}`, JSON.stringify(value));
|
|
32
|
+
}
|
|
33
|
+
storage.setItem(versionKey, String(currentVersion));
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function xorTransform(text, secret) {
|
|
2
|
+
return text.split('').map((char, i) => {
|
|
3
|
+
const keyChar = secret[i % secret.length];
|
|
4
|
+
return String.fromCharCode(char.charCodeAt(0) ^ keyChar.charCodeAt(0));
|
|
5
|
+
}).join('');
|
|
6
|
+
}
|
|
7
|
+
export function xorEncrypt(text, secret) {
|
|
8
|
+
const xored = xorTransform(text, secret);
|
|
9
|
+
return btoa(xored);
|
|
10
|
+
}
|
|
11
|
+
export function xorDecrypt(text, secret) {
|
|
12
|
+
const xored = atob(text);
|
|
13
|
+
return xorTransform(xored, secret);
|
|
14
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createStorage } from './create-storage.js';
|
|
2
|
-
export { createHeavyStorage } from './heavy-storage.js';
|
|
1
|
+
export { createStorage } from './core/create-storage.js';
|
|
2
|
+
export { createHeavyStorage } from './features/heavy-storage/heavy-storage.js';
|
|
3
3
|
export type { StorageSignal, StorageSchema, StorageResult, StorageSignalOptions } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createStorage } from './create-storage.js';
|
|
2
|
-
export { createHeavyStorage } from './heavy-storage.js';
|
|
1
|
+
export { createStorage } from './core/create-storage.js';
|
|
2
|
+
export { createHeavyStorage } from './features/heavy-storage/heavy-storage.js';
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
// Types
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
StorageSchema,
|
|
4
|
+
StorageResult,
|
|
5
|
+
StorageSignalOptions
|
|
6
|
+
} from '../types.js';
|
|
3
7
|
|
|
4
8
|
// Storage Signal
|
|
5
9
|
import { createStorageSignal } from './storage-signal.js';
|
|
6
10
|
|
|
7
11
|
// Migraciones
|
|
8
|
-
import { applyMigrations } from '
|
|
12
|
+
import { applyMigrations } from '../features/migrations.js';
|
|
9
13
|
|
|
10
14
|
function registerPrefix (
|
|
11
15
|
prefix: string,
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import LZString from 'lz-string';
|
|
2
2
|
|
|
3
3
|
// Tipos
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
StorageSignal,
|
|
6
|
+
StorageSignalOptions
|
|
7
|
+
} from "../types.js";
|
|
5
8
|
|
|
6
9
|
// Memory
|
|
7
10
|
import { MemoryStorage } from "./memory-storage.js";
|
|
8
11
|
|
|
9
12
|
// Xor
|
|
10
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
xorEncrypt,
|
|
15
|
+
xorDecrypt
|
|
16
|
+
} from '../features/xor.js';
|
|
11
17
|
|
|
12
18
|
// Interface
|
|
13
19
|
interface StoredValue<T> {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Indexed
|
|
2
|
+
import {
|
|
3
|
+
dbDelete,
|
|
4
|
+
dbGet,
|
|
5
|
+
dbSet,
|
|
6
|
+
openDB
|
|
7
|
+
} from "./indexeddb-driver";
|
|
8
|
+
|
|
9
|
+
// Types
|
|
10
|
+
import {
|
|
11
|
+
HeavySignal,
|
|
12
|
+
HeavyStorageOptions,
|
|
13
|
+
HeavyStorageResult,
|
|
14
|
+
HeavyStorageSchema
|
|
15
|
+
} from "./heavy-storage.types";
|
|
16
|
+
|
|
17
|
+
export function createHeavySignal<T>(
|
|
18
|
+
key: string,
|
|
19
|
+
initialValue: T,
|
|
20
|
+
options?: HeavyStorageOptions
|
|
21
|
+
): HeavySignal<T> {
|
|
22
|
+
const dbName = options?.dbName ?? 'typed-storage-heavy';
|
|
23
|
+
const listeners: Array<(value: T) => void> = [];
|
|
24
|
+
|
|
25
|
+
function notify ( value: T ): void {
|
|
26
|
+
listeners.forEach(cb => cb(value));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const signal: any = {};
|
|
30
|
+
|
|
31
|
+
signal.get = async function(): Promise<T> {
|
|
32
|
+
const db = await openDB(dbName);
|
|
33
|
+
const stored = await dbGet(db, key);
|
|
34
|
+
|
|
35
|
+
if (stored === undefined) return initialValue;
|
|
36
|
+
|
|
37
|
+
// Verifica TTL si existe
|
|
38
|
+
if (stored.expiresAt && Date.now() > stored.expiresAt) {
|
|
39
|
+
await dbDelete(db, key);
|
|
40
|
+
return initialValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return stored.value ?? initialValue;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
signal.set = async function(value: T): Promise<void> {
|
|
47
|
+
const db = await openDB(dbName);
|
|
48
|
+
await dbSet(db, key, {
|
|
49
|
+
value,
|
|
50
|
+
expiresAt: options?.ttl ? Date.now() + options.ttl : undefined
|
|
51
|
+
});
|
|
52
|
+
notify(value);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
signal.remove = async function(): Promise<void> {
|
|
56
|
+
const db = await openDB(dbName);
|
|
57
|
+
await dbDelete(db, key);
|
|
58
|
+
notify(initialValue);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
signal.onChange = function ( callback: (value: T) => void ): void {
|
|
62
|
+
listeners.push(callback);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return signal as HeavySignal<T>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ===========================================
|
|
69
|
+
// Iteramos el schema completo
|
|
70
|
+
|
|
71
|
+
export function createHeavyStorage<T extends HeavyStorageSchema>(
|
|
72
|
+
schema: T,
|
|
73
|
+
options?: HeavyStorageOptions
|
|
74
|
+
): HeavyStorageResult<T> {
|
|
75
|
+
const result: any = {};
|
|
76
|
+
|
|
77
|
+
const keys = Object.keys(schema);
|
|
78
|
+
for (const key of keys) {
|
|
79
|
+
result[key] = createHeavySignal(key, schema[key], options);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
result.clear = async () => {
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
await result[key].remove();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return result as HeavyStorageResult<T>;
|
|
89
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface HeavyStorageOptions {
|
|
2
|
+
dbName?: string;
|
|
3
|
+
ttl?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface HeavySignal<T> {
|
|
7
|
+
get(): Promise<T>;
|
|
8
|
+
set(value: T): Promise<void>;
|
|
9
|
+
remove(): Promise<void>;
|
|
10
|
+
onChange(callback: (value: T) => void): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ==============================================
|
|
14
|
+
|
|
15
|
+
export type HeavyStorageSchema = Record<string, any>;
|
|
16
|
+
|
|
17
|
+
export type HeavyStorageResult<T extends HeavyStorageSchema> = {
|
|
18
|
+
[K in keyof T]: HeavySignal<T[K]>;
|
|
19
|
+
} & {
|
|
20
|
+
clear(): Promise<void>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Abrir/crear la base de datos
|
|
2
|
+
export function openDB(dbName: string): Promise<IDBDatabase> {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const request = indexedDB.open(dbName, 1);
|
|
5
|
+
|
|
6
|
+
request.onupgradeneeded = (event) => {
|
|
7
|
+
const db = (event.target as IDBOpenDBRequest).result;
|
|
8
|
+
// Crea el "object store" — equivalente a una tabla
|
|
9
|
+
if (!db.objectStoreNames.contains('storage')) {
|
|
10
|
+
db.createObjectStore('storage');
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
request.onsuccess = () => resolve(request.result);
|
|
15
|
+
request.onerror = () => reject(request.error);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function dbGet(db: IDBDatabase, key: string): Promise<any> {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const transaction = db.transaction('storage', 'readonly');
|
|
22
|
+
const store = transaction.objectStore('storage');
|
|
23
|
+
const request = store.get(key);
|
|
24
|
+
|
|
25
|
+
request.onsuccess = () => resolve(request.result);
|
|
26
|
+
request.onerror = () => reject(request.error);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function dbSet(db: IDBDatabase, key: string, value: any): Promise<void> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const transaction = db.transaction('storage', 'readwrite');
|
|
33
|
+
const store = transaction.objectStore('storage');
|
|
34
|
+
const request = store.put(value, key);
|
|
35
|
+
|
|
36
|
+
request.onsuccess = () => resolve();
|
|
37
|
+
request.onerror = () => reject(request.error);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function dbDelete(db: IDBDatabase, key: string): Promise<void> {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const transaction = db.transaction('storage', 'readwrite');
|
|
44
|
+
const store = transaction.objectStore('storage');
|
|
45
|
+
const request = store.delete(key);
|
|
46
|
+
|
|
47
|
+
request.onsuccess = () => resolve();
|
|
48
|
+
request.onerror = () => reject(request.error);
|
|
49
|
+
});
|
|
50
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { createStorage } from './create-storage.js';
|
|
2
|
-
export { createHeavyStorage } from './heavy-storage.js';
|
|
1
|
+
export { createStorage } from './core/create-storage.js';
|
|
2
|
+
export { createHeavyStorage } from './features/heavy-storage/heavy-storage.js';
|
|
3
3
|
export type {
|
|
4
4
|
StorageSignal,
|
|
5
5
|
StorageSchema,
|
package/src/heavy-storage.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
// Abrir/crear la base de datos
|
|
2
|
-
function openDB(dbName: string): Promise<IDBDatabase> {
|
|
3
|
-
return new Promise((resolve, reject) => {
|
|
4
|
-
const request = indexedDB.open(dbName, 1);
|
|
5
|
-
|
|
6
|
-
request.onupgradeneeded = (event) => {
|
|
7
|
-
const db = (event.target as IDBOpenDBRequest).result;
|
|
8
|
-
// Crea el "object store" — equivalente a una tabla
|
|
9
|
-
if (!db.objectStoreNames.contains('storage')) {
|
|
10
|
-
db.createObjectStore('storage');
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
request.onsuccess = () => resolve(request.result);
|
|
15
|
-
request.onerror = () => reject(request.error);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function dbGet(db: IDBDatabase, key: string): Promise<any> {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const transaction = db.transaction('storage', 'readonly');
|
|
22
|
-
const store = transaction.objectStore('storage');
|
|
23
|
-
const request = store.get(key);
|
|
24
|
-
|
|
25
|
-
request.onsuccess = () => resolve(request.result);
|
|
26
|
-
request.onerror = () => reject(request.error);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function dbSet(db: IDBDatabase, key: string, value: any): Promise<void> {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const transaction = db.transaction('storage', 'readwrite');
|
|
33
|
-
const store = transaction.objectStore('storage');
|
|
34
|
-
const request = store.put(value, key);
|
|
35
|
-
|
|
36
|
-
request.onsuccess = () => resolve();
|
|
37
|
-
request.onerror = () => reject(request.error);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function dbDelete(db: IDBDatabase, key: string): Promise<void> {
|
|
42
|
-
return new Promise((resolve, reject) => {
|
|
43
|
-
const transaction = db.transaction('storage', 'readwrite');
|
|
44
|
-
const store = transaction.objectStore('storage');
|
|
45
|
-
const request = store.delete(key);
|
|
46
|
-
|
|
47
|
-
request.onsuccess = () => resolve();
|
|
48
|
-
request.onerror = () => reject(request.error);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ===========================================
|
|
53
|
-
|
|
54
|
-
interface HeavyStorageOptions {
|
|
55
|
-
dbName?: string;
|
|
56
|
-
ttl?: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface HeavySignal<T> {
|
|
60
|
-
get(): Promise<T>;
|
|
61
|
-
set(value: T): Promise<void>;
|
|
62
|
-
remove(): Promise<void>;
|
|
63
|
-
onChange(callback: (value: T) => void): void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function createHeavySignal<T>(
|
|
67
|
-
key: string,
|
|
68
|
-
initialValue: T,
|
|
69
|
-
options?: HeavyStorageOptions
|
|
70
|
-
): HeavySignal<T> {
|
|
71
|
-
const dbName = options?.dbName ?? 'typed-storage-heavy';
|
|
72
|
-
const listeners: Array<(value: T) => void> = [];
|
|
73
|
-
|
|
74
|
-
function notify(value: T): void {
|
|
75
|
-
listeners.forEach(cb => cb(value));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const signal: any = {};
|
|
79
|
-
|
|
80
|
-
signal.get = async function(): Promise<T> {
|
|
81
|
-
const db = await openDB(dbName);
|
|
82
|
-
const stored = await dbGet(db, key);
|
|
83
|
-
|
|
84
|
-
if (stored === undefined) return initialValue;
|
|
85
|
-
|
|
86
|
-
// Verifica TTL si existe
|
|
87
|
-
if (stored.expiresAt && Date.now() > stored.expiresAt) {
|
|
88
|
-
await dbDelete(db, key);
|
|
89
|
-
return initialValue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return stored.value ?? initialValue;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
signal.set = async function(value: T): Promise<void> {
|
|
96
|
-
const db = await openDB(dbName);
|
|
97
|
-
await dbSet(db, key, {
|
|
98
|
-
value,
|
|
99
|
-
expiresAt: options?.ttl ? Date.now() + options.ttl : undefined
|
|
100
|
-
});
|
|
101
|
-
notify(value);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
signal.remove = async function(): Promise<void> {
|
|
105
|
-
const db = await openDB(dbName);
|
|
106
|
-
await dbDelete(db, key);
|
|
107
|
-
notify(initialValue);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
signal.onChange = function(callback: (value: T) => void): void {
|
|
111
|
-
listeners.push(callback);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
return signal as HeavySignal<T>;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ===========================================
|
|
118
|
-
// Iteramos el schema completo
|
|
119
|
-
|
|
120
|
-
type HeavyStorageSchema = Record<string, any>;
|
|
121
|
-
|
|
122
|
-
type HeavyStorageResult<T extends HeavyStorageSchema> = {
|
|
123
|
-
[K in keyof T]: HeavySignal<T[K]>;
|
|
124
|
-
} & {
|
|
125
|
-
clear(): Promise<void>;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export function createHeavyStorage<T extends HeavyStorageSchema>(
|
|
129
|
-
schema: T,
|
|
130
|
-
options?: HeavyStorageOptions
|
|
131
|
-
): HeavyStorageResult<T> {
|
|
132
|
-
const result: any = {};
|
|
133
|
-
|
|
134
|
-
const keys = Object.keys(schema);
|
|
135
|
-
for (const key of keys) {
|
|
136
|
-
result[key] = createHeavySignal(key, schema[key], options);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
result.clear = async () => {
|
|
140
|
-
for (const key of keys) {
|
|
141
|
-
await result[key].remove();
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
return result as HeavyStorageResult<T>;
|
|
146
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|