dc-ts
v0.1.9-unstable
Published
Utilities for domain-centric typescript
Downloads
43
Readme
✅ dc-ts
Domain-Centric TypeScript — a functional, event-driven approach to core domain logic in TypeScript.
What is dc-ts?
dc-ts is a lightweight library and architecture style for implementing core domain logic using:
- 🧠 Typed workflows via
CoreWf - 🧪 Type-safe validation using TypeBox
- ♻️ Pure business logic, built around
constrain → decide → evolve - 🔍 Designed for event sourcing and collaborative modeling
Architecture
📦 Core/
├─ domain.ts # your types and workflows
└─ _implementation/
├─ _schema.ts # generated TypeBox schemas
├─ constructors.ts # smart type-safe constructors
├─ constrain.ts # constraint logic
├─ decide.ts # decision logic (commands → events)
├─ evolve.ts # state transitions (events → state)
└─ tests/ # BDD-style tests🧱 CoreWf
At the heart of dc-ts is the CoreWf type:
export type CoreWf<C, iA, E, oA, F> = {
cmd: C // domain command
inputAg: iA // aggregate before
outputAg: oA // aggregate after
aggregate: iA | oA
evt: E // emitted event
fails: F // failure messages
validateAggregate: SafeParse<iA | oA>
}🧪 Example: ToDo Domain
export type CreateToDoWf = CoreWf<
CMD<'create-todo', { title: string }>,
AGG<'initial', { title?: string }>,
EVT<'todo-created', { id: string; title: string }>,
AGG<'created', { id: string; title: string; status: 'pending' }>,
'title_required'
>✅ Constrain
Used to enforce business rules before deciding.
export const constrainToDo: Constrain<CreateToDoWf['cmd'], CreateToDoWf['inputAg'], 'title_required'> =
(cmd) => (state) => {
if (!cmd.data.title?.trim()) {
return fail('title_required')
}
return succeed(state)
}🧠 Decide
Takes a command and a validated state and returns event(s).
export const decideToDo = (cmd: CreateToDoWf['cmd']) => (state: CreateToDoWf['inputAg']) => {
const evtRes = newToDoEVT<CreateToDoWf['evt']>()('todo-created')(dtFromMsg(cmd))({
id: crypto.randomUUID(),
title: cmd.data.title
})
return isFailure(evtRes) ? evtRes : succeed([evtRes.data])
}🔁 Evolve
Transitions the aggregate to its next state.
export const evolveToDo = (evt: CreateToDoWf['evt']) => (state: CreateToDoWf['inputAg']): CreateToDoWf['outputAg'] => {
return {
_tag: 'created',
data: {
id: evt.data.id,
title: evt.data.title,
status: 'pending'
}
}
}🧪 Validation
All inputs and state transitions are validated using TypeBox:
export const newToDoCMD = <C extends CreateToDoWf['cmd']>() =>
newCmd<C['type'], C['data']>(safeParseTBox(ToDoCmdSchema))🧪 BDD Testing
Tests can be generated and follow a Given/When/Then format:
Deno.test("Given a valid command, When constraints run, Then succeed", () => {
const cmd = { ... }
const state = { ... }
const result = constrainToDo(cmd)(state)
assertEquals(result.outcome, "success")
})🎯 Design Principles
- 🔄 One source of truth: models power code, tests, and docs
- 🧩 Composable and side-effect free: logic stays pure and testable
- 🛠 Schema-driven development: everything is validated at runtime
- 🧠 Business-first modeling: workflows match EventStorming patterns
🧩 Works Best With
📘 Need business documentation?
Use the dcts CLI to generate:
- ✅ Business summaries
- 🔁 Decision tables
- 🧪 BDD-style test templates
- 📬 AsyncAPI specs
📝 License
MIT — Giovanni & Contributors
