Build your first machine
Create and run a typed counter workflow from an empty TypeScript project.
In this tutorial, we will build a counter workflow that starts in Ready, accepts increments in
Counting, and completes in Done. Running it will print three observable snapshots:
initial: { _tag: 'Ready', count: 1 }
counting: { _tag: 'Counting', count: 4 }
completed: { _tag: 'Done', count: 4 }By the end, you will have defined a machine's data and behavior, started a scoped instance, sent events to it, and read its completion value.
Prerequisites
Start in an empty directory with Node.js 26 and pnpm 11 available. This tutorial uses
effect-state-machine, effect, TypeScript, and tsx.
Create the project
Initialize the project and install the pinned dependencies:
pnpm init
pnpm add effect-state-machine effect
pnpm add --save-dev typescript tsxCreate index.ts with the following program:
/** Complete program used by the first-machine documentation tutorial. */
import * as Console from "effect/Console"
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
import * as Machine from "effect-state-machine/Machine"
import * as MachineEngine from "effect-state-machine/MachineEngine"
const Input = Schema.Struct({ initialCount: Schema.Number })
const State = Machine.taggedUnion({
Ready: {
fields: { count: Schema.Number },
description: "Wait for counting to start.",
},
Counting: {
fields: { count: Schema.Number },
description: "Accept counter updates.",
},
Done: {
fields: { count: Schema.Number },
description: "Finish with the final count.",
},
})
const Event = Machine.taggedUnion({
Start: { fields: {} },
Increment: { fields: { amount: Schema.Number } },
Finish: { fields: {} },
})
const counter = Machine.builder({ input: Input, state: State, event: Event })
const definition = counter.define(
{
id: "counter",
idempotencyKey: ({ initialCount }) => String(initialCount),
initial: ({ initialCount }) => ({ _tag: "Ready", count: initialCount }),
},
{
Ready: counter.state({
Start: {
target: "Counting",
reduce: ({ state }) => ({ count: state.count }),
},
}),
Counting: counter.state({
Increment: {
stay: ({ state, event }) => ({ count: state.count + event.amount }),
},
Finish: {
target: "Done",
reduce: ({ state }) => ({ count: state.count }),
},
}),
Done: counter.final(),
},
)
const program = Effect.scoped(
Effect.gen(function* () {
const handle = yield* definition.run({ initialCount: 1 })
const initial = yield* handle.snapshot
yield* Console.log("initial:", initial)
yield* handle.send({ _tag: "Start" })
yield* handle.send({ _tag: "Increment", amount: 3 })
const counting = yield* handle.snapshot
yield* Console.log("counting:", counting)
yield* handle.send({ _tag: "Finish" })
const completed = yield* handle.completion
yield* Console.log("completed:", completed)
}),
).pipe(Effect.provide(MachineEngine.layerMemory()))
await Effect.runPromise(program)
Notice that the definition record has exactly one key for every state Schema tag. Each key narrows
state and event inside its handlers. stay updates Counting without exiting and re-entering it.
idempotencyKey derives one logical machine identity from the input. The explicit memory layer is
the persistence choice for this tutorial; one layer value shares its volatile database across every
run beneath it.
Run the machine
Execute the program:
pnpm exec tsx index.tsThe output should look like this:
initial: { _tag: 'Ready', count: 1 }
counting: { _tag: 'Counting', count: 4 }
completed: { _tag: 'Done', count: 4 }The first snapshot comes from initial. Each call to send waits until its event has been
processed. Entering the Done final state resolves completion with that final state value.
Explore the same machine in Studio
Select Start counting, Increment by 3, and Finish in order. Notice that the active node, state value, and History panel follow the three snapshots printed by the program.
You can now define and run a machine with ordinary transitions. Continue with Run an Effect when a state becomes active when a state must own asynchronous work, or consult the generated Machine API for the complete builder surface.