use-storage-persisted-state 1.0.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/LICENSE +22 -0
- package/README.md +235 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +478 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +446 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
BooleanCodec: () => BooleanCodec,
|
|
26
|
+
JsonCodec: () => JsonCodec,
|
|
27
|
+
NumberCodec: () => NumberCodec,
|
|
28
|
+
StringCodec: () => StringCodec,
|
|
29
|
+
inferCodec: () => inferCodec,
|
|
30
|
+
readStoragePersistedState: () => readStoragePersistedState,
|
|
31
|
+
setStoragePersistedState: () => setStoragePersistedState,
|
|
32
|
+
useStoragePersistedState: () => useStoragePersistedState
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/useStoragePersistedState.ts
|
|
37
|
+
var import_react = require("react");
|
|
38
|
+
var import_shim = require("use-sync-external-store/shim");
|
|
39
|
+
|
|
40
|
+
// src/codecs.ts
|
|
41
|
+
var JsonCodec = {
|
|
42
|
+
encode: (value) => {
|
|
43
|
+
if (value === null) return null;
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
},
|
|
46
|
+
decode: (value) => {
|
|
47
|
+
if (value === null) return null;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(value);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.warn(`LocalStorage parse error for value "${value}".`, e);
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var StringCodec = {
|
|
57
|
+
encode: (value) => value ?? null,
|
|
58
|
+
decode: (value) => value ?? null
|
|
59
|
+
};
|
|
60
|
+
var BooleanCodec = {
|
|
61
|
+
encode: (value) => value ? "true" : "false",
|
|
62
|
+
decode: (value) => value === "true"
|
|
63
|
+
};
|
|
64
|
+
var NumberCodec = {
|
|
65
|
+
encode: (value) => {
|
|
66
|
+
if (value === null) return null;
|
|
67
|
+
return String(value);
|
|
68
|
+
},
|
|
69
|
+
decode: (value) => {
|
|
70
|
+
if (value === null || value === "") return null;
|
|
71
|
+
if (value === "NaN") return NaN;
|
|
72
|
+
if (value === "Infinity") return Infinity;
|
|
73
|
+
if (value === "-Infinity") return -Infinity;
|
|
74
|
+
const parsed = Number(value);
|
|
75
|
+
return isNaN(parsed) ? null : parsed;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function inferCodec(defaultValue) {
|
|
79
|
+
const type = typeof defaultValue;
|
|
80
|
+
if (type === "boolean") return BooleanCodec;
|
|
81
|
+
if (type === "number") return NumberCodec;
|
|
82
|
+
if (type === "string") return StringCodec;
|
|
83
|
+
return JsonCodec;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/storage.ts
|
|
87
|
+
var MemoryStorageAdapter = class {
|
|
88
|
+
constructor() {
|
|
89
|
+
__publicField(this, "store", /* @__PURE__ */ new Map());
|
|
90
|
+
}
|
|
91
|
+
getItem(key) {
|
|
92
|
+
return this.store.has(key) ? this.store.get(key) : null;
|
|
93
|
+
}
|
|
94
|
+
setItem(key, value) {
|
|
95
|
+
this.store.set(key, value);
|
|
96
|
+
}
|
|
97
|
+
removeItem(key) {
|
|
98
|
+
this.store.delete(key);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var FallbackStorageAdapter = class {
|
|
102
|
+
constructor(primary, fallback) {
|
|
103
|
+
this.primary = primary;
|
|
104
|
+
this.fallback = fallback;
|
|
105
|
+
}
|
|
106
|
+
getItem(key) {
|
|
107
|
+
const fallbackValue = this.fallback.getItem(key);
|
|
108
|
+
if (fallbackValue !== null) return fallbackValue;
|
|
109
|
+
return this.primary.getItem(key);
|
|
110
|
+
}
|
|
111
|
+
setItem(key, value) {
|
|
112
|
+
try {
|
|
113
|
+
this.primary.setItem(key, value);
|
|
114
|
+
this.fallback.removeItem(key);
|
|
115
|
+
} catch {
|
|
116
|
+
this.fallback.setItem(key, value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
removeItem(key) {
|
|
120
|
+
try {
|
|
121
|
+
this.primary.removeItem(key);
|
|
122
|
+
} finally {
|
|
123
|
+
this.fallback.removeItem(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var StorageSyncManager = class {
|
|
128
|
+
constructor(storage, defaultPollingIntervalMs = 2e3, options = {}) {
|
|
129
|
+
this.storage = storage;
|
|
130
|
+
this.defaultPollingIntervalMs = defaultPollingIntervalMs;
|
|
131
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Map());
|
|
132
|
+
__publicField(this, "pollingIntervalId", null);
|
|
133
|
+
__publicField(this, "pollingIntervalMsActive", null);
|
|
134
|
+
__publicField(this, "enableCrossTabSync");
|
|
135
|
+
__publicField(this, "enablePolling");
|
|
136
|
+
// Keep a cache of values to detect changes during polling
|
|
137
|
+
__publicField(this, "snapshotCache", /* @__PURE__ */ new Map());
|
|
138
|
+
this.enableCrossTabSync = options.enableCrossTabSync ?? true;
|
|
139
|
+
this.enablePolling = options.enablePolling ?? true;
|
|
140
|
+
if (typeof window !== "undefined" && this.enableCrossTabSync) {
|
|
141
|
+
window.addEventListener("storage", (e) => {
|
|
142
|
+
if (e.key && this.listeners.has(e.key)) {
|
|
143
|
+
this.notifyCrossTab(e.key);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
subscribe(key, callback, options = {}) {
|
|
149
|
+
if (!this.listeners.has(key)) {
|
|
150
|
+
this.listeners.set(key, /* @__PURE__ */ new Map());
|
|
151
|
+
}
|
|
152
|
+
const listenerOptions = {
|
|
153
|
+
crossTabSync: this.enableCrossTabSync && (options.crossTabSync ?? true),
|
|
154
|
+
pollingIntervalMs: this.enablePolling ? this.normalizePollingInterval(options.pollingIntervalMs) : null
|
|
155
|
+
};
|
|
156
|
+
this.listeners.get(key).set(callback, listenerOptions);
|
|
157
|
+
this.updatePollingInterval();
|
|
158
|
+
return () => {
|
|
159
|
+
const listenersForKey = this.listeners.get(key);
|
|
160
|
+
listenersForKey?.delete(callback);
|
|
161
|
+
if (listenersForKey?.size === 0) {
|
|
162
|
+
this.listeners.delete(key);
|
|
163
|
+
}
|
|
164
|
+
this.updatePollingInterval();
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Called when WE change the value in this tab
|
|
168
|
+
notify(key) {
|
|
169
|
+
this.notifyListeners(key);
|
|
170
|
+
}
|
|
171
|
+
notifyListeners(key, predicate) {
|
|
172
|
+
this.listeners.get(key)?.forEach((options, cb) => {
|
|
173
|
+
if (!predicate || predicate(options)) {
|
|
174
|
+
cb();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
notifyCrossTab(key) {
|
|
179
|
+
this.notifyListeners(key, (options) => options.crossTabSync);
|
|
180
|
+
}
|
|
181
|
+
notifyPolling(key) {
|
|
182
|
+
this.notifyListeners(key, (options) => options.pollingIntervalMs !== null);
|
|
183
|
+
}
|
|
184
|
+
normalizePollingInterval(pollingIntervalMs) {
|
|
185
|
+
if (pollingIntervalMs === null) return null;
|
|
186
|
+
if (pollingIntervalMs === void 0) return this.defaultPollingIntervalMs;
|
|
187
|
+
if (pollingIntervalMs <= 0 || Number.isNaN(pollingIntervalMs)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return pollingIntervalMs;
|
|
191
|
+
}
|
|
192
|
+
getMinPollingIntervalMs() {
|
|
193
|
+
let minInterval = null;
|
|
194
|
+
this.listeners.forEach((listenersForKey) => {
|
|
195
|
+
listenersForKey.forEach((options) => {
|
|
196
|
+
if (options.pollingIntervalMs === null) return;
|
|
197
|
+
if (minInterval === null || options.pollingIntervalMs < minInterval) {
|
|
198
|
+
minInterval = options.pollingIntervalMs;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
return minInterval;
|
|
203
|
+
}
|
|
204
|
+
updatePollingInterval() {
|
|
205
|
+
if (typeof window === "undefined") return;
|
|
206
|
+
if (!this.enablePolling) return;
|
|
207
|
+
const nextInterval = this.getMinPollingIntervalMs();
|
|
208
|
+
if (nextInterval === null) {
|
|
209
|
+
if (this.pollingIntervalId) {
|
|
210
|
+
clearInterval(this.pollingIntervalId);
|
|
211
|
+
}
|
|
212
|
+
this.pollingIntervalId = null;
|
|
213
|
+
this.pollingIntervalMsActive = null;
|
|
214
|
+
this.snapshotCache.clear();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (this.pollingIntervalId && this.pollingIntervalMsActive === nextInterval) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (this.pollingIntervalId) {
|
|
221
|
+
clearInterval(this.pollingIntervalId);
|
|
222
|
+
}
|
|
223
|
+
this.pollingIntervalId = window.setInterval(() => {
|
|
224
|
+
this.listeners.forEach((listenersForKey, key) => {
|
|
225
|
+
const shouldPoll = Array.from(listenersForKey.values()).some(
|
|
226
|
+
(options) => options.pollingIntervalMs !== null
|
|
227
|
+
);
|
|
228
|
+
if (!shouldPoll) return;
|
|
229
|
+
const currentValue = this.storage.getItem(key);
|
|
230
|
+
const lastValue = this.snapshotCache.get(key);
|
|
231
|
+
if (currentValue !== lastValue) {
|
|
232
|
+
this.snapshotCache.set(key, currentValue);
|
|
233
|
+
this.notifyPolling(key);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}, nextInterval);
|
|
237
|
+
this.pollingIntervalMsActive = nextInterval;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var localStorageFallback = new MemoryStorageAdapter();
|
|
241
|
+
var sessionStorageFallback = new MemoryStorageAdapter();
|
|
242
|
+
var _localStorageSync = null;
|
|
243
|
+
var _sessionStorageSync = null;
|
|
244
|
+
var _memoryStorageSync = null;
|
|
245
|
+
function getLocalStorageSync() {
|
|
246
|
+
if (!_localStorageSync) {
|
|
247
|
+
if (typeof window !== "undefined") {
|
|
248
|
+
_localStorageSync = new StorageSyncManager(
|
|
249
|
+
new FallbackStorageAdapter(window.localStorage, localStorageFallback)
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
return getMemoryStorageSync();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return _localStorageSync;
|
|
256
|
+
}
|
|
257
|
+
function getSessionStorageSync() {
|
|
258
|
+
if (!_sessionStorageSync) {
|
|
259
|
+
if (typeof window !== "undefined") {
|
|
260
|
+
_sessionStorageSync = new StorageSyncManager(
|
|
261
|
+
new FallbackStorageAdapter(
|
|
262
|
+
window.sessionStorage,
|
|
263
|
+
sessionStorageFallback
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
} else {
|
|
267
|
+
return getMemoryStorageSync();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return _sessionStorageSync;
|
|
271
|
+
}
|
|
272
|
+
function getMemoryStorageSync() {
|
|
273
|
+
if (!_memoryStorageSync) {
|
|
274
|
+
_memoryStorageSync = new StorageSyncManager(
|
|
275
|
+
new MemoryStorageAdapter(),
|
|
276
|
+
2e3,
|
|
277
|
+
{
|
|
278
|
+
enableCrossTabSync: false,
|
|
279
|
+
enablePolling: false
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return _memoryStorageSync;
|
|
284
|
+
}
|
|
285
|
+
var localStorageSync = {
|
|
286
|
+
get storage() {
|
|
287
|
+
return getLocalStorageSync().storage;
|
|
288
|
+
},
|
|
289
|
+
subscribe(key, callback, options) {
|
|
290
|
+
return getLocalStorageSync().subscribe(key, callback, options);
|
|
291
|
+
},
|
|
292
|
+
notify(key) {
|
|
293
|
+
return getLocalStorageSync().notify(key);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var sessionStorageSync = {
|
|
297
|
+
get storage() {
|
|
298
|
+
return getSessionStorageSync().storage;
|
|
299
|
+
},
|
|
300
|
+
subscribe(key, callback, options) {
|
|
301
|
+
return getSessionStorageSync().subscribe(key, callback, options);
|
|
302
|
+
},
|
|
303
|
+
notify(key) {
|
|
304
|
+
return getSessionStorageSync().notify(key);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
var memoryStorageSync = {
|
|
308
|
+
get storage() {
|
|
309
|
+
return getMemoryStorageSync().storage;
|
|
310
|
+
},
|
|
311
|
+
subscribe(key, callback, options) {
|
|
312
|
+
return getMemoryStorageSync().subscribe(key, callback, options);
|
|
313
|
+
},
|
|
314
|
+
notify(key) {
|
|
315
|
+
return getMemoryStorageSync().notify(key);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// src/useStoragePersistedState.ts
|
|
320
|
+
function useStoragePersistedState(key, defaultValue, options = {}) {
|
|
321
|
+
const syncManager = options.storageType === "sessionStorage" ? sessionStorageSync : options.storageType === "memory" ? memoryStorageSync : localStorageSync;
|
|
322
|
+
const adapter = syncManager.storage;
|
|
323
|
+
const codec = (0, import_react.useMemo)(() => {
|
|
324
|
+
if (options?.codec) return options.codec;
|
|
325
|
+
return inferCodec(defaultValue);
|
|
326
|
+
}, [defaultValue, options?.codec]);
|
|
327
|
+
if ((defaultValue === void 0 || defaultValue === null) && !options.codec) {
|
|
328
|
+
console.warn(
|
|
329
|
+
`useStorage: Key "${key}" uses undefined or null default without explicit Codec. defaulting to JSON.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
const lastRaw = (0, import_react.useRef)(null);
|
|
333
|
+
const lastParsed = (0, import_react.useRef)(void 0);
|
|
334
|
+
const getSnapshot = (0, import_react.useCallback)(() => {
|
|
335
|
+
const raw = adapter.getItem(key);
|
|
336
|
+
if (raw === null && defaultValue !== void 0) {
|
|
337
|
+
return defaultValue;
|
|
338
|
+
}
|
|
339
|
+
if (raw === lastRaw.current) {
|
|
340
|
+
if (lastParsed.current === void 0) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
return lastParsed.current;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const decoded = codec.decode(raw);
|
|
347
|
+
lastRaw.current = raw;
|
|
348
|
+
lastParsed.current = decoded;
|
|
349
|
+
return decoded;
|
|
350
|
+
} catch (e) {
|
|
351
|
+
console.error(`Error parsing storage key "${key}"`, e);
|
|
352
|
+
return defaultValue;
|
|
353
|
+
}
|
|
354
|
+
}, [adapter, key, codec, defaultValue]);
|
|
355
|
+
const value = (0, import_shim.useSyncExternalStore)(
|
|
356
|
+
(callback) => syncManager.subscribe(key, callback, {
|
|
357
|
+
crossTabSync: options.crossTabSync,
|
|
358
|
+
pollingIntervalMs: options.pollingIntervalMs
|
|
359
|
+
}),
|
|
360
|
+
getSnapshot,
|
|
361
|
+
() => defaultValue
|
|
362
|
+
// Server Snapshot
|
|
363
|
+
);
|
|
364
|
+
const setValue = (0, import_react.useCallback)(
|
|
365
|
+
(newValueOrFn) => {
|
|
366
|
+
try {
|
|
367
|
+
const currentRaw = adapter.getItem(key);
|
|
368
|
+
const current = currentRaw !== null ? codec.decode(currentRaw) : defaultValue;
|
|
369
|
+
const newValue = newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;
|
|
370
|
+
if (newValue === void 0 || newValue === null) {
|
|
371
|
+
lastRaw.current = null;
|
|
372
|
+
lastParsed.current = void 0;
|
|
373
|
+
adapter.removeItem(key);
|
|
374
|
+
} else {
|
|
375
|
+
const encoded = codec.encode(newValue);
|
|
376
|
+
lastRaw.current = encoded;
|
|
377
|
+
lastParsed.current = newValue;
|
|
378
|
+
if (encoded === null || encoded === void 0) {
|
|
379
|
+
adapter.removeItem(key);
|
|
380
|
+
} else {
|
|
381
|
+
adapter.setItem(key, encoded);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
syncManager.notify(key);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error(`Error setting storage key "${key}":`, error);
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
[adapter, key, codec, defaultValue, syncManager]
|
|
390
|
+
);
|
|
391
|
+
const removeItem = (0, import_react.useCallback)(() => {
|
|
392
|
+
try {
|
|
393
|
+
lastRaw.current = null;
|
|
394
|
+
lastParsed.current = void 0;
|
|
395
|
+
adapter.removeItem(key);
|
|
396
|
+
syncManager.notify(key);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error(`Error removing storage key "${key}":`, error);
|
|
399
|
+
}
|
|
400
|
+
}, [adapter, key, syncManager]);
|
|
401
|
+
return [value, setValue, removeItem];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/storagePersistedState.ts
|
|
405
|
+
function getSyncManager(storageType) {
|
|
406
|
+
if (storageType === "sessionStorage") return sessionStorageSync;
|
|
407
|
+
if (storageType === "memory") return memoryStorageSync;
|
|
408
|
+
return localStorageSync;
|
|
409
|
+
}
|
|
410
|
+
function resolveCodec(key, valueHint, options) {
|
|
411
|
+
if (options?.codec) return options.codec;
|
|
412
|
+
if ((valueHint === void 0 || valueHint === null) && !options?.codec) {
|
|
413
|
+
console.warn(
|
|
414
|
+
`storagePersistedState: Key "${key}" uses undefined or null default without explicit Codec. defaulting to JSON.`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return inferCodec(valueHint);
|
|
418
|
+
}
|
|
419
|
+
function readStoragePersistedState(key, defaultValue, options = {}) {
|
|
420
|
+
const syncManager = getSyncManager(options.storageType);
|
|
421
|
+
const adapter = syncManager.storage;
|
|
422
|
+
const codec = resolveCodec(key, defaultValue, options);
|
|
423
|
+
const raw = adapter.getItem(key);
|
|
424
|
+
if (raw === null && defaultValue !== void 0) {
|
|
425
|
+
return defaultValue;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
return codec.decode(raw);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error(`Error parsing storage key "${key}"`, error);
|
|
431
|
+
return defaultValue;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function setStoragePersistedState(key, newValueOrFn, options = {}) {
|
|
435
|
+
const syncManager = getSyncManager(options.storageType);
|
|
436
|
+
const adapter = syncManager.storage;
|
|
437
|
+
let codec;
|
|
438
|
+
if (!(newValueOrFn instanceof Function)) {
|
|
439
|
+
codec = resolveCodec(key, newValueOrFn, options);
|
|
440
|
+
} else {
|
|
441
|
+
if (!options.codec) {
|
|
442
|
+
console.warn(
|
|
443
|
+
`storagePersistedState: Key "${key}" uses functional update without explicit Codec. defaulting to JSON.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
codec = options.codec ?? JsonCodec;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
const currentRaw = adapter.getItem(key);
|
|
450
|
+
const current = currentRaw !== null ? codec.decode(currentRaw) : null;
|
|
451
|
+
const newValue = newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;
|
|
452
|
+
if (newValue === void 0 || newValue === null) {
|
|
453
|
+
adapter.removeItem(key);
|
|
454
|
+
} else {
|
|
455
|
+
const encoded = codec.encode(newValue);
|
|
456
|
+
if (encoded === null || encoded === void 0) {
|
|
457
|
+
adapter.removeItem(key);
|
|
458
|
+
} else {
|
|
459
|
+
adapter.setItem(key, encoded);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
syncManager.notify(key);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
console.error(`Error setting storage key "${key}":`, error);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
468
|
+
0 && (module.exports = {
|
|
469
|
+
BooleanCodec,
|
|
470
|
+
JsonCodec,
|
|
471
|
+
NumberCodec,
|
|
472
|
+
StringCodec,
|
|
473
|
+
inferCodec,
|
|
474
|
+
readStoragePersistedState,
|
|
475
|
+
setStoragePersistedState,
|
|
476
|
+
useStoragePersistedState
|
|
477
|
+
});
|
|
478
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/useStoragePersistedState.ts","../src/codecs.ts","../src/storage.ts","../src/storagePersistedState.ts"],"sourcesContent":["export { useStoragePersistedState } from \"./useStoragePersistedState\";\nexport type {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\nexport type { Codec } from \"./codecs\";\nexport {\n JsonCodec,\n StringCodec,\n BooleanCodec,\n NumberCodec,\n inferCodec,\n} from \"./codecs\";\nexport {\n readStoragePersistedState,\n setStoragePersistedState,\n} from \"./storagePersistedState\";\n","\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\nimport { Codec, inferCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\n\nexport type StorageType = \"localStorage\" | \"sessionStorage\" | \"memory\";\n\n/**\n * Options for the useStoragePersistedState hook\n */\nexport interface StoragePersistedStateOptions<T> {\n /**\n * Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).\n * If not provided, codec is inferred from defaultValue type.\n */\n codec?: Codec<T>;\n /**\n * Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.\n */\n storageType?: StorageType;\n /**\n * Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.\n * defaults to true.\n */\n crossTabSync?: boolean;\n /**\n * Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.\n * defaults to 2000ms.\n */\n pollingIntervalMs?: number | null;\n}\n\n// Overload 1: Default provided, T inferred\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): [T, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Implementation\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager =\n options.storageType === \"sessionStorage\"\n ? sessionStorageSync\n : options.storageType === \"memory\"\n ? memoryStorageSync\n : localStorageSync;\n const adapter = syncManager.storage;\n\n // 1. Determine the codec.\n // If no explicit codec is passed, we try to infer it from defaultValue.\n // If defaultValue is undefined and no codec is passed, we fall back to JSON.\n const codec = useMemo(() => {\n if (options?.codec) return options.codec;\n return inferCodec(defaultValue);\n }, [defaultValue, options?.codec]);\n\n if ((defaultValue === undefined || defaultValue === null) && !options.codec) {\n console.warn(\n `useStorage: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n // Memoize the decoded value to prevent infinite loops in useSyncExternalStore\n // when the codec returns a new object reference (e.g. JSON.parse).\n const lastRaw = useRef<string | null>(null); // string | null, because storage returns null for missing keys\n const lastParsed = useRef<T | undefined>(undefined);\n\n const getSnapshot = useCallback(() => {\n const raw = adapter.getItem(key);\n\n // Return default if storage is missing the key\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n // If raw value matches cache, return cached object.\n if (raw === lastRaw.current) {\n if (lastParsed.current === undefined) {\n return null as T;\n }\n return lastParsed.current as T;\n }\n\n // Decode new raw value\n try {\n const decoded = codec.decode(raw);\n\n lastRaw.current = raw;\n lastParsed.current = decoded;\n return decoded;\n } catch (e) {\n console.error(`Error parsing storage key \"${key}\"`, e);\n return defaultValue as T;\n }\n }, [adapter, key, codec, defaultValue]);\n\n // 2. Subscribe to the external store (Local/SessionStorage + Polling/Events)\n // useSyncExternalStore handles the hydration mismatch automatically by\n // taking a `getServerSnapshot` (returning defaultValue).\n const value = useSyncExternalStore(\n (callback) =>\n syncManager.subscribe(key, callback, {\n crossTabSync: options.crossTabSync,\n pollingIntervalMs: options.pollingIntervalMs,\n }),\n getSnapshot,\n () => defaultValue as T, // Server Snapshot\n );\n\n // 3. Create the Setter\n const setValue = useCallback(\n (newValueOrFn: T | ((prev: T) => T)) => {\n try {\n const currentRaw = adapter.getItem(key);\n const current =\n currentRaw !== null ? codec.decode(currentRaw) : defaultValue;\n\n const newValue =\n newValueOrFn instanceof Function\n ? newValueOrFn(current)\n : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n lastRaw.current = encoded;\n lastParsed.current = newValue;\n\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n // Notify other hooks/tabs\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [adapter, key, codec, defaultValue, syncManager],\n );\n\n const removeItem = useCallback(() => {\n try {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [adapter, key, syncManager]);\n\n return [value, setValue, removeItem] as const;\n}\n","/**\n * Defines how to serialize/deserialize a value from localStorage.\n * null means the key doesn't exist.\n */\nexport interface Codec<T> {\n encode: (value: T) => string | null;\n decode: (value: string | null) => T;\n}\n\n/**\n * A robust JSON codec that handles parsing errors gracefully.\n * Works with objects, arrays, and other JSON-serializable values.\n *\n * @example\n * ```ts\n * const [user, setUser] = useStoragePersistedState<User | null>(\n * \"user\",\n * null,\n * { codec: JsonCodec }\n * );\n * ```\n */\nexport const JsonCodec: Codec<unknown> = {\n encode: (value) => {\n if (value === null) return null;\n return JSON.stringify(value);\n },\n decode: (value) => {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch (e) {\n console.warn(`LocalStorage parse error for value \"${value}\".`, e);\n throw e;\n }\n },\n};\n\n/**\n * Codec for string values. Stores strings as-is without any transformation.\n *\n * @example\n * ```ts\n * const [name, setName] = useStoragePersistedState(\"name\", \"Guest\");\n * // Automatically uses StringCodec because defaultValue is a string\n * ```\n */\nexport const StringCodec: Codec<string | null> = {\n encode: (value) => value ?? null,\n decode: (value) => value ?? null,\n};\n\n/**\n * Codec for boolean values. Stores as \"true\" or \"false\" strings.\n *\n * @example\n * ```ts\n * const [isDark, setIsDark] = useStoragePersistedState(\"darkMode\", false);\n * // Automatically uses BooleanCodec because defaultValue is a boolean\n * ```\n */\nexport const BooleanCodec: Codec<boolean> = {\n encode: (value) => (value ? \"true\" : \"false\"),\n decode: (value) => value === \"true\",\n};\n\n/**\n * Codec for number values. Handles NaN, Infinity, and -Infinity correctly.\n * Returns null for unparseable values (e.g., empty string, invalid number string).\n *\n * @example\n * ```ts\n * const [count, setCount] = useStoragePersistedState(\"count\", 0);\n * // Automatically uses NumberCodec because defaultValue is a number\n * ```\n */\nexport const NumberCodec: Codec<number | null> = {\n encode: (value) => {\n if (value === null) return null;\n return String(value);\n },\n decode: (value) => {\n if (value === null || value === \"\") return null;\n // Handle special numeric values\n if (value === \"NaN\") return NaN;\n if (value === \"Infinity\") return Infinity;\n if (value === \"-Infinity\") return -Infinity;\n const parsed = Number(value);\n return isNaN(parsed) ? null : parsed;\n },\n};\n\n/**\n * Infers the appropriate codec based on the default value's type.\n * Used internally when the user doesn't provide an explicit codec.\n *\n * - `boolean` → BooleanCodec\n * - `number` → NumberCodec\n * - `string` → StringCodec\n * - `object`, `array`, `undefined`, `null` → JsonCodec\n *\n * @param defaultValue - The default value to infer the codec from\n * @returns The inferred codec for the given value type\n */\nexport function inferCodec<T>(defaultValue: T): Codec<T> {\n const type = typeof defaultValue;\n\n // Type assertions through unknown are needed because we're doing runtime type inference\n // The actual codec returned will match the runtime type of defaultValue\n if (type === \"boolean\") return BooleanCodec as unknown as Codec<T>;\n if (type === \"number\") return NumberCodec as unknown as Codec<T>;\n if (type === \"string\") return StringCodec as unknown as Codec<T>;\n\n // Default to JSON for objects, arrays, or undefined\n return JsonCodec as unknown as Codec<T>;\n}\n","\"use client\";\n\n/**\n * Interface allows swapping LocalStorage for SessionStorage or Async Storage later\n */\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface SubscribeOptions {\n crossTabSync?: boolean;\n pollingIntervalMs?: number | null;\n}\n\ninterface ListenerOptions {\n crossTabSync: boolean;\n pollingIntervalMs: number | null;\n}\n\ninterface StorageSyncManagerOptions {\n enableCrossTabSync?: boolean;\n enablePolling?: boolean;\n}\n\nclass MemoryStorageAdapter implements StorageAdapter {\n private store = new Map<string, string>();\n\n getItem(key: string) {\n return this.store.has(key) ? this.store.get(key)! : null;\n }\n\n setItem(key: string, value: string) {\n this.store.set(key, value);\n }\n\n removeItem(key: string) {\n this.store.delete(key);\n }\n}\n\nclass FallbackStorageAdapter implements StorageAdapter {\n constructor(\n private primary: StorageAdapter,\n private fallback: StorageAdapter,\n ) {}\n\n getItem(key: string) {\n const fallbackValue = this.fallback.getItem(key);\n if (fallbackValue !== null) return fallbackValue;\n return this.primary.getItem(key);\n }\n\n setItem(key: string, value: string) {\n try {\n this.primary.setItem(key, value);\n this.fallback.removeItem(key);\n } catch {\n this.fallback.setItem(key, value);\n }\n }\n\n removeItem(key: string) {\n try {\n this.primary.removeItem(key);\n } finally {\n this.fallback.removeItem(key);\n }\n }\n}\n\n// Singleton manager ensures we only have ONE listener per key globally\nclass StorageSyncManager {\n private listeners = new Map<string, Map<() => void, ListenerOptions>>();\n private pollingIntervalId: number | null = null;\n private pollingIntervalMsActive: number | null = null;\n private enableCrossTabSync: boolean;\n private enablePolling: boolean;\n\n constructor(\n public readonly storage: StorageAdapter,\n private defaultPollingIntervalMs = 2000,\n options: StorageSyncManagerOptions = {},\n ) {\n this.enableCrossTabSync = options.enableCrossTabSync ?? true;\n this.enablePolling = options.enablePolling ?? true;\n\n if (typeof window !== \"undefined\" && this.enableCrossTabSync) {\n // 1. Cross-tab sync (\"storage\" event is a built-in browser feature that fires\n // when localStorage/sessionStorage changes in ANOTHER tab)\n // We do not remove this listener because this manager is a singleton meant to last\n // the entire application lifecycle. It is effectively cleaned up when the page unloads.\n window.addEventListener(\"storage\", (e) => {\n if (e.key && this.listeners.has(e.key)) {\n this.notifyCrossTab(e.key);\n }\n });\n // 2. Start polling for DevTools or direct window.localStorage changes (robustness)\n // We only poll if there are listeners. Lazy start in subscribe.\n }\n }\n\n subscribe(key: string, callback: () => void, options: SubscribeOptions = {}) {\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Map());\n }\n const listenerOptions: ListenerOptions = {\n crossTabSync: this.enableCrossTabSync && (options.crossTabSync ?? true),\n pollingIntervalMs: this.enablePolling\n ? this.normalizePollingInterval(options.pollingIntervalMs)\n : null,\n };\n this.listeners.get(key)!.set(callback, listenerOptions);\n\n this.updatePollingInterval();\n\n return () => {\n const listenersForKey = this.listeners.get(key);\n listenersForKey?.delete(callback);\n if (listenersForKey?.size === 0) {\n this.listeners.delete(key);\n }\n this.updatePollingInterval();\n };\n }\n\n // Called when WE change the value in this tab\n notify(key: string) {\n this.notifyListeners(key);\n }\n\n // Keep a cache of values to detect changes during polling\n private snapshotCache = new Map<string, string | null>();\n\n private notifyListeners(\n key: string,\n predicate?: (options: ListenerOptions) => boolean,\n ) {\n this.listeners.get(key)?.forEach((options, cb) => {\n if (!predicate || predicate(options)) {\n cb();\n }\n });\n }\n\n private notifyCrossTab(key: string) {\n this.notifyListeners(key, (options) => options.crossTabSync);\n }\n\n private notifyPolling(key: string) {\n this.notifyListeners(key, (options) => options.pollingIntervalMs !== null);\n }\n\n private normalizePollingInterval(\n pollingIntervalMs: number | null | undefined,\n ) {\n if (pollingIntervalMs === null) return null;\n if (pollingIntervalMs === undefined) return this.defaultPollingIntervalMs;\n if (pollingIntervalMs <= 0 || Number.isNaN(pollingIntervalMs)) {\n return null;\n }\n return pollingIntervalMs;\n }\n\n private getMinPollingIntervalMs() {\n let minInterval: number | null = null;\n this.listeners.forEach((listenersForKey) => {\n listenersForKey.forEach((options) => {\n if (options.pollingIntervalMs === null) return;\n if (minInterval === null || options.pollingIntervalMs < minInterval) {\n minInterval = options.pollingIntervalMs;\n }\n });\n });\n return minInterval;\n }\n\n private updatePollingInterval() {\n if (typeof window === \"undefined\") return;\n if (!this.enablePolling) return;\n\n const nextInterval = this.getMinPollingIntervalMs();\n if (nextInterval === null) {\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n this.pollingIntervalId = null;\n this.pollingIntervalMsActive = null;\n this.snapshotCache.clear();\n return;\n }\n\n if (\n this.pollingIntervalId &&\n this.pollingIntervalMsActive === nextInterval\n ) {\n return;\n }\n\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n\n this.pollingIntervalId = window.setInterval(() => {\n this.listeners.forEach((listenersForKey, key) => {\n const shouldPoll = Array.from(listenersForKey.values()).some(\n (options) => options.pollingIntervalMs !== null,\n );\n if (!shouldPoll) return;\n\n const currentValue = this.storage.getItem(key);\n const lastValue = this.snapshotCache.get(key);\n\n if (currentValue !== lastValue) {\n this.snapshotCache.set(key, currentValue);\n this.notifyPolling(key);\n }\n });\n }, nextInterval);\n this.pollingIntervalMsActive = nextInterval;\n }\n}\n\n// Lazy-initialized singletons to avoid SSR crashes (window is not defined on server)\nconst localStorageFallback = new MemoryStorageAdapter();\nconst sessionStorageFallback = new MemoryStorageAdapter();\n\nlet _localStorageSync: StorageSyncManager | null = null;\nlet _sessionStorageSync: StorageSyncManager | null = null;\nlet _memoryStorageSync: StorageSyncManager | null = null;\n\nfunction getLocalStorageSync(): StorageSyncManager {\n if (!_localStorageSync) {\n if (typeof window !== \"undefined\") {\n _localStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(window.localStorage, localStorageFallback),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _localStorageSync;\n}\n\nfunction getSessionStorageSync(): StorageSyncManager {\n if (!_sessionStorageSync) {\n if (typeof window !== \"undefined\") {\n _sessionStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(\n window.sessionStorage,\n sessionStorageFallback,\n ),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _sessionStorageSync;\n}\n\nfunction getMemoryStorageSync(): StorageSyncManager {\n if (!_memoryStorageSync) {\n _memoryStorageSync = new StorageSyncManager(\n new MemoryStorageAdapter(),\n 2000,\n {\n enableCrossTabSync: false,\n enablePolling: false,\n },\n );\n }\n return _memoryStorageSync;\n}\n\n/**\n * Proxy object for localStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const localStorageSync = {\n get storage() {\n return getLocalStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getLocalStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getLocalStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for sessionStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const sessionStorageSync = {\n get storage() {\n return getSessionStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getSessionStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getSessionStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for memory storage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const memoryStorageSync = {\n get storage() {\n return getMemoryStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getMemoryStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getMemoryStorageSync().notify(key);\n },\n};\n","\"use client\";\n\nimport { Codec, inferCodec, JsonCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\nimport {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\n\nfunction getSyncManager(storageType: StorageType | undefined) {\n if (storageType === \"sessionStorage\") return sessionStorageSync;\n if (storageType === \"memory\") return memoryStorageSync;\n return localStorageSync;\n}\n\nfunction resolveCodec<T>(\n key: string,\n valueHint: T,\n options?: StoragePersistedStateOptions<T>,\n): Codec<T> {\n if (options?.codec) return options.codec;\n\n if ((valueHint === undefined || valueHint === null) && !options?.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n return inferCodec(valueHint);\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Read a persisted value from storage using the same codec behavior as the hook.\n *\n * Returns the provided defaultValue when the key is missing or parsing fails.\n */\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): T;\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\n/**\n * Read a persisted value from storage using an explicit codec.\n *\n * Use this overload when defaultValue is null or undefined.\n */\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): T | null;\n\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n const codec = resolveCodec(key, defaultValue, options);\n const raw = adapter.getItem(key);\n\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n try {\n return codec.decode(raw);\n } catch (error) {\n console.error(`Error parsing storage key \"${key}\"`, error);\n return defaultValue as T;\n }\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Set a persisted value in storage and notify active hooks for the same key.\n *\n * Supports functional updates using the current decoded value.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): void;\n\n// Overload 2: Explicit Codec provided, newValue can be null or undefined\n/**\n * Set a persisted value in storage using an explicit codec and notify listeners.\n *\n * Use this overload when the new value is null or undefined or when you want custom serialization.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): void;\n\nexport function setStoragePersistedState<T>(\n key: string,\n newValueOrFn: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n\n let codec: Codec<T>;\n if (!(newValueOrFn instanceof Function)) {\n codec = resolveCodec(key, newValueOrFn, options);\n } else {\n // For functional updates, we cannot infer from newValue\n if (!options.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses functional update without explicit Codec. defaulting to JSON.`,\n );\n }\n codec = options.codec ?? (JsonCodec as unknown as Codec<T>);\n }\n\n try {\n const currentRaw = adapter.getItem(key);\n const current = currentRaw !== null ? codec.decode(currentRaw) : null;\n\n const newValue =\n newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n}\n"],"mappings":"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","names":[]}
|