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

@rotorsoft/act

v1.25.0

Published

act core library

Readme

@rotorsoft/act

NPM Version NPM Downloads License: MIT

Event-sourcing framework for TypeScript — three primitives, Zod end to end, no broker required.

Why this package

This is the framework core: the builders (state, slice, projection, act), the port interfaces (Store, Cache, Logger) with bundled in-memory implementations, the orchestrator that runs the correlate → drain loop, and the snapshot/cache layer that keeps load() fast on long streams. Around three primitives — actions (the changes you want to make), state (the data you care about), and reactions (what happens as a result) — it provides input validation against Zod schemas, optimistic-concurrency commit, derived state via patch reducers, fan-out reactions with backoff and dead-lettering, blocked-stream recovery, time-travel queries against the same log.

Your domain stays in TypeScript; your schemas stay in Zod. Pick a store at bootstrap — Postgres (@rotorsoft/act-pg), SQLite (@rotorsoft/act-sqlite), or the bundled in-memory default — and the application code stays the same. The published surface is stable under SemVer as of 1.0.0.

For the project-level overview, see the root README.

Installation

pnpm add @rotorsoft/act

For production, also install one of the durable stores: @rotorsoft/act-pg (Postgres) or @rotorsoft/act-sqlite (SQLite). The bundled InMemoryStore is used by default and is intended for development and tests.

Quick start

import { act, state } from "@rotorsoft/act";
import { z } from "zod";

const Counter = state({ Counter: z.object({ count: z.number() }) })
  .init(() => ({ count: 0 }))
  .emits({ Incremented: z.object({ amount: z.number() }) })
  .patch({ Incremented: ({ data }, s) => ({ count: s.count + data.amount }) })
  .on({ increment: z.object({ by: z.number() }) })
  .emit((action) => ["Incremented", { amount: action.by }])
  .build();

const app = act().withState(Counter).build();

await app.do("increment", { stream: "counter1", actor: { id: "1", name: "u" } }, { by: 5 });
const snap = await app.load(Counter, "counter1");
console.log(snap.state); // { count: 5 }

Define state, declare actions, dispatch, load. Everything else — projections, reactions, slices, cross-process drain, time-travel — is more of the same builder calls.

API

Top-level exports:

  • Buildersstate(), slice(), projection(), act() build the domain. withState, withSlice, withProjection compose them.
  • Portsstore(), cache(), log() are first-call-wins singletons; pass an adapter on first call to override the default. dispose() registers shutdown callbacks.
  • Act orchestratordo, load, query, query_array, query_streams, query_stats, drain, settle, correlate, reset, unblock, blocked_streams, close plus lifecycle events (committed, notified, settled, blocked, closed, …).
  • ErrorsValidationError, InvariantError, ConcurrencyError, StreamClosedError, NonRetryableError plus the Errors constants for string-matching.
  • In-memory adaptersInMemoryStore, InMemoryCache, ConsoleLogger.
  • ConstantsSNAP_EVENT, TOMBSTONE_EVENT.
  • Types — full re-export of port interfaces, builder result types, lifecycle event payloads.

Full type reference: typedoc.

Common patterns

Slices and projections

slice() groups partial state with scoped reactions (vertical-slice architecture); projection() builds read-model updaters. Compose with .withSlice() / .withProjection():

import { projection, slice } from "@rotorsoft/act";

// Projection — read-model updater. Handlers receive (event, stream).
const CounterProjection = projection("counters")
  .on({ Incremented: z.object({ amount: z.number() }) })
    .do(async ({ stream, data }) => { /* update read model */ })
  .build();

// Slice — partial state + scoped reactions. Handlers receive (event, stream, app).
const CounterSlice = slice()
  .withState(Counter)
  .withProjection(CounterProjection)
  .on("Incremented")
    .do(async (event, _stream, app) => { /* dispatch via app */ })
    .to("counter-target")
  .build();

const app = act().withSlice(CounterSlice).build();

Standalone projections (cross-slice events) work at the act() level via .withProjection().

Lifecycle wiring at bootstrap

import { dispose, log, store } from "@rotorsoft/act";
import { PostgresStore } from "@rotorsoft/act-pg";

store(new PostgresStore({ /* … */ }));
await store().seed();

app.on("committed", () => app.settle());          // drain reactions on every commit
app.on("blocked", (xs) => log().error({ xs }));   // page on blocked streams
dispose(async () => { /* your cleanup */ });      // wired into SIGINT/SIGTERM

See the production checklist for the full pre-deploy walkthrough.

Time-travel

await app.load(Counter, "counter1", undefined, { before: 5000 });            // state at event id
await app.load(Counter, "counter1", undefined, { created_before: someDate }); // state at timestamp

Same load() as everything else. The third parameter is a step-through callback that receives each intermediate snapshot during replay.

Recovery loop (operating Act)

When a reaction handler fails past its retry budget (or throws NonRetryableError), the stream is blocked and stays out of claim() results. Operators:

const blocked = await app.blocked_streams();
// Inspect, fix the underlying cause, then:
await app.unblock(["webhooks-out-customer-42"]);
await app.unblock({ stream: "^webhooks-out-" }); // bulk

unblock resumes from where the stream stopped — it does not replay history. Use app.reset(...) only for projection rebuilds.

Compatibility

  • Node: >=22.18.0
  • Peer: zod ^4.4.3
  • Bundled deps: @rotorsoft/act-patch (state reducer)
  • Module formats: ESM + CJS
  • TypeScript: strict mode recommended for full inference

Stability

Public API governed by the Act Stability Charter. The charter names exactly which surfaces are protected by SemVer (builders, Act interface, port interfaces, lifecycle event shapes, public type exports) and what's free to evolve (internal modules, performance characteristics, log formats). Breaking changes require a BREAKING CHANGE: commit footer and a written migration note. Charter is in effect as of 1.0.0; the milestone tracker is milestone 1.0.

Related packages

Documentation

  • Get started — 5-minute walkthrough.
  • Concepts — state management, event sourcing, error handling, real-time, testing, configuration.
  • Architecture — concurrency model, cache + snapshots, correlation + drain, cross-process reactions, priority lanes, close-cycle, schema evolution, extension points.
  • Guides — production checklist, projections to database, external integration, writing a custom store/cache/logger, contributing a new package, contracts CLI.
  • PERFORMANCE.md — measured throughput numbers, optimization history, and the reaction-latency benchmark answering "how long from do() to reaction firing?"
  • BENCH.md — index of every benchmark in the workspace with run commands.

License

MIT