@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.
Files changed (32) hide show
  1. package/dist/core/create-storage.d.ts +2 -0
  2. package/dist/core/create-storage.js +31 -0
  3. package/dist/core/memory-storage.d.ts +6 -0
  4. package/dist/core/memory-storage.js +14 -0
  5. package/dist/core/storage-signal.d.ts +2 -0
  6. package/dist/core/storage-signal.js +129 -0
  7. package/dist/features/heavy-storage/heavy-storage.d.ts +3 -0
  8. package/dist/features/heavy-storage/heavy-storage.js +50 -0
  9. package/dist/features/heavy-storage/heavy-storage.types.d.ts +16 -0
  10. package/dist/features/heavy-storage/heavy-storage.types.js +1 -0
  11. package/dist/features/heavy-storage/indexeddb-driver.d.ts +4 -0
  12. package/dist/features/heavy-storage/indexeddb-driver.js +40 -0
  13. package/dist/features/migrations.d.ts +1 -0
  14. package/dist/features/migrations.js +34 -0
  15. package/dist/features/xor.d.ts +3 -0
  16. package/dist/features/xor.js +14 -0
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js +2 -2
  19. package/package.json +1 -1
  20. package/src/{create-storage.ts → core/create-storage.ts} +6 -2
  21. package/src/{storage-signal.ts → core/storage-signal.ts} +8 -2
  22. package/src/features/heavy-storage/heavy-storage.ts +89 -0
  23. package/src/features/heavy-storage/heavy-storage.types.ts +21 -0
  24. package/src/features/heavy-storage/indexeddb-driver.ts +50 -0
  25. package/src/index.ts +2 -2
  26. package/src/heavy-storage.ts +0 -146
  27. /package/src/{memory-storage.ts → core/memory-storage.ts} +0 -0
  28. /package/src/{storage-signal.test.ts → core/storage-signal.test.ts} +0 -0
  29. /package/src/{heavy-storage.test.ts → features/heavy-storage/heavy-storage.test.ts} +0 -0
  30. /package/src/{migrations.test.ts → features/migrations.test.ts} +0 -0
  31. /package/src/{migrations.ts → features/migrations.ts} +0 -0
  32. /package/src/{xor.ts → features/xor.ts} +0 -0
@@ -0,0 +1,2 @@
1
+ import { StorageSchema, StorageResult, StorageSignalOptions } from '../types.js';
2
+ export declare function createStorage<T extends StorageSchema>(schema: T, options?: StorageSignalOptions): StorageResult<T>;
@@ -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,6 @@
1
+ export declare class MemoryStorage {
2
+ private data;
3
+ getItem(key: string): string | null;
4
+ setItem(key: string, value: string): void;
5
+ removeItem(key: string): void;
6
+ }
@@ -0,0 +1,14 @@
1
+ export class MemoryStorage {
2
+ constructor() {
3
+ this.data = new Map();
4
+ }
5
+ getItem(key) {
6
+ return this.data.get(key) ?? null;
7
+ }
8
+ setItem(key, value) {
9
+ this.data.set(key, value);
10
+ }
11
+ removeItem(key) {
12
+ this.data.delete(key);
13
+ }
14
+ }
@@ -0,0 +1,2 @@
1
+ import { StorageSignal, StorageSignalOptions } from "../types.js";
2
+ export declare function createStorageSignal<T>(key: string, initialValue: T, options?: StorageSignalOptions): StorageSignal<T>;
@@ -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,3 @@
1
+ export declare function xorTransform(text: string, secret: string): string;
2
+ export declare function xorEncrypt(text: string, secret: string): string;
3
+ export declare function xorDecrypt(text: string, secret: string): string;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeanharo98/typed-storage",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Type-safe localStorage with reactive signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,11 +1,15 @@
1
1
  // Types
2
- import { StorageSchema, StorageResult, StorageSignalOptions } from './types.js';
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 './migrations.js';
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 { StorageSignal, StorageSignalOptions } from "./types.js";
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 { xorEncrypt, xorDecrypt } from './xor.js';
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,
@@ -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