Skip to content

Commit 5d6ea3b

Browse files
committed
Update readme
1 parent 3cd7126 commit 5d6ea3b

8 files changed

Lines changed: 141 additions & 175 deletions

File tree

.github/workflows/ci.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Set up Go
1919
uses: actions/setup-go@v5
2020
with:
21-
go-version: "1.25"
21+
go-version: "1.24"
2222

2323
- name: Run go vet
2424
run: go vet ./...
@@ -33,10 +33,10 @@ jobs:
3333
fi
3434
3535
- name: Run golangci-lint (strict rules)
36-
uses: golangci/golangci-lint-action@v6
36+
uses: golangci/golangci-lint-action@v7
3737
with:
38-
version: v1.64
39-
args: --default=none --enable=nlreturn --enable=noinlineerr --enable=wsl_v5
38+
version: latest
39+
args: --default=none --enable=nlreturn
4040

4141
test:
4242
runs-on: ubuntu-latest
@@ -63,7 +63,7 @@ jobs:
6363
- name: Set up Go
6464
uses: actions/setup-go@v5
6565
with:
66-
go-version: "1.25"
66+
go-version: "1.24"
6767

6868
- name: Download dependencies
6969
run: go mod download
@@ -96,13 +96,19 @@ jobs:
9696
- name: Generate coverage report
9797
run: go tool cover -html=coverage.out -o coverage.html
9898

99+
- name: Convert coverage to Cobertura format
100+
run: |
101+
go install github.com/boumenot/gocover-cobertura@latest
102+
gocover-cobertura < coverage.out > coverage.xml
103+
99104
- name: Upload coverage reports to Codecov
100105
uses: codecov/codecov-action@v5
101106
continue-on-error: true
102107
with:
103108
token: ${{ secrets.CODECOV_TOKEN }}
104-
slug: hatmaxkit/hatmax
105-
files: ./coverage.out
109+
files: ./coverage.xml
110+
verbose: true
111+
fail_ci_if_error: false
106112

107113
- name: Upload coverage artifacts
108114
uses: actions/upload-artifact@v4
@@ -121,7 +127,7 @@ jobs:
121127
- name: Set up Go
122128
uses: actions/setup-go@v5
123129
with:
124-
go-version: "1.25"
130+
go-version: "1.24"
125131

126132
- name: Build
127133
run: go build -v ./...

README.md

Lines changed: 90 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -6,161 +6,115 @@
66

