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.

The plugins example's 'Everything in one binary' page: an Authors panel listing two authors and a Published posts panel listing three posts with their authors resolved via relation expand — all HTML, JS, and CSS served from assets embedded in the executable
The plugins example’s ‘Everything in one binary’ page: an Authors panel listing two authors and a Published posts panel listing three posts with their authors resolved via relation expand — all HTML, JS, and CSS served from assets embedded in the executable

What it proves

  1. Custom storage plugin (AuditStorage) wrapping zigbase.LocalStorage. Implements the plugin contract create(gpa, io, cfg) !Self / interface(*Self) zigbase.Storage / deinit(*Self) void, returning a zigbase.Storage vtable whose four methods log each operation before delegating to the inner LocalStorage backend. Registered via App(.{ .storage = AuditStorage }).

  2. Custom mailer plugin (AuditMailer). Implements the same plugin contract for zigbase.Mailer / zigbase.Email, logging and counting every outbound email. Registered via App(.{ .mailer = AuditMailer }), replacing the built-in DefaultMailerPlugin.

  3. Comptime schema via .collections. Four related collections: authors (auth: webauthn + api_token), commenters (auth: magic_link), posts (relation → authors), and comments (relation → posts + commenters), each with .target set by name so ZigBase resolves the relation at provisioning time (create-missing + additive field-add). Access rules are applied per-collection.

  4. Explicit migrations via .migrations. Current SQLite migration batches use a fail-fast, permanent data.db.migrations.lock sidecar: a competing command reports MigrationBusy before 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. Two zigbase.Migration entries: 0001_create_audit_log creates a side table via w.exec, and 0002_index_audit_note is a multi-statement migration that creates an index on the migration-owned plugin_audit_log table and seeds a metadata row (DDL + DML in one transaction) — the escape hatch for non-additive changes the additive auto-provisioner won’t make.

  5. onError handler (.onError = handleError), receiving a *zigbase.ErrorEvent whose .phase field identifies where the error originated (.request / .cron / .job / .file_serve / …).

  6. A DB-touching cron job (* * * * *, every minute) that reads the DB via ctx.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).

  7. 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.

  8. Fully embedded static frontend via embedStaticDir. The Zigapagos frontend build output in frontend/dist is compiled into the binary at build time via .static_files = .{ .embedded = &@import("static_assets").files } — there is no runtime dependency on the frontend/dist directory.

  9. onAuth hook 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), and collection=commenters method=magic_link.

  10. Custom AuthMethod plugin (ApiTokenMethod) via .auth_methods, demonstrating the auth-method plugin contract alongside storage + mailer. Enabled on authors via .auth.methods.custom = .{"api_token"}.

  11. Comptime .indexes with .collation = .nocase on authors.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.

  12. 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 .spa marker 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:

ExampleWhat it proves
examples/blogbare packaging proof (ZigBase as a dependency)
examples/golfsima realistic app built on ZigBase (hooks, routes, cron)
examples/pluginsthe 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

View source on GitHub →