Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/adr/000-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# ADR-000: [Short Title of Decision]

## Status

[Proposed | Accepted | Deprecated | Superseded by ADR-NNN]

## Date

YYYY-MM-DD

## Context

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.

## Decision

What is the change that we're proposing and/or doing? State the decision clearly and concisely.

## Consequences

What becomes easier or more difficult to do because of this change?

### Positive

- List the benefits of this decision.

### Negative

- List the drawbacks or trade-offs.

### Neutral

- List any side effects that are neither clearly positive nor negative.

---

## Usage

To propose a new ADR:

1. Copy this template to `NNN-title.md` where `NNN` is the next sequential number.
2. Fill in all sections. The Context section should be thorough — it's the most valuable part.
3. Set status to **Proposed**.
4. Open a pull request for team review.
5. Once merged, update status to **Accepted** and add the date.
6. Add an entry to the [ADR index](README.md).

To supersede an ADR:

1. Create a new ADR referencing the old one.
2. Update the old ADR's status to **Superseded by ADR-NNN** (link to the new ADR).
53 changes: 53 additions & 0 deletions docs/adr/001-hono-over-express.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ADR-001: Hono as HTTP Framework Over Express

## Status

Accepted

## Date

2026-03-29

## Context

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.

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.

Key constraints:

- **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.
- **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.
- **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.

Alternatives considered:

- **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.
- **Fastify** — Strong TypeScript support and good performance on Node.js. However, it is Node.js-only and cannot run on Cloudflare Workers.
- **Elysia** — Bun-first framework with excellent TypeScript inference. However, it is tightly coupled to Bun runtime and does not support Cloudflare Workers.

## Decision

Use **Hono** as the HTTP framework for all generated APIs.

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`).

## Consequences

### Positive

- **Single framework for both deployment targets.** The same route handlers run on Cloudflare Workers and Node.js without modification.
- **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.
- **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.
- **Minimal bundle size.** Hono's core is under 15KB, well within Workers bundle limits.
- **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.

### Negative

- **Smaller ecosystem than Express.** Many npm packages assume Express middleware signatures (`req, res, next`). Some third-party integrations require adaptation.
- **Smaller community.** Less Stack Overflow coverage and fewer tutorials compared to Express. Debugging unfamiliar edge cases may require reading Hono source code.
- **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.

### Neutral

- 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.
51 changes: 51 additions & 0 deletions docs/adr/002-drizzle-over-prisma.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# ADR-002: Drizzle ORM Over Prisma and TypeORM

## Status

Accepted

## Date

2026-03-29

## Context

Nerva's schema-to-API pipeline needs an ORM or query builder that can:

1. **Define the database schema in TypeScript** — the schema serves as the single source of truth for migrations, Zod validators, and TypeScript types.
2. **Run on Cloudflare Workers** — the primary deployment target has no persistent filesystem, limited CPU time, and no support for native Node.js addons.
3. **Generate SQL migrations** — schema changes must produce reviewable SQL files that can be version-controlled and rolled back.
4. **Integrate with Zod** — the pipeline derives runtime validators from the database schema via `drizzle-zod`.

Alternatives considered:

- **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.
- **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.
- **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.

## Decision

Use **Drizzle ORM** as the database layer for all generated APIs.

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.

## Consequences

### Positive

- **Schema is TypeScript.** Table definitions are plain TypeScript objects, making them importable by other pipeline stages (Zod generation, type exports, test fixtures).
- **Zero runtime overhead.** No query engine binary, no proxy objects, no reflection. Drizzle compiles to direct SQL — critical for Workers CPU limits.
- **`drizzle-zod` integration.** `createInsertSchema(users)` generates a Zod validator directly from the table definition, keeping validation and schema in sync without manual duplication.
- **Type-safe joins and relations.** `relations()` definitions enable typed eager loading without the N+1 query traps that implicit ORM loading can introduce.
- **SQL-transparent.** Drizzle's query builder maps closely to SQL. Developers can reason about the generated queries, which makes performance tuning straightforward.
- **Migration generation.** `drizzle-kit generate` produces SQL migration files from schema diffs, enabling version-controlled, reviewable database changes.

### Negative

- **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).
- **Smaller community.** Less third-party tooling and fewer tutorials than Prisma. The documentation, while improving, is less comprehensive.
- **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.

### Neutral

- Drizzle supports PostgreSQL, MySQL, and SQLite. Nerva defaults to PostgreSQL but the schema definitions are portable if a project needs a different database.
65 changes: 65 additions & 0 deletions docs/adr/003-tdd-mandatory-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ADR-003: TDD as a Mandatory Pipeline Gate

## Status

Accepted

## Date

2026-03-29

## Context

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.

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).

Key considerations:

- **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.
- **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.
- **Pipeline phases are sequential.** A hard gate between test writing (Phase 2) and code generation (Phase 3) ensures tests are never skipped or deferred.
- **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.

## Decision

**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`:

```json
{
"tdd": {
"enforced": true,
"redPhaseRequired": true,
"greenPhaseRequired": true,
"coverageThreshold": 80,
"integrationTestRequired": true,
"contractTestRequired": true,
"testExistenceGate": true
}
}
```

The pipeline orchestration enforces this dependency:

- Phase 2 (`tdd-scaffold`) depends on Phase 1 (`database-design`) and is **blocking**.
- Phase 3 (`route-generation`) depends on Phase 2 (`tdd-scaffold`) and cannot start until all tests are confirmed failing (RED phase).

## Consequences

### Positive

- **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.
- **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.
- **Regressions caught immediately.** When modifying generated code or adding features, the pre-existing test suite catches contract violations.
- **Tests serve as executable documentation.** Integration tests demonstrate how each endpoint is called and what it returns, serving as living documentation for API consumers.
- **Confidence in refactoring.** Generated code can be restructured (extracting services, optimizing queries) with confidence that the test suite will catch behavioral changes.

### Negative

- **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.
- **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.
- **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.

### Neutral

- 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.
24 changes: 24 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Architecture Decision Records

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.

## Index

| ADR | Title | Status | Date |
|-----|-------|--------|------|
| [000](000-template.md) | ADR Template | — | — |
| [001](001-hono-over-express.md) | Hono as HTTP Framework Over Express | Accepted | 2026-03-29 |
| [002](002-drizzle-over-prisma.md) | Drizzle ORM Over Prisma and TypeORM | Accepted | 2026-03-29 |
| [003](003-tdd-mandatory-gate.md) | TDD as a Mandatory Pipeline Gate | Accepted | 2026-03-29 |

## Creating a New ADR

1. Copy [000-template.md](000-template.md) to `NNN-title.md` (next sequential number, kebab-case title).
2. Fill in all sections — the **Context** section is the most valuable part.
3. Set status to **Proposed** and open a pull request.
4. Once merged, update status to **Accepted** and set the date.
5. Add an entry to the index table above.

## Further Reading

- [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) by Michael Nygard (the original ADR proposal)
12 changes: 12 additions & 0 deletions docs/onboarding/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ Nerva is designed to work with [Aurelius](https://github.com/PMDevSolutions/Aure
3. Generate backend with Nerva (`/build-from-aurelius`)
4. Share typed API client (`./scripts/generate-client.sh`)

## Architecture Decision Records

Key architectural decisions are documented as ADRs in [`docs/adr/`](../adr/README.md):

| ADR | Decision |
|-----|----------|
| [001](../adr/001-hono-over-express.md) | Hono as HTTP framework over Express |
| [002](../adr/002-drizzle-over-prisma.md) | Drizzle ORM over Prisma and TypeORM |
| [003](../adr/003-tdd-mandatory-gate.md) | TDD enforced as a hard pipeline gate |

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).

## Configuration

Pipeline behavior is controlled by `.claude/pipeline.config.json`. Key sections:
Expand Down
Loading