diff --git a/DIACHRON.md b/DIACHRON.md new file mode 100644 index 0000000..dc40a8f --- /dev/null +++ b/DIACHRON.md @@ -0,0 +1,168 @@ +# What is diachron? + +diachron is a web framework for TypeScript and Node.js. It uses a Go-based +master process that handles file watching, building, process management, and +request proxying. The application code is TypeScript running on Express.js. + +If you're joining a project that uses diachron, this document will orient you. + +## Why diachron exists + +diachron was built around a few frustrations with mainstream web frameworks: + +- **No dev/prod split.** Most frameworks behave differently in development and + production. diachron doesn't. The master process watches files, rebuilds, + and manages workers the same way everywhere. There is no `NODE_ENV`. + +- **Managed tooling.** Node.js, pnpm, and other tools are downloaded and + pinned to exact versions inside the project. You don't install them + system-wide. Everyone on the team runs the same binaries. + +- **PostgreSQL, directly.** No ORM, no database abstraction layer. You write + SQL (via Kysely for type safety) and talk to PostgreSQL. If you need + MySQL or SQLite support, this is not the framework for you. + +- **Debuggability over magic.** Everything is explicit and inspectable. + Logging and observability are first-class concerns, not afterthoughts. + +diachron is inspired by the +[Taking PHP Seriously](https://slack.engineering/taking-php-seriously/) essay +from Slack Engineering. It's designed for small to medium systems (what we +call "Ring 0 and Ring 1") -- not heavy-compliance or banking-scale +applications. + +## How it works + +When you run `./master`, the following happens: + +1. The Go master process starts and watches your TypeScript source files. +2. It builds the backend using `@vercel/ncc`, producing a single bundled JS + file. +3. It starts one or more Node.js worker processes running your Express app. +4. It proxies HTTP requests from port 8080 to the workers. +5. When you edit a source file, it rebuilds and restarts the workers + automatically. +6. If a worker crashes, it restarts automatically. + +There is no separate "dev server" or "hot module replacement." The master +process is the only way to run the application. + +## Project structure + +A diachron project looks like this: + +``` +. +├── DIACHRON.md # This file (framework overview for newcomers) +├── master/ # Go master process (framework-owned) +├── logger/ # Go logging service (framework-owned) +├── diachron/ # Managed binaries, shims, framework library +│ ├── AGENTS.md # Guide for AI coding agents +│ ├── binaries/ # Downloaded Node.js, pnpm (gitignored) +│ ├── cmd.d/ # Commands available via ./cmd +│ ├── shims/ # Wrappers that use managed binaries +│ └── ... +├── backend/ # Your application code +│ ├── app.ts # Entry point +│ ├── routes.ts # Route definitions +│ ├── handlers.ts # Route handlers +│ ├── services.ts # Service layer +│ ├── types.ts # Application types +│ ├── config.ts # Application configuration +│ └── diachron/ # Framework library code (framework-owned) +├── cmd # Run managed commands (./cmd pnpm install, etc.) +├── develop # Development-only commands (./develop reset-db, etc.) +├── mgmt # Management commands safe for production +├── sync.sh # Install/update all dependencies +├── master # The compiled master binary (after sync) +└── docker-compose.yml +``` + +### File ownership + +There are two owners of files in a diachron project: + +- **You own** everything in `backend/` (except `backend/diachron/`), plus + `docker-compose.yml`, `package.json`, and anything else you create. +- **The framework owns** `master/`, `logger/`, `diachron/`, + `backend/diachron/`, and the top-level scripts (`cmd`, `develop`, `mgmt`, + `sync.sh`, `check.sh`). + +Don't modify framework-owned files. This separation keeps framework upgrades +clean. + +## Getting started + +```bash +# Install dependencies and build the master process +./sync.sh + +# Start the application +./master +``` + +The app will be available at `http://localhost:8080`. + +You need Linux or macOS on x86_64. For the full stack (database, Redis, +etc.), you also need `docker compose`. + +## The command system + +diachron has three types of commands, separated by intent and safety: + +- **`./cmd `** -- Run managed tools (node, pnpm, tsx, etc.). These + use the project's pinned versions, not whatever is installed on your system. + + ```bash + ./cmd pnpm install + ./cmd pnpm test + ``` + +- **`./mgmt `** -- Management commands that are safe to run in + production. Migrations, user management, that sort of thing. + + ```bash + ./mgmt migrate + ./mgmt add-user + ``` + +- **`./develop `** -- Development commands that may be destructive. + Database resets, fixture loading, etc. These are gated in production. + + ```bash + ./develop reset-db + ./develop db # Open a database shell + ``` + +The rule of thumb: if you'd run it at 3am while tired and worried, it's a +`mgmt` command. If it destroys data on purpose, it's a `develop` command. + +## Key concepts + +### Call and Result + +diachron wraps Express's `Request` and `Response` in its own types called +`Call` and `Result`. This avoids shadowing and keeps the framework's +interface distinct from Express internals. Your handlers receive a `Call` +and return a `Result`. + +### Routes + +Routes are defined as data (arrays of `Route` objects in `routes.ts`), not +through decorators or method chaining. The framework processes them into +Express handlers. + +### No environment variables for behavior + +There is no `NODE_ENV`, no `DEBUG`, no mode switching. Configuration that +must vary between deployments (database URLs, secrets) lives in +configuration files, but the application's behavior doesn't branch on +environment. + +## Further reading + +- `README.md` -- Project introduction and requirements +- `diachron/AGENTS.md` -- Guide for AI coding agents +- `docs/` -- Design documents and philosophy +- `docs/commands.md` -- Detailed explanation of the command system +- `docs/concentric-circles.md` -- What diachron is (and isn't) designed for diff --git a/backend/services.ts b/backend/services.ts new file mode 100644 index 0000000..ea2e84f --- /dev/null +++ b/backend/services.ts @@ -0,0 +1,15 @@ +// This is a sample file provided by diachron. You are encouraged to modify it. + +// Application services go here. A service encapsulates a capability that +// handlers depend on: database queries, external API calls, business logic +// that doesn't belong in a handler. +// +// The framework provides core services via `core` (from ./diachron/core): +// core.database, core.logging, core.misc, etc. This file is for your +// application's own services. + +import { core } from "./diachron/core"; + +const db = core.database.db; + +export { db }; diff --git a/backend/types.ts b/backend/types.ts new file mode 100644 index 0000000..2442415 --- /dev/null +++ b/backend/types.ts @@ -0,0 +1,8 @@ +// This is a sample file provided by diachron. You are encouraged to modify it. + +// Application-specific types go here. Framework types (Call, Result, Route, +// Handler, etc.) are defined in ./diachron/types and should be imported from +// there. +// +// This file is for your domain types: the nouns and shapes that are specific +// to your application. diff --git a/diachron/AGENTS.md b/diachron/AGENTS.md new file mode 100644 index 0000000..26a0a3c --- /dev/null +++ b/diachron/AGENTS.md @@ -0,0 +1,184 @@ +# Working with diachron (Agent Guide) + +This document helps AI coding agents work effectively with projects built on +the diachron framework. It covers the conventions, commands, and structures +you need to know. + +## Quick orientation + +diachron is a TypeScript/Express web framework with a Go master process. +Your application code lives in `backend/`. The framework owns `master/`, +`logger/`, `diachron/`, and `backend/diachron/`. + +**Do not modify framework-owned files** unless explicitly asked to work on +the framework itself. + +## Running the application + +```bash +./sync.sh # Install dependencies (run once, or after pulling changes) +./master # Start the application (watches files, rebuilds, proxies) +``` + +By default, the app listens on port 8080 (proxy) and workers run on +ports starting at 3000. + +## Commands you'll need + +All tools (node, pnpm, tsx, etc.) are managed by the framework. Do not +invoke system-installed versions. + +```bash +./cmd pnpm install # Install npm packages +./cmd pnpm test # Run tests +./cmd pnpm biome check . # Lint (run from backend/) +./develop db # Open database shell +./develop reset-db # Drop and recreate database +./develop migrate # Run migrations (development) +./mgmt migrate # Run migrations (production-safe) +``` + +### Formatting and linting + +```bash +cd backend && ../cmd pnpm biome check --write . +``` + +### Building Go code + +```bash +cd master && go build +cd logger && go build +``` + +### Quality checks + +```bash +./check.sh # shellcheck + golangci-lint +``` + +## Application structure + +### Where to put code + +| What | Where | +|--------------------------|-----------------------------| +| Application entry point | `backend/app.ts` | +| Route definitions | `backend/routes.ts` | +| Route handlers | `backend/handlers.ts` | +| Service layer | `backend/services.ts` | +| Application types | `backend/types.ts` | +| Application config | `backend/config.ts` | +| Database migrations | `backend/migrations/` | +| Framework library code | `backend/diachron/` | + +### Types and naming + +- HTTP request wrapper: `Call` (not Request) +- HTTP response wrapper: `Result` (not Response) +- Route definitions: arrays of `Route` objects +- Handlers: functions that take a `Call` and return a `Result` + +These names are intentional. Use them consistently. + +Import framework types from `./diachron/types`: + +```typescript +import type { Call, Result, Route, Handler } from "./diachron/types"; +``` + +Application-specific domain types go in `backend/types.ts`. + +### Services + +Application services go in `backend/services.ts`. Framework services are +accessed through the `core` object: + +```typescript +import { core } from "./diachron/core"; + +core.database.db // Kysely database instance +core.logging.log // Logging +core.misc.sleep // Utilities +``` + +### Exports + +When a TypeScript file exports symbols, they should be listed in +alphabetical order. + +## Database + +diachron uses PostgreSQL exclusively, accessed through Kysely (type-safe +query builder). There is no ORM. + +- Write SQL via Kysely, not raw query strings (unless Kysely can't express + the query) +- Migrations live in `backend/migrations/` +- Run `./develop codegen` after schema changes to regenerate Kysely types + +## Key conventions + +### No dev/prod distinction + +There is no `NODE_ENV`. The application behaves identically everywhere. +Do not introduce environment-based branching. + +### Managed tooling + +Never reference globally installed `node`, `npm`, `npx`, or `pnpm`. +Always use `./cmd node`, `./cmd pnpm`, etc. + +### File ownership boundary + +``` +You may edit: backend/* (except backend/diachron/) +Do not edit: master/*, logger/*, diachron/*, backend/diachron/* +``` + +If a task requires framework changes, confirm with the user first. + +### Command safety tiers + +- `./cmd` -- Tool wrappers, always safe +- `./mgmt` -- Production-safe operations (migrations, user management) +- `./develop` -- Destructive operations, development only + +Never use `./develop` commands against production data. + +## Common tasks + +### Add a new route + +1. Define the route in `backend/routes.ts` as a `Route` object +2. Implement the handler in `backend/handlers.ts` +3. Add any needed types to `backend/types.ts` + +### Add a database migration + +1. Create a migration file in `backend/migrations/` +2. Run `./develop migrate` to apply it +3. Run `./develop codegen` to regenerate Kysely types + +### Install a package + +```bash +cd backend && ../cmd pnpm add +``` + +### Run a one-off TypeScript script + +```bash +./develop tsx backend/path/to/script.ts +``` + +## Things to avoid + +- Do not introduce `.env` files or `dotenv` without checking with the + team first. The configuration story is still being decided. +- Do not introduce webpack, vite, or other bundlers. The master process + handles building via `@vercel/ncc`. +- Do not add express middleware directly. Use the framework's route + processing in `backend/diachron/routing.ts`. +- Do not use `npm` or globally installed `pnpm`. Use `./cmd pnpm`. +- Do not add `NODE_ENV` checks or development/production branches.