generated from coulomb/repo-seed
Adds all Phase 0 content that was created but never committed: - CLAUDE.md and SCOPE.md — agent and developer orientation - specs/TailwindForInteractionHubs_v0.2.md — IHF Tailwind coding guide - docs/ — five IHP v1.5 reference guides (overview, data, controllers, realtime, ihf-mapping) - workplans/IHUB-WP-0001 — Phase 1 implementation plan (12 tasks, state-hub synced) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
244 lines
8.9 KiB
Markdown
244 lines
8.9 KiB
Markdown
# IHP Framework Overview
|
||
|
||
> Reference notes for implementing the Interaction Hub Framework (IHF) using IHP.
|
||
> Based on IHP v1.5.0 (released March 2026).
|
||
|
||
---
|
||
|
||
## What IHP Is
|
||
|
||
**IHP** (Integrated Haskell Platform) is a batteries-included, full-stack web framework built on Haskell and Nix. Its goal is rapid application development with robust, type-safe code.
|
||
|
||
- **Language:** Haskell (GHC 9.10 default; GHC 9.12 experimental in v1.5)
|
||
- **Paradigm:** Functional, strongly typed, server-rendered with optional realtime
|
||
- **Creator:** [digitally induced](https://github.com/digitallyinduced) (Hamburg). Open-sourced 2020, in production since 2017
|
||
- **Current version:** v1.5.0 (March 25, 2026) — largest release to date (1,051 commits)
|
||
- **License:** MIT
|
||
|
||
### v1.5 Headline Changes
|
||
|
||
| Change | Impact |
|
||
|--------|--------|
|
||
| `postgresql-simple` → `hasql` driver | Up to 50% lower query latency |
|
||
| Dev server RAM: 4 GB → 500–800 MB | Practical on smaller machines |
|
||
| Session middleware 3×, URL gen 5×, rendering 2× faster | Overall snappier |
|
||
| `typedSql` quasiquoter | Compile-time SQL type checking against live dev DB |
|
||
| `fetchPipelined` | Multiple queries in one network round-trip |
|
||
| Composite primary key support | Needed for join-table models |
|
||
| Integration test mode | Temporary Postgres DB per test run |
|
||
| 15+ modules on Hackage separately | `ihp-mail`, `ihp-datasync`, etc. |
|
||
|
||
### Design Philosophy
|
||
|
||
- Type errors at compile time, not runtime
|
||
- Single command (`devenv up`) starts a fully self-contained environment — Postgres included, managed by Nix. No Docker, no Kubernetes required
|
||
- Optimized for AI-assisted development — the type system automatically verifies generated code
|
||
|
||
---
|
||
|
||
## Core Architecture
|
||
|
||
### MVC-Influenced Structure
|
||
|
||
| Layer | IHP Location | Role |
|
||
|-------|-------------|------|
|
||
| Model | `Application/Schema.sql` + generated types | Schema, query builder, relationships |
|
||
| Controller | `Web/Controller/<Name>.hs` | Action handlers, parameter binding, DB calls |
|
||
| View | `Web/View/<Name>/<Action>.hs` | HSX templates, `View` typeclass |
|
||
| Routing | `Web/Routes.hs` + `Web/FrontController.hs` | URL ↔ action mapping |
|
||
| Types | `Web/Types.hs` | All controller action constructors |
|
||
| Helpers | `Application/Helper/Controller.hs`, `Application/Helper/View.hs` | Shared logic |
|
||
|
||
Multi-application support: a single project can contain multiple sub-apps (`Web/`, `Admin/`). `new-application admin` generates an `Admin/` subtree with routes auto-prefixed `/admin/`.
|
||
|
||
### Type-Safe URL / Action System
|
||
|
||
The IHP router always maps HTTP requests to **data constructors** defined in `Web/Types.hs`:
|
||
|
||
```haskell
|
||
data WidgetsController
|
||
= WidgetsAction
|
||
| NewWidgetAction
|
||
| ShowWidgetAction { widgetId :: !(Id Widget) }
|
||
| CreateWidgetAction
|
||
| EditWidgetAction { widgetId :: !(Id Widget) }
|
||
| UpdateWidgetAction { widgetId :: !(Id Widget) }
|
||
| DeleteWidgetAction { widgetId :: !(Id Widget) }
|
||
deriving (Eq, Show, Data)
|
||
```
|
||
|
||
- URLs generated from values, not strings: `pathTo ShowWidgetAction { widgetId = someId }`
|
||
- Compile-time guarantee: broken links are type errors, not 404s
|
||
- `urlTo` generates absolute URLs (protocol + domain)
|
||
|
||
### Routing
|
||
|
||
Defined in `Web/Routes.hs`, registered in `Web/FrontController.hs`.
|
||
|
||
**AutoRoute** (most common): `instance AutoRoute WidgetsController` — IHP generates RESTful routes from action name prefixes:
|
||
|
||
| Prefix | HTTP Method |
|
||
|--------|------------|
|
||
| `Delete` | DELETE |
|
||
| `Create`, `Update` | POST/PATCH |
|
||
| anything else | GET |
|
||
|
||
**Custom routes:** implement `CanRoute` (attoparsec parser URL → action) and `HasPath` (reverse). `customRoutes` overrides individual AutoRoute entries. Supports SEO slugs like `/widgets/my-slug`.
|
||
|
||
HTTP method override for HTML forms: pass `_method=DELETE` (or `PATCH`) as a hidden field.
|
||
|
||
---
|
||
|
||
## Development Workflow
|
||
|
||
### Installation
|
||
|
||
```bash
|
||
# 1. Install Determinate Nix (with Flakes + lazy-trees)
|
||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||
|
||
# 2. Install ihp-new
|
||
nix profile install nixpkgs#ihp-new
|
||
|
||
# 3. Install direnv and hook into shell
|
||
nix profile add nixpkgs#direnv
|
||
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc # or zshrc
|
||
```
|
||
|
||
### Creating a Project
|
||
|
||
```bash
|
||
ihp-new my-project
|
||
cd my-project
|
||
devenv up
|
||
```
|
||
|
||
First startup: 10–15 minutes (downloads GHC, Postgres, all Haskell deps via Nix binary cache). Subsequent starts are fast (under 30s).
|
||
|
||
### Dev Server
|
||
|
||
`devenv up` starts everything:
|
||
- Application server on `http://localhost:8000`
|
||
- IHP IDE + Schema Designer on `http://localhost:8001`
|
||
- Postgres (managed by Nix; no system Postgres needed)
|
||
- Live reloading (typically sub-50ms after save)
|
||
|
||
### Project File Structure
|
||
|
||
```
|
||
my-project/
|
||
├── Application/
|
||
│ ├── Schema.sql # Single source of truth for all DB types
|
||
│ ├── Migration/ # <timestamp>-description.sql files
|
||
│ ├── Helper/
|
||
│ │ ├── Controller.hs # Shared controller helpers
|
||
│ │ └── View.hs # Shared view helpers
|
||
│ └── Script/ # One-off scripts / cron job binaries
|
||
├── Web/
|
||
│ ├── Types.hs # ALL controller action constructors
|
||
│ ├── Routes.hs # AutoRoute instance declarations
|
||
│ ├── FrontController.hs # WAI entry; dispatch; auth init; default layout
|
||
│ ├── Controller/ # One file per controller
|
||
│ ├── View/ # One dir per controller, one file per action
|
||
│ │ └── Layout.hs # Default layout (Html -> Html)
|
||
│ └── Component/ # Server-Side Components (optional)
|
||
├── Config/
|
||
│ ├── Config.hs # Env vars, secrets, feature flags
|
||
│ └── nix/
|
||
│ └── hosts/
|
||
│ └── production/ # Declarative NixOS server config
|
||
├── Test/ # Integration tests
|
||
├── static/ # CSS, JS, images
|
||
├── flake.nix # Nix flake — all deps declared here
|
||
├── App.cabal # Cabal package definition
|
||
├── Main.hs # Entry point
|
||
└── Makefile # Build targets
|
||
```
|
||
|
||
### Adding Dependencies
|
||
|
||
In `flake.nix`:
|
||
```nix
|
||
ihp = {
|
||
enable = true;
|
||
projectPath = ./.;
|
||
packages = [ pkgs.imagemagick ]; # native deps
|
||
haskellPackages = p: [
|
||
p.ihp
|
||
p.aeson
|
||
p.your-library
|
||
];
|
||
};
|
||
```
|
||
|
||
### Code Generators
|
||
|
||
Navigate to `http://localhost:8001/Generators` or right-click a table in Schema Designer → "Generate Controller". Scaffolds controllers, views, routes, and type entries to match the table's fields. Also available: Migration, Job, Mail, Script generators.
|
||
|
||
### Testing
|
||
|
||
v1.5: integration test mode creates a temporary Postgres DB automatically per test run. Tests live in `Test/`.
|
||
|
||
---
|
||
|
||
## Deployment
|
||
|
||
### Primary: NixOS / `deploy-to-nixos`
|
||
|
||
The entire server config (nginx, Let's Encrypt, systemd, app config) lives declaratively in `Config/nix/hosts/production/` — version-controlled and reproducible.
|
||
|
||
```bash
|
||
deploy-to-nixos production
|
||
```
|
||
|
||
Runs `nixos-rebuild` remotely over SSH. AWS EC2: NixOS AMI, `t3a.small` min (`t3a.medium` recommended), 60 GiB root disk, ports 22/80/443.
|
||
|
||
**Required env vars:**
|
||
|
||
| Variable | Purpose |
|
||
|----------|---------|
|
||
| `IHP_SESSION_SECRET` | Session encryption key (`new-session-secret` to generate) |
|
||
| `DATABASE_URL` | Postgres connection string |
|
||
| `IHP_BASEURL` | External URL (e.g., `https://example.com`) |
|
||
|
||
**Production features built-in:**
|
||
- Systemd watchdog (heartbeat 30s; auto-restart after 60s)
|
||
- Socket activation (queues requests during restarts — zero-downtime deploys)
|
||
- Sentry integration via `ihp-sentry` (IHP Pro)
|
||
|
||
### Docker (IHP Pro)
|
||
|
||
```bash
|
||
nix build .#unoptimized-docker-image --option sandbox false
|
||
cat result | podman load
|
||
```
|
||
|
||
Env vars: same as above + `IHP_ASSET_VERSION` (cache-busting) + `IHP_REQUEST_LOGGER_IP_ADDR_SOURCE=FromHeader` (behind load balancer). Minimal Docker images lack CA certificates — copy `caCertificates` and set `SSL_CERT_FILE`.
|
||
|
||
### Bare Metal Binary
|
||
|
||
```bash
|
||
nix build .#optimized-prod-server # full optimisation
|
||
nix build .#unoptimized-prod-server # faster build, for staging
|
||
```
|
||
|
||
Runtime: `IHP_ENV=Production`, `DATABASE_URL`, `PORT` (default 8000). GHC RTS tunable via `GHCRTS`.
|
||
|
||
### CSS/JS Bundling
|
||
|
||
```bash
|
||
make static/prod.js static/prod.css
|
||
```
|
||
|
||
Dev uses individual files; production uses bundled/minified versions. Background jobs require deploying the `RunJobs` binary alongside the main app.
|
||
|
||
---
|
||
|
||
## Key Links
|
||
|
||
- [IHP Homepage](https://ihp.digitallyinduced.com/)
|
||
- [IHP Guide (full docs)](https://ihp.digitallyinduced.com/Guide/)
|
||
- [GitHub: digitallyinduced/ihp](https://github.com/digitallyinduced/ihp)
|
||
- [ihp-boilerplate](https://github.com/digitallyinduced/ihp-boilerplate) — template used by `ihp-new`
|
||
- [v1.5 release announcement](https://discourse.haskell.org/t/ihp-v1-5-has-been-released/13846)
|