Skip to content

Releases: mrgnhnt96/zonai

Zonai v0.9.1

Choose a tag to compare

@github-actions github-actions released this 02 Sep 03:33
  • Every refusal now tells a client when to come back. A rate-limited 429
    carries retry-after alongside x-ratelimit-limit, x-ratelimit-remaining
    and x-ratelimit-reset, and its body is now JSON naming the collection and
    operation that hit the limit. If you match on the old plain-text body, key
    on the status code instead.
    Both backpressure 503s carry retry-after
    too. The one refusal that deliberately still does not is the email limiter,
    which does not know its own window and will not invent one.
  • A saturated read answers 503, not 500. Through 0.9.0 a burst past the
    read-concurrency limit escaped uncaught and reached the client as an
    unclaimed 500 with no header and nothing useful in the body. It is now the
    same shaped refusal as the write side. Reaching it takes 256 genuinely
    concurrent reads, so most callers will never have seen it.
  • Writes survive concurrent load instead of collapsing. A write now
    reserves its queue slot before the identity check and the password hash,
    and waits briefly for one instead of being refused the instant the queue is
    full. At 100 concurrent creates a release build went from ~82 successful
    writes a second with 98% refused to ~1100–3200 a second with none refused,
    and p99 latency fell from ~790ms to ~43ms. The behaviour change worth
    knowing:
    a write that used to fail immediately may now wait up to 250ms and
    then succeed. The cliff is moved rather than removed — capacity is 64 in
    flight plus 64 waiting, so a client past ~128 concurrent writes still meets
    backpressure, now with a retry-after to act on.
  • The server stops writing a stack trace for every refusal it authored.
    Shedding load used to cost about six times more than serving a request,
    because each deliberate 503 formatted and printed a full trace — so
    saturation fed itself and a saturated sweep could put 40MB in the serve log.
    Fixed upstream in revali_router 5.1.2, which this release requires. If you
    parse the serve log, note that a released build now logs nothing for a
    refusal: an empty log is no longer evidence that nothing was refused.

Full Changelog: v0.9.0...v0.9.1

Zonai v0.9.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 07:28
  • Reclaim space on any database, not just the log. The Maintenance card's
    "Reclaim log space" is now "Reclaim space", with a picker built from this
    deployment's real files and their real reclaimable bytes. Reclaiming the main
    database asks for its filename typed first, because a VACUUM there takes an
    exclusive lock on application data. ZonaiDb.reclaimSpace and POST /dashboard/maintenance/reclaim-space take the target and the floor; the old
    reclaim-log-space route stays, redirecting onto the new one and behaving
    exactly as it did.
  • zonai compile and zonai build refuse a mismatched Dart SDK. They now
    exit 1 with a message naming both versions — "zonai was built with Dart
    3.13.2; you are on 3.12.0" — instead of producing workers that fail later at
    spawn. Every other command warns once and continues, and
    --no-dart-sdk-check turns the check off. This is the one thing here that can
    stop a command that used to succeed.
  • Workers compiled by a Dart SDK that does not match the one zonai was built
    with are no longer loaded in-process. That combination could kill the running
    host outright with SIGABRT — no exception to catch, every in-flight request
    gone with it. The host now decides before the spawn and falls back to the
    worker process, which serves identically.
  • zonai compile exits non-zero when a worker fails to compile. It used to
    report success on a project full of analyzer errors, and zonai build — which
    guards on that exit code — bundled whatever stale executables were already on
    disk.
  • zonai db migrate and zonai build no longer die inside your project with
    Couldn't resolve the package 'sqlite3'. The vendored DDL driver reached
    package:sqlite3 through an export chain nothing called; zonai_schema keeps
    that package a dev_dependency so a query-only client never has to resolve it.
  • zonai is now built with Dart 3.13.2.

Full Changelog: v0.8.5...v0.9.0

Zonai v0.8.5

Choose a tag to compare

@github-actions github-actions released this 26 Aug 20:21
  • The dashboard's "Most sessions" list is clickable — each user opens the same
    row-detail panel the tables screen opens, instead of printing an id to copy.
  • Long tooltips stay inside the window. They wrap at their authored newlines,
    flip on both axes, and measure their real box rather than a hardcoded 44px.
  • The dashboard scrollbar sits flush against the right edge of the viewport
    instead of 20px in from it.

