npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@effectstream/runtime

v0.101.1

Published

State machine runtime for EffectStream

Readme

@effectstream/runtime

The state-machine runtime - the loop that ties sync, state machine, database, events, and HTTP API together inside an EffectStream node. Boot it with init() then drive it with start(config) and your node is up.

  • The state-machine runtime that owns an Effectstream node's process model.
  • init() once, start(config) to run; ties sync, state machine, DB, events, and HTTP API together.
  • Used by every template.
  • Also reachable through @effectstream/node-sdk/runtime.

Install

bun add @effectstream/runtime
# or
npm install @effectstream/runtime

@effectstream/runtime is the canonical import for init and start, used directly by every template. The same surface is also reachable through @effectstream/node-sdk/runtime if you prefer the umbrella.

Standalone usage

This package owns the node's process model. You don't pick parts of it; you call init() once at boot and then start(config). The config brings together everything else:

init() and start() are Effection operations, so they must be yielded inside an Effection main():

import { main } from "effection";
import { init, start } from "@effectstream/runtime";
import { Stm } from "@effectstream/sm";
import { config } from "./config.dev.ts";

await main(function* () {
  yield* init();

  const gameStm = new Stm(grammar);
  gameStm.addStateTransition("join", function* () { /* ... */ });

  yield* start({
    config,
    gameStateTransitions: [gameStm],
    apiRouter: undefined,           // optional Fastify route plugin
    migrations: [],                 // SQL migrations
  });
});

While running, the runtime:

  • Reads finalized blocks via @effectstream/sync.
  • Routes batcher inputs through your registered Stms.
  • Commits all yielded SQL inside a per-block transaction.
  • Publishes lifecycle and app events via @effectstream/event-server.
  • Serves the optional Fastify API.
  • Emits OpenTelemetry traces and logs through @effectstream/log.

Inside EffectStream

@effectstream/runtime is the conductor: it doesn't define any chain integrations or queries itself, but every other node package only gets exercised when this loop is running. The StartConfig shape determines which DB migrations apply, which state machines fire, and whether the runtime exposes a Fastify router.

Empty-block coalescing (catch-up)

The runtime replays one Effectstream block per main-clock tick, each in its own DB transaction. During a deep catch-up over an idle gap (e.g. an NTP main chain at one block/second across days), most of those blocks are empty — no on-chain content, no scheduled input, no migration — yet still cost a full BEGIN…COMMIT round-trip. Empty-block coalescing folds a run of consecutive empty blocks into a single transaction that writes only the run's endpoint. It is opt-in and off by default:

EFFECTSTREAM_COALESCE_EMPTY_BLOCKS=true

The lag threshold that defines "behind the chain tip" defaults to 20× the main clock's block time (falling back to 60 s when no blockTimeMS is exposed by the network config). Override it with EFFECTSTREAM_LAG_THRESHOLD_MS (milliseconds), e.g. for chains whose main clock fires faster than the runtime can keep up with one transaction per block:

EFFECTSTREAM_LAG_THRESHOLD_MS=30000   # engage coalescing past 30 s of lag

Behavior and guarantees:

  • Catch-up only. Coalescing engages only while behind the chain tip (now - block.timestamp greater than the lag threshold) and disengages at the tip, so steady-state stays one block per transaction.
  • Identical database. A coalesced sync produces a byte-for-byte identical database to a non-coalesced one. Empty blocks already never advance the block hash or RNG seed, so only the endpoint's block row and the per-protocol resume watermark are written — exactly as the normal path would leave them.
  • Never skips work. A run is flushed before any block that has on-chain content, a migration at its height, or a due block-height/timestamp-scheduled input — so nothing that mutates state is ever folded away.
  • Tip stays visible. If the producer goes quiet on an empty tail, the run is flushed on a short idle timeout so the latest height still commits.
  • A run is also capped (MAX_EMPTY_RUN) to bound transaction size and recovery.

Implementation: src/coalesce.ts (the createEmptyBlockCoalescer coordinator the main loop drives).

Key exports

  • init() - Operation<void>. One-shot setup: OpenTelemetry, config validation, version pinning. Call before start.
  • start(config: StartConfig) - Operation<void>. Run the node loop until cancelled.

Types and helpers re-exported alongside init / start:

  • DBMigrations - versioned SQL migrations passed into start.
  • StartConfig, StartConfigGameStateTransitions, StartConfigApiRouter - start's config types. Templates type-check against these implicitly but don't usually import them by name.
  • PrimitiveConstructor<T> - extension point for new primitives.
  • VERSION - ${number}.${number}.${number} literal type for version pinning.
  • Pagination helpers re-exported from ./api/pagination.ts.

Examples

The templates under templates/ are full working init() + start() examples. The simplest is templates/minimal/.

End-to-end EVM sync test: e2e/evm/sync/.

Runnable: test/examples.test.ts.

Links

  • Docs: https://effectstream.github.io/docs/packages/node/runtime
  • Source: https://github.com/effectstream/effectstream/tree/main/packages/node-sdk/runtime