hyperstorage-js 6.0.4 β†’ 6.0.6

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 CHANGED
@@ -1,332 +1,385 @@
1
- # HyperStorage: Storage Manager for JavaScript/TypeScript
2
-
3
- A lightweight wrapper for Storage interfaces (e.g., `localStorage` or `sessionStorage`) with **efficient caching** and **type-preserving serialization**.
4
-
5
- The biggest burdens of working with the **Storage API** is verifying values on every read, providing proper default values and only being able to store strings, having to `JSON.stringify()` and `JSON.parse()` manually everytime. This package eliminates this all by providing a safe, automatic and efficient wrapper that handles everything for you. You can read/store numbers and objects without any extra steps, lose no performance and improve code readability.
6
-
7
- [![npm version](https://img.shields.io/npm/v/hyperstorage-js.svg)](https://www.npmjs.com/package/hyperstorage-js)
8
- [![npm downloads](https://img.shields.io/npm/dt/hyperstorage-js.svg)](https://www.npmjs.com/package/hyperstorage-js)
9
- [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/hyperstorage-js/badge)](https://www.jsdelivr.com/package/npm/hyperstorage-js)
10
-
11
- <br>
12
-
13
- ## Features
14
-
15
- - πŸ“ **Default values**: are automatically set when the key is not in Storage.
16
- - 🧩 **(Super)JSON support**: automatically serializes and parses everything using [superjson](https://github.com/flightcontrolhq/superjson).
17
- - 🧠 **TypeScript support**: full type safety with strongly typed keys, values, and callbacks.
18
- - πŸ”’ **Optional encoding/decoding**: hooks to obfuscate data or change the serializer.
19
- - ⚑ **Fast caching**: memory cache avoids repeated JSON convertions.
20
- - πŸ› οΈ **Utility helpers**: use `.set()` and `.isDefault()` to simplify storage operations.
21
- - 🌐 **Custom storage**: works with any object implementing the standard Storage API. (`localStorage`, `sessionStorage`, ...)
22
-
23
- ### Supported Types
24
-
25
- | Type | Supported by Storage API | Supported by HyperStorage (trough superjson) |
26
- | --------- | ------------------------ | -------------------------------------------- |
27
- | string | βœ… | βœ… |
28
- | number | ❌ | βœ… |
29
- | boolean | ❌ | βœ… |
30
- | null | ❌ | βœ… |
31
- | Array | ❌ | βœ… |
32
- | Object | ❌ | βœ… |
33
- | undefined | ❌ | βœ… |
34
- | bigint | ❌ | βœ… |
35
- | Date | ❌ | βœ… |
36
- | RegExp | ❌ | βœ… |
37
- | Set | ❌ | βœ… |
38
- | Map | ❌ | βœ… |
39
- | Error | ❌ | βœ… |
40
- | URL | ❌ | βœ… |
41
-
42
- <br>
43
-
44
- ## Installation
45
-
46
- ```bash
47
- # npm
48
- npm install hyperstorage-js
49
-
50
- # pnpm
51
- pnpm add hyperstorage-js
52
-
53
- # yarn
54
- yarn add hyperstorage-js
55
- ```
56
-
57
- <br>
58
-
59
- ## Constructor Syntax
60
-
61
- ```ts
62
- class HyperStorage<T> {
63
- constructor(
64
- itemName: string,
65
- defaultValue: T,
66
- options: {
67
- encodeFn?: (value: T) => string
68
- decodeFn?: (value: string) => T
69
- storage?: Storage
70
- } = {}
71
- )
72
- }
73
- ```
74
-
75
- <br>
76
-
77
- ## Usage
78
-
79
- ```js
80
- import HyperStorage from 'hyperstorage-js'
81
- ```
82
-
83
- ```js
84
- const defaultValue = { theme: 'light', language: 'en' }
85
- const userStore = new HyperStorage('userSettings', defaultValue)
86
-
87
- // If 'userSettings' is not present in the Storage, the defaultValue is set:
88
- console.log(userStore.value) // { theme: 'light', language: 'en' }
89
-
90
- // Change theme to dark
91
- userStore.value = { theme: 'dark', language: 'en' }
92
- // or
93
- userStore.set('theme', 'dark')
94
-
95
- console.log(userStore.value) // { theme: 'dark', language: 'en' }
96
- console.log(userStore.value.theme) // 'dark'
97
-
98
- // Present in localStorage
99
- console.log(userStore.storage) // StorageΒ {userSettings: '{"json":{"theme":"dark","language":"en"}}', length: 1}
100
- ```
101
-
102
- ### Advanced Ways to Assign a New Value
103
-
104
- ```js
105
- // Overwrite entirely
106
- userStore.value = { theme: 'dark', language: 'en' }
107
-
108
- // Change single property using the setter
109
- userStore.value = { ...userStore.value, theme: 'dark' }
110
-
111
- // Change single property using a callback
112
- userStore.set((v) => (v.theme = 'dark'))
113
-
114
- // Change single property using a property setter
115
- userStore.set('theme', 'dark')
116
- ```
117
-
118
- ### Using Another Storage API
119
-
120
- Use `sessionStorage` to only remember data for the duration of a session.
121
-
122
- ```js
123
- const sessionStore = new HyperStorage('sessionData', 'none', {
124
- storage: window.sessionStorage,
125
- })
126
-
127
- sessionStore.value = 'temporary'
128
- console.log(sessionStore.value) // 'temporary'
129
- console.log(sessionStore.storage) // Storage {sessionData: '{"json":"temporary"}', length: 1}
130
- ```
131
-
132
- ### Using Encoding and Decoding Functions
133
-
134
- If you want to make stored data significantly harder to reverse-engineer, use the `encodeFn` and `decodeFn` options.
135
-
136
- Apply Base64 encoding using JavaScript's `btoa` (String to Base64) and `atob` (Base64 to String).
137
-
138
- ```js
139
- const sessionStore = new HyperStorage('sessionData', 'none', {
140
- encodeFn: (value) => btoa(value),
141
- decodeFn: (value) => atob(value),
142
- })
143
-
144
- sessionStore.value = 'temporary'
145
- console.log(sessionStore.value) // 'temporary'
146
- console.log(sessionStore.storage) // StorageΒ Β {sessionData: 'dGVtcG9yYXJ5', length: 1}
147
- ```
148
-
149
- The default values for `encodeFn` and `decodeFn` are:
150
-
151
- ```ts
152
- encodeFn = (value) => HyperStorage.superjson.stringify(value)
153
- decodeFn = (value) => HyperStorage.superjson.parse<T>(value)
154
- ```
155
-
156
- ### Resetting Values
157
-
158
- ```js
159
- sessionStore.reset()
160
- console.log(sessionStore.defaultValue) // 'none'
161
- console.log(sessionStore.value) // 'none'
162
- ```
163
-
164
- <br>
165
-
166
- ## TypeScript Usage
167
-
168
- ### Using Type Parameter `T`
169
-
170
- ```ts
171
- interface Settings {
172
- theme: 'system' | 'light' | 'dark'
173
- language: string
174
- }
175
-
176
- const defaultValue: Settings = { theme: 'system', language: 'en' }
177
- const userStore = new HyperStorage<Settings>('userSettings', defaultValue)
178
-
179
- // Property 'language' is missing in type '{ theme: "dark"; }' but required in type 'Settings'. ts(2741)
180
- userStore.value = { theme: 'dark' }
181
- ```
182
-
183
- ### Using `sync()`
184
-
185
- Safe usage of `sync()` requires explicit runtime validation before accessing any properties. It quickly becomes clear how type-unsafe `sync()` is and why it should be avoided.
186
-
187
- ```ts
188
- // ... continues from the above example
189
-
190
- const result = userStore.sync() // (method): unknown
191
- // Right now, 'result' equals 'userStore.value' exactly
192
-
193
- // 'result' is of type 'unknown'. ts(18046)
194
- console.log(result.theme) // 'dark'
195
-
196
- // No error, but unsafe
197
- console.log(userStore.value.theme) // 'dark'
198
-
199
- // Must narrow down to be safe
200
- if (result && typeof result === 'object' && 'theme' in result) {
201
- // No error, safe
202
- console.log(result.theme) // 'dark'
203
- }
204
- ```
205
-
206
- <br>
207
-
208
- ## API
209
-
210
- ### `constructor<T>(itemName: string, defaultValue: T, options = {})`
211
-
212
- - **itemName**: `string` β€” key under which the data is stored.
213
- - **defaultValue**: default value to be stored if none exists.
214
- - **options** _(optional)_:
215
- - `encodeFn` β€” function to encode values before writing to the `Storage`.
216
- - `decodeFn` β€” function to decode values when reading from the `Storage`.
217
- - `storage` β€” a `Storage` instance (e.g., `localStorage` or `sessionStorage`).
218
-
219
- ### `value`
220
-
221
- - **Getter** β€” returns the cached value (very fast, does not use `JSON.parse`).
222
- - **Setter** β€” sets and caches the value, serializing and encoding it into `Storage`.
223
-
224
- ### `set(keyOrCallback: (keyof T) | ((value: T) => T), value?: T[keyof T]): T`
225
-
226
- - If a **callback** is provided, it receives the current value and must return the new value.
227
- - If a **key and value** are provided, it updates that single property on the stored object.
228
- - Returns the updated stored value.
229
-
230
- ### `reset(): T`
231
-
232
- - Resets the stored value to `defaultValue`.
233
- - Updates both `Storage` and internal cache.
234
- - Returns the restored default value.
235
-
236
- ### `isDefault(): boolean`
237
-
238
- - Checks whether the cached value equals the configured default.
239
- - Uses reference comparison for objects and strict equality for primitives.
240
- - Returns `true` if the current value matches the default, otherwise `false`.
241
-
242
- ```js
243
- if (userStore.isDefault()) {
244
- console.log('value equals the default value.')
245
- }
246
- ```
247
-
248
- ### `sync(decodeFn = this.decodeFn): unknown`
249
-
250
- If the underlying `Storage` is not modified through the value setter, the internal cache will **not automatically update**. Use `sync()` to synchronize the internal cache with the actual value stored in `Storage`.
251
-
252
- - **decodeFn** _(optional)_ β€” a function to decode values when reading (defaults to `this.decodeFn`).
253
- - Reads the value from storage.
254
- - Decodes it using `decodeFn`.
255
- - Updates the internal cache.
256
- - Returns the synchronized value.
257
-
258
- The return type is `unknown` because data read from `Storage` cannot be type-checked or trusted at compile time because it may have been modified externally.
259
-
260
- ```js
261
- // External change to storage (to be avoided)
262
- localStorage.setItem('userSettings', '{"theme":"dark"}')
263
-
264
- // Resynchronize the cache, optionally with a custom decoder
265
- userStore.sync((value) => JSON.parse(value))
266
-
267
- console.log(userStore.value) // { theme: 'dark' }
268
- console.log(userStore.storage) // Storage {userSettings: '{"json":{"theme":"dark"}}', length: 1}
269
- ```
270
-
271
- #### Why `sync()` is unsafe
272
-
273
- 1. The item in `Storage` is undecodable by the `decodeFn` passed to `sync()`.
274
-
275
- ```js
276
- localStorage.setItem('userSettings', 'not an object')
277
-
278
- // SyntaxError: Unexpected token 'o', "not an object" is not valid JSON
279
- const result = userStore.sync((value) => JSON.parse(value))
280
- // Execution continues because sync() uses a try-catch
281
-
282
- console.log(result) // instance of SyntaxError
283
-
284
- // Reset to default value
285
- console.log(userStore.value) // {theme: 'system', language: 'en'}
286
- ```
287
-
288
- 2. The item in `Storage`, after being decoded, is not of type `T`.
289
-
290
- ```js
291
- localStorage.setItem('userSettings', '{"not":"valid"}')
292
-
293
- const result = userStore.sync((value) => JSON.parse(value))
294
- console.log(result) // {not: 'valid'}
295
- console.log(userStore.value) // {not: 'valid'}
296
- ```
297
-
298
- No errors, but `result` and `userStore.value` are both not of type `T`.
299
-
300
- This **must** be manually checked and `userStore.reset()` should be called if the check fails.
301
-
302
- ```js
303
- // This example is specifically for type 'Settings'
304
- if (
305
- result &&
306
- typeof result === 'object' &&
307
- 'theme' in result &&
308
- 'language' in result &&
309
- (result.theme === 'system' || result.theme === 'light' || result.theme === 'dark') &&
310
- typeof result.language === 'string'
311
- ) {
312
- // 'result' is of type 'T'
313
- } else {
314
- // 'result' is not of type 'T'
315
- userStore.reset()
316
- console.log(userStore.value) // {theme: 'system', language: 'en'}
317
- }
318
-
319
- // 'userStore.value' is of type 'T'
320
- ```
321
-
322
- <br>
323
-
324
- ## Source
325
-
326
- [GitHub Repository](https://github.com/Khoeckman/HyperStorage)
327
-
328
- <br>
329
-
330
- ## License
331
-
332
- MIT
1
+ # HyperStorage: Storage Manager for JavaScript/TypeScript
2
+
3
+ [![npm version](https://img.shields.io/npm/v/hyperstorage-js.svg)](https://www.npmjs.com/package/hyperstorage-js)
4
+ [![npm downloads](https://img.shields.io/npm/dt/hyperstorage-js.svg)](https://www.npmjs.com/package/hyperstorage-js)
5
+ [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/hyperstorage-js/badge)](https://www.jsdelivr.com/package/npm/hyperstorage-js)
6
+
7
+ ## Description
8
+
9
+ A lightweight wrapper for Storage interfaces (e.g., `localStorage` or `sessionStorage`) with **efficient caching** and **type-preserving serialization**.
10
+
11
+ The biggest burdens of working with the **Storage API** is verifying values on every read, providing proper default values and only being able to store strings, having to `JSON.stringify()` and `JSON.parse()` manually everytime. This package eliminates this all by providing a safe, automatic and efficient wrapper that handles everything for you. You can read/store numbers and objects without any extra steps, lose no performance and improve code readability.
12
+
13
+ ## Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Installation](#installation)
17
+ - [Constructor Syntax](#constructor-syntax)
18
+ - [Usage](#usage)
19
+ - [Putting a New Value in Storage](#putting-a-new-value-in-storage)
20
+ - [Using Another Storage API](#using-another-storage-api)
21
+ - [Using Encoding and Decoding Functions](#using-encoding-and-decoding-functions)
22
+ - [Reset to Default](#reset-to-default)
23
+ - [TypeScript Usage](#typescript-usage)
24
+ - [Using Type Parameter `T` in `HyperStorage<T>`](#using-type-parameter-t-in-hyperstoraget)
25
+ - [Using `sync()`](#using-sync)
26
+ - [Supported Types In Storage](#supported-types-in-storage)
27
+ - [Class Reference](#class-reference)
28
+
29
+ <br>
30
+
31
+ ## Features
32
+
33
+ - πŸ“ **Default values**: are automatically set when the key is not in Storage.
34
+ - 🧩 **(Super)JSON support**: automatically serializes and parses everything using [superjson](https://github.com/flightcontrolhq/superjson).
35
+ - 🧠 **TypeScript support**: full type safety with strongly typed keys, values, and callbacks.
36
+ - πŸ”’ **Optional encoding/decoding**: hooks to obfuscate data or change the serializer.
37
+ - ⚑ **Fast caching**: memory cache avoids repeated JSON convertions.
38
+ - πŸ› οΈ **Utility helpers**: use `.set()` and `.isDefault()` to simplify storage operations.
39
+ - 🌐 **Custom storage**: works with any object implementing the standard Storage API. (`localStorage`, `sessionStorage`, ...)
40
+
41
+ <br>
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ # npm
47
+ npm install hyperstorage-js
48
+
49
+ # pnpm
50
+ pnpm add hyperstorage-js
51
+
52
+ # bun
53
+ bun i hyperstorage-js
54
+ ```
55
+
56
+ <br>
57
+
58
+ ## Constructor Syntax
59
+
60
+ ```ts
61
+ class HyperStorage<T> {
62
+ constructor(
63
+ itemName: string,
64
+ defaultValue: T,
65
+ options: {
66
+ encodeFn?: (value: T) => string
67
+ decodeFn?: (value: string) => T
68
+ storage?: Storage
69
+ } = {}
70
+ )
71
+ }
72
+ ```
73
+
74
+ <br>
75
+
76
+ ## Usage
77
+
78
+ ```js
79
+ import HyperStorage from 'hyperstorage-js'
80
+ ```
81
+
82
+ ```js
83
+ const defaultValue = { theme: 'light', language: 'en' }
84
+ const settingsStore = new HyperStorage('settings', defaultValue)
85
+
86
+ // If 'settings' is not present in localStorage, the defaultValue is set
87
+ console.log(settingsStore.value) // { theme: 'light', language: 'en' }
88
+
89
+ // Change theme to dark (one of multiple ways)
90
+ settingsStore.set('theme', 'dark')
91
+
92
+ console.log(settingsStore.value) // { theme: 'dark', language: 'en' }
93
+ console.log(window.localStorage) // StorageΒ {userSettings: '{"json":{"theme":"dark","language":"en"}}', length: 1}
94
+ ```
95
+
96
+ ### Putting a New Value in Storage
97
+
98
+ The default method that works for all use cases is the `value` setter.
99
+
100
+ ```js
101
+ store.value = 'anything'
102
+ ```
103
+
104
+ The [`set()` (click)](#setkeyorcallback-keyof-t--value-t--t-value-tkeyof-t-t) method is a handy wrapper for assigning and can be used as shown in the examples below, which are sorted from best to worst practice.
105
+
106
+ <details>
107
+ <summary><strong>Object: Change Only One Property</strong></summary>
108
+
109
+ ```js
110
+ // Property setter
111
+ settingsStore.set('theme', 'dark')
112
+
113
+ // Callback setter
114
+ settingsStore.set((v) => (v.theme = 'dark'))
115
+
116
+ // Spread operator
117
+ settingsStore.value = { ...settingsStore.value, theme: 'dark' }
118
+ ```
119
+
120
+ </details>
121
+
122
+ <details>
123
+ <summary><strong>Number: Increment</strong></summary>
124
+
125
+ ```js
126
+ // Increment operator
127
+ numberStore.value += 2
128
+
129
+ // Callback setter
130
+ numberStore.set((v) => (v += 2))
131
+
132
+ // Assign new value
133
+ numberStore.value = numberStore.value + 2
134
+ ```
135
+
136
+ </details>
137
+
138
+ ### Using Another Storage API
139
+
140
+ Use `sessionStorage` to only remember data for the duration of a session.
141
+
142
+ ```js
143
+ const sessionStore = new HyperStorage('sessionData', 'none', {
144
+ storage: window.sessionStorage,
145
+ })
146
+
147
+ sessionStore.value = 'temporary'
148
+ console.log(sessionStore.value) // 'temporary'
149
+ console.log(sessionStore.storage) // Storage {sessionData: '{"json":"temporary"}', length: 1}
150
+ ```
151
+
152
+ ### Using Encoding and Decoding Functions
153
+
154
+ If you want to make stored data harder to reverse-engineer, use the `encodeFn` and `decodeFn` options.
155
+
156
+ The default values for `encodeFn` and `decodeFn` are:
157
+
158
+ ```ts
159
+ encodeFn = (value) => HyperStorage.superjson.stringify(value)
160
+ decodeFn = (value) => HyperStorage.superjson.parse(value)
161
+ ```
162
+
163
+ <details>
164
+ <summary><strong>Base64 Example</strong></summary>
165
+
166
+ Apply Base64 encoding using JavaScript's `btoa` (String to Base64) and `atob` (Base64 to String).
167
+
168
+ ```js
169
+ const sessionStore = new HyperStorage('sessionData', 'none', {
170
+ encodeFn: (value) => btoa(value),
171
+ decodeFn: (value) => atob(value),
172
+ })
173
+
174
+ sessionStore.value = 'temporary'
175
+ console.log(sessionStore.value) // 'temporary'
176
+ console.log(sessionStore.storage) // StorageΒ Β {sessionData: 'dGVtcG9yYXJ5', length: 1}
177
+ ```
178
+
179
+ </details>
180
+
181
+ ### Reset to Default
182
+
183
+ ```js
184
+ console.log(sessionStore.value) // 'temporary'
185
+ console.log(sessionStore.defaultValue) // 'none'
186
+
187
+ sessionStore.reset()
188
+ console.log(sessionStore.value) // 'none'
189
+ ```
190
+
191
+ <br>
192
+
193
+ ## TypeScript Usage
194
+
195
+ ### Using Type Parameter `T` in `HyperStorage<T>`
196
+
197
+ ```ts
198
+ interface Settings {
199
+ theme: 'system' | 'light' | 'dark'
200
+ language: string
201
+ }
202
+
203
+ const defaultValue: Settings = { theme: 'system', language: 'en' }
204
+ const userStore = new HyperStorage<Settings>('userSettings', defaultValue)
205
+
206
+ // Property 'language' is missing in type '{ theme: "dark"; }' but required in type 'Settings'. ts(2741)
207
+ userStore.value = { theme: 'dark' }
208
+ ```
209
+
210
+ ### Using `sync()`
211
+
212
+ Safe usage of `sync()` requires explicit runtime validation before accessing any properties. It quickly becomes clear how type-unsafe `sync()` is and why it should be avoided.
213
+
214
+ ```ts
215
+ // ... continues from the above example
216
+
217
+ const result = userStore.sync() // (method): unknown
218
+ // Right now, 'result' equals 'userStore.value' exactly
219
+
220
+ // 'result' is of type 'unknown'. ts(18046)
221
+ console.log(result.theme) // 'dark'
222
+
223
+ // No error, but unsafe
224
+ console.log(userStore.value.theme) // 'dark'
225
+
226
+ // Must narrow down to be safe
227
+ if (result && typeof result === 'object' && 'theme' in result) {
228
+ // No error, safe
229
+ console.log(result.theme) // 'dark'
230
+ }
231
+ ```
232
+
233
+ <br>
234
+
235
+ ## Supported Types in Storage
236
+
237
+ | Type | Supported by Storage API | Supported by HyperStorage (trough superjson) |
238
+ | --------- | ------------------------ | -------------------------------------------- |
239
+ | string | βœ… | βœ… |
240
+ | undefined | ❌ | βœ… |
241
+ | null | ❌ | βœ… |
242
+ | boolean | ❌ | βœ… |
243
+ | number | ❌ | βœ… |
244
+ | bigint | ❌ | βœ… |
245
+ | Object | ❌ | βœ… |
246
+ | Array | ❌ | βœ… |
247
+ | Set | ❌ | βœ… |
248
+ | Map | ❌ | βœ… |
249
+ | URL | ❌ | βœ… |
250
+ | Date | ❌ | βœ… |
251
+ | RegExp | ❌ | βœ… |
252
+ | Error | ❌ | βœ… |
253
+
254
+ <br>
255
+
256
+ ## Class Reference
257
+
258
+ ### `constructor<T>(itemName: string, defaultValue: T, options = {})`
259
+
260
+ - **itemName**: `string` β€” key under which the data is stored.
261
+ - **defaultValue**: default value to be stored if none exists.
262
+ - **options** _(optional)_:
263
+ - `encodeFn` β€” function to encode values before writing to the `Storage`.
264
+ - `decodeFn` β€” function to decode values when reading from the `Storage`.
265
+ - `storage` β€” a `Storage` instance (e.g., `localStorage` or `sessionStorage`).
266
+
267
+ ### `value`
268
+
269
+ - **Getter** β€” returns the cached value (very fast, does not use `JSON.parse`).
270
+ - **Setter** β€” sets and caches the value, serializing and encoding it into `Storage`.
271
+
272
+ ### `set(keyOrCallback: (keyof T) | ((value: T) => T), value?: T[keyof T]): T`
273
+
274
+ - If a **callback** is provided, it receives the current value and must return the new value.
275
+ - If a **key and value** are provided, it updates that single property on the stored object.
276
+ - Returns the updated stored value.
277
+
278
+ ### `reset(): T`
279
+
280
+ - Resets the stored value to `defaultValue`.
281
+ - Updates both `Storage` and internal cache.
282
+ - Returns the restored default value.
283
+
284
+ ### `isDefault(): boolean`
285
+
286
+ - Checks whether the cached value equals the configured default.
287
+ - Uses reference comparison for objects and strict equality for primitives.
288
+ - Returns `true` if the current value matches the default, otherwise `false`.
289
+
290
+ ```js
291
+ if (userStore.isDefault()) {
292
+ console.log('value equals the default value.')
293
+ }
294
+ ```
295
+
296
+ ### `sync(decodeFn = this.decodeFn): unknown`
297
+
298
+ If the underlying `Storage` is not modified through the value setter, the internal cache will **not automatically update**. Use `sync()` to synchronize the internal cache with the actual value stored in `Storage`.
299
+
300
+ - **decodeFn** _(optional)_ β€” a function to decode values when reading (defaults to `this.decodeFn`).
301
+ - Reads the value from storage.
302
+ - Decodes it using `decodeFn`.
303
+ - Updates the internal cache.
304
+ - Returns the synchronized value.
305
+
306
+ The return type is `unknown` because data read from `Storage` cannot be type-checked or trusted at compile time because it may have been modified externally.
307
+
308
+ ```js
309
+ // External change to storage (to be avoided)
310
+ localStorage.setItem('userSettings', '{"theme":"dark"}')
311
+
312
+ // Resynchronize the cache, optionally with a custom decoder
313
+ userStore.sync((value) => JSON.parse(value))
314
+
315
+ console.log(userStore.value) // { theme: 'dark' }
316
+ console.log(userStore.storage) // Storage {userSettings: '{"json":{"theme":"dark"}}', length: 1}
317
+ ```
318
+
319
+ <br>
320
+
321
+ <details>
322
+ <summary><strong> Why <code>sync()</code> is unsafe</strong></summary>
323
+
324
+ 1. The item in `Storage` is undecodable by the `decodeFn` passed to `sync()`.
325
+
326
+ ```js
327
+ localStorage.setItem('userSettings', 'not an object')
328
+
329
+ // SyntaxError: Unexpected token 'o', "not an object" is not valid JSON
330
+ const result = userStore.sync((value) => JSON.parse(value))
331
+ // Execution continues because sync() uses a try-catch
332
+
333
+ console.log(result) // instance of SyntaxError
334
+
335
+ // Reset to default value
336
+ console.log(userStore.value) // {theme: 'system', language: 'en'}
337
+ ```
338
+
339
+ 2. The item in `Storage`, after being decoded, is not of type `T`.
340
+
341
+ ```js
342
+ localStorage.setItem('userSettings', '{"not":"valid"}')
343
+
344
+ const result = userStore.sync((value) => JSON.parse(value))
345
+ console.log(result) // {not: 'valid'}
346
+ console.log(userStore.value) // {not: 'valid'}
347
+ ```
348
+
349
+ No errors, but `result` and `userStore.value` are both not of type `T`.
350
+
351
+ This **must** be manually checked and `userStore.reset()` should be called if the check fails.
352
+
353
+ ```js
354
+ // This example is specifically for type 'Settings'
355
+ if (
356
+ result &&
357
+ typeof result === 'object' &&
358
+ 'theme' in result &&
359
+ 'language' in result &&
360
+ (result.theme === 'system' || result.theme === 'light' || result.theme === 'dark') &&
361
+ typeof result.language === 'string'
362
+ ) {
363
+ // 'result' is of type 'T'
364
+ } else {
365
+ // 'result' is not of type 'T'
366
+ userStore.reset()
367
+ console.log(userStore.value) // {theme: 'system', language: 'en'}
368
+ }
369
+
370
+ // 'userStore.value' is of type 'T'
371
+ ```
372
+
373
+ </details>
374
+
375
+ <br>
376
+
377
+ ## Source
378
+
379
+ [GitHub Repository](https://github.com/Khoeckman/HyperStorage)
380
+
381
+ <br>
382
+
383
+ ## License
384
+
385
+ MIT
package/dist/index.cjs CHANGED
@@ -867,7 +867,7 @@ SuperJSON.allowErrorProps;
867
867
  */
868
868
  class HyperStorage {
869
869
  /** Version of the library, injected via Rollup replace plugin. */
870
- static version = "6.0.4";
870
+ static version = "6.0.6";
871
871
  static superjson = SuperJSON;
872
872
  /** Key name under which the data is stored. */
873
873
  itemName;
@@ -941,15 +941,15 @@ class HyperStorage {
941
941
  * Using this function should be avoided when possible and is not type safe.
942
942
  */
943
943
  sync(decodeFn = this.#decodeFn) {
944
- let json = this.storage.getItem(this.itemName);
944
+ const json = this.storage.getItem(this.itemName);
945
945
  // Reset value to defaultValue if it does not exist in storage
946
- if (typeof json !== 'string')
946
+ if (json === null)
947
947
  return this.reset();
948
- // Reset value to defaultValue if the incoming value is not properly encoded
949
948
  try {
950
949
  return (this.value = decodeFn(json));
951
950
  }
952
951
  catch (err) {
952
+ // Reset value to defaultValue if the incoming value is not properly encoded
953
953
  console.error(err);
954
954
  this.reset();
955
955
  return err;
package/dist/index.mjs CHANGED
@@ -865,7 +865,7 @@ SuperJSON.allowErrorProps;
865
865
  */
866
866
  class HyperStorage {
867
867
  /** Version of the library, injected via Rollup replace plugin. */
868
- static version = "6.0.4";
868
+ static version = "6.0.6";
869
869
  static superjson = SuperJSON;
870
870
  /** Key name under which the data is stored. */
871
871
  itemName;
@@ -939,15 +939,15 @@ class HyperStorage {
939
939
  * Using this function should be avoided when possible and is not type safe.
940
940
  */
941
941
  sync(decodeFn = this.#decodeFn) {
942
- let json = this.storage.getItem(this.itemName);
942
+ const json = this.storage.getItem(this.itemName);
943
943
  // Reset value to defaultValue if it does not exist in storage
944
- if (typeof json !== 'string')
944
+ if (json === null)
945
945
  return this.reset();
946
- // Reset value to defaultValue if the incoming value is not properly encoded
947
946
  try {
948
947
  return (this.value = decodeFn(json));
949
948
  }
950
949
  catch (err) {
950
+ // Reset value to defaultValue if the incoming value is not properly encoded
951
951
  console.error(err);
952
952
  this.reset();
953
953
  return err;
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).HyperStorage=t()}(this,function(){"use strict";class e{constructor(){this.keyToValue=new Map,this.valueToKey=new Map}set(e,t){this.keyToValue.set(e,t),this.valueToKey.set(t,e)}getByKey(e){return this.keyToValue.get(e)}getByValue(e){return this.valueToKey.get(e)}clear(){this.keyToValue.clear(),this.valueToKey.clear()}}class t{constructor(t){this.generateIdentifier=t,this.kv=new e}register(e,t){this.kv.getByValue(e)||(t||(t=this.generateIdentifier(e)),this.kv.set(t,e))}clear(){this.kv.clear()}getIdentifier(e){return this.kv.getByValue(e)}getValue(e){return this.kv.getByKey(e)}}class r extends t{constructor(){super(e=>e.name),this.classToAllowedProps=new Map}register(e,t){"object"==typeof t?(t.allowProps&&this.classToAllowedProps.set(e,t.allowProps),super.register(e,t.identifier)):super.register(e,t)}getAllowedProps(e){return this.classToAllowedProps.get(e)}}function n(e,t){const r=function(e){if("values"in Object)return Object.values(e);const t=[];for(const r in e)e.hasOwnProperty(r)&&t.push(e[r]);return t}(e);if("find"in r)return r.find(t);const n=r;for(let e=0;e<n.length;e++){const r=n[e];if(t(r))return r}}function s(e,t){Object.entries(e).forEach(([e,r])=>t(r,e))}function o(e,t){return-1!==e.indexOf(t)}function i(e,t){for(let r=0;r<e.length;r++){const n=e[r];if(t(n))return n}}class a{constructor(){this.transfomers={}}register(e){this.transfomers[e.name]=e}findApplicable(e){return n(this.transfomers,t=>t.isApplicable(e))}findByName(e){return this.transfomers[e]}}const l=e=>void 0===e,u=e=>"object"==typeof e&&null!==e&&(e!==Object.prototype&&(null===Object.getPrototypeOf(e)||Object.getPrototypeOf(e)===Object.prototype)),c=e=>u(e)&&0===Object.keys(e).length,f=e=>Array.isArray(e),p=e=>e instanceof Map,d=e=>e instanceof Set,y=e=>"Symbol"===(e=>Object.prototype.toString.call(e).slice(8,-1))(e),g=e=>e instanceof Error,m=e=>"number"==typeof e&&isNaN(e),h=e=>(e=>"boolean"==typeof e)(e)||(e=>null===e)(e)||l(e)||(e=>"number"==typeof e&&!isNaN(e))(e)||(e=>"string"==typeof e)(e)||y(e),b=e=>e.replace(/\\/g,"\\\\").replace(/\./g,"\\."),w=e=>e.map(String).map(b).join("."),v=(e,t)=>{const r=[];let n="";for(let s=0;s<e.length;s++){let o=e.charAt(s);if(!t&&"\\"===o){const t=e.charAt(s+1);if("\\"===t){n+="\\",s++;continue}if("."!==t)throw Error("invalid path")}if("\\"===o&&"."===e.charAt(s+1)){n+=".",s++;continue}"."===o?(r.push(n),n=""):n+=o}const s=n;return r.push(s),r};function E(e,t,r,n){return{isApplicable:e,annotation:t,transform:r,untransform:n}}const I=[E(l,"undefined",()=>null,()=>{}),E(e=>"bigint"==typeof e,"bigint",e=>e.toString(),e=>"undefined"!=typeof BigInt?BigInt(e):(console.error("Please add a BigInt polyfill."),e)),E(e=>e instanceof Date&&!isNaN(e.valueOf()),"Date",e=>e.toISOString(),e=>new Date(e)),E(g,"Error",(e,t)=>{const r={name:e.name,message:e.message};return"cause"in e&&(r.cause=e.cause),t.allowedErrorProps.forEach(t=>{r[t]=e[t]}),r},(e,t)=>{const r=new Error(e.message,{cause:e.cause});return r.name=e.name,r.stack=e.stack,t.allowedErrorProps.forEach(t=>{r[t]=e[t]}),r}),E(e=>e instanceof RegExp,"regexp",e=>""+e,e=>{const t=e.slice(1,e.lastIndexOf("/")),r=e.slice(e.lastIndexOf("/")+1);return new RegExp(t,r)}),E(d,"set",e=>[...e.values()],e=>new Set(e)),E(p,"map",e=>[...e.entries()],e=>new Map(e)),E(e=>{return m(e)||((t=e)===1/0||t===-1/0);var t},"number",e=>m(e)?"NaN":e>0?"Infinity":"-Infinity",Number),E(e=>0===e&&1/e==-1/0,"number",()=>"-0",Number),E(e=>e instanceof URL,"URL",e=>e.toString(),e=>new URL(e))];function k(e,t,r,n){return{isApplicable:e,annotation:t,transform:r,untransform:n}}const O=k((e,t)=>{if(y(e)){return!!t.symbolRegistry.getIdentifier(e)}return!1},(e,t)=>["symbol",t.symbolRegistry.getIdentifier(e)],e=>e.description,(e,t,r)=>{const n=r.symbolRegistry.getValue(t[1]);if(!n)throw new Error("Trying to deserialize unknown symbol");return n}),j=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,Uint8ClampedArray].reduce((e,t)=>(e[t.name]=t,e),{}),A=k(e=>ArrayBuffer.isView(e)&&!(e instanceof DataView),e=>["typed-array",e.constructor.name],e=>[...e],(e,t)=>{const r=j[t[1]];if(!r)throw new Error("Trying to deserialize unknown typed array");return new r(e)});function T(e,t){if(e?.constructor){return!!t.classRegistry.getIdentifier(e.constructor)}return!1}const P=k(T,(e,t)=>["class",t.classRegistry.getIdentifier(e.constructor)],(e,t)=>{const r=t.classRegistry.getAllowedProps(e.constructor);if(!r)return{...e};const n={};return r.forEach(t=>{n[t]=e[t]}),n},(e,t,r)=>{const n=r.classRegistry.getValue(t[1]);if(!n)throw new Error(`Trying to deserialize unknown class '${t[1]}' - check https://github.com/blitz-js/superjson/issues/116#issuecomment-773996564`);return Object.assign(Object.create(n.prototype),e)}),V=k((e,t)=>!!t.customTransformerRegistry.findApplicable(e),(e,t)=>["custom",t.customTransformerRegistry.findApplicable(e).name],(e,t)=>t.customTransformerRegistry.findApplicable(e).serialize(e),(e,t,r)=>{const n=r.customTransformerRegistry.findByName(t[1]);if(!n)throw new Error("Trying to deserialize unknown custom value");return n.deserialize(e)}),R=[P,O,V,A],S=(e,t)=>{const r=i(R,r=>r.isApplicable(e,t));if(r)return{value:r.transform(e,t),type:r.annotation(e,t)};const n=i(I,r=>r.isApplicable(e,t));return n?{value:n.transform(e,t),type:n.annotation}:void 0},N={};I.forEach(e=>{N[e.annotation]=e});const z=(e,t)=>{if(t>e.size)throw new Error("index out of bounds");const r=e.keys();for(;t>0;)r.next(),t--;return r.next().value};function _(e){if(o(e,"__proto__"))throw new Error("__proto__ is not allowed as a property");if(o(e,"prototype"))throw new Error("prototype is not allowed as a property");if(o(e,"constructor"))throw new Error("constructor is not allowed as a property")}const x=(e,t,r)=>{if(_(t),0===t.length)return r(e);let n=e;for(let e=0;e<t.length-1;e++){const r=t[e];if(f(n)){n=n[+r]}else if(u(n))n=n[r];else if(d(n)){n=z(n,+r)}else if(p(n)){if(e===t.length-2)break;const s=+r,o=0===+t[++e]?"key":"value",i=z(n,s);switch(o){case"key":n=i;break;case"value":n=n.get(i)}}}const s=t[t.length-1];if(f(n)?n[+s]=r(n[+s]):u(n)&&(n[s]=r(n[s])),d(n)){const e=z(n,+s),t=r(e);e!==t&&(n.delete(e),n.add(t))}if(p(n)){const e=+t[t.length-2],o=z(n,e);switch(0===+s?"key":"value"){case"key":{const e=r(o);n.set(e,n.get(o)),e!==o&&n.delete(o);break}case"value":n.set(o,r(n.get(o)))}}return e},F=e=>e<1;function B(e,t,r,n=[]){if(!e)return;const o=F(r);if(!f(e))return void s(e,(e,s)=>B(e,t,r,[...n,...v(s,o)]));const[i,a]=e;a&&s(a,(e,s)=>{B(e,t,r,[...n,...v(s,o)])}),t(i,n)}function C(e,t,r,n){return B(t,(t,r)=>{e=x(e,r,e=>((e,t,r)=>{if(!f(t)){const n=N[t];if(!n)throw new Error("Unknown transformation: "+t);return n.untransform(e,r)}switch(t[0]){case"symbol":return O.untransform(e,t,r);case"class":return P.untransform(e,t,r);case"custom":return V.untransform(e,t,r);case"typed-array":return A.untransform(e,t,r);default:throw new Error("Unknown transformation: "+t)}})(e,t,n))},r),e}function U(e,t,r){const n=F(r);function o(t,r){const s=((e,t)=>{_(t);for(let r=0;r<t.length;r++){const n=t[r];if(d(e))e=z(e,+n);else if(p(e)){const s=+n,o=0===+t[++r]?"key":"value",i=z(e,s);switch(o){case"key":e=i;break;case"value":e=e.get(i)}}else e=e[n]}return e})(e,v(r,n));t.map(e=>v(e,n)).forEach(t=>{e=x(e,t,()=>s)})}if(f(t)){const[r,i]=t;r.forEach(t=>{e=x(e,v(t,n),()=>e)}),i&&s(i,o)}else s(t,o);return e}const M=(e,t,r,n,i=[],a=[],l=new Map)=>{const y=h(e);if(!y){!function(e,t,r){const n=r.get(e);n?n.push(t):r.set(e,[t])}(e,i,t);const r=l.get(e);if(r)return n?{transformedValue:null}:r}if(!((e,t)=>u(e)||f(e)||p(e)||d(e)||g(e)||T(e,t))(e,r)){const t=S(e,r),n=t?{transformedValue:t.value,annotations:[t.type]}:{transformedValue:e};return y||l.set(e,n),n}if(o(a,e))return{transformedValue:null};const m=S(e,r),w=m?.value??e,v=f(w)?[]:{},E={};s(w,(o,c)=>{if("__proto__"===c||"constructor"===c||"prototype"===c)throw new Error(`Detected property ${c}. This is a prototype pollution risk, please remove it from your object.`);const p=M(o,t,r,n,[...i,c],[...a,e],l);v[c]=p.transformedValue,f(p.annotations)?E[b(c)]=p.annotations:u(p.annotations)&&s(p.annotations,(e,t)=>{E[b(c)+"."+t]=e})});const I=c(E)?{transformedValue:v,annotations:m?[m.type]:void 0}:{transformedValue:v,annotations:m?[m.type,E]:E};return y||l.set(e,I),I};function D(e){return Object.prototype.toString.call(e).slice(8,-1)}function K(e){return"Array"===D(e)}function q(e,t={}){if(K(e))return e.map(e=>q(e,t));if(!function(e){if("Object"!==D(e))return!1;const t=Object.getPrototypeOf(e);return!!t&&t.constructor===Object&&t===Object.prototype}(e))return e;return[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)].reduce((r,n)=>{if("__proto__"===n)return r;if(K(t.props)&&!t.props.includes(n))return r;return function(e,t,r,n,s){const o={}.propertyIsEnumerable.call(n,t)?"enumerable":"nonenumerable";"enumerable"===o&&(e[t]=r),s&&"nonenumerable"===o&&Object.defineProperty(e,t,{value:r,enumerable:!1,writable:!0,configurable:!0})}(r,n,q(e[n],t),e,t.nonenumerable),r},{})}class L{constructor({dedupe:e=!1}={}){this.classRegistry=new r,this.symbolRegistry=new t(e=>e.description??""),this.customTransformerRegistry=new a,this.allowedErrorProps=[],this.dedupe=e}serialize(e){const t=new Map,r=M(e,t,this,this.dedupe),n={json:r.transformedValue};r.annotations&&(n.meta={...n.meta,values:r.annotations});const s=function(e,t){const r={};let n;return e.forEach(e=>{if(e.length<=1)return;t||(e=e.map(e=>e.map(String)).sort((e,t)=>e.length-t.length));const[s,...o]=e;0===s.length?n=o.map(w):r[w(s)]=o.map(w)}),n?c(r)?[n]:[n,r]:c(r)?void 0:r}(t,this.dedupe);return s&&(n.meta={...n.meta,referentialEqualities:s}),n.meta&&(n.meta.v=1),n}deserialize(e,t){const{json:r,meta:n}=e;let s=t?.inPlace?r:q(r);return n?.values&&(s=C(s,n.values,n.v??0,this)),n?.referentialEqualities&&(s=U(s,n.referentialEqualities,n.v??0)),s}stringify(e){return JSON.stringify(this.serialize(e))}parse(e){return this.deserialize(JSON.parse(e),{inPlace:!0})}registerClass(e,t){this.classRegistry.register(e,t)}registerSymbol(e,t){this.symbolRegistry.register(e,t)}registerCustom(e,t){this.customTransformerRegistry.register({name:t,...e})}allowErrorProps(...e){this.allowedErrorProps.push(...e)}}L.defaultInstance=new L,L.serialize=L.defaultInstance.serialize.bind(L.defaultInstance),L.deserialize=L.defaultInstance.deserialize.bind(L.defaultInstance),L.stringify=L.defaultInstance.stringify.bind(L.defaultInstance),L.parse=L.defaultInstance.parse.bind(L.defaultInstance),L.registerClass=L.defaultInstance.registerClass.bind(L.defaultInstance),L.registerSymbol=L.defaultInstance.registerSymbol.bind(L.defaultInstance),L.registerCustom=L.defaultInstance.registerCustom.bind(L.defaultInstance),L.allowErrorProps=L.defaultInstance.allowErrorProps.bind(L.defaultInstance),L.serialize,L.deserialize,L.stringify,L.parse,L.registerClass,L.registerCustom,L.registerSymbol,L.allowErrorProps;class J{static version="6.0.4";static superjson=L;itemName;defaultValue;#e;#t;storage;#r;constructor(e,t,r={}){const{encodeFn:n,decodeFn:s,storage:o=window.localStorage}=r;if("string"!=typeof e)throw new TypeError("itemName is not a string");if(this.itemName=e,this.defaultValue=t,n&&"function"!=typeof n)throw new TypeError("encodeFn is defined but is not a function");if(this.#e=n||(e=>J.superjson.stringify(e)),s&&"function"!=typeof s)throw new TypeError("decodeFn is defined but is not a function");if(this.#t=s||(e=>J.superjson.parse(e)),!(o instanceof Storage))throw new TypeError("storage must be an instance of Storage");this.storage=o,this.sync()}set value(e){this.#r=e,this.storage.setItem(this.itemName,this.#e(e))}get value(){return this.#r}set(e,t){return this.value="function"==typeof e?e(this.#r):{...this.#r,[e]:t}}sync(e=this.#t){let t=this.storage.getItem(this.itemName);if("string"!=typeof t)return this.reset();try{return this.value=e(t)}catch(e){return console.error(e),this.reset(),e}}reset(){return this.value=this.defaultValue}isDefault(){return this.#r===this.defaultValue}}return J});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).HyperStorage=t()}(this,function(){"use strict";class DoubleIndexedKV{constructor(){this.keyToValue=new Map,this.valueToKey=new Map}set(e,t){this.keyToValue.set(e,t),this.valueToKey.set(t,e)}getByKey(e){return this.keyToValue.get(e)}getByValue(e){return this.valueToKey.get(e)}clear(){this.keyToValue.clear(),this.valueToKey.clear()}}class Registry{constructor(e){this.generateIdentifier=e,this.kv=new DoubleIndexedKV}register(e,t){this.kv.getByValue(e)||(t||(t=this.generateIdentifier(e)),this.kv.set(t,e))}clear(){this.kv.clear()}getIdentifier(e){return this.kv.getByValue(e)}getValue(e){return this.kv.getByKey(e)}}class ClassRegistry extends Registry{constructor(){super(e=>e.name),this.classToAllowedProps=new Map}register(e,t){"object"==typeof t?(t.allowProps&&this.classToAllowedProps.set(e,t.allowProps),super.register(e,t.identifier)):super.register(e,t)}getAllowedProps(e){return this.classToAllowedProps.get(e)}}function e(e,t){const r=function(e){if("values"in Object)return Object.values(e);const t=[];for(const r in e)e.hasOwnProperty(r)&&t.push(e[r]);return t}(e);if("find"in r)return r.find(t);const n=r;for(let e=0;e<n.length;e++){const r=n[e];if(t(r))return r}}function t(e,t){Object.entries(e).forEach(([e,r])=>t(r,e))}function r(e,t){return-1!==e.indexOf(t)}function n(e,t){for(let r=0;r<e.length;r++){const n=e[r];if(t(n))return n}}class CustomTransformerRegistry{constructor(){this.transfomers={}}register(e){this.transfomers[e.name]=e}findApplicable(t){return e(this.transfomers,e=>e.isApplicable(t))}findByName(e){return this.transfomers[e]}}const s=e=>void 0===e,o=e=>"object"==typeof e&&null!==e&&(e!==Object.prototype&&(null===Object.getPrototypeOf(e)||Object.getPrototypeOf(e)===Object.prototype)),i=e=>o(e)&&0===Object.keys(e).length,a=e=>Array.isArray(e),u=e=>e instanceof Map,l=e=>e instanceof Set,c=e=>"Symbol"===(e=>Object.prototype.toString.call(e).slice(8,-1))(e),f=e=>e instanceof Error,p=e=>"number"==typeof e&&isNaN(e),d=e=>(e=>"boolean"==typeof e)(e)||(e=>null===e)(e)||s(e)||(e=>"number"==typeof e&&!isNaN(e))(e)||(e=>"string"==typeof e)(e)||c(e),y=e=>e.replace(/\\/g,"\\\\").replace(/\./g,"\\."),g=e=>e.map(String).map(y).join("."),m=(e,t)=>{const r=[];let n="";for(let s=0;s<e.length;s++){let o=e.charAt(s);if(!t&&"\\"===o){const t=e.charAt(s+1);if("\\"===t){n+="\\",s++;continue}if("."!==t)throw Error("invalid path")}if("\\"===o&&"."===e.charAt(s+1)){n+=".",s++;continue}"."===o?(r.push(n),n=""):n+=o}const s=n;return r.push(s),r};function h(e,t,r,n){return{isApplicable:e,annotation:t,transform:r,untransform:n}}const S=[h(s,"undefined",()=>null,()=>{}),h(e=>"bigint"==typeof e,"bigint",e=>e.toString(),e=>"undefined"!=typeof BigInt?BigInt(e):(console.error("Please add a BigInt polyfill."),e)),h(e=>e instanceof Date&&!isNaN(e.valueOf()),"Date",e=>e.toISOString(),e=>new Date(e)),h(f,"Error",(e,t)=>{const r={name:e.name,message:e.message};return"cause"in e&&(r.cause=e.cause),t.allowedErrorProps.forEach(t=>{r[t]=e[t]}),r},(e,t)=>{const r=new Error(e.message,{cause:e.cause});return r.name=e.name,r.stack=e.stack,t.allowedErrorProps.forEach(t=>{r[t]=e[t]}),r}),h(e=>e instanceof RegExp,"regexp",e=>""+e,e=>{const t=e.slice(1,e.lastIndexOf("/")),r=e.slice(e.lastIndexOf("/")+1);return new RegExp(t,r)}),h(l,"set",e=>[...e.values()],e=>new Set(e)),h(u,"map",e=>[...e.entries()],e=>new Map(e)),h(e=>{return p(e)||((t=e)===1/0||t===-1/0);var t},"number",e=>p(e)?"NaN":e>0?"Infinity":"-Infinity",Number),h(e=>0===e&&1/e==-1/0,"number",()=>"-0",Number),h(e=>e instanceof URL,"URL",e=>e.toString(),e=>new URL(e))];function b(e,t,r,n){return{isApplicable:e,annotation:t,transform:r,untransform:n}}const w=b((e,t)=>{if(c(e)){return!!t.symbolRegistry.getIdentifier(e)}return!1},(e,t)=>["symbol",t.symbolRegistry.getIdentifier(e)],e=>e.description,(e,t,r)=>{const n=r.symbolRegistry.getValue(t[1]);if(!n)throw new Error("Trying to deserialize unknown symbol");return n}),O=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,Uint8ClampedArray].reduce((e,t)=>(e[t.name]=t,e),{}),N=b(e=>ArrayBuffer.isView(e)&&!(e instanceof DataView),e=>["typed-array",e.constructor.name],e=>[...e],(e,t)=>{const r=O[t[1]];if(!r)throw new Error("Trying to deserialize unknown typed array");return new r(e)});function v(e,t){if(e?.constructor){return!!t.classRegistry.getIdentifier(e.constructor)}return!1}const E=b(v,(e,t)=>["class",t.classRegistry.getIdentifier(e.constructor)],(e,t)=>{const r=t.classRegistry.getAllowedProps(e.constructor);if(!r)return{...e};const n={};return r.forEach(t=>{n[t]=e[t]}),n},(e,t,r)=>{const n=r.classRegistry.getValue(t[1]);if(!n)throw new Error(`Trying to deserialize unknown class '${t[1]}' - check https://github.com/blitz-js/superjson/issues/116#issuecomment-773996564`);return Object.assign(Object.create(n.prototype),e)}),I=b((e,t)=>!!t.customTransformerRegistry.findApplicable(e),(e,t)=>["custom",t.customTransformerRegistry.findApplicable(e).name],(e,t)=>t.customTransformerRegistry.findApplicable(e).serialize(e),(e,t,r)=>{const n=r.customTransformerRegistry.findByName(t[1]);if(!n)throw new Error("Trying to deserialize unknown custom value");return n.deserialize(e)}),J=[E,w,I,N],k=(e,t)=>{const r=n(J,r=>r.isApplicable(e,t));if(r)return{value:r.transform(e,t),type:r.annotation(e,t)};const s=n(S,r=>r.isApplicable(e,t));return s?{value:s.transform(e,t),type:s.annotation}:void 0},j={};S.forEach(e=>{j[e.annotation]=e});const A=(e,t)=>{if(t>e.size)throw new Error("index out of bounds");const r=e.keys();for(;t>0;)r.next(),t--;return r.next().value};function T(e){if(r(e,"__proto__"))throw new Error("__proto__ is not allowed as a property");if(r(e,"prototype"))throw new Error("prototype is not allowed as a property");if(r(e,"constructor"))throw new Error("constructor is not allowed as a property")}const R=(e,t,r)=>{if(T(t),0===t.length)return r(e);let n=e;for(let e=0;e<t.length-1;e++){const r=t[e];if(a(n)){n=n[+r]}else if(o(n))n=n[r];else if(l(n)){n=A(n,+r)}else if(u(n)){if(e===t.length-2)break;const s=+r,o=0===+t[++e]?"key":"value",i=A(n,s);switch(o){case"key":n=i;break;case"value":n=n.get(i)}}}const s=t[t.length-1];if(a(n)?n[+s]=r(n[+s]):o(n)&&(n[s]=r(n[s])),l(n)){const e=A(n,+s),t=r(e);e!==t&&(n.delete(e),n.add(t))}if(u(n)){const e=+t[t.length-2],o=A(n,e);switch(0===+s?"key":"value"){case"key":{const e=r(o);n.set(e,n.get(o)),e!==o&&n.delete(o);break}case"value":n.set(o,r(n.get(o)))}}return e},V=e=>e<1;function P(e,r,n,s=[]){if(!e)return;const o=V(n);if(!a(e))return void t(e,(e,t)=>P(e,r,n,[...s,...m(t,o)]));const[i,u]=e;u&&t(u,(e,t)=>{P(e,r,n,[...s,...m(t,o)])}),r(i,s)}function z(e,t,r,n){return P(t,(t,r)=>{e=R(e,r,e=>((e,t,r)=>{if(!a(t)){const n=j[t];if(!n)throw new Error("Unknown transformation: "+t);return n.untransform(e,r)}switch(t[0]){case"symbol":return w.untransform(e,t,r);case"class":return E.untransform(e,t,r);case"custom":return I.untransform(e,t,r);case"typed-array":return N.untransform(e,t,r);default:throw new Error("Unknown transformation: "+t)}})(e,t,n))},r),e}function _(e,r,n){const s=V(n);function o(t,r){const n=((e,t)=>{T(t);for(let r=0;r<t.length;r++){const n=t[r];if(l(e))e=A(e,+n);else if(u(e)){const s=+n,o=0===+t[++r]?"key":"value",i=A(e,s);switch(o){case"key":e=i;break;case"value":e=e.get(i)}}else e=e[n]}return e})(e,m(r,s));t.map(e=>m(e,s)).forEach(t=>{e=R(e,t,()=>n)})}if(a(r)){const[n,i]=r;n.forEach(t=>{e=R(e,m(t,s),()=>e)}),i&&t(i,o)}else t(r,o);return e}const x=(e,n,s,c,p=[],g=[],m=new Map)=>{const h=d(e);if(!h){!function(e,t,r){const n=r.get(e);n?n.push(t):r.set(e,[t])}(e,p,n);const t=m.get(e);if(t)return c?{transformedValue:null}:t}if(!((e,t)=>o(e)||a(e)||u(e)||l(e)||f(e)||v(e,t))(e,s)){const t=k(e,s),r=t?{transformedValue:t.value,annotations:[t.type]}:{transformedValue:e};return h||m.set(e,r),r}if(r(g,e))return{transformedValue:null};const S=k(e,s),b=S?.value??e,w=a(b)?[]:{},O={};t(b,(r,i)=>{if("__proto__"===i||"constructor"===i||"prototype"===i)throw new Error(`Detected property ${i}. This is a prototype pollution risk, please remove it from your object.`);const u=x(r,n,s,c,[...p,i],[...g,e],m);w[i]=u.transformedValue,a(u.annotations)?O[y(i)]=u.annotations:o(u.annotations)&&t(u.annotations,(e,t)=>{O[y(i)+"."+t]=e})});const N=i(O)?{transformedValue:w,annotations:S?[S.type]:void 0}:{transformedValue:w,annotations:S?[S.type,O]:O};return h||m.set(e,N),N};function C(e){return Object.prototype.toString.call(e).slice(8,-1)}function F(e){return"Array"===C(e)}function B(e,t={}){if(F(e))return e.map(e=>B(e,t));if(!function(e){if("Object"!==C(e))return!1;const t=Object.getPrototypeOf(e);return!!t&&t.constructor===Object&&t===Object.prototype}(e))return e;return[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)].reduce((r,n)=>{if("__proto__"===n)return r;if(F(t.props)&&!t.props.includes(n))return r;return function(e,t,r,n,s){const o={}.propertyIsEnumerable.call(n,t)?"enumerable":"nonenumerable";"enumerable"===o&&(e[t]=r),s&&"nonenumerable"===o&&Object.defineProperty(e,t,{value:r,enumerable:!1,writable:!0,configurable:!0})}(r,n,B(e[n],t),e,t.nonenumerable),r},{})}class SuperJSON{constructor({dedupe:e=!1}={}){this.classRegistry=new ClassRegistry,this.symbolRegistry=new Registry(e=>e.description??""),this.customTransformerRegistry=new CustomTransformerRegistry,this.allowedErrorProps=[],this.dedupe=e}serialize(e){const t=new Map,r=x(e,t,this,this.dedupe),n={json:r.transformedValue};r.annotations&&(n.meta={...n.meta,values:r.annotations});const s=function(e,t){const r={};let n;return e.forEach(e=>{if(e.length<=1)return;t||(e=e.map(e=>e.map(String)).sort((e,t)=>e.length-t.length));const[s,...o]=e;0===s.length?n=o.map(g):r[g(s)]=o.map(g)}),n?i(r)?[n]:[n,r]:i(r)?void 0:r}(t,this.dedupe);return s&&(n.meta={...n.meta,referentialEqualities:s}),n.meta&&(n.meta.v=1),n}deserialize(e,t){const{json:r,meta:n}=e;let s=t?.inPlace?r:B(r);return n?.values&&(s=z(s,n.values,n.v??0,this)),n?.referentialEqualities&&(s=_(s,n.referentialEqualities,n.v??0)),s}stringify(e){return JSON.stringify(this.serialize(e))}parse(e){return this.deserialize(JSON.parse(e),{inPlace:!0})}registerClass(e,t){this.classRegistry.register(e,t)}registerSymbol(e,t){this.symbolRegistry.register(e,t)}registerCustom(e,t){this.customTransformerRegistry.register({name:t,...e})}allowErrorProps(...e){this.allowedErrorProps.push(...e)}}SuperJSON.defaultInstance=new SuperJSON,SuperJSON.serialize=SuperJSON.defaultInstance.serialize.bind(SuperJSON.defaultInstance),SuperJSON.deserialize=SuperJSON.defaultInstance.deserialize.bind(SuperJSON.defaultInstance),SuperJSON.stringify=SuperJSON.defaultInstance.stringify.bind(SuperJSON.defaultInstance),SuperJSON.parse=SuperJSON.defaultInstance.parse.bind(SuperJSON.defaultInstance),SuperJSON.registerClass=SuperJSON.defaultInstance.registerClass.bind(SuperJSON.defaultInstance),SuperJSON.registerSymbol=SuperJSON.defaultInstance.registerSymbol.bind(SuperJSON.defaultInstance),SuperJSON.registerCustom=SuperJSON.defaultInstance.registerCustom.bind(SuperJSON.defaultInstance),SuperJSON.allowErrorProps=SuperJSON.defaultInstance.allowErrorProps.bind(SuperJSON.defaultInstance),SuperJSON.serialize,SuperJSON.deserialize,SuperJSON.stringify,SuperJSON.parse,SuperJSON.registerClass,SuperJSON.registerCustom,SuperJSON.registerSymbol,SuperJSON.allowErrorProps;class HyperStorage{static version="6.0.6";static superjson=SuperJSON;itemName;defaultValue;#e;#t;storage;#r;constructor(e,t,r={}){const{encodeFn:n,decodeFn:s,storage:o=window.localStorage}=r;if("string"!=typeof e)throw new TypeError("itemName is not a string");if(this.itemName=e,this.defaultValue=t,n&&"function"!=typeof n)throw new TypeError("encodeFn is defined but is not a function");if(this.#e=n||(e=>HyperStorage.superjson.stringify(e)),s&&"function"!=typeof s)throw new TypeError("decodeFn is defined but is not a function");if(this.#t=s||(e=>HyperStorage.superjson.parse(e)),!(o instanceof Storage))throw new TypeError("storage must be an instance of Storage");this.storage=o,this.sync()}set value(e){this.#r=e,this.storage.setItem(this.itemName,this.#e(e))}get value(){return this.#r}set(e,t){return this.value="function"==typeof e?e(this.#r):{...this.#r,[e]:t}}sync(e=this.#t){const t=this.storage.getItem(this.itemName);if(null===t)return this.reset();try{return this.value=e(t)}catch(e){return console.error(e),this.reset(),e}}reset(){return this.value=this.defaultValue}isDefault(){return this.#r===this.defaultValue}}return HyperStorage});
package/package.json CHANGED
@@ -1,74 +1,74 @@
1
- {
2
- "name": "hyperstorage-js",
3
- "version": "6.0.4",
4
- "description": "A lightweight wrapper for localStorage/sessionStorage with efficient caching and type-preserving serialization.",
5
- "license": "MIT",
6
- "author": "Khoeckman",
7
- "type": "module",
8
- "main": "dist/index.umd.js",
9
- "module": "dist/index.mjs",
10
- "types": "dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.mjs",
15
- "require": "./dist/index.cjs",
16
- "default": "./dist/index.umd.js"
17
- }
18
- },
19
- "files": [
20
- "src/",
21
- "dist/"
22
- ],
23
- "scripts": {
24
- "test": "exit 0",
25
- "build": "rollup -c rollup.config.js",
26
- "prepack": "npm run build"
27
- },
28
- "dependencies": {
29
- "superjson": "^2.2.6"
30
- },
31
- "devDependencies": {
32
- "@rollup/plugin-commonjs": "^29.0.0",
33
- "@rollup/plugin-node-resolve": "^16.0.3",
34
- "@rollup/plugin-replace": "^6.0.3",
35
- "@rollup/plugin-terser": "^0.4.4",
36
- "@rollup/plugin-typescript": "^12.3.0",
37
- "@types/node": "^24.10.7",
38
- "pnpm": "^10.28.0",
39
- "prettier": "^3.7.4",
40
- "rollup": "^4.55.1",
41
- "rollup-plugin-delete": "^3.0.2",
42
- "rollup-plugin-prettier": "^4.1.1",
43
- "tslib": "^2.8.1",
44
- "typescript": "^5.9.3"
45
- },
46
- "publishConfig": {
47
- "access": "public"
48
- },
49
- "homepage": "https://github.com/Khoeckman/HyperStorage#readme",
50
- "bugs": {
51
- "url": "https://github.com/Khoeckman/HyperStorage/issues"
52
- },
53
- "repository": {
54
- "type": "git",
55
- "url": "git+https://github.com/Khoeckman/HyperStorage.git"
56
- },
57
- "keywords": [
58
- "localStorage",
59
- "sessionStorage",
60
- "storage",
61
- "utility",
62
- "javascript",
63
- "js",
64
- "typescript",
65
- "ts",
66
- "ecmascript",
67
- "es",
68
- "umd",
69
- "browser",
70
- "client",
71
- "module",
72
- "commonjs"
73
- ]
74
- }
1
+ {
2
+ "name": "hyperstorage-js",
3
+ "version": "6.0.6",
4
+ "description": "A lightweight wrapper for localStorage/sessionStorage with efficient caching and type-preserving serialization.",
5
+ "license": "MIT",
6
+ "author": "Khoeckman",
7
+ "type": "module",
8
+ "main": "dist/index.umd.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.cjs",
16
+ "default": "./dist/index.umd.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "src/",
21
+ "dist/"
22
+ ],
23
+ "scripts": {
24
+ "test": "exit 0",
25
+ "build": "rollup -c rollup.config.js",
26
+ "prepack": "npm run build"
27
+ },
28
+ "dependencies": {
29
+ "superjson": "^2.2.6"
30
+ },
31
+ "devDependencies": {
32
+ "@rollup/plugin-commonjs": "^29.0.2",
33
+ "@rollup/plugin-node-resolve": "^16.0.3",
34
+ "@rollup/plugin-replace": "^6.0.3",
35
+ "@rollup/plugin-terser": "^1.0.0",
36
+ "@rollup/plugin-typescript": "^12.3.0",
37
+ "@types/node": "^25.3.5",
38
+ "pnpm": "^10.30.3",
39
+ "prettier": "^3.8.1",
40
+ "rollup": "^4.59.0",
41
+ "rollup-plugin-delete": "^3.0.2",
42
+ "rollup-plugin-prettier": "^4.1.2",
43
+ "tslib": "^2.8.1",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "homepage": "https://github.com/Khoeckman/HyperStorage#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/Khoeckman/HyperStorage/issues"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/Khoeckman/HyperStorage.git"
56
+ },
57
+ "keywords": [
58
+ "localStorage",
59
+ "sessionStorage",
60
+ "storage",
61
+ "utility",
62
+ "javascript",
63
+ "js",
64
+ "typescript",
65
+ "ts",
66
+ "ecmascript",
67
+ "es",
68
+ "umd",
69
+ "browser",
70
+ "client",
71
+ "module",
72
+ "commonjs"
73
+ ]
74
+ }
package/src/index.ts CHANGED
@@ -68,7 +68,7 @@ class HyperStorage<T> {
68
68
  this.#encodeFn = encodeFn || ((v) => HyperStorage.superjson.stringify(v))
69
69
 
70
70
  if (decodeFn && typeof decodeFn !== 'function') throw new TypeError('decodeFn is defined but is not a function')
71
- this.#decodeFn = decodeFn || ((v) => HyperStorage.superjson.parse<T>(v))
71
+ this.#decodeFn = decodeFn || ((v) => HyperStorage.superjson.parse(v))
72
72
 
73
73
  if (!(storage instanceof Storage)) throw new TypeError('storage must be an instance of Storage')
74
74
  this.storage = storage
@@ -113,15 +113,15 @@ class HyperStorage<T> {
113
113
  * Using this function should be avoided when possible and is not type safe.
114
114
  */
115
115
  sync(decodeFn = this.#decodeFn): unknown {
116
- let json = this.storage.getItem(this.itemName)
116
+ const json = this.storage.getItem(this.itemName)
117
117
 
118
118
  // Reset value to defaultValue if it does not exist in storage
119
- if (typeof json !== 'string') return this.reset()
119
+ if (json === null) return this.reset()
120
120
 
121
- // Reset value to defaultValue if the incoming value is not properly encoded
122
121
  try {
123
122
  return (this.value = decodeFn(json))
124
123
  } catch (err) {
124
+ // Reset value to defaultValue if the incoming value is not properly encoded
125
125
  console.error(err)
126
126
  this.reset()
127
127
  return err