Skip to content

Commit 500cab9

Browse files
Paul Mulliganclaude
andcommitted
feat: add Architecture Decision Records (ADRs) template and initial decisions
Add docs/adr/ directory with a standard ADR template and three initial ADRs documenting key architectural decisions: Hono over Express, Drizzle over Prisma/TypeORM, and TDD as a mandatory pipeline gate. Link from the onboarding architecture guide. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0554815 commit 500cab9

6 files changed

Lines changed: 256 additions & 0 deletions

File tree

docs/adr/000-template.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ADR-000: [Short Title of Decision]
2+
3+
## Status
4+
5+
[Proposed | Accepted | Deprecated | Superseded by [ADR-NNN](NNN-title.md)]
6+
7+
## Date
8+
9+
YYYY-MM-DD
10+
11+
## Context
12+
13+
What is the issue that we're seeing that is motivating this decision or change? Describe the forces at play — technical constraints, business requirements, team capabilities, and any other factors influencing the decision.
14+
15+
## Decision
16+
17+
What is the change that we're proposing and/or doing? State the decision clearly and concisely.
18+
19+
## Consequences
20+
21+
What becomes easier or more difficult to do because of this change?
22+
23+
### Positive
24+
25+
- List the benefits of this decision.
26+
27+
### Negative
28+
29+
- List the drawbacks or trade-offs.
30+
31+
### Neutral
32+
33+
- List any side effects that are neither clearly positive nor negative.
34+
35+
---
36+
37+
## Usage
38+
39+
To propose a new ADR:
40+
41+
1. Copy this template to `NNN-title.md` where `NNN` is the next sequential number.
42+
2. Fill in all sections. The Context section should be thorough — it's the most valuable part.
43+
3. Set status to **Proposed**.
44+
4. Open a pull request for team review.
45+
5. Once merged, update status to **Accepted** and add the date.
46+
6. Add an entry to the [ADR index](README.md).
47+
48+
To supersede an ADR:
49+
50+
1. Create a new ADR referencing the old one.
51+
2. Update the old ADR's status to **Superseded by [ADR-NNN](NNN-title.md)**.

