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

@seike460/minamo

v0.3.0

Published

Type-safe CQRS+ES for AWS Serverless

Readme

minamo

English | 日本語

npm version CI Node.js ≥24 License: MIT

Type-safe CQRS + Event Sourcing for AWS Serverless.

minamo is a CQRS + Event Sourcing library for TypeScript / Node 24 / AWS SDK v3 that stays thin, strict, and lets you write the write side in your domain's own words. It exposes exactly four public surfaces — Aggregate, Command, Event, and Projection Bridge — and never takes AWS primitives out of your hands.

  • No SLA. Production guarantees are the consumer's responsibility
  • Single-maintainer (@seike460). Pull requests welcome — see GOVERNANCE.md for roles and the path to becoming a co-maintainer
  • MIT License

Status: published on npm as @seike460/minamo (see the npm badge above for the latest version). The public API follows docs/concept.md §5 verbatim. The path to v1 is tracked in docs/roadmap-v1.md.


Why minamo?

Doing CQRS + Event Sourcing on DynamoDB + Lambda + TypeScript? The write side is where correctness is hardest — optimistic locking, retry-from-load, and keeping your tests honest against production. minamo solves exactly that, and nothing more:

  • DynamoDB-first, not multi-DB. TransactWriteItems + ConsistentRead are baked into the design, not hidden behind a portable adapter.
  • Full-cycle retry as a core API. executeCommand owns Load → Rehydrate → Decide → Append and retries the whole cycle on ConcurrencyError — the wiring hand-rolled implementations get wrong.
  • InMemory ⇄ DynamoDB parity, guaranteed. Both stores run the same Contract Tests, so your tests can't pass while production breaks.
  • One runtime dependency. AWS SDK v3 (optional peer). Minimal cold-start and dependency surface.

Full comparison with castore and @ocoda/event-sourcing: docs/concept.md §7. Not sure if you need CQRS+ES at all? §1–§2 of the concept doc help you decide.


Install

pnpm add @seike460/minamo
# AWS SDK v3 is an optional peer dependency — only needed when you use DynamoEventStore
pnpm add @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb @aws-sdk/util-dynamodb
  • "type": "module", ESM only. Node ≥ 24
  • Include the .js extension in import paths (verbatimModuleSyntax)

Quick Start — InMemory in one minute

import {
  type AggregateConfig,
  type CommandHandler,
  InMemoryEventStore,
  executeCommand,
} from "@seike460/minamo";

type CounterEvents = {
  Incremented: { amount: number };
};

const counter: AggregateConfig<number, CounterEvents> = {
  initialState: 0,
  evolve: {
    Incremented: (state, data) => state + data.amount,
  },
};

const increment: CommandHandler<number, CounterEvents, { amount: number }> = (agg, input) => {
  if (input.amount === 0) return [];
  return [{ type: "Incremented", data: { amount: input.amount } }];
};

const store = new InMemoryEventStore<CounterEvents>();
const { aggregate } = await executeCommand({
  config: counter,
  store,
  handler: increment,
  aggregateId: "counter-1",
  input: { amount: 5 },
});

console.log(aggregate.state); // 5

InMemoryEventStore is for tests and local exploration. In production you swap it for DynamoEventStore. Both implementations run the same Contract Tests, so behavioural drift between them is structurally constrained.

Production — switch to DynamoEventStore

import { DynamoEventStore } from "@seike460/minamo";

const store = new DynamoEventStore<CounterEvents>({
  tableName: "events",
  // Use clientConfig when you need region / credentials, or
  // pass a pre-built DocumentClient via `client` (see API reference)
});

await executeCommand({
  config: counter,
  store,
  handler: increment,
  aggregateId: "counter-1",
  input: { amount: 5 },
});

Table schema (concept.md §3 / C11):

| attribute | kind | type | |---|---|---| | aggregateId | PK (HASH) | S | | version | SK (RANGE) | N |

Enable StreamViewType=NEW_IMAGE and use parseStreamRecord in a Projection Lambda to build Read Models.

Projection — Stream → Read Model

import { eventNamesOf, parseStreamRecord } from "@seike460/minamo";

const accepted = eventNamesOf(counter); // ["Incremented"]

export const handler = async (event: { Records: unknown[] }) => {
  for (const record of event.Records) {
    const stored = parseStreamRecord<CounterEvents>(record, accepted);
    if (stored === null) continue; // MODIFY / REMOVE records are skipped
    // Unregistered event types throw InvalidStreamRecordError (strict by default);
    // pass { ignoreUnknownTypes: true } to skip them instead
    // consumer updates the Read Model
    await updateReadModel(stored);
  }
};

