react-wire-persisted 1.5.0 → 2.0.1

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 ADDED
@@ -0,0 +1,58 @@
1
+ 'use client'
2
+
3
+ // This file is specifically for Next.js App Router client components
4
+ // Import this directly: import { HydrationProvider } from 'react-wire-persisted/client'
5
+
6
+ import { useEffect, useRef } from 'react'
7
+ import { upgradeStorage } from './dist/react-wire-persisted.js'
8
+
9
+ // Inline the hydration detection logic to avoid import issues
10
+ const getIsClient = () => typeof window !== 'undefined'
11
+
12
+ const getHasHydrated = () => {
13
+ if (!getIsClient()) return false
14
+ return document.readyState !== 'loading'
15
+ }
16
+
17
+ /**
18
+ * A Next.js App Router compatible client component that handles automatic storage upgrade
19
+ * after hydration. Import this from 'react-wire-persisted/client' to ensure proper client-side operation.
20
+ *
21
+ * @param {Object} props Component props
22
+ * @param {React.ReactNode} props.children Child components to render
23
+ * @param {Function} props.onUpgrade Callback called when storage is upgraded
24
+ * @param {Boolean} props.autoUpgrade Whether to automatically upgrade storage (default: true)
25
+ */
26
+ export function HydrationProvider({ children, onUpgrade, autoUpgrade = true }) {
27
+ const hasUpgraded = useRef(false)
28
+
29
+ useEffect(() => {
30
+ if (!autoUpgrade || hasUpgraded.current || !getIsClient()) {
31
+ return
32
+ }
33
+
34
+ const attemptUpgrade = () => {
35
+ if (getHasHydrated() && !hasUpgraded.current) {
36
+ const upgraded = upgradeStorage()
37
+
38
+ if (upgraded) {
39
+ hasUpgraded.current = true
40
+ onUpgrade?.()
41
+ }
42
+ }
43
+ }
44
+
45
+ // Try to upgrade immediately if already hydrated
46
+ attemptUpgrade()
47
+
48
+ // Also try after a short delay to ensure DOM is ready
49
+ const timeoutId = setTimeout(attemptUpgrade, 0)
50
+
51
+ return () => clearTimeout(timeoutId)
52
+ }, [autoUpgrade, onUpgrade])
53
+
54
+ return children
55
+ }
56
+
57
+ // Re-export the hook from the main package for convenience
58
+ export { useHydration } from './dist/react-wire-persisted.js'
package/client.mjs ADDED
@@ -0,0 +1,108 @@
1
+ 'use client'
2
+
3
+ // This file is specifically for Next.js App Router client components
4
+ // Import this directly: import { HydrationProvider } from 'react-wire-persisted/client.mjs'
5
+
6
+ import { useEffect, useRef } from 'react'
7
+
8
+ // Inline all the necessary utilities to avoid any import issues
9
+ const isClient = typeof window !== 'undefined'
10
+
11
+ const isLocalStorageAvailable = () => {
12
+ if (!isClient) return false
13
+
14
+ try {
15
+ const testKey = '__rwp_test__'
16
+ window.localStorage.setItem(testKey, 'test')
17
+ window.localStorage.removeItem(testKey)
18
+ return true
19
+ } catch (e) {
20
+ return false
21
+ }
22
+ }
23
+
24
+ // Simple fake storage for SSR
25
+ const fakeStorage = {
26
+ data: {},
27
+ getItem: (key) => fakeStorage.data[key] || null,
28
+ setItem: (key, value) => {
29
+ fakeStorage.data[key] = value
30
+ },
31
+ removeItem: (key) => {
32
+ delete fakeStorage.data[key]
33
+ }
34
+ }
35
+
36
+ // Get current storage (real or fake)
37
+ const getCurrentStorage = () => {
38
+ return isLocalStorageAvailable() ? window.localStorage : fakeStorage
39
+ }
40
+
41
+ // Simple storage upgrade function
42
+ const upgradeStorageToReal = () => {
43
+ if (!isClient || !isLocalStorageAvailable()) {
44
+ return false
45
+ }
46
+
47
+ // If we're already using real localStorage, nothing to do
48
+ if (fakeStorage.data && Object.keys(fakeStorage.data).length === 0) {
49
+ return false
50
+ }
51
+
52
+ try {
53
+ // Move data from fake to real storage
54
+ const fakeData = { ...fakeStorage.data }
55
+ Object.keys(fakeData).forEach(key => {
56
+ if (fakeData[key] != null) {
57
+ window.localStorage.setItem(key, fakeData[key])
58
+ }
59
+ })
60
+
61
+ // Clear fake storage
62
+ fakeStorage.data = {}
63
+ return true
64
+ } catch (e) {
65
+ return false
66
+ }
67
+ }
68
+
69
+ /**
70
+ * A Next.js App Router compatible client component that handles automatic storage upgrade
71
+ * after hydration. Import this from 'react-wire-persisted/client.mjs' to ensure proper client-side operation.
72
+ *
73
+ * @param {Object} props Component props
74
+ * @param {React.ReactNode} props.children Child components to render
75
+ * @param {Function} props.onUpgrade Callback called when storage is upgraded
76
+ * @param {Boolean} props.autoUpgrade Whether to automatically upgrade storage (default: true)
77
+ */
78
+ export function HydrationProvider({ children, onUpgrade, autoUpgrade = true }) {
79
+ const hasUpgraded = useRef(false)
80
+
81
+ useEffect(() => {
82
+ if (!autoUpgrade || hasUpgraded.current || !isClient) {
83
+ return
84
+ }
85
+
86
+ const attemptUpgrade = () => {
87
+ // Wait for DOM to be ready
88
+ if (document.readyState !== 'loading' && !hasUpgraded.current) {
89
+ const upgraded = upgradeStorageToReal()
90
+
91
+ if (upgraded) {
92
+ hasUpgraded.current = true
93
+ onUpgrade?.()
94
+ }
95
+ }
96
+ }
97
+
98
+ // Try to upgrade immediately if DOM is already ready
99
+ attemptUpgrade()
100
+
101
+ // Also try after a short delay to ensure everything is ready
102
+ const timeoutId = setTimeout(attemptUpgrade, 100)
103
+
104
+ return () => clearTimeout(timeoutId)
105
+ }, [autoUpgrade, onUpgrade])
106
+
107
+ return children
108
+ }
@@ -1,4 +1,5 @@
1
- import { createWire as b } from "@forminator/react-wire";
1
+ import { createWire as U } from "@forminator/react-wire";
2
+ import { useRef as b, useEffect as O } from "react";
2
3
  (function() {
3
4
  const s = {};
4
5
  try {
@@ -10,42 +11,62 @@ import { createWire as b } from "@forminator/react-wire";
10
11
  }
11
12
  globalThis.process = { env: s };
12
13
  })();
