caddymodule

package module
v0.1.73 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

hazedb as a Caddy module

This package wraps hazedb as a Caddy HTTP handler, so a single Caddy binary serves your site and an in-process SQL store — no separate database process, no network hop.

It exposes five endpoints. POST /query and POST /exec take a JSON body {"sql": "...", "args": [...]}; the GET routes take URL parameters.

Endpoint Runs Returns
POST /query ad-hoc SELECT (incl. JOINs) {"columns":[...],"rows":[[...],...]}
POST /exec INSERT / UPDATE / DELETE / CREATE TABLE / DROP TABLE {"affected":N}
GET /get?table=T&id=UUID (or &col=C&val=V) [&cols=a,b] one-row read (PK or indexed-column fast path) one JSON object, or null
GET /list?table=T [&cols=a,b][&col=C&val=V][&limit=N] multi-row read [{...},...]
GET /meta store-size overview {"tables":N,"max_bytes":M,"total_rows":R,"total_approx_bytes":B,"total_tombstones":T,"table_stats":[{name,rows,columns,indexes,approx_bytes,tombstones},...]}

GET /meta takes no parameters; it reports the table count, the configured max_bytes cap (0 = unlimited), the store-wide total_rows / total_approx_bytes / total_tombstones, and per table the row / column / index counts, an approximate in-RAM byte size, and tombstones — for dashboards and health checks. The byte sizes are estimates (cell payloads plus modeled overhead, biased slightly high), not exact accounting.

Tombstones are rows deleted but whose memory slot is not yet reclaimed. A background sweeper compacts shards that have gone mostly dead, so tombstones is a gauge — it rises with deletes and falls as the sweeper runs, rather than only resetting on restart. A momentarily high tombstones / (rows + tombstones) fraction between sweeps is normal; a persistently high one means deletes are outrunning the sweeper. (Scan cost is independent — partitioned scans stay proportional to live rows regardless.)

Byte cap. Set max_bytes (below) to bound the store's RAM. An INSERT that would push total_approx_bytes past the cap is rejected with HTTP 507 (Insufficient Storage); the store never auto-evicts, so the client frees space with DELETE / DROP TABLE. total_approx_bytes vs max_bytes from /meta is the headroom gauge.

args is an optional positional list for ? placeholders. JSON → SQL value mapping: number → INT, bool → BOOL, null → NULL, string → STRING, except a string in canonical UUID form (8-4-4-4-12 hex) → UUID — so you can address and insert UUID columns from JSON.

Build a Caddy binary with the module

