Add newcomer guide, agent guide, and sample app files

DIACHRON.md explains the framework to newcomers joining a
diachron-based project.  diachron/AGENTS.md helps AI coding
agents work with the framework conventions and commands.
backend/types.ts and backend/services.ts are sample starting
points for application-specific types and services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 13:30:50 -05:00
parent cf04ecc78a
commit 44d904a07e
4 changed files with 375 additions and 0 deletions

168
DIACHRON.md Normal file
View File

@@ -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 <command>`** -- 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 <command>`** -- Management commands that are safe to run in
production. Migrations, user management, that sort of thing.
```bash
./mgmt migrate
./mgmt add-user
```
- **`./develop <command>`** -- 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

15
backend/services.ts Normal file
View File

@@ -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 };

8
backend/types.ts Normal file
View File

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

184
diachron/AGENTS.md Normal file
View File

@@ -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 <package>
```
### 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.