@effectstream/sm
Package:
@effectstream/sm· Source
The state-machine DSL inside an EffectStream node. Define a typed grammar
of commands, register one generator per command, and Stm parses each
incoming batcher input, dispatches it to the right handler, and yields
SQL updates through the runtime.
Install
bun add @effectstream/sm
# or
npm install @effectstream/sm
Usage
This package pairs with @effectstream/runtime,
which drives a per-block loop that calls stm.processInput(...) for every
batcher subunit, collects the SQL yielded by your generators, and commits
it inside a per-block postgres transaction. You author the DSL here;
the runtime executes it.
The DSL is also directly testable in a pure-TS unit test (parse + dispatch
without a database) — see
primitives/src/evm-erc20/erc20-primitive.test.ts.
import { Stm } from "@effectstream/sm";
import { World } from "@effectstream/coroutine";
import { Type } from "@sinclair/typebox";
import { join, leave } from "./queries.ts"; // pgtyped queries
const grammar = {
join: [["user", Type.String()]] as const,
leave: [["user", Type.String()]] as const,
} as const;
const stm = new Stm(grammar);
stm.addStateTransition("join", function* ({ parsedInput, msTimestamp }) {
yield* World.resolve(join, { user: parsedInput.user, ts: msTimestamp });
});
stm.addStateTransition("leave", function* ({ parsedInput }) {
yield* World.resolve(leave, { user: parsedInput.user });
});
The runtime calls stm.processInput(input) for every batcher subunit; the
DSL parses against grammar, finds the right handler, and the generator
yields World.resolve(...) so the runtime can execute the pgtyped queries.
Inside EffectStream
Stm is the central piece a node author writes. The runtime's per-block
loop wires each user input through the corresponding Stm instance,
collects yielded SQL, and commits it inside the per-block transaction.
The built-in primitives package (@effectstream/sm/builtin) covers
common on-chain events — ERC-20/721/1155 transfers, Cardano transfers,
Midnight events, etc. — so you don't re-implement them.
Key exports
Stm<Grammar, Events>— the state machine..addStateTransition(prefix, handler),.processInput(input),.grammar,.fullJsonGrammar,.keyedJsonGrammar.ParamToData<Params>— derives the typed argument shape from a grammar entry.BaseStfInput— the input shape passed to every handler (includesmsTimestamp,blockHeight, etc.).delegate-wallethelpers — account delegation primitives reused by built-ins.
MessageListener<Events, Params> is exported as the handler type but is
inferred at call sites rather than imported directly.
Subpath exports:
@effectstream/sm/builtin—PrimitiveTypeERC20,PrimitiveTypeERC721,PrimitiveTypeERC1155,PrimitiveTypeCardanoTransfer,PrimitiveTypeMidnightGeneric, and many more (20+ chain-specific event tags).@effectstream/sm/grammar— the underlying grammar/parsing utilities (also re-exported from@effectstream/concise).
Examples
primitives/src/evm-erc20/erc20-primitive.test.ts— a real primitive's behavior unit-tested.- Game logic in
templates/dice/packages/node/shows the fullnew Stm(...).addStateTransition(...)pattern.
Runnable: test/examples.test.ts.