@systemfsoftware/xstate-store 0.0.0-stage → 5.0.0-beta.6

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.
@@ -0,0 +1,630 @@
1
+ import { n as assertNoInternalEventTypeCollisions } from "./store-BMZdYhoE.mjs";
2
+ // src/persist.ts
3
+ const PERSIST_INTERNALS = Symbol.for("xstate-store-persist");
4
+ const PERSIST_REVISION = Symbol("xstate-store-persist-revision");
5
+ const PERSIST_REHYDRATE_EVENT_TYPE = "__persist.rehydrate";
6
+ const noopStorage = {
7
+ getItem: () => null,
8
+ setItem: () => {},
9
+ removeItem: () => {}
10
+ };
11
+ function getStorage(options) {
12
+ return options.storage ?? createJSONStorage(() => localStorage);
13
+ }
14
+ function isEventStrategy(options) {
15
+ return options.strategy === "event";
16
+ }
17
+ function serializeSnapshotValue(options, value) {
18
+ return options.serialize ? options.serialize(value) : JSON.stringify(value);
19
+ }
20
+ function deserializeSnapshotValue(options, str) {
21
+ return options.deserialize ? options.deserialize(str) : JSON.parse(str);
22
+ }
23
+ function serializeEventValue(options, value) {
24
+ return options.serialize ? options.serialize(value) : JSON.stringify(value);
25
+ }
26
+ function deserializeEventValue(options, str) {
27
+ return options.deserialize ? options.deserialize(str) : JSON.parse(str);
28
+ }
29
+ function mergeContext(options, persistedContext, currentContext) {
30
+ return options.merge ? options.merge(persistedContext, currentContext) : {
31
+ ...currentContext,
32
+ ...persistedContext
33
+ };
34
+ }
35
+ function migrateSnapshotIfNeeded(options, stored) {
36
+ const currentVersion = options.version ?? 0;
37
+ if (stored.version !== currentVersion && options.migrate) return options.migrate(stored.context, stored.version);
38
+ return stored.context;
39
+ }
40
+ function migrateEventsIfNeeded(options, stored) {
41
+ const currentVersion = options.version ?? 0;
42
+ if (stored.version !== currentVersion && options.migrate) return options.migrate(stored.events, stored.version);
43
+ return stored.events;
44
+ }
45
+ function enqueueWrite(internals, write) {
46
+ const result = internals.pendingWrite ? internals.pendingWrite.then(write, write) : write();
47
+ if (result instanceof Promise) {
48
+ const pending = result.finally(() => {
49
+ if (internals.pendingWrite === pending) internals.pendingWrite = null;
50
+ });
51
+ internals.pendingWrite = pending;
52
+ return pending;
53
+ }
54
+ }
55
+ function writeSnapshotToStorage(internals, context) {
56
+ const options = internals.options;
57
+ const { storage } = internals;
58
+ const contextToPersist = options.pick ? options.pick(context) : context;
59
+ const value = {
60
+ context: contextToPersist,
61
+ version: options.version ?? 0
62
+ };
63
+ return enqueueWrite(internals, () => {
64
+ try {
65
+ const serialized = serializeSnapshotValue(options, value);
66
+ const result = storage.setItem(options.name, serialized);
67
+ if (result instanceof Promise) return result.then(() => options.onDone?.(contextToPersist)).catch((err) => options.onError?.(err));
68
+ options.onDone?.(contextToPersist);
69
+ } catch (err) {
70
+ options.onError?.(err);
71
+ }
72
+ });
73
+ }
74
+ function writeEventsToStorage(internals, events, checkpoint) {
75
+ const options = internals.options;
76
+ const { storage } = internals;
77
+ const value = {
78
+ events,
79
+ version: options.version ?? 0
80
+ };
81
+ if (checkpoint !== void 0) value.checkpoint = checkpoint;
82
+ return enqueueWrite(internals, () => {
83
+ try {
84
+ const serialized = serializeEventValue(options, value);
85
+ const result = storage.setItem(options.name, serialized);
86
+ if (result instanceof Promise) return result.then(() => options.onDone?.(events)).catch((err) => options.onError?.(err));
87
+ options.onDone?.(events);
88
+ } catch (err) {
89
+ options.onError?.(err);
90
+ }
91
+ });
92
+ }
93
+ function createInternals(options) {
94
+ const internals = {
95
+ options,
96
+ storage: getStorage(options),
97
+ pendingContext: null,
98
+ pendingEvents: null,
99
+ pendingCheckpoint: null,
100
+ flushTimeoutId: null,
101
+ pendingWrite: null,
102
+ lastScheduledRevision: 0,
103
+ flush: () => {
104
+ if (internals.flushTimeoutId !== null) {
105
+ clearTimeout(internals.flushTimeoutId);
106
+ internals.flushTimeoutId = null;
107
+ }
108
+ if (isEventStrategy(options)) {
109
+ if (internals.pendingEvents !== null) {
110
+ const events = internals.pendingEvents;
111
+ const checkpoint = internals.pendingCheckpoint;
112
+ internals.pendingEvents = null;
113
+ internals.pendingCheckpoint = null;
114
+ return writeEventsToStorage(internals, events, checkpoint);
115
+ }
116
+ } else if (internals.pendingContext !== null) {
117
+ const context = internals.pendingContext;
118
+ internals.pendingContext = null;
119
+ return writeSnapshotToStorage(internals, context);
120
+ }
121
+ return internals.pendingWrite ?? void 0;
122
+ }
123
+ };
124
+ return internals;
125
+ }
126
+ function persistSnapshotFromLogic(logic, options) {
127
+ assertNoInternalEventTypeCollisions(logic.eventTypes, [PERSIST_REHYDRATE_EVENT_TYPE], "persist");
128
+ const internals = createInternals(options);
129
+ const { storage } = internals;
130
+ const throttleMs = options.throttle ?? 0;
131
+ return {
132
+ ...logic,
133
+ eventTypes: logic.eventTypes,
134
+ getInitialSnapshot: () => {
135
+ const baseSnapshot = logic.getInitialSnapshot();
136
+ if (options.skipHydration) return {
137
+ ...baseSnapshot,
138
+ _persist: { hydrated: false },
139
+ [PERSIST_INTERNALS]: internals
140
+ };
141
+ try {
142
+ const storedValue = storage.getItem(options.name);
143
+ if (storedValue instanceof Promise) {
144
+ storedValue.catch((error) => options.onError?.(error));
145
+ return {
146
+ ...baseSnapshot,
147
+ _persist: { hydrated: false },
148
+ [PERSIST_INTERNALS]: internals
149
+ };
150
+ }
151
+ if (storedValue === null) return {
152
+ ...baseSnapshot,
153
+ _persist: { hydrated: true },
154
+ [PERSIST_INTERNALS]: internals
155
+ };
156
+ const mergedContext = mergeContext(options, migrateSnapshotIfNeeded(options, deserializeSnapshotValue(options, storedValue)), baseSnapshot.context);
157
+ return {
158
+ ...baseSnapshot,
159
+ context: mergedContext,
160
+ _persist: { hydrated: true },
161
+ [PERSIST_INTERNALS]: internals
162
+ };
163
+ } catch (err) {
164
+ options.onError?.(err);
165
+ return {
166
+ ...baseSnapshot,
167
+ _persist: { hydrated: true },
168
+ [PERSIST_INTERNALS]: internals
169
+ };
170
+ }
171
+ },
172
+ transition: (snapshot, event) => {
173
+ if (event.type === PERSIST_REHYDRATE_EVENT_TYPE) {
174
+ const rawState = event.state;
175
+ if (!rawState) return [{
176
+ ...snapshot,
177
+ _persist: {
178
+ ...snapshot._persist,
179
+ hydrated: true
180
+ }
181
+ }, []];
182
+ try {
183
+ const mergedContext = mergeContext(options, migrateSnapshotIfNeeded(options, deserializeSnapshotValue(options, rawState)), snapshot.context);
184
+ return [{
185
+ ...snapshot,
186
+ context: mergedContext,
187
+ _persist: {
188
+ ...snapshot._persist,
189
+ hydrated: true
190
+ }
191
+ }, []];
192
+ } catch (err) {
193
+ options.onError?.(err);
194
+ return [{
195
+ ...snapshot,
196
+ _persist: {
197
+ ...snapshot._persist,
198
+ hydrated: true
199
+ }
200
+ }, []];
201
+ }
202
+ }
203
+ const [nextSnapshot, effects] = logic.transition(snapshot, event);
204
+ const revision = (snapshot[PERSIST_REVISION] ?? 0) + 1;
205
+ const snapshotWithMeta = {
206
+ ...nextSnapshot,
207
+ [PERSIST_REVISION]: revision,
208
+ _persist: snapshot._persist ?? { hydrated: false },
209
+ [PERSIST_INTERNALS]: internals
210
+ };
211
+ if (!snapshotWithMeta._persist?.hydrated) return [snapshotWithMeta, effects];
212
+ if (options.filter && !options.filter(event)) return [snapshotWithMeta, effects];
213
+ const persistEffect = () => {
214
+ if (revision <= internals.lastScheduledRevision) return;
215
+ internals.lastScheduledRevision = revision;
216
+ if (throttleMs > 0) {
217
+ internals.pendingContext = nextSnapshot.context;
218
+ if (internals.flushTimeoutId === null) internals.flushTimeoutId = setTimeout(() => {
219
+ internals.flush();
220
+ }, throttleMs);
221
+ } else writeSnapshotToStorage(internals, nextSnapshot.context);
222
+ };
223
+ return [snapshotWithMeta, [persistEffect, ...effects]];
224
+ }
225
+ };
226
+ }
227
+ function persistEventFromLogic(logic, options) {
228
+ assertNoInternalEventTypeCollisions(logic.eventTypes, [PERSIST_REHYDRATE_EVENT_TYPE], "persist");
229
+ const internals = createInternals(options);
230
+ const { storage } = internals;
231
+ const throttleMs = options.throttle ?? 0;
232
+ const maxEvents = options.maxEvents ?? Infinity;
233
+ function replayEvents(baseSnapshot, events) {
234
+ let current = baseSnapshot;
235
+ for (const ev of events) {
236
+ const [next] = logic.transition(current, ev);
237
+ current = next;
238
+ }
239
+ return current;
240
+ }
241
+ return {
242
+ ...logic,
243
+ eventTypes: logic.eventTypes,
244
+ getInitialSnapshot: () => {
245
+ const baseSnapshot = logic.getInitialSnapshot();
246
+ if (options.skipHydration) return {
247
+ ...baseSnapshot,
248
+ _persistEvents: [],
249
+ _persistCheckpoint: null,
250
+ _persist: { hydrated: false },
251
+ [PERSIST_INTERNALS]: internals
252
+ };
253
+ try {
254
+ const storedValue = storage.getItem(options.name);
255
+ if (storedValue instanceof Promise) {
256
+ storedValue.catch((error) => options.onError?.(error));
257
+ return {
258
+ ...baseSnapshot,
259
+ _persistEvents: [],
260
+ _persistCheckpoint: null,
261
+ _persist: { hydrated: false },
262
+ [PERSIST_INTERNALS]: internals
263
+ };
264
+ }
265
+ if (storedValue === null) return {
266
+ ...baseSnapshot,
267
+ _persistEvents: [],
268
+ _persistCheckpoint: null,
269
+ _persist: { hydrated: true },
270
+ [PERSIST_INTERNALS]: internals
271
+ };
272
+ const parsed = deserializeEventValue(options, storedValue);
273
+ const events = migrateEventsIfNeeded(options, parsed);
274
+ return {
275
+ ...replayEvents(parsed.checkpoint ? {
276
+ ...baseSnapshot,
277
+ context: parsed.checkpoint
278
+ } : baseSnapshot, events),
279
+ _persistEvents: events,
280
+ _persistCheckpoint: parsed.checkpoint ?? null,
281
+ _persist: { hydrated: true },
282
+ [PERSIST_INTERNALS]: internals
283
+ };
284
+ } catch (err) {
285
+ options.onError?.(err);
286
+ return {
287
+ ...baseSnapshot,
288
+ _persistEvents: [],
289
+ _persistCheckpoint: null,
290
+ _persist: { hydrated: true },
291
+ [PERSIST_INTERNALS]: internals
292
+ };
293
+ }
294
+ },
295
+ transition: (snapshot, event) => {
296
+ if (event.type === PERSIST_REHYDRATE_EVENT_TYPE) {
297
+ const rawState = event.state;
298
+ if (!rawState) return [{
299
+ ...snapshot,
300
+ _persistEvents: snapshot._persistEvents ?? [],
301
+ _persistCheckpoint: snapshot._persistCheckpoint ?? null,
302
+ _persist: {
303
+ ...snapshot._persist,
304
+ hydrated: true
305
+ }
306
+ }, []];
307
+ try {
308
+ const parsed = deserializeEventValue(options, rawState);
309
+ const events = migrateEventsIfNeeded(options, parsed);
310
+ const baseSnapshot = logic.getInitialSnapshot();
311
+ return [{
312
+ ...replayEvents(parsed.checkpoint ? {
313
+ ...baseSnapshot,
314
+ context: parsed.checkpoint
315
+ } : baseSnapshot, events),
316
+ [PERSIST_REVISION]: snapshot[PERSIST_REVISION],
317
+ _persistEvents: events,
318
+ _persistCheckpoint: parsed.checkpoint ?? null,
319
+ _persist: {
320
+ ...snapshot._persist,
321
+ hydrated: true
322
+ },
323
+ [PERSIST_INTERNALS]: internals
324
+ }, []];
325
+ } catch (err) {
326
+ options.onError?.(err);
327
+ return [{
328
+ ...snapshot,
329
+ _persistEvents: snapshot._persistEvents ?? [],
330
+ _persistCheckpoint: snapshot._persistCheckpoint ?? null,
331
+ _persist: {
332
+ ...snapshot._persist,
333
+ hydrated: true
334
+ }
335
+ }, []];
336
+ }
337
+ }
338
+ const [nextSnapshot, effects] = logic.transition(snapshot, event);
339
+ const prevEvents = snapshot._persistEvents ?? [];
340
+ const prevCheckpoint = snapshot._persistCheckpoint ?? null;
341
+ const revision = (snapshot[PERSIST_REVISION] ?? 0) + 1;
342
+ const snapshotWithMeta = {
343
+ ...nextSnapshot,
344
+ [PERSIST_REVISION]: revision,
345
+ _persistEvents: prevEvents,
346
+ _persistCheckpoint: prevCheckpoint,
347
+ _persist: snapshot._persist ?? { hydrated: false },
348
+ [PERSIST_INTERNALS]: internals
349
+ };
350
+ if (!snapshotWithMeta._persist?.hydrated) return [snapshotWithMeta, effects];
351
+ let nextEvents = [...prevEvents, event];
352
+ let nextCheckpoint = prevCheckpoint;
353
+ if (nextEvents.length > maxEvents) {
354
+ const droppedEvents = nextEvents.slice(0, -maxEvents);
355
+ const baseSnapshot = logic.getInitialSnapshot();
356
+ nextCheckpoint = replayEvents(prevCheckpoint ? {
357
+ ...baseSnapshot,
358
+ context: prevCheckpoint
359
+ } : baseSnapshot, droppedEvents).context;
360
+ nextEvents = nextEvents.slice(-maxEvents);
361
+ }
362
+ const snapshotWithEvents = {
363
+ ...snapshotWithMeta,
364
+ _persistEvents: nextEvents,
365
+ _persistCheckpoint: nextCheckpoint
366
+ };
367
+ const persistEffect = () => {
368
+ if (revision <= internals.lastScheduledRevision) return;
369
+ internals.lastScheduledRevision = revision;
370
+ if (throttleMs > 0) {
371
+ internals.pendingEvents = nextEvents;
372
+ internals.pendingCheckpoint = nextCheckpoint;
373
+ if (internals.flushTimeoutId === null) internals.flushTimeoutId = setTimeout(() => {
374
+ internals.flush();
375
+ }, throttleMs);
376
+ } else writeEventsToStorage(internals, nextEvents, nextCheckpoint);
377
+ };
378
+ return [snapshotWithEvents, [persistEffect, ...effects]];
379
+ }
380
+ };
381
+ }
382
+ function persistFromLogic(logic, options) {
383
+ if (isEventStrategy(options)) return persistEventFromLogic(logic, options);
384
+ return persistSnapshotFromLogic(logic, options);
385
+ }
386
+ /**
387
+ * Creates a store extension that persists context to storage.
388
+ *
389
+ * @example
390
+ *
391
+ * ```ts
392
+ * import { persist } from '@systemfsoftware/xstate-store/persist';
393
+ *
394
+ * const store = createStore({
395
+ * context: { count: 0 },
396
+ * on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
397
+ * }).with(persist({ name: 'my-store' }));
398
+ * ```
399
+ *
400
+ * @example
401
+ *
402
+ * ```ts
403
+ * // Throttled writes with callbacks
404
+ * const store = createStore({
405
+ * context: { count: 0 },
406
+ * on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
407
+ * }).with(
408
+ * persist({
409
+ * name: 'my-store',
410
+ * throttle: 1000,
411
+ * onDone: (ctx) => console.log('Persisted:', ctx),
412
+ * onError: (err) => console.error('Persist failed:', err)
413
+ * })
414
+ * );
415
+ * ```
416
+ *
417
+ * @example
418
+ *
419
+ * ```ts
420
+ * // Async storage with manual rehydration
421
+ * import {
422
+ * persist,
423
+ * rehydrateStore,
424
+ * createJSONStorage
425
+ * } from '@systemfsoftware/xstate-store/persist';
426
+ *
427
+ * const store = createStore({
428
+ * context: { count: 0 },
429
+ * on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
430
+ * }).with(
431
+ * persist({
432
+ * name: 'my-store',
433
+ * storage: createJSONStorage(() => AsyncStorage),
434
+ * skipHydration: true
435
+ * })
436
+ * );
437
+ *
438
+ * await rehydrateStore(store);
439
+ * ```
440
+ */
441
+ function persist(options) {
442
+ return (logic) => persistFromLogic(logic, options);
443
+ }
444
+ /**
445
+ * Creates a storage adapter with error handling and SSR safety.
446
+ *
447
+ * @example
448
+ *
449
+ * ```ts
450
+ * import { createJSONStorage } from '@systemfsoftware/xstate-store/persist';
451
+ *
452
+ * // Safe for SSR — returns noop storage if localStorage is unavailable
453
+ * const storage = createJSONStorage(() => localStorage);
454
+ *
455
+ * // Async storage (React Native)
456
+ * const asyncStorage = createJSONStorage(() => AsyncStorage);
457
+ * ```
458
+ */
459
+ function createJSONStorage(getStorage) {
460
+ let storage;
461
+ try {
462
+ storage = getStorage();
463
+ } catch {
464
+ return noopStorage;
465
+ }
466
+ return {
467
+ getItem: (name) => {
468
+ try {
469
+ const result = storage?.getItem(name);
470
+ if (result instanceof Promise) return result.catch(() => null);
471
+ return result ?? null;
472
+ } catch {
473
+ return null;
474
+ }
475
+ },
476
+ setItem: (name, value) => {
477
+ try {
478
+ const result = storage?.setItem(name, value);
479
+ if (result instanceof Promise) return result.catch(() => {});
480
+ return result;
481
+ } catch {}
482
+ },
483
+ removeItem: (name) => {
484
+ try {
485
+ const result = storage?.removeItem(name);
486
+ if (result instanceof Promise) return result.catch(() => {});
487
+ return result;
488
+ } catch {}
489
+ }
490
+ };
491
+ }
492
+ /**
493
+ * Removes persisted data from storage for a store that uses the `persist`
494
+ * extension.
495
+ *
496
+ * @example
497
+ *
498
+ * ```ts
499
+ * import { clearStorage } from '@systemfsoftware/xstate-store/persist';
500
+ *
501
+ * clearStorage(store);
502
+ * ```
503
+ */
504
+ function clearStorage(store) {
505
+ const internals = store.getSnapshot()?.[PERSIST_INTERNALS];
506
+ if (!internals) throw new Error("clearStorage: store does not have a persist extension");
507
+ if (internals.flushTimeoutId !== null) {
508
+ clearTimeout(internals.flushTimeoutId);
509
+ internals.flushTimeoutId = null;
510
+ }
511
+ internals.pendingContext = null;
512
+ internals.pendingEvents = null;
513
+ internals.pendingCheckpoint = null;
514
+ return enqueueWrite(internals, () => internals.storage.removeItem(internals.options.name));
515
+ }
516
+ /**
517
+ * Flushes pending throttled updates and waits for already queued async writes.
518
+ * Synchronous storage writes complete before this function returns.
519
+ *
520
+ * @example
521
+ *
522
+ * ```ts
523
+ * import { flushStorage } from '@systemfsoftware/xstate-store/persist';
524
+ *
525
+ * // Force write before page unload
526
+ * window.addEventListener('beforeunload', () => {
527
+ * flushStorage(store);
528
+ * });
529
+ * ```
530
+ */
531
+ function flushStorage(store) {
532
+ const internals = store.getSnapshot()?.[PERSIST_INTERNALS];
533
+ if (!internals) throw new Error("flushStorage: store does not have a persist extension");
534
+ return internals.flush();
535
+ }
536
+ /**
537
+ * Returns whether the store has been hydrated from storage.
538
+ *
539
+ * @example
540
+ *
541
+ * ```ts
542
+ * import { isHydrated } from '@systemfsoftware/xstate-store/persist';
543
+ *
544
+ * if (isHydrated(store)) {
545
+ * // Safe to read persisted state
546
+ * }
547
+ * ```
548
+ */
549
+ function isHydrated(store) {
550
+ return store.getSnapshot()?._persist?.hydrated === true;
551
+ }
552
+ /**
553
+ * Reads persisted state from (potentially async) storage and rehydrates the
554
+ * store. Use this for async storage adapters or when `skipHydration` is
555
+ * `true`.
556
+ *
557
+ * @example
558
+ *
559
+ * ```ts
560
+ * import { rehydrateStore } from '@systemfsoftware/xstate-store/persist';
561
+ *
562
+ * const store = createStore({ ... }).with(persist({
563
+ * name: 'my-store',
564
+ * storage: createJSONStorage(() => AsyncStorage),
565
+ * skipHydration: true
566
+ * }));
567
+ *
568
+ * await rehydrateStore(store);
569
+ * ```
570
+ */
571
+ async function rehydrateStore(store) {
572
+ const internals = store.getSnapshot()?.[PERSIST_INTERNALS];
573
+ if (!internals) throw new Error("rehydrateStore: store does not have a persist extension");
574
+ const data = await internals.storage.getItem(internals.options.name);
575
+ store.send({
576
+ type: PERSIST_REHYDRATE_EVENT_TYPE,
577
+ state: data
578
+ });
579
+ }
580
+ function assertBroadcastChannelAvailable() {
581
+ if (typeof BroadcastChannel === "undefined") throw new Error("createBroadcastStorage: BroadcastChannel is not available in this environment");
582
+ }
583
+ /**
584
+ * Wraps a storage adapter so writes are announced across same-origin tabs.
585
+ *
586
+ * Pair with `subscribeToBroadcastStorage(...)` on persisted stores that should
587
+ * rehydrate when another tab writes to the same storage key.
588
+ */
589
+ function createBroadcastStorage(baseStorage, options) {
590
+ assertBroadcastChannelAvailable();
591
+ const channel = new BroadcastChannel(options?.channel ?? "xstate-store");
592
+ return {
593
+ channel,
594
+ getItem: (name) => baseStorage.getItem(name),
595
+ setItem: (name, value) => {
596
+ const result = baseStorage.setItem(name, value);
597
+ const broadcast = () => {
598
+ channel.postMessage({
599
+ type: "xstate-store-update",
600
+ name
601
+ });
602
+ };
603
+ if (result instanceof Promise) return result.then(broadcast);
604
+ broadcast();
605
+ return result;
606
+ },
607
+ removeItem: (name) => baseStorage.removeItem(name)
608
+ };
609
+ }
610
+ /**
611
+ * Subscribes a persisted store to broadcast storage updates.
612
+ *
613
+ * Returns an unsubscribe function that closes the broadcast listener.
614
+ */
615
+ function subscribeToBroadcastStorage(store) {
616
+ const internals = store.getSnapshot()?.[PERSIST_INTERNALS];
617
+ if (!internals) throw new Error("subscribeToBroadcastStorage: store does not have a persist extension");
618
+ const channel = internals.storage.channel;
619
+ if (typeof BroadcastChannel === "undefined" || !(channel instanceof BroadcastChannel)) throw new Error("subscribeToBroadcastStorage: store storage must be wrapped with createBroadcastStorage()");
620
+ const storeName = internals.options.name;
621
+ const handler = (event) => {
622
+ const data = event.data;
623
+ if (data && data.type === "xstate-store-update" && data.name === storeName) rehydrateStore(store);
624
+ };
625
+ channel.addEventListener("message", handler);
626
+ return () => {
627
+ channel.removeEventListener("message", handler);
628
+ };
629
+ }
630
+ export { clearStorage, createBroadcastStorage, createJSONStorage, flushStorage, isHydrated, persist, rehydrateStore, subscribeToBroadcastStorage };
@@ -0,0 +1,51 @@
1
+ import { B as StoreContext, U as StoreExtension, _ as EventObject, v as EventPayloadMap } from "./types-0n0RpfhK.js";
2
+ interface ResetOptions<TContext extends StoreContext> {
3
+ /**
4
+ * Custom reset function. Receives the initial context and the current
5
+ * context, returns the context to reset to.
6
+ *
7
+ * Defaults to returning the initial context (full reset).
8
+ */
9
+ to?: (initialContext: TContext, currentContext: TContext) => TContext;
10
+ }
11
+ /**
12
+ * Creates a store extension that adds a `reset` trigger to reset the store
13
+ * context to its initial state.
14
+ *
15
+ * @example
16
+ *
17
+ * ```ts
18
+ * import { reset } from '@systemfsoftware/xstate-store/reset';
19
+ *
20
+ * const store = createStore({
21
+ * context: { count: 0 },
22
+ * on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
23
+ * }).with(reset());
24
+ *
25
+ * store.trigger.inc();
26
+ * store.trigger.reset(); // count = 0
27
+ * ```
28
+ *
29
+ * @example
30
+ *
31
+ * ```ts
32
+ * // Partial reset: keep some fields
33
+ * const store = createStore({
34
+ * context: { count: 0, user: null as string | null },
35
+ * on: {
36
+ * inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }),
37
+ * login: (ctx, e: { user: string }) => ({ ...ctx, user: e.user })
38
+ * }
39
+ * }).with(
40
+ * reset({
41
+ * to: (initial, current) => ({ ...initial, user: current.user })
42
+ * })
43
+ * );
44
+ *
45
+ * store.trigger.reset(); // resets count, keeps user
46
+ * ```
47
+ */
48
+ export declare function reset<TContext extends StoreContext, TEventPayloadMap extends EventPayloadMap, TEmitted extends EventObject>(options?: ResetOptions<TContext>): StoreExtension<TContext, TEventPayloadMap, {
49
+ reset: null;
50
+ }, TEmitted>;
51
+ export {};