Skip to content

TypeScript Inference Tips

To ensure the best developer experience with Matchina:

  1. Define your transitions inside your createMachine function call
  2. Avoid standalone transition objects: These break TypeScript’s ability to validate your code
  3. Let TypeScript help you: Embrace the rich type checking and autocomplete features

For full type inference to work properly, these transitions should be defined inside createMachine() rather than as standalone objects.

Benefits of this approach:

  • Full type checking between states and transitions
  • Autocomplete for state names in transitions
  • Parameter type checking for transition functions
  • Prevents common errors like typos in state names

When defining transitions as standalone variables, use the satisfies FactoryMachineTransitions<typeof states> to ensure they match the expected type. This provides additional type safety and helps catch errors early.

Compare the type support for inline transitions versus standalone transition objects:

import { function defineStates<Config extends TaggedTypes>(config: Config): StateMatchboxFactory<Config>
`defineStates` creates a type-safe state factory for your state machine. Each key in the config becomes a state constructor, inferring parameters and data shape. Usage: ```ts const states = defineStates({ SomeEmptyState: undefined, // No parameters SomeValueState: "any value here" || 42, // Any value as data SomeStateWithCreate: (...anyParameters) => ({ ...anyPayload }), }); // Usage states.SomeEmptyState().key; // "SomeEmptyState" states.SomeValueState().data; // "any value here" || 42 states.SomeStateWithCreate("param1", "param2").data; // { ...anyPayload } ``` Type benefits: - State keys and data are fully inferred - Pattern matching and type guards are available - Exhaustive match on state keys
@example```typescript const states = defineStates({ Idle: undefined, Loading: (query: string) => ({ query }), Success: (query: string, results: string[]) => ({ query, results }), Error: (query: string, message: string) => ({ query, message }), }); // Usage: states.Idle().key // "Idle" states.Loading("search").data // { query: "search" } states.Success("search", ["a", "b"]).data // { query, results } ```
defineStates
,
function matchina<SF extends KeyedStateFactory, TC extends FactoryMachineTransitions<SF>, FC extends FactoryMachineContext<SF> = {
    ...;
}>(states: SF, transitions: TC, init: KeysWithZeroRequiredArgs<FC["states"]> | FactoryKeyedState<FC["states"]>): import("/Users/winston/dev/personal/matchina/dist/factory-machine-types").FactoryMachine<FC> & import("/Users/winston/dev/personal/matchina/dist/utility-types").DrainOuterGeneric<(object & import("/Users/winston/dev/personal/matchina/dist/utility-types").TUnionToIntersection<import("/Users/winston/dev/personal/matchina/dist/utility-types").FlatMemberUnion<import("/Users/winston/dev/personal/matchina/dist/factory-machine-api-types").StateEventTransitionSenders<import("/Users/winston/dev/personal/matchina/dist/factory-machine-types").FactoryMachine<FC>, keyof FC["transitions"]>>> extends infer T ? { [K in keyof T]: (object & import("/Users/winston/dev/personal/matchina/dist/utility-types").TUnionToIntersection<import("/Users/winston/dev/personal/matchina/dist/utility-types").FlatMemberUnion<import("/Users/winston/dev/personal/matchina/dist/factory-machine-api-types").StateEventTransitionSenders<import("/Users/winston/dev/personal/matchina/dist/factory-machine-types").FactoryMachine<FC>, keyof FC["transitions"]>>>)[K]; } : never) & object>
Creates a strongly-typed state machine using the provided states, transitions, and initial state. Wraps the machine with additional utilities via `assignEventApi` for ergonomic usage.
@templateSF - State factory type@templateTC - Transition table type@templateFC - Machine context type (defaults to `{ states: SF; transitions: TC }`)@paramstates - State factory object defining all possible states.@paramtransitions - Transition table mapping state keys to allowed transitions.@paraminit - Initial state key or state object to start the machine.@returnsA state machine instance with enhanced ergonomics. Example: ```ts import { matchina, effect, guard, defineStates, setup } from "matchina"; const machine = matchina( defineStates({ Idle: () => ({}), Active: (user: string, someCondition: boolean) => ({ user, someCondition }), }), { Idle: { activate: "Active" }, Active: { deactivate: "Idle" }, }, "Idle" ); setup(machine)( effect((ev) => { console.log("Effect triggered for event:", ev.type); }), guard((ev) => ev.to.data.someCondition) ); machine.activate("Alice", true); // Effect runs, guard checks someCondition machine.deactivate(); ```@see{@link assignEventApi } - for ergonomic machine enhancement and setup support@see{@link addEventApi } - for adding event API methods to machines@sourceThis function is a wrapper around `createMachine` that enhances the machine with additional utilities. It provides a more ergonomic API for working with state machines in TypeScript, including event trigger methods and a setup function for adding hooks and enhancers.
matchina
} from "matchina";
const
const states: StateMatchboxFactory<{
    Idle: undefined;
    Playing: (trackId: string) => {
        trackId: string;
    };
    Paused: (trackId: string) => {
        trackId: string;
    };
    Stopped: undefined;
}>
states
=
defineStates<{
    Idle: undefined;
    Playing: (trackId: string) => {
        trackId: string;
    };
    Paused: (trackId: string) => {
        trackId: string;
    };
    Stopped: undefined;
}>(config: {
    Idle: undefined;
    Playing: (trackId: string) => {
        trackId: string;
    };
    Paused: (trackId: string) => {
        trackId: string;
    };
    Stopped: undefined;
}): StateMatchboxFactory<{
    Idle: undefined;
    Playing: (trackId: string) => {
        trackId: string;
    };
    Paused: (trackId: string) => {
        trackId: string;
    };
    Stopped: undefined;
}>
`defineStates` creates a type-safe state factory for your state machine. Each key in the config becomes a state constructor, inferring parameters and data shape. Usage: ```ts const states = defineStates({ SomeEmptyState: undefined, // No parameters SomeValueState: "any value here" || 42, // Any value as data SomeStateWithCreate: (...anyParameters) => ({ ...anyPayload }), }); // Usage states.SomeEmptyState().key; // "SomeEmptyState" states.SomeValueState().data; // "any value here" || 42 states.SomeStateWithCreate("param1", "param2").data; // { ...anyPayload } ``` Type benefits: - State keys and data are fully inferred - Pattern matching and type guards are available - Exhaustive match on state keys
@example```typescript const states = defineStates({ Idle: undefined, Loading: (query: string) => ({ query }), Success: (query: string, results: string[]) => ({ query, results }), Error: (query: string, message: string) => ({ query, message }), }); // Usage: states.Idle().key // "Idle" states.Loading("search").data // { query: "search" } states.Success("search", ["a", "b"]).data // { query, results } ```
defineStates
({
type Idle: undefinedIdle: var undefinedundefined,
type Playing: (trackId: string) => {
    trackId: string;
}
Playing
: (trackId: stringtrackId: string) => ({ trackId: stringtrackId }),
type Paused: (trackId: string) => {
    trackId: string;
}
Paused
: (trackId: stringtrackId: string) => ({ trackId: stringtrackId }),
type Stopped: undefinedStopped: var undefinedundefined, }); // ✅ GOOD PRACTICE: Full type inference with inline definitions const
const machine: FactoryMachine<{
    states: StateMatchboxFactory<{
        Idle: undefined;
        Playing: (trackId: string) => {
            trackId: string;
        };
        Paused: (trackId: string) => {
            trackId: string;
        };
        Stopped: undefined;
    }>;
    transitions: {
        ...;
    };
}> & {
    ...;
} & object
machine
=
matchina<StateMatchboxFactory<{
    Idle: undefined;
    Playing: (trackId: string) => {
        trackId: string;
    };
    Paused: (trackId: string) => {
        trackId: string;
    };
    Stopped: undefined;
}>, {
    ...;
}, {
    ...;
}>(states: StateMatchboxFactory<...>, transitions: {
    ...;
}, init: StateMatchbox<...> | ... 3 more ... | KeysWithZeroRequiredArgs<...>): import("/Users/winston/dev/personal/matchina/dist/factory-machine-types").FactoryMachine<{
    ...;
}> & import("/Users/winston/dev/personal/matchina/dist/utility-types").DrainOuterGeneric<(object & import("/Users/winston/dev/personal/matchina/dist/utility-types").TUnionToIntersection<import("/Users/winston/dev/personal/matchina/dist/utility-types").FlatMemberUnion<import("/Users/winston/dev/personal/matchina/dist/factory-machine-api-types").StateEventTransitionSenders<import("/Users/winston/dev/personal/matchina/dist/factory-machine-types").FactoryMachine<{
    ...;
}>, "Idle" | ... 1 more ... | "Paused">>> extends infer T ? { [K in keyof T]: (object & import("/Users/winston/dev/personal/matchina/dist/utility-types").TUnionToIntersection<import("/Users/winston/dev/personal/matchina/dist/utility-types").FlatMemberUnion<import("/Users/winston/dev/personal/matchina/dist/factory-machine-api-types").StateEventTransitionSenders<import("/Users/winston/dev/personal/matchina/dist/factory-machine-types").FactoryMachine<{
    ...;
}>, "Idle" | ... 1 more ... | "Paused">>>)[K]; } : never) & object>
Creates a strongly-typed state machine using the provided states, transitions, and initial state. Wraps the machine with additional utilities via `assignEventApi` for ergonomic usage.
@templateSF - State factory type@templateTC - Transition table type@templateFC - Machine context type (defaults to `{ states: SF; transitions: TC }`)@paramstates - State factory object defining all possible states.@paramtransitions - Transition table mapping state keys to allowed transitions.@paraminit - Initial state key or state object to start the machine.@returnsA state machine instance with enhanced ergonomics. Example: ```ts import { matchina, effect, guard, defineStates, setup } from "matchina"; const machine = matchina( defineStates({ Idle: () => ({}), Active: (user: string, someCondition: boolean) => ({ user, someCondition }), }), { Idle: { activate: "Active" }, Active: { deactivate: "Idle" }, }, "Idle" ); setup(machine)( effect((ev) => { console.log("Effect triggered for event:", ev.type); }), guard((ev) => ev.to.data.someCondition) ); machine.activate("Alice", true); // Effect runs, guard checks someCondition machine.deactivate(); ```@see{@link assignEventApi } - for ergonomic machine enhancement and setup support@see{@link addEventApi } - for adding event API methods to machines@sourceThis function is a wrapper around `createMachine` that enhances the machine with additional utilities. It provides a more ergonomic API for working with state machines in TypeScript, including event trigger methods and a setup function for adding hooks and enhancers.
matchina
(
const states: StateMatchboxFactory<{
    Idle: undefined;
    Playing: (trackId: string) => {
        trackId: string;
    };
    Paused: (trackId: string) => {
        trackId: string;
    };
    Stopped: undefined;
}>
states
,
{
type Idle: {
    start: "Playing";
}
Idle
: {
// TypeScript will auto-complete state names start: "Playing"start: "Playing", },
type Playing: {
    pause: "Paused";
    stop: "Stopped";
}
Playing
: {
// TypeScript will error if the state doesn't exist pause: "Paused"pause: "Paused", stop: "Stopped"stop: "Stopped", },
type Paused: {
    resume: "Playing";
}
Paused
: {
resume: "Playing"resume: "Playing", }, }, "Idle" );
const machine: FactoryMachine<{
    states: StateMatchboxFactory<{
        Idle: undefined;
        Playing: (trackId: string) => {
            trackId: string;
        };
        Paused: (trackId: string) => {
            trackId: string;
        };
        Stopped: undefined;
    }>;
    transitions: {
        ...;
    };
}> & {
    ...;
} & object
machine
.st
  • start
  • states
  • stop
start: (trackId: string) => voidart("track-123"); // Idle -> Playing
// nice!

Following these practices will help you catch errors earlier, write more reliable code, and get the most out of Matchina’s TypeScript integration.