Type-Safe State Matchina
Build strongly-typed state machines with powerful pattern matching
const const states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
states = defineStates<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>(config: {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}): StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
`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 keysdefineStates({
type Fee: undefined
Fee: var undefined
undefined,
type Fi: () => {}
Fi: () => ({}),
type Fo: (name: string) => {
name: string;
}
Fo: (name: string
name: string) => ({ name: string
name }),
type Fum: (name: string, age: number) => {
name: string;
age: number;
}
Fum: (name: string
name: string, age: number
age: number) => ({ name: string
name, age: number
age }),
});
const const fo: StateMatchbox<"Fo", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
fo = const states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
states.F- Fee
- Fi
- Fo
- Fum
type Fo: (name: string) => StateMatchbox<"Fo", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
o("John");
const fo: StateMatchbox<"Fo", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
fo.key: "Fo"
key; // "Fo"
const fo: StateMatchbox<"Fo", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
fo.MatchboxMemberApi<{ Fee: undefined; Fi: () => {}; Fo: (name: string) => { name: string; }; Fum: (name: string, age: number) => { name: string; age: number; }; }, "key">.is: <"Fo">(key: "Fo") => this is MatchboxMember<T, DataSpecs, TagProp>
is("Fo"); // true
const fo: StateMatchbox<"Fo", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
fo.data: {
name: string;
}
data; // { name: "John" }
export const { const name: string
name } = const fo: StateMatchbox<"Fo", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
fo.data: {
name: string;
}
data; // { name: "John" }
export const { const age: number
age } = const states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
states.type Fum: (name: string, age: number) => StateMatchbox<"Fum", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
Fum("Jane", 30).data: {
name: string;
age: number;
}
data; // { name: "Jane", age: 30 }
const const giant: FactoryMachine<{
states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
transitions: {
...;
};
}> & {
...;
} & object
giant = matchina<StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>, {
Fee: {
...;
};
Fi: {
...;
};
Fo: {
...;
};
Fum: {
...;
};
}, {
...;
}>(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<{
...;
}>, "Fee" | ... 2 more ... | "Fum">>> 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<{
...;
}>, "Fee" | ... 2 more ... | "Fum">>>)[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.matchina(
const states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>
states,
{
type Fee: {
toFi: "Fi";
}
Fee: { toFi: "Fi"
toFi: "Fi" },
type Fi: {
toFo: "Fo";
}
Fi: { toFo: "Fo"
toFo: "Fo" },
type Fo: {
toFum: "Fum";
}
Fo: { toFum: "Fum"
toFum: "Fum" },
type Fum: {
toFee: "Fee";
}
Fum: { toFee: "Fee"
toFee: "Fee" },
},
"Fee"
);
const giant: FactoryMachine<{
states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
transitions: {
...;
};
}> & {
...;
} & object
giant.function getState(): (({
key: "Fi";
data: {};
} & MatchboxMemberApi<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}, "key">) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>)) | (({
key: "Fi";
data: {};
} & MatchboxMemberApi<...>) | ... 2 more ... | ({
...;
} & MatchboxMemberApi<...>))
Returns the current state of the machine (the `to` property of the last change).getState().MatchboxMemberApi<DataSpecs, TagProp extends string>.is: <"Fee">(key: "Fee") => this is MatchboxMember<T, DataSpecs, TagProp>
is("Fee"); // true
const giant: FactoryMachine<{
states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
transitions: {
...;
};
}> & {
...;
} & object
giant.toFi: () => void
toFi();
const giant: FactoryMachine<{
states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
transitions: {
...;
};
}> & {
...;
} & object
giant.toFo: (name: string) => void
toFo("John");
const giant: FactoryMachine<{
states: StateMatchboxFactory<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
transitions: {
...;
};
}> & {
...;
} & object
giant.toFum: (name: string, age: number) => void
toFum("John", 30);
const const nickname: string
nickname = const giant: FactoryMachine<{
states: {
Fee: () => StateMatchbox<"Fee", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
Fi: () => StateMatchbox<"Fi", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
Fo: (name: string) => StateMatchbox<"Fo", {
...;
}>;
Fum: (name: string, age: number) => StateMatchbox<"Fum", {
...;
}>;
};
transitions: {
...;
};
}> & {
...;
} & object
giant.function getState(): (({
key: "Fi";
data: {};
} & MatchboxMemberApi<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}, "key">) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>)) | (({
key: "Fi";
data: {};
} & MatchboxMemberApi<...>) | ... 2 more ... | ({
...;
} & MatchboxMemberApi<...>))
Returns the current state of the machine (the `to` property of the last change).getState().MatchboxMemberApi<DataSpecs, TagProp extends string>.match<string>(cases: MatchCases<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}, string, true>, exhaustive?: boolean): string (+1 overload)
match({
type Fee: () => string
Fee: () => "Fee state",
type Fi: () => string
Fi: () => "Fi state",
type Fo: (data: {
name: string;
}) => string
Fo: (data: {
name: string;
}
data) => `Fo state with name ${data: {
name: string;
}
data.name: string
name}`,
type Fum: (data: {
name: string;
age: number;
}) => string
Fum: (data: {
name: string;
age: number;
}
data) => `Fum state with name ${data: {
name: string;
age: number;
}
data.name: string
name} and age ${data: {
name: string;
age: number;
}
data.age: number
age}`,
});
const const state: ({
key: "Fi";
data: {};
} & MatchboxMemberApi<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}, "key">) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>)
state = const giant: FactoryMachine<{
states: {
Fee: () => StateMatchbox<"Fee", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
Fi: () => StateMatchbox<"Fi", {
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}>;
Fo: (name: string) => StateMatchbox<"Fo", {
...;
}>;
Fum: (name: string, age: number) => StateMatchbox<"Fum", {
...;
}>;
};
transitions: {
...;
};
}> & {
...;
} & object
giant.function getState(): (({
key: "Fi";
data: {};
} & MatchboxMemberApi<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}, "key">) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>)) | (({
key: "Fi";
data: {};
} & MatchboxMemberApi<...>) | ... 2 more ... | ({
...;
} & MatchboxMemberApi<...>))
Returns the current state of the machine (the `to` property of the last change).getState();
if (const state: ({
key: "Fi";
data: {};
} & MatchboxMemberApi<{
Fee: undefined;
Fi: () => {};
Fo: (name: string) => {
name: string;
};
Fum: (name: string, age: number) => {
name: string;
age: number;
};
}, "key">) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>) | ({
...;
} & MatchboxMemberApi<...>)
state.MatchboxMemberApi<DataSpecs, TagProp extends string>.is: <"Fum">(key: "Fum") => this is MatchboxMember<T, DataSpecs, TagProp>
is("Fum")) {
const { const name: string
name, const age: number
age } = const state: {
...;
} & MatchboxMemberApi<...>
state.data: {
name: string;
age: number;
}
data;
var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without calling `require('console')`.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(`Fum state with name ${const name: string
name} and age ${const age: number
age}`);
}
Quick Installation
Section titled “Quick Installation”npm install matchina
Get Started
Section titled “Get Started” 📚 Quick Start Guide Get up and running with Matchina in minutes
⚙️ State Machines Guide Learn how to build complex state machines with type safety
⏳ Promise Machines Guide Manage async operations with type-safe promise machines
🛡️ Type Guards Guide Understand type-safe state handling with Matchina
⚛️ React Integration Guide Integrate state machines with React components
🧩 Matchbox Factories Create type-safe tagged union factories with pattern matching
Explore Examples
Section titled “Explore Examples” 🔄 Toggle Simple on/off toggle state machine
📡 Data Fetching Type-safe async data fetching
🚦 Traffic Light State machine with automatic transitions