Buildable example
Plugins & comptime config — ZigBase examples
The advanced framework surface: plugins, schema, migrations, pool levers, and a Zigapagos frontend embedded in the executable.
The example starts from the comptime minimal resource profile and explicitly overrides its reader, scheduler-worker, memory-job-worker, and SQLite-cache settings. Run the built binary with resources to inspect the compiled settings as JSON. Profiles select transparent defaults; they do not enable features or impose a process memory cap. memory_job_workers reports the lazy pool count, with job_stack_bytes shared by scheduler and memory workers after its floor.
The example also limits synchronous HTTP admission to three callbacks. Its source shows how to opt into a shared HTTP/memory-job work-count budget: forward coordinated-admission to the ZigBase dependency and add .admission.max_work. The default example leaves that additional integration compiled out; a shared count does not imply a byte or RSS cap. The same source comment illustrates optional .admission.max_job_bytes for retained memory-job payload and app.submit name copies, independently of the work-count limit. This narrower byte ceiling still does not bound total RSS.
The report’s envelope makes the tradeoffs explicit: this example retains up to four idle SQLite readers with 512 KiB soft cache targets (2.5 MiB including the writer), and admits at most three synchronous HTTP callbacks. Overflow reader connections and transport buffers are additional costs, not included in that cache target. scheduler_stack_bytes uses the effective stack size after the 1 MiB floor. The separate memory_job_stack_bytes describes up to 2 MiB of virtual stacks for this example’s two lazy memory workers, not current allocation or RSS. Both calculations reject arithmetic overflow. Measure peak RSS under load before choosing a deployment size.
Run ./zig-out/bin/plugins migrate preview --json to inspect the two compiled consumer migration declarations without booting plugins or opening a database. Their up callbacks have no declared reverse; the inventory does not execute them or determine pending state, SQL, side effects or runtime reversibility.
This is the advanced framework example. It is a standalone package with a path dependency on the repo root (../..) and exercises — using only public zigbase.* exports, no reaching into ZigBase internals — the comptime-config features a consumer configures in code.
What it proves
Custom storage plugin (
AuditStorage) wrappingzigbase.LocalStorage. Implements the plugin contractcreate(gpa, io, cfg) !Self/interface(*Self) zigbase.Storage/deinit(*Self) void, returning azigbase.Storagevtable whose four methods log each operation before delegating to the innerLocalStoragebackend. Registered viaApp(.{ .storage = AuditStorage }).Custom mailer plugin (
AuditMailer). Implements the same plugin contract forzigbase.Mailer/zigbase.Email, logging and counting every outbound email. Registered viaApp(.{ .mailer = AuditMailer }), replacing the built-inDefaultMailerPlugin.Comptime schema via
.collections. Four related collections:authors(auth: webauthn + api_token),commenters(auth: magic_link),posts(relation →authors), andcomments(relation →posts+commenters), each with.targetset by name so ZigBase resolves the relation at provisioning time (create-missing + additive field-add). Access rules are applied per-collection.Explicit migrations via
.migrations. Current SQLite migration batches use a fail-fast, permanentdata.db.migrations.locksidecar: a competing command reportsMigrationBusybefore system/ledger setup and can be retried after the active batch finishes. Pool opening and later collection provisioning remain outside the consumer lease. This protects batch selection across individual commits without a background coordinator; it does not coordinate automatic provisioning or older writers. Twozigbase.Migrationentries:0001_create_audit_logcreates a side table viaw.exec, and0002_index_audit_noteis a multi-statement migration that creates an index on the migration-ownedplugin_audit_logtable and seeds a metadata row (DDL + DML in one transaction) — the escape hatch for non-additive changes the additive auto-provisioner won’t make.onErrorhandler (.onError = handleError), receiving a*zigbase.ErrorEventwhose.phasefield identifies where the error originated (.request/.cron/.job/.file_serve/ …).A DB-touching cron job (
* * * * *, every minute) that reads the DB viactx.records()to count published posts, then writes an audit log row to a migration-owned table with raw SQL on the pooled writer (ctx.app.pool).Pool levers via
.pools(.readers/.jobs/.memory_jobs/.cache_kib) to tune warm readers, scheduled work, lazy memory-job/submit workers, and SQLite cache targets. Choose fewer memory workers for a small deployment or more for parallel background work; unused pools start no threads. This example uses two.Fully embedded static frontend via
embedStaticDir. The Zigapagos frontend build output infrontend/distis compiled into the binary at build time via.static_files = .{ .embedded = &@import("static_assets").files }— there is no runtime dependency on thefrontend/distdirectory.onAuthhook logging collection + method for every successful session mint. With two auth collections and three methods in use, log lines show[onAuth] collection=authors method=webauthn,method=custom(api_token), andcollection=commenters method=magic_link.Custom
AuthMethodplugin (ApiTokenMethod) via.auth_methods, demonstrating the auth-method plugin contract alongside storage + mailer. Enabled onauthorsvia.auth.methods.custom = .{"api_token"}.Comptime
.indexeswith.collation = .nocaseonauthors.contact_email— the correct way to index a comptime-managed collection, since its physical column is named by the human field name, not the field id.Tier-2 SPA fallback routing via
.static_routes(#183): a/app/**catch-all serves the embedded frontend shell for any static miss below/app/. The serve target is validated against the embedded manifest at comptime. Declaring routes flips the Tier-1.spamarker default off — see Framework → Static files.
The fact that this package compiles against the published zigbase module is the proof that the documented plugin / schema / migration / pool features are usable by an external consumer.
Pre-1.0: ZigBase is pre-1.0 — these comptime-config shapes may change between releases.
The ladder
The three examples form a ladder:
| Example | What it proves |
|---|---|
examples/blog | bare packaging proof (ZigBase as a dependency) |
examples/golfsim | a realistic app built on ZigBase (hooks, routes, cron) |
examples/plugins | the comptime-config surface a framework integrator uses |
Frontend (Zigapagos frontend)
frontend/ is a single-page Zigapagos site with targeted Preact islands that browse the authors and published posts collections. The HTML, JS, and CSS are compiled into the executable via embedStaticDir + .static_files = .{ .embedded = ... }. There is no runtime dependency on the frontend/dist directory — delete it after building and the site still serves.
This demonstrates the embedded static-files mode: The Zigapagos frontend is compiled into the binary by embedStaticDir in build.zig. --serve-static is rejected as an unknown flag because the mode is comptime-hardcoded. The other modes are shown by the blog (runtime flag) and golfsim (hardcoded dir) examples.
Building and running
This example needs Zig 0.16, which you can get via mise (mise exec zig@0.16.0 -- zig ...). From examples/plugins/:
cd frontend && ./build.sh && cd ..
zig build # embeds frontend/dist into the binary
./zig-out/bin/plugins help
# --insecure-cookies: local dev is over plain HTTP, and auth cookies are Secure by default.
# A strong JWT secret is auto-generated and persisted under the data dir on first run.
./zig-out/bin/plugins serve --insecure-cookies # provisions authors/posts + runs the migration
# open http://127.0.0.1:8090/ — same-origin frontend, so no --realtime-origins needed