docs/adr/001-hono-over-express.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# ADR-001: Hono as HTTP Framework Over Express
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Date
8+
9+
2026-03-29
10+
11+
## Context
12+
13+
Nerva needs an HTTP framework that supports both Cloudflare Workers (edge) and Node.js deployment targets from a single codebase. The framework must handle typed middleware composition, request validation with Zod, and JWT authentication — all with strong TypeScript support.
14+
15+
Express is the most popular Node.js HTTP framework with the largest ecosystem. However, it was designed before TypeScript, before edge runtimes, and before the Web Standards API (`Request`/`Response`) became the common interface across JavaScript runtimes.
16+
17+
Key constraints:
18+
19+
- **Cloudflare Workers is the primary deployment target.** Workers have strict CPU time limits and no access to Node.js-specific APIs. Express depends on Node.js `http` module internals and cannot run on Workers without compatibility layers.
20+
- **TypeScript strict mode is enforced.** The framework must provide strong type inference for routes, middleware, context, and environment bindings without requiring manual type annotations everywhere.
21+
- **The pipeline generates code.** Nerva's schema-to-API pipeline produces route handlers, middleware, and validation code. The target framework must have predictable, composable patterns that generated code can follow consistently.
22+
23+
Alternatives considered:
24+
25+
- **Express** — Largest ecosystem, but Node.js-only. No native TypeScript support. Middleware typing is weak (`req: any`-style patterns). Would require a separate framework for Workers.
26+
- **Fastify** — Strong TypeScript support and good performance on Node.js. However, it is Node.js-only and cannot run on Cloudflare Workers.
27+
- **Elysia** — Bun-first framework with excellent TypeScript inference. However, it is tightly coupled to Bun runtime and does not support Cloudflare Workers.
28+
29+
## Decision
30+
31+
Use **Hono** as the HTTP framework for all generated APIs.
32+
33+
Hono is a lightweight, Web Standards-based framework that runs on Cloudflare Workers, Node.js, Deno, and Bun with zero adapter code. It provides typed route definitions, first-class middleware composition via `app.use()`, and built-in integrations for Zod validation (`@hono/zod-validator`) and JWT (`hono/jwt`).
34+
35+
## Consequences
36+
37+
### Positive
38+
39+
- **Single framework for both deployment targets.** The same route handlers run on Cloudflare Workers and Node.js without modification.
40+
- **Native TypeScript inference.** Route parameters, context bindings, and middleware outputs are fully typed. The `Hono<{ Bindings: Env }>` pattern provides compile-time safety for environment variables.
41+
- **Built-in Zod integration.** `@hono/zod-validator` provides request validation middleware that feeds typed data into handlers, which aligns with the pipeline's Zod-first validation approach.
42+
- **Minimal bundle size.** Hono's core is under 15KB, well within Workers bundle limits.
43+
- **Testing without a server.** `app.request()` enables integration testing by sending requests directly to the app instance — no need to start a real HTTP server.
44+
45+
### Negative
46+
47+
- **Smaller ecosystem than Express.** Many npm packages assume Express middleware signatures (`req, res, next`). Some third-party integrations require adaptation.
48+
- **Smaller community.** Less Stack Overflow coverage and fewer tutorials compared to Express. Debugging unfamiliar edge cases may require reading Hono source code.
49+
- **Workers-specific patterns.** Cloudflare bindings (D1, KV, Durable Objects) are accessed via Hono's context rather than global imports, which is unfamiliar to developers coming from traditional Node.js.
50+
51+
### Neutral
52+
53+
- Hono's middleware signature differs from Express (`c, next` instead of `req, res, next`). Developers need to learn the new convention but it is straightforward.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ADR-002: Drizzle ORM Over Prisma and TypeORM
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Date
8+
9+
2026-03-29
10+
11+
## Context
12+
13+
Nerva's schema-to-API pipeline needs an ORM or query builder that can:
14+
15+
1. **Define the database schema in TypeScript** — the schema serves as the single source of truth for migrations, Zod validators, and TypeScript types.
16+
2. **Run on Cloudflare Workers** — the primary deployment target has no persistent filesystem, limited CPU time, and no support for native Node.js addons.
17+
3. **Generate SQL migrations** — schema changes must produce reviewable SQL files that can be version-controlled and rolled back.
18+
4. **Integrate with Zod** — the pipeline derives runtime validators from the database schema via `drizzle-zod`.
19+
20+
Alternatives considered:
21+
22+
- **Prisma** — The most popular TypeScript ORM. Generates types from a `.prisma` schema file (not TypeScript). Requires a binary query engine (~15MB) that cannot run in Cloudflare Workers without WASM workarounds. The Prisma schema is a separate DSL, meaning the database definition lives outside TypeScript and cannot be directly referenced by other pipeline stages.
23+
- **TypeORM** — Mature ORM with decorator-based entity definitions. Relies heavily on `reflect-metadata` and runtime decorators, which add overhead and complexity. TypeScript support is weaker than Drizzle or Prisma — many operations return `any` or require manual casting. No native edge runtime support.
24+
- **Kysely** — Lightweight, type-safe query builder. Strong TypeScript inference but does not provide schema definition or migration generation — those would need to be handled separately, adding complexity to the pipeline.
25+
26+
## Decision
27+
28+
Use **Drizzle ORM** as the database layer for all generated APIs.
29+
30+
Drizzle provides a TypeScript-native schema definition API, a type-safe query builder, automatic migration generation via `drizzle-kit`, and a `drizzle-zod` package that derives Zod schemas directly from table definitions. It has zero runtime dependencies beyond the database driver.
31+
32+
## Consequences
33+
34+
### Positive
35+
36+
- **Schema is TypeScript.** Table definitions are plain TypeScript objects, making them importable by other pipeline stages (Zod generation, type exports, test fixtures).
37+
- **Zero runtime overhead.** No query engine binary, no proxy objects, no reflection. Drizzle compiles to direct SQL — critical for Workers CPU limits.
38+
- **`drizzle-zod` integration.** `createInsertSchema(users)` generates a Zod validator directly from the table definition, keeping validation and schema in sync without manual duplication.
39+
- **Type-safe joins and relations.** `relations()` definitions enable typed eager loading without the N+1 query traps that implicit ORM loading can introduce.
40+
- **SQL-transparent.** Drizzle's query builder maps closely to SQL. Developers can reason about the generated queries, which makes performance tuning straightforward.
41+
- **Migration generation.** `drizzle-kit generate` produces SQL migration files from schema diffs, enabling version-controlled, reviewable database changes.
42+
43+
### Negative
44+
45+
- **Less batteries-included than Prisma.** No built-in GUI studio equivalent to Prisma Studio in production workflows (though `drizzle-kit studio` exists for development). No built-in connection pooling management — this is handled separately (via `pg` Pool or Cloudflare Hyperdrive).
46+
- **Smaller community.** Less third-party tooling and fewer tutorials than Prisma. The documentation, while improving, is less comprehensive.
47+
- **SQL knowledge expected.** Because Drizzle maps closely to SQL, developers need to understand SQL concepts (joins, subqueries, indexes) rather than relying on ORM abstractions to hide them.
48+
49+
### Neutral
50+
51+
- Drizzle supports PostgreSQL, MySQL, and SQLite. Nerva defaults to PostgreSQL but the schema definitions are portable if a project needs a different database.