13
- const m = {}, p = (s) => {
14
- m[s] = s;
15
- }, I = (s) => p(s), O = () => m, S = (s, e = null) => {
16
- const t = e || m;
17
- return s ? Object.keys(t).reduce((r, n) => ({
14
+ const y = {}, w = (s) => {
15
+ y[s] = s;
16
+ }, j = (s) => w(s), x = () => y, N = (s, e = null) => {
17
+ const t = e || y;
18
+ return s ? Object.keys(t).reduce((r, o) => ({
18
19
  ...r,
19
- [n]: `${s}.${t[n]}`
20
+ [o]: `${s}.${t[o]}`
20
21
  }), {}) : t;
21
22
  }, g = {
23
+ __IS_FAKE_LOCAL_STORAGE__: !0
24
+ }, h = {
22
25
  getItem: (s) => g[s],
23
26
  setItem: (s, e) => {
24
27
  g[s] = e;
25
28
  },
26
29
  removeItem: (s) => {
27
30
  delete g[s];
31
+ },
32
+ // Make Object.keys() work properly for _resetAll method
33
+ ...g
34
+ };
35
+ let S = !1, p = !1;
36
+ typeof window < "u" && (S = !0, document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", () => {
37
+ p = !0;
38
+ }) : p = !0);
39
+ const m = () => S, _ = () => p, d = () => {
40
+ if (!S) return !1;
41
+ try {
42
+ const s = "__rwp_test__";
43
+ return window.localStorage.setItem(s, "test"), window.localStorage.removeItem(s), !0;
44
+ } catch {
45
+ return !1;
28
46
  }
29
- }, d = (s) => {
47
+ }, A = (s) => {
30
48
  const e = typeof s;
31
49
  return s === null ? !0 : Array.isArray(s) || e === "object" ? !1 : e !== "function";
32
- }, j = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
50
+ }, k = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
33
51
  __proto__: null,
34
- addKey: p,
35
- fakeLocalStorage: g,
36
- getKeys: O,
37
- getPrefixedKeys: S,
38
- isPrimitive: d,
39
- key: I
52
+ addKey: w,
53
+ fakeLocalStorage: h,
54
+ getHasHydrated: _,
55
+ getIsClient: m,
56
+ getKeys: x,
57
+ getPrefixedKeys: N,
58
+ isLocalStorageAvailable: d,
59
+ isPrimitive: A,
60
+ key: j
40
61
  }, Symbol.toStringTag, { value: "Module" }));
