react-wire-persisted 2.1.0 → 3.1.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/README.md CHANGED
@@ -5,23 +5,13 @@
5
5
  ## Install
6
6
 
7
7
  ```shell
8
- $ pnpm add -D @forminator/react-wire react-wire-persisted
9
- ```
10
-
11
- ## Building
12
-
13
- ```shell
14
- $ pnpm build
15
- $ npm version patch # patch, minor, major
16
- $ git push
17
- $ git push --tags
18
- $ npm publish
8
+ $ pnpm add @forminator/react-wire react-wire-persisted
19
9
  ```
20
10
 
21
11
  ## Usage
22
12
 
23
- ```javascript
24
- // constants.js
13
+ ```typescript
14
+ // constants.ts
25
15
  import { key, getPrefixedKeys } from 'react-wire-persisted/lib/utils'
26
16
 
27
17
  export const NS = 'myapp'
@@ -33,36 +23,48 @@ const prefixedKeys = getPrefixedKeys(NS)
33
23
  export { prefixedKeys as keys }
34
24
  ```
35
25
 
36
- ```javascript
37
- // index.js
26
+ ```tsx
27
+ // index.tsx
38
28
  import { NS } from './constants'
39
- import * as reactWirePersisted from 'react-wire-persisted'
29
+ import * as rwp from 'react-wire-persisted'
30
+
31
+ rwp.setNamespace(NS)
40
32
 
41
- reactWirePersisted.setNamespace(NS)
33
+ rwp.setOptions({
34
+ logging: {
35
+ enabled: true,
36
+ },
37
+ storageProvider: rwp.MemoryStorageProvider,
38
+ })
42
39
 
43
40
  // Normal React init code
44
41
  ```
45
42
 
46
- ```javascript
47
- // store.js
43
+ ```typescript
44
+ // store.ts
48
45
  import { createPersistedWire } from 'react-wire-persisted'
49
46
  import { keys } from './constants'
50
47
 