docs/adr/003-tdd-mandatory-gate.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ADR-003: TDD as a Mandatory Pipeline Gate
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Date
8+
9+
2026-03-29
10+
11+
## Context
12+
13+
Nerva's `/build-from-schema` pipeline generates route handlers, middleware, and service code from an OpenAPI specification. Code generation introduces a specific risk: **generated code can appear correct without actually matching the contract it was generated from.** Without tests that independently verify the OpenAPI contract, there is no guarantee that the generated API behaves as specified.
14+
15+
The pipeline has 10 phases. The question is whether integration tests should be written before route handlers (TDD, phases 2 then 3) or after (traditional testing, phases 3 then a later test phase).
16+
17+
Key considerations:
18+
19+
- **The OpenAPI spec is the contract.** Tests derived from the spec verify that the API conforms to what was promised, independent of how the code was generated.
20+
- **Code generation can drift.** If tests are written after handlers, there is a risk of writing tests that verify the implementation rather than the specification — testing what the code does rather than what it should do.
21+
- **Pipeline phases are sequential.** A hard gate between test writing (Phase 2) and code generation (Phase 3) ensures tests are never skipped or deferred.
22+
- **80% coverage threshold.** The quality gate at Phase 7 requires minimum 80% code coverage. Writing tests first makes this threshold achievable by design rather than requiring retroactive test writing.
23+
24+
## Decision
25+
26+
**TDD is enforced as a hard gate in the pipeline.** Phase 2 (test writing) must complete before Phase 3 (route generation) can begin. This is configured in `.claude/pipeline.config.json`:
27+
28+
```json
29+
{
30+
"tdd": {
31+
"enforced": true,
32+
"redPhaseRequired": true,
33+
"greenPhaseRequired": true,
34+
"coverageThreshold": 80,
35+
"integrationTestRequired": true,
36+
"contractTestRequired": true,
37+
"testExistenceGate": true
38+
}
39+
}
40+
```
41+
42+
The pipeline orchestration enforces this dependency:
43+
44+
- Phase 2 (`tdd-scaffold`) depends on Phase 1 (`database-design`) and is **blocking**.
45+
- Phase 3 (`route-generation`) depends on Phase 2 (`tdd-scaffold`) and cannot start until all tests are confirmed failing (RED phase).
46+
47+
## Consequences
48+
49+
### Positive
50+
51+
- **Tests verify the spec, not the implementation.** Because tests are written from the OpenAPI specification before any handler code exists, they are an independent check on contract conformance.
52+
- **Coverage by design.** Every endpoint has integration tests before its handler is written, so the 80% coverage threshold is met naturally rather than requiring a separate test-writing effort.
53+
- **Regressions caught immediately.** When modifying generated code or adding features, the pre-existing test suite catches contract violations.
54+
- **Tests serve as executable documentation.** Integration tests demonstrate how each endpoint is called and what it returns, serving as living documentation for API consumers.
55+
- **Confidence in refactoring.** Generated code can be restructured (extracting services, optimizing queries) with confidence that the test suite will catch behavioral changes.
56+
57+
### Negative
58+
59+
- **Slower initial pipeline execution.** Writing tests before code adds time to the generation process. The pipeline cannot produce a running API until both Phase 2 and Phase 3 complete.
60+
- **Test maintenance burden.** If the OpenAPI spec changes, tests must be updated before route handlers can be regenerated. This is by design (the spec is the source of truth) but adds friction to spec changes.
61+
- **Hard gate can block progress.** If tests cannot be written for a particular endpoint (e.g., due to external dependencies), the entire pipeline stalls. The `refactorPhaseOptional: true` config provides some flexibility, but the RED and GREEN phases are mandatory.
62+
63+
### Neutral
64+
65+
- The TDD approach follows the Red-Green-Refactor cycle: tests fail first (Red), handlers make them pass (Green), then code is cleaned up (Refactor, optional). This is a well-established methodology, not a Nerva invention — but enforcing it in an automated pipeline is unusual.

