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 +121 -28
- package/dist/index.d.ts +276 -0
- package/dist/index.js +238 -339
- package/dist/react-wire-persisted.js +238 -339
- package/dist/react-wire-persisted.js.map +1 -0
- package/dist/react-wire-persisted.umd.cjs +2 -1
- package/dist/react-wire-persisted.umd.cjs.map +1 -0
- package/package.json +32 -23
- package/src/components/{HydrationProvider.jsx → HydrationProvider.tsx} +12 -2
- package/src/components/index.tsx +1 -0
- package/src/global.d.ts +16 -0
- package/src/hooks/{useHydration.js → useHydration.ts} +8 -3
- package/src/{index.js → index.ts} +4 -1
- package/src/providers/{LocalStorageProvider.js → LocalStorageProvider.ts} +35 -34
- package/src/providers/MemoryStorageProvider.ts +14 -0
- package/src/providers/{StorageProvider.js → RWPStorageProvider.ts} +30 -19
- package/src/{react-wire-persisted.js → react-wire-persisted.ts} +43 -47
- package/src/types.ts +19 -0
- package/src/utils/fakeLocalStorage.ts +17 -0
- package/src/utils/{index.js → index.ts} +4 -4
- package/src/utils/{isomorphic.js → isomorphic.ts} +8 -8
- package/src/utils/keys.ts +49 -0
- package/src/components/HydrationProvider.js +0 -45
- package/src/components/index.js +0 -1
- package/src/providers/MemoryStorageProvider.js +0 -14
- package/src/utils/fakeLocalStorage.js +0 -15
- package/src/utils/keys.js +0 -46
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base class to allow storage access
|
|
3
|
-
* @see `LocalStorageProvider.
|
|
3
|
+
* @see `LocalStorageProvider.ts` for an example implementation
|
|
4
4
|
*/
|
|
5
5
|
/** biome-ignore-all lint/correctness/noUnusedFunctionParameters: WIP next PR will switch to TypeScript */
|
|
6
|
-
class
|
|
6
|
+
export abstract class RWPStorageProvider {
|
|
7
|
+
namespace: string | null
|
|
8
|
+
registry: Record<string, unknown>
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* Initializes the class
|
|
9
|
-
* @param {String} namespace Namespace to prefix all keys with. Mostly used for the logging
|
|
12
|
+
* @param {String} namespace Namespace to prefix all keys with. Mostly used for the logging and reset functions
|
|
10
13
|
* @param {Object} registry (Optional) Initialize the storage provider with an existing registry
|
|
11
14
|
*/
|
|
12
|
-
constructor(namespace, registry) {
|
|
15
|
+
protected constructor(namespace: string, registry: Record<string, unknown>) {
|
|
13
16
|
// Simulate being an abstract class
|
|
14
|
-
if (new.target ===
|
|
17
|
+
if (new.target === RWPStorageProvider)
|
|
15
18
|
throw TypeError(`StorageProvider is abstract. Extend this class to implement it`)
|
|
16
19
|
|
|
17
20
|
this.namespace = namespace || null
|
|
@@ -24,14 +27,14 @@ class StorageProvider {
|
|
|
24
27
|
* @param {String} namespace New namespace for this storage provider
|
|
25
28
|
*/
|
|
26
29
|
/* istanbul ignore next */
|
|
27
|
-
setNamespace(namespace)
|
|
30
|
+
abstract setNamespace(namespace: string | null): void
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
|
-
* Registers an item with
|
|
31
|
-
* @param {String} key
|
|
32
|
-
* @param {*} initialValue
|
|
33
|
+
* Registers an item with its initial value. This is used for logging, resetting, etc.
|
|
34
|
+
* @param {String} key InternalStorage item's key
|
|
35
|
+
* @param {*} initialValue InternalStorage item's initial value
|
|
33
36
|
*/
|
|
34
|
-
register(key, initialValue) {
|
|
37
|
+
register<T>(key: string, initialValue: T | null) {
|
|
35
38
|
this.registry[key] = initialValue
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -40,7 +43,7 @@ class StorageProvider {
|
|
|
40
43
|
* @param {String} key Key for the item to retrieve
|
|
41
44
|
*/
|
|
42
45
|
/* istanbul ignore next */
|
|
43
|
-
getItem(key)
|
|
46
|
+
abstract getItem<T>(key: string): T | null
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Stores a value
|
|
@@ -48,7 +51,7 @@ class StorageProvider {
|
|
|
48
51
|
* @param {String} value Item's value to store
|
|
49
52
|
*/
|
|
50
53
|
/* istanbul ignore next */
|
|
51
|
-
setItem(key, value)
|
|
54
|
+
abstract setItem<T>(key: string, value: T | null): void
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Removes an item from storage
|
|
@@ -56,14 +59,14 @@ class StorageProvider {
|
|
|
56
59
|
* @param {Boolean} fromRegistry (Optional) If the item should also be removed from the registry
|
|
57
60
|
*/
|
|
58
61
|
/* istanbul ignore next */
|
|
59
|
-
removeItem(key, fromRegistry
|
|
62
|
+
abstract removeItem(key: string, fromRegistry: boolean): void
|
|
60
63
|
|
|
61
64
|
/**
|
|
62
|
-
* Gets all stored keys
|
|
65
|
+
* Gets all stored keys and values
|
|
63
66
|
* If a `namespace` was set, only keys prefixed with the namespace will be returned
|
|
64
67
|
*/
|
|
65
68
|
/* istanbul ignore next */
|
|
66
|
-
getAll()
|
|
69
|
+
abstract getAll(): Record<string, unknown>
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
72
|
*
|
|
@@ -72,7 +75,7 @@ class StorageProvider {
|
|
|
72
75
|
* @param {Boolean} clearRegistry (Optional) If the registry should also be cleared
|
|
73
76
|
*/
|
|
74
77
|
/* istanbul ignore next */
|
|
75
|
-
_resetAll(useInitialValues
|
|
78
|
+
abstract _resetAll(useInitialValues: boolean, excludedKeys: string[], clearRegistry: boolean): void
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
81
|
* Resets all values to their initial values
|
|
@@ -80,7 +83,7 @@ class StorageProvider {
|
|
|
80
83
|
* @param {String[]} excludedKeys (Optional) List of keys to exclude
|
|
81
84
|
*/
|
|
82
85
|
/* istanbul ignore next */
|
|
83
|
-
resetAll(excludedKeys
|
|
86
|
+
abstract resetAll(excludedKeys: string[]): void
|
|
84
87
|
|
|
85
88
|
/**
|
|
86
89
|
* Removes all items from local storage.
|
|
@@ -88,7 +91,15 @@ class StorageProvider {
|
|
|
88
91
|
* @param {String[]} excludedKeys (Optional) List of keys to exclude
|
|
89
92
|
*/
|
|
90
93
|
/* istanbul ignore next */
|
|
91
|
-
removeAll(excludedKeys
|
|
94
|
+
abstract removeAll(excludedKeys: string[]): void
|
|
95
|
+
|
|
96
|
+
upgradeToRealStorage(): boolean {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
isUsingFakeStorage(): boolean {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
92
103
|
}
|
|
93
104
|
|
|
94
|
-
export default
|
|
105
|
+
export default RWPStorageProvider
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import { createWire } from '@forminator/react-wire'
|
|
2
|
-
import LocalStorageProvider from '
|
|
3
|
-
import
|
|
1
|
+
import { createWire, type Defined, type Wire } from '@forminator/react-wire'
|
|
2
|
+
import LocalStorageProvider from '@/providers/LocalStorageProvider'
|
|
3
|
+
import type RWPStorageProvider from '@/providers/RWPStorageProvider'
|
|
4
|
+
import type { PersistedWire, RWPOptions, WireLikeObject } from '@/types'
|
|
5
|
+
import { getHasHydratedStorage, getIsClient, markStorageAsHydrated } from '@/utils'
|
|
4
6
|
|
|
5
7
|
// Generate unique instance ID
|
|
6
8
|
const instanceId = Math.random().toString(36).substring(7)
|
|
7
|
-
const rwpLog = (...args) => {
|
|
9
|
+
const rwpLog = (...args: unknown[]) => {
|
|
8
10
|
if (typeof globalThis !== 'undefined' && globalThis.__RWP_LOGGING_ENABLED__ !== false) {
|
|
9
11
|
console.log(...args)
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
export const defaultOptions = {
|
|
15
|
+
export const defaultOptions: RWPOptions = {
|
|
14
16
|
logging: {
|
|
15
17
|
enabled: false,
|
|
16
18
|
},
|
|
19
|
+
storageProvider: LocalStorageProvider,
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
let options: RWPOptions = { ...defaultOptions }
|
|
23
|
+
let storage: RWPStorageProvider
|
|
24
|
+
const pendingLogs: unknown[][] = []
|
|
25
|
+
|
|
19
26
|
// Set global logging flag on startup
|
|
20
27
|
if (typeof globalThis !== 'undefined' && globalThis.__RWP_LOGGING_ENABLED__ === undefined) {
|
|
21
28
|
globalThis.__RWP_LOGGING_ENABLED__ = defaultOptions.logging.enabled
|
|
@@ -23,26 +30,21 @@ if (typeof globalThis !== 'undefined' && globalThis.__RWP_LOGGING_ENABLED__ ===
|
|
|
23
30
|
|
|
24
31
|
rwpLog('[RWP] Module initialized, instance ID:', instanceId)
|
|
25
32
|
|
|
26
|
-
const Provider = LocalStorageProvider
|
|
27
|
-
|
|
28
33
|
// Make storage global so all instances share the same storage after upgrade
|
|
29
34
|
rwpLog('[RWP] About to check global storage, instanceId:', instanceId)
|
|
30
|
-
let storage
|
|
31
35
|
try {
|
|
32
36
|
if (!globalThis.__RWP_STORAGE__) {
|
|
33
37
|
rwpLog('[RWP] Creating global storage in instance:', instanceId)
|
|
34
|
-
globalThis.__RWP_STORAGE__ = new
|
|
38
|
+
globalThis.__RWP_STORAGE__ = new LocalStorageProvider('__internal_rwp_storage__')
|
|
35
39
|
} else {
|
|
36
40
|
rwpLog('[RWP] Using existing global storage in instance:', instanceId)
|
|
37
41
|
}
|
|
38
42
|
storage = globalThis.__RWP_STORAGE__
|
|
39
|
-
rwpLog('[RWP]
|
|
43
|
+
rwpLog('[RWP] InternalStorage assigned successfully')
|
|
40
44
|
} catch (error) {
|
|
41
45
|
if (globalThis.__RWP_LOGGING_ENABLED__) console.error('[RWP] Error setting up global storage:', error)
|
|
42
|
-
storage = new
|
|
46
|
+
storage = new LocalStorageProvider('__internal_rwp_storage__')
|
|
43
47
|
}
|
|
44
|
-
let options = { ...defaultOptions }
|
|
45
|
-
const pendingLogs = []
|
|
46
48
|
|
|
47
49
|
// Use a global registry to handle multiple module instances
|
|
48
50
|
// This ensures all instances share the same wire registry
|
|
@@ -56,38 +58,32 @@ if (typeof globalThis !== 'undefined') {
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
// Registry to track wire instances for hydration refresh
|
|
59
|
-
const registeredWires = globalThis.__RWP_REGISTERED_WIRES__ || new Map()
|
|
61
|
+
const registeredWires = globalThis.__RWP_REGISTERED_WIRES__ || new Map<string, WireLikeObject>()
|
|
60
62
|
rwpLog('[RWP] registeredWires Map reference in instance:', instanceId, 'size:', registeredWires.size)
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
* Gets the namespace of the storage provider
|
|
64
|
-
*
|
|
65
|
-
* @returns {String}
|
|
66
|
-
*/
|
|
67
|
-
export const getNamespace = () => storage.namespace
|
|
64
|
+
export const getNamespace = (): string | null => storage.namespace
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
* Gets the current storage provider class instance
|
|
71
|
-
*
|
|
72
|
-
* @returns {StorageProvider}
|
|
73
|
-
*/
|
|
74
|
-
export const getStorage = () => storage
|
|
75
|
-
|
|
76
|
-
export const getOptions = () => options
|
|
66
|
+
export const getStorage = (): RWPStorageProvider => storage
|
|
77
67
|
|
|
78
68
|
/**
|
|
79
69
|
* Sets the namespace for the storage provider
|
|
80
70
|
*
|
|
81
71
|
* @param {String} namespace The namespace for the storage provider
|
|
82
72
|
*/
|
|
83
|
-
export const setNamespace = (namespace) => {
|
|
73
|
+
export const setNamespace = (namespace: string) => {
|
|
84
74
|
rwpLog('[RWP] setNamespace() called with:', namespace, 'registered wires before:', registeredWires.size)
|
|
75
|
+
const currentNamespace = namespace || getNamespace()
|
|
76
|
+
|
|
77
|
+
if (!currentNamespace) throw new Error('react-wire-persisted: Cannot set namespace to null or undefined')
|
|
78
|
+
|
|
85
79
|
storage.setNamespace(namespace)
|
|
86
|
-
storage = new
|
|
87
|
-
rwpLog(
|
|
80
|
+
storage = new LocalStorageProvider(currentNamespace)
|
|
81
|
+
rwpLog(`[RWP] setNamespace() done, registered wires after:`, registeredWires.size)
|
|
88
82
|
}
|
|
89
83
|
|
|
90
|
-
export const
|
|
84
|
+
export const getOptions = (): RWPOptions => options
|
|
85
|
+
|
|
86
|
+
export const setOptions = (value: Partial<RWPOptions>) => {
|
|
91
87
|
options = {
|
|
92
88
|
...options,
|
|
93
89
|
...value,
|
|
@@ -101,7 +97,7 @@ export const setOptions = (value) => {
|
|
|
101
97
|
console.info('Flushing', pendingLogs.length, 'pending logs')
|
|
102
98
|
while (pendingLogs.length)
|
|
103
99
|
/* istanbul ignore next */
|
|
104
|
-
console.log(...pendingLogs.shift())
|
|
100
|
+
console.log(...(pendingLogs.shift() || []))
|
|
105
101
|
}
|
|
106
102
|
}
|
|
107
103
|
|
|
@@ -113,7 +109,7 @@ const refreshAllWires = () => {
|
|
|
113
109
|
rwpLog('[RWP] refreshAllWires() called in instance:', instanceId, 'registered wires:', registeredWires.size)
|
|
114
110
|
log('react-wire-persisted: refreshAllWires() called, registered wires:', registeredWires.size)
|
|
115
111
|
|
|
116
|
-
registeredWires.forEach((wire, key) => {
|
|
112
|
+
registeredWires.forEach((wire: Wire<unknown> | WireLikeObject, key: string) => {
|
|
117
113
|
const storedValue = storage.getItem(key)
|
|
118
114
|
const currentValue = wire.getValue()
|
|
119
115
|
|
|
@@ -141,9 +137,9 @@ const refreshAllWires = () => {
|
|
|
141
137
|
* Attempts to upgrade the storage provider from fake storage to real localStorage
|
|
142
138
|
* This should be called on the client side after hydration
|
|
143
139
|
*
|
|
144
|
-
* @returns
|
|
140
|
+
* @returns true if upgrade was successful
|
|
145
141
|
*/
|
|
146
|
-
export const upgradeStorage = () => {
|
|
142
|
+
export const upgradeStorage = (): boolean => {
|
|
147
143
|
rwpLog('[RWP] upgradeStorage() called in instance:', instanceId, {
|
|
148
144
|
isClient: getIsClient(),
|
|
149
145
|
isUsingFakeStorage: storage.isUsingFakeStorage(),
|
|
@@ -173,7 +169,7 @@ export const upgradeStorage = () => {
|
|
|
173
169
|
return upgraded
|
|
174
170
|
}
|
|
175
171
|
|
|
176
|
-
const log = (...args) => {
|
|
172
|
+
const log = (...args: unknown[]) => {
|
|
177
173
|
/* istanbul ignore next */
|
|
178
174
|
if (options.logging.enabled)
|
|
179
175
|
/* istanbul ignore next */
|
|
@@ -182,28 +178,28 @@ const log = (...args) => {
|
|
|
182
178
|
}
|
|
183
179
|
|
|
184
180
|
/**
|
|
185
|
-
* Creates a persisted Wire using the `
|
|
181
|
+
* Creates a persisted Wire using the `RWPStorageProvider` that is currently set
|
|
186
182
|
* Defaults to `localStorage` via `LocalStorageProvider`
|
|
187
183
|
*
|
|
188
184
|
* @param {String} key Unique key for storing this value
|
|
189
185
|
* @param {*} value Initial value of this Wire
|
|
190
186
|
* @returns A new Wire decorated with localStorage functionality
|
|
191
187
|
*/
|
|
192
|
-
export const createPersistedWire = (key, value = null) => {
|
|
188
|
+
export const createPersistedWire = <T = null>(key: string, value: T = null as T): PersistedWire<T> => {
|
|
193
189
|
rwpLog('[RWP] createPersistedWire() called in instance:', instanceId, 'key:', key, 'value:', value)
|
|
194
190
|
|
|
195
191
|
// This check helps ensure no accidental key typos occur
|
|
196
|
-
if (!key
|
|
192
|
+
if (!key) throw new Error(`createPersistedWire: Key cannot be a falsey value (${key}}`)
|
|
197
193
|
|
|
198
194
|
// Track this writable entry so we can easily clear all
|
|
199
195
|
storage.register(key, value)
|
|
200
196
|
|
|
201
197
|
// The actual Wire backing object
|
|
202
|
-
const wire = createWire(value)
|
|
198
|
+
const wire = createWire<T>(value)
|
|
203
199
|
|
|
204
200
|
const getValue = () => wire.getValue()
|
|
205
201
|
|
|
206
|
-
const setValue = (newValue) => {
|
|
202
|
+
const setValue = (newValue: Defined<T>) => {
|
|
207
203
|
rwpLog(
|
|
208
204
|
'[RWP] setValue called in instance:',
|
|
209
205
|
instanceId,
|
|
@@ -216,19 +212,19 @@ export const createPersistedWire = (key, value = null) => {
|
|
|
216
212
|
return wire.setValue(newValue)
|
|
217
213
|
}
|
|
218
214
|
|
|
219
|
-
const subscribe = (
|
|
220
|
-
wire.subscribe(
|
|
215
|
+
const subscribe = (callback: (value: Defined<T>) => void) => {
|
|
216
|
+
return wire.subscribe(callback)
|
|
221
217
|
}
|
|
222
218
|
|
|
223
219
|
// Always start with default value to ensure SSR consistency
|
|
224
|
-
let initialValue = value
|
|
220
|
+
let initialValue: T = value
|
|
225
221
|
|
|
226
222
|
// Only read from storage if we've hydrated OR if storage is already using real localStorage
|
|
227
223
|
// (prevents hydration mismatch in SSR, but allows normal behavior in client-only apps)
|
|
228
224
|
const canReadStorage = getHasHydratedStorage() || !storage.isUsingFakeStorage()
|
|
229
225
|
|
|
230
226
|
if (canReadStorage && getIsClient()) {
|
|
231
|
-
const storedValue = storage.getItem(key)
|
|
227
|
+
const storedValue = storage.getItem<T>(key)
|
|
232
228
|
|
|
233
229
|
if (storedValue !== null) initialValue = storedValue
|
|
234
230
|
}
|
|
@@ -241,7 +237,7 @@ export const createPersistedWire = (key, value = null) => {
|
|
|
241
237
|
canReadStorage,
|
|
242
238
|
})
|
|
243
239
|
|
|
244
|
-
if (initialValue !== value) setValue(initialValue)
|
|
240
|
+
if (initialValue !== value && initialValue !== undefined) setValue(initialValue as Defined<T>)
|
|
245
241
|
|
|
246
242
|
// Register wire for post-hydration refresh
|
|
247
243
|
registeredWires.set(key, {
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Wire } from '@forminator/react-wire'
|
|
2
|
+
import type RWPStorageProvider from '@/providers/RWPStorageProvider'
|
|
3
|
+
|
|
4
|
+
export type RWPOptions = {
|
|
5
|
+
logging: { enabled: boolean }
|
|
6
|
+
storageProvider?: typeof RWPStorageProvider
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type PersistedWire<T> = Wire<T>
|
|
10
|
+
|
|
11
|
+
export interface InternalStorage {
|
|
12
|
+
getItem: (key: string) => string | null
|
|
13
|
+
setItem: (key: string, value: string) => void
|
|
14
|
+
removeItem: (key: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type AnyStorage = InternalStorage | Storage
|
|
18
|
+
|
|
19
|
+
export type WireLikeObject = Pick<Wire<unknown>, 'getValue' | 'setValue' | 'subscribe'>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { InternalStorage } from '@/types'
|
|
2
|
+
|
|
3
|
+
const storage: Record<string, string> = {
|
|
4
|
+
__IS_FAKE_LOCAL_STORAGE__: 'true',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const fakeLocalStorage: InternalStorage = {
|
|
8
|
+
getItem: (key: string): string | null => storage[key],
|
|
9
|
+
setItem: (key: string, value: string): void => {
|
|
10
|
+
storage[key] = value
|
|
11
|
+
},
|
|
12
|
+
removeItem: (key: string): void => {
|
|
13
|
+
delete storage[key]
|
|
14
|
+
},
|
|
15
|
+
// Make Object.keys() work properly for _resetAll method
|
|
16
|
+
...storage,
|
|
17
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from './fakeLocalStorage
|
|
2
|
-
export * from './isomorphic
|
|
3
|
-
export * from './keys
|
|
1
|
+
export * from './fakeLocalStorage'
|
|
2
|
+
export * from './isomorphic'
|
|
3
|
+
export * from './keys'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Checks if a value is a primitive type
|
|
@@ -8,7 +8,7 @@ export * from './keys.js'
|
|
|
8
8
|
* @param {*} val Value to check
|
|
9
9
|
* @returns {Boolean} True if value is a primitive type
|
|
10
10
|
*/
|
|
11
|
-
export const isPrimitive = (val) => {
|
|
11
|
+
export const isPrimitive = (val: unknown): boolean => {
|
|
12
12
|
const type = typeof val
|
|
13
13
|
|
|
14
14
|
if (val === null) return true
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Utilities for handling server-side rendering and client-side hydration
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
let isClient = false
|
|
6
|
-
let hasHydrated = false
|
|
7
|
-
let hasHydratedStorage = false
|
|
5
|
+
let isClient: boolean = false
|
|
6
|
+
let hasHydrated: boolean = false
|
|
7
|
+
let hasHydratedStorage: boolean = false
|
|
8
8
|
|
|
9
9
|
// Detect if we're running in a browser environment
|
|
10
10
|
if (typeof window !== 'undefined') {
|
|
@@ -21,29 +21,29 @@ if (typeof window !== 'undefined') {
|
|
|
21
21
|
/**
|
|
22
22
|
* Check if we're running in a browser environment
|
|
23
23
|
*/
|
|
24
|
-
export const getIsClient = () => isClient
|
|
24
|
+
export const getIsClient = (): boolean => isClient
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Check if the client has finished hydrating
|
|
28
28
|
*/
|
|
29
|
-
export const getHasHydrated = () => hasHydrated
|
|
29
|
+
export const getHasHydrated = (): boolean => hasHydrated
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Check if storage has been hydrated (safe to read from real localStorage)
|
|
33
33
|
*/
|
|
34
|
-
export const getHasHydratedStorage = () => hasHydratedStorage
|
|
34
|
+
export const getHasHydratedStorage = (): boolean => hasHydratedStorage
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Mark storage as hydrated (called after upgradeStorage)
|
|
38
38
|
*/
|
|
39
|
-
export const markStorageAsHydrated = () => {
|
|
39
|
+
export const markStorageAsHydrated = (): void => {
|
|
40
40
|
hasHydratedStorage = true
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Check if localStorage is available and safe to use
|
|
45
45
|
*/
|
|
46
|
-
export const isLocalStorageAvailable = () => {
|
|
46
|
+
export const isLocalStorageAvailable = (): boolean => {
|
|
47
47
|
if (!isClient) return false
|
|
48
48
|
|
|
49
49
|
try {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience map of keys
|
|
3
|
+
*/
|
|
4
|
+
const storageKeys: Record<string, string> = {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Adds a key to the keys map
|
|
8
|
+
*
|
|
9
|
+
* @param {String} value Key name
|
|
10
|
+
*/
|
|
11
|
+
export const addKey = (value: string): void => {
|
|
12
|
+
storageKeys[value] = value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adds a key to the keys map
|
|
17
|
+
* (Alias for `addKey`)
|
|
18
|
+
*
|
|
19
|
+
* @param {String} value Key name
|
|
20
|
+
*/
|
|
21
|
+
export const key = (value: string) => addKey(value)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convenience method to get internally managed storage keys
|
|
25
|
+
*
|
|
26
|
+
* @returns {Object} InternalStorage keys map
|
|
27
|
+
*/
|
|
28
|
+
export const getKeys = (): Record<string, string> => storageKeys
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Helper utility to prefix all keys in a map to use a namespace
|
|
32
|
+
*
|
|
33
|
+
* @param {String} namespace InternalStorage namespace prefix
|
|
34
|
+
* @param {Object} keys (Optional) InternalStorage key/values. Defaults to the internally managed keys map
|
|
35
|
+
*/
|
|
36
|
+
export const getPrefixedKeys = (namespace: string, keys: Record<string, string> | null = null) => {
|
|
37
|
+
const items = keys || storageKeys
|
|
38
|
+
|
|
39
|
+
if (!namespace) return items
|
|
40
|
+
|
|
41
|
+
return Object.keys(items).reduce(
|
|
42
|
+
(acc, it) => {
|
|
43
|
+
acc[it] = `${namespace}.${items[it]}`
|
|
44
|
+
|
|
45
|
+
return acc
|
|
46
|
+
},
|
|
47
|
+
{} as Record<string, string>,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef } from 'react'
|
|
4
|
-
import { upgradeStorage } from '../react-wire-persisted.js'
|
|
5
|
-
import { getHasHydrated, getIsClient } from '../utils/index.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A Next.js App Router compatible component that handles automatic storage upgrade
|
|
9
|
-
* after hydration. Use this in your root layout.
|
|
10
|
-
*
|
|
11
|
-
* @param {Object} props Component props
|
|
12
|
-
* @param {React.ReactNode} props.children Child components to render
|
|
13
|
-
* @param {Function} props.onUpgrade Callback called when storage is upgraded
|
|
14
|
-
* @param {Boolean} props.autoUpgrade Whether to automatically upgrade storage (default: true)
|
|
15
|
-
*/
|
|
16
|
-
export function HydrationProvider({ children, onUpgrade, autoUpgrade = true }) {
|
|
17
|
-
const hasUpgraded = useRef(false)
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!autoUpgrade || hasUpgraded.current || !getIsClient()) {
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const attemptUpgrade = () => {
|
|
25
|
-
if (getHasHydrated() && !hasUpgraded.current) {
|
|
26
|
-
const upgraded = upgradeStorage()
|
|
27
|
-
|
|
28
|
-
if (upgraded) {
|
|
29
|
-
hasUpgraded.current = true
|
|
30
|
-
onUpgrade?.()
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Try to upgrade immediately if already hydrated
|
|
36
|
-
attemptUpgrade()
|
|
37
|
-
|
|
38
|
-
// Also try after a short delay to ensure DOM is ready
|
|
39
|
-
const timeoutId = setTimeout(attemptUpgrade, 0)
|
|
40
|
-
|
|
41
|
-
return () => clearTimeout(timeoutId)
|
|
42
|
-
}, [autoUpgrade, onUpgrade])
|
|
43
|
-
|
|
44
|
-
return children
|
|
45
|
-
}
|
package/src/components/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { HydrationProvider } from './HydrationProvider.js'
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { fakeLocalStorage } from '../utils/index.js'
|
|
2
|
-
import LocalStorageProvider from './LocalStorageProvider.js'
|
|
3
|
-
|
|
4
|
-
class MemoryStorageProvider extends LocalStorageProvider {
|
|
5
|
-
constructor(namespace = null, registry = {}) {
|
|
6
|
-
super(namespace, registry)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
getStorage() {
|
|
10
|
-
return fakeLocalStorage
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default MemoryStorageProvider
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const storage = {
|
|
2
|
-
__IS_FAKE_LOCAL_STORAGE__: true,
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export const fakeLocalStorage = {
|
|
6
|
-
getItem: (key) => storage[key],
|
|
7
|
-
setItem: (key, value) => {
|
|
8
|
-
storage[key] = value
|
|
9
|
-
},
|
|
10
|
-
removeItem: (key) => {
|
|
11
|
-
delete storage[key]
|
|
12
|
-
},
|
|
13
|
-
// Make Object.keys() work properly for _resetAll method
|
|
14
|
-
...storage,
|
|
15
|
-
}
|
package/src/utils/keys.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convenience map of keys
|
|
3
|
-
*/
|
|
4
|
-
const storageKeys = {}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Adds a key to the keys map
|
|
8
|
-
*
|
|
9
|
-
* @param {String} value Key name
|
|
10
|
-
*/
|
|
11
|
-
export const addKey = (value) => {
|
|
12
|
-
storageKeys[value] = value
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Adds a key to the keys map
|
|
17
|
-
* (Alias for `addKey`)
|
|
18
|
-
*
|
|
19
|
-
* @param {String} value Key name
|
|
20
|
-
*/
|
|
21
|
-
export const key = (value) => addKey(value)
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Convenience method to get internally managed storage keys
|
|
25
|
-
*
|
|
26
|
-
* @returns {Object} Storage keys map
|
|
27
|
-
*/
|
|
28
|
-
export const getKeys = () => storageKeys
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Helper utility to prefix all keys in a map to use a namespace
|
|
32
|
-
*
|
|
33
|
-
* @param {String} namespace Storage namespace prefix
|
|
34
|
-
* @param {Object} keys (Optional) Storage key/values. Defaults to the internally managed keys map
|
|
35
|
-
*/
|
|
36
|
-
export const getPrefixedKeys = (namespace, keys = null) => {
|
|
37
|
-
const items = keys || storageKeys
|
|
38
|
-
|
|
39
|
-
if (!namespace) return items
|
|
40
|
-
|
|
41
|
-
return Object.keys(items).reduce((acc, it) => {
|
|
42
|
-
acc[it] = `${namespace}.${items[it]}`
|
|
43
|
-
|
|
44
|
-
return acc
|
|
45
|
-
}, {})
|
|
46
|
-
}
|