51
- export const foo = createPersistedWire(keys.foo, null)
48
+ type User = {
49
+ id: number
50
+ email: string
51
+ }
52
+
53
+ export const user = createPersistedWire<User | null>(keys.foo, null)
52
54
  ```
53
55
 
54
- ```javascript
55
- // SomeComponent.js
56
+ ```tsx
57
+ // SomeComponent.tsx
56
58
  import { useWireState } from '@forminator/react-wire'
57
59
  import * as store from './store'
58
60
 
59
61
  const SomeComponent = () => {
60
- const [foo, setFoo] = useWireState(store.foo)
62
+ const [user, setUser] = useWireState(store.user)
61
63
  return (
62
64
  <div>
63
- <p>Foo: {foo}</p>
64
- <button onClick={() => setFoo('bar')}>
65
- CHANGE FOO
65
+ <p>User: {user?.email}</p>
66
+ <button onClick={() => setUser({ ...user, email: 'example@gmail.com'})}>
67
+ Change Email
66
68
  </button>
67
69
  </div>
68
70
  )
@@ -75,9 +77,100 @@ See the [demo](demo) folder for a more complete example.
75
77
 
76
78
  ## Storage Providers
77
79
 
78
- This library uses `localStorage`, and will only work in browser environments.
80
+ By default, this library uses `localStorage` via `LocalStorageProvider` in browser environments. However, you can customize the storage behavior using `setOptions`:
81
+
82
+ ```typescript
83
+ import { setOptions, createPersistedWire } from 'react-wire-persisted'
84
+ import { MemoryStorageProvider } from 'react-wire-persisted'
85
+
86
+ // Use in-memory storage (useful for testing or SSR)
87
+ setOptions({
88
+ storageProvider: MemoryStorageProvider,
89
+ })
90
+ ```
91
+
92
+ This is useful for:
93
+ - **Testing**: Avoid localStorage in unit tests
94
+ - **SSR**: Use in-memory storage during server-side rendering
95
+ - **Custom storage**: Implement your own provider by extending `RWPStorageProvider`
96
+
97
+ See [LocalStorageProvider](src/providers/LocalStorageProvider.ts) and [MemoryStorageProvider](src/providers/MemoryStorageProvider.ts) for implementation examples.
98
+
99
+ ## API
100
+
101
+ ### `createPersistedWire<T = null>(key: string, value?: T): PersistedWire<T>`
102
+
103
+ Creates a persisted Wire that automatically syncs with the configured storage provider.
104
+
105
+ ### `setNamespace(namespace: string): void`
106
+
107
+ Sets the namespace for storage keys. This prefixes all keys to avoid collisions.
108
+
109
+ ### `setOptions(options: Partial<RWPOptions>): void`
110
+
111
+ Configure library options:
112
+ - `logging.enabled`: Enable/disable logging (default: `false`)
113
+ - `storageProvider`: Custom storage provider class
114
+
115
+ ### `getOptions(): RWPOptions`
116
+
117
+ Get current options.
79
118
 
80
- See [LocalStorageProvider](src/LocalStorageProvider.js) for implementation.
119
+ ### `getNamespace(): string | null`
120
+
121
+ Get current namespace.
122
+
123
+ ### `getStorage(): RWPStorageProvider`
124
+
125
+ Get the current storage provider instance.
126
+
127
+ ### `upgradeStorage(): boolean`
128
+
129
+ Attempts to upgrade from fake storage (used during SSR) to real localStorage. Returns `true` if upgrade was successful. Call this after hydration in client-side code.
130
+
131
+ ## Building
132
+
133
+ ```shell
134
+ $ pnpm build
135
+ ```
136
+
137
+ ## Contributing
138
+
139
+ Contributions are welcome. Please feel free to submit a [GitHub Issue](https://github.com/forminator/react-wire-persisted/issues) or pull request.
140
+
141
+ ### Development
142
+
143
+ ```shell
144
+ # Install dependencies
145
+ pnpm install
146
+
147
+ # Run tests
148
+ pnpm test
149
+
150
+ # Run linter
151
+ pnpm lint
152
+ ```
153
+
154
+ ## Publishing
155
+
156
+ To publish a new version to npm:
157
+
158
+ ```shell
159
+ # Build the project
160
+ pnpm build
161
+
162
+ # Bump version (choose one)
163
+ npm version patch # bug fixes (e.g., 2.1.0 -> 2.1.1)
164
+ npm version minor # new features (e.g., 2.1.0 -> 2.2.0)
165
+ npm version major # breaking changes (e.g., 2.1.0 -> 3.0.0)
166
+
167
+ # Push changes and tags
168
+ git push
169
+ git push --tags
170
+
171
+ # Publish to npm
172
+ npm publish
173
+ ```
81
174
 
82
175
  ## Miscellaneous
83
176
 
@@ -0,0 +1,276 @@
1
+ import { ReactNode } from 'react';
2
+ import { Wire } from '@forminator/react-wire';
3
+
4
+ /**
5
+ * Adds a key to the keys map
6
+ *
7
+ * @param {String} value Key name
8
+ */
9
+ declare const addKey: (value: string) => void;
10
+
11
+ declare type AnyStorage = InternalStorage | Storage;
12
+
13
+ /**
14
+ * Creates a persisted Wire using the `RWPStorageProvider` that is currently set
15
+ * Defaults to `localStorage` via `LocalStorageProvider`
16
+ *
17
+ * @param {String} key Unique key for storing this value
18
+ * @param {*} value Initial value of this Wire
19
+ * @returns A new Wire decorated with localStorage functionality
20
+ */
21
+ export declare const createPersistedWire: <T = null>(key: string, value?: T) => PersistedWire<T>;
22
+
23
+ export declare const defaultOptions: RWPOptions;
24
+
25
+ declare const fakeLocalStorage: InternalStorage;
26
+
27
+ /**
28
+ * Check if the client has finished hydrating
29
+ */
30
+ declare const getHasHydrated: () => boolean;
31
+
32
+ /**
33
+ * Check if storage has been hydrated (safe to read from real localStorage)
34
+ */
35
+ declare const getHasHydratedStorage: () => boolean;
36
+
37
+ /**
38
+ * Check if we're running in a browser environment
39
+ */
40
+ declare const getIsClient: () => boolean;
41
+
42
+ /**
43
+ * Convenience method to get internally managed storage keys
44
+ *
45
+ * @returns {Object} InternalStorage keys map
46
+ */
47
+ declare const getKeys: () => Record<string, string>;
48
+
49
+ export declare const getNamespace: () => string | null;
50
+
51
+ export declare const getOptions: () => RWPOptions;
52
+
53
+ /**
54
+ * Helper utility to prefix all keys in a map to use a namespace
55
+ *
56
+ * @param {String} namespace InternalStorage namespace prefix
57
+ * @param {Object} keys (Optional) InternalStorage key/values. Defaults to the internally managed keys map
58
+ */
59
+ declare const getPrefixedKeys: (namespace: string, keys?: Record<string, string> | null) => Record<string, string>;
60
+
61
+ export declare const getStorage: () => RWPStorageProvider;
62
+
63
+ export declare const HydrationProvider: ({ children, onUpgrade, autoUpgrade }: HydrationProviderProps) => ReactNode;
64
+
65
+ /**
66
+ * A Next.js App Router compatible component that handles automatic storage upgrade
67
+ * after hydration. Use this in your root layout.
68
+ *
69
+ * @param {Object} props Component props
70
+ * @param {React.ReactNode} props.children Child components to render
71
+ * @param {Function} props.onUpgrade Callback called when storage is upgraded
72
+ * @param {Boolean} props.autoUpgrade Whether to automatically upgrade storage (default: true)
73
+ */
74
+ declare type HydrationProviderProps = {
75
+ children: ReactNode;
76
+ onUpgrade?: () => void;
77
+ autoUpgrade?: boolean;
78
+ };
79
+
80
+ declare interface InternalStorage {
81
+ getItem: (key: string) => string | null;
82
+ setItem: (key: string, value: string) => void;
83
+ removeItem: (key: string) => void;
84
+ }
85
+
86
+ /**
87
+ * Check if localStorage is available and safe to use
88
+ */
89
+ declare const isLocalStorageAvailable: () => boolean;
90
+
91
+ /**
92
+ * Checks if a value is a primitive type
93
+ *
94
+ * @param {*} val Value to check
95
+ * @returns {Boolean} True if value is a primitive type
96
+ */
97
+ declare const isPrimitive: (val: unknown) => boolean;
98
+
99
+ /**
100
+ * Adds a key to the keys map
101
+ * (Alias for `addKey`)
102
+ *
103
+ * @param {String} value Key name
104
+ */
105
+ declare const key: (value: string) => void;
106
+
107
+ /**
108
+ * A storage provider for `localStorage`
109
+ * @see `RWPStorageProvider.ts` for documentation
110
+ */
111
+ export declare class LocalStorageProvider extends RWPStorageProvider {
112
+ storage: AnyStorage;
113
+ private _isUsingFakeStorage;
114
+ constructor(namespace: string, registry?: Record<string, unknown>);
115
+ getStorage(): AnyStorage;
116
+ setNamespace(namespace: string): void;
117
+ getItem(key: string): any;
118
+ setItem(key: string, value: unknown): void;
119
+ removeItem(key: string, fromRegistry?: boolean): void;
120
+ getAll(): Record<string, unknown>;
121
+ _resetAll(useInitialValues?: boolean, excludedKeys?: string[], clearRegistry?: boolean): void;
122
+ resetAll(excludedKeys?: string[], clearRegistry?: boolean): void;
123
+ removeAll(excludedKeys?: string[], clearRegistry?: boolean): void;
124
+ /**
125
+ * Attempt to upgrade from fake storage to real localStorage
126
+ * This is useful for hydration scenarios
127
+ */
128
+ upgradeToRealStorage(): boolean;
129
+ /**
130
+ * Check if currently using fake storage
131
+ */
132
+ isUsingFakeStorage(): boolean;
133
+ }
134
+
135
+ /**
136
+ * Mark storage as hydrated (called after upgradeStorage)
137
+ */
138
+ declare const markStorageAsHydrated: () => void;
139
+
140
+ export declare class MemoryStorageProvider extends LocalStorageProvider {
141
+ constructor(namespace: string, registry?: Record<string, unknown>);
142
+ getStorage(): InternalStorage;
143
+ }
144
+
145
+ declare type PersistedWire<T> = Wire<T>;
146
+
147
+ declare type RWPOptions = {
148
+ logging: {
149
+ enabled: boolean;
150
+ };
151
+ storageProvider?: typeof RWPStorageProvider;
152
+ };
153
+
154
+ /**
155
+ * Base class to allow storage access
156
+ * @see `LocalStorageProvider.ts` for an example implementation
157
+ */
158
+ /** biome-ignore-all lint/correctness/noUnusedFunctionParameters: WIP next PR will switch to TypeScript */
159
+ export declare abstract class RWPStorageProvider {
160
+ namespace: string | null;
161
+ registry: Record<string, unknown>;
162
+ /**
163
+ * Initializes the class
164
+ * @param {String} namespace Namespace to prefix all keys with. Mostly used for the logging and reset functions
165
+ * @param {Object} registry (Optional) Initialize the storage provider with an existing registry
166
+ */
167
+ protected constructor(namespace: string, registry: Record<string, unknown>);
168
+ /**
169
+ * Sets the namespace for this storage provider, and migrates
170
+ * all stored values to the new namespace
171
+ * @param {String} namespace New namespace for this storage provider
172
+ */
173
+ abstract setNamespace(namespace: string | null): void;
174
+ /**
175
+ * Registers an item with its initial value. This is used for logging, resetting, etc.
176
+ * @param {String} key InternalStorage item's key
177
+ * @param {*} initialValue InternalStorage item's initial value
178
+ */
179
+ register<T>(key: string, initialValue: T | null): void;
180
+ /**
181
+ * Reads an item from storage
182
+ * @param {String} key Key for the item to retrieve
183
+ */
184
+ abstract getItem<T>(key: string): T | null;
185
+ /**
186
+ * Stores a value
187
+ * @param {String} key Item's storage key
188
+ * @param {String} value Item's value to store
189
+ */
190
+ abstract setItem<T>(key: string, value: T | null): void;
191
+ /**
192
+ * Removes an item from storage
193
+ * @param {String} key Item's storage key
194
+ * @param {Boolean} fromRegistry (Optional) If the item should also be removed from the registry
195
+ */
196
+ abstract removeItem(key: string, fromRegistry: boolean): void;
197
+ /**
198
+ * Gets all stored keys and values
199
+ * If a `namespace` was set, only keys prefixed with the namespace will be returned
200
+ */
201
+ abstract getAll(): Record<string, unknown>;
202
+ /**
203
+ *
204
+ * @param {Boolean} useInitialValues If values should be replaced with their initial values. If false, keys are removed
205
+ * @param {String[]} excludedKeys (Optional) List of keys to exclude
206
+ * @param {Boolean} clearRegistry (Optional) If the registry should also be cleared
207
+ */
208
+ abstract _resetAll(useInitialValues: boolean, excludedKeys: string[], clearRegistry: boolean): void;
209
+ /**
210
+ * Resets all values to their initial values
211
+ * If a `namespace` is set, only keys prefixed with the namespace will be reset
212
+ * @param {String[]} excludedKeys (Optional) List of keys to exclude
213
+ */
214
+ abstract resetAll(excludedKeys: string[]): void;
215
+ /**
216
+ * Removes all items from local storage.
217
+ * If a `namespace` is set, only keys prefixed with the namespace will be removed
218
+ * @param {String[]} excludedKeys (Optional) List of keys to exclude
219
+ */
220
+ abstract removeAll(excludedKeys: string[]): void;
221
+ upgradeToRealStorage(): boolean;
222
+ isUsingFakeStorage(): boolean;
223
+ }
224
+
225
+ /**
226
+ * Sets the namespace for the storage provider
227
+ *
228
+ * @param {String} namespace The namespace for the storage provider
229
+ */
230
+ export declare const setNamespace: (namespace: string) => void;
231
+
232
+ export declare const setOptions: (value: Partial<RWPOptions>) => void;
233
+
234
+ /**
235
+ * Attempts to upgrade the storage provider from fake storage to real localStorage
236
+ * This should be called on the client side after hydration
237
+ *
238
+ * @returns true if upgrade was successful
239
+ */
240
+ export declare const upgradeStorage: () => boolean;
241
+
242
+ /**
243
+ * React hook that handles automatic storage upgrade after hydration
244
+ * This should be used in the root component of your application
245
+ *
246
+ * @param {Object} options Configuration options
247
+ * @param {Boolean} options.autoUpgrade Whether to automatically upgrade storage (default: true)
248
+ * @param {Function} options.onUpgrade Callback called when storage is upgraded
249
+ */
250
+ export declare const useHydration: (options?: UseHydrationOptions) => {
251
+ hasUpgraded: boolean;
252
+ };
253
+
254
+ export declare type UseHydrationOptions = {
255
+ autoUpgrade?: boolean;
256
+ onUpgrade?: () => void;
257
+ };
258
+
259
+ declare namespace utils {
260
+ export {
261
+ isPrimitive,
262
+ fakeLocalStorage,
263
+ getIsClient,
264
+ getHasHydrated,
265
+ getHasHydratedStorage,
266
+ markStorageAsHydrated,
267
+ isLocalStorageAvailable,
268
+ addKey,
269
+ key,
270
+ getKeys,
271
+ getPrefixedKeys
272
+ }
273
+ }
274
+ export { utils }
275
+
276
+ export { }