hyperstorage-js 6.0.5 → 6.0.7

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,334 +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
- <br>
24
-
25
- ## Installation
26
-
27
- ```bash
28
- # npm
29
- npm install hyperstorage-js
30
-
31
- # pnpm
32
- pnpm add hyperstorage-js
33
-
34
- # yarn
35
- yarn add hyperstorage-js
36
- ```
37
-
38
- <br>
39
-
40
- ## Constructor Syntax
41
-
42
- ```ts
43
- class HyperStorage<T> {
44
- constructor(
45
- itemName: string,
46
- defaultValue: T,
47
- options: {
48
- encodeFn?: (value: T) => string
49
- decodeFn?: (value: string) => T
50
- storage?: Storage
51
- } = {}
52
- )
53
- }
54
- ```
55
-
56
- <br>
57
-
58
- ## Usage
59
-
60
- ```js
61
- import HyperStorage from 'hyperstorage-js'
62
- ```
63
-
64
- ```js
65
- const defaultValue = { theme: 'light', language: 'en' }
66
- const userStore = new HyperStorage('userSettings', defaultValue)
67
-
68
- // If 'userSettings' is not present in the Storage, the defaultValue is set:
69
- console.log(userStore.value) // { theme: 'light', language: 'en' }
70
-
71
- // Change theme to dark
72
- userStore.value = { theme: 'dark', language: 'en' }
73
- // or
74
- userStore.set('theme', 'dark')
75
-
76
- console.log(userStore.value) // { theme: 'dark', language: 'en' }
77
- console.log(userStore.value.theme) // 'dark'
78
-
79
- // Present in localStorage
80
- console.log(userStore.storage) // Storage {userSettings: '{"json":{"theme":"dark","language":"en"}}', length: 1}
81
- ```
82
-
83
- ### Advanced Ways to Assign a New Value
84
-
85
- ```js
86
- // Overwrite entirely
87
- userStore.value = { theme: 'dark', language: 'en' }
88
-
89
- // Change single property using the setter
90
- userStore.value = { ...userStore.value, theme: 'dark' }
91
-
92
- // Change single property using a callback
93
- userStore.set((v) => (v.theme = 'dark'))
94
-
95
- // Change single property using a property setter
96
- userStore.set('theme', 'dark')
97
- ```
98
-
99
- ### Using Another Storage API
100
-
101
- Use `sessionStorage` to only remember data for the duration of a session.
102
-
103
- ```js
104
- const sessionStore = new HyperStorage('sessionData', 'none', {
105
- storage: window.sessionStorage,
106
- })
107
-
108
- sessionStore.value = 'temporary'
109
- console.log(sessionStore.value) // 'temporary'
110
- console.log(sessionStore.storage) // Storage {sessionData: '{"json":"temporary"}', length: 1}
111
- ```
112
-
113
- ### Using Encoding and Decoding Functions
114
-
115
- If you want to make stored data significantly harder to reverse-engineer, use the `encodeFn` and `decodeFn` options.
116
-
117
- Apply Base64 encoding using JavaScript's `btoa` (String to Base64) and `atob` (Base64 to String).
118
-
119
- ```js
120
- const sessionStore = new HyperStorage('sessionData', 'none', {
121
- encodeFn: (value) => btoa(value),
122
- decodeFn: (value) => atob(value),
123
- })
124
-
125
- sessionStore.value = 'temporary'
126
- console.log(sessionStore.value) // 'temporary'
127
- console.log(sessionStore.storage) // Storage  {sessionData: 'dGVtcG9yYXJ5', length: 1}
128
- ```
129
-
130
- The default values for `encodeFn` and `decodeFn` are:
131
-
132
- ```ts
133
- encodeFn = (value) => HyperStorage.superjson.stringify(value)
134
- decodeFn = (value) => HyperStorage.superjson.parse<T>(value)
135
- ```
136
-
137
- ### Resetting Values
138
-
139
- ```js
140
- sessionStore.reset()
141
- console.log(sessionStore.defaultValue) // 'none'
142
- console.log(sessionStore.value) // 'none'
143
- ```
144
-
145
- <br>
146
-
147
- ## TypeScript Usage
148
-
149
- ### Using Type Parameter `T`
150
-
151
- ```ts
152
- interface Settings {
153
- theme: 'system' | 'light' | 'dark'
154
- language: string
155
- }
156
-
157
- const defaultValue: Settings = { theme: 'system', language: 'en' }
158
- const userStore = new HyperStorage<Settings>('userSettings', defaultValue)
159
-
160
- // Property 'language' is missing in type '{ theme: "dark"; }' but required in type 'Settings'. ts(2741)
161
- userStore.value = { theme: 'dark' }
162
- ```
163
-
164
- ### Using `sync()`
165
-
166
- 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.
167
-
168
- ```ts
169
- // ... continues from the above example
170
-
171
- const result = userStore.sync() // (method): unknown
172
- // Right now, 'result' equals 'userStore.value' exactly
173
-
174
- // 'result' is of type 'unknown'. ts(18046)
175
- console.log(result.theme) // 'dark'
176
-
177
- // No error, but unsafe
178
- console.log(userStore.value.theme) // 'dark'
179
-
180
- // Must narrow down to be safe
181
- if (result && typeof result === 'object' && 'theme' in result) {
182
- // No error, safe
183
- console.log(result.theme) // 'dark'
184
- }
185
- ```
186
-
187
- <br>
188
-
189
- ## Supported Types in Storage
190
-
191
- | Type | Supported by Storage API | Supported by HyperStorage (trough superjson) |
192
- | --------- | ------------------------ | -------------------------------------------- |
193
- | string | ✅ | ✅ |
194
- | undefined | ❌ | ✅ |
195
- | null | ❌ | ✅ |
196
- | boolean | ❌ | ✅ |
197
- | number | ❌ | ✅ |
198
- | bigint | ❌ | ✅ |
199
- | Object | | ✅ |
200
- | Array | ❌ | ✅ |
201
- | Set | ❌ | ✅ |
202
- | Map | ❌ | ✅ |
203
- | URL | ❌ | ✅ |
204
- | Date | ❌ | ✅ |
205
- | RegExp | ❌ | ✅ |
206
- | Error | ❌ | ✅ |
207
-
208
- <br>
209
-
210
- ## API
211
-
212
- ### `constructor<T>(itemName: string, defaultValue: T, options = {})`
213
-
214
- - **itemName**: `string` — key under which the data is stored.
215
- - **defaultValue**: default value to be stored if none exists.
216
- - **options** _(optional)_:
217
- - `encodeFn` function to encode values before writing to the `Storage`.
218
- - `decodeFn` function to decode values when reading from the `Storage`.
219
- - `storage` — a `Storage` instance (e.g., `localStorage` or `sessionStorage`).
220
-
221
- ### `value`
222
-
223
- - **Getter** — returns the cached value (very fast, does not use `JSON.parse`).
224
- - **Setter** — sets and caches the value, serializing and encoding it into `Storage`.
225
-
226
- ### `set(keyOrCallback: (keyof T) | ((value: T) => T), value?: T[keyof T]): T`
227
-
228
- - If a **callback** is provided, it receives the current value and must return the new value.
229
- - If a **key and value** are provided, it updates that single property on the stored object.
230
- - Returns the updated stored value.
231
-
232
- ### `reset(): T`
233
-
234
- - Resets the stored value to `defaultValue`.
235
- - Updates both `Storage` and internal cache.
236
- - Returns the restored default value.
237
-
238
- ### `isDefault(): boolean`
239
-
240
- - Checks whether the cached value equals the configured default.
241
- - Uses reference comparison for objects and strict equality for primitives.
242
- - Returns `true` if the current value matches the default, otherwise `false`.
243
-
244
- ```js
245
- if (userStore.isDefault()) {
246
- console.log('value equals the default value.')
247
- }
248
- ```
249
-
250
- ### `sync(decodeFn = this.decodeFn): unknown`
251
-
252
- 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`.
253
-
254
- - **decodeFn** _(optional)_ — a function to decode values when reading (defaults to `this.decodeFn`).
255
- - Reads the value from storage.
256
- - Decodes it using `decodeFn`.
257
- - Updates the internal cache.
258
- - Returns the synchronized value.
259
-
260
- 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.
261
-
262
- ```js
263
- // External change to storage (to be avoided)
264
- localStorage.setItem('userSettings', '{"theme":"dark"}')
265
-
266
- // Resynchronize the cache, optionally with a custom decoder
267
- userStore.sync((value) => JSON.parse(value))
268
-
269
- console.log(userStore.value) // { theme: 'dark' }
270
- console.log(userStore.storage) // Storage {userSettings: '{"json":{"theme":"dark"}}', length: 1}
271
- ```
272
-
273
- #### Why `sync()` is unsafe
274
-
275
- 1. The item in `Storage` is undecodable by the `decodeFn` passed to `sync()`.
276
-
277
- ```js
278
- localStorage.setItem('userSettings', 'not an object')
279
-
280
- // SyntaxError: Unexpected token 'o', "not an object" is not valid JSON
281
- const result = userStore.sync((value) => JSON.parse(value))
282
- // Execution continues because sync() uses a try-catch
283
-
284
- console.log(result) // instance of SyntaxError
285
-
286
- // Reset to default value
287
- console.log(userStore.value) // {theme: 'system', language: 'en'}
288
- ```
289
-
290
- 2. The item in `Storage`, after being decoded, is not of type `T`.
291
-
292
- ```js
293
- localStorage.setItem('userSettings', '{"not":"valid"}')
294
-
295
- const result = userStore.sync((value) => JSON.parse(value))
296
- console.log(result) // {not: 'valid'}
297
- console.log(userStore.value) // {not: 'valid'}
298
- ```
299
-
300
- No errors, but `result` and `userStore.value` are both not of type `T`.
301
-
302
- This **must** be manually checked and `userStore.reset()` should be called if the check fails.
303
-
304
- ```js
305
- // This example is specifically for type 'Settings'
306
- if (
307
- result &&
308
- typeof result === 'object' &&
309
- 'theme' in result &&
310
- 'language' in result &&
311
- (result.theme === 'system' || result.theme === 'light' || result.theme === 'dark') &&
312
- typeof result.language === 'string'
313
- ) {
314
- // 'result' is of type 'T'
315
- } else {
316
- // 'result' is not of type 'T'
317
- userStore.reset()
318
- console.log(userStore.value) // {theme: 'system', language: 'en'}
319
- }
320
-
321
- // 'userStore.value' is of type 'T'
322
- ```
323
-
324
- <br>
325
-
326
- ## Source
327
-
328
- [GitHub Repository](https://github.com/Khoeckman/HyperStorage)
329
-
330
- <br>
331
-
332
- ## License
333
-
334
- 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 i 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()`](#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 Single 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('session', 'none', {
144
+ storage: window.sessionStorage,
145
+ })
146
+
147
+ sessionStore.value = 'temporary'
148
+ console.log(sessionStore.value) // 'temporary'
149
+ console.log(sessionStore.storage) // Storage {session: '{"json":"temporary"}', length: 1}
150
+ ```
151
+
152
+ ### Using the Encoding and Decoding Functions
153
+
154
+ If you want to make stored data harder to reverse-engineer or change the parser, 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 on top of superjson using JavaScript's `btoa` (String to Base64) and `atob` (Base64 to String) functions.
167
+
168
+ ```js
169
+ const sessionStore = new HyperStorage('session', 'none', {
170
+ encodeFn: (value) => btoa(HyperStorage.superjson.stringify(value)),
171
+ decodeFn: (value) => HyperStorage.superjson.parse(atob(value)),
172
+ })
173
+
174
+ sessionStore.value = 'temporary'
175
+ console.log(sessionStore.value) // 'temporary'
176
+ console.log(sessionStore.storage) // Storage  {session: 'eyJqc29uIjoidGVtcG9yYXJ5In0=', 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 settingsStore = new HyperStorage<Settings>('userSettings', defaultValue)
205
+
206
+ // Property 'language' is missing in type '{ theme: "dark"; }' but required in type 'Settings'. ts(2741)
207
+ settingsStore.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 = settingsStore.sync() // (method): unknown
218
+ // Right now, 'result' equals 'settingsStore.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(settingsStore.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 (settingsStore.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('settings', '{"theme":"dark"}')
311
+
312
+ // Resynchronize the cache, optionally with a custom decoder
313
+ settingsStore.sync((value) => JSON.parse(value))
314
+
315
+ console.log(settingsStore.value) // { theme: 'dark' }
316
+ console.log(settingsStore.storage) // Storage {settings: '{"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('settings', 'not an object')
328
+
329
+ // SyntaxError: Unexpected token 'o', "not an object" is not valid JSON
330
+ const result = settingsStore.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(settingsStore.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('settings', '{"not":"valid"}')
343
+
344
+ const result = settingsStore.sync((value) => JSON.parse(value))
345
+ console.log(result) // {not: 'valid'}
346
+ console.log(settingsStore.value) // {not: 'valid'}
347
+ ```
348
+
349
+ No errors, but `result` and `settingsStore.value` are both not of type `T`.
350
+
351
+ This **must** be manually checked and `settingsStore.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
+ settingsStore.reset()
367
+ console.log(settingsStore.value) // {theme: 'system', language: 'en'}
368
+ }
369
+
370
+ // 'settingsStore.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.5";
870
+ static version = "6.0.7";
871
871
  static superjson = SuperJSON;
872
872
  /** Key name under which the data is stored. */
873
873
  itemName;
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.5";
868
+ static version = "6.0.7";
869
869
  static superjson = SuperJSON;
870
870
  /** Key name under which the data is stored. */
871
871
  itemName;
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.5";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){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 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.7";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.5",
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.7",
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.4.0",
38
+ "pnpm": "^10.32.0",
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
+ }