77
[![Go Reference](https://pkg.go.dev/badge/github.com/hatmaxkit/hatmax.svg)](https://pkg.go.dev/github.com/hatmaxkit/hatmax)
88
[![CI](https://github.com/hatmaxkit/hatmax/actions/workflows/ci.yml/badge.svg)](https://github.com/hatmaxkit/hatmax/actions)
9-
[![codecov](https://codecov.io/gh/hatmaxkit/hatmax/branch/main/graph/badge.svg)](https://codecov.io/gh/hatmaxkit/hatmax)
9+
[![codecov](https://codecov.io/gh/hatmaxkit/hatmax/branch/main/graph/badge.svg?token=VDYCRMI31Q)](https://codecov.io/gh/hatmaxkit/hatmax)
1010

11-
**A lightweight Go library for building single-binary web applications.**
11+
**A composable Go toolkit for building web applications with consistent wiring, clear configuration boundaries, and predictable package integration.**
1212

13-
## What is HatMax?
13+
## Overview
1414

15-
HatMax is a focused library for building web applications that compile to a single executable and use Postgres as their primary database. It provides authentication primitives, lifecycle management, and structured logging as core features, letting you focus on your domain logic instead of reinventing infrastructure.
15+
HatMax provides practical, composable packages for building web applications in Go.
1616

17-
If you've built a few Go web apps and found yourself copying similar patterns (lifecycle management, auth flows, database setup, middleware chains), HatMax extracts those patterns into small, composable packages. It's designed for applications that benefit from simplicity: one binary, one database.
17+
It includes:
1818

19-
## Core Principles
19+
- Consistent constructors and dependency wiring across packages.
20+
- Practical building blocks that work together out of the box.
21+
- Composable roles and interfaces instead of hidden global state.
22+
- Postgres-first primitives for auth, scheduling, pubsub, and app lifecycle.
2023

21-
- **Single binary deployment** - Build applications that compile to one executable. Simple deployment, straightforward operations.
22-
- **Just use Postgres** - Leverage Postgres for relational data, JSONB, full-text search, pub/sub, and queues. Start simple, add specialized tools only when truly needed.
23-
- **PubSub** - Domain events with no external broker. Default implementation uses Postgres `LISTEN`/`NOTIFY`.
24-
- **HTML + htmx** - Server-side rendering with htmx for dynamic interactions. Build modern UX with straightforward patterns.
25-
- **Explicit over magic** - Every dependency visible in constructors, every middleware applied manually, every lifecycle hook opt-in.
26-
27-
## What's Included
28-
29-
- **Lifecycle management** - Component startup, shutdown, and route registration with automatic discovery
30-
- **Authentication** - User signup, signin, sessions, password hashing, and middleware for protected routes
31-
- **Structured logging** - Context-aware logging with multiple output formats
32-
- **Configuration** - Load from YAML, environment variables, and command-line flags
33-
- **Database** - Postgres connection pooling, migrations, transactions, and health checks
34-
- **HTTP middleware** - Request tracking, logging, panic recovery, and CORS
35-
- **Model utilities** - ID generation, timestamp helpers, and common patterns
36-
- **Templates** - HTML rendering with embedded assets and htmx support
37-
- **Type-safe queries** - Integration with sqlc for compile-time SQL validation
38-
- **Testing patterns** - Comprehensive test coverage with practical examples
39-
40-
## Architecture
41-
42-
HatMax applications follow a straightforward pattern:
43-
44-
```
45-
HTTP Request
46-
47-
Handler (chi.Router)
48-
49-
Service Layer (business logic)
50-
51-
sqlc Queries (type-safe SQL)
52-
53-
Postgres
54-
```
55-
56-
### Direct Data Access
57-
58-
Services use `sqlc`-generated code directly. Write SQL queries, generate type-safe Go code, and call those methods from your service layer.
24+
## Quick Start
5925

6026
```go
61-
type UserService struct {
62-
queries *sqlc.Queries
63-
log log.Logger
64-
}
65-
66-
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
67-
return s.queries.GetUserByID(ctx, id)
27+
package main
28+
29+
import (
30+
"context"
31+
"embed"
32+
"fmt"
33+
"os"
34+
35+
"github.com/hatmaxkit/hatmax/app"
36+
"github.com/hatmaxkit/hatmax/config"
37+
"github.com/hatmaxkit/hatmax/db"
38+
"github.com/hatmaxkit/hatmax/log"
39+
"github.com/hatmaxkit/hatmax/mailer"
40+
"github.com/hatmaxkit/hatmax/pubsub/postgres"
41+
)
42+
43+
//go:embed assets/*
44+
var assetsFS embed.FS
45+
46+
func main() {
47+
cfg, err := config.Load("config.yml", "APP_", os.Args) // your env prefix
48+
if err != nil {
49+
fmt.Fprintf(os.Stderr, "cannot load config: %v\n", err)
50+
os.Exit(1)
51+
}
52+
53+
ctx := context.Background()
54+
logger := log.NewLogger(cfg)
55+
router := app.NewRouter(logger, app.WithPing(), app.WithDebugRoutes())
56+
57+
// Infrastructure
58+
database := db.New(assetsFS, "postgres", cfg, logger)
59+
events := postgres.New(database, cfg, logger)
60+
mail := mailer.New(cfg, logger)
61+
62+
// Application layer
63+
service := featname.NewService(database, events, mail, cfg, logger)
64+
handler := featname.NewHandler(service, cfg, logger)
65+
66+
deps := []any{
67+
database,
68+
events,
69+
mail,
70+
service,
71+
handler,
72+
}
73+
74+
starts, stops, registrars := app.Setup(ctx, router, deps...)
75+
76+
err = app.Start(ctx, logger, starts, stops, registrars, router)
77+
if err != nil {
78+
logger.Errorf("cannot start app: %v", err)
79+
os.Exit(1)
80+
}
6881
}
6982
```
7083

71-
### Clear Dependencies
72-
73-
Constructor signatures reveal what each component needs to function.
74-
75-
```go
76-
func NewUserHandler(
77-
authSvc *AuthService,
78-
userSvc *UserService,
79-
tmpl *TemplateManager,
80-
cfg *Config,
81-
log log.Logger,
82-
) *UserHandler
83-
```
84-
85-
### Lifecycle Discovery
84+
## Package Map
8685

87-
Components implement interfaces to declare their capabilities. The `Setup` function discovers these capabilities, and `Start` orchestrates initialization with rollback on failure.
86+
| Package | Role |
87+
| --------------------- | ---------------------------------------------------------------------- |
88+
| `app` | Lifecycle orchestration and component startup/shutdown |
89+
| `config` | Static configuration loading and structure |
90+
| `settings` | Dynamic runtime settings and attributes |
91+
| `db` | Postgres connection, migration helpers, DB wiring |
92+
| `auth` | Authentication, sessions, auth middleware primitives |
93+
| `mailer` | Pluggable mail delivery providers (SES, SendGrid, Mailgun, SMTP, Noop) |
94+
| `scheduler` | Background job scheduling and execution |
95+
| `pubsub` | Event publication/subscription (including Postgres implementation) |
96+
| `web` / `htmx` / `ui` | HTTP, template rendering, htmx helpers, UI primitives |
8897

89-
```go
90-
type Startable interface { Start(context.Context) error }
91-
type Stoppable interface { Stop(context.Context) error }
92-
type RouteRegistrar interface { RegisterRoutes(chi.Router) }
93-
94-
// In main.go
95-
database := db.New(assetsFS, "postgres", cfg, logger)
96-
authService := auth.NewService(database, cfg, logger)
97-
userHandler := user.NewHandler(authService, assetsFS, cfg, logger)
98-
99-
deps := []any{database, authService, userHandler}
100-
starts, stops, registrars := app.Setup(ctx, router, deps...)
101-
app.Start(ctx, logger, starts, stops, registrars, router)
102-
```
103-
104-
## Persistence
105-
106-
HatMax follows the "Just Use Postgres" philosophy, leveraging its capabilities to handle most application needs:
107-
108-
- **Relational data** - Standard tables and foreign keys
109-
- **Semi-structured data** - JSONB columns with indexing
110-
- **Full-text search** - `tsvector` and `pg_trgm`
111-
- **Pub/sub** - `LISTEN`/`NOTIFY` for real-time updates
112-
- **Queues** - Advisory locks + queue tables for background jobs
113-
- **Geospatial** - PostGIS extension for location data
114-
- **Time-series** - TimescaleDB extension for metrics
115-
116-
Queries are written in SQL and type-checked with `sqlc`. This keeps the data layer explicit and maintainable. As your application grows, you can integrate specialized tools where they provide clear value.
117-
118-
## Scope
119-
120-
HatMax is focused on single-binary applications with straightforward patterns:
98+
## Interfaces
12199

122-
- **Database** - Postgres-first approach with sqlc for type-safe queries
123-
- **Architecture** - Monolithic applications that deploy as one executable
124-
- **Presentation** - HTML templates with htmx for interactive experiences
125-
- **Data layer** - Direct SQL queries, explicit service coordination
126-
- **Authorization** - Core auth primitives as building blocks
127-
- **Interface** - Standard HTTP handlers and middleware
100+
Small interfaces make components swappable:
128101

129-
## Documentation
102+
| Component | Interface | Implementations |
103+
|-----------|-----------|-----------------|
104+
| Mail | `Mailer` | SMTP, SES, SendGrid, Mailgun |
105+
| Events | `Publisher`, `Subscriber` | Postgres |
106+
| Jobs | `JobStore` | Postgres |
130107

131-
- [Features](docs/features.md) - Package overview
132-
- [Gallery](docs/gallery.md) - Visual examples
133-
134-
## Development
135-
136-
### Running Tests
137-
138-
```bash
139-
# Run all tests
140-
make test
141-
142-
# Run tests with coverage by package
143-
make test-coverage
144-
145-
# Display coverage table by package
146-
make test-coverage-summary
147-
148-
# Generate HTML coverage report
149-
make test-coverage-html
150-
151-
# Run all quality checks (format, vet, test, coverage, lint)
152-
make check
153-
```
108+
## Key Patterns
154109

155-
## Status
110+
- **Transactional startup**: If component N fails to start, components 0→N-1 stop in reverse order automatically.
111+
- **Two config layers**: Static config at startup, dynamic settings with schema validation at runtime. Use what you need.
112+
- **Just Use Postgres**: PubSub, scheduler, and sessions run on Postgres. Add Redis or RabbitMQ if you need them, but you probably don't.
156113

157-
HatMax is under active development. The library API is stabilizing but may change before v1.0.
114+
## Docs
158115

159-
**Current focus:**
160-
- Core library packages (app, log, config, db, auth, middleware, model)
161-
- Authentication primitives (signup, signin, sessions, middleware)
162-
- Comprehensive test coverage and examples
163-
- Example application demonstrating all features
116+
- [Features](docs/features.md)
117+
- [Gallery](docs/gallery.md)
164118

165119
## License
166120

examples/ticked/main.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"os/signal"
99
"syscall"
1010

11-
"github.com/go-chi/chi/v5"
1211
"github.com/hatmaxkit/hatmax/app"
1312
"github.com/hatmaxkit/hatmax/auth"
1413
"github.com/hatmaxkit/hatmax/config"
@@ -44,9 +43,8 @@ func main() {
4443
ctx, cancel := context.WithCancel(context.Background())
4544
defer cancel()
4645

47-
router := chi.NewRouter()
46+
router := app.NewRouter(logger, app.WithPing(), app.WithDebugRoutes())
4847
router.Use(middleware.DefaultStack()...)
49-
app.ApplyRouterOptions(router, app.WithPing(), app.WithDebugRoutes())
5048

5149
database := db.New(assetsFS, "postgres", cfg, logger)
5250
migrator := db.NewMigrator(database, assetsFS, "postgres", logger)

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/hatmaxkit/hatmax
22

3-
go 1.25.5
3+
go 1.24.0
44

55
require (
66
aidanwoods.dev/go-paseto v1.6.0
@@ -26,6 +26,7 @@ require (
2626
golang.org/x/crypto v0.46.0
2727
golang.org/x/image v0.35.0
2828
golang.org/x/text v0.33.0
29+
gopkg.in/yaml.v3 v3.0.1
2930
)
3031

3132
require (
@@ -107,5 +108,4 @@ require (
107108
golang.org/x/sys v0.39.0 // indirect
108109
google.golang.org/grpc v1.78.0 // indirect
109110
google.golang.org/protobuf v1.36.11 // indirect
110-
gopkg.in/yaml.v3 v3.0.1 // indirect
111111
)

mailer/readme.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ if err := mailer.Send(ctx, msg); err != nil { ... }
2828

2929
```go
3030
// Static only (from config.Config.Mailer)
31-
m := mailer.NewFromConfig(cfg, log)
31+
m := mailer.New(cfg, log)
3232

33-
// Dynamic override (settings before cfg)
34-
m := mailer.NewWithConfig(settingsSvc, cfg, log)
33+
// Dynamic override (settings take precedence over cfg)
34+
m := mailer.NewWithSettings(settingsSvc, cfg, log)
3535
```
3636

37-
`NewWithConfig` resolves provider/mode with dynamic settings overrides on top of static config.
37+
`NewWithSettings` resolves provider/mode with dynamic settings overrides on top of static config.
3838

3939
## API
4040

mailer/runtime.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ func RegisterSchemas(r *settings.Registry) {
9797
}
9898
}
9999

100-
// NewFromConfig resolves mailer from static configuration.
101-
func NewFromConfig(cfg *config.Config, log log.Logger) Mailer {
102-
return NewWithConfig(nil, cfg, log)
100+
// New resolves mailer from static configuration.
101+
func New(cfg *config.Config, log log.Logger) Mailer {
102+
return NewWithSettings(nil, cfg, log)
103103
}
104104

105-
// NewWithConfig resolves mailer from static config and dynamic settings overrides.
106-
func NewWithConfig(settings SettingsProvider, cfg *config.Config, log log.Logger) Mailer {
105+
// NewWithSettings resolves mailer from static config and dynamic settings overrides.
106+
func NewWithSettings(settings SettingsProvider, cfg *config.Config, log log.Logger) Mailer {
107107
resolved := resolveFromConfig(cfg)
108108
resolved = applySettings(context.Background(), settings, resolved)
109109

0 commit comments

Comments
 (0)