react-wire-persisted 2.0.1 → 2.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/client.js +1 -1
- package/client.mjs +6 -5
- package/dist/index.js +208 -171
- package/dist/react-wire-persisted.js +208 -171
- package/dist/react-wire-persisted.umd.cjs +1 -1
- package/nextjs.js +4 -27
- package/package.json +82 -75
- package/src/components/HydrationProvider.js +3 -3
- package/src/components/HydrationProvider.jsx +2 -2
- package/src/components/index.js +1 -1
- package/src/hooks/useHydration.js +13 -25
- package/src/index.js +2 -2
- package/src/providers/LocalStorageProvider.js +40 -90
- package/src/providers/MemoryStorageProvider.js +3 -9
- package/src/providers/StorageProvider.js +13 -21
- package/src/react-wire-persisted.js +162 -37
- package/src/utils/fakeLocalStorage.js +3 -4
- package/src/utils/index.js +7 -9
- package/src/utils/isomorphic.js +19 -6
- package/src/utils/keys.js +14 -17
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { createWire } from '@forminator/react-wire'
|
|
2
|
-
import LocalStorageProvider from './providers/LocalStorageProvider'
|
|
3
|
-
import { getIsClient,
|
|
2
|
+
import LocalStorageProvider from './providers/LocalStorageProvider.js'
|
|
3
|
+
import { getHasHydratedStorage, getIsClient, markStorageAsHydrated } from './utils/index.js'
|
|
4
|
+
|
|
5
|
+
// Generate unique instance ID
|
|
6
|
+
const instanceId = Math.random().toString(36).substring(7)
|
|
7
|
+
const rwpLog = (...args) => {
|
|
8
|
+
if (typeof globalThis !== 'undefined' && globalThis.__RWP_LOGGING_ENABLED__ !== false) {
|
|
9
|
+
console.log(...args)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
export const defaultOptions = {
|
|
6
14
|
logging: {
|
|
@@ -8,21 +16,59 @@ export const defaultOptions = {
|
|
|
8
16
|
},
|
|
9
17
|
}
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
// Set global logging flag on startup
|
|
20
|
+
if (typeof globalThis !== 'undefined' && globalThis.__RWP_LOGGING_ENABLED__ === undefined) {
|
|
21
|
+
globalThis.__RWP_LOGGING_ENABLED__ = defaultOptions.logging.enabled
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
rwpLog('[RWP] Module initialized, instance ID:', instanceId)
|
|
25
|
+
|
|
26
|
+
const Provider = LocalStorageProvider
|
|
27
|
+
|
|
28
|
+
// Make storage global so all instances share the same storage after upgrade
|
|
29
|
+
rwpLog('[RWP] About to check global storage, instanceId:', instanceId)
|
|
30
|
+
let storage
|
|
31
|
+
try {
|
|
32
|
+
if (!globalThis.__RWP_STORAGE__) {
|
|
33
|
+
rwpLog('[RWP] Creating global storage in instance:', instanceId)
|
|
34
|
+
globalThis.__RWP_STORAGE__ = new Provider()
|
|
35
|
+
} else {
|
|
36
|
+
rwpLog('[RWP] Using existing global storage in instance:', instanceId)
|
|
37
|
+
}
|
|
38
|
+
storage = globalThis.__RWP_STORAGE__
|
|
39
|
+
rwpLog('[RWP] Storage assigned successfully')
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (globalThis.__RWP_LOGGING_ENABLED__) console.error('[RWP] Error setting up global storage:', error)
|
|
42
|
+
storage = new Provider()
|
|
43
|
+
}
|
|
13
44
|
let options = { ...defaultOptions }
|
|
14
|
-
|
|
45
|
+
const pendingLogs = []
|
|
46
|
+
|
|
47
|
+
// Use a global registry to handle multiple module instances
|
|
48
|
+
// This ensures all instances share the same wire registry
|
|
49
|
+
if (typeof globalThis !== 'undefined') {
|
|
50
|
+
if (!globalThis.__RWP_REGISTERED_WIRES__) {
|
|
51
|
+
rwpLog('[RWP] Creating global registeredWires in instance:', instanceId)
|
|
52
|
+
globalThis.__RWP_REGISTERED_WIRES__ = new Map()
|
|
53
|
+
} else {
|
|
54
|
+
rwpLog('[RWP] Using existing global registeredWires in instance:', instanceId)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Registry to track wire instances for hydration refresh
|
|
59
|
+
const registeredWires = globalThis.__RWP_REGISTERED_WIRES__ || new Map()
|
|
60
|
+
rwpLog('[RWP] registeredWires Map reference in instance:', instanceId, 'size:', registeredWires.size)
|
|
15
61
|
|
|
16
62
|
/**
|
|
17
63
|
* Gets the namespace of the storage provider
|
|
18
|
-
*
|
|
64
|
+
*
|
|
19
65
|
* @returns {String}
|
|
20
66
|
*/
|
|
21
67
|
export const getNamespace = () => storage.namespace
|
|
22
68
|
|
|
23
69
|
/**
|
|
24
70
|
* Gets the current storage provider class instance
|
|
25
|
-
*
|
|
71
|
+
*
|
|
26
72
|
* @returns {StorageProvider}
|
|
27
73
|
*/
|
|
28
74
|
export const getStorage = () => storage
|
|
@@ -31,19 +77,25 @@ export const getOptions = () => options
|
|
|
31
77
|
|
|
32
78
|
/**
|
|
33
79
|
* Sets the namespace for the storage provider
|
|
34
|
-
*
|
|
80
|
+
*
|
|
35
81
|
* @param {String} namespace The namespace for the storage provider
|
|
36
82
|
*/
|
|
37
|
-
export const setNamespace = namespace => {
|
|
83
|
+
export const setNamespace = (namespace) => {
|
|
84
|
+
rwpLog('[RWP] setNamespace() called with:', namespace, 'registered wires before:', registeredWires.size)
|
|
38
85
|
storage.setNamespace(namespace)
|
|
39
86
|
storage = new Provider(namespace || getNamespace())
|
|
87
|
+
rwpLog('[RWP] setNamespace() done, registered wires after:', registeredWires.size)
|
|
40
88
|
}
|
|
41
89
|
|
|
42
|
-
export const setOptions = value => {
|
|
90
|
+
export const setOptions = (value) => {
|
|
43
91
|
options = {
|
|
44
92
|
...options,
|
|
45
93
|
...value,
|
|
46
94
|
}
|
|
95
|
+
// Update global logging flag
|
|
96
|
+
if (typeof globalThis !== 'undefined') {
|
|
97
|
+
globalThis.__RWP_LOGGING_ENABLED__ = options.logging.enabled
|
|
98
|
+
}
|
|
47
99
|
/* istanbul ignore next */
|
|
48
100
|
if (options.logging.enabled) {
|
|
49
101
|
console.info('Flushing', pendingLogs.length, 'pending logs')
|
|
@@ -53,6 +105,38 @@ export const setOptions = value => {
|
|
|
53
105
|
}
|
|
54
106
|
}
|
|
55
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Refresh all registered wires by reading from storage
|
|
110
|
+
* Called after storage upgrade to sync wires with persisted values
|
|
111
|
+
*/
|
|
112
|
+
const refreshAllWires = () => {
|
|
113
|
+
rwpLog('[RWP] refreshAllWires() called in instance:', instanceId, 'registered wires:', registeredWires.size)
|
|
114
|
+
log('react-wire-persisted: refreshAllWires() called, registered wires:', registeredWires.size)
|
|
115
|
+
|
|
116
|
+
registeredWires.forEach((wire, key) => {
|
|
117
|
+
const storedValue = storage.getItem(key)
|
|
118
|
+
const currentValue = wire.getValue()
|
|
119
|
+
|
|
120
|
+
rwpLog('[RWP] Checking wire', key, {
|
|
121
|
+
storedValue,
|
|
122
|
+
currentValue,
|
|
123
|
+
willUpdate: storedValue !== null && storedValue !== currentValue,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
log('react-wire-persisted: Checking wire', key, {
|
|
127
|
+
storedValue,
|
|
128
|
+
currentValue,
|
|
129
|
+
willUpdate: storedValue !== null && storedValue !== currentValue,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
if (storedValue !== null && storedValue !== currentValue) {
|
|
133
|
+
rwpLog('[RWP] Refreshing wire', key, 'with stored value', storedValue)
|
|
134
|
+
log('react-wire-persisted: Refreshing wire', key, 'with stored value', storedValue)
|
|
135
|
+
wire.setValue(storedValue)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
56
140
|
/**
|
|
57
141
|
* Attempts to upgrade the storage provider from fake storage to real localStorage
|
|
58
142
|
* This should be called on the client side after hydration
|
|
@@ -60,14 +144,30 @@ export const setOptions = value => {
|
|
|
60
144
|
* @returns {Boolean} True if upgrade was successful
|
|
61
145
|
*/
|
|
62
146
|
export const upgradeStorage = () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
147
|
+
rwpLog('[RWP] upgradeStorage() called in instance:', instanceId, {
|
|
148
|
+
isClient: getIsClient(),
|
|
149
|
+
isUsingFakeStorage: storage.isUsingFakeStorage(),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
log('react-wire-persisted: upgradeStorage() called', {
|
|
153
|
+
isClient: getIsClient(),
|
|
154
|
+
isUsingFakeStorage: storage.isUsingFakeStorage(),
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (!getIsClient()) return false
|
|
66
158
|
|
|
67
159
|
const upgraded = storage.upgradeToRealStorage()
|
|
68
160
|
|
|
161
|
+
rwpLog('[RWP] upgradeToRealStorage() returned', upgraded)
|
|
162
|
+
log('react-wire-persisted: upgradeToRealStorage() returned', upgraded)
|
|
163
|
+
|
|
69
164
|
if (upgraded) {
|
|
165
|
+
markStorageAsHydrated()
|
|
166
|
+
rwpLog('[RWP] Upgraded to real localStorage, calling refreshAllWires()')
|
|
70
167
|
log('react-wire-persisted: Upgraded to real localStorage after hydration')
|
|
168
|
+
|
|
169
|
+
// Refresh all wires with stored values
|
|
170
|
+
refreshAllWires()
|
|
71
171
|
}
|
|
72
172
|
|
|
73
173
|
return upgraded
|
|
@@ -78,59 +178,84 @@ const log = (...args) => {
|
|
|
78
178
|
if (options.logging.enabled)
|
|
79
179
|
/* istanbul ignore next */
|
|
80
180
|
console.log(...args)
|
|
81
|
-
else
|
|
82
|
-
pendingLogs.push(args)
|
|
181
|
+
else pendingLogs.push(args)
|
|
83
182
|
}
|
|
84
183
|
|
|
85
184
|
/**
|
|
86
185
|
* Creates a persisted Wire using the `StorageProvider` that is currently set
|
|
87
186
|
* Defaults to `localStorage` via `LocalStorageProvider`
|
|
88
|
-
*
|
|
187
|
+
*
|
|
89
188
|
* @param {String} key Unique key for storing this value
|
|
90
189
|
* @param {*} value Initial value of this Wire
|
|
91
190
|
* @returns A new Wire decorated with localStorage functionality
|
|
92
191
|
*/
|
|
93
192
|
export const createPersistedWire = (key, value = null) => {
|
|
94
|
-
|
|
193
|
+
rwpLog('[RWP] createPersistedWire() called in instance:', instanceId, 'key:', key, 'value:', value)
|
|
194
|
+
|
|
95
195
|
// This check helps ensure no accidental key typos occur
|
|
96
|
-
if (!key &&
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
|
|
196
|
+
if (!key && typeof key !== 'number') throw new Error(`createPersistedWire: Key cannot be a falsey value (${key}}`)
|
|
197
|
+
|
|
100
198
|
// Track this writable entry so we can easily clear all
|
|
101
199
|
storage.register(key, value)
|
|
102
|
-
|
|
200
|
+
|
|
103
201
|
// The actual Wire backing object
|
|
104
202
|
const wire = createWire(value)
|
|
105
|
-
|
|
203
|
+
|
|
106
204
|
const getValue = () => wire.getValue()
|
|
107
|
-
|
|
108
|
-
const setValue = newValue => {
|
|
205
|
+
|
|
206
|
+
const setValue = (newValue) => {
|
|
207
|
+
rwpLog(
|
|
208
|
+
'[RWP] setValue called in instance:',
|
|
209
|
+
instanceId,
|
|
210
|
+
'key:',
|
|
211
|
+
key,
|
|
212
|
+
'isUsingFakeStorage:',
|
|
213
|
+
storage.isUsingFakeStorage(),
|
|
214
|
+
)
|
|
109
215
|
storage.setItem(key, newValue)
|
|
110
216
|
return wire.setValue(newValue)
|
|
111
217
|
}
|
|
112
|
-
|
|
113
|
-
const subscribe = fn => {
|
|
218
|
+
|
|
219
|
+
const subscribe = (fn) => {
|
|
114
220
|
wire.subscribe(fn)
|
|
115
221
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
222
|
+
|
|
223
|
+
// Always start with default value to ensure SSR consistency
|
|
224
|
+
let initialValue = value
|
|
225
|
+
|
|
226
|
+
// Only read from storage if we've hydrated OR if storage is already using real localStorage
|
|
227
|
+
// (prevents hydration mismatch in SSR, but allows normal behavior in client-only apps)
|
|
228
|
+
const canReadStorage = getHasHydratedStorage() || !storage.isUsingFakeStorage()
|
|
229
|
+
|
|
230
|
+
if (canReadStorage && getIsClient()) {
|
|
231
|
+
const storedValue = storage.getItem(key)
|
|
232
|
+
|
|
233
|
+
if (storedValue !== null) initialValue = storedValue
|
|
234
|
+
}
|
|
235
|
+
|
|
120
236
|
log('react-wire-persisted: create', key, {
|
|
121
237
|
value,
|
|
122
|
-
storedValue,
|
|
123
238
|
initialValue,
|
|
239
|
+
hasHydratedStorage: getHasHydratedStorage(),
|
|
240
|
+
isUsingFakeStorage: storage.isUsingFakeStorage(),
|
|
241
|
+
canReadStorage,
|
|
124
242
|
})
|
|
125
|
-
|
|
126
|
-
if (initialValue !== value)
|
|
127
|
-
|
|
128
|
-
|
|
243
|
+
|
|
244
|
+
if (initialValue !== value) setValue(initialValue)
|
|
245
|
+
|
|
246
|
+
// Register wire for post-hydration refresh
|
|
247
|
+
registeredWires.set(key, {
|
|
248
|
+
getValue,
|
|
249
|
+
setValue,
|
|
250
|
+
subscribe,
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
rwpLog('[RWP] Wire registered, total wires:', registeredWires.size, 'keys:', Array.from(registeredWires.keys()))
|
|
254
|
+
|
|
129
255
|
return {
|
|
130
256
|
...wire,
|
|
131
257
|
getValue,
|
|
132
258
|
setValue,
|
|
133
259
|
subscribe,
|
|
134
260
|
}
|
|
135
|
-
|
|
136
261
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
|
|
2
1
|
const storage = {
|
|
3
2
|
__IS_FAKE_LOCAL_STORAGE__: true,
|
|
4
3
|
}
|
|
5
4
|
|
|
6
5
|
export const fakeLocalStorage = {
|
|
7
|
-
getItem: key => storage[key],
|
|
6
|
+
getItem: (key) => storage[key],
|
|
8
7
|
setItem: (key, value) => {
|
|
9
8
|
storage[key] = value
|
|
10
9
|
},
|
|
11
|
-
removeItem: key => {
|
|
10
|
+
removeItem: (key) => {
|
|
12
11
|
delete storage[key]
|
|
13
12
|
},
|
|
14
13
|
// Make Object.keys() work properly for _resetAll method
|
|
15
|
-
...storage
|
|
14
|
+
...storage,
|
|
16
15
|
}
|
package/src/utils/index.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
1
|
+
export * from './fakeLocalStorage.js'
|
|
2
|
+
export * from './isomorphic.js'
|
|
3
|
+
export * from './keys.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Checks if a value is a primitive type
|
|
7
|
-
*
|
|
7
|
+
*
|
|
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 => {
|
|
12
|
-
|
|
11
|
+
export const isPrimitive = (val) => {
|
|
13
12
|
const type = typeof val
|
|
14
|
-
|
|
13
|
+
|
|
15
14
|
if (val === null) return true
|
|
16
15
|
if (Array.isArray(val)) return false
|
|
17
16
|
if (type === 'object') return false
|
|
18
|
-
|
|
17
|
+
|
|
19
18
|
return type !== 'function'
|
|
20
|
-
|
|
21
19
|
}
|
package/src/utils/isomorphic.js
CHANGED
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
let isClient = false
|
|
6
6
|
let hasHydrated = false
|
|
7
|
+
let hasHydratedStorage = false
|
|
7
8
|
|
|
8
9
|
// Detect if we're running in a browser environment
|
|
9
10
|
if (typeof window !== 'undefined') {
|
|
10
11
|
isClient = true
|
|
11
12
|
|
|
12
13
|
// Mark as hydrated when the DOM is ready
|
|
13
|
-
if (document.readyState === 'loading')
|
|
14
|
+
if (document.readyState === 'loading')
|
|
14
15
|
document.addEventListener('DOMContentLoaded', () => {
|
|
15
16
|
hasHydrated = true
|
|
16
17
|
})
|
|
17
|
-
|
|
18
|
-
hasHydrated = true
|
|
19
|
-
}
|
|
18
|
+
else hasHydrated = true
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/**
|
|
@@ -29,6 +28,18 @@ export const getIsClient = () => isClient
|
|
|
29
28
|
*/
|
|
30
29
|
export const getHasHydrated = () => hasHydrated
|
|
31
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Check if storage has been hydrated (safe to read from real localStorage)
|
|
33
|
+
*/
|
|
34
|
+
export const getHasHydratedStorage = () => hasHydratedStorage
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Mark storage as hydrated (called after upgradeStorage)
|
|
38
|
+
*/
|
|
39
|
+
export const markStorageAsHydrated = () => {
|
|
40
|
+
hasHydratedStorage = true
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
/**
|
|
33
44
|
* Check if localStorage is available and safe to use
|
|
34
45
|
*/
|
|
@@ -37,10 +48,12 @@ export const isLocalStorageAvailable = () => {
|
|
|
37
48
|
|
|
38
49
|
try {
|
|
39
50
|
const testKey = '__rwp_test__'
|
|
51
|
+
|
|
40
52
|
window.localStorage.setItem(testKey, 'test')
|
|
41
53
|
window.localStorage.removeItem(testKey)
|
|
54
|
+
|
|
42
55
|
return true
|
|
43
|
-
} catch (
|
|
56
|
+
} catch (_) {
|
|
44
57
|
return false
|
|
45
58
|
}
|
|
46
|
-
}
|
|
59
|
+
}
|
package/src/utils/keys.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Convenience map of keys
|
|
4
3
|
*/
|
|
@@ -6,44 +5,42 @@ const storageKeys = {}
|
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Adds a key to the keys map
|
|
9
|
-
*
|
|
8
|
+
*
|
|
10
9
|
* @param {String} value Key name
|
|
11
10
|
*/
|
|
12
|
-
export const addKey = value => {
|
|
11
|
+
export const addKey = (value) => {
|
|
13
12
|
storageKeys[value] = value
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Adds a key to the keys map
|
|
18
17
|
* (Alias for `addKey`)
|
|
19
|
-
*
|
|
18
|
+
*
|
|
20
19
|
* @param {String} value Key name
|
|
21
20
|
*/
|
|
22
|
-
export const key = value => addKey(value)
|
|
21
|
+
export const key = (value) => addKey(value)
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Convenience method to get internally managed storage keys
|
|
26
|
-
*
|
|
25
|
+
*
|
|
27
26
|
* @returns {Object} Storage keys map
|
|
28
27
|
*/
|
|
29
28
|
export const getKeys = () => storageKeys
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
31
|
* Helper utility to prefix all keys in a map to use a namespace
|
|
33
|
-
*
|
|
32
|
+
*
|
|
34
33
|
* @param {String} namespace Storage namespace prefix
|
|
35
34
|
* @param {Object} keys (Optional) Storage key/values. Defaults to the internally managed keys map
|
|
36
35
|
*/
|
|
37
36
|
export const getPrefixedKeys = (namespace, keys = null) => {
|
|
38
|
-
|
|
39
37
|
const items = keys || storageKeys
|
|
40
|
-
|
|
41
|
-
if (!namespace)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
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
|
+
}, {})
|
|
49
46
|
}
|