Skip to content

Commit 77ec4ad

Browse files
authored
[@xstate/store] Add store.with(…) for store extensions (#5427)
* Add store.with(…) extension, deprecate inline undoRedo * Changeset * Use event payload map more consistently, fix go-to-def for undo/redo
1 parent 2fd7ca6 commit 77ec4ad

File tree

6 files changed

+664
-880
lines changed

6 files changed

+664
-880
lines changed

.changeset/odd-wombats-thank.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@xstate/store': minor
3+
---
4+
5+
Add `.with()` method for store extensions.
6+
7+
```ts
8+
import { createStore } from '@xstate/store';
9+
import { undoRedo } from '@xstate/store/undo';
10+
11+
const store = createStore({
12+
context: { count: 0 },
13+
on: {
14+
inc: (ctx) => ({ count: ctx.count + 1 }),
15+
dec: (ctx) => ({ count: ctx.count - 1 })
16+
}
17+
}).with(undoRedo());
18+
19+
store.trigger.inc(); // count = 1
20+
21+
// Added from the undoRedo extension
22+
store.trigger.undo(); // count = 0
23+
store.trigger.redo(); // count = 1
24+
```

packages/xstate-store/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ export type {
3737
ReadonlyAtom,
3838
EventFromStoreConfig,
3939
EmitsFromStoreConfig,
40-
ContextFromStoreConfig
40+
ContextFromStoreConfig,
41+
StoreExtension
4142
} from './types';

packages/xstate-store/src/store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ function createStoreCore<
186186
return createAtom(() => selector(store.get().context), {
187187
compare: equalityFn
188188
});
189+
},
190+
with(extension) {
191+
const extendedLogic = extension(logic as any);
192+
return createStoreCore(extendedLogic) as any;
189193
}
190194
};
191195

packages/xstate-store/src/types.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ export interface Store<
146146
ExtractEvents<TEventPayloadMap>,
147147
TEmitted
148148
>;
149+
/**
150+
* Extends the store with additional functionality via a store extension.
151+
*
152+
* @example
153+
*
154+
* ```ts
155+
* const store = createStore({
156+
* context: { count: 0 },
157+
* on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
158+
* }).with(undoRedo());
159+
*
160+
* store.trigger.inc();
161+
* store.trigger.undo(); // undoes the increment
162+
* ```
163+
*/
164+
with<TNewEventPayloadMap extends EventPayloadMap>(
165+
extension: StoreExtension<
166+
TContext,
167+
TEventPayloadMap,
168+
TNewEventPayloadMap,
169+
TEmitted
170+
>
171+
): Store<TContext, TEventPayloadMap & TNewEventPayloadMap, TEmitted>;
149172
}
150173

151174
export type StoreTransition<
@@ -443,6 +466,35 @@ export type StoreLogic<
443466
};
444467
export type AnyStoreLogic = StoreLogic<any, any, any>;
445468

469+
/**
470+
* A store extension that transforms store logic, optionally adding new events.
471+
*
472+
* @example
473+
*
474+
* ```ts
475+
* const store = createStore({
476+
* context: { count: 0 },
477+
* on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
478+
* }).with(undoRedo());
479+
* ```
480+
*/
481+
export type StoreExtension<
482+
TContext extends StoreContext,
483+
TEventPayloadMap extends EventPayloadMap,
484+
TNewEventPayloadMap extends EventPayloadMap,
485+
TEmitted extends EventObject
486+
> = (
487+
logic: StoreLogic<
488+
StoreSnapshot<TContext>,
489+
ExtractEvents<TEventPayloadMap>,
490+
TEmitted
491+
>
492+
) => StoreLogic<
493+
StoreSnapshot<TContext>,
494+
ExtractEvents<TEventPayloadMap> | ExtractEvents<TNewEventPayloadMap>,
495+
TEmitted
496+
>;
497+
446498
export type AnyStoreConfig = StoreConfig<any, any, any>;
447499
export type EventFromStoreConfig<TStore extends AnyStoreConfig> =
448500
TStore extends StoreConfig<any, infer TEventPayloadMap, any>

0 commit comments

Comments
 (0)