41
- class h {
62
+ class I {
42
63
  /**
43
64
  * Initializes the class
44
65
  * @param {String} namespace Namespace to prefix all keys with. Mostly used for the logging & reset functions
45
66
  * @param {Object} registry (Optional) Initialize the storage provider with an existing registry
46
67
  */
47
68
  constructor(e, t) {
48
- if (new.target === h)
69
+ if (new.target === I)
49
70
  throw TypeError("StorageProvider is abstract. Extend this class to implement it");
50
71
  this.namespace = e || null, this.registry = t || /* istanbul ignore next */
51
72
  {};
@@ -122,16 +143,12 @@ class h {
122
143
  removeAll(e = []) {
123
144
  }
124
145
  }
125
- class v extends h {
146
+ class T extends I {
126
147
  constructor(e = null, t = {}) {
127
- super(e, t), this.storage = this.getStorage();
148
+ super(e, t), this.storage = this.getStorage(), this._isUsingFakeStorage = !d();
128
149
  }
129
150
  getStorage() {
130
- try {
131
- return window.localStorage;
132
- } catch {
133
- return console.warn(new Error("LocalStorageProvider: localStorage not supported")), g;
134
- }
151
+ return d() ? window.localStorage : h;
135
152
  }
136
153
  setNamespace(e) {
137
154
  if (!this.namespace) {
@@ -142,9 +159,9 @@ class v extends h {
142
159
  return;
143
160
  const t = JSON.parse(JSON.stringify(this.getAll()));
144
161
  this.removeAll();
145
- for (const [r, n] of Object.entries(t)) {
146
- const o = r.replace(this.namespace, e);
147
- this.setItem(o, n);
162
+ for (const [r, o] of Object.entries(t)) {
163
+ const n = r.replace(this.namespace, e);
164
+ this.setItem(n, o);
148
165
  }
149
166
  this.namespace = e;
150
167
  }
@@ -160,7 +177,7 @@ class v extends h {
160
177
  }
161
178
  setItem(e, t) {
162
179
  let r = t;
163
- return r != null && (r = d(t) ? t : JSON.stringify(t)), this.storage.setItem(e, r);
180
+ return r != null && (r = A(t) ? t : JSON.stringify(t)), this.storage.setItem(e, r);
164
181
  }
165
182
  removeItem(e, t = !1) {
166
183
  return t && delete this.registry[e], this.storage.removeItem(e);
@@ -170,10 +187,10 @@ class v extends h {
170
187
  return Object.keys(this.storage).reduce((t, r) => ((!this.namespace || r.startsWith(e)) && (t[r] = this.storage.getItem(r)), t), {});
171
188
  }
172
189
  _resetAll(e = !0, t = [], r = !1) {
173
- const n = `${this.namespace}.`;
174
- Object.keys(localStorage).forEach((o) => {
175
- const l = this.namespace ? o.startsWith(n) : !0, a = t?.includes(o) || !1;
176
- !l || a || (e ? Object.prototype.hasOwnProperty.call(this.registry, o) ? this.storage.setItem(o, this.registry[o]) : this.storage.removeItem(o) : (this.storage.removeItem(o), r && delete this.registry[o]));
190
+ const o = `${this.namespace}.`;
191
+ Object.keys(this.storage).forEach((n) => {
192
+ const a = this.namespace ? n.startsWith(o) : !0, l = t?.includes(n) || !1;
193
+ !a || l || (e ? Object.prototype.hasOwnProperty.call(this.registry, n) ? this.storage.setItem(n, this.registry[n]) : this.storage.removeItem(n) : (this.storage.removeItem(n), r && delete this.registry[n]));
177
194
  });
178
195
  }
179
196
  resetAll(e = [], t = !1) {
@@ -182,50 +199,111 @@ class v extends h {
182
199
  removeAll(e = [], t = !1) {
183
200
  this._resetAll(!1, e || [], t);
184
201
  }
202
+ /**
203
+ * Attempt to upgrade from fake storage to real localStorage
204
+ * This is useful for hydration scenarios
205
+ */
206
+ upgradeToRealStorage() {
207
+ if (!this._isUsingFakeStorage || !d())
208
+ return !1;
209
+ const e = { ...this.storage };
210
+ return this.storage = window.localStorage, this._isUsingFakeStorage = !1, Object.keys(e).forEach((t) => {
211
+ if (t !== "__IS_FAKE_LOCAL_STORAGE__" && e[t] != null)
212
+ try {
213
+ this.storage.setItem(t, e[t]);
214
+ } catch {
215
+ return this.storage = h, this._isUsingFakeStorage = !0, !1;
216
+ }
217
+ }), !0;
218
+ }
219
+ /**
220
+ * Check if currently using fake storage
221
+ */
222
+ isUsingFakeStorage() {
223
+ return this._isUsingFakeStorage;
224
+ }
185
225
  }
186
- const A = {
226
+ const L = {
187
227
  logging: {
188
228
  enabled: !1
189
229
  }
190
230
  };
191
- let y = v, i = new y(), u = { ...A }, f = [];
192
- const w = () => i.namespace, K = () => i, P = () => u, _ = (s) => {
193
- i.setNamespace(s), i = new y(s || w());
194
- }, E = (s) => {
231
+ let v = T, i = new v(), u = { ...L }, f = [];
232
+ const P = () => i.namespace, V = () => i, $ = () => u, C = (s) => {
233
+ i.setNamespace(s), i = new v(s || P());
234
+ }, H = (s) => {
195
235
  if (u = {
196
236
  ...u,
197
237
  ...s
198
238
  }, u.logging.enabled)
199
239
  for (console.info("Flushing", f.length, "pending logs"); f.length; )
200
240
  console.log(...f.shift());
201
- }, x = (...s) => {
241
+ }, K = () => {
242
+ if (!m())
243
+ return !1;
244
+ const s = i.upgradeToRealStorage();
245
+ return s && E("react-wire-persisted: Upgraded to real localStorage after hydration"), s;
246
+ }, E = (...s) => {
202
247
  u.logging.enabled ? console.log(...s) : f.push(s);
203
- }, V = (s, e = null) => {
248
+ }, W = (s, e = null) => {
204
249
  if (!s && typeof s != "number") throw new Error(
205
250
  `createPersistedWire: Key cannot be a falsey value (${s}}`
206
251
  );
207
252
  i.register(s, e);
208
- const t = b(e), r = () => t.getValue(), n = (c) => (i.setItem(s, c), t.setValue(c)), o = (c) => {
253
+ const t = U(e), r = () => t.getValue(), o = (c) => (i.setItem(s, c), t.setValue(c)), n = (c) => {
209
254
  t.subscribe(c);
210
- }, l = i.getItem(s), a = l === null ? e : l;
211
- return x("react-wire-persisted: create", s, {
255
+ }, a = i.getItem(s), l = a === null ? e : a;
256
+ return E("react-wire-persisted: create", s, {
212
257
  value: e,
213
- storedValue: l,
214
- initialValue: a
215
- }), a !== e && n(a), {
258
+ storedValue: a,
259
+ initialValue: l
260
+ }), l !== e && o(l), {
216
261
  ...t,
217
262
  getValue: r,
218
- setValue: n,
219
- subscribe: o
263
+ setValue: o,
264
+ subscribe: n
265
+ };
266
+ }, J = (s = {}) => {
267
+ const {
268
+ autoUpgrade: e = !0,
269
+ onUpgrade: t
270
+ } = s, r = b(!1);
271
+ return O(() => {
272
+ if (!e || r.current || !m())
273
+ return;
274
+ const o = () => {
275
+ _() && !r.current && K() && (r.current = !0, t?.());
276
+ };
277
+ o();
278
+ const n = setTimeout(o, 0);
279
+ return () => clearTimeout(n);
280
+ }, [e, t]), {
281
+ hasUpgraded: r.current
220
282
  };
221
283
  };
284
+ function D({ children: s, onUpgrade: e, autoUpgrade: t = !0 }) {
285
+ const r = b(!1);
286
+ return O(() => {
287
+ if (!t || r.current || !m())
288
+ return;
289
+ const o = () => {
290
+ _() && !r.current && K() && (r.current = !0, e?.());
291
+ };
292
+ o();
293
+ const n = setTimeout(o, 0);
294
+ return () => clearTimeout(n);
295
+ }, [t, e]), s;
296
+ }
222
297
  export {
223
- V as createPersistedWire,
224
- A as defaultOptions,
225
- w as getNamespace,
226
- P as getOptions,
227
- K as getStorage,
228
- _ as setNamespace,
229
- E as setOptions,
230
- j as utils
298
+ D as HydrationProvider,
299
+ W as createPersistedWire,
300
+ L as defaultOptions,
301
+ P as getNamespace,
302
+ $ as getOptions,
303
+ V as getStorage,
304
+ C as setNamespace,
305
+ H as setOptions,
306
+ K as upgradeStorage,
307
+ J as useHydration,
308
+ k as utils
231
309
  };
@@ -0,0 +1,309 @@
1
+ import { createWire as U } from "@forminator/react-wire";
2
+ import { useRef as b, useEffect as O } from "react";
3
+ (function() {
4
+ const s = {};
5
+ try {
6
+ if (process) {
7
+ process.env = Object.assign({}, process.env), Object.assign(process.env, s);
8
+ return;
9
+ }
10
+ } catch {
11
+ }
12
+ globalThis.process = { env: s };
13
+ })();
14
+ const y = {}, w = (s) => {
15
+ y[s] = s;
16
+ }, j = (s) => w(s), x = () => y, N = (s, e = null) => {
17
+ const t = e || y;
18
+ return s ? Object.keys(t).reduce((r, o) => ({
19
+ ...r,
20
+ [o]: `${s}.${t[o]}`
21
+ }), {}) : t;
22
+ }, g = {
23
+ __IS_FAKE_LOCAL_STORAGE__: !0
24
+ }, h = {
25
+ getItem: (s) => g[s],
26
+ setItem: (s, e) => {
27
+ g[s] = e;
28
+ },
29
+ removeItem: (s) => {
30
+ delete g[s];
31
+ },
32
+ // Make Object.keys() work properly for _resetAll method
33
+ ...g
34
+ };
35
+ let S = !1, p = !1;
36
+ typeof window < "u" && (S = !0, document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", () => {
37
+ p = !0;
38
+ }) : p = !0);
39
+ const m = () => S, _ = () => p, d = () => {
40
+ if (!S) return !1;
41
+ try {
42
+ const s = "__rwp_test__";
43
+ return window.localStorage.setItem(s, "test"), window.localStorage.removeItem(s), !0;
44
+ } catch {
45
+ return !1;
46
+ }
47
+ }, A = (s) => {
48
+ const e = typeof s;
49
+ return s === null ? !0 : Array.isArray(s) || e === "object" ? !1 : e !== "function";
50
+ }, k = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
51
+ __proto__: null,
52
+ addKey: w,
53
+ fakeLocalStorage: h,
54
+ getHasHydrated: _,
55
+ getIsClient: m,
56
+ getKeys: x,
57
+ getPrefixedKeys: N,
58
+ isLocalStorageAvailable: d,
59
+ isPrimitive: A,
60
+ key: j
61
+ }, Symbol.toStringTag, { value: "Module" }));
62
+ class I {
63
+ /**
64
+ * Initializes the class
65
+ * @param {String} namespace Namespace to prefix all keys with. Mostly used for the logging & reset functions
66
+ * @param {Object} registry (Optional) Initialize the storage provider with an existing registry
67
+ */
68
+ constructor(e, t) {
69
+ if (new.target === I)
70
+ throw TypeError("StorageProvider is abstract. Extend this class to implement it");
71
+ this.namespace = e || null, this.registry = t || /* istanbul ignore next */
72
+ {};
73
+ }
74
+ /**
75
+ * Sets the namespace for this storage provider, and migrates
76
+ * all stored values to the new namespace
77
+ * @param {String} namespace New namespace for this storage provider
78
+ */
79
+ /* istanbul ignore next */
80
+ setNamespace(e) {
81
+ }
82
+ /**
83
+ * Registers an item with it's initial value. This is used for logging, resetting, etc.
84
+ * @param {String} key Storage item's key
85
+ * @param {*} initialValue Storage item's initial value
86
+ */
87
+ register(e, t) {
88
+ this.registry[e] = t;
89
+ }
90
+ /**
91
+ * Reads an item from storage
92
+ * @param {String} key Key for the item to retrieve
93
+ */
94
+ /* istanbul ignore next */
95
+ getItem(e) {
96
+ }
97
+ /**
98
+ * Stores a value
99
+ * @param {String} key Item's storage key
100
+ * @param {String} value Item's value to store
101
+ */
102
+ /* istanbul ignore next */
103
+ setItem(e, t) {
104
+ }
105
+ /**
106
+ * Removes an item from storage
107
+ * @param {String} key Item's storage key
108
+ * @param {Boolean} fromRegistry (Optional) If the item should also be removed from the registry
109
+ */
110
+ /* istanbul ignore next */
111
+ removeItem(e, t = !1) {
112
+ }
113
+ /**
114
+ * Gets all stored keys & values
115
+ * If a `namespace` was set, only keys prefixed with the namespace will be returned
116
+ */
117
+ /* istanbul ignore next */
118
+ getAll() {
119
+ }
120
+ /**
121
+ *
122
+ * @param {Boolean} useInitialValues If values should be replaced with their initial values. If false, keys are removed
123
+ * @param {String[]} excludedKeys (Optional) List of keys to exclude
124
+ * @param {Boolean} clearRegistry (Optional) If the registry should also be cleared
125
+ */
126
+ /* istanbul ignore next */
127
+ _resetAll(e = !0, t = [], r = !1) {
128
+ }
129
+ /**
130
+ * Resets all values to their initial values
131
+ * If a `namespace` is set, only keys prefixed with the namespace will be reset
132
+ * @param {String[]} excludedKeys (Optional) List of keys to exclude
133
+ */
134
+ /* istanbul ignore next */
135
+ resetAll(e = []) {
136
+ }
137
+ /**
138
+ * Removes all items from local storage.
139
+ * If a `namespace` is set, only keys prefixed with the namespace will be removed
140
+ * @param {String[]} excludedKeys (Optional) List of keys to exclude
141
+ */
142
+ /* istanbul ignore next */
143
+ removeAll(e = []) {
144
+ }
145
+ }
146
+ class T extends I {
147
+ constructor(e = null, t = {}) {
148
+ super(e, t), this.storage = this.getStorage(), this._isUsingFakeStorage = !d();
149
+ }
150
+ getStorage() {
151
+ return d() ? window.localStorage : h;
152
+ }
153
+ setNamespace(e) {
154
+ if (!this.namespace) {
155
+ this.namespace = e;
156
+ return;
157
+ }
158
+ if (this.namespace === e)
159
+ return;
160
+ const t = JSON.parse(JSON.stringify(this.getAll()));
161
+ this.removeAll();
162
+ for (const [r, o] of Object.entries(t)) {
163
+ const n = r.replace(this.namespace, e);
164
+ this.setItem(n, o);
165
+ }
166
+ this.namespace = e;
167
+ }
168
+ getItem(e) {
169
+ const t = this.storage.getItem(e);
170
+ if (t == null)
171
+ return null;
172
+ try {
173
+ return JSON.parse(t);
174
+ } catch {
175
+ return t;
176
+ }
177
+ }
178
+ setItem(e, t) {
179
+ let r = t;
180
+ return r != null && (r = A(t) ? t : JSON.stringify(t)), this.storage.setItem(e, r);
181
+ }
182
+ removeItem(e, t = !1) {
183
+ return t && delete this.registry[e], this.storage.removeItem(e);
184
+ }
185
+ getAll() {
186
+ const e = `${this.namespace}.`;
187
+ return Object.keys(this.storage).reduce((t, r) => ((!this.namespace || r.startsWith(e)) && (t[r] = this.storage.getItem(r)), t), {});
188
+ }
189
+ _resetAll(e = !0, t = [], r = !1) {
190
+ const o = `${this.namespace}.`;
191
+ Object.keys(this.storage).forEach((n) => {
192
+ const a = this.namespace ? n.startsWith(o) : !0, l = t?.includes(n) || !1;
193
+ !a || l || (e ? Object.prototype.hasOwnProperty.call(this.registry, n) ? this.storage.setItem(n, this.registry[n]) : this.storage.removeItem(n) : (this.storage.removeItem(n), r && delete this.registry[n]));
194
+ });
195
+ }
196
+ resetAll(e = [], t = !1) {
197
+ this._resetAll(!0, e || [], t);
198
+ }
199
+ removeAll(e = [], t = !1) {
200
+ this._resetAll(!1, e || [], t);
201
+ }
202
+ /**
203
+ * Attempt to upgrade from fake storage to real localStorage
204
+ * This is useful for hydration scenarios
205
+ */
206
+ upgradeToRealStorage() {
207
+ if (!this._isUsingFakeStorage || !d())
208
+ return !1;
209
+ const e = { ...this.storage };
210
+ return this.storage = window.localStorage, this._isUsingFakeStorage = !1, Object.keys(e).forEach((t) => {
211
+ if (t !== "__IS_FAKE_LOCAL_STORAGE__" && e[t] != null)
212
+ try {
213
+ this.storage.setItem(t, e[t]);
214
+ } catch {
215
+ return this.storage = h, this._isUsingFakeStorage = !0, !1;
216
+ }
217
+ }), !0;
218
+ }
219
+ /**
220
+ * Check if currently using fake storage
221
+ */
222
+ isUsingFakeStorage() {
223
+ return this._isUsingFakeStorage;
224
+ }
225
+ }
226
+ const L = {
227
+ logging: {
228
+ enabled: !1
229
+ }
230
+ };
231
+ let v = T, i = new v(), u = { ...L }, f = [];
232
+ const P = () => i.namespace, V = () => i, $ = () => u, C = (s) => {
233
+ i.setNamespace(s), i = new v(s || P());
234
+ }, H = (s) => {
235
+ if (u = {
236
+ ...u,
237
+ ...s
238
+ }, u.logging.enabled)
239
+ for (console.info("Flushing", f.length, "pending logs"); f.length; )
240
+ console.log(...f.shift());
241
+ }, K = () => {
242
+ if (!m())
243
+ return !1;
244
+ const s = i.upgradeToRealStorage();
245
+ return s && E("react-wire-persisted: Upgraded to real localStorage after hydration"), s;
246
+ }, E = (...s) => {
247
+ u.logging.enabled ? console.log(...s) : f.push(s);
248
+ }, W = (s, e = null) => {
249
+ if (!s && typeof s != "number") throw new Error(
250
+ `createPersistedWire: Key cannot be a falsey value (${s}}`
251
+ );
252
+ i.register(s, e);
253
+ const t = U(e), r = () => t.getValue(), o = (c) => (i.setItem(s, c), t.setValue(c)), n = (c) => {
254
+ t.subscribe(c);
255
+ }, a = i.getItem(s), l = a === null ? e : a;
256
+ return E("react-wire-persisted: create", s, {
257
+ value: e,
258
+ storedValue: a,
259
+ initialValue: l
260
+ }), l !== e && o(l), {
261
+ ...t,
262
+ getValue: r,
263
+ setValue: o,
264
+ subscribe: n
265
+ };
266
+ }, J = (s = {}) => {
267
+ const {
268
+ autoUpgrade: e = !0,
269
+ onUpgrade: t
270
+ } = s, r = b(!1);
271
+ return O(() => {
272
+ if (!e || r.current || !m())
273
+ return;
274
+ const o = () => {
275
+ _() && !r.current && K() && (r.current = !0, t?.());
276
+ };
277
+ o();
278
+ const n = setTimeout(o, 0);
279
+ return () => clearTimeout(n);
280
+ }, [e, t]), {
281
+ hasUpgraded: r.current
282
+ };
283
+ };
284
+ function D({ children: s, onUpgrade: e, autoUpgrade: t = !0 }) {
285
+ const r = b(!1);
286
+ return O(() => {
287
+ if (!t || r.current || !m())
288
+ return;
289
+ const o = () => {
290
+ _() && !r.current && K() && (r.current = !0, e?.());
291
+ };
292
+ o();
293
+ const n = setTimeout(o, 0);
294
+ return () => clearTimeout(n);
295
+ }, [t, e]), s;
296
+ }
297
+ export {
298
+ D as HydrationProvider,
299
+ W as createPersistedWire,
300
+ L as defaultOptions,
301
+ P as getNamespace,
302
+ $ as getOptions,
303
+ V as getStorage,
304
+ C as setNamespace,
305
+ H as setOptions,
306
+ K as upgradeStorage,
307
+ J as useHydration,
308
+ k as utils
309
+ };
@@ -0,0 +1 @@
1
+ (function(n,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("@forminator/react-wire"),require("react")):typeof define=="function"&&define.amd?define(["exports","@forminator/react-wire","react"],l):(n=typeof globalThis<"u"?globalThis:n||self,l(n["react-wire-persisted"]={},n.reactWire,n.React))})(this,(function(n,l,f){"use strict";(function(){const s={};try{if(process){process.env=Object.assign({},process.env),Object.assign(process.env,s);return}}catch{}globalThis.process={env:s}})();const S={},v=s=>{S[s]=s},P=s=>v(s),U=()=>S,R=(s,e=null)=>{const t=e||S;return s?Object.keys(t).reduce((r,o)=>({...r,[o]:`${s}.${t[o]}`}),{}):t},m={__IS_FAKE_LOCAL_STORAGE__:!0},_={getItem:s=>m[s],setItem:(s,e)=>{m[s]=e},removeItem:s=>{delete m[s]},...m};let I=!1,O=!1;typeof window<"u"&&(I=!0,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{O=!0}):O=!0);const h=()=>I,b=()=>O,p=()=>{if(!I)return!1;try{const s="__rwp_test__";return window.localStorage.setItem(s,"test"),window.localStorage.removeItem(s),!0}catch{return!1}},T=s=>{const e=typeof s;return s===null?!0:Array.isArray(s)||e==="object"?!1:e!=="function"},L=Object.freeze(Object.defineProperty({__proto__:null,addKey:v,fakeLocalStorage:_,getHasHydrated:b,getIsClient:h,getKeys:U,getPrefixedKeys:R,isLocalStorageAvailable:p,isPrimitive:T,key:P},Symbol.toStringTag,{value:"Module"}));class w{constructor(e,t){if(new.target===w)throw TypeError("StorageProvider is abstract. Extend this class to implement it");this.namespace=e||null,this.registry=t||{}}setNamespace(e){}register(e,t){this.registry[e]=t}getItem(e){}setItem(e,t){}removeItem(e,t=!1){}getAll(){}_resetAll(e=!0,t=[],r=!1){}resetAll(e=[]){}removeAll(e=[]){}}class F extends w{constructor(e=null,t={}){super(e,t),this.storage=this.getStorage(),this._isUsingFakeStorage=!p()}getStorage(){return p()?window.localStorage:_}setNamespace(e){if(!this.namespace){this.namespace=e;return}if(this.namespace===e)return;const t=JSON.parse(JSON.stringify(this.getAll()));this.removeAll();for(const[r,o]of Object.entries(t)){const i=r.replace(this.namespace,e);this.setItem(i,o)}this.namespace=e}getItem(e){const t=this.storage.getItem(e);if(t==null)return null;try{return JSON.parse(t)}catch{return t}}setItem(e,t){let r=t;return r!=null&&(r=T(t)?t:JSON.stringify(t)),this.storage.setItem(e,r)}removeItem(e,t=!1){return t&&delete this.registry[e],this.storage.removeItem(e)}getAll(){const e=`${this.namespace}.`;return Object.keys(this.storage).reduce((t,r)=>((!this.namespace||r.startsWith(e))&&(t[r]=this.storage.getItem(r)),t),{})}_resetAll(e=!0,t=[],r=!1){const o=`${this.namespace}.`;Object.keys(this.storage).forEach(i=>{const c=this.namespace?i.startsWith(o):!0,g=t?.includes(i)||!1;!c||g||(e?Object.prototype.hasOwnProperty.call(this.registry,i)?this.storage.setItem(i,this.registry[i]):this.storage.removeItem(i):(this.storage.removeItem(i),r&&delete this.registry[i]))})}resetAll(e=[],t=!1){this._resetAll(!0,e||[],t)}removeAll(e=[],t=!1){this._resetAll(!1,e||[],t)}upgradeToRealStorage(){if(!this._isUsingFakeStorage||!p())return!1;const e={...this.storage};return this.storage=window.localStorage,this._isUsingFakeStorage=!1,Object.keys(e).forEach(t=>{if(t!=="__IS_FAKE_LOCAL_STORAGE__"&&e[t]!=null)try{this.storage.setItem(t,e[t])}catch{return this.storage=_,this._isUsingFakeStorage=!0,!1}}),!0}isUsingFakeStorage(){return this._isUsingFakeStorage}}const j={logging:{enabled:!1}};let E=F,a=new E,u={...j},y=[];const K=()=>a.namespace,k=()=>a,H=()=>u,W=s=>{a.setNamespace(s),a=new E(s||K())},V=s=>{if(u={...u,...s},u.logging.enabled)for(console.info("Flushing",y.length,"pending logs");y.length;)console.log(...y.shift())},A=()=>{if(!h())return!1;const s=a.upgradeToRealStorage();return s&&N("react-wire-persisted: Upgraded to real localStorage after hydration"),s},N=(...s)=>{u.logging.enabled?console.log(...s):y.push(s)},$=(s,e=null)=>{if(!s&&typeof s!="number")throw new Error(`createPersistedWire: Key cannot be a falsey value (${s}}`);a.register(s,e);const t=l.createWire(e),r=()=>t.getValue(),o=d=>(a.setItem(s,d),t.setValue(d)),i=d=>{t.subscribe(d)},c=a.getItem(s),g=c===null?e:c;return N("react-wire-persisted: create",s,{value:e,storedValue:c,initialValue:g}),g!==e&&o(g),{...t,getValue:r,setValue:o,subscribe:i}},C=(s={})=>{const{autoUpgrade:e=!0,onUpgrade:t}=s,r=f.useRef(!1);return f.useEffect(()=>{if(!e||r.current||!h())return;const o=()=>{b()&&!r.current&&A()&&(r.current=!0,t?.())};o();const i=setTimeout(o,0);return()=>clearTimeout(i)},[e,t]),{hasUpgraded:r.current}};function J({children:s,onUpgrade:e,autoUpgrade:t=!0}){const r=f.useRef(!1);return f.useEffect(()=>{if(!t||r.current||!h())return;const o=()=>{b()&&!r.current&&A()&&(r.current=!0,e?.())};o();const i=setTimeout(o,0);return()=>clearTimeout(i)},[t,e]),s}n.HydrationProvider=J,n.createPersistedWire=$,n.defaultOptions=j,n.getNamespace=K,n.getOptions=H,n.getStorage=k,n.setNamespace=W,n.setOptions=V,n.upgradeStorage=A,n.useHydration=C,n.utils=L,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
package/nextjs.js ADDED
@@ -0,0 +1,40 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+
5
+ /**
6
+ * Simple Next.js App Router compatible component for react-wire-persisted
7
+ * This component handles the localStorage hydration issue in Next.js
8
+ *
9
+ * Usage:
10
+ * import { NextJSHydrationProvider } from 'react-wire-persisted/nextjs'
11
+ *
12
+ * <NextJSHydrationProvider>
13
+ * <YourApp />
14
+ * </NextJSHydrationProvider>
15
+ */
16
+ export function NextJSHydrationProvider({ children }) {
17
+ const [isHydrated, setIsHydrated] = useState(false)
18
+
19
+ useEffect(() => {
20
+ // Mark as hydrated and try to upgrade storage
21
+ setIsHydrated(true)
22
+
23
+ // Dynamically import and upgrade storage after hydration
24
+ if (typeof window !== 'undefined') {
25
+ import('./dist/react-wire-persisted.js')
26
+ .then(module => {
27
+ // Attempt to upgrade storage
28
+ if (module.upgradeStorage) {
29
+ module.upgradeStorage()
30
+ }
31
+ })
32
+ .catch(err => {
33
+ console.warn('Failed to upgrade react-wire-persisted storage:', err)
34
+ })
35
+ }
36
+ }, [])
37
+
38
+ // Render children immediately, but storage upgrade happens after hydration
39
+ return children
40
+ }
package/package.json CHANGED
@@ -1,54 +1,66 @@
1
1
  {
2
2
  "name": "react-wire-persisted",
3
- "version": "1.5.0",
3
+ "version": "2.0.1",
4
4
  "author": "Wesley Bliss <wesley.bliss@gmail.com>",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/react-wire-persisted.umd.cjs",
8
- "module": "./dist/react-wire-persisted.mjs",
8
+ "module": "./dist/index.js",
9
9
  "files": [
10
10
  "src",
11
- "dist"
11
+ "dist",
12
+ "client.js",
13
+ "client.mjs",
14
+ "nextjs.js"
12
15
  ],
13
16
  "exports": {
14
17
  ".": {
15
- "import": "./dist/react-wire-persisted.mjs",
18
+ "import": "./dist/react-wire-persisted.js",
16
19
  "require": "./dist/react-wire-persisted.umd.cjs"
20
+ },
21
+ "./client": {
22
+ "import": "./client.mjs",
23
+ "require": "./client.js"
24
+ },
25
+ "./nextjs": {
26
+ "import": "./nextjs.js",
27
+ "require": "./nextjs.js"
17
28
  }
18
29
  },
19
30
  "scripts": {
20
31
  "dev": "vite --host --config config/vite.config.development.js",
21
- "build": "vite build --config config/vite.config.production.js",
32
+ "build": "vite build --config config/vite.config.production.js && cp dist/react-wire-persisted.js dist/index.js",
22
33
  "test:unit": "NODE_ENV=test vitest run",
23
34
  "test:unit:only": "NODE_ENV=test vitest run -t ",
24
- "test:unit:coverage": "NODE_ENV=test vitest run --no-color --reporter=junit --coverage --outputFile=coverage/report.xml"
35
+ "test:unit:coverage": "NODE_ENV=test vitest run --no-color --reporter=junit --coverage --outputFile=coverage/report.xml",
36
+ "yalc": "pnpm build && yalc push"
25
37
  },
26
38
  "devDependencies": {
27
39
  "@forminator/react-wire": "^0.7.0",
28
- "@testing-library/jest-dom": "^6.6.4",
40
+ "@testing-library/dom": "^10.4.1",
41
+ "@testing-library/jest-dom": "^6.8.0",
29
42
  "@testing-library/react": "^16.3.0",
30
43
  "@testing-library/user-event": "^14.6.1",
31
- "@vitejs/plugin-react": "^4.7.0",
32
- "@vitest/coverage-c8": "^0.33.0",
33
- "browserslist": "^4.25.1",
34
- "c8": "^10.1.3",
35
- "dotenv": "^17.2.1",
36
- "esbuild": "^0.25.8",
37
- "jest": "^30.0.5",
38
- "jest-environment-jsdom": "^30.0.5",
44
+ "@vitejs/plugin-react": "^5.0.3",
45
+ "@vitest/coverage-v8": "^4.0.18",
46
+ "browserslist": "^4.26.2",
47
+ "dotenv": "^17.2.2",
48
+ "esbuild": "^0.25.10",
49
+ "jest": "^30.1.3",
50
+ "jest-environment-jsdom": "^30.1.2",
39
51
  "np": "^10.2.0",
40
52
  "react": "^19.1.1",
41
53
  "react-dom": "^19.1.1",
42
54
  "rollup-plugin-inject-process-env": "^1.3.1",
43
55
  "snapshot-diff": "^0.10.0",
44
- "vite": "^7.0.6",
56
+ "vite": "^7.1.7",
45
57
  "vite-jest": "^0.1.4",
46
- "vitest": "^3.2.4"
58
+ "vitest": "^4.0.18"
47
59
  },
48
60
  "peerDependencies": {
49
- "@forminator/react-wire": "^0.5.0-alpha.1",
50
- "react": "^17.0.2",
51
- "react-dom": "^17.0.2"
61
+ "@forminator/react-wire": "^0.7.0",
62
+ "react": "^18.3.1 || ^19.0.0",
63
+ "react-dom": "^18.3.1 || ^19.0.0"
52
64
  },
53
65
  "browserslist": {
54
66
  "production": [
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef } from 'react'
4
+ import { upgradeStorage } from '../react-wire-persisted'
5
+ import { getIsClient, getHasHydrated } from '../utils'
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
+ }
@@ -0,0 +1,17 @@
1
+ 'use client'
2
+
3
+ import { useHydration } from '../hooks/useHydration'
4
+
5
+ /**
6
+ * A Next.js App Router compatible component that handles automatic storage upgrade
7
+ * after hydration. Use this in your root layout.
8
+ *
9
+ * @param {Object} props Component props
10
+ * @param {React.ReactNode} props.children Child components to render
11
+ * @param {Function} props.onUpgrade Callback called when storage is upgraded
12
+ * @param {Boolean} props.autoUpgrade Whether to automatically upgrade storage (default: true)
13
+ */
14
+ export function HydrationProvider({ children, onUpgrade, autoUpgrade = true }) {
15
+ useHydration({ onUpgrade, autoUpgrade })
16
+ return children
17
+ }
@@ -0,0 +1 @@
1
+ export { HydrationProvider } from './HydrationProvider.js'
@@ -0,0 +1,58 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef } from 'react'
4
+ import { upgradeStorage } from '../react-wire-persisted'
5
+ import { getIsClient, getHasHydrated } from '../utils'
6
+
7
+ /**
8
+ * React hook that handles automatic storage upgrade after hydration
9
+ * This should be used in the root component of your application
10
+ *
11
+ * @param {Object} options Configuration options
12
+ * @param {Boolean} options.autoUpgrade Whether to automatically upgrade storage (default: true)
13
+ * @param {Function} options.onUpgrade Callback called when storage is upgraded
14
+ */
15
+ export const useHydration = (options = {}) => {
16
+
17
+ const {
18
+ autoUpgrade = true,
19
+ onUpgrade
20
+ } = options
21
+
22
+ const hasUpgraded = useRef(false)
23
+
24
+ useEffect(() => {
25
+
26
+ if (!autoUpgrade || hasUpgraded.current || !getIsClient())
27
+ return
28
+
29
+ const attemptUpgrade = () => {
30
+
31
+ if (getHasHydrated() && !hasUpgraded.current) {
32
+
33
+ const upgraded = upgradeStorage()
34
+
35
+ if (upgraded) {
36
+ hasUpgraded.current = true
37
+ onUpgrade?.()
38
+ }
39
+
40
+ }
41
+
42
+ }
43
+
44
+ // Try to upgrade immediately if already hydrated
45
+ attemptUpgrade()
46
+
47
+ // Also try after a short delay to ensure DOM is ready
48
+ const timeoutId = setTimeout(attemptUpgrade, 0)
49
+
50
+ return () => clearTimeout(timeoutId)
51
+
52
+ }, [autoUpgrade, onUpgrade])
53
+
54
+ return {
55
+ hasUpgraded: hasUpgraded.current
56
+ }
57
+
58
+ }
package/src/index.js CHANGED
@@ -2,3 +2,5 @@ import * as utils from './utils'
2
2
 
3
3
  export { utils }
4
4
  export * from './react-wire-persisted'
5
+ export * from './hooks/useHydration'
6
+ export * from './components'
@@ -1,5 +1,5 @@
1
1
  import StorageProvider from './StorageProvider'
2
- import { fakeLocalStorage, isPrimitive } from '../utils'
2
+ import { fakeLocalStorage, isPrimitive, isLocalStorageAvailable } from '../utils'
3
3
 
4
4
  /**
5
5
  * A storage provider for `localStorage`
@@ -8,24 +8,24 @@ import { fakeLocalStorage, isPrimitive } from '../utils'
8
8
  class LocalStorageProvider extends StorageProvider {
9
9
 
10
10
  constructor(namespace = null, registry = {}) {
11
-
11
+
12
12
  super(namespace, registry)
13
-
13
+
14
14
  this.storage = this.getStorage()
15
-
15
+ this._isUsingFakeStorage = !isLocalStorageAvailable()
16
+
16
17
  }
17
18
 
18
19
  getStorage() {
19
-
20
- try {
20
+
21
+ // Use the isomorphic utility to check localStorage availability
22
+ if (isLocalStorageAvailable()) {
21
23
  return window.localStorage
22
- } catch (e) {
23
- /* istanbul ignore next */
24
- console.warn(new Error('LocalStorageProvider: localStorage not supported'))
25
- /* istanbul ignore next */
26
- return fakeLocalStorage
27
24
  }
28
-
25
+
26
+ // Fallback to fake localStorage for SSR or when localStorage is disabled
27
+ return fakeLocalStorage
28
+
29
29
  }
30
30
 
31
31
  setNamespace(namespace) {
@@ -107,36 +107,36 @@ class LocalStorageProvider extends StorageProvider {
107
107
  excludedKeys = [],
108
108
  clearRegistry = false
109
109
  ) {
110
-
110
+
111
111
  const prefixNs = `${this.namespace}.`
112
-
113
- Object.keys(localStorage).forEach(it => {
114
-
112
+
113
+ Object.keys(this.storage).forEach(it => {
114
+
115
115
  const isAppKey = this.namespace ? it.startsWith(prefixNs) : true
116
116
  const isExcluded = excludedKeys?.includes(it) || false
117
-
117
+
118
118
  if (!isAppKey || isExcluded) return
119
-
119
+
120
120
  if (useInitialValues) {
121
-
121
+
122
122
  const isRegistered = Object.prototype.hasOwnProperty.call(this.registry, it)
123
-
123
+
124
124
  if (isRegistered)
125
125
  this.storage.setItem(it, this.registry[it])
126
126
  else
127
127
  this.storage.removeItem(it)
128
-
128
+
129
129
  } else {
130
-
130
+
131
131
  this.storage.removeItem(it)
132
-
132
+
133
133
  if (clearRegistry)
134
134
  delete this.registry[it]
135
-
135
+
136
136
  }
137
-
137
+
138
138
  })
139
-
139
+
140
140
  }
141
141
 
142
142
  resetAll(excludedKeys = [], clearRegistry = false) {
@@ -146,7 +146,49 @@ class LocalStorageProvider extends StorageProvider {
146
146
  removeAll(excludedKeys = [], clearRegistry = false) {
147
147
  this._resetAll(false, excludedKeys || [], clearRegistry)
148
148
  }
149
-
149
+
150
+ /**
151
+ * Attempt to upgrade from fake storage to real localStorage
152
+ * This is useful for hydration scenarios
153
+ */
154
+ upgradeToRealStorage() {
155
+
156
+ if (!this._isUsingFakeStorage) {
157
+ return false // Already using real storage
158
+ }
159
+
160
+ if (!isLocalStorageAvailable()) {
161
+ return false // Real storage still not available
162
+ }
163
+
164
+ const fakeData = { ...this.storage }
165
+ this.storage = window.localStorage
166
+ this._isUsingFakeStorage = false
167
+
168
+ // Migrate data from fake storage to real storage
169
+ Object.keys(fakeData).forEach(key => {
170
+ if (key !== '__IS_FAKE_LOCAL_STORAGE__' && fakeData[key] != null) {
171
+ try {
172
+ this.storage.setItem(key, fakeData[key])
173
+ } catch (e) {
174
+ // If we can't write to localStorage, revert to fake storage
175
+ this.storage = fakeLocalStorage
176
+ this._isUsingFakeStorage = true
177
+ return false
178
+ }
179
+ }
180
+ })
181
+
182
+ return true
183
+ }
184
+
185
+ /**
186
+ * Check if currently using fake storage
187
+ */
188
+ isUsingFakeStorage() {
189
+ return this._isUsingFakeStorage
190
+ }
191
+
150
192
  }
151
193
 
152
194
  export default LocalStorageProvider
@@ -1,5 +1,6 @@
1
1
  import { createWire } from '@forminator/react-wire'
2
2
  import LocalStorageProvider from './providers/LocalStorageProvider'
3
+ import { getIsClient, getHasHydrated } from './utils'
3
4
 
4
5
  export const defaultOptions = {
5
6
  logging: {
@@ -52,6 +53,26 @@ export const setOptions = value => {
52
53
  }
53
54
  }
54
55
 
56
+ /**
57
+ * Attempts to upgrade the storage provider from fake storage to real localStorage
58
+ * This should be called on the client side after hydration
59
+ *
60
+ * @returns {Boolean} True if upgrade was successful
61
+ */
62
+ export const upgradeStorage = () => {
63
+ if (!getIsClient()) {
64
+ return false
65
+ }
66
+
67
+ const upgraded = storage.upgradeToRealStorage()
68
+
69
+ if (upgraded) {
70
+ log('react-wire-persisted: Upgraded to real localStorage after hydration')
71
+ }
72
+
73
+ return upgraded
74
+ }
75
+
55
76
  const log = (...args) => {
56
77
  /* istanbul ignore next */
57
78
  if (options.logging.enabled)
@@ -4,11 +4,13 @@ const storage = {
4
4
  }
5
5
 
6
6
  export const fakeLocalStorage = {
7
- getItem: key => fakeLocalStorage[key],
7
+ getItem: key => storage[key],
8
8
  setItem: (key, value) => {
9
- fakeLocalStorage[key] = value
9
+ storage[key] = value
10
10
  },
11
11
  removeItem: key => {
12
- delete fakeLocalStorage[key]
13
- }
12
+ delete storage[key]
13
+ },
14
+ // Make Object.keys() work properly for _resetAll method
15
+ ...storage
14
16
  }
@@ -1,5 +1,6 @@
1
1
  export * from './keys'
2
2
  export * from './fakeLocalStorage'
3
+ export * from './isomorphic'
3
4
 
4
5
  /**
5
6
  * Checks if a value is a primitive type
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Utilities for handling server-side rendering and client-side hydration
3
+ */
4
+
5
+ let isClient = false
6
+ let hasHydrated = false
7
+
8
+ // Detect if we're running in a browser environment
9
+ if (typeof window !== 'undefined') {
10
+ isClient = true
11
+
12
+ // Mark as hydrated when the DOM is ready
13
+ if (document.readyState === 'loading') {
14
+ document.addEventListener('DOMContentLoaded', () => {
15
+ hasHydrated = true
16
+ })
17
+ } else {
18
+ hasHydrated = true
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Check if we're running in a browser environment
24
+ */
25
+ export const getIsClient = () => isClient
26
+
27
+ /**
28
+ * Check if the client has finished hydrating
29
+ */
30
+ export const getHasHydrated = () => hasHydrated
31
+
32
+ /**
33
+ * Check if localStorage is available and safe to use
34
+ */
35
+ export const isLocalStorageAvailable = () => {
36
+ if (!isClient) return false
37
+
38
+ try {
39
+ const testKey = '__rwp_test__'
40
+ window.localStorage.setItem(testKey, 'test')
41
+ window.localStorage.removeItem(testKey)
42
+ return true
43
+ } catch (e) {
44
+ return false
45
+ }
46
+ }
@@ -1 +0,0 @@
1
- (function(n,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("@forminator/react-wire")):typeof define=="function"&&define.amd?define(["exports","@forminator/react-wire"],a):(n=typeof globalThis<"u"?globalThis:n||self,a(n["react-wire-persisted"]={},n.reactWire))})(this,function(n,a){"use strict";(function(){const s={};try{if(process){process.env=Object.assign({},process.env),Object.assign(process.env,s);return}}catch{}globalThis.process={env:s}})();const h={},y=s=>{h[s]=s},w=s=>y(s),v=()=>h,A=(s,e=null)=>{const t=e||h;return s?Object.keys(t).reduce((r,o)=>({...r,[o]:`${s}.${t[o]}`}),{}):t},c={getItem:s=>c[s],setItem:(s,e)=>{c[s]=e},removeItem:s=>{delete c[s]}},b=s=>{const e=typeof s;return s===null?!0:Array.isArray(s)||e==="object"?!1:e!=="function"},N=Object.freeze(Object.defineProperty({__proto__:null,addKey:y,fakeLocalStorage:c,getKeys:v,getPrefixedKeys:A,isPrimitive:b,key:w},Symbol.toStringTag,{value:"Module"}));class p{constructor(e,t){if(new.target===p)throw TypeError("StorageProvider is abstract. Extend this class to implement it");this.namespace=e||null,this.registry=t||{}}setNamespace(e){}register(e,t){this.registry[e]=t}getItem(e){}setItem(e,t){}removeItem(e,t=!1){}getAll(){}_resetAll(e=!0,t=[],r=!1){}resetAll(e=[]){}removeAll(e=[]){}}class j extends p{constructor(e=null,t={}){super(e,t),this.storage=this.getStorage()}getStorage(){try{return window.localStorage}catch{return console.warn(new Error("LocalStorageProvider: localStorage not supported")),c}}setNamespace(e){if(!this.namespace){this.namespace=e;return}if(this.namespace===e)return;const t=JSON.parse(JSON.stringify(this.getAll()));this.removeAll();for(const[r,o]of Object.entries(t)){const i=r.replace(this.namespace,e);this.setItem(i,o)}this.namespace=e}getItem(e){const t=this.storage.getItem(e);if(t==null)return null;try{return JSON.parse(t)}catch{return t}}setItem(e,t){let r=t;return r!=null&&(r=b(t)?t:JSON.stringify(t)),this.storage.setItem(e,r)}removeItem(e,t=!1){return t&&delete this.registry[e],this.storage.removeItem(e)}getAll(){const e=`${this.namespace}.`;return Object.keys(this.storage).reduce((t,r)=>((!this.namespace||r.startsWith(e))&&(t[r]=this.storage.getItem(r)),t),{})}_resetAll(e=!0,t=[],r=!1){const o=`${this.namespace}.`;Object.keys(localStorage).forEach(i=>{const u=this.namespace?i.startsWith(o):!0,f=t?.includes(i)||!1;!u||f||(e?Object.prototype.hasOwnProperty.call(this.registry,i)?this.storage.setItem(i,this.registry[i]):this.storage.removeItem(i):(this.storage.removeItem(i),r&&delete this.registry[i]))})}resetAll(e=[],t=!1){this._resetAll(!0,e||[],t)}removeAll(e=[],t=!1){this._resetAll(!1,e||[],t)}}const O={logging:{enabled:!1}};let I=j,l=new I,g={...O},m=[];const S=()=>l.namespace,P=()=>l,K=()=>g,W=s=>{l.setNamespace(s),l=new I(s||S())},_=s=>{if(g={...g,...s},g.logging.enabled)for(console.info("Flushing",m.length,"pending logs");m.length;)console.log(...m.shift())},E=(...s)=>{g.logging.enabled?console.log(...s):m.push(s)},T=(s,e=null)=>{if(!s&&typeof s!="number")throw new Error(`createPersistedWire: Key cannot be a falsey value (${s}}`);l.register(s,e);const t=a.createWire(e),r=()=>t.getValue(),o=d=>(l.setItem(s,d),t.setValue(d)),i=d=>{t.subscribe(d)},u=l.getItem(s),f=u===null?e:u;return E("react-wire-persisted: create",s,{value:e,storedValue:u,initialValue:f}),f!==e&&o(f),{...t,getValue:r,setValue:o,subscribe:i}};n.createPersistedWire=T,n.defaultOptions=O,n.getNamespace=S,n.getOptions=K,n.getStorage=P,n.setNamespace=W,n.setOptions=_,n.utils=N,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})});