react-wire-persisted 2.0.0 → 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.
@@ -1,6 +1,14 @@
1
1
  import { createWire } from '@forminator/react-wire'
2
- import LocalStorageProvider from './providers/LocalStorageProvider'
3
- import { getIsClient, getHasHydrated } from './utils'
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
- let Provider = LocalStorageProvider
12
- let storage = new Provider()
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
- let pendingLogs = []
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
- if (!getIsClient()) {
64
- return false
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 && (typeof key) !== 'number') throw new Error(
97
- `createPersistedWire: Key cannot be a falsey value (${key}}`
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
- const storedValue = storage.getItem(key)
118
- const initialValue = storedValue === null ? value : storedValue
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
- setValue(initialValue)
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
  }
@@ -1,21 +1,19 @@
1
- export * from './keys'
2
- export * from './fakeLocalStorage'
3
- export * from './isomorphic'
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
  }
@@ -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
- } else {
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 (e) {
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
- return items
43
-
44
- return Object.keys(items).reduce((acc, it) => ({
45
- ...acc,
46
- [it]: `${namespace}.${items[it]}`,
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
  }