faster queue reclaim sweep
repro
go -C adapters/queue/db test -run=NONE -bench=ReclaimStale -benchmem -count=10Describe your services in a small .zen schema. Zever compiles it into typed ORM, specs, migrations and routing, and wires every backend through swappable adapters. Your business logic stays yours.
curl -fsSL https://raw.githubusercontent.com/zenta-dev/zever/main/install.sh | shentity Task {
id: uuid @primary
title: string @validate(min_len: 1, max_len: 200)
done: bool
created_at: timestamp @default(now())
}
service TaskService {
rpc CreateTask(title: string) -> Task {
http: POST "/v1/tasks"
auth: required
}
rpc ListTasks() -> Task {
http: GET "/v1/tasks"
auth: required
paginated: true
}
}zever generatetask.zenorm.goGenerics table + column handlesopenapi.jsonSpec from the same schemaschema.hclAtlas schema from the same sourceroutes.goPOST /v1/tasks, GET /v1/tasksEvery service wants a pool, a config path, a shutdown hook. Zever resolves them lazily from one config and closes them in dependency order.
package main
import (
"context"
"database/sql"
"log/slog"
"net/smtp"
"os"
"os/signal"
"strconv"
"syscall"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/redis/go-redis/v9"
)
func main() {
log := slog.New(slog.NewJSONHandler(os.Stdout, nil))
dsn := os.Getenv("DB_DSN")
if dsn == "" {
log.Error("DB_DSN required")
os.Exit(1)
}
maxConns, _ := strconv.Atoi(os.Getenv("DB_MAXCONNS"))
db, err := sql.Open("pgx", dsn)
if err != nil {
log.Error("db", "err", err)
os.Exit(1)
}
db.SetMaxOpenConns(maxConns)
db.SetConnMaxLifetime(30 * time.Minute)
rdb := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDR")})
q := newRedisQueue(rdb, "jobs", 30*time.Second)
auth := smtp.PlainAuth("", os.Getenv("SMTP_USER"),
os.Getenv("SMTP_PASS"), os.Getenv("SMTP_HOST"))
mailer := newMailer(os.Getenv("SMTP_HOST")+":587", auth)
ctx, stop := signal.NotifyContext(context.Background(),
os.Interrupt, syscall.SIGTERM)
defer stop()
go q.Work(ctx, handleJobs(db, mailer, log))
srv := newServer(db, rdb, q, mailer, log)
go srv.ListenAndServe()
<-ctx.Done()
sctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(sctx)
q.Close()
mailer.Close()
rdb.Close()
db.Close()
}cfg, _ := config.Load("")
c := container.New(cfg)
defer c.Close(ctx)
db, _ := c.DB()
q, _ := c.Queue()
mail, _ := c.Mailer()Entities and services in a small .zen file. Validation, defaults and HTTP bindings live next to the types.
One command emits the typed ORM, the OpenAPI spec and the migration DDL, all from the same source of truth.
Your handler asks the container for what it needs. The adapter behind c.DB() is a config line, not a code change.
Migrate, serve, call. Business logic stays yours; the infrastructure was never your problem.
Entities and services in a small .zen file. Validation, defaults and HTTP bindings live next to the types.
entity Task {
id: uuid @primary
title: string @validate(min_len: 1, max_len: 200)
done: bool
created_at: timestamp @default(now())
}
service TaskService {
rpc CreateTask(title: string) -> Task {
http: POST "/v1/tasks"
auth: required
}
rpc ListTasks() -> Task {
http: GET "/v1/tasks"
auth: required
paginated: true
}
}// Code generated by the zever DSL zenorm backend. DO NOT EDIT.
package app
import (
"time"
"github.com/zenta-dev/zever/orm"
)
// Tasks is the orm.Table for Task, generated from the "Task" entity.
var Tasks = orm.NewTable[Task]("tasks", []string{"id", "title", "done", "created_at"})
// TaskCols holds typed column references for Task.
var TaskCols = struct {
ID orm.Column[Task, string]
Title orm.Column[Task, string]
Done orm.Column[Task, bool]
CreatedAt orm.Column[Task, time.Time]
}{
ID: orm.NewColumn[Task, string]("tasks", "id"),
Title: orm.NewColumn[Task, string]("tasks", "title"),
Done: orm.NewColumn[Task, bool]("tasks", "done"),
CreatedAt: orm.NewColumn[Task, time.Time]("tasks", "created_at"),
}
// Task mirrors the "Task" entity declared in the schema.
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
CreatedAt time.Time `json:"created_at"`
}{
"openapi": "3.0.3",
"info": { "title": "zever API", "version": "0.0.0" },
"paths": {
"/v1/tasks": {
"post": {
"operationId": "TaskService_CreateTask",
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/default_TaskServiceCreateTaskRequest" } } }
},
"responses": {
"200": {
"description": "OK",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/default_Task" } } }
}
},
"security": [{ "bearerAuth": [] }]
},
"get": {
"operationId": "TaskService_ListTasks",
"security": [{ "bearerAuth": [] }]
}
}
}
}table "tasks" {
schema = schema.public
column "id" {
null = false
type = uuid
}
column "title" {
null = false
type = varchar(200)
}
column "done" {
null = false
type = boolean
}
column "created_at" {
null = false
type = timestamptz
}
primary_key {
columns = [column.id]
}
}// You write this. Zever resolved the pool.
func (s *TaskService) CreateTask(ctx context.Context, req *app.CreateTaskRequest) (*app.Task, error) {
db, err := s.c.DB() // sqlite today, postgres by config
if err != nil {
return nil, err
}
t := &app.Task{ID: uuid.NewString(), Title: req.Title, CreatedAt: time.Now().UTC()}
err = orm.InsertInto(app.Tasks).Values(
orm.Set(app.TaskCols.ID, t.ID),
orm.Set(app.TaskCols.Title, t.Title),
orm.Set(app.TaskCols.Done, false),
orm.Set(app.TaskCols.CreatedAt, t.CreatedAt),
).Exec(ctx, db)
return t, err
}$ zever generate
wrote orm, openapi, atlas, routes
$ zever db migrate
applied schema.hcl
$ zever serve
listening on :8080
$ curl -X POST localhost:8080/v1/tasks -d '{"title":"ship"}'
{"id":"7c1e...","title":"ship","done":false}Every battery is a small interface with interchangeable adapters. Pick one in .env; your code keeps calling the same accessor.
DB_ADAPTER=sqliteDB_PATH=data/app.dbc := container.New(cfg)db, err := c.DB()if err != nil { return err }Each battery is one small interface in core/, with adapters you register and swap by config.
Typed generics query builder, migrations, seeding.
sqlitepostgresOne interface, memory to redis.
memoryredisdbFull-text across db, postgres, meilisearch.
dbpostgresmeilisearchsqliteEmbeddings on pgvector or qdrant.
dbsqlitepgvectorqdrantLocal, S3, R2 behind one API.
locals3r2Durable jobs with visibility timeouts.
memoryredisdbCron on the same queue.
embeddedpostgresIn-process or redis pub/sub.
memoryredisTransactional events, db or CDC.
memorydbcdcLong-running flows with compensation.
memorydbpostgresLog in dev, SMTP in prod.
logsmtpSMS and push with one call.
logtwiliofcmSigned delivery, queued retries.
httpqueueJWT, sessions, OIDC.
jwtsessionoidcRBAC and casbin policies.
nooprbaccasbinEnv or vault, never logged.
envvaultargon2id defaults.
argon2idToken bucket, memory or redis.
memoryredisSafe retries across processes.
memoryredisTraces and metrics via OTLP.
noopstdoutotlpzerolog, slog, pretty.
noopzerologslogprettyBreakers and bulkheads.
memoryredisStatic or firebase.
staticfirebaseSingle or header-resolved.
singleheaderfiber or stdlib net/http.
fiberstdhttpAnthropic, OpenAI, Gemini, Ollama.
anthropicopenaigeminiollamaLocal or S3-backed pipelines.
locals3PDF and LaTeX rendering.
localremotelatexStripe and Paddle.
stubstripepaddleGeocoding, google or OSM.
googlestaticosmServices open on first accessor call and nothing sooner. Shutdown walks the dependency graph backwards, so no one uses a closed pool.
Close order: scheduler, job, snapshots, cache and queue, db pools, gRPC.
errors.Join; panics become *ClosePanicError.*CloseTimeoutError; 5s per service unless the context has a deadline.Defaults are sqlite, memory and log, so a clean machine goes from nothing to a listening server without any external service.
Grouped command map. Every row is an exact CLI invocation, so nothing hides behind a prompt.
zever newzever add <battery>zever generatezever extractzever compilezever checkzever breakingzever fmtzever doctorzever routeszever explainzever graphzever servezever devzever queue:workzever schedule:runzever tinkerzever db migratezever db rollbackzever db seedBare zever prints usage and exits 1, so scripts never hang on a prompt. CLI reference →
The same flags-only CLI humans use is stable enough for an agent: structured output, safe previews, and nothing that waits on a prompt.
Global --json envelopes with typed exit codes, and --help --agent prints a command catalog agents can parse.
--dry-run works across all nine generate subcommands: validation and rendering run, no files are written.
compile --backend mcp renders each service RPC as an MCP tool and emits a runnable stdio server with handler stubs.
zever docs --skill writes a SKILL.md that teaches Claude Code the commands and safe workflows.
Add a Booking entity with a guest and a starts_at time.
Previewing first with --dry-run.
$ zever generate entity bookings Booking \
--field guest:string --field starts_at:timestamp --dry-rundry run: no files writtenwould append to internal/bookings/bookings.zen:+ entity Booking {+ id: uuid @primary+ guest: string+ starts_at: timestamp+ created_at: timestamp @default(now())+ }Looks right. Re-running without the flag to apply it.
Each figure is a before/after from a committed benchmark. Open the repro to run the exact command yourself.
faster queue reclaim sweep
go -C adapters/queue/db test -run=NONE -bench=ReclaimStale -benchmem -count=10fewer allocs in closest matcher
go -C cmd/zever test -run=NONE -bench='^BenchmarkClosest$' -benchmemallocs per mailer-log Send
go -C adapters/mailer/log test -run=NONE -bench='^BenchmarkSend$' -benchmemfaster config env overlay
go -C config test -run=NONE -bench=ApplyEnv -benchmem -count=10Point-in-time micro-benchmarks (v0.8.0): compare shapes (allocs/op) and relative deltas, not raw ns. Results vary by machine and Go version. Full results and methodology →
Every example is a runnable app or focused snippet in the repository.
Largest end-to-end app: auth, queue, search, workers.
View on GitHub TutorialThe Booking API tutorial, runnable.
View on GitHub StarterSmallest useful schema-to-API loop.
View on GitHub ORMTransactions and rollback patterns.
View on GitHub SearchFull-text search on sqlite and postgres.
View on GitHub ORMCursor and offset pagination.
View on GitHub ORMRecursive CTE queries, typed.
View on GitHub ORMBatch upserts without raw SQL.
View on GitHub ORMJSON column queries.
View on GitHub AdaptersPlug in a custom SMS adapter.
View on GitHubShort answers to what people ask before they try it.
No. It compiles a schema into plumbing (ORM, specs, DDL, routing glue) and wires services through one container. Handlers and business logic stay yours.
Never. Only the infrastructure that logic runs on.
Change DB_ADAPTER in .env. Code calling c.DB() is untouched.
No. Defaults use sqlite, memory, local, log and noop adapters, so the first build passes on a clean machine.
Config is strictly decoded: unknown services or fields fail at load, not at 3am.
Yes. The CLI is agent-friendly, generate supports --dry-run, and there is an MCP codegen backend plus an agent skill.
Go 1.27+.
Apache-2.0.
One schema, one container, every adapter a config change away.
curl -fsSL https://raw.githubusercontent.com/zenta-dev/zever/main/install.sh | sh