docs/adr/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Architecture Decision Records
2+
3+
This directory contains Architecture Decision Records (ADRs) for the Nerva framework. ADRs document the reasoning behind significant architectural choices, helping contributors understand not just what was decided, but why.
4+
5+
## Index
6+
7+
| ADR | Title | Status | Date |
8+
|-----|-------|--------|------|
9+
| [000](000-template.md) | ADR Template |||
10+
| [001](001-hono-over-express.md) | Hono as HTTP Framework Over Express | Accepted | 2026-03-29 |
11+
| [002](002-drizzle-over-prisma.md) | Drizzle ORM Over Prisma and TypeORM | Accepted | 2026-03-29 |
12+
| [003](003-tdd-mandatory-gate.md) | TDD as a Mandatory Pipeline Gate | Accepted | 2026-03-29 |
13+
14+
## Creating a New ADR
15+
16+
1. Copy [000-template.md](000-template.md) to `NNN-title.md` (next sequential number, kebab-case title).
17+
2. Fill in all sections — the **Context** section is the most valuable part.
18+
3. Set status to **Proposed** and open a pull request.
19+
4. Once merged, update status to **Accepted** and set the date.
20+
5. Add an entry to the index table above.
21+
22+
## Further Reading
23+
24+
- [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) by Michael Nygard (the original ADR proposal)

docs/onboarding/architecture.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,18 @@ Nerva is designed to work with [Aurelius](https://github.com/PMDevSolutions/Aure
129129
3. Generate backend with Nerva (`/build-from-aurelius`)
130130
4. Share typed API client (`./scripts/generate-client.sh`)
131131

132+
## Architecture Decision Records
133+
134+
Key architectural decisions are documented as ADRs in [`docs/adr/`](../adr/README.md):
135+
136+
| ADR | Decision |
137+
|-----|----------|
138+
| [001](../adr/001-hono-over-express.md) | Hono as HTTP framework over Express |
139+
| [002](../adr/002-drizzle-over-prisma.md) | Drizzle ORM over Prisma and TypeORM |
140+
| [003](../adr/003-tdd-mandatory-gate.md) | TDD enforced as a hard pipeline gate |
141+
142+
To understand why a particular technology or approach was chosen, start with the ADRs. To propose a change, create a new ADR using the [template](../adr/000-template.md).
143+
132144
## Configuration
133145

134146
Pipeline behavior is controlled by `.claude/pipeline.config.json`. Key sections:

0 commit comments

Comments
 (0)