A real-time CQRS document system where TypeScript types are the single source of truth. Define entity shapes in one file, derive everything else — tables, CRUD functions, change triggers, WebSocket subscriptions.
Built with Bun, PostgreSQL, and zero frameworks.
domain.ts (types) --> SQL tables + functions + triggers --> WebSocket broadcasts
You define entities and their relationships as TypeScript types in domain.ts. The SQL layer is derived from those types — save_* functions handle insert/update, delete_* functions remove rows, and open_* functions compose documents as JSON. PostgreSQL triggers detect changes and broadcast via LISTEN/NOTIFY. A Bun WebSocket server picks up notifications and pushes them to subscribed clients in real time.
There is no ORM, no REST layer, no build step. The server is ~200 lines and doesn't change when the domain grows.
The system models venues that belong to organisations, with areas and sites within each venue, and bookings made by sponsor organisations. Users authenticate and act on behalf of organisations via an acts_for relationship.
Documents are composed views scoped by a key — a venue document includes its areas and sites, a booking document includes joined organisation and contact details. Clients subscribe to a document channel and receive updates whenever any participating table changes.
| Document | Criteria | What it contains |
|---|---|---|
venue |
venueId | Venue with areas and sites |
venueBookings |
venueId | Bookings with org/contact details |
orgVenues |
organisationId | Venue list for an organisation |
profile |
userId | User profile with org memberships |
Prerequisites: Bun, Docker.
bun create blueshed/typed my-project
cd my-project
bun run db # start PostgreSQL in Docker
bun run dev # start the WebSocket server with --watchSeed the database and interact via the CLI:
bun cli.ts register alice@example.com password123 "My Org"
bun cli.ts call save_venue '{"name":"My Venue","ownerId":1}'
bun cli.ts open venue 1 open_venue 1
bun cli.ts batch basic-data.jsonl- Runtime: Bun — server, tests, CLI
- Database: PostgreSQL 16 — tables, functions, triggers, LISTEN/NOTIFY
- Auth: JWT (via
jose) — register, login, token refresh - Protocol: WebSocket —
openDocsubscribes,callmutates,doc_changedpushes updates
bun testTests run against an isolated PostgreSQL instance on port 5433 (see compose.test.yml).
This project includes Claude Code skills that automate the type-to-SQL derivation. You describe what you want — Claude writes the types, tables, functions, and triggers following the project's patterns.
Strip the example entities (venues, bookings, etc.) and keep only user auth. Claude will ask you to choose an auth route:
| Route | What you get |
|---|---|
| password | Email/password register + login via WebSocket |
| oauth | GitHub (or other provider) login via HTTP callback |
| both | Password auth + OAuth with automatic account linking |
Describe a domain concept and Claude derives the full stack — types, tables, functions, and triggers. server.ts and types.ts never change.
> /init-domain
Claude asks which auth route you want, strips the example domain,
and leaves you with a clean slate: users + auth + profile document.
> /new-doc
Describe your domain — "I need projects with tasks, assigned to users"
— and Claude will agree on the types in domain.ts with you, then
derive the SQL. Run `bun run db` to apply, and it's live.
Repeat for each document in your model.
domain.ts TypeScript types — the source of truth
types.ts WebSocket message protocol
server.ts Bun WebSocket server
cli.ts CLI for interacting with the live system
init_db/
000_sys.sql Infrastructure (RPC dispatcher, safe notify)
001_tables.sql Domain tables
002_functions.sql save/delete/open functions
003_triggers.sql Change notification triggers
basic-data.jsonl Seed data for development