From 23ff9b330b325241d8fde93d3cdfbd1388760212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Espi=C3=B1a?= Date: Fri, 9 Oct 2026 15:20:18 +0200 Subject: [PATCH] feat: lead with the problem, and show the API the packages have now The hero says what the tables lose before how Bounda works. The code shows declared rejections and begin, and the story speaks of evolve, as the framework does since its last renames. --- src/components/landing/code.astro | 18 +++++++++--------- src/components/landing/facts.astro | 4 ++-- src/components/landing/hero.astro | 6 +++--- src/components/landing/story.astro | 4 ++-- src/layouts/base.astro | 2 +- src/scripts/landing/story.ts | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/landing/code.astro b/src/components/landing/code.astro index 5f62318..6552879 100644 --- a/src/components/landing/code.astro +++ b/src/components/landing/code.astro @@ -7,16 +7,17 @@ const files = [ title: "Command handler", role: "decides", code: `// app/domain/order/commands/place-order.ts -import { DomainError } from "@bounda-dev/core"; 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 handler = ({ command, state, events }: Command.HandlerArgs) => { - if (state.status !== "new") { - throw new DomainError(\`Order \${command.aggregateId} was already placed\`); - } +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 }), ]; @@ -24,15 +25,14 @@ export const handler = ({ command, state, events }: Command.HandlerArgs) => { }, { title: "Event", - role: "applies", + role: "opens the order", code: `// app/domain/order/order-placed.ts import type { Event } from "./+types/order-placed"; export const payload = ({ z }: Event.PayloadArgs) => z.object({ customerId: z.string(), total: z.number().positive() }); -export const apply = ({ state, event }: Event.ApplyArgs) => ({ - ...state, +export const begin = ({ event }: Event.BeginArgs) => ({ status: "placed" as const, customerId: event.payload.customerId, total: event.payload.total, @@ -63,7 +63,7 @@ export const project = async ({ event, table }: Projection.Args) => {

The code

Three files. Their place and name are the declaration.

- A command handler decides, an event applies, a projection writes a row. bounda generate reads the folders and + A command handler decides, an event becomes the state, a projection writes a row. bounda generate reads the folders and infers every type, so there is no registry to keep in sync.

app/
diff --git a/src/components/landing/facts.astro b/src/components/landing/facts.astro
index e45dbc6..2f37350 100644
--- a/src/components/landing/facts.astro
+++ b/src/components/landing/facts.astro
@@ -8,7 +8,7 @@ const facts = [
   {
     label: "bounda rebuild",
     title: "Read models rebuild.",
-    body: "Replay the global stream into a fresh read model without touching a single event.",
+    body: "A wrong dashboard is a fixed projection and a rebuild, not a migration script. The events are never touched.",
   },
   {
     label: "Processes and policies",
@@ -18,7 +18,7 @@ const facts = [
   {
     label: "One database",
     title: "Runs where you are.",
-    body: "PostgreSQL, SQLite or libSQL, or a Cloudflare Durable Object per tenant. React Router integration and OpenTelemetry.",
+    body: "PostgreSQL, SQLite or libSQL. On Cloudflare, one Durable Object per tenant holds the whole domain, driven by its alarm. React Router integration and OpenTelemetry.",
   },
 ];
 ---
diff --git a/src/components/landing/hero.astro b/src/components/landing/hero.astro
index eee110f..7127d6d 100644
--- a/src/components/landing/hero.astro
+++ b/src/components/landing/hero.astro
@@ -11,13 +11,13 @@ import Install from "../install.astro";
       ceremony.
     
     

- Keep every change as an ordered fact. Rebuild any view from it, on one database. + Your tables keep the last write. Bounda keeps every change. Rebuild any view from it, on one database.

- Keep every change as an ordered fact and rebuild any view from it. Plain TypeScript modules, inferred types, one - database. No broker to run. + Your tables keep the last write. Bounda keeps every change. Rebuild any view, explain any past state. Plain + TypeScript modules, inferred types, one database, no broker.

diff --git a/src/components/landing/story.astro b/src/components/landing/story.astro index ecccdd7..20163fb 100644 --- a/src/components/landing/story.astro +++ b/src/components/landing/story.astro @@ -18,9 +18,9 @@

OrderPlaced is appended to the global stream at position 4,212. Facts are never edited or deleted.

-

Apply

+

Evolve

State is a fold.

-

The global stream interleaves every aggregate. apply folds only order/7f3a's events, in order: created, item added, placed.

+

The global stream interleaves every aggregate. evolve folds only order/7f3a's events, in order: created, item added, placed.

Projection

diff --git a/src/layouts/base.astro b/src/layouts/base.astro index 0339d76..f01c869 100644 --- a/src/layouts/base.astro +++ b/src/layouts/base.astro @@ -9,7 +9,7 @@ interface Props { const { title = "Bounda — Event sourcing and CQRS for TypeScript", - description = "Keep every change as an ordered fact and rebuild any view from it. Plain TypeScript modules, inferred types, one database.", + description = "Your tables keep the last write. Bounda keeps every change, so any view can be rebuilt from it. Plain TypeScript modules, inferred types, one database.", } = Astro.props; const canonicalUrl = new URL(Astro.url.pathname, Astro.site ?? "https://bounda.dev"); diff --git a/src/scripts/landing/story.ts b/src/scripts/landing/story.ts index 5ff6a5d..189e443 100644 --- a/src/scripts/landing/story.ts +++ b/src/scripts/landing/story.ts @@ -164,7 +164,7 @@ const readoutAt = (b: number): Readout => { const step = FOLD[k]; if (!step) throw new Error(`no fold step ${k}`); return at( - `Apply ${k + 1} of 3 · order/7f3a`, + `Evolve ${k + 1} of 3 · order/7f3a`, formatPosition(step.position), `${step.name} → ${step.state}`, step.position,