@bimetal/kanban-data
v0.35.0
Published
Kanban-domain event-sourcing: Board-as-Aggregate (Columns + Cards + Reihenfolge sind EIN Aggregat mit harten Invarianten — Spalten-Ordnung, WIP-Limit). Commands/events, kanbanAggregate, kanbanProjection, createKanbanStore. Built on @bimetal/event-sourcing
Maintainers
Readme
@bimetal/kanban-data
Kanban-domain event-sourcing, built on @bimetal/event-sourcing.
Board-as-Aggregate. The whole board — columns, cards, and their ordering —
is ONE aggregate. Its invariants are hard, enforced in the aggregate's
handle, not as soft rules:
- Column ordering is the
columnsarray order. - Per-column WIP limits (
column.wipLimit) reject creates/cross-column moves that would overflow — surfaced as aDataValidationError, never an emitted event.
T = Board, so the generic AggregateState<Board> carries the board as a
single entity keyed by its boardId; every event's entityId is the boardId,
and card/column identity lives in the payload. Concurrency is therefore
board-granular (the consistency boundary the WIP invariant requires).
Whole-board undo. The board is a singleton root — created once, mutated by
card/column facts, never deleted. Each mutation pushes the prior whole-board
snapshot onto the undo stack; undo(boardId) reverts to it via a
BoardReverted fact. No previous fields, no LWW, no conflict resolver
(architecture-tested).
import { createKanbanStore, createBoard, createCard, moveCard } from '@bimetal/kanban-data';
const store = createKanbanStore();
await store.dispatch(createBoard({
id: 'b1',
columns: [
{ id: 'todo', title: 'Todo', wipLimit: null, cardIds: [] },
{ id: 'doing', title: 'Doing', wipLimit: 2, cardIds: [] },
{ id: 'done', title: 'Done', wipLimit: null, cardIds: [] },
],
cards: {},
}));
await store.dispatch(createCard('b1', 'todo', { id: 'c1', title: 'Write spec' }));
await store.dispatch(moveCard('b1', 'c1', 'doing', 0));
await store.undo('b1'); // card back in 'todo'The dispatch loop, idempotency, snapshots, concurrency checks and live cross-instance subscription are all inherited from the generic schicht unchanged — this package adds only the board fold, invariants, and queries.