Uses xcaddy. Until the core hazedb version that ships db_registry.go + wire.go is tagged, build against the local checkout (the submodule's replace points at ../):

# from a checkout of this repo
xcaddy build \
    --with github.com/VeloxCoding/hazedb/caddymodule=./caddymodule \
    --with github.com/VeloxCoding/hazedb=.

After the core is published, the plain form works:

xcaddy build --with github.com/VeloxCoding/hazedb/caddymodule

Configure (Caddyfile)

:8080 {
    handle /db/* {
        hazedb {
            wal            on                        # on (default) = durable (crash loses ≤~0.5s); off = memory-only
            wal_path       /var/lib/hazedb/wal       # optional: override the WAL directory. Default <caddy-data-dir>/hazedb/wal
            companion_path /var/lib/hazedb/hazedb.db # optional: on-disk SQLite companion (events/health always, + data mirror when wal is on). Default <caddy-data-dir>/hazedb/hazedb.db (or next to wal_path)
            init_sql       /etc/hazedb/schema.sql   # CREATE TABLE + seed, run once at startup
            registry_name  default                  # name the *DB is published under for the PHP extension
            max_body_bytes 4194304                  # POST body cap for /query and /exec (default 4 MiB)
            max_bytes      1073741824               # cap the store's RAM (1 GiB); over-cap INSERT → HTTP 507. 0/unset = unlimited
        }
    }
}

All subdirectives are optional. By default the store is durable (wal on): with nothing set, the WAL and the SQLite companion both live under <caddy-data-dir>/hazedb/ (e.g. /var/lib/caddy/hazedb/ under systemd). Set wal off for a memory-only store. The WAL has one durability story — writes seal to disk within ~0.5s, so a crash loses at most that window; there are no durability levels or fsync modes. Tables are created at runtime: put your CREATE TABLE statements in the init_sql file, or POST them to /exec.

Schema / tables

hazedb creates tables at runtime, so the module opens with an empty schema. Define tables one of two ways:

  • init_sql — a file of ;-separated statements run once at startup (typical: CREATE TABLE ... plus any seed rows). It runs as a trusted script, so a ; inside a string literal is safe and seed rows may use inline literal values.
  • POST /exec — send CREATE TABLE ... like any other write.

Sharing the instance with PHP

The module publishes its *DB in hazedb's process-wide registry under registry_name (default "default"). The FrankenPHP/PHP extension (addons/frankenphp-ext) looks up that same name, so PHP calls and HTTP calls hit one identical database — no second copy.

WAL + config reload caveat

With wal_path set, a Caddy graceful config reload runs the new handler's Provision (which opens the WAL file) before the old handler's Cleanup (which closes it) — briefly two writers on one file. Memory mode reloads cleanly. For durable deployments, prefer a full restart over a graceful reload when changing this handler.

Documentation

Overview

Package caddymodule exposes hazedb as a Caddy HTTP handler.

The core hazedb package stays Caddy-free; this adapter owns the transport: it opens a *DB, serves GET /get (single-row read), GET /list (multi-row read), POST /query, POST /exec, and GET /meta (store-size overview) over an internal mux, and registers the *DB under a name in the core registry so an in-process consumer (the FrankenPHP/PHP extension) reaches the very same instance. Per the gateway boundary in the RFC, request-context cross-cutting concerns (auth, per-tenant routing, rate limits) belong here, never in core.

Schema: the module opens with an empty schema by default. hazedb supports runtime CREATE TABLE, so operators define tables via an init_sql file (run once at Provision) or by POSTing DDL to /exec.

WAL + reload caveat: with wal_path set, Caddy config reload runs the new Provision (which opens the same file) before the old Cleanup (which closes it) — two writers on one WAL file for that window. Since WAL is on by default, this applies to a default deployment; memory mode (wal off) reloads cleanly. For durable deployments, restart rather than graceful-reload when changing this handler.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// WAL switches the write-ahead log on or off. "on" (the default; empty means
	// the default) makes the store durable: a write is sealed to disk within ~0.5s,
	// so a crash loses at most that window. "off" is memory-only — nothing persists
	// across a restart.
	WAL string `json:"wal,omitempty"`
	// WALPath optionally overrides the directory holding the WAL segments. Empty
	// uses the default <caddy-data-dir>/hazedb/wal. Ignored when wal is off.
	WALPath string `json:"wal_path,omitempty"`
	// CompanionPath optionally overrides where the SQLite companion file lives —
	// always a real file on disk, present whether wal is on or off. It holds the
	// _hz_events operational log (logging, health) in every mode, plus the data
	// mirror and recovery base when wal is on. Empty defaults to
	// <caddy-data-dir>/hazedb/hazedb.db, or <wal_path>/hazedb.db when a custom
	// wal_path is set. An in-memory path (:memory:) is rejected — never in-memory.
	CompanionPath string `json:"companion_path,omitempty"`
	// InitSQL is an absolute path to a .sql file run once at Provision, before
	// Caddy serves — typically CREATE TABLE + seed rows. It runs as a trusted
	// script (ExecScript): statements are split on top-level ';' (a ';' inside a
	// string literal is safe) and may use inline literal values for seed data.
	InitSQL string `json:"init_sql,omitempty"`
	// RegistryName is the name the *DB is published under for in-process
	// consumers. Empty = "default" (what the PHP extension looks up).
	RegistryName string `json:"registry_name,omitempty"`
	// MaxBodyBytes caps the POST body for /query and /exec, in bytes. 0 = the
	// 4 MiB default. Bounds per-request memory against an oversized body — and
	// since the SQL string and the positional-arg array both live in that body,
	// it bounds their sizes too.
	MaxBodyBytes int64 `json:"max_body_bytes,omitempty"`
	// MaxBytes caps the store's total in-RAM size, in bytes. 0 = unlimited. An
	// INSERT that would push the store past it is rejected with HTTP 507; the
	// store never auto-evicts, so the client frees space with DELETE / DROP TABLE.
	// Distinct from MaxBodyBytes (a per-request limit) — this bounds the whole
	// dataset.
	MaxBytes int64 `json:"max_bytes,omitempty"`
	// contains filtered or unexported fields
}

Handler is the Caddy HTTP handler that embeds hazedb.

func (Handler) CaddyModule

func (Handler) CaddyModule() caddy.ModuleInfo

CaddyModule registers the handler under http.handlers.* so it works as a `handle`/`hazedb` directive or a JSON handler entry.

func (*Handler) Cleanup

func (h *Handler) Cleanup() error

Cleanup deregisters and closes the *DB. DeregisterDBIf is the CAS-safe form: during a config reload the new Provision has already overwritten the slot, so this won't clobber it; it only clears when the handler is fully removed.

func (*Handler) Provision

func (h *Handler) Provision(ctx caddy.Context) error

Provision opens the *DB, runs init_sql, wires the routes, and registers the instance. Called once per module instance at Caddy start / config reload.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error

ServeHTTP dispatches to the hazedb mux; unmatched paths fall through to the next handler, so the module can be mounted under a prefix alongside others.

func (*Handler) UnmarshalCaddyfile

func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error

UnmarshalCaddyfile parses the `hazedb` handler directive. Example:

hazedb {
    wal             on                       # on (default) | off (memory-only)
    wal_path        /var/lib/hazedb/wal      # optional: override the WAL directory
    companion_path  /var/lib/hazedb/hazedb.db
    init_sql        /etc/hazedb/schema.sql
    registry_name   default
    max_bytes       1073741824
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL