@rune-hub/utils 1.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/hooks/index.d.ts +5 -0
  4. package/hooks/index.js +8 -0
  5. package/hooks/index.mjs +5 -0
  6. package/hooks/persistent/index.d.ts +1 -0
  7. package/hooks/persistent/index.js +9 -0
  8. package/hooks/persistent/index.mjs +1 -0
  9. package/hooks/persistent/persistent.d.ts +69 -0
  10. package/hooks/persistent/persistent.js +41 -0
  11. package/hooks/persistent/persistent.mjs +37 -0
  12. package/hooks/persistentBool/index.d.ts +1 -0
  13. package/hooks/persistentBool/index.js +9 -0
  14. package/hooks/persistentBool/index.mjs +1 -0
  15. package/hooks/persistentBool/persistentBool.d.ts +66 -0
  16. package/hooks/persistentBool/persistentBool.js +75 -0
  17. package/hooks/persistentBool/persistentBool.mjs +71 -0
  18. package/hooks/persistentJSON/index.d.ts +1 -0
  19. package/hooks/persistentJSON/index.js +9 -0
  20. package/hooks/persistentJSON/index.mjs +1 -0
  21. package/hooks/persistentJSON/persistentJSON.d.ts +46 -0
  22. package/hooks/persistentJSON/persistentJSON.js +53 -0
  23. package/hooks/persistentJSON/persistentJSON.mjs +49 -0
  24. package/hooks/persistentNum/index.d.ts +1 -0
  25. package/hooks/persistentNum/index.js +9 -0
  26. package/hooks/persistentNum/index.mjs +1 -0
  27. package/hooks/persistentNum/persistentNum.d.ts +57 -0
  28. package/hooks/persistentNum/persistentNum.js +66 -0
  29. package/hooks/persistentNum/persistentNum.mjs +62 -0
  30. package/hooks/persistentRune/index.d.ts +1 -0
  31. package/hooks/persistentRune/index.js +10 -0
  32. package/hooks/persistentRune/index.mjs +1 -0
  33. package/hooks/persistentRune/persistentRune.d.ts +4 -0
  34. package/hooks/persistentRune/persistentRune.js +66 -0
  35. package/hooks/persistentRune/persistentRune.mjs +61 -0
  36. package/index.d.ts +1 -0
  37. package/index.js +19 -0
  38. package/index.mjs +6 -0
  39. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mike Lysikov
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,377 @@
1
+ <h1 align="center">
2
+ <a href="https://github.com/d8corp/rune-hub">
3
+ <img src="https://cdn.jsdelivr.net/gh/d8corp/rune-hub@main/logo.svg" width="80">
4
+ </a>
5
+ <br>
6
+ @rune-hub/utils
7
+ </h1>
8
+
9
+ <div align="center">
10
+ <a href="https://www.npmjs.com/package/@rune-hub/utils" target="_blank">
11
+ <img src="https://img.shields.io/npm/v/@rune-hub/utils.svg" alt="rune-hub npm">
12
+ </a>
13
+ <a href="https://github.com/d8corp/rune-hub-utils" target="_blank">
14
+ <img src="https://img.shields.io/badge/-GitHub-181717?logo=github&logoColor=white" alt="rune-hub source code">
15
+ </a>
16
+ <a href="https://www.npmtrends.com/@rune-hub/utils" target="_blank">
17
+ <img src="https://img.shields.io/npm/dm/@rune-hub/utils.svg" alt="rune-hub downloads">
18
+ </a>
19
+ <a href="https://github.com/d8corp/rune-hub-utils/tree/main/release" target="_blank">
20
+ <img src="https://packagephobia.com/badge?p=@rune-hub/utils" alt="rune-hub install size">
21
+ </a>
22
+ <a href="https://www.typescriptlang.org" target="_blank">
23
+ <img src="https://img.shields.io/npm/types/@rune-hub/utils" alt="TypeScript">
24
+ </a>
25
+ <a href="https://github.com/d8corp/rune-hub-utils/blob/main/LICENSE" target="_blank">
26
+ <img src="https://img.shields.io/npm/l/@rune-hub/utils" alt="rune-hub license">
27
+ </a>
28
+ <a href="https://github.com/d8corp/rune-hub-utils/blob/main/CHANGELOG.md" target="_blank">
29
+ <img src="https://img.shields.io/badge/Changelog-⋮-brightgreen" alt="rune-hub changelog">
30
+ </a>
31
+ <a href="https://d8corp.github.io/rune-hub-utils/coverage/lcov-report" target="_blank">
32
+ <img src="https://github.com/d8corp/rune-hub-utils/actions/workflows/tests.yml/badge.svg" alt="rune-hub tests">
33
+ </a>
34
+ <a href="https://github.com/d8corp/rune-hub-utils/issues" target="_blank">
35
+ <img src="https://img.shields.io/github/issues-raw/d8corp/rune-hub-utils" alt="Open issues">
36
+ </a>
37
+ <a href="https://github.com/d8corp/rune-hub-utils/pulls" target="_blank">
38
+ <img src="https://img.shields.io/github/issues-pr-raw/d8corp/rune-hub-utils" alt="Pull requests">
39
+ </a>
40
+ </div>
41
+ <br>
42
+
43
+ `@rune-hub/utils` provides utility hooks for [rune-hub](https://www.npmjs.com/package/rune-hub) that simplify common patterns like persistent state management.
44
+ The library includes specialized hooks for browser storage synchronization with automatic encoding/decoding, cross-tab updates, and type-safe interfaces.
45
+
46
+ [![stars](https://img.shields.io/github/stars/d8corp/rune-hub-utils?style=social)](https://github.com/d8corp/rune-hub-utils/stargazers)
47
+ [![watchers](https://img.shields.io/github/watchers/d8corp/rune-hub-utils?style=social)](https://github.com/d8corp/rune-hub-utils/watchers)
48
+
49
+ ## Index
50
+
51
+ <sup>**[ [Install](#install) ]**</sup>
52
+ <sup>**[ [Hooks](#hooks) ]** [persistent](#persistent) • [persistentBool](#persistentbool) • [persistentNum](#persistentnum) • [persistentJSON](#persistentjson)</sup>
53
+ <sup>**[ [Links](#links) ]**</sup>
54
+
55
+ ## Install
56
+ ###### [🏠︎](#index) / Install [↓](#hooks)
57
+
58
+ **Requires [rune-hub 1.0+](https://www.npmjs.com/package/rune-hub) as a peer dependency.**
59
+
60
+ These utilities are designed to work with rune-hub's reactive state management system.
61
+ Make sure to install both packages:
62
+
63
+ ```shell
64
+ npm i rune-hub @rune-hub/utils
65
+ ```
66
+
67
+ ## Hooks
68
+ ###### [🏠︎](#index) / Hooks [↑](#install) [↓](#links)
69
+
70
+ <sup>[persistent](#persistent) • [persistentBool](#persistentbool) • [persistentNum](#persistentnum) • [persistentJSON](#persistentjson)</sup>
71
+
72
+ ### persistent
73
+ ###### [🏠︎](#index) / [Hooks](#hooks) / persistent [↓](#persistentbool)
74
+
75
+ Creates a persistent [Rune](https://github.com/d8corp/rune-hub#rune) that synchronizes its state with browser storage (`localStorage` by default).
76
+
77
+ The Rune automatically loads the initial value from storage and saves changes back.
78
+ It also listens to `storage` and `pageshow` events to sync state across tabs and page navigations.
79
+
80
+ The simplest form accepts only a `key` parameter and works directly with string values.
81
+ The Rune returns `string | null`.
82
+ This approach provides direct one-to-one mapping between your state and storage:
83
+ if there's no value in storage, you get `null`, otherwise you get the exact string stored.
84
+ When you set the value to `null`, it clears the storage entry completely.
85
+
86
+ ```ts
87
+ import { get, set } from 'rune-hub'
88
+ import { persistent } from '@rune-hub/utils'
89
+
90
+ const state = () => persistent('state')
91
+
92
+ console.log(get(state)) // null initially
93
+
94
+ set(state, 'foo')
95
+ console.log(get(state)) // 'foo'
96
+
97
+ set(state, null)
98
+ console.log(get(state)) // null (storage is clean)
99
+ ```
100
+
101
+ When you provide an `initial` value, it serves as a fallback when storage is empty.
102
+ Note that passing `null` as the initial value works the same as omitting it entirely.
103
+
104
+ When you provide a string as the `initial` value, the Rune becomes strictly typed and returns `string` (not nullable).
105
+ In this mode, you cannot clear the state by setting it to the initial value — the storage will keep the entry with that value.
106
+ This is useful when you always want a valid string state without null checks.
107
+
108
+ ```ts
109
+ const lang = () => persistent('lang', 'en')
110
+
111
+ console.log(get(lang)) // 'en' initially
112
+
113
+ set(lang, 'ru')
114
+ console.log(get(lang)) // 'ru'
115
+
116
+ set(lang, 'en')
117
+ console.log(get(lang)) // 'en' (storage is not clean)
118
+ ```
119
+
120
+ This approach also allows you to safely change the default value in the future.
121
+ For example, if the default theme was initially `'dark'`, a user changed it to `'light'`, then back to `'dark'`.
122
+ Later, developers change the default to `'auto'` — users who manually selected `'dark'` will keep their choice because it's stored.
123
+ Only users who never changed this setting (no value in storage) will get the new `'auto'` default.
124
+
125
+ You can specify allowed state variants using generics to get type safety and autocomplete:
126
+
127
+ ```ts
128
+ type Theme = 'dark' | 'light' | 'auto'
129
+ const theme = () => persistent<Theme>('search', 'auto')
130
+ ```
131
+
132
+ By default, `persistent` uses `localStorage`, which persists data permanently across browser sessions and tabs.
133
+ You can use `sessionStorage` instead for temporary data that only exists within the current tab and is cleared when the tab closes.
134
+ Also, you can use simple/proxy object as storage, the storage changes by set or delete value (`storage[key] = value` or `delete storage[key]`).
135
+
136
+ ```ts
137
+ const chatDraft = () => persistent('chatDraft', '', {
138
+ storage: sessionStorage
139
+ })
140
+ ```
141
+
142
+ When your state type is not `string | null`, you must provide `encode` and `decode` functions to convert between your type and string storage format.
143
+
144
+ The `encode` function converts your state value to a string (or `null` to clear storage), while `decode` converts the stored string back to your state type.
145
+
146
+ ```ts
147
+ const isEnabled = () => persistent('isEnabled', false, {
148
+ decode: v => v === '+',
149
+ encode: v => v ? '+' : '-'
150
+ })
151
+
152
+ set(isEnabled, true)
153
+ console.log(localStorage.getItem('isEnabled')) // '+'
154
+ ```
155
+
156
+ For `string | null` or `string` state types, you can optionally provide `encode` and `decode` to override the default conversion logic.
157
+ For example, you can make the state clear from storage when set back to the initial value:
158
+
159
+ ```ts
160
+ const lang = () => persistent('lang', '', {
161
+ encode: v => v ? v : null
162
+ })
163
+
164
+ set(lang, 'en')
165
+ console.log(localStorage.getItem('lang')) // 'en'
166
+
167
+ set(lang, '')
168
+ console.log(localStorage.getItem('lang')) // null (storage is clean)
169
+ ```
170
+
171
+ ### persistentBool
172
+ ###### [🏠︎](#index) / [Hooks](#hooks) / persistentBool [↑](#persistent) [↓](#persistentnum)
173
+
174
+ Creates a persistent Rune for boolean values with convenient string encoding (`+` for true, `-` for false by default).
175
+ Automatically loads the initial value from storage and saves changes back to it.
176
+
177
+ The simplest form accepts only a `key` parameter and works with nullable boolean values.
178
+ The Rune returns `boolean | null`.
179
+ If there's no value in storage, you get `null`, otherwise you get the decoded boolean value.
180
+ When you set the value to `null`, it clears the storage entry completely.
181
+
182
+ ```ts
183
+ import { get, set } from 'rune-hub'
184
+ import { persistentBool } from '@rune-hub/utils'
185
+
186
+ const consent = () => persistentBool('consent')
187
+
188
+ console.log(get(consent)) // null initially
189
+
190
+ set(consent, true)
191
+ console.log(get(consent)) // true
192
+ console.log(localStorage.getItem('consent')) // '+' (encoded)
193
+
194
+ set(consent, null)
195
+ console.log(get(consent)) // null (storage is clean)
196
+ ```
197
+
198
+ When you provide an `initial` value, it serves as a fallback when storage is empty.
199
+ When you provide a boolean as the `initial` value, the Rune becomes strictly typed and returns `boolean` (not nullable).
200
+ In this mode, you cannot clear the state by setting it to `null` — the storage will keep the entry with the encoded value.
201
+ This is useful when you always want a valid boolean state without null checks.
202
+
203
+ ```ts
204
+ const isDarkMode = () => persistentBool('darkMode', false)
205
+
206
+ console.log(get(isDarkMode)) // false initially
207
+
208
+ set(isDarkMode, true)
209
+ console.log(localStorage.getItem('darkMode')) // '+' (true encoded)
210
+
211
+ set(isDarkMode, false)
212
+ console.log(localStorage.getItem('darkMode')) // '-' (false encoded)
213
+ ```
214
+
215
+ You can customize the encoding by specifying what strings represent `true` and `false`:
216
+
217
+ ```ts
218
+ const isAccepted = () => persistentBool('accepted', false, {
219
+ true: 'yes',
220
+ false: 'no'
221
+ })
222
+
223
+ set(isAccepted, true)
224
+ console.log(localStorage.getItem('accepted')) // 'yes'
225
+ ```
226
+
227
+ By default, `persistentBool` uses `localStorage` for permanent persistence across browser sessions.
228
+ You can use `sessionStorage` instead for temporary data that only exists within the current tab and is cleared when the tab closes.
229
+ You can also use a simple/proxy object as storage.
230
+
231
+ ```ts
232
+ const isExpanded = () => persistentBool('expanded', false, {
233
+ storage: sessionStorage
234
+ })
235
+ ```
236
+
237
+ ### persistentNum
238
+ ###### [🏠︎](#index) / [Hooks](#hooks) / persistentNum [↑](#persistentbool) [↓](#persistentjson)
239
+
240
+ Creates a persistent Rune for numeric values with automatic number parsing and string conversion.
241
+ Automatically loads the initial value from storage and saves changes back to it.
242
+
243
+ The simplest form accepts only a `key` parameter and works with nullable numeric values.
244
+ The Rune returns `number | null`.
245
+
246
+ If there's no value in storage, you get `null`, otherwise you get the parsed number.
247
+ When you set the value to `null`, it clears the storage entry completely.
248
+
249
+ ```ts
250
+ import { get, set } from 'rune-hub'
251
+ import { persistentNum } from '@rune-hub/utils'
252
+
253
+ const score = () => persistentNum('score')
254
+
255
+ console.log(get(score)) // null initially
256
+
257
+ set(score, 100)
258
+ console.log(get(score)) // 100
259
+ console.log(localStorage.getItem('score')) // '100'
260
+
261
+ set(score, null)
262
+ console.log(get(score)) // null (storage is clean)
263
+ console.log(localStorage.getItem('score')) // null
264
+ ```
265
+
266
+ When you provide an `initial` value, it serves as a fallback when storage is empty.
267
+ When you provide a number as the `initial` value, the Rune becomes strictly typed and returns `number` (not nullable).
268
+ In this mode, you cannot clear the state by setting it to `null` — the storage will keep the entry with that value.
269
+ This is useful when you always want a valid numeric state without null checks.
270
+
271
+ ```ts
272
+ const count = () => persistentNum('count', 0)
273
+
274
+ console.log(get(count)) // 0 initially
275
+
276
+ set(count, 42)
277
+ console.log(localStorage.getItem('count')) // '42'
278
+
279
+ set(count, 0)
280
+ console.log(localStorage.getItem('count')) // '0' (storage is not clean)
281
+ ```
282
+
283
+ By default, `persistentNum` uses `localStorage` for permanent persistence across browser sessions.
284
+ You can use `sessionStorage` instead for temporary data that only exists within the current tab and is cleared when the tab closes.
285
+ You can also use a simple/proxy object as storage.
286
+
287
+ ```ts
288
+ const tempCounter = () => persistentNum('counter', 0, {
289
+ storage: sessionStorage
290
+ })
291
+ ```
292
+
293
+ ### persistentJSON
294
+ ###### [🏠︎](#index) / [Hooks](#hooks) / persistentJSON [↑](#persistentnum)
295
+
296
+ Creates a persistent Rune with automatic JSON serialization/deserialization for complex data structures.
297
+ Automatically loads the initial value from storage and saves changes back to it.
298
+
299
+ The simplest form accepts only a `key` parameter and works with nullable values.
300
+ The Rune returns `T | null` where `T` is your data type.
301
+
302
+ If there's no value in storage, you get `null`, otherwise you get the parsed object.
303
+ When you set the value to `null`, it stores the string `"null"` in storage (unlike other persistent hooks).
304
+
305
+ ```ts
306
+ import { get, set } from 'rune-hub'
307
+ import { persistentJSON } from '@rune-hub/utils'
308
+
309
+ interface User {
310
+ name: string
311
+ age: number
312
+ }
313
+
314
+ const user = () => persistentJSON<User>('user')
315
+
316
+ console.log(get(user)) // null initially
317
+
318
+ set(user, { name: 'John', age: 30 })
319
+ console.log(get(user)) // { name: 'John', age: 30 }
320
+ console.log(localStorage.getItem('user')) // '{"name":"John","age":30}'
321
+
322
+ set(user, null)
323
+ console.log(localStorage.getItem('user')) // '"null"' (stored as string)
324
+ ```
325
+
326
+ When you provide an `initial` value, it serves as a fallback when storage is empty.
327
+ When you provide an object as the `initial` value, the Rune becomes strictly typed and returns your type `T` (not nullable).
328
+ In this mode, you cannot clear the state by setting it to `null` — the storage will keep the entry with the encoded value.
329
+ This is useful when you always want a valid object state without null checks.
330
+
331
+ ```ts
332
+ import { get, set } from 'rune-hub'
333
+ import { persistentJSON } from '@rune-hub/utils'
334
+
335
+ const user = () => persistentJSON('user', { name: 'John', age: 30 })
336
+
337
+ console.log(get(user)) // { name: 'John', age: 30 } initially
338
+
339
+ set(user, { name: 'Jane', age: 25 })
340
+ console.log(localStorage.getItem('user')) // '{"name":"Jane","age":25}'
341
+
342
+ set(user, { name: 'John', age: 30 })
343
+ console.log(localStorage.getItem('user')) // '{"name":"John","age":30}' (storage is not clean)
344
+ ```
345
+
346
+ You can also use `persistentJSON` with arrays:
347
+
348
+ ```ts
349
+ const items = () => persistentJSON<string[]>('items', [])
350
+
351
+ set(items, ['apple', 'banana', 'orange'])
352
+ console.log(get(items)) // ['apple', 'banana', 'orange']
353
+ console.log(localStorage.getItem('items')) // '["apple","banana","orange"]'
354
+ ```
355
+
356
+ By default, `persistentJSON` uses `localStorage` for permanent persistence across browser sessions.
357
+ You can use `sessionStorage` instead for temporary data that only exists within the current tab and is cleared when the tab closes.
358
+ You can also use a simple/proxy object as storage.
359
+
360
+ ```ts
361
+ const tempData = () => persistentJSON<any>('data', null, {
362
+ storage: sessionStorage
363
+ })
364
+ ```
365
+
366
+ ## Links
367
+ ###### [🏠︎](#index) / Links [↑](#hooks)
368
+
369
+ - **Creator**: [Mike Lysikov](http://github.com/d8corp)
370
+ - **Source Code**: [GitHub](https://github.com/d8corp/rune-hub-utils)
371
+ - **Repository**: [npm](https://www.npmjs.com/package/@rune-hub/utils) • [npmx](https://npmx.dev/package/@rune-hub/utils)
372
+ - **Core**: [rune-hub](https://github.com/d8corp/rune-hub)
373
+
374
+ **Contributions are welcome!** Please feel free to submit [issues](https://github.com/d8corp/rune-hub-utils/issues) and [pull requests](https://github.com/d8corp/rune-hub-utils/pulls).
375
+
376
+ [![issues](https://img.shields.io/github/issues-raw/d8corp/rune-hub-utils)](https://github.com/d8corp/rune-hub-utils/issues)
377
+ [![pulls](https://img.shields.io/github/issues-pr-raw/d8corp/rune-hub-utils)](https://github.com/d8corp/rune-hub-utils/pulls)
@@ -0,0 +1,5 @@
1
+ export * from './persistent';
2
+ export * from './persistentBool';
3
+ export * from './persistentJSON';
4
+ export * from './persistentNum';
5
+ export * from './persistentRune';
package/hooks/index.js ADDED
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ require('./persistent/index.js');
4
+ require('./persistentBool/index.js');
5
+ require('./persistentJSON/index.js');
6
+ require('./persistentNum/index.js');
7
+ require('./persistentRune/index.js');
8
+
@@ -0,0 +1,5 @@
1
+ import './persistent/index.mjs';
2
+ import './persistentBool/index.mjs';
3
+ import './persistentJSON/index.mjs';
4
+ import './persistentNum/index.mjs';
5
+ import './persistentRune/index.mjs';
@@ -0,0 +1 @@
1
+ export * from './persistent';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var persistent = require('./persistent.js');
6
+
7
+
8
+
9
+ exports.persistent = persistent.persistent;
@@ -0,0 +1 @@
1
+ export { persistent } from './persistent.mjs';
@@ -0,0 +1,69 @@
1
+ import type { PersistentStorage } from '../persistentRune';
2
+ export type PersistentDecode<T> = (v: string) => T;
3
+ export type PersistentEncode<T> = (v: T) => string | null;
4
+ export interface PersistentParams<T> {
5
+ decode: PersistentDecode<T>;
6
+ encode: PersistentEncode<T>;
7
+ storage?: PersistentStorage;
8
+ }
9
+ /**
10
+ * Creates a persistent Rune that synchronizes its state with browser storage (localStorage by default).
11
+ *
12
+ * The Rune automatically loads the initial value from storage and saves changes back.
13
+ * It also listens to `storage` and `pageshow` events to sync state across tabs and page navigations.
14
+ *
15
+ * @param key - Storage key to persist the value under
16
+ * @returns The current stored value (string | null) when called with only a key
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const theme = () => persistent('theme')
21
+ *
22
+ * console.log(get(theme)) // null initially
23
+ * set(theme, 'dark')
24
+ * console.log(localStorage.getItem('theme')) // 'dark'
25
+ * ```
26
+ */
27
+ export declare function persistent(key: string): string | null;
28
+ /**
29
+ * Creates a persistent Rune with custom encoding/decoding logic.
30
+ *
31
+ * @param key - Storage key to persist the value under
32
+ * @param initial - Initial value to use when storage is empty
33
+ * @param params - Encoding/decoding parameters and optional storage
34
+ * @returns The current value (decoded from storage or initial value)
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const isEnabled = () => persistent('isEnabled', false, {
39
+ * decode: v => v === '+',
40
+ * encode: v => v ? '+' : '-'
41
+ * })
42
+ *
43
+ * set(isEnabled, true)
44
+ * console.log(localStorage.getItem('isEnabled')) // '+'
45
+ * ```
46
+ */
47
+ export declare function persistent<T, I = T>(key: string, initial: I, params: PersistentParams<T | I>): I | (unknown extends T ? string : T);
48
+ /**
49
+ * Creates a persistent Rune of `string | null` with optional params.
50
+ *
51
+ * @param key - Storage key to persist the value under
52
+ * @param initial - Initial value to use when storage is empty
53
+ * @param params - Optional encoding/decoding parameters and storage
54
+ * @returns The current value (from storage or initial value)
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const lang = () => persistent('lang', 'en')
59
+ *
60
+ * console.log(get(lang)) // 'en' initially
61
+ *
62
+ * set(lang, 'ru')
63
+ * console.log(localStorage.getItem('lang')) // 'ru'
64
+ *
65
+ * set(lang, 'en')
66
+ * console.log(localStorage.getItem('lang')) // 'en' (storage is not clean)
67
+ * ```
68
+ */
69
+ export declare function persistent<T, I extends string | null = T extends string | null ? T : string | null>(key: string, initial: I, params?: Partial<PersistentParams<T | I>>): I | (unknown extends T ? string : T);
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var runeHub = require('rune-hub');
6
+ require('../persistentRune/index.js');
7
+ var persistentRune = require('../persistentRune/persistentRune.js');
8
+
9
+ const asIs = (v) => v;
10
+ function persistent(key, initial = null, params) {
11
+ var _a, _b, _c;
12
+ const ctx = ((_a = runeHub.Hub.cur) !== null && _a !== void 0 ? _a : runeHub.Hub.root).ctx;
13
+ if (!ctx)
14
+ return initial;
15
+ const decode = ((_b = params === null || params === void 0 ? void 0 : params.decode) !== null && _b !== void 0 ? _b : asIs);
16
+ const initDecode = (value = null) => {
17
+ return value === null ? initial : decode(value);
18
+ };
19
+ const persistentSlot = runeHub.slot(persistentRune.persistentRune(key, params === null || params === void 0 ? void 0 : params.storage));
20
+ let result = persistentSlot.value;
21
+ if (!ctx.inited) {
22
+ const encode = ((_c = params === null || params === void 0 ? void 0 : params.encode) !== null && _c !== void 0 ? _c : asIs);
23
+ ctx.on('change', () => {
24
+ result = encode(ctx.cur);
25
+ persistentSlot.set(result);
26
+ });
27
+ ctx.on('get', () => {
28
+ if (!ctx.up) {
29
+ const value = persistentSlot.raw;
30
+ if (result !== value) {
31
+ result = value;
32
+ ctx.prev = ctx.cur;
33
+ ctx.cur = initDecode(result);
34
+ }
35
+ }
36
+ });
37
+ }
38
+ return initDecode(result);
39
+ }
40
+
41
+ exports.persistent = persistent;
@@ -0,0 +1,37 @@
1
+ import { Hub, slot } from 'rune-hub';
2
+ import '../persistentRune/index.mjs';
3
+ import { persistentRune } from '../persistentRune/persistentRune.mjs';
4
+
5
+ const asIs = (v) => v;
6
+ function persistent(key, initial = null, params) {
7
+ var _a, _b, _c;
8
+ const ctx = ((_a = Hub.cur) !== null && _a !== void 0 ? _a : Hub.root).ctx;
9
+ if (!ctx)
10
+ return initial;
11
+ const decode = ((_b = params === null || params === void 0 ? void 0 : params.decode) !== null && _b !== void 0 ? _b : asIs);
12
+ const initDecode = (value = null) => {
13
+ return value === null ? initial : decode(value);
14
+ };
15
+ const persistentSlot = slot(persistentRune(key, params === null || params === void 0 ? void 0 : params.storage));
16
+ let result = persistentSlot.value;
17
+ if (!ctx.inited) {
18
+ const encode = ((_c = params === null || params === void 0 ? void 0 : params.encode) !== null && _c !== void 0 ? _c : asIs);
19
+ ctx.on('change', () => {
20
+ result = encode(ctx.cur);
21
+ persistentSlot.set(result);
22
+ });
23
+ ctx.on('get', () => {
24
+ if (!ctx.up) {
25
+ const value = persistentSlot.raw;
26
+ if (result !== value) {
27
+ result = value;
28
+ ctx.prev = ctx.cur;
29
+ ctx.cur = initDecode(result);
30
+ }
31
+ }
32
+ });
33
+ }
34
+ return initDecode(result);
35
+ }
36
+
37
+ export { persistent };
@@ -0,0 +1 @@
1
+ export * from './persistentBool';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var persistentBool = require('./persistentBool.js');
6
+
7
+
8
+
9
+ exports.persistentBool = persistentBool.persistentBool;
@@ -0,0 +1 @@
1
+ export { persistentBool } from './persistentBool.mjs';
@@ -0,0 +1,66 @@
1
+ import type { PersistentParams } from '../persistent';
2
+ export interface PersistentBoolParams<T extends boolean | null = boolean | null> extends Omit<PersistentParams<T>, 'decode' | 'encode'> {
3
+ true?: string;
4
+ false?: string;
5
+ }
6
+ /**
7
+ * Creates a persistent Rune for boolean values with customizable string representations.
8
+ *
9
+ * The Rune automatically loads the initial value from storage (localStorage by default) and saves changes back.
10
+ * It also listens to `storage` and `pageshow` events to sync state across tabs and page navigations.
11
+ *
12
+ * @param key - Storage key to persist the value under.
13
+ * @param initial - Initial value to use when storage is empty. Can be `boolean` or `null`.
14
+ * @param params - Optional parameters to customize string representations for `true` and `false` values.
15
+ * @returns The current value (from storage or initial value). Return type depends on `initial`:
16
+ * - If `initial` is `null`, returns `boolean | null`.
17
+ * - Otherwise, returns `boolean`.
18
+ *
19
+ * @example Basic usage without initial value
20
+ * ```ts
21
+ * const isDarkMode = () => persistentBool('darkMode')
22
+ *
23
+ * console.log(get(isDarkMode)) // null initially
24
+ *
25
+ * set(isDarkMode, true)
26
+ * console.log(localStorage.getItem('darkMode')) // '+'
27
+ *
28
+ * set(isDarkMode, null)
29
+ * console.log(localStorage.getItem('darkMode')) // null
30
+ * ```
31
+ *
32
+ * @example Basic usage with initial value
33
+ * ```ts
34
+ * const isDarkMode = () => persistentBool('darkMode', false)
35
+ *
36
+ * console.log(get(isDarkMode)) // false initially
37
+ *
38
+ * set(isDarkMode, true)
39
+ * console.log(localStorage.getItem('darkMode')) // '+'
40
+ *
41
+ * set(isDarkMode, false)
42
+ * console.log(localStorage.getItem('darkMode')) // '-'
43
+ * ```
44
+ *
45
+ * @example Basic usage with custom storage values
46
+ * ```ts
47
+ * const isEnabled = () => persistentBool('isEnabled', false, {
48
+ * true: 'enabled',
49
+ * false: 'disabled'
50
+ * })
51
+ *
52
+ * set(isEnabled, true)
53
+ * console.log(localStorage.getItem('isEnabled')) // 'enabled'
54
+ * ```
55
+ *
56
+ * @example Usage with custom storage
57
+ * ```ts
58
+ * const isEnabled = () => persistentBool('isEnabled', false, {
59
+ * storage: sessionStorage,
60
+ * })
61
+ *
62
+ * set(isEnabled, true)
63
+ * console.log(sessionStorage.getItem('isEnabled')) // '+'
64
+ * ```
65
+ */
66
+ export declare function persistentBool<T extends boolean | null = boolean | null>(key: string, initial?: T, params?: PersistentBoolParams<T>): T extends null ? null | boolean : boolean;