export type EventPayloadMap = Record; export type ExtractEvents = Values<{ [K in keyof T & string]: T[K] & { type: K; }; }>; type AllKeys = T extends any ? keyof T : never; type EmitterFunction = (...args: { type: TEmittedEvent['type']; } extends TEmittedEvent ? AllKeys extends 'type' ? [] : [DistributiveOmit?] : [DistributiveOmit]) => void; export type EnqueueObject = { emit: { [E in TEmittedEvent as E['type']]: EmitterFunction; }; effect: (fn: () => void) => void; }; export type StoreEffect = (() => void) | TEmitted; export type StoreAssigner = (context: TContext, event: TEvent, enq: EnqueueObject) => TContext | void; export type StoreProducerAssigner = (context: TContext, event: TEvent, enq: EnqueueObject) => void; export type Snapshot = { status: 'active'; output: undefined; error: undefined; } | { status: 'done'; output: TOutput; error: undefined; } | { status: 'error'; output: undefined; error: unknown; } | { status: 'stopped'; output: undefined; error: undefined; }; export type StoreSnapshot = Snapshot & { context: TContext; }; /** * An actor-like object that: * * - Has its own state * - Can receive events * - Is observable */ export interface Store extends Subscribable>, InteropObservable>, BaseAtom> { send: (event: ExtractEvents) => void; getSnapshot: () => StoreSnapshot; /** @alias getSnapshot */ get: () => StoreSnapshot; getInitialSnapshot: () => StoreSnapshot; /** * Subscribes to [inspection events](https://stately.ai/docs/inspection) from * the store. * * Inspectors that call `store.inspect(…)` will immediately receive an * "@xstate.actor" inspection event. */ inspect: (observer: Observer | ((inspectionEvent: StoreInspectionEvent) => void)) => Subscription; sessionId: string; on: (type: TType, handler: (emitted: Compute) => void) => Subscription; /** * A proxy object that allows you to send events to the store without manually * constructing event objects. * * @example * * ```ts * // Equivalent to: * // store.send({ type: 'increment', by: 1 }); * store.trigger.increment({ by: 1 }); * ``` */ trigger: { [K in keyof TEventPayloadMap]: IsEmptyObject> extends true ? () => void : (eventPayload: DistributiveOmit) => void; }; select(selector: Selector, equalityFn?: (a: TSelected, b: TSelected) => boolean): Selection; /** * Returns the next state and effects for the given state and event, as a * tuple. * * @example * * ```ts * const [nextState, effects] = store.transition(store.getSnapshot(), { * type: 'increment', * by: 1 * }); * ``` */ transition: StoreTransition, TEmitted>; /** * Extends the store with additional functionality via a store extension. * * @example * * ```ts * const store = createStore({ * context: { count: 0 }, * on: { inc: (ctx) => ({ count: ctx.count + 1 }) } * }).with(undoRedo()); * * store.trigger.inc(); * store.trigger.undo(); // undoes the increment * ``` */ with(extension: StoreExtension): Store; } export type StoreTransition = (state: StoreSnapshot, event: TEvent) => [StoreSnapshot, StoreEffect[]]; export type StoreConfig = { context: TContext; emits?: { [K in keyof TEmitted]: (payload: TEmitted[K]) => void; }; on: { [K in keyof TEventPayloadMap & string]: StoreAssigner>; }; }; export type SpecificStoreConfig = { context: TContext; emits?: { [E in TEmitted as E['type']]: (payload: E) => void; }; on: { [E in TEvent as E['type']]: StoreAssigner; }; }; type IsEmptyObject = T extends Record ? true : false; type Compute = A extends any ? { [K in keyof A]: A[K]; } : never; export type AnyStore = Store; export type SnapshotFromStore> = TStore extends Store ? StoreSnapshot : never; /** * Extract the type of events from a `Store`. * * @example * * ```ts * const store = createStore( * { count: 0 }, * { * inc: (context, event: { by: number }) => ({ * count: context.count + event.by * }), * dec: (context, event: { by: number }) => ({ * count: context.count - event.by * }) * } * ); * type StoreEvent = EventFromStore; * // ^? { type: 'inc', by: number } | { type: 'dec', by: number } * ``` * * @example * * Using utility types derived from `EventFromStore` to create individual * type-safe event transition functions for a store: * * ```ts * import { * createStore, * type EventFromStore, * type Store * } from '@xstate/store'; * * // Extract the event where `Type` matches the event's `type` from the given * // `Store`. * type EventByType< * TStore extends Store, * Type extends EventFromStore['type'] * > = Extract, { type: Type }>; * * // Extract a specific store event's "input" type (the event type without the * // `type` property). * type EventInputByType< * TStore extends Store, * Type extends EventFromStore['type'] * > = Omit, 'type'>; * * const store = createStore( * { count: 0 }, * { * add: (context, event: { addend: number }) => ({ * count: context.count + event.addend * }), * multiply: (context, event: { multiplier: number }) => ({ * count: context.count * event.multiplier * }) * } * ); * * const add = (input: EventInputByType) => * store.send({ type: 'add', addend: input.addend }); * * add({ addend: 1 }); // sends { type: 'add', addend: 1 } * * const multiply = (input: EventInputByType) => * store.send({ type: 'multiply', multiplier: input.multiplier }); * * multiply({ multiplier: 2 }); // sends { type: 'multiply', multiplier: 2 } * ``` */ export type EventFromStore> = TStore extends Store ? ExtractEvents : never; export interface InteropSubscribable { subscribe(observer: Observer): Subscription; } interface InteropObservable { [Symbol.observable]: () => InteropSubscribable; } export type Observer = { next?: (value: T) => void; error?: (err: unknown) => void; complete?: () => void; }; export interface Subscription { unsubscribe(): void; } export interface Subscribable extends InteropSubscribable { subscribe(observer: Observer): Subscription; subscribe(next: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription; } export type StoreContext = Record; /** The full definition of an event, with a string `type`. */ export type EventObject = { /** The type of event that is sent. */ type: string; }; type Values = T[keyof T]; export type StoreInspectionEvent = StoreInspectedSnapshotEvent | StoreInspectedEventEvent | StoreInspectedActorEvent; interface StoreBaseInspectionEventProperties { rootId: string; /** * The relevant actorRef for the inspection event. * * - For snapshot events, this is the `actorRef` of the snapshot. * - For event events, this is the target `actorRef` (recipient of event). * - For actor events, this is the `actorRef` of the registered actor. */ actorRef: ActorRefLike; } export interface StoreInspectedSnapshotEvent extends StoreBaseInspectionEventProperties { type: '@xstate.snapshot'; event: AnyEventObject; snapshot: Snapshot; } export interface StoreInspectedActionEvent extends StoreBaseInspectionEventProperties { type: '@xstate.action'; action: { type: string; params: Record; }; } export interface StoreInspectedEventEvent extends StoreBaseInspectionEventProperties { type: '@xstate.event'; sourceRef: AnyStore | undefined; event: AnyEventObject; } interface AnyEventObject { type: string; [key: string]: any; } export interface StoreInspectedActorEvent extends StoreBaseInspectionEventProperties { type: '@xstate.actor'; } export type ActorRefLike = { sessionId: string; send: (event: any) => void; getSnapshot: () => any; }; export type Selector = (context: TContext) => TSelected; export type Selection = Readable; export interface Readable extends Subscribable { get: () => T; } export interface BaseAtom extends Subscribable, Readable { } export interface InternalBaseAtom extends Subscribable, Readable { } export interface Atom extends BaseAtom { /** Sets the value of the atom using a function. */ set(fn: (prevVal: T) => T): void; /** Sets the value of the atom. */ set(value: T): void; } export interface AtomOptions { compare?: (prev: T, next: T) => boolean; } export type AnyAtom = BaseAtom; /** * An atom that is read-only and cannot be set. * * @example * * ```ts * const atom = createAtom(() => 42); * // @ts-expect-error - Cannot set a readonly atom * atom.set(43); * ``` */ export interface ReadonlyAtom extends BaseAtom { } /** A version of `Omit` that works with distributive types. */ type DistributiveOmit = T extends any ? Omit : never; export type StoreLogic, TEvent extends EventObject, TEmitted extends EventObject> = { getInitialSnapshot: () => TSnapshot; transition: (snapshot: TSnapshot, event: TEvent) => [TSnapshot, StoreEffect[]]; }; export type AnyStoreLogic = StoreLogic; /** * A store extension that transforms store logic, optionally adding new events. * * @example * * ```ts * const store = createStore({ * context: { count: 0 }, * on: { inc: (ctx) => ({ count: ctx.count + 1 }) } * }).with(undoRedo()); * ``` */ export type StoreExtension = (logic: StoreLogic, ExtractEvents, TEmitted>) => StoreLogic, ExtractEvents | ExtractEvents, TEmitted>; export type AnyStoreConfig = StoreConfig; export type EventFromStoreConfig = TStore extends StoreConfig ? ExtractEvents : never; export type EmitsFromStoreConfig = TStore extends StoreConfig ? ExtractEvents : never; export type ContextFromStoreConfig = TStore extends StoreConfig ? TContext : never; export {};