@coherent.js/state 1.1.0 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -15
- package/dist/index.js +929 -335
- package/dist/index.js.map +3 -3
- package/dist/reactive-state.js +468 -111
- package/dist/reactive-state.js.map +2 -2
- package/dist/state-manager.js +141 -38
- package/dist/state-manager.js.map +3 -3
- package/dist/state-persistence.js +239 -143
- package/dist/state-persistence.js.map +2 -2
- package/dist/state-validation.js +70 -43
- package/dist/state-validation.js.map +2 -2
- package/package.json +1 -4
- package/types/index.d.ts +143 -36
|
@@ -1,90 +1,40 @@
|
|
|
1
1
|
// src/state-persistence.js
|
|
2
|
-
var
|
|
3
|
-
constructor() {
|
|
4
|
-
this.
|
|
2
|
+
var WebStorageAdapter = class {
|
|
3
|
+
constructor(storageName) {
|
|
4
|
+
this.storageName = storageName;
|
|
5
|
+
this.available = typeof globalThis[storageName] !== "undefined" && globalThis[storageName] !== null;
|
|
6
|
+
}
|
|
7
|
+
get storage() {
|
|
8
|
+
return globalThis[this.storageName];
|
|
5
9
|
}
|
|
6
10
|
async get(key) {
|
|
7
11
|
if (!this.available) return null;
|
|
8
|
-
|
|
9
|
-
return localStorage.getItem(key);
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error("LocalStorage get error:", error);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
12
|
+
return this.storage.getItem(key);
|
|
14
13
|
}
|
|
15
14
|
async set(key, value) {
|
|
16
15
|
if (!this.available) return false;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return true;
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.error("LocalStorage set error:", error);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
16
|
+
this.storage.setItem(key, value);
|
|
17
|
+
return true;
|
|
24
18
|
}
|
|
25
19
|
async remove(key) {
|
|
26
20
|
if (!this.available) return false;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return true;
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error("LocalStorage remove error:", error);
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
21
|
+
this.storage.removeItem(key);
|
|
22
|
+
return true;
|
|
34
23
|
}
|
|
35
24
|
async clear() {
|
|
36
25
|
if (!this.available) return false;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return true;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error("LocalStorage clear error:", error);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
26
|
+
this.storage.clear();
|
|
27
|
+
return true;
|
|
44
28
|
}
|
|
45
29
|
};
|
|
46
|
-
var
|
|
30
|
+
var LocalStorageAdapter = class extends WebStorageAdapter {
|
|
47
31
|
constructor() {
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
async get(key) {
|
|
51
|
-
if (!this.available) return null;
|
|
52
|
-
try {
|
|
53
|
-
return sessionStorage.getItem(key);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error("SessionStorage get error:", error);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async set(key, value) {
|
|
60
|
-
if (!this.available) return false;
|
|
61
|
-
try {
|
|
62
|
-
sessionStorage.setItem(key, value);
|
|
63
|
-
return true;
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error("SessionStorage set error:", error);
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async remove(key) {
|
|
70
|
-
if (!this.available) return false;
|
|
71
|
-
try {
|
|
72
|
-
sessionStorage.removeItem(key);
|
|
73
|
-
return true;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error("SessionStorage remove error:", error);
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
32
|
+
super("localStorage");
|
|
78
33
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return true;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error("SessionStorage clear error:", error);
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
34
|
+
};
|
|
35
|
+
var SessionStorageAdapter = class extends WebStorageAdapter {
|
|
36
|
+
constructor() {
|
|
37
|
+
super("sessionStorage");
|
|
88
38
|
}
|
|
89
39
|
};
|
|
90
40
|
var IndexedDBAdapter = class {
|
|
@@ -93,19 +43,21 @@ var IndexedDBAdapter = class {
|
|
|
93
43
|
this.storeName = storeName;
|
|
94
44
|
this.available = typeof indexedDB !== "undefined";
|
|
95
45
|
this.db = null;
|
|
46
|
+
this.opening = null;
|
|
96
47
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Open the database, creating the store in an upgrade. Without `version`,
|
|
50
|
+
* opens the current version (creating version 1 for a new database).
|
|
51
|
+
*/
|
|
52
|
+
open(version) {
|
|
100
53
|
return new Promise((resolve, reject) => {
|
|
101
|
-
const request = indexedDB.open(this.dbName,
|
|
54
|
+
const request = version === void 0 ? indexedDB.open(this.dbName) : indexedDB.open(this.dbName, version);
|
|
102
55
|
request.onerror = () => {
|
|
103
56
|
console.error("IndexedDB open error:", request.error);
|
|
104
57
|
reject(request.error);
|
|
105
58
|
};
|
|
106
59
|
request.onsuccess = () => {
|
|
107
|
-
|
|
108
|
-
resolve(true);
|
|
60
|
+
resolve(request.result);
|
|
109
61
|
};
|
|
110
62
|
request.onupgradeneeded = (event) => {
|
|
111
63
|
const db = event.target.result;
|
|
@@ -115,6 +67,27 @@ var IndexedDBAdapter = class {
|
|
|
115
67
|
};
|
|
116
68
|
});
|
|
117
69
|
}
|
|
70
|
+
async init() {
|
|
71
|
+
if (!this.available) return false;
|
|
72
|
+
if (this.db) return true;
|
|
73
|
+
this.opening ??= (async () => {
|
|
74
|
+
let db = await this.open();
|
|
75
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
76
|
+
const version = db.version + 1;
|
|
77
|
+
db.close();
|
|
78
|
+
db = await this.open(version);
|
|
79
|
+
}
|
|
80
|
+
db.onversionchange = () => {
|
|
81
|
+
db.close();
|
|
82
|
+
if (this.db === db) this.db = null;
|
|
83
|
+
};
|
|
84
|
+
this.db = db;
|
|
85
|
+
return true;
|
|
86
|
+
})().finally(() => {
|
|
87
|
+
this.opening = null;
|
|
88
|
+
});
|
|
89
|
+
return this.opening;
|
|
90
|
+
}
|
|
118
91
|
async get(key) {
|
|
119
92
|
if (!this.available) return null;
|
|
120
93
|
await this.init();
|
|
@@ -200,47 +173,85 @@ var MemoryAdapter = class {
|
|
|
200
173
|
return true;
|
|
201
174
|
}
|
|
202
175
|
};
|
|
203
|
-
var
|
|
204
|
-
constructor(
|
|
205
|
-
this.
|
|
176
|
+
var ServerAdapter = class {
|
|
177
|
+
constructor() {
|
|
178
|
+
this.available = false;
|
|
179
|
+
}
|
|
180
|
+
async get() {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
async set() {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
async remove() {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
async clear() {
|
|
190
|
+
return false;
|
|
206
191
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
192
|
+
};
|
|
193
|
+
function toBase64(bytes) {
|
|
194
|
+
let binary = "";
|
|
195
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
196
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
197
|
+
}
|
|
198
|
+
return btoa(binary);
|
|
199
|
+
}
|
|
200
|
+
function fromBase64(encoded) {
|
|
201
|
+
const binary = atob(encoded);
|
|
202
|
+
const bytes = new Uint8Array(binary.length);
|
|
203
|
+
for (let i = 0; i < binary.length; i++) {
|
|
204
|
+
bytes[i] = binary.charCodeAt(i);
|
|
205
|
+
}
|
|
206
|
+
return bytes;
|
|
207
|
+
}
|
|
208
|
+
var XorObfuscation = class {
|
|
209
|
+
constructor(key) {
|
|
210
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
211
|
+
throw new TypeError(
|
|
212
|
+
"createPersistentState: `encrypt: true` requires a non-empty `encryptionKey`. It is XOR obfuscation, not encryption; there is no default key."
|
|
212
213
|
);
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
for (let i = 0; i < text.length; i++) {
|
|
220
|
-
result += String.fromCharCode(
|
|
221
|
-
text.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length)
|
|
222
|
-
);
|
|
215
|
+
this.keyBytes = new globalThis.TextEncoder().encode(key);
|
|
216
|
+
}
|
|
217
|
+
xor(bytes) {
|
|
218
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
219
|
+
bytes[i] ^= this.keyBytes[i % this.keyBytes.length];
|
|
223
220
|
}
|
|
224
|
-
return
|
|
221
|
+
return bytes;
|
|
222
|
+
}
|
|
223
|
+
encode(text) {
|
|
224
|
+
return toBase64(this.xor(new globalThis.TextEncoder().encode(text)));
|
|
225
|
+
}
|
|
226
|
+
decode(encoded) {
|
|
227
|
+
return new globalThis.TextDecoder().decode(this.xor(fromBase64(encoded)));
|
|
225
228
|
}
|
|
226
229
|
};
|
|
227
|
-
function createStorageAdapter(type) {
|
|
230
|
+
function createStorageAdapter(type, options = {}) {
|
|
228
231
|
switch (type) {
|
|
229
232
|
case "localStorage":
|
|
230
233
|
return new LocalStorageAdapter();
|
|
231
234
|
case "sessionStorage":
|
|
232
235
|
return new SessionStorageAdapter();
|
|
233
236
|
case "indexedDB":
|
|
234
|
-
return new IndexedDBAdapter();
|
|
237
|
+
return new IndexedDBAdapter(options.dbName ?? void 0, options.storeName ?? void 0);
|
|
235
238
|
case "memory":
|
|
236
239
|
return new MemoryAdapter();
|
|
237
240
|
default:
|
|
238
241
|
return new LocalStorageAdapter();
|
|
239
242
|
}
|
|
240
243
|
}
|
|
244
|
+
function createInstanceId() {
|
|
245
|
+
const cryptoApi = globalThis.crypto;
|
|
246
|
+
if (cryptoApi && typeof cryptoApi.randomUUID === "function") {
|
|
247
|
+
return cryptoApi.randomUUID();
|
|
248
|
+
}
|
|
249
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
250
|
+
}
|
|
241
251
|
function createPersistentState(initialState = {}, options = {}) {
|
|
242
252
|
const opts = {
|
|
243
253
|
storage: "localStorage",
|
|
254
|
+
adapter: null,
|
|
244
255
|
key: "coherent-state",
|
|
245
256
|
debounce: true,
|
|
246
257
|
debounceDelay: 300,
|
|
@@ -260,11 +271,30 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
260
271
|
crossTab: false,
|
|
261
272
|
...options
|
|
262
273
|
};
|
|
263
|
-
const
|
|
264
|
-
const
|
|
274
|
+
const onServer = typeof window === "undefined";
|
|
275
|
+
const obfuscation = opts.encrypt ? new XorObfuscation(opts.encryptionKey) : null;
|
|
276
|
+
let adapter;
|
|
277
|
+
if (opts.adapter) {
|
|
278
|
+
adapter = opts.adapter;
|
|
279
|
+
} else if (onServer && opts.storage !== "memory") {
|
|
280
|
+
adapter = new ServerAdapter();
|
|
281
|
+
} else {
|
|
282
|
+
adapter = createStorageAdapter(opts.storage, opts);
|
|
283
|
+
}
|
|
284
|
+
const instanceId = createInstanceId();
|
|
265
285
|
let state = { ...initialState };
|
|
266
286
|
let saveTimeout = null;
|
|
287
|
+
let destroyed = false;
|
|
267
288
|
const listeners = /* @__PURE__ */ new Set();
|
|
289
|
+
let initialRestorePending = false;
|
|
290
|
+
const touchedKeys = /* @__PURE__ */ new Set();
|
|
291
|
+
function reportError(error) {
|
|
292
|
+
if (opts.onError) {
|
|
293
|
+
opts.onError(error);
|
|
294
|
+
} else {
|
|
295
|
+
console.error("State persistence error:", error);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
268
298
|
function filterKeys(obj) {
|
|
269
299
|
if (!obj || typeof obj !== "object") return obj;
|
|
270
300
|
if (opts.include && Array.isArray(opts.include)) {
|
|
@@ -285,11 +315,31 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
285
315
|
}
|
|
286
316
|
return obj;
|
|
287
317
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
318
|
+
let channel = null;
|
|
319
|
+
if (opts.crossTab && !onServer && typeof BroadcastChannel !== "undefined") {
|
|
320
|
+
channel = new BroadcastChannel(`coherent-state-sync:${opts.key}`);
|
|
321
|
+
channel.onmessage = (event) => {
|
|
322
|
+
const message = event.data;
|
|
323
|
+
if (destroyed || !message || message.type !== "state-update" || message.source === instanceId) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const oldState = { ...state };
|
|
327
|
+
state = { ...state, ...message.state };
|
|
328
|
+
notifyListeners(oldState, state);
|
|
329
|
+
};
|
|
330
|
+
channel.unref?.();
|
|
331
|
+
}
|
|
332
|
+
function broadcast(filteredState) {
|
|
333
|
+
if (!channel) return;
|
|
334
|
+
try {
|
|
335
|
+
channel.postMessage({ type: "state-update", source: instanceId, state: filteredState });
|
|
336
|
+
} catch (error) {
|
|
337
|
+
reportError(error);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function write() {
|
|
341
|
+
if (adapter.available === false) {
|
|
342
|
+
return false;
|
|
293
343
|
}
|
|
294
344
|
try {
|
|
295
345
|
const filteredState = filterKeys(state);
|
|
@@ -301,31 +351,45 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
301
351
|
ttl: opts.ttl
|
|
302
352
|
};
|
|
303
353
|
let dataString = JSON.stringify(data);
|
|
304
|
-
if (
|
|
305
|
-
dataString =
|
|
354
|
+
if (obfuscation) {
|
|
355
|
+
dataString = obfuscation.encode(dataString);
|
|
356
|
+
}
|
|
357
|
+
const stored = await adapter.set(opts.key, dataString);
|
|
358
|
+
if (stored === false) {
|
|
359
|
+
throw new Error(`State "${opts.key}" could not be written to storage`);
|
|
306
360
|
}
|
|
307
|
-
await adapter.set(opts.key, dataString);
|
|
308
361
|
if (opts.onSave) {
|
|
309
362
|
opts.onSave(filteredState);
|
|
310
363
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
channel.postMessage({ type: "state-update", state: filteredState });
|
|
314
|
-
channel.close();
|
|
315
|
-
}
|
|
364
|
+
broadcast(filteredState);
|
|
365
|
+
return true;
|
|
316
366
|
} catch (error) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
opts.onError(error);
|
|
320
|
-
}
|
|
367
|
+
reportError(error);
|
|
368
|
+
return false;
|
|
321
369
|
}
|
|
322
370
|
}
|
|
371
|
+
function save(immediate = false) {
|
|
372
|
+
if (destroyed) {
|
|
373
|
+
return Promise.resolve(false);
|
|
374
|
+
}
|
|
375
|
+
if (opts.debounce && !immediate) {
|
|
376
|
+
clearTimeout(saveTimeout);
|
|
377
|
+
saveTimeout = setTimeout(() => {
|
|
378
|
+
saveTimeout = null;
|
|
379
|
+
write();
|
|
380
|
+
}, opts.debounceDelay);
|
|
381
|
+
return void 0;
|
|
382
|
+
}
|
|
383
|
+
clearTimeout(saveTimeout);
|
|
384
|
+
saveTimeout = null;
|
|
385
|
+
return write();
|
|
386
|
+
}
|
|
323
387
|
async function load() {
|
|
324
388
|
try {
|
|
325
389
|
let dataString = await adapter.get(opts.key);
|
|
326
390
|
if (!dataString) return null;
|
|
327
|
-
if (
|
|
328
|
-
dataString =
|
|
391
|
+
if (obfuscation) {
|
|
392
|
+
dataString = obfuscation.decode(dataString);
|
|
329
393
|
}
|
|
330
394
|
const data = JSON.parse(dataString);
|
|
331
395
|
if (data.ttl && data.timestamp) {
|
|
@@ -348,10 +412,7 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
348
412
|
}
|
|
349
413
|
return loadedState;
|
|
350
414
|
} catch (error) {
|
|
351
|
-
|
|
352
|
-
if (opts.onError) {
|
|
353
|
-
opts.onError(error);
|
|
354
|
-
}
|
|
415
|
+
reportError(error);
|
|
355
416
|
return null;
|
|
356
417
|
}
|
|
357
418
|
}
|
|
@@ -368,6 +429,20 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
368
429
|
}
|
|
369
430
|
});
|
|
370
431
|
}
|
|
432
|
+
function applyLoaded(loaded, skipTouched) {
|
|
433
|
+
if (!loaded || typeof loaded !== "object") {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const updates = Object.fromEntries(
|
|
437
|
+
Object.entries(loaded).filter(([key]) => !skipTouched || !touchedKeys.has(key))
|
|
438
|
+
);
|
|
439
|
+
if (Object.keys(updates).length > 0) {
|
|
440
|
+
const oldState = { ...state };
|
|
441
|
+
state = { ...state, ...updates };
|
|
442
|
+
notifyListeners(oldState, state);
|
|
443
|
+
}
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
371
446
|
function getState(key) {
|
|
372
447
|
return key ? state[key] : { ...state };
|
|
373
448
|
}
|
|
@@ -376,6 +451,9 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
376
451
|
if (typeof updates === "function") {
|
|
377
452
|
updates = updates(oldState);
|
|
378
453
|
}
|
|
454
|
+
if (initialRestorePending && updates && typeof updates === "object") {
|
|
455
|
+
for (const key of Object.keys(updates)) touchedKeys.add(key);
|
|
456
|
+
}
|
|
379
457
|
state = { ...state, ...updates };
|
|
380
458
|
notifyListeners(oldState, state);
|
|
381
459
|
if (persist2) {
|
|
@@ -384,6 +462,10 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
384
462
|
}
|
|
385
463
|
function resetState(persist2 = true) {
|
|
386
464
|
const oldState = { ...state };
|
|
465
|
+
if (initialRestorePending) {
|
|
466
|
+
for (const key of Object.keys(oldState)) touchedKeys.add(key);
|
|
467
|
+
for (const key of Object.keys(initialState)) touchedKeys.add(key);
|
|
468
|
+
}
|
|
387
469
|
state = { ...initialState };
|
|
388
470
|
notifyListeners(oldState, state);
|
|
389
471
|
if (persist2) {
|
|
@@ -391,33 +473,44 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
391
473
|
}
|
|
392
474
|
}
|
|
393
475
|
async function clearStorage() {
|
|
394
|
-
|
|
476
|
+
try {
|
|
477
|
+
await adapter.remove(opts.key);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
reportError(error);
|
|
480
|
+
}
|
|
395
481
|
}
|
|
396
482
|
async function persist() {
|
|
397
|
-
|
|
483
|
+
return save(true);
|
|
398
484
|
}
|
|
399
485
|
async function restore() {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
486
|
+
return applyLoaded(await load(), false);
|
|
487
|
+
}
|
|
488
|
+
async function destroy() {
|
|
489
|
+
if (destroyed) return;
|
|
490
|
+
const pending = saveTimeout !== null;
|
|
491
|
+
clearTimeout(saveTimeout);
|
|
492
|
+
saveTimeout = null;
|
|
493
|
+
if (pending) {
|
|
494
|
+
await write();
|
|
406
495
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
496
|
+
destroyed = true;
|
|
497
|
+
channel?.close();
|
|
498
|
+
channel = null;
|
|
499
|
+
listeners.clear();
|
|
500
|
+
}
|
|
501
|
+
let ready;
|
|
502
|
+
if (opts.storage !== "memory" || opts.adapter) {
|
|
503
|
+
initialRestorePending = true;
|
|
504
|
+
ready = load().then((loaded) => {
|
|
505
|
+
initialRestorePending = false;
|
|
506
|
+
if (destroyed) return false;
|
|
507
|
+
const restored = applyLoaded(loaded, true);
|
|
508
|
+
if (restored && touchedKeys.size > 0) save();
|
|
509
|
+
touchedKeys.clear();
|
|
510
|
+
return restored;
|
|
511
|
+
});
|
|
512
|
+
} else {
|
|
513
|
+
ready = Promise.resolve(false);
|
|
421
514
|
}
|
|
422
515
|
return {
|
|
423
516
|
getState,
|
|
@@ -429,6 +522,9 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
429
522
|
clearStorage,
|
|
430
523
|
load,
|
|
431
524
|
save: () => save(true),
|
|
525
|
+
destroy,
|
|
526
|
+
/** Settles once the automatic restore on creation is done */
|
|
527
|
+
ready,
|
|
432
528
|
get adapter() {
|
|
433
529
|
return adapter;
|
|
434
530
|
}
|