Skip to content
bounda-devPublic

About

Event sourcing and CQRS for TypeScript without the ceremony

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Bounda

Event sourcing and CQRS for TypeScript without the ceremony.

npm version CI status Mutation score Node version License

Your tables keep the last write. Bounda keeps every change, so any view can be rebuilt from it and any past state explained. Commands, events and projections are plain TypeScript modules, every type is inferred from them, and it all runs on one database with no broker, or on one Cloudflare Durable Object per tenant.

npm create bounda@latest my-app

bounda.dev · Documentation · Getting started · Core concepts · Deploy to Cloudflare

What it looks like

A command decides; the event it returns becomes the state. The file's place and name are the declaration, and bounda generate writes the types next to it:

// app/domain/order/commands/place-order.ts
import type { Command } from "./+types/place-order";

export const payload = ({ z }: Command.PayloadArgs) =>
  z.object({ orderId: z.uuid(), customerId: z.string().min(1), total: z.number().positive() });

export const rejections = ({ command }: Command.RejectionsArgs) => ({
  AlreadyPlaced: `Order ${command.aggregateId} was already placed`,
});

export const handler = ({ command, state, events, reject }: Command.HandlerArgs) => {
  if (state.status !== undefined) return reject("AlreadyPlaced");
  return [events.orderPlaced({ customerId: command.payload.customerId, total: command.payload.total })];
};

command.payload is typed from the schema, state from the order's events, events only offers this aggregate's events and reject only the codes above. A projection turns OrderPlaced into a row a query reads; the getting started guide builds the whole slice in fifteen minutes.

Why Bounda

  • Types are inferred. A file's place and name are its declaration, and bounda generate infers every type from them: no command bus, repository or registry to keep in sync. Change a field and the compiler points at every place that breaks.
  • Read models rebuild. A wrong dashboard is a fixed projection and bounda rebuild, not a migration script. The events are never touched.
  • Workflows have a home. A workflow of several steps is a process next to its aggregate, with its own state and deadlines, not a state machine buried in a handler. Policies react to events, and retries, dead letters and upcasters come with the runtime.
  • Nothing else to run. Events, read models and the work still pending share one database: PostgreSQL, SQLite or libSQL. Start there; when a read model outgrows it, it gets a database of its own. On Cloudflare, the database is a Durable Object per tenant.

React Router has its own integration, and the runtime reports OpenTelemetry spans and metrics. How Bounda runs says how far one store goes, and what is not there yet is one honest list.

Status

0.x. Until 1.0 the API can still change between minor versions (0.1 to 0.2), and every change that breaks something is called out in the changelog of the package it touches.

Packages

Package Purpose
@bounda-dev/core Runtime and public API, with the docs as Markdown for your agent
@bounda-dev/cli bounda CLI: reads the layout, writes the registry and the types
@bounda-dev/sqlite SQLite and libSQL storage
@bounda-dev/postgresql PostgreSQL storage
@bounda-dev/cloudflare A Durable Object per tenant on Cloudflare
@bounda-dev/react-router React Router integration and its Vite plugin
create-bounda Project scaffolder

Each package README carries its own mutation score; the badge above is the whole repository. @bounda-dev/cloudflare has none: its tests run inside workerd, where Stryker cannot mutate.

Two runnable examples live here: examples/storefront, on Node and SQLite, and examples/onboarding, on React Router.

Contributing

Issues and pull requests are welcome. CONTRIBUTING.md says how to set the repository up, what a pull request needs and how to report a security problem.

License

Apache 2.0. See LICENSE.

About

Event sourcing and CQRS for TypeScript without the ceremony

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages