Level 1 — CRUD, no jargon
You want a task entity with create, edit and list. Here is the complete
feature — this is the whole file, not an excerpt:
// Basic Entity Sample// Shows: how to wire one event-sourced aggregate end-to-end via r.crud.// Custom logic or per-verb access? Skip a verb in `verbs` and register explicitly.
import { createBooleanField, createEntity, createTextField, defineEntityDeleteHandler, defineEntityRestoreHandler, defineFeature,} from "@cosmicdrift/kumiko-framework/engine";
export const taskEntity = createEntity({ table: "read_sample_tasks", fields: { title: createTextField({ required: true }), description: createTextField(), // sortable: true so the integration test can exercise list-with-sort. status: createTextField({ sortable: true }), isArchived: createBooleanField({ default: false }), }, softDelete: true,});
const editorWrite = { access: { roles: ["Admin", "User"] } } as const;const adminWrite = { access: { roles: ["Admin"] } } as const;const openRead = { access: { openToAll: true } } as const;
export const taskFeature = defineFeature("tasks", (r) => { r.crud("task", taskEntity, { write: editorWrite, read: openRead, verbs: { delete: false, restore: false }, }); r.writeHandler(defineEntityDeleteHandler("task", taskEntity, adminWrite)); r.writeHandler(defineEntityRestoreHandler("task", taskEntity, adminWrite));});📄 On GitHub: samples/recipes/basic-entity/src/feature.ts
That’s it. r.crud("task", taskEntity, …) registers create, update, list and
detail handlers, wires validation from your field definitions, applies the
role-based access you passed, and scopes everything to the tenant.
What you get without asking
Section titled “What you get without asking”Because Kumiko stores every write as an event under the hood, this one call also gives you things CRUD frameworks usually don’t:
- A complete history. Every create and every edit of every task is recorded — who, when, what changed, what the row looked like before.
- An audit trail with zero setup. There is no “enable auditing” switch because the history is the storage. It can’t drift, and it can’t be forgotten.
- Rebuildable tables. If you change your table’s shape later, Kumiko can re-derive every row from the history — no hand-written data migration.
- Optimistic locking. Two users editing the same task get a clean conflict instead of silently overwriting each other.
You did not write any of that, and at this level you don’t need to know how it works.
Taking a verb back
Section titled “Taking a verb back”r.crud is sugar, not a cage. The recipe above skips delete and restore
(verbs: { delete: false, restore: false }) and registers them explicitly
with stricter access — mixing generated and hand-written verbs is the normal
pattern, not an escape hatch. For a fully custom write path, see the
custom write handler guide.
When to climb to Level 2
Section titled “When to climb to Level 2”Stay on Level 1 as long as your domain is nouns. The moment it grows verbs — an invoice is approved, an order is shipped, a member is promoted — modelling them as field updates loses what actually happened. Level 2 gives those verbs a first-class home.