@jeanharo98/typed-storage 0.1.4 → 0.1.5
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/README.md +36 -0
- package/dist/storage-signal.js +20 -5
- package/dist/types.d.ts +1 -0
- package/index.html +19 -26
- package/package.json +7 -2
- package/src/storage-signal.test.ts +34 -0
- package/src/storage-signal.ts +29 -11
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -174,10 +174,45 @@ const appStorage = createStorage(schema, options);
|
|
|
174
174
|
| `sync` | `boolean` | `false` | Sync values across browser tabs via `StorageEvent` |
|
|
175
175
|
| `version` | `number` | — | Current schema version — required for migrations |
|
|
176
176
|
| `migrations` | `Record<number, (data) => data>` | — | Migration functions per version |
|
|
177
|
+
| `compress` | `boolean` | `false` | Compresses data with LZ-string before storing |
|
|
177
178
|
| `encrypt` | `boolean` | `false` | Shows a security warning — see note below |
|
|
178
179
|
|
|
179
180
|
---
|
|
180
181
|
|
|
182
|
+
## 📦 Compression
|
|
183
|
+
|
|
184
|
+
For large or repetitive data (lists, history, complex objects), enable compression to reduce the space used in `localStorage`:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const appStorage = createStorage({
|
|
188
|
+
cart: { items: [] }
|
|
189
|
+
}, {
|
|
190
|
+
prefix: 'shop',
|
|
191
|
+
compress: true
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
appStorage.cart.set({ items: [...manyProducts] });
|
|
195
|
+
// Data is compressed with LZ-string before saving
|
|
196
|
+
// and decompressed automatically when read
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### When to use it
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
✅ Useful for:
|
|
203
|
+
- Large lists (shopping carts, history)
|
|
204
|
+
- Repetitive JSON structures
|
|
205
|
+
- Data approaching localStorage's ~5MB limit
|
|
206
|
+
|
|
207
|
+
❌ Not needed for:
|
|
208
|
+
- Small values like theme, language, fontSize
|
|
209
|
+
- The compression overhead isn't worth it for tiny data
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Compression only runs when `compress: true` is explicitly set — there's zero overhead for the default use case.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
181
216
|
## 🔔 onChange
|
|
182
217
|
|
|
183
218
|
Subscribe to changes on any key:
|
|
@@ -297,6 +332,7 @@ Creates a storage object from a schema. Returns a `StorageResult<T>` with one `S
|
|
|
297
332
|
|---------|-------------|
|
|
298
333
|
| [@jeanharo98/typed-storage-angular](https://github.com/JeanHaro/typed-storage-angular) | Angular wrapper with native Signals |
|
|
299
334
|
| [@jeanharo98/typed-storage-react](https://github.com/JeanHaro/typed-storage-react) | React wrapper with useStorage() hook |
|
|
335
|
+
| [typed-storage-devtools](https://github.com/JeanHaro/typed-storage-devtools) | Chrome DevTools extension for real-time inspection |
|
|
300
336
|
|
|
301
337
|
---
|
|
302
338
|
|
package/dist/storage-signal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import LZString from 'lz-string';
|
|
1
2
|
import { MemoryStorage } from "./memory-storage.js";
|
|
2
3
|
function safeParseJSON(value, fallback) {
|
|
3
4
|
if (!value)
|
|
@@ -31,7 +32,10 @@ export function createStorageSignal(key, initialValue, options) {
|
|
|
31
32
|
if (options?.prefix) {
|
|
32
33
|
key = `${options.prefix}:${key}`;
|
|
33
34
|
}
|
|
34
|
-
const
|
|
35
|
+
const rawData = sto.getItem(key);
|
|
36
|
+
const savedData = options?.compress && rawData
|
|
37
|
+
? LZString.decompress(rawData)
|
|
38
|
+
: rawData;
|
|
35
39
|
let currentValue;
|
|
36
40
|
const listeners = [];
|
|
37
41
|
function notify(value) {
|
|
@@ -60,7 +64,10 @@ export function createStorageSignal(key, initialValue, options) {
|
|
|
60
64
|
notify(initialValue);
|
|
61
65
|
return currentValue = initialValue;
|
|
62
66
|
}
|
|
63
|
-
const
|
|
67
|
+
const rawNewValue = options?.compress
|
|
68
|
+
? LZString.decompress(event.newValue)
|
|
69
|
+
: event.newValue;
|
|
70
|
+
const item = safeParseJSON(rawNewValue, initialValue);
|
|
64
71
|
notify(item.value);
|
|
65
72
|
return currentValue = item.value;
|
|
66
73
|
}
|
|
@@ -69,15 +76,23 @@ export function createStorageSignal(key, initialValue, options) {
|
|
|
69
76
|
signalBase.set = function (newValue) {
|
|
70
77
|
currentValue = newValue;
|
|
71
78
|
notify(currentValue);
|
|
72
|
-
|
|
79
|
+
const dataToStore = JSON.stringify({
|
|
73
80
|
value: newValue,
|
|
74
81
|
expiresAt: options?.ttl ? Date.now() + options.ttl : undefined
|
|
75
|
-
})
|
|
82
|
+
});
|
|
83
|
+
const finalData = options?.compress
|
|
84
|
+
? LZString.compress(dataToStore)
|
|
85
|
+
: dataToStore;
|
|
86
|
+
sto.setItem(key, finalData);
|
|
76
87
|
};
|
|
77
88
|
signalBase.reset = function () {
|
|
78
89
|
currentValue = initialValue;
|
|
79
90
|
notify(currentValue);
|
|
80
|
-
|
|
91
|
+
const dataToStore = JSON.stringify(initialValue);
|
|
92
|
+
const finalData = options?.compress
|
|
93
|
+
? LZString.compress(dataToStore)
|
|
94
|
+
: dataToStore;
|
|
95
|
+
sto.setItem(key, finalData);
|
|
81
96
|
};
|
|
82
97
|
signalBase.has = function () {
|
|
83
98
|
return !!sto.getItem(key);
|
package/dist/types.d.ts
CHANGED
package/index.html
CHANGED
|
@@ -7,35 +7,28 @@
|
|
|
7
7
|
<script type="module">
|
|
8
8
|
import { createStorage } from './dist/index.js';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
localStorage.setItem('app:fontSize', '16');
|
|
13
|
-
localStorage.setItem('app__version__', '1');
|
|
14
|
-
|
|
15
|
-
// Crea el storage con schema v2 y migración
|
|
16
|
-
const appStorage = createStorage({
|
|
17
|
-
theme: 'dark',
|
|
18
|
-
preferences: {
|
|
19
|
-
fontSize: 16,
|
|
20
|
-
language: 'es'
|
|
21
|
-
}
|
|
10
|
+
const compressedStorage = createStorage({
|
|
11
|
+
bigData: { items: [] }
|
|
22
12
|
}, {
|
|
23
|
-
prefix: '
|
|
24
|
-
|
|
25
|
-
migrations: {
|
|
26
|
-
1: (oldData) => ({
|
|
27
|
-
theme: oldData.theme,
|
|
28
|
-
preferences: {
|
|
29
|
-
fontSize: oldData.fontSize,
|
|
30
|
-
language: 'es'
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
13
|
+
prefix: 'compressed',
|
|
14
|
+
compress: true
|
|
34
15
|
});
|
|
35
16
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
// Genera datos grandes para probar
|
|
18
|
+
const bigArray = Array.from({ length: 100 }, (_, i) => ({
|
|
19
|
+
id: i,
|
|
20
|
+
name: `Item ${i}`,
|
|
21
|
+
description: 'Lorem ipsum dolor sit amet consectetur'
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
compressedStorage.bigData.set({ items: bigArray });
|
|
25
|
+
|
|
26
|
+
console.log('Valor leído:', compressedStorage.bigData());
|
|
27
|
+
console.log('Tamaño en localStorage:', localStorage.getItem('compressed:bigData')?.length, 'caracteres');
|
|
28
|
+
|
|
29
|
+
// Compara con la versión sin comprimir
|
|
30
|
+
const uncompressedSize = JSON.stringify({ value: { items: bigArray } }).length;
|
|
31
|
+
console.log('Tamaño sin comprimir sería:', uncompressedSize, 'caracteres');
|
|
39
32
|
</script>
|
|
40
33
|
</body>
|
|
41
34
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeanharo98/typed-storage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Type-safe localStorage with reactive signals",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"jsdom": "^29.1.1",
|
|
20
20
|
"ts-node": "^10.9.2",
|
|
21
21
|
"typescript": "^6.0.3",
|
|
22
|
+
"vite": "^8.0.16",
|
|
22
23
|
"vitest": "^4.1.8"
|
|
23
24
|
},
|
|
24
25
|
"repository": {
|
|
@@ -29,10 +30,14 @@
|
|
|
29
30
|
"bugs": {
|
|
30
31
|
"url": "https://github.com/JeanHaro/typed-storage/issues"
|
|
31
32
|
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"lz-string": "^1.5.0"
|
|
35
|
+
},
|
|
32
36
|
"scripts": {
|
|
33
37
|
"dev": "ts-node src/index.ts",
|
|
34
38
|
"build": "tsc",
|
|
35
39
|
"test": "vitest",
|
|
36
|
-
"test:coverage": "vitest --coverage"
|
|
40
|
+
"test:coverage": "vitest --coverage",
|
|
41
|
+
"serve": "vite"
|
|
37
42
|
}
|
|
38
43
|
}
|
|
@@ -84,4 +84,38 @@ describe('onChange', () => {
|
|
|
84
84
|
theme.reset();
|
|
85
85
|
expect(callback).toHaveBeenCalledWith('dark');
|
|
86
86
|
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('compress option', () => {
|
|
90
|
+
beforeEach(() => localStorage.clear());
|
|
91
|
+
|
|
92
|
+
it('debe comprimir y descomprimir correctamente', () => {
|
|
93
|
+
const signal = createStorageSignal<{ items: any[] }>(
|
|
94
|
+
'data',
|
|
95
|
+
{ items: [] },
|
|
96
|
+
{ compress: true }
|
|
97
|
+
);
|
|
98
|
+
const bigData = {
|
|
99
|
+
items: Array.from({ length: 50 },
|
|
100
|
+
(_, i) => ({ id: i }))
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
signal.set(bigData);
|
|
104
|
+
|
|
105
|
+
expect(signal()).toEqual(bigData);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('el dato comprimido debe ocupar menos espacio que sin comprimir', () => {
|
|
109
|
+
const compressed = createStorageSignal('data1', '', { compress: true, prefix: 'c1' });
|
|
110
|
+
const uncompressed = createStorageSignal('data2', '', { prefix: 'c2' });
|
|
111
|
+
|
|
112
|
+
const repetitive = 'a'.repeat(1000);
|
|
113
|
+
compressed.set(repetitive);
|
|
114
|
+
uncompressed.set(repetitive);
|
|
115
|
+
|
|
116
|
+
const compressedSize = localStorage.getItem('c1:data1')!.length;
|
|
117
|
+
const uncompressedSize = localStorage.getItem('c2:data2')!.length;
|
|
118
|
+
|
|
119
|
+
expect(compressedSize).toBeLessThan(uncompressedSize);
|
|
120
|
+
});
|
|
87
121
|
});
|
package/src/storage-signal.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import LZString from 'lz-string';
|
|
2
|
+
|
|
1
3
|
// Tipos
|
|
2
4
|
import { StorageSignal, StorageSignalOptions } from "./types.js";
|
|
3
5
|
|
|
@@ -33,7 +35,7 @@ function safeParseJSON<T>(value: string, fallback: T): StoredValue<T> {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
// Obtenemos el valor del localStorage o SessionStorage,
|
|
38
|
+
// Obtenemos el valor del localStorage o SessionStorage, sino MemoryStorage
|
|
37
39
|
function getStorage(type: 'local' | 'session'): Storage | MemoryStorage {
|
|
38
40
|
try {
|
|
39
41
|
const sto = type === 'session' ? sessionStorage : localStorage;
|
|
@@ -63,7 +65,10 @@ export function createStorageSignal<T>(
|
|
|
63
65
|
key = `${options.prefix}:${key}`;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
const
|
|
68
|
+
const rawData = sto.getItem(key);
|
|
69
|
+
const savedData = options?.compress && rawData
|
|
70
|
+
? LZString.decompress(rawData)
|
|
71
|
+
: rawData;
|
|
67
72
|
let currentValue: T;
|
|
68
73
|
|
|
69
74
|
const listeners: Array<(value: T) => void> = [];
|
|
@@ -101,7 +106,10 @@ export function createStorageSignal<T>(
|
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
// Parseamos el nuevo valor
|
|
104
|
-
const
|
|
109
|
+
const rawNewValue = options?.compress
|
|
110
|
+
? LZString.decompress(event.newValue)
|
|
111
|
+
: event.newValue;
|
|
112
|
+
const item = safeParseJSON(rawNewValue, initialValue);
|
|
105
113
|
|
|
106
114
|
notify(item.value as T);
|
|
107
115
|
return currentValue = item.value as T;
|
|
@@ -112,19 +120,29 @@ export function createStorageSignal<T>(
|
|
|
112
120
|
signalBase.set = function ( newValue: T ): void {
|
|
113
121
|
currentValue = newValue;
|
|
114
122
|
notify(currentValue);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
|
|
124
|
+
const dataToStore = JSON.stringify({
|
|
125
|
+
value: newValue,
|
|
126
|
+
expiresAt: options?.ttl ? Date.now() + options.ttl : undefined
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const finalData = options?.compress
|
|
130
|
+
? LZString.compress(dataToStore)
|
|
131
|
+
: dataToStore;
|
|
132
|
+
|
|
133
|
+
sto.setItem( key, finalData );
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
signalBase.reset = function(): void {
|
|
125
137
|
currentValue = initialValue;
|
|
126
138
|
notify(currentValue);
|
|
127
|
-
|
|
139
|
+
|
|
140
|
+
const dataToStore = JSON.stringify(initialValue);
|
|
141
|
+
const finalData = options?.compress
|
|
142
|
+
? LZString.compress(dataToStore)
|
|
143
|
+
: dataToStore;
|
|
144
|
+
|
|
145
|
+
sto.setItem(key, finalData);
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
signalBase.has = function(): boolean {
|