Skip to main content

@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 (includes msTimestamp, blockHeight, etc.).
  • delegate-wallet helpers — 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/builtinPrimitiveTypeERC20, 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

Runnable: test/examples.test.ts.