Isolate poison pills by configuring BisectBatchOnFunctionError, an OnFailure destination, and ReportBatchItemFailures on the consumer side (DEC-013 / DEC-014).

Optional — validate input with Standard Schema

CommandHandler is synchronous, deterministic, and side-effect free. Runtime validation happens at the boundary (outside executeCommand), and the validated value flows in as input (DEC-005 / DEC-010 / DEC-015). minamo does not depend on any validator implementation; instead it ships a validate helper that accepts the Standard Schema v1 interface.

import { type CommandHandler, type InferSchemaOutput, executeCommand, validate } from "@seike460/minamo";
import { z } from "zod"; // Zod v3.24+ / Valibot v1 / ArkType v2 — any Standard Schema-compatible validator

const incrementInputSchema = z.object({ amount: z.number().int() });
type IncrementInput = InferSchemaOutput<typeof incrementInputSchema>;

const handler: CommandHandler<number, CounterEvents, IncrementInput> = (_agg, input) => {
  if (input.amount === 0) return [];
  return [{ type: "Incremented", data: { amount: input.amount } }];
};

// Validate at the boundary — throws ValidationError on failure, returns a typed value on success
const input = await validate(incrementInputSchema, rawInput);
await executeCommand({ config: counter, store, handler, aggregateId: "counter-1", input });

See docs/concept.md §5.9 / DEC-015 for the full specification.

Design Boundaries

What minamo intentionally does not do, and why. Full rationale in docs/concept.md §4 "設計の姿勢" / §6 Non-Goals / §11 Decisions.

  • Projection layer is consumer-owned (DEC-013 / DEC-014). EventStore.append / EventStore.load is the write-side contract. Stream → Read Model delivery, poison-pill isolation, and append-time projection middleware are out of scope. For sync projection in local/test runtimes use the Decorator recipe in examples/projected-event-store/; for production use DynamoDB Streams + parseStreamRecord (examples/multi-aggregate-projection/).
  • Event type naming is not enforced (DEC-009). minamo does not register or validate event type strings. Shared-table deployments should use Aggregate prefixes (Counter.Incremented, Wallet.Credited) by convention, not by library check.
  • Aggregate state is plain data, not a framework object (DEC-011). State must be structured-cloneable and DynamoDB-marshallable. evolve returns a new value with spread / concat / filter. immer and similar draft libraries can be used inside your evolve if you prefer, but they are not a dependency and the state contract stays plain.
  • Ergonomic helpers are thin wrappers, not new contracts (DEC-023). The Aggregate-spanning createEventStoreTable facade and the first-party createCommandRunner shipped in v0.2.0, but the core write side stays the single-Aggregate EventStore<TMap> contract with the object-params executeCommand surface — .for<TMap>() narrows to one Aggregate rather than exposing a heterogeneous union.

Design

  • docs/concept.md — Design philosophy and the canonical public API spec (§5 API Design / §11 Decisions)
  • docs/design/v0.1.0/ — Per-unit detailed design (U1–U9)
  • docs/design/v0.1.0.md — Implementation order and module structure
  • docs/roadmap.md — Shipped v0.2.0 helpers and the remaining backlog
  • docs/pitfalls.md — Pitfalls and gotchas learned from real production use
  • API reference — Auto-generated by typedoc, served on GitHub Pages

Examples

Runnable examples under examples/ mirror the code in this README and provide canonical patterns for common use cases:

  • examples/counter/ — Minimal InMemory and DynamoEventStore demos (concept.md §4)
  • examples/multi-aggregate-projection/ — Route N Aggregates through one Lambda with parseStreamRecord + eventNamesOf (DEC-009 + DEC-013)
  • examples/projected-event-store/ — Consumer Decorator for sync projection and a createCommandRunner factory. Shows how to handle append → projection without adding hooks to the core API (DEC-013 / DEC-014)
  • examples/dynamodb-local/ — End-to-end DynamoEventStore on Docker DynamoDB Local (append / load / rehydrate / ConcurrencyError)
  • examples/upcasting/ — Schema evolution via AggregateConfig.upcast (consumer-owned transform, DEC-020)
  • examples/snapshot/SnapshotStore + snapshotPolicy to shorten rehydration, with ExecuteObserver showing the replay-count reduction (DEC-019 / DEC-021)

The path to v1.0.0 (v1 features shipped in v0.2.0; the remaining gates are non-code) is tracked in docs/roadmap-v1.md.

License

MIT © Shiro Seike