Full Changelog: v0.8.4...v0.8.5

Zonai v0.8.4

Choose a tag to compare

@github-actions github-actions released this 25 Aug 03:01
  • API tokens. A credential that needs no sign-in: zonai db token create/list/revoke/delete talks to the database file directly, /admin/tokens
    mints one over HTTP, and the dashboard has an API tokens screen (and a
    mint-a-bound-token action on an auth row's panel). Tokens are scoped to
    tables and operations, admin unless told otherwise, stored as a SHA-256, and
    record when they were last used.
  • Forced password reset. An account can be made to owe a new password —
    from zonai db, from the server, and from the dashboard's own door. A
    password sign-in that owes one is refused with a 403 password_reset_required envelope, pinned in the swagger and typed in the
    client as PasswordResetRequiredException.
  • beforeSignUp. AuthExtension can now decline a sign-up instead of only
    being told one happened, and the gate runs before the OTP and magic-link
    email rather than after.
  • Push from the dashboard. Select rows in a table with a device-token
    column, compose one notification, and send it to every selected device.
  • zonai ai update. Refreshes the reference files a project already has —
    which are version-stamped now, so a stale one is visible — without installing
    files for tools it never asked for.
  • Fixes: the reads connection got the busy_timeout everything assumed it had;
    POST /auth/confirm is rate-limited; a disposed mailman worker no longer
    turns a dropped reply into a 10-second hang; a fire-and-forget email send owns
    its failure; a row's password reset goes to that row's own table; two
    conditionally-rendered auth components no longer break their own teardown;
    and the web build recovers from a stale asset graph instead of dying.
  • zonai_schema 0.4.2 on pub.dev, with the changelog owed since 0.4.1.

Full Changelog: v0.8.3...v0.8.4

Zonai v0.8.3

Choose a tag to compare

@github-actions github-actions released this 20 Aug 07:06

Full Changelog: v0.8.2...v0.8.3

Zonai v0.8.2

Choose a tag to compare

@github-actions github-actions released this 20 Aug 03:26

Full Changelog: v0.8.1...v0.8.2

Zonai v0.8.1

Choose a tag to compare

@github-actions github-actions released this 19 Aug 23:16

Highlights

zonai gen client — a typed Dart client, generated from your schema

The headline of this release. zonai gen client reads .zonai/schema.json and emits a client where a query that compiles is a query the server accepts.

  • Typed rows and column tokensPostsRow.title is a String; Posts.title.eq('x') builds the filter. A column that does not exist is a compile error, not a runtime 400.
  • Typed expand, both halvesPosts.expand.authorId.companyId builds the request path with each hop typed by the table it points at, and PostsExpanded.authorId comes back as an AuthorsRow?.
  • The full write surfacecreate, createMany, update, updateMany, delete, deleteMany all have typed mirrors. Update fields take a Patch, so "leave alone" and "set to NULL" are different call sites rather than the same nullable argument.
  • MapField.at() — patch one path inside a JSON map column (settings.theme) instead of rewriting the whole column.
  • Typed live querieslisten.one, listen.list and listen.count take the same column tokens and expand paths as the non-streaming methods.
  • Enum columns get their own type — an extension type over String with named constants, values and isKnown. A member the server adds after you generate still arrives and still round-trips, which a Dart enum could not do.
  • One import — the generated barrel re-exports zonai_client, so Where, OrderByTerm, SortDirection and the rest resolve without a second import.
  • gen client --check fails when the committed output has drifted from the schema, so a generated client can be committed and kept honest in CI.

Email preheaders

  • Email gains a preheader — the line a mail client shows next to the subject. Without one, clients scrape the first visible text, so every message previews as its greeting.
  • Every shipped template carries a hidden preheader block, hidden four ways so it survives Outlook and clients that strip display:none, with the zero-width padding that stops the preview running on into a button label.
  • Each built-in auth email ships a default. The OTP one deliberately leaves the code out — the preheader is what renders on a locked phone.

Dashboard

  • Push queue and sessions panels — queue depth by status, the last drain's real outcome, and the three things _jwt can honestly support: active, expiring soon, distinct users.
  • Test-send panel — appears only when a collection declares a deviceToken column. It reuses the push transport and writes nothing: no job row, no cursor, no token pruning, no onPushRejected. It reports the provider's own words, so Unregistered (app gone) and BadDeviceToken (wrong APNs environment) stop collapsing into one answer.

Fixes

  • A 5xx your app authored is delivered again. revali_router below 5.1.1 replaced every response at or above 500 with a bare Internal Server Error when debug: false — so all six 5xx sites in the exception catcher were dead in a released binary and correct under zonai dev. The floor is now ^5.1.1.
  • push(...) works from a user-triggered hook. It was authorized by the caller's identity, and extension requests carry the requesting user's JWT — so the pattern both docs pages prescribe threw TableAccessDeniedException for every ordinary user. Authorization is now by provenance: only developer-authored Dart in a hook or cron can enqueue. The deviceToken-column guard that bounds what a fan-out can read is unchanged.
  • A table named field no longer breaks the generated barrel. The runtime exports fourteen names now, and any of them can collide with a table name; a clash is refused up front, naming the table and the names.<table>.row override that fixes it.
  • zonai gen client --check no longer reports every file as drifted on Windows. Git for Windows checks out CRLF while the generator emits LF, so a committed client looked stale with no way to fix it. Line endings are now normalised for the comparison; anything else is still drift.
  • The generated expand example is derived from the table's own foreign keys rather than invented.
  • Where.isNull(column) / Where.isNotNull(column) — const factories so those clauses stay constructible without importing Null, which shadows dart:core's.

Bundled SQLite (resqlite)

Three defects fixed in the vendored engine; its suite is back to 172 passed, 0 skipped.

  • db_status walked reader handles that were never opened, segfaulting on a NULL dereference.
  • Readers could not open a database nothing had written to yet, reporting "reader not open" instead of SQLite's own no such table.
  • Rows whose schema reports no column names read their real values instead of appearing empty.

Docs and internals

  • The typed client is documented on the site, indexed in llms.txt, and taught to the assistant rules the CLI installs into your project. Ten of its snippets are now compiled against a real generated client, and five more against a second fixture client built to match the page's prose.
  • Push docs say who can send and the amplification that comes with it, and stop claiming everything routes through FCM when direct APNs shipped.
  • Four missing pages added to the site; push notifications added to the feature grid.
  • The stress harness gates the error rate and deliberately refuses to gate p99, with the calibration data that says why — three runs put p99 at a 139% spread at the median cell and the error rate at 0.00 points.
  • CI parses workflows with Ruby when PyYAML is absent, instead of exiting 1 and silently checking nothing.
  • Golden-file tests for the generated client, covering the manifest and orphaned files.

Upgrading

  • zonai gen client output needs zonai_schema 0.4.1+ and zonai_client 0.2.2+, both published alongside this release. The generated runtime calls Where.isNull, and its single import relies on the zonai_client barrel re-exporting the query vocabulary; neither exists in earlier published versions, and generated code will not compile against them. zonai init now scaffolds zonai_schema: ^0.4.1; existing projects should bump both.
  • Re-run zonai compile after upgrading. The .protocol stamp records the IPC framing version, not the message vocabulary, so it will not catch a stale worker.
  • Nothing in this release is breaking for code that does not use the generated client.

Zonai v0.8.0

Choose a tag to compare

@github-actions github-actions released this 17 Aug 05:43

Highlights

Features

  • Sign in with OAuth — Google, Apple, GitHub, Microsoft, Facebook, Discord, GitLab, LinkedIn, plus OAuthProvider.custom(...). PKCE, token exchange, id_token verification, Apple ES256. Add with OAuth to your auth table and list your providers.
  • OAuth in the Dart clientAuth.providers(), startUrl(), complete(), signInWithIdToken(), covering the redirect and native flows.
  • Admin invites — invite by email, check a link is live without spending it, revoke, list members, remove one (which revokes their sessions). Two dashboard screens and an invite email template.
  • Push notificationspush(...) from any rule, operation or cron, fanned out through a checkpointed job table, so a large send survives a restart and never blocks the request that started it.
  • APNs without Firebase — a declared platform column routes each device to Apple or Google. Verified against real APNs and FCM on physical devices.
  • Maintenance screen — per-table storage metrics, plus purge logs, purge a table, clean up unreferenced photos, reclaim log space.

Security

Seventeen fixes, landed as one sweep:

  • Admin role is server-derived on every request — demotion is immediate, and a tampered JWT admin claim is ignored.
  • Weak and reused secrets are refused at startup, including jwtSecret equal to passwordSecret, or either appearing in its own previous*Secrets.
  • Admin auth throttled to 10 requests / 15 minutes, per IP (was the generic 100/min).
  • where can no longer filter on unknown or secret columns — passwords included, on update and delete too.
  • POST /email requires an admin token. It answered anybody, which made the server an open mail relay.
  • Exposure closed on CORS, Swagger, server binding and error oracles; plus the photo path guard, the AsAdmin sign-up default, and the dev backdoors.
  • Default JWT lifetime is 24 hours, down from 14 days.

Fixes

  • A cancelled OAuth sign-in returns to the app instead of 400ing at it.
  • Apple is asked for form_post, with the field pinned across the worker boundary.
  • A checkout with no OAuth credentials boots again.
  • One malformed push message could prune every recipient in a batch.
  • A missing APNs key failed the whole job rather than the leg that needed it.
  • A large fan-out stopped early, and the push config bounds were not enforced.
  • Migration 0007 regenerated, so the internal snapshot chain is unbroken.
  • The storage payload reports the database path absolutely, as it promises.
  • Maintenance screen styles register for SSR; Swagger covers the storage, cleanup and admin-OAuth routes.

Upgrading

  • Needs zonai_schema 0.4.0+ and zonai_client 0.2.1+. The floor moved because this CLI sends AuthType.oauth and the oauthStart / oauthCallback / adminInvite rate-limit operations, and an older schema throws on a name it does not have.
  • AuthType and RateLimitOperation gained values — an exhaustive switch over either stops compiling until you handle them.
  • Re-run zonai compile. Your .zonai/executables/*.exe keep the old message vocabulary until rebuilt, and the .protocol stamp will not catch it.
  • POST /email now needs an admin token — anything calling it unauthenticated will start getting 403.

Full Changelog: v0.7.2...v0.8.0

Zonai v0.7.2

Choose a tag to compare

@github-actions github-actions released this 15 Aug 09:21

What's Changed

  • ci: the static job never generated the code it analyzes, and e2e never built a binary by @mrgnhnt96 in #30
  • chore(deps): bump raindrop to the 2026-08-15 upstream sync by @mrgnhnt96 in #31

Full Changelog: v0.7.1...v0.7.2

Zonai v0.7.1

Choose a tag to compare

@github-actions github-actions released this 14 Aug 03:03

Full Changelog: v0.7.0...v0.7.1

Fixes the regression in v0.7.0

v0.7.0 returned 500 on every PATCH /db. If you are on 0.7.0, upgrade.

In / NotIn where-clauses could not cross the isolate worker boundary.
serializeWhereValues ended in .cast<Object>(), which returns a CastList
rather than a plain List — and an isolate message may only carry primitives
plus plain List/Map instances, so it was rejected with ArgumentError
before the request was ever dispatched. Released binaries always use the
isolate transport, so this only ever failed there.

The clause had been broken since the isolate transport landed, but nothing put
one on a hot path until v0.7.0's update read-back started keying on it — which
put an In on the update path of every request.

Also upgrade zonai_schema to 0.3.1

The CLI carries its own copy, so this release fixes PATCH /db on its own.
Your workers compile against the zonai_schema your project resolves, and
that copy still has the bug — so a rule, operation or cron calling get.* with
an In/NotIn filter keeps failing until you upgrade:

dependencies:
  zonai_schema: ^0.3.1

Then re-run zonai compile. Your .zonai/executables/*.exe keep the old
code until rebuilt, and the .protocol stamp will not catch it — that records
the IPC framing version, not what the messages contain.

Verification

Verify Release run 31765112689
was green on all 17 jobs — no exception claimed, unlike v0.7.0. The two
gates excepted for that release both cleared: compat-* self-cleared once
v0.7.0 became the baseline, and cross-run-linux-x64 went green after its
fixture pin was moved off 0.6.1, so its positive control now actually fires.

The released binary was then checked by hand against a clean-slate project on
the isolate transport: where on id, on a non-id column, on a column the
same update rewrites, and a where matching nothing (404, not 500) — all
correct, with the writes verified in the database afterwards.