hyperstorage-js 5.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kyle Hoeckman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,255 @@
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**. Perfect for easy, fast and type-safe client-side data management.
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 all of this by providing a safe and automatic wrapper that handles everything at once. You can read/store numbers and objects without any extra steps and lose no performance.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@khoeckman/hyperstorage.svg)](https://www.npmjs.com/package/@khoeckman/hyperstorage)
8
+ [![npm downloads](https://img.shields.io/npm/dt/@khoeckman/hyperstorage.svg)](https://www.npmjs.com/package/@khoeckman/hyperstorage)
9
+ [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/@khoeckman/hyperstorage/badge)](https://www.jsdelivr.com/package/npm/@khoeckman/hyperstorage)
10
+ [![License](https://img.shields.io/npm/l/@khoeckman/hyperstorage.svg)](LICENSE)
11
+
12
+ ---
13
+
14
+ ## Features
15
+
16
+ - 📝 **Default values**: are automatically set when the key is not in Storage.
17
+ - 🧩 **JSON support**: automatically serializes and parses objects or non-string primitives (`undefined`, `NaN`, `Infinity`) which the Storage API does not support by default.
18
+ - ⚡ **Fast caching**: memory cache avoids repeated JSON convertions.
19
+ - 🔒 **Optional encoding/decoding** hooks to obfuscate data.
20
+ - 🌐 **Custom storage**: works with any object implementing the standard Storage API. (`localStorage`, `sessionStorage`, ...)
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ # npm
28
+ npm install hyperstorage-js
29
+
30
+ # pnpm
31
+ pnpm add hyperstorage-js
32
+
33
+ # yarn
34
+ yarn add hyperstorage-js
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Constructor Syntax
40
+
41
+ ```ts
42
+ class StorageManager<T> {
43
+ constructor(
44
+ itemName: string,
45
+ defaultValue: T,
46
+ options: {
47
+ encodeFn?: (value: string) => string
48
+ decodeFn?: (value: string) => string
49
+ storage?: Storage
50
+ } = {}
51
+ )
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Usage
58
+
59
+ ```js
60
+ import HyperStorage from 'hyperstorage-js'
61
+ ```
62
+
63
+ ```js
64
+ const defaultValue = { theme: 'dark', language: 'en' }
65
+ const userStore = new HyperStorage('userSettings', defaultValue)
66
+
67
+ // If 'userSettings' is not present in the Storage, the defaultValue is set:
68
+ console.log(userStore.value) // { theme: 'dark', language: 'en' }
69
+
70
+ // Change theme to light
71
+ userStore.value = { theme: 'light', language: 'en' }
72
+
73
+ console.log(userStore.value) // { theme: 'light' }
74
+ console.log(userStore.value.theme) // 'light'
75
+
76
+ // Present in localStorage:
77
+ console.log(userStore.storage) // Storage {userSettings: '\x00{"theme":"light"}', length: 1}
78
+ ```
79
+
80
+ ### Different Ways to Assign a New Value
81
+
82
+ ```js
83
+ // Overwrite all
84
+ userStore.value = { theme: 'light', language: 'en' }
85
+
86
+ // Overwrite specific
87
+ userStore.value = { ...userStore.value, theme: 'light' }
88
+
89
+ // Overwrite all using callback
90
+ userStore.set((v) => (v = { theme: 'light', language: 'en' }))
91
+
92
+ // Overwrite specific using callback
93
+ userStore.set((v) => (v.theme = 'light'))
94
+
95
+ // Overwrite and store result
96
+ const result = userStore.set((v) => (v.theme = 'light'))
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: '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, you should 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: 'hN0IEUdoqmJ/', length: 1}
128
+ ```
129
+
130
+ ### Resetting Values
131
+
132
+ ```js
133
+ sessionStore.reset()
134
+ console.log(sessionStore.value) // 'none'
135
+ ```
136
+
137
+ ### Removing Values
138
+
139
+ Internally uses `Storage.removeItem()` to remove the item from storage and sets the cached value to `undefined`.
140
+
141
+ ```js
142
+ sessionStore.remove()
143
+ console.log(sessionStore.value) // undefined
144
+ console.log(sessionStore.storage) // Storage {length: 0}
145
+ ```
146
+
147
+ ---
148
+
149
+ ## TypeScript Usage
150
+
151
+ ### Using Type Parameter `T`
152
+
153
+ ```ts
154
+ interface Settings {
155
+ theme: 'dark' | 'light'
156
+ language: string
157
+ }
158
+
159
+ const defaultValue: Settings = { theme: 'dark', language: 'en' }
160
+ const userStore = new HyperStorage<Settings>('userSettings', { defaultValue })
161
+
162
+ // Property 'language' is missing in type '{ theme: "light"; }' but required in type 'Settings'. ts(2741)
163
+ userStore.value = { theme: 'light' }
164
+
165
+ const current = userStore.sync() // (method): Settings | undefined
166
+ // 'current' is possibly 'undefined'. ts(18048)
167
+ console.log(current.theme) // { theme: 'light' }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## API
173
+
174
+ ### `constructor<T>(itemName: string, defaultValue: T, options = {})`
175
+
176
+ - **itemName**: `string` — key under which the data is stored.
177
+ - **defaultValue**: default value to be stored if none exists.
178
+ - **options** _(optional)_:
179
+ - `encodeFn` — function to encode values before writing to the `Storage`.
180
+ - `decodeFn` — function to decode values when reading from the `Storage`.
181
+ - `storage` — a `Storage` instance (e.g., `localStorage` or `sessionStorage`).
182
+
183
+ ### `value`
184
+
185
+ - **Getter** — returns the cached value (very fast, does not use `JSON.parse`).
186
+ - **Setter** — sets and caches the value, serializing and encoding it into `Storage`.
187
+
188
+ ### `set(callback: (value: T) => T): T`
189
+
190
+ - Updates the stored value using a callback function.
191
+ - The callback receives the current value and must return the new value.
192
+ - Returns the newly stored value.
193
+
194
+ ### `reset(): T`
195
+
196
+ - Resets the stored value to `defaultValue`.
197
+ - Updates both `Storage` and internal cache.
198
+ - Returns the restored default value.
199
+
200
+ ### `remove(): void`
201
+
202
+ - Removes the key and its value from `Storage`.
203
+ - Sets the internal cache to `undefined`.
204
+ - Returns nothing.
205
+
206
+ ### `clear(): void`
207
+
208
+ - Clears **all keys** in `Storage`.
209
+ - Affects all stored data, not just this key.
210
+ - Returns nothing.
211
+
212
+ ### `isDefault(): boolean`
213
+
214
+ - Checks whether the cached value equals the configured default.
215
+ - Uses reference comparison for objects and strict equality for primitives.
216
+ - Returns `true` if the current value matches the default, otherwise `false`.
217
+
218
+ ```js
219
+ if (userStore.isDefault()) {
220
+ console.log('value equals the default value.')
221
+ }
222
+ ```
223
+
224
+ ### `sync(decodeFn = this.decodeFn): unknown`
225
+
226
+ 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`.
227
+
228
+ - **decodeFn** _(optional)_ — a function to decode values when reading (defaults to `this.decodeFn`).
229
+ - Reads the value from storage.
230
+ - Decodes it using `decodeFn`.
231
+ - Updates the internal cache.
232
+ - Returns the synchronized value. The return type is `unknown` because data read from `Storage` cannot be type-checked or trusted at compile time, especially when it may have been modified externally.
233
+
234
+ ```js
235
+ // External change to storage (to be avoided)
236
+ localStorage.setItem('userSettings', '{"theme":"blue"}')
237
+
238
+ // Resynchronize the cache, optionally with a custom decoder
239
+ userStore.sync((value) => JSON.parse(value))
240
+
241
+ console.log(userStore.value) // { theme: 'blue' }
242
+ console.log(userStore.storage) // Storage {userSettings: '\x00{"theme":"blue"}', length: 1}
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Source
248
+
249
+ [GitHub Repository](https://github.com/Khoeckman/HyperStorage)
250
+
251
+ ---
252
+
253
+ ## License
254
+
255
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @class HyperStorage
5
+ * @classdesc A lightweight wrapper for localStorage/sessionStorage
6
+ * with efficient caching and type-preserving serialization.
7
+ *
8
+ * @source https://github.com/Khoeckman/HyperStorage
9
+ */
10
+ class HyperStorage {
11
+ /** Version of the library, injected via Rollup replace plugin. */
12
+ static version = "5.0.0";
13
+ /** Key name under which the data is stored. */
14
+ itemName;
15
+ /** Default value used when the key does not exist in storage. */
16
+ defaultValue;
17
+ /** Function to encode values before storing. */
18
+ encodeFn;
19
+ /** Function to decode values when reading. */
20
+ decodeFn;
21
+ /** The underlying storage backend (defaults to `window.localStorage`). */
22
+ storage;
23
+ /** Internal cached value to improve access speed. */
24
+ #value;
25
+ /**
26
+ * Creates a new HyperStorage instance.
27
+ *
28
+ * @param {string} itemName - The key name under which the data will be stored.
29
+ * @param {T} [defaultValue] - Default value if the key does not exist.
30
+ * @param {Object} [options={}] - Optional configuration parameters.
31
+ * @param {(value: string) => string} [options.encodeFn] - Optional function to encode stored values.
32
+ * @param {(value: string) => string} [options.decodeFn] - Optional function to decode stored values.
33
+ * @param {Storage} [options.storage=window.localStorage] - Optional custom storage backend.
34
+ *
35
+ * @throws {TypeError} If `itemName` is not a string.
36
+ * @throws {TypeError} If `encodeFn` or `decodeFn` are defined but not functions.
37
+ * @throws {TypeError} If `storage` does not implement the standard Storage API.
38
+ */
39
+ constructor(itemName, defaultValue, options = {}) {
40
+ const { encodeFn, decodeFn, storage = window.localStorage } = options;
41
+ if (typeof itemName !== 'string')
42
+ throw new TypeError('itemName is not a string');
43
+ this.itemName = itemName;
44
+ this.defaultValue = defaultValue;
45
+ if (encodeFn && typeof encodeFn !== 'function')
46
+ throw new TypeError('encodeFn is defined but is not a function');
47
+ this.encodeFn = encodeFn || ((v) => v);
48
+ if (decodeFn && typeof decodeFn !== 'function')
49
+ throw new TypeError('decodeFn is defined but is not a function');
50
+ this.decodeFn = decodeFn || ((v) => v);
51
+ if (!(storage instanceof Storage))
52
+ throw new TypeError('storage must be an instance of Storage');
53
+ this.storage = storage;
54
+ this.sync();
55
+ }
56
+ /**
57
+ * Sets the current value in storage.
58
+ * Automatically caches, stringifies and encodes the value.
59
+ */
60
+ set value(value) {
61
+ // Cache real value
62
+ this.#value = value;
63
+ // Store stringified value with prefix to distinguish from raw strings
64
+ let stringValue;
65
+ if (typeof value === 'string') {
66
+ if (value[0] === '\0')
67
+ stringValue = '\0' + value;
68
+ else
69
+ stringValue = value;
70
+ }
71
+ else if (value === undefined ||
72
+ (typeof value === 'number' && (isNaN(value) || value === Infinity || value === -Infinity)))
73
+ // Manually stringify non-JSON values
74
+ stringValue = String(value);
75
+ else
76
+ stringValue = '\0' + JSON.stringify(value);
77
+ this.storage.setItem(this.itemName, this.encodeFn(stringValue));
78
+ }
79
+ /**
80
+ * Gets the current cached value.
81
+ */
82
+ get value() {
83
+ return this.#value ?? this.defaultValue;
84
+ }
85
+ /**
86
+ * Allows using the setter with a callback.
87
+ */
88
+ set(callback) {
89
+ return (this.value = callback(this.value));
90
+ }
91
+ /**
92
+ * Synchronizes the internal cache (`#value`) with the actual value in storage.
93
+ *
94
+ * This is only necessary if the stored value may have been modified externally.
95
+ * Using this function should be avoided when possible and is not type safe.
96
+ */
97
+ sync(decodeFn = this.decodeFn) {
98
+ let value = this.storage.getItem(this.itemName);
99
+ // Reset value to defaultValue if it does not exist in storage
100
+ if (typeof value !== 'string')
101
+ return this.reset();
102
+ // Reset value to defaultValue if the incoming value is not properly encoded
103
+ try {
104
+ value = decodeFn(value);
105
+ }
106
+ catch (err) {
107
+ console.error(err);
108
+ return this.reset();
109
+ }
110
+ if (value[0] !== '\0')
111
+ return (this.value = value); // Raw string value
112
+ // Slice off '\0' prefix
113
+ value = value.slice(1);
114
+ if (value[0] === '\0')
115
+ return (this.value = value); // Raw string value that started with '\0'
116
+ // Parse non JSON
117
+ if (value === 'undefined')
118
+ return (this.value = undefined);
119
+ if (value === 'NaN')
120
+ return (this.value = NaN);
121
+ if (value === 'Infinity')
122
+ return (this.value = Infinity);
123
+ if (value === '-Infinity')
124
+ return (this.value = -Infinity);
125
+ return (this.value = JSON.parse(value));
126
+ }
127
+ /**
128
+ * Resets the stored value to its configured default.
129
+ *
130
+ * Updates both the underlying storage and the internal cache.
131
+ */
132
+ reset() {
133
+ return (this.value = this.defaultValue);
134
+ }
135
+ /**
136
+ * Removes this specific key and its value from storage.
137
+ *
138
+ * Also clears the internal cache to prevent stale data access.
139
+ */
140
+ remove() {
141
+ this.#value = undefined;
142
+ this.storage.removeItem(this.itemName);
143
+ }
144
+ /**
145
+ * Clears **all** data fromstorage.
146
+ *
147
+ * This affects every key in the storage.
148
+ * Also clears the internal cache to prevent stale data access.
149
+ */
150
+ clear() {
151
+ this.#value = undefined;
152
+ this.storage.clear();
153
+ }
154
+ /**
155
+ * Checks whether the current cached value matches the configured default value.
156
+ *
157
+ * Uses reference comparison for objects and strict equality for primitives.
158
+ */
159
+ isDefault() {
160
+ return this.#value === this.defaultValue;
161
+ }
162
+ }
163
+
164
+ module.exports = HyperStorage;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @class HyperStorage
3
+ * @classdesc A lightweight wrapper for localStorage/sessionStorage
4
+ * with efficient caching and type-preserving serialization.
5
+ *
6
+ * @source https://github.com/Khoeckman/HyperStorage
7
+ */
8
+ declare class HyperStorage<T> {
9
+ #private;
10
+ /** Version of the library, injected via Rollup replace plugin. */
11
+ static readonly version: string;
12
+ /** Key name under which the data is stored. */
13
+ readonly itemName: string;
14
+ /** Default value used when the key does not exist in storage. */
15
+ private readonly defaultValue;
16
+ /** Function to encode values before storing. */
17
+ private readonly encodeFn;
18
+ /** Function to decode values when reading. */
19
+ private readonly decodeFn;
20
+ /** The underlying storage backend (defaults to `window.localStorage`). */
21
+ readonly storage: Storage;
22
+ /**
23
+ * Creates a new HyperStorage instance.
24
+ *
25
+ * @param {string} itemName - The key name under which the data will be stored.
26
+ * @param {T} [defaultValue] - Default value if the key does not exist.
27
+ * @param {Object} [options={}] - Optional configuration parameters.
28
+ * @param {(value: string) => string} [options.encodeFn] - Optional function to encode stored values.
29
+ * @param {(value: string) => string} [options.decodeFn] - Optional function to decode stored values.
30
+ * @param {Storage} [options.storage=window.localStorage] - Optional custom storage backend.
31
+ *
32
+ * @throws {TypeError} If `itemName` is not a string.
33
+ * @throws {TypeError} If `encodeFn` or `decodeFn` are defined but not functions.
34
+ * @throws {TypeError} If `storage` does not implement the standard Storage API.
35
+ */
36
+ constructor(itemName: string, defaultValue: T, options?: {
37
+ encodeFn?: (value: string) => string;
38
+ decodeFn?: (value: string) => string;
39
+ storage?: Storage;
40
+ });
41
+ /**
42
+ * Sets the current value in storage.
43
+ * Automatically caches, stringifies and encodes the value.
44
+ */
45
+ set value(value: T);
46
+ /**
47
+ * Gets the current cached value.
48
+ */
49
+ get value(): T;
50
+ /**
51
+ * Allows using the setter with a callback.
52
+ */
53
+ set(callback: (value: T) => T): T;
54
+ /**
55
+ * Synchronizes the internal cache (`#value`) with the actual value in storage.
56
+ *
57
+ * This is only necessary if the stored value may have been modified externally.
58
+ * Using this function should be avoided when possible and is not type safe.
59
+ */
60
+ sync(decodeFn?: (value: string) => string): unknown;
61
+ /**
62
+ * Resets the stored value to its configured default.
63
+ *
64
+ * Updates both the underlying storage and the internal cache.
65
+ */
66
+ reset(): T;
67
+ /**
68
+ * Removes this specific key and its value from storage.
69
+ *
70
+ * Also clears the internal cache to prevent stale data access.
71
+ */
72
+ remove(): void;
73
+ /**
74
+ * Clears **all** data fromstorage.
75
+ *
76
+ * This affects every key in the storage.
77
+ * Also clears the internal cache to prevent stale data access.
78
+ */
79
+ clear(): void;
80
+ /**
81
+ * Checks whether the current cached value matches the configured default value.
82
+ *
83
+ * Uses reference comparison for objects and strict equality for primitives.
84
+ */
85
+ isDefault(): boolean;
86
+ }
87
+ export default HyperStorage;
package/dist/index.mjs ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * @class HyperStorage
3
+ * @classdesc A lightweight wrapper for localStorage/sessionStorage
4
+ * with efficient caching and type-preserving serialization.
5
+ *
6
+ * @source https://github.com/Khoeckman/HyperStorage
7
+ */
8
+ class HyperStorage {
9
+ /** Version of the library, injected via Rollup replace plugin. */
10
+ static version = "5.0.0";
11
+ /** Key name under which the data is stored. */
12
+ itemName;
13
+ /** Default value used when the key does not exist in storage. */
14
+ defaultValue;
15
+ /** Function to encode values before storing. */
16
+ encodeFn;
17
+ /** Function to decode values when reading. */
18
+ decodeFn;
19
+ /** The underlying storage backend (defaults to `window.localStorage`). */
20
+ storage;
21
+ /** Internal cached value to improve access speed. */
22
+ #value;
23
+ /**
24
+ * Creates a new HyperStorage instance.
25
+ *
26
+ * @param {string} itemName - The key name under which the data will be stored.
27
+ * @param {T} [defaultValue] - Default value if the key does not exist.
28
+ * @param {Object} [options={}] - Optional configuration parameters.
29
+ * @param {(value: string) => string} [options.encodeFn] - Optional function to encode stored values.
30
+ * @param {(value: string) => string} [options.decodeFn] - Optional function to decode stored values.
31
+ * @param {Storage} [options.storage=window.localStorage] - Optional custom storage backend.
32
+ *
33
+ * @throws {TypeError} If `itemName` is not a string.
34
+ * @throws {TypeError} If `encodeFn` or `decodeFn` are defined but not functions.
35
+ * @throws {TypeError} If `storage` does not implement the standard Storage API.
36
+ */
37
+ constructor(itemName, defaultValue, options = {}) {
38
+ const { encodeFn, decodeFn, storage = window.localStorage } = options;
39
+ if (typeof itemName !== 'string')
40
+ throw new TypeError('itemName is not a string');
41
+ this.itemName = itemName;
42
+ this.defaultValue = defaultValue;
43
+ if (encodeFn && typeof encodeFn !== 'function')
44
+ throw new TypeError('encodeFn is defined but is not a function');
45
+ this.encodeFn = encodeFn || ((v) => v);
46
+ if (decodeFn && typeof decodeFn !== 'function')
47
+ throw new TypeError('decodeFn is defined but is not a function');
48
+ this.decodeFn = decodeFn || ((v) => v);
49
+ if (!(storage instanceof Storage))
50
+ throw new TypeError('storage must be an instance of Storage');
51
+ this.storage = storage;
52
+ this.sync();
53
+ }
54
+ /**
55
+ * Sets the current value in storage.
56
+ * Automatically caches, stringifies and encodes the value.
57
+ */
58
+ set value(value) {
59
+ // Cache real value
60
+ this.#value = value;
61
+ // Store stringified value with prefix to distinguish from raw strings
62
+ let stringValue;
63
+ if (typeof value === 'string') {
64
+ if (value[0] === '\0')
65
+ stringValue = '\0' + value;
66
+ else
67
+ stringValue = value;
68
+ }
69
+ else if (value === undefined ||
70
+ (typeof value === 'number' && (isNaN(value) || value === Infinity || value === -Infinity)))
71
+ // Manually stringify non-JSON values
72
+ stringValue = String(value);
73
+ else
74
+ stringValue = '\0' + JSON.stringify(value);
75
+ this.storage.setItem(this.itemName, this.encodeFn(stringValue));
76
+ }
77
+ /**
78
+ * Gets the current cached value.
79
+ */
80
+ get value() {
81
+ return this.#value ?? this.defaultValue;
82
+ }
83
+ /**
84
+ * Allows using the setter with a callback.
85
+ */
86
+ set(callback) {
87
+ return (this.value = callback(this.value));
88
+ }
89
+ /**
90
+ * Synchronizes the internal cache (`#value`) with the actual value in storage.
91
+ *
92
+ * This is only necessary if the stored value may have been modified externally.
93
+ * Using this function should be avoided when possible and is not type safe.
94
+ */
95
+ sync(decodeFn = this.decodeFn) {
96
+ let value = this.storage.getItem(this.itemName);
97
+ // Reset value to defaultValue if it does not exist in storage
98
+ if (typeof value !== 'string')
99
+ return this.reset();
100
+ // Reset value to defaultValue if the incoming value is not properly encoded
101
+ try {
102
+ value = decodeFn(value);
103
+ }
104
+ catch (err) {
105
+ console.error(err);
106
+ return this.reset();
107
+ }
108
+ if (value[0] !== '\0')
109
+ return (this.value = value); // Raw string value
110
+ // Slice off '\0' prefix
111
+ value = value.slice(1);
112
+ if (value[0] === '\0')
113
+ return (this.value = value); // Raw string value that started with '\0'
114
+ // Parse non JSON
115
+ if (value === 'undefined')
116
+ return (this.value = undefined);
117
+ if (value === 'NaN')
118
+ return (this.value = NaN);
119
+ if (value === 'Infinity')
120
+ return (this.value = Infinity);
121
+ if (value === '-Infinity')
122
+ return (this.value = -Infinity);
123
+ return (this.value = JSON.parse(value));
124
+ }
125
+ /**
126
+ * Resets the stored value to its configured default.
127
+ *
128
+ * Updates both the underlying storage and the internal cache.
129
+ */
130
+ reset() {
131
+ return (this.value = this.defaultValue);
132
+ }
133
+ /**
134
+ * Removes this specific key and its value from storage.
135
+ *
136
+ * Also clears the internal cache to prevent stale data access.
137
+ */
138
+ remove() {
139
+ this.#value = undefined;
140
+ this.storage.removeItem(this.itemName);
141
+ }
142
+ /**
143
+ * Clears **all** data fromstorage.
144
+ *
145
+ * This affects every key in the storage.
146
+ * Also clears the internal cache to prevent stale data access.
147
+ */
148
+ clear() {
149
+ this.#value = undefined;
150
+ this.storage.clear();
151
+ }
152
+ /**
153
+ * Checks whether the current cached value matches the configured default value.
154
+ *
155
+ * Uses reference comparison for objects and strict equality for primitives.
156
+ */
157
+ isDefault() {
158
+ return this.#value === this.defaultValue;
159
+ }
160
+ }
161
+
162
+ export { HyperStorage as default };
@@ -0,0 +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).StorageManager=t()}(this,function(){"use strict";return class{static version="5.0.0";itemName;defaultValue;encodeFn;decodeFn;storage;#e;constructor(e,t,i={}){const{encodeFn:n,decodeFn:s,storage:o=window.localStorage}=i;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.encodeFn=n||(e=>e),s&&"function"!=typeof s)throw new TypeError("decodeFn is defined but is not a function");if(this.decodeFn=s||(e=>e),!(o instanceof Storage))throw new TypeError("storage must be an instance of Storage");this.storage=o,this.sync()}set value(e){let t;this.#e=e,t="string"==typeof e?"\0"===e[0]?"\0"+e:e:void 0===e||"number"==typeof e&&(isNaN(e)||e===1/0||e===-1/0)?String(e):"\0"+JSON.stringify(e),this.storage.setItem(this.itemName,this.encodeFn(t))}get value(){return this.#e??this.defaultValue}set(e){return this.value=e(this.value)}sync(e=this.decodeFn){let t=this.storage.getItem(this.itemName);if("string"!=typeof t)return this.reset();try{t=e(t)}catch(e){return console.error(e),this.reset()}return"\0"!==t[0]?this.value=t:(t=t.slice(1),"\0"===t[0]?this.value=t:this.value="undefined"===t?void 0:"NaN"===t?NaN:"Infinity"===t?1/0:"-Infinity"===t?-1/0:JSON.parse(t))}reset(){return this.value=this.defaultValue}remove(){this.#e=void 0,this.storage.removeItem(this.itemName)}clear(){this.#e=void 0,this.storage.clear()}isDefault(){return this.#e===this.defaultValue}}});
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "hyperstorage-js",
3
+ "version": "5.0.0",
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
+ "devDependencies": {
29
+ "@rollup/plugin-replace": "^6.0.3",
30
+ "@rollup/plugin-terser": "^0.4.4",
31
+ "@rollup/plugin-typescript": "^12.3.0",
32
+ "@types/node": "^24.10.4",
33
+ "prettier": "^3.7.4",
34
+ "rollup": "^4.55.1",
35
+ "rollup-plugin-delete": "^3.0.2",
36
+ "rollup-plugin-prettier": "^4.1.2",
37
+ "tslib": "^2.8.1",
38
+ "typescript": "^5.9.3"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "homepage": "https://github.com/Khoeckman/HyperStorage#readme",
44
+ "bugs": {
45
+ "url": "https://github.com/Khoeckman/HyperStorage/issues"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/Khoeckman/HyperStorage.git"
50
+ },
51
+ "keywords": [
52
+ "localStorage",
53
+ "sessionStorage",
54
+ "storage",
55
+ "utility",
56
+ "javascript",
57
+ "js",
58
+ "typescript",
59
+ "ts",
60
+ "ecmascript",
61
+ "es",
62
+ "umd",
63
+ "browser",
64
+ "client",
65
+ "module",
66
+ "commonjs"
67
+ ],
68
+ "dependencies": {
69
+ "pnpm": "^10.27.0"
70
+ }
71
+ }
package/src/index.ts ADDED
@@ -0,0 +1,188 @@
1
+ // Injected by Rollup
2
+ declare const __VERSION__: string
3
+
4
+ /**
5
+ * @class HyperStorage
6
+ * @classdesc A lightweight wrapper for localStorage/sessionStorage
7
+ * with efficient caching and type-preserving serialization.
8
+ *
9
+ * @source https://github.com/Khoeckman/HyperStorage
10
+ */
11
+ class HyperStorage<T> {
12
+ /** Version of the library, injected via Rollup replace plugin. */
13
+ static readonly version: string = __VERSION__
14
+
15
+ /** Key name under which the data is stored. */
16
+ readonly itemName: string
17
+
18
+ /** Default value used when the key does not exist in storage. */
19
+ private readonly defaultValue: T
20
+
21
+ /** Function to encode values before storing. */
22
+ private readonly encodeFn: (value: string) => string
23
+
24
+ /** Function to decode values when reading. */
25
+ private readonly decodeFn: (value: string) => string
26
+
27
+ /** The underlying storage backend (defaults to `window.localStorage`). */
28
+ readonly storage: Storage
29
+
30
+ /** Internal cached value to improve access speed. */
31
+ #value?: T
32
+
33
+ /**
34
+ * Creates a new HyperStorage instance.
35
+ *
36
+ * @param {string} itemName - The key name under which the data will be stored.
37
+ * @param {T} [defaultValue] - Default value if the key does not exist.
38
+ * @param {Object} [options={}] - Optional configuration parameters.
39
+ * @param {(value: string) => string} [options.encodeFn] - Optional function to encode stored values.
40
+ * @param {(value: string) => string} [options.decodeFn] - Optional function to decode stored values.
41
+ * @param {Storage} [options.storage=window.localStorage] - Optional custom storage backend.
42
+ *
43
+ * @throws {TypeError} If `itemName` is not a string.
44
+ * @throws {TypeError} If `encodeFn` or `decodeFn` are defined but not functions.
45
+ * @throws {TypeError} If `storage` does not implement the standard Storage API.
46
+ */
47
+ constructor(
48
+ itemName: string,
49
+ defaultValue: T,
50
+ options: {
51
+ encodeFn?: (value: string) => string
52
+ decodeFn?: (value: string) => string
53
+ storage?: Storage
54
+ } = {}
55
+ ) {
56
+ const { encodeFn, decodeFn, storage = window.localStorage } = options
57
+
58
+ if (typeof itemName !== 'string') throw new TypeError('itemName is not a string')
59
+ this.itemName = itemName
60
+ this.defaultValue = defaultValue
61
+
62
+ if (encodeFn && typeof encodeFn !== 'function') throw new TypeError('encodeFn is defined but is not a function')
63
+ this.encodeFn = encodeFn || ((v) => v)
64
+
65
+ if (decodeFn && typeof decodeFn !== 'function') throw new TypeError('decodeFn is defined but is not a function')
66
+ this.decodeFn = decodeFn || ((v) => v)
67
+
68
+ if (!(storage instanceof Storage)) throw new TypeError('storage must be an instance of Storage')
69
+ this.storage = storage
70
+
71
+ this.sync()
72
+ }
73
+
74
+ /**
75
+ * Sets the current value in storage.
76
+ * Automatically caches, stringifies and encodes the value.
77
+ */
78
+ set value(value: T) {
79
+ // Cache real value
80
+ this.#value = value
81
+
82
+ // Store stringified value with prefix to distinguish from raw strings
83
+ let stringValue: string
84
+
85
+ if (typeof value === 'string') {
86
+ if (value[0] === '\0') stringValue = '\0' + value
87
+ else stringValue = value
88
+ } else if (
89
+ value === undefined ||
90
+ (typeof value === 'number' && (isNaN(value) || value === Infinity || value === -Infinity))
91
+ )
92
+ // Manually stringify non-JSON values
93
+ stringValue = String(value)
94
+ else stringValue = '\0' + JSON.stringify(value)
95
+ this.storage.setItem(this.itemName, this.encodeFn(stringValue))
96
+ }
97
+
98
+ /**
99
+ * Gets the current cached value.
100
+ */
101
+ get value(): T {
102
+ return this.#value ?? this.defaultValue
103
+ }
104
+
105
+ /**
106
+ * Allows using the setter with a callback.
107
+ */
108
+ set(callback: (value: T) => T): T {
109
+ return (this.value = callback(this.value))
110
+ }
111
+
112
+ /**
113
+ * Synchronizes the internal cache (`#value`) with the actual value in storage.
114
+ *
115
+ * This is only necessary if the stored value may have been modified externally.
116
+ * Using this function should be avoided when possible and is not type safe.
117
+ */
118
+ sync(decodeFn = this.decodeFn): unknown {
119
+ let value = this.storage.getItem(this.itemName)
120
+
121
+ // Reset value to defaultValue if it does not exist in storage
122
+ if (typeof value !== 'string') return this.reset()
123
+
124
+ // Reset value to defaultValue if the incoming value is not properly encoded
125
+ try {
126
+ value = decodeFn(value)
127
+ } catch (err) {
128
+ console.error(err)
129
+ return this.reset()
130
+ }
131
+
132
+ if (value[0] !== '\0') return (this.value = value as T) // Raw string value
133
+
134
+ // Slice off '\0' prefix
135
+ value = value.slice(1)
136
+
137
+ if (value[0] === '\0') return (this.value = value as T) // Raw string value that started with '\0'
138
+
139
+ // Parse non JSON
140
+ if (value === 'undefined') return (this.value = undefined as any)
141
+ if (value === 'NaN') return (this.value = NaN as any)
142
+ if (value === 'Infinity') return (this.value = Infinity as any)
143
+ if (value === '-Infinity') return (this.value = -Infinity as any)
144
+
145
+ return (this.value = JSON.parse(value) as any)
146
+ }
147
+
148
+ /**
149
+ * Resets the stored value to its configured default.
150
+ *
151
+ * Updates both the underlying storage and the internal cache.
152
+ */
153
+ reset(): T {
154
+ return (this.value = this.defaultValue)
155
+ }
156
+
157
+ /**
158
+ * Removes this specific key and its value from storage.
159
+ *
160
+ * Also clears the internal cache to prevent stale data access.
161
+ */
162
+ remove(): void {
163
+ this.#value = undefined
164
+ this.storage.removeItem(this.itemName)
165
+ }
166
+
167
+ /**
168
+ * Clears **all** data fromstorage.
169
+ *
170
+ * This affects every key in the storage.
171
+ * Also clears the internal cache to prevent stale data access.
172
+ */
173
+ clear(): void {
174
+ this.#value = undefined
175
+ this.storage.clear()
176
+ }
177
+
178
+ /**
179
+ * Checks whether the current cached value matches the configured default value.
180
+ *
181
+ * Uses reference comparison for objects and strict equality for primitives.
182
+ */
183
+ isDefault(): boolean {
184
+ return this.#value === this.defaultValue
185
+ }
186
+ }
187
+
188
+ export default HyperStorage