@plq/use-persisted-state 1.2.0 → 1.4.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 (110) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +168 -43
  3. package/esm/index.d.mts +13 -0
  4. package/esm/index.mjs +10 -0
  5. package/esm/storages/browser-storage.d.mts +1 -0
  6. package/esm/storages/browser-storage.mjs +5 -0
  7. package/esm/storages/chrome-storage.d.mts +1 -0
  8. package/esm/storages/chrome-storage.mjs +5 -0
  9. package/esm/storages/local-storage.d.mts +1 -0
  10. package/esm/storages/local-storage.mjs +3 -0
  11. package/esm/storages/session-storage.d.mts +1 -0
  12. package/esm/storages/session-storage.mjs +3 -0
  13. package/lib/@types/storage.d.ts.map +1 -1
  14. package/lib/create-async-persisted-state.d.ts +2 -2
  15. package/lib/create-async-persisted-state.d.ts.map +1 -1
  16. package/lib/create-async-persisted-state.js +63 -13
  17. package/lib/create-async-persisted-state.js.map +1 -1
  18. package/lib/create-persisted-state.d.ts +2 -2
  19. package/lib/create-persisted-state.d.ts.map +1 -1
  20. package/lib/create-persisted-state.js +32 -14
  21. package/lib/create-persisted-state.js.map +1 -1
  22. package/lib/index.d.ts +6 -5
  23. package/lib/index.d.ts.map +1 -1
  24. package/lib/index.js +5 -3
  25. package/lib/index.js.map +1 -1
  26. package/lib/storages/browser-storage.d.ts +2 -2
  27. package/lib/storages/browser-storage.d.ts.map +1 -1
  28. package/lib/storages/browser-storage.js +6 -26
  29. package/lib/storages/browser-storage.js.map +1 -1
  30. package/lib/storages/chrome-storage.d.ts +2 -2
  31. package/lib/storages/chrome-storage.d.ts.map +1 -1
  32. package/lib/storages/chrome-storage.js +10 -27
  33. package/lib/storages/chrome-storage.js.map +1 -1
  34. package/lib/storages/local-storage.d.ts +1 -1
  35. package/lib/storages/local-storage.d.ts.map +1 -1
  36. package/lib/storages/local-storage.js +3 -1
  37. package/lib/storages/local-storage.js.map +1 -1
  38. package/lib/storages/session-storage.d.ts +1 -1
  39. package/lib/storages/session-storage.d.ts.map +1 -1
  40. package/lib/storages/session-storage.js +3 -1
  41. package/lib/storages/session-storage.js.map +1 -1
  42. package/lib/utils/change-notifier.d.ts +18 -0
  43. package/lib/utils/change-notifier.d.ts.map +1 -0
  44. package/lib/utils/change-notifier.js +38 -0
  45. package/lib/utils/change-notifier.js.map +1 -0
  46. package/lib/utils/create-web-storage.d.ts +10 -2
  47. package/lib/utils/create-web-storage.d.ts.map +1 -1
  48. package/lib/utils/create-web-storage.js +72 -75
  49. package/lib/utils/create-web-storage.js.map +1 -1
  50. package/lib/utils/extension-storage.d.ts +38 -0
  51. package/lib/utils/extension-storage.d.ts.map +1 -0
  52. package/lib/utils/extension-storage.js +66 -0
  53. package/lib/utils/extension-storage.js.map +1 -0
  54. package/lib/utils/get-new-item.d.ts.map +1 -1
  55. package/lib/utils/get-new-item.js +1 -1
  56. package/lib/utils/get-new-item.js.map +1 -1
  57. package/lib/utils/get-new-value.d.ts +1 -1
  58. package/lib/utils/get-new-value.d.ts.map +1 -1
  59. package/lib/utils/get-new-value.js +1 -1
  60. package/lib/utils/get-new-value.js.map +1 -1
  61. package/lib/utils/get-persisted-value.d.ts +8 -1
  62. package/lib/utils/get-persisted-value.d.ts.map +1 -1
  63. package/lib/utils/get-persisted-value.js +20 -6
  64. package/lib/utils/get-persisted-value.js.map +1 -1
  65. package/lib/utils/is-async-storage.d.ts +7 -0
  66. package/lib/utils/is-async-storage.d.ts.map +1 -1
  67. package/lib/utils/is-async-storage.js +52 -30
  68. package/lib/utils/is-async-storage.js.map +1 -1
  69. package/lib/utils/storage-event-router.d.ts +17 -0
  70. package/lib/utils/storage-event-router.d.ts.map +1 -0
  71. package/lib/utils/storage-event-router.js +54 -0
  72. package/lib/utils/storage-event-router.js.map +1 -0
  73. package/lib/utils/use-storage-handler.d.ts +3 -3
  74. package/lib/utils/use-storage-handler.d.ts.map +1 -1
  75. package/lib/utils/use-storage-handler.js +80 -26
  76. package/lib/utils/use-storage-handler.js.map +1 -1
  77. package/package.json +105 -44
  78. package/src/@types/storage.ts +13 -13
  79. package/src/create-async-persisted-state.ts +81 -18
  80. package/src/create-persisted-state.ts +48 -22
  81. package/src/index.ts +12 -7
  82. package/src/storages/browser-storage.ts +8 -36
  83. package/src/storages/chrome-storage.ts +24 -46
  84. package/src/storages/local-storage.ts +3 -1
  85. package/src/storages/session-storage.ts +3 -1
  86. package/src/utils/change-notifier.ts +43 -0
  87. package/src/utils/create-web-storage.ts +74 -77
  88. package/src/utils/extension-storage.ts +79 -0
  89. package/src/utils/get-new-item.ts +1 -1
  90. package/src/utils/get-new-value.ts +1 -1
  91. package/src/utils/get-persisted-value.ts +23 -8
  92. package/src/utils/is-async-storage.ts +72 -37
  93. package/src/utils/storage-event-router.ts +67 -0
  94. package/src/utils/use-storage-handler.ts +112 -36
  95. package/storages/browser-storage.d.ts +1 -0
  96. package/storages/browser-storage.js +11 -0
  97. package/storages/chrome-storage.d.ts +1 -0
  98. package/storages/chrome-storage.js +11 -0
  99. package/storages/local-storage.d.ts +1 -0
  100. package/storages/local-storage.js +9 -0
  101. package/storages/session-storage.d.ts +1 -0
  102. package/storages/session-storage.js +9 -0
  103. package/lib/utils/is-function.d.ts +0 -2
  104. package/lib/utils/is-function.d.ts.map +0 -1
  105. package/lib/utils/is-function.js +0 -9
  106. package/lib/utils/is-function.js.map +0 -1
  107. package/lib/utils/is-promise.d.ts +0 -2
  108. package/lib/utils/is-promise.d.ts.map +0 -1
  109. package/lib/utils/is-promise.js +0 -10
  110. package/lib/utils/is-promise.js.map +0 -1
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 Uploadcare
3
+ Copyright (c) 2019 Alexander Kurganov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,21 +1,37 @@
1
1
  # usePersistedState
2
+
2
3
  [![npm version](https://badge.fury.io/js/@plq%2Fuse-persisted-state.svg)](https://www.npmjs.com/package/@plq/use-persisted-state)
3
- [![Tests](https://github.com/Akurganow/use-persisted-state/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/Akurganow/use-persisted-state/actions/workflows/main.yml)
4
+ [![Tests](https://github.com/Akurganow/use-persisted-state/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/Akurganow/use-persisted-state/actions/workflows/main.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![Ko-fi](https://img.shields.io/badge/Ko--fi-support-FF5E5B?logo=ko-fi&logoColor=white)](https://ko-fi.com/akurganow)
4
7
 
5
- Persists the state to localStorage, sessionStorage or any custom storage
8
+ A React `useState` that persists to `localStorage`, `sessionStorage`, extension storage (`browser.storage` / `chrome.storage`), or any custom backend.
6
9
 
7
10
  ## Features
8
11
 
9
- - Persist the state to `localStorage`, `sessionStorage` or almost anything else implements [storage API](https://github.com/Akurganow/use-persisted-state/blob/master/docs/storage-api.md)
10
- - The state factory takes as many keys as needed, so you don't have to call the factory for each variable
11
- - Written with the TypeScript, the definitions go with the library
12
- - No third-party dependencies
12
+ - Persist state to `localStorage`, `sessionStorage`, extension storage, or almost anything else that implements the [storage API](https://github.com/Akurganow/use-persisted-state/blob/main/docs/storage-api.md)
13
+ - One state factory serves as many keys as needed, so you don't have to call the factory for each variable
14
+ - Supports both synchronous and asynchronous storage backends
15
+ - Components using the same key stay in sync; with the `localStorage` adapter, changes also propagate across browser tabs
16
+ - Written in TypeScript — type definitions ship with the package
17
+ - A single tiny runtime dependency: [`@plq/is`](https://www.npmjs.com/package/@plq/is)
18
+
19
+ ## Requirements
20
+
21
+ To use `@plq/use-persisted-state`, you must use `react@16.8.0` or greater, which includes Hooks.
22
+ The library is tested against React 19. The peer range allows React 16.8 and above, though only 19 is exercised in CI.
13
23
 
14
- ## Example
24
+ ## Install
25
+
26
+ ```sh
27
+ npm install @plq/use-persisted-state
28
+ ```
29
+
30
+ ## Quick start
15
31
 
16
32
  ```jsx
17
33
  import createPersistedState from '@plq/use-persisted-state'
18
- import storage from '@plq/use-persisted-state/lib/storages/local-storage'
34
+ import storage from '@plq/use-persisted-state/storages/local-storage'
19
35
 
20
36
  const [usePersistedState] = createPersistedState('example', storage)
21
37
 
@@ -32,15 +48,64 @@ export default function App() {
32
48
  }
33
49
  ```
34
50
 
35
- [![Edit @plq/use-persisted-state](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/plquse-persisted-state-ob2od?fontsize=14)
51
+ This example uses the bundled `localStorage` adapter; [Storage adapters](#storage-adapters) lists the rest and what each one is for.
52
+
53
+ To try it locally, run the demo app from a repository checkout: `npm ci && npm run demo`.
54
+
55
+ ## API
56
+
57
+ ### `createPersistedState(name, storage)` — default export
58
+
59
+ ```ts
60
+ const [usePersistedState, clear] = createPersistedState(name, storage)
61
+ ```
62
+
63
+ - `name` — a namespace for this factory. All keys created by the returned hook are stored together in a single storage entry named `persisted_state_hook:<name>`.
64
+ - `storage` — a synchronous or asynchronous storage backend implementing the [storage API](https://github.com/Akurganow/use-persisted-state/blob/main/docs/storage-api.md).
65
+
66
+ Returns a `[usePersistedState, clear]` tuple. The default export detects whether the backend is asynchronous from the shape of its methods wherever it can — a `get` declared `async` settles the question without a call. Only when that does not hold does it call `get('')` once, as a method of your storage, to see whether the result is a `Promise`; `set` and `remove` are never invoked, so detection cannot write to your storage. All three members must be functions. If even a read during setup is undesirable, import one of the named factories below instead — they skip detection entirely.
67
+
68
+ ### Named factories
69
+
70
+ ```ts
71
+ import { createPersistedState, createAsyncPersistedState } from '@plq/use-persisted-state'
72
+ ```
73
+
74
+ - `createPersistedState(name, storage)` — for synchronous backends only (`localStorage`, `sessionStorage`).
75
+ - `createAsyncPersistedState(name, storage)` — for asynchronous backends only (`browser.storage`, `chrome.storage`, custom promise-based backends).
76
+
77
+ ### `usePersistedState(key, initialValue)`
78
+
79
+ ```ts
80
+ const [state, setState] = usePersistedState(key, initialValue)
81
+ ```
82
+
83
+ Works like `useState`: returns the current state and a setter that accepts either a value or an updater function (`prev => next`). In addition, every update is written to the storage backend, and external changes to the stored value update the state.
84
+
85
+ - Values are serialized with `JSON.stringify`, so they must be JSON-serializable (no functions, class instances, `Map`, `Set`, etc.). A few values serialize into something else rather than failing outright — see [Values JSON cannot carry](#values-json-cannot-carry).
86
+ - With an asynchronous backend, the hook renders `initialValue` first and updates once the stored value has loaded.
87
+
88
+ ### `clear()`
89
+
90
+ Removes the factory's whole storage entry. Hooks created by that factory fall back to their initial values. Returns `void` for synchronous backends and `Promise<void>` for asynchronous ones.
91
+
92
+ ### TypeScript
93
+
94
+ The package ships its own type definitions. The storage contract and the hook's own types are exported from the entry point:
95
+
96
+ ```ts
97
+ import type { Storage, AsyncStorage, StorageChange, StorageChangeListener } from '@plq/use-persisted-state'
98
+ ```
99
+
100
+ `Storage` and `AsyncStorage` are the two backend contracts. `StorageChange`, `StorageChangeListener` and `StorageChangeEvent` describe the `onChanged` event an adapter provides. `PersistedState` and `UsePersistedState` type the hook and the tuple it returns.
101
+
102
+ The longer `@plq/use-persisted-state/lib/@types/storage` path that earlier versions documented continues to work and resolves to the same types. Prefer the entry point: `lib/` is kept open for compatibility only.
36
103
 
37
- ## Requirement
38
- To use `@plq/use-persisted-state`, you must use `react@16.8.0` or greater which includes Hooks.
104
+ ## Clear storage
39
105
 
40
- ## Clear Storage
41
106
  ```jsx
42
107
  import createPersistedState from '@plq/use-persisted-state'
43
- import storage from '@plq/use-persisted-state/lib/storages/local-storage'
108
+ import storage from '@plq/use-persisted-state/storages/local-storage'
44
109
 
45
110
  const [usePersistedState, clear] = createPersistedState('example', storage)
46
111
 
@@ -57,44 +122,36 @@ export default function App() {
57
122
  )
58
123
  }
59
124
  ```
125
+
60
126
  ## Use sessionStorage
127
+
61
128
  ```jsx
62
129
  import createPersistedState from '@plq/use-persisted-state'
63
- import storage from '@plq/use-persisted-state/lib/storages/session-storage'
130
+ import storage from '@plq/use-persisted-state/storages/session-storage'
64
131
 
65
132
  const [usePersistedState, clear] = createPersistedState('example', storage)
66
133
  ```
134
+
67
135
  ## Use async storage
136
+
68
137
  ```jsx
69
138
  import createPersistedState from '@plq/use-persisted-state'
70
- // or
139
+ // or, to skip async detection:
71
140
  import { createAsyncPersistedState } from '@plq/use-persisted-state'
72
- import { local } from '@plq/use-persisted-state/lib/storages/browser-storage'
141
+ import { local } from '@plq/use-persisted-state/storages/browser-storage'
73
142
 
74
143
  const [usePersistedState, clear] = createPersistedState('example', local)
75
144
  ```
145
+
76
146
  ## Use custom storage
77
147
 
78
- The [storage API](https://github.com/Akurganow/use-persisted-state/blob/master/docs/storage-api.md) is similar to the browser.storage but with a few differences
148
+ The [storage API](https://github.com/Akurganow/use-persisted-state/blob/main/docs/storage-api.md) is similar to the WebExtensions `browser.storage` API, with a few differences. Any object implementing it works:
79
149
 
80
150
  ```jsx
81
151
  import createPersistedState from '@plq/use-persisted-state'
82
152
 
83
153
  const storageListeners = new Set()
84
154
 
85
- onChangeSomeStorage(event => {
86
- const changes = {
87
- [event.key]: {
88
- newValue: event.newValue,
89
- oldValue: event.oldValue,
90
- },
91
- }
92
-
93
- listeners.forEach(listener => {
94
- listener(changes)
95
- })
96
- })
97
-
98
155
  const myStorage = {
99
156
  get: keys => getItemsFromSomeStorage(keys),
100
157
  set: items => setItemsToSomeStorage(items),
@@ -103,21 +160,89 @@ const myStorage = {
103
160
  addListener: listener => storageListeners.add(listener),
104
161
  removeListener: listener => storageListeners.delete(listener),
105
162
  hasListener: listener => storageListeners.has(listener),
106
- }
163
+ },
107
164
  }
108
165
 
109
166
  const [usePersistedState, clear] = createPersistedState('example', myStorage)
110
167
  ```
168
+
169
+ Your adapter owns that listener set and calls every listener when the backing store changes, which is what keeps components on the same key in sync. The [storage API](https://github.com/Akurganow/use-persisted-state/blob/main/docs/storage-api.md) documents the change payload and carries the complete example, including how to forward a backend's own events to the listeners.
170
+
111
171
  ## Storage adapters
112
- ### [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) `@plq/use-persisted-state/lib/storages/local-storage`
113
- - Useful for average web application
114
- ### [sessionStorage](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage) `@plq/use-persisted-state/lib/storages/session-storage`
115
- - Useful for average web application
116
- ### [browser.storage](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/storage) `@plq/use-persisted-state/lib/storages/browser-storage`
117
- - Only for web extensions.
118
- - Don't forget to set up [polyfill](https://github.com/mozilla/webextension-polyfill) if you want to run extension in Chrome browser.
119
- - To use this storage you need to include the "storage" [permission](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions) in your `manifest.json` file
120
- ### [chrome.storage](https://developer.chrome.com/apps/storage) `@plq/use-persisted-state/lib/storages/chrome-storage`
121
- - Only for Chrome-based web extensions.
122
- - If you develop extension that will be run only in Chrome browser you can use this storage without [polyfill](https://github.com/mozilla/webextension-polyfill).
123
- - You must declare the "storage" permission in the [extension manifest](https://developer.chrome.com/apps/manifest) to use this storage.
172
+
173
+ The longer `@plq/use-persisted-state/lib/storages/…` paths that earlier versions documented continue
174
+ to work. Prefer the shorter ones below: imported from ES module code, the `lib` path hands back the
175
+ CommonJS exports object rather than the adapter, so it needs an extra `.default` that the shorter
176
+ path does not.
177
+
178
+ ### [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) `@plq/use-persisted-state/storages/local-storage`
179
+
180
+ - Useful for the average web application.
181
+ - Synchronous. Changes made in other browser tabs are picked up through the [`storage` event](https://developer.mozilla.org/docs/Web/API/Window/storage_event).
182
+
183
+ ### [sessionStorage](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage) `@plq/use-persisted-state/storages/session-storage`
184
+
185
+ - Useful for state that should not outlive the browser session.
186
+ - Synchronous.
187
+
188
+ ### [browser.storage](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/storage) `@plq/use-persisted-state/storages/browser-storage`
189
+
190
+ - Only for web extensions. Asynchronous.
191
+ - Named exports for each storage area: `local`, `sync` and `managed` (note that the [managed area](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/storage/managed) is read-only for the extension).
192
+ - Don't forget to set up the [polyfill](https://github.com/mozilla/webextension-polyfill) if you want to run the extension in a Chromium-based browser.
193
+ - You need to declare the "storage" [permission](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions) in your `manifest.json` file.
194
+
195
+ ### [chrome.storage](https://developer.chrome.com/docs/extensions/reference/api/storage) `@plq/use-persisted-state/storages/chrome-storage`
196
+
197
+ - Only for Chromium-based web extensions. Asynchronous.
198
+ - Named exports for each storage area: `local`, `sync` and `managed` (the managed area is read-only for the extension).
199
+ - If your extension runs only in Chromium-based browsers, you can use this adapter without the polyfill.
200
+ - You must declare the "storage" permission in the [extension manifest](https://developer.chrome.com/docs/extensions/reference/manifest) to use this adapter.
201
+
202
+ ```jsx
203
+ import createPersistedState from '@plq/use-persisted-state'
204
+ import { local } from '@plq/use-persisted-state/storages/chrome-storage'
205
+
206
+ const [usePersistedState, clear] = createPersistedState('example', local)
207
+ ```
208
+
209
+ ## Server-side rendering
210
+
211
+ This library targets browser environments. Two things matter when rendering on a server:
212
+
213
+ - The bundled `local-storage` and `session-storage` adapters can be imported without `localStorage` / `sessionStorage`. Where the global is missing the adapter reads back nothing and discards every write, so the hook keeps its initial value rather than throwing.
214
+ - The synchronous hook reads from storage during render.
215
+
216
+ When using an SSR framework (Next.js, Remix, etc.), make sure the components using the hook run only on the client — for example, in client-only components or behind a dynamic import that is disabled during SSR.
217
+
218
+ ## How values are stored
219
+
220
+ Each factory keeps all of its keys in a single storage entry named `persisted_state_hook:<name>`, holding a JSON object with one property per key:
221
+
222
+ ```
223
+ persisted_state_hook:example → {"count":0}
224
+ ```
225
+
226
+ Storage backends only ever see serialized strings. Anything you persist ends up unencrypted in the underlying storage — do not store secrets or sensitive data (see [SECURITY.md](https://github.com/Akurganow/use-persisted-state/blob/main/SECURITY.md)).
227
+
228
+ ### Values JSON cannot carry
229
+
230
+ A few values do not survive `JSON.stringify`. This follows from the format rather than from a choice
231
+ the library makes, and no adapter can repair it: once a value has been written, nothing in storage
232
+ tells a `null` you stored apart from a `null` that JSON produced.
233
+
234
+ - **`undefined`** — the key is dropped, so nothing reaches storage at all. The component that set it
235
+ keeps `undefined` in memory, as `useState` would, and other components already mounted on that key
236
+ see no change and keep the value they hold. After a remount or a reload the key is absent, so the
237
+ initial value comes back.
238
+ - **`NaN`, `Infinity`, `-Infinity`** — these are written as `null`. The component that set the value
239
+ keeps it until it remounts; every other component on that key reads back `null`, as does any
240
+ reader after a reload.
241
+
242
+ ## Contributing
243
+
244
+ Contributions are welcome — see [CONTRIBUTING.md](https://github.com/Akurganow/use-persisted-state/blob/main/CONTRIBUTING.md) for setup, testing and the commit convention, and [CODE_OF_CONDUCT.md](https://github.com/Akurganow/use-persisted-state/blob/main/CODE_OF_CONDUCT.md) for community standards.
245
+
246
+ ## License
247
+
248
+ [MIT](LICENSE)
@@ -0,0 +1,13 @@
1
+ // Type-level aliases of the build output rather than restated signatures: a
2
+ // copy keeps compiling after src changes and hands ESM consumers stale types
3
+ // while CJS consumers get the current ones.
4
+ export { default, createPersistedState, createAsyncPersistedState } from '../lib/index.js'
5
+ export type {
6
+ AsyncStorage,
7
+ PersistedState,
8
+ Storage,
9
+ StorageChange,
10
+ StorageChangeEvent,
11
+ StorageChangeListener,
12
+ UsePersistedState,
13
+ } from '../lib/index.js'
package/esm/index.mjs ADDED
@@ -0,0 +1,10 @@
1
+ // Hand-written ES module wrapper over the single CJS build. A second compiled
2
+ // ESM copy would duplicate the module-level listener registries, so hooks
3
+ // loaded through one module system would stop seeing writes made through the
4
+ // other. Values are re-exported through property access because Node's named
5
+ // export detection for CJS is heuristic.
6
+ import cjs from '../lib/index.js'
7
+
8
+ export default cjs.default
9
+ export const createPersistedState = cjs.createPersistedState
10
+ export const createAsyncPersistedState = cjs.createAsyncPersistedState
@@ -0,0 +1 @@
1
+ export { local, sync, managed } from '../../lib/storages/browser-storage.js'
@@ -0,0 +1,5 @@
1
+ import cjs from '../../lib/storages/browser-storage.js'
2
+
3
+ export const local = cjs.local
4
+ export const sync = cjs.sync
5
+ export const managed = cjs.managed
@@ -0,0 +1 @@
1
+ export { local, sync, managed } from '../../lib/storages/chrome-storage.js'
@@ -0,0 +1,5 @@
1
+ import cjs from '../../lib/storages/chrome-storage.js'
2
+
3
+ export const local = cjs.local
4
+ export const sync = cjs.sync
5
+ export const managed = cjs.managed
@@ -0,0 +1 @@
1
+ export { default } from '../../lib/storages/local-storage.js'
@@ -0,0 +1,3 @@
1
+ import cjs from '../../lib/storages/local-storage.js'
2
+
3
+ export default cjs.default
@@ -0,0 +1 @@
1
+ export { default } from '../../lib/storages/session-storage.js'
@@ -0,0 +1,3 @@
1
+ import cjs from '../../lib/storages/session-storage.js'
2
+
3
+ export default cjs.default
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/@types/storage.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;CAAE,KAAK,IAAI,CAAA;AAEvF,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACnD,cAAc,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACtD,WAAW,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC;CACvD;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC5D,GAAG,EAAE,CAAC,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;IAC1C,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,GAAG,EAAE,CAAC,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,SAAS,EAAE,kBAAkB,CAAC;CAC/B"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/@types/storage.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;CAAE,KAAK,IAAI,CAAA;AAEvF,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAA;IAClD,cAAc,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAA;IACrD,WAAW,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAA;CACtD;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;IAC3D,GAAG,EAAE,CAAC,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IAC/C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAA;IACzC,SAAS,EAAE,kBAAkB,CAAA;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAAA;IACpE,GAAG,EAAE,CAAC,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,SAAS,EAAE,kBAAkB,CAAA;CAC9B"}
@@ -1,4 +1,4 @@
1
- import { AsyncStorage } from './@types/storage';
2
- import { PersistedState } from './@types/hook';
1
+ import type { AsyncStorage } from './@types/storage';
2
+ import type { PersistedState } from './@types/hook';
3
3
  export default function createAsyncPersistedState<S extends AsyncStorage>(storageKey: string, storage: S): [PersistedState, () => Promise<void>];
4
4
  //# sourceMappingURL=create-async-persisted-state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-async-persisted-state.d.ts","sourceRoot":"","sources":["../src/create-async-persisted-state.ts"],"names":[],"mappings":"AAOA,OAAO,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAC,cAAc,EAAoB,MAAM,eAAe,CAAA;AAE/D,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,CAAC,SAAS,YAAY,EACtE,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,GACT,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAuCvC"}
1
+ {"version":3,"file":"create-async-persisted-state.d.ts","sourceRoot":"","sources":["../src/create-async-persisted-state.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,eAAe,CAAA;AAEtE,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,CAAC,SAAS,YAAY,EACtE,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,GACT,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAqGvC"}
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = createAsyncPersistedState;
6
7
  const react_1 = require("react");
7
8
  const use_storage_handler_1 = __importDefault(require("./utils/use-storage-handler"));
8
9
  const get_new_value_1 = __importDefault(require("./utils/get-new-value"));
@@ -15,26 +16,75 @@ function createAsyncPersistedState(storageKey, storage) {
15
16
  };
16
17
  const usePersistedState = (key, initialValue) => {
17
18
  const [state, setState] = (0, react_1.useState)(initialValue);
18
- const setPersistedState = async (newState) => {
19
- const newValue = (0, get_new_value_1.default)(newState, state);
20
- setState(newValue);
19
+ // The setter must resolve updater functions against the last value applied,
20
+ // not against the one captured by the render that created it. Reading the
21
+ // render closure collapses updates batched into a single event.
22
+ const latestValue = (0, react_1.useRef)(state);
23
+ // Whether anything has already put a value in place. A load that settles after
24
+ // the caller set one must not revert it, and no signal local to the load can
25
+ // answer this: the write it loses to happens outside the load entirely.
26
+ const hasAppliedValue = (0, react_1.useRef)(false);
27
+ const applyValue = (0, react_1.useCallback)((value) => {
28
+ latestValue.current = value;
29
+ hasAppliedValue.current = true;
30
+ setState(value);
31
+ }, []);
32
+ // The exact entry this hook last wrote and has not yet seen reported back.
33
+ const pendingOwnWrite = (0, react_1.useRef)(null);
34
+ const setPersistedState = (0, react_1.useCallback)(async (newState) => {
35
+ const newValue = (0, get_new_value_1.default)(newState, latestValue.current);
36
+ applyValue(newValue);
21
37
  const persistedItem = await storage.get(safeStorageKey);
22
38
  const newItem = (0, get_new_item_1.default)(key, persistedItem[safeStorageKey], newValue);
39
+ // Recorded before the write, because the backend may report it before the
40
+ // promise settles.
41
+ pendingOwnWrite.current = newItem;
23
42
  await storage.set({ [safeStorageKey]: newItem });
24
- };
43
+ }, [key, applyValue]);
44
+ // As in `useState`, the value the load falls back to is the one given on the
45
+ // first render. Later identities of an inline object must not reload, or the
46
+ // effect re-runs on every render and never settles.
47
+ const mountInitialValue = (0, react_1.useRef)(initialValue);
48
+ // Subscribed before the load below, and the order is load-bearing: React runs
49
+ // effects in declaration order, so reading first would leave an interval
50
+ // between the read and the subscription in which a write belongs to neither
51
+ // and is lost. Reading last covers everything written before the subscription
52
+ // began, and the listener covers everything after.
53
+ (0, use_storage_handler_1.default)(key, safeStorageKey, applyValue, storage, initialValue, pendingOwnWrite);
25
54
  (0, react_1.useEffect)(() => {
26
- const setInitialValue = async () => {
27
- const persist = await storage.get(safeStorageKey);
28
- const persistedState = persist[safeStorageKey];
29
- const initialOrPersistedValue = (0, get_persisted_value_1.default)(key, initialValue, persistedState);
30
- setState(initialOrPersistedValue);
55
+ // Two separate questions, and one cell cannot hold both: whether this load
56
+ // is still the current one, which only the closure it belongs to can answer,
57
+ // and whether a value has been applied meanwhile, which outlives it. A load
58
+ // abandoned by a key change would otherwise read the flag its successor had
59
+ // just set, apply the previous key's value and shut the successor out. The
60
+ // second question is also what makes subscribing first safe: a value the
61
+ // listener delivers while the load is in flight is not overwritten by it.
62
+ let isCancelled = false;
63
+ hasAppliedValue.current = false;
64
+ const loadPersistedValue = async () => {
65
+ try {
66
+ const persist = await storage.get(safeStorageKey);
67
+ if (isCancelled || hasAppliedValue.current)
68
+ return;
69
+ applyValue((0, get_persisted_value_1.default)(key, mountInitialValue.current, persist[safeStorageKey]));
70
+ }
71
+ catch (err) {
72
+ // An extension backend rejects on a quota error or an invalidated
73
+ // extension context. Nothing awaits this load, so an unclaimed
74
+ // rejection terminates the process on Node 15+ and surfaces as an
75
+ // uncaught error in the browser. The initial value is already in
76
+ // state, so the mount stands on it and the failure is reported rather
77
+ // than swallowed, as the synchronous path reports its own.
78
+ console.error("use-persisted-state: Can't read value from storage", err);
79
+ }
80
+ };
81
+ loadPersistedValue();
82
+ return () => {
83
+ isCancelled = true;
31
84
  };
32
- setInitialValue();
33
- }, [initialValue, key]);
34
- (0, use_storage_handler_1.default)(key, safeStorageKey, setState, storage, initialValue);
85
+ }, [key, applyValue]);
35
86
  return [state, setPersistedState];
36
87
  };
37
88
  return [usePersistedState, clear];
38
89
  }
39
- exports.default = createAsyncPersistedState;
40
90
  //# sourceMappingURL=create-async-persisted-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-async-persisted-state.js","sourceRoot":"","sources":["../src/create-async-persisted-state.ts"],"names":[],"mappings":";;;;;AAAA,iCAAgD;AAEhD,sFAA2D;AAC3D,0EAA+C;AAC/C,wEAA6C;AAC7C,sFAA2D;AAK3D,SAAwB,yBAAyB,CAC/C,UAAkB,EAClB,OAAU;IAEV,MAAM,cAAc,GAAG,wBAAwB,UAAU,EAAE,CAAA;IAE3D,MAAM,KAAK,GAAG,GAAkB,EAAE;QAChC,OAAO,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;IACvC,CAAC,CAAA;IAED,MAAM,iBAAiB,GAAG,CAAI,GAAW,EAAE,YAA2B,EAAwB,EAAE;QAC9F,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAI,YAAY,CAAC,CAAA;QAEnD,MAAM,iBAAiB,GAAG,KAAK,EAAE,QAAiC,EAAiB,EAAE;YACnF,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAI,QAAQ,EAAE,KAAK,CAAC,CAAA;YAEhD,QAAQ,CAAC,QAAQ,CAAC,CAAA;YAElB,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;YACvD,MAAM,OAAO,GAAG,IAAA,sBAAU,EAAI,GAAG,EAAE,aAAa,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAA;YAE3E,MAAM,OAAO,CAAC,GAAG,CAAC,EAAC,CAAC,cAAc,CAAC,EAAE,OAAO,EAAC,CAAC,CAAA;QAChD,CAAC,CAAA;QAED,IAAA,iBAAS,EAAC,GAAG,EAAE;YACb,MAAM,eAAe,GAAG,KAAK,IAAI,EAAE;gBACjC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;gBACjD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;gBAC9C,MAAM,uBAAuB,GAAG,IAAA,6BAAiB,EAAI,GAAG,EAAE,YAAY,EAAE,cAAc,CAAC,CAAA;gBAEvF,QAAQ,CAAC,uBAAuB,CAAC,CAAA;YACnC,CAAC,CAAA;YAED,eAAe,EAAE,CAAA;QACnB,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,CAAA;QAEvB,IAAA,6BAAiB,EAAI,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAA;QAE1E,OAAO,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAA;IACnC,CAAC,CAAA;IAED,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;AACnC,CAAC;AA1CD,4CA0CC"}
1
+ {"version":3,"file":"create-async-persisted-state.js","sourceRoot":"","sources":["../src/create-async-persisted-state.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Storage } from './@types/storage';
2
- import { PersistedState } from './@types/hook';
1
+ import type { Storage } from './@types/storage';
2
+ import type { PersistedState } from './@types/hook';
3
3
  export default function createPersistedState(storageKey: string, storage: Storage): [PersistedState, () => void];
4
4
  //# sourceMappingURL=create-persisted-state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-persisted-state.d.ts","sourceRoot":"","sources":["../src/create-persisted-state.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAC,cAAc,EAAoB,MAAM,eAAe,CAAA;AAO/D,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,GACf,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC,CAoC9B"}
1
+ {"version":3,"file":"create-persisted-state.d.ts","sourceRoot":"","sources":["../src/create-persisted-state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,eAAe,CAAA;AAOtE,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC,CAgE/G"}
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = createPersistedState;
6
7
  const react_1 = require("react");
7
8
  const use_storage_handler_1 = __importDefault(require("./utils/use-storage-handler"));
8
9
  const get_new_value_1 = __importDefault(require("./utils/get-new-value"));
@@ -13,26 +14,43 @@ function createPersistedState(storageKey, storage) {
13
14
  const clear = () => {
14
15
  storage.remove(safeStorageKey);
15
16
  };
17
+ const readPersisted = (key, initialValue) => (0, get_persisted_value_1.default)(key, initialValue, storage.get(safeStorageKey)[safeStorageKey]);
16
18
  const usePersistedState = (key, initialValue) => {
17
- const persist = storage.get(safeStorageKey);
18
- const persistedState = persist[safeStorageKey];
19
- const initialOrPersistedValue = (0, get_persisted_value_1.default)(key, initialValue, persistedState);
20
- const [state, setState] = (0, react_1.useState)(initialOrPersistedValue);
21
- const setPersistedState = (newState) => {
22
- const newValue = (0, get_new_value_1.default)(newState, state);
23
- setState(newValue);
19
+ // Reading through the initializer keeps the storage out of the render path:
20
+ // the hook runs on every render of every consuming component, so a re-read is
21
+ // only warranted when the key it is asked for actually changes.
22
+ const [state, setState] = (0, react_1.useState)(() => readPersisted(key, initialValue));
23
+ // The setter must resolve updater functions against the last value applied,
24
+ // not against the one captured by the render that created it. Reading the
25
+ // render closure collapses updates batched into a single event.
26
+ const latestValue = (0, react_1.useRef)(state);
27
+ const applyValue = (0, react_1.useCallback)((value) => {
28
+ latestValue.current = value;
29
+ setState(value);
30
+ }, []);
31
+ // A mounted hook handed a different key has to show that key's value before
32
+ // anything can be written, or the next update stores the previous key's value
33
+ // under the new key and destroys what was there. Adjusting during render ties
34
+ // the re-read to the change itself, where an effect would cost a read and a
35
+ // second render on every mount as well.
36
+ const renderedKey = (0, react_1.useRef)(key);
37
+ if (renderedKey.current !== key) {
38
+ renderedKey.current = key;
39
+ applyValue(readPersisted(key, initialValue));
40
+ }
41
+ // The exact entry this hook last wrote and has not yet seen reported back.
42
+ const pendingOwnWrite = (0, react_1.useRef)(null);
43
+ const setPersistedState = (0, react_1.useCallback)((newState) => {
44
+ const newValue = (0, get_new_value_1.default)(newState, latestValue.current);
45
+ applyValue(newValue);
24
46
  const persistedItem = storage.get(safeStorageKey)[safeStorageKey];
25
47
  const newItem = (0, get_new_item_1.default)(key, persistedItem, newValue);
48
+ pendingOwnWrite.current = newItem;
26
49
  storage.set({ [safeStorageKey]: newItem });
27
- };
28
- (0, react_1.useEffect)(() => {
29
- const initialOrPersistedValue = (0, get_persisted_value_1.default)(key, initialValue, persistedState);
30
- setState(initialOrPersistedValue);
31
- }, [initialValue, key, persistedState]);
32
- (0, use_storage_handler_1.default)(key, safeStorageKey, setState, storage, initialValue);
50
+ }, [key, applyValue]);
51
+ (0, use_storage_handler_1.default)(key, safeStorageKey, applyValue, storage, initialValue, pendingOwnWrite);
33
52
  return [state, setPersistedState];
34
53
  };
35
54
  return [usePersistedState, clear];
36
55
  }
37
- exports.default = createPersistedState;
38
56
  //# sourceMappingURL=create-persisted-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-persisted-state.js","sourceRoot":"","sources":["../src/create-persisted-state.ts"],"names":[],"mappings":";;;;;AAAA,iCAAgD;AAKhD,sFAA2D;AAC3D,0EAA+C;AAC/C,wEAA6C;AAC7C,sFAA2D;AAE3D,SAAwB,oBAAoB,CAC1C,UAAkB,EAClB,OAAgB;IAEhB,MAAM,cAAc,GAAG,wBAAwB,UAAU,EAAE,CAAA;IAC3D,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;IAChC,CAAC,CAAA;IAED,MAAM,iBAAiB,GAAG,CAAI,GAAW,EAAE,YAAe,EAAwB,EAAE;QAClF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;QAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;QAC9C,MAAM,uBAAuB,GAAG,IAAA,6BAAiB,EAAI,GAAG,EAAE,YAAY,EAAE,cAAc,CAAC,CAAA;QAEvF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAI,uBAAuB,CAAC,CAAA;QAE9D,MAAM,iBAAiB,GAAG,CAAC,QAAiC,EAAQ,EAAE;YACpE,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAI,QAAQ,EAAE,KAAK,CAAC,CAAA;YAEhD,QAAQ,CAAC,QAAQ,CAAC,CAAA;YAElB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC,CAAA;YACjE,MAAM,OAAO,GAAG,IAAA,sBAAU,EAAI,GAAG,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAA;YAE3D,OAAO,CAAC,GAAG,CAAC,EAAC,CAAC,cAAc,CAAC,EAAE,OAAO,EAAC,CAAC,CAAA;QAC1C,CAAC,CAAA;QAED,IAAA,iBAAS,EAAC,GAAG,EAAE;YACb,MAAM,uBAAuB,GAAG,IAAA,6BAAiB,EAAI,GAAG,EAAE,YAAY,EAAE,cAAc,CAAC,CAAA;YAEvF,QAAQ,CAAC,uBAAuB,CAAC,CAAA;QACnC,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC,CAAA;QAEvC,IAAA,6BAAiB,EAAI,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAA;QAE1E,OAAO,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAA;IACnC,CAAC,CAAA;IAED,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;AACnC,CAAC;AAvCD,uCAuCC"}
1
+ {"version":3,"file":"create-persisted-state.js","sourceRoot":"","sources":["../src/create-persisted-state.ts"],"names":[],"mappings":";;;;;AAWA,uCAgEC;AA1ED,iCAAqD;AAKrD,sFAA2D;AAC3D,0EAA+C;AAC/C,wEAA6C;AAC7C,sFAA2D;AAE3D,SAAwB,oBAAoB,CAAC,UAAkB,EAAE,OAAgB;IAC/E,MAAM,cAAc,GAAG,wBAAwB,UAAU,EAAE,CAAA;IAC3D,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;IAChC,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,CAAI,GAAW,EAAE,YAAe,EAAK,EAAE,CAC3D,IAAA,6BAAiB,EAAI,GAAG,EAAE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC,CAAC,CAAA;IAEtF,MAAM,iBAAiB,GAAG,CAAI,GAAW,EAAE,YAAe,EAAwB,EAAE;QAClF,4EAA4E;QAC5E,8EAA8E;QAC9E,gEAAgE;QAChE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAI,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAA;QAE7E,4EAA4E;QAC5E,0EAA0E;QAC1E,gEAAgE;QAChE,MAAM,WAAW,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAA;QAEjC,MAAM,UAAU,GAAG,IAAA,mBAAW,EAAC,CAAC,KAAQ,EAAQ,EAAE;YAChD,WAAW,CAAC,OAAO,GAAG,KAAK,CAAA;YAE3B,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC,EAAE,EAAE,CAAC,CAAA;QAEN,4EAA4E;QAC5E,8EAA8E;QAC9E,8EAA8E;QAC9E,4EAA4E;QAC5E,wCAAwC;QACxC,MAAM,WAAW,GAAG,IAAA,cAAM,EAAC,GAAG,CAAC,CAAA;QAE/B,IAAI,WAAW,CAAC,OAAO,KAAK,GAAG,EAAE,CAAC;YAChC,WAAW,CAAC,OAAO,GAAG,GAAG,CAAA;YAEzB,UAAU,CAAC,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAA;QAC9C,CAAC;QAED,2EAA2E;QAC3E,MAAM,eAAe,GAAG,IAAA,cAAM,EAAgB,IAAI,CAAC,CAAA;QAEnD,MAAM,iBAAiB,GAAG,IAAA,mBAAW,EACnC,CAAC,QAAiC,EAAQ,EAAE;YAC1C,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAI,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;YAE9D,UAAU,CAAC,QAAQ,CAAC,CAAA;YAEpB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC,CAAA;YACjE,MAAM,OAAO,GAAG,IAAA,sBAAU,EAAI,GAAG,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAA;YAE3D,eAAe,CAAC,OAAO,GAAG,OAAO,CAAA;YAEjC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5C,CAAC,EACD,CAAC,GAAG,EAAE,UAAU,CAAC,CAClB,CAAA;QAED,IAAA,6BAAiB,EAAI,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,CAAC,CAAA;QAE7F,OAAO,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAA;IACnC,CAAC,CAAA;IAED,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;AACnC,CAAC"}
package/lib/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { default as createSyncPersistedState } from './create-persisted-state';
2
- import createAsyncPersistedState from './create-async-persisted-state';
3
- import { Storage, AsyncStorage } from './@types/storage';
4
- import { PersistedState } from './@types/hook';
1
+ import type { Storage, AsyncStorage } from './@types/storage';
2
+ import type { PersistedState } from './@types/hook';
5
3
  export default function createPersistedState<S extends Storage | AsyncStorage>(name: string, storage: S): [PersistedState, () => void | Promise<void>];
6
- export { createSyncPersistedState as createPersistedState, createAsyncPersistedState, };
4
+ export { default as createPersistedState } from './create-persisted-state';
5
+ export { default as createAsyncPersistedState } from './create-async-persisted-state';
6
+ export type { AsyncStorage, Storage, StorageChange, StorageChangeEvent, StorageChangeListener } from './@types/storage';
7
+ export type { PersistedState, UsePersistedState } from './@types/hook';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAC9E,OAAO,yBAAyB,MAAM,gCAAgC,CAAA;AAItE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,CAAC,SAAS,OAAO,GAAG,YAAY,EAC3E,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GACT,CAAC,cAAc,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAM9C;AAED,OAAO,EACL,wBAAwB,IAAI,oBAAoB,EAChD,yBAAyB,GAC1B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,CAAC,SAAS,OAAO,GAAG,YAAY,EAC3E,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GACT,CAAC,cAAc,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAM9C;AAED,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC1E,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAMrF,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACvH,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA"}