Golden path
The smallest complete Kumiko feature: one entity, one write handler that emits
an event, one projection, one screen, and an integration test that writes
through HTTP and reads the result back. Every other recipe is a variation on
this shape. The walkthrough is in docs/golden-path.md.
What it shows
Section titled “What it shows”- Entity —
ticketEntitydeclarestitleandisClosed.r.crudregistersticket:create,ticket:listandticket:detailfor it; update and delete are switched off withverbs. - Write handler —
ticket:closechecks that the ticket exists and emits theticket-closedevent. It never touches the table. - Event —
r.defineEventdeclaresticket-closedwith a Zod payload;emitEventappends it to the ticket’s stream in the handler’s transaction. - Projection —
ticket-statusfoldsticket-closedinto the ticket row (isClosed: true), in the same transaction as the write. - Screen —
ticket-listis a declarativeentityList; the renderer builds the UI from it. - Test —
setupTestStackboots the real server and database;stack.http.writeOkandqueryOkgo over HTTP. Each test runs in its own tenant.
src/feature.ts entity, event, handler, projection, screensrc/__tests__/feature.integration.test.ts write → read back, access, not foundSource code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
import { createBooleanField, createEntity, createEntityExecutor, createTextField, defineFeature, emitEvent, setFields,} from "@cosmicdrift/kumiko-framework/engine";import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/errors";import { z } from "zod";
export const ticketEntity = createEntity({ table: "read_golden_path_tickets", fields: { title: createTextField({ personal: false, reason: "is_business_data", required: true }), isClosed: createBooleanField({ default: false }), },});
export const ticketingFeature = defineFeature("ticketing", (r) => { const { table: ticketTable, executor: ticketExecutor } = createEntityExecutor( "ticket", ticketEntity, );
r.crud("ticket", ticketEntity, { write: { access: { roles: ["Admin", "User"] } }, read: { access: { roles: ["Admin", "User"] } }, verbs: { update: false, delete: false }, });
const ticketClosed = r.defineEvent("ticket-closed", z.object({ resolution: z.string().min(1) }), { piiFields: "none", });
r.writeHandler({ name: "ticket:close", schema: z.object({ id: z.uuid(), resolution: z.string().min(1) }), access: { roles: ["Admin"] }, handler: async (event, ctx) => { const ticket = await ticketExecutor.detail({ id: event.payload.id }, event.user, ctx.db); if (!ticket) { return failNotFound("ticket", event.payload.id); } if (ticket["isClosed"]) return failUnprocessable("ticket_already_closed"); await emitEvent(ctx, ticketClosed, { aggregateId: event.payload.id, aggregateType: "ticket", payload: { resolution: event.payload.resolution }, }); return { isSuccess: true as const, data: { id: event.payload.id } }; }, });
r.projection({ name: "ticket-status", source: "ticket", table: ticketTable, apply: { [ticketClosed.name]: setFields(ticketTable, { isClosed: true }), }, });
r.screen({ id: "ticket-list", type: "entityList", entity: "ticket", columns: ["title", "isClosed"], });});📄 On GitHub: samples/recipes/golden-path/src/feature.ts