Skip to content

Commit 0502c04

Browse files
authored
Expose store schemas (#5530)
1 parent ddca0ff commit 0502c04

5 files changed

Lines changed: 74 additions & 4 deletions

File tree

.changeset/short-pugs-bow.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@xstate/store': minor
3+
---
4+
5+
Expose `store.schemas` so integrations can read the store's context, event, and emitted event schemas at runtime.
6+
7+
```ts
8+
const store = createStore({
9+
schemas: {
10+
context: z.object({ count: z.number() }),
11+
events: {
12+
inc: z.object({ by: z.number() })
13+
}
14+
},
15+
context: { count: 0 },
16+
on: {
17+
inc: (context, event) => ({ count: context.count + event.by })
18+
}
19+
});
20+
21+
store.schemas?.events?.inc;
22+
```

packages/xstate-store/src/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ function createStoreCore<
246246
let currentSnapshot: TSnapshot = initialSnapshot;
247247
const atom = createAtom<StoreSnapshot<TContext>>(currentSnapshot);
248248
const eventTypes = logic.eventTypes;
249+
const schemas = logic.schemas;
249250

250251
const emit = (ev: TEmitted) => {
251252
listeners?.get(ev.type)?.forEach((listener) => listener(ev));
@@ -360,6 +361,7 @@ function createStoreCore<
360361
transition(state, event) {
361362
return transition(state as TSnapshot, event);
362363
},
364+
schemas,
363365
sessionId: uniqueId(),
364366
send,
365367
getSnapshot() {

packages/xstate-store/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ export interface Store<
193193
> extends Subscribable<StoreSnapshot<TContext>>,
194194
InteropObservable<StoreSnapshot<TContext>>,
195195
Readable<StoreSnapshot<TContext>> {
196+
/** Standard Schema definitions for this store, if provided. */
197+
readonly schemas?: StoreSchemas<any, any, any>;
196198
send: (event: ExtractEvents<TEventPayloadMap>) => void;
197199
getSnapshot: () => StoreSnapshot<TContext>;
198200
/** Read the current snapshot as a `Readable` value. */

packages/xstate-store/test/store.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,43 @@ it('does not expose atom internals at runtime', () => {
155155
expect('_snapshot' in store).toBe(false);
156156
});
157157

158+
it('exposes schemas at runtime', () => {
159+
const schemas = {
160+
context: z.object({ count: z.number() }),
161+
events: {
162+
inc: z.object({ by: z.number() })
163+
},
164+
emitted: {
165+
increased: z.object({ by: z.number() })
166+
}
167+
};
168+
const store = createStore({
169+
schemas,
170+
context: { count: 0 },
171+
on: {
172+
inc: (context, event, enq) => {
173+
enq.emit.increased({ by: event.by });
174+
return { count: context.count + event.by };
175+
}
176+
}
177+
});
178+
179+
expect(store.schemas).toBe(schemas);
180+
});
181+
182+
it('exposes schemas after extension', () => {
183+
const schemas = {
184+
context: z.object({ count: z.number() })
185+
};
186+
const store = createStore({
187+
schemas,
188+
context: { count: 0 },
189+
on: {}
190+
}).with(reset());
191+
192+
expect(store.schemas).toBe(schemas);
193+
});
194+
158195
it('can be inspected', () => {
159196
const store = createStore({
160197
context: {

packages/xstate-store/test/types.test.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createActor } from 'xstate';
2-
import { createStore, createStoreLogic, fromStore } from '../src/index.ts';
2+
import {
3+
createStore,
4+
createStoreLogic,
5+
fromStore,
6+
type StoreSchemas
7+
} from '../src/index.ts';
38
import { z } from 'zod';
49

510
describe('emitted', () => {
@@ -372,10 +377,11 @@ describe('schemas', () => {
372377
});
373378

374379
it('uses schema-declared context for snapshot typing', () => {
380+
const schemas = {
381+
context: z.object({ count: z.number(), label: z.string() })
382+
};
375383
const store = createStore({
376-
schemas: {
377-
context: z.object({ count: z.number(), label: z.string() })
378-
},
384+
schemas,
379385
context: {
380386
count: 0,
381387
label: 'ready'
@@ -384,6 +390,7 @@ describe('schemas', () => {
384390
});
385391

386392
store.getSnapshot().context.label satisfies string;
393+
store.schemas satisfies StoreSchemas | undefined;
387394

388395
// @ts-expect-error
389396
store.getSnapshot().context.label satisfies number;

0 commit comments

Comments
 (0)