Skip to content

Architecture decisions

Component Pinned version Notes
Bun 1.4.2 Only validated runtime
TypeScript 7.0.2 tsc type checking only (noEmit)
Hono 4.13.13
@hono/zod-validator 0.9.1 peer: zod ^4, hono ≥4.11.2
zod 4.6.5 z.toJSONSchema used for OpenAPI
drizzle-orm 0.45.4 latest stable (1.0 is RC; not adopted)
drizzle-kit 0.31.11 migration generation/check only
drizzle-zod 0.8.3 standalone package behind @framework/database adapter
postgres (postgres-js) 3.4.9
PostgreSQL 17 (alpine image) deployment/compose.yaml, port 54329 on loopback
Biome 2.5.15 lint + format

ADR-1 Bun first. The starter used Bun; all scripts, tests and the server entry run on Bun. Node support needs its own adapter and test matrix and is not selectable (create --runtime node is planned).

ADR-2 bun test instead of Vitest. The spec names Vitest. Bun’s runner is already the runtime, needs no config, and supports JUnit output used by make:crud --verify. This is a deliberate deviation; the test command delegates to project.tools.test, so switching runners is an adapter change.

ADR-3 Standalone drizzle-zod behind an adapter. Upstream docs show drizzle-orm/zod with a release-candidate install. Per spec we pinned stable drizzle-zod@0.8.3 and route all imports through @framework/database (src/schema/zod.ts), so moving to drizzle-orm/zod is a one-file change.

ADR-4 String-mode timestamps + ISO normalization. Generated tables use mode: 'string' timestamps so JSON transport never goes through z.date(). PostgreSQL text (2026-10-03 22:27:19+00) is normalized to ISO-8601 by toIsoTimestamp in response serializers. Soft deletion writes now() in SQL so it works for both Date- and string-mode columns.

ADR-5 Error boundary is onError. Hono catches downstream exceptions before they return through await next(); the kernel installs onError/notFound and never relies on try/catch around next(). Postgres unique/foreign-key violations (23505/23503) map to 409; 5xx messages are generic unless debug is on outside production.

ADR-6 Explicit registration with markers. Modules, providers, routes and OpenAPI operations are registered explicitly. Generators add imports syntax-aware (after the last import statement) and list entries after // @framework:* markers; missing markers and duplicates are errors.

ADR-7 Mixins as constrained class expressions. Readable/Creatable/Updatable/Deletable/SoftDeletable wrap RepositoryCore<typeof table>. CrudTable intentionally has no column index signature so stacked mixins keep the concrete row types; collisions (e.g. Deletable + SoftDeletable) throw at definition time.

ADR-8 Explicit public access. The spec forbids silently generating public access. make:crud therefore requires --access public (or a definition file / interactive answer) — a deviation from the spec’s bare examples, which would otherwise have to invent an access policy. authenticated exits 3 until Phase 3.

ADR-9 Migrations are a release step. db:migrate takes a PostgreSQL advisory lock on a reserved connection (pool ≥ 2, enforced) and is never run on server/worker startup. Test databases are created per run (fw_test_*), migrated, and dropped in success and failure paths.

ADR-10 Workspace layout. This repository is the framework workspace (packages/*, apps/playground). The playground is the reference application and the template the generator’s end-to-end gate copies into .tmp/ to verify generated code.