Green Tea

Green Tea 26.9.0-beta.1: three crashes closed, and a registry that now works

Green Tea 26.9.0-beta.1 was published on 4 September 2026, to npm and JSR.

The previous release gave the framework a way to report what it was doing. This one is largely the result of reading that report: three ways an application’s own code could end its process, a second registry that had been announced but never worked, and two performance changes that only appear once an application grows.

Three failures that ended the process

Each is the same shape — the framework called code the application supplied, and did not account for it throwing.

A cors.origins predicate runs on every request. An exception from it escaped into the request pipeline and took the process with it. A custom onError had the same flaw with worse timing: the handler written to stop a bad request becoming an outage was itself the outage. It now falls back to the built-in renderer and reports the failure rather than losing the response.

The third was quieter. Reaching Node’s maxConnections destroyed the socket with no HTTP response and logged nothing, which from the client’s side is indistinguishable from a network fault. It now logs a warning naming the dropped peer, rate-limited to one a minute.

Anyone running 26.8.0-beta.1 with a CORS predicate or a custom error handler should treat this as the reason to upgrade.

The JSR package works

26.8.0-beta.1 announced publication to JSR. It installed and then failed on first import: JSR serves src/ rather than the bundled build, and the ESM build’s extensionless relative imports are not something Deno resolves. The npm package was unaffected throughout, which is why it went unnoticed — every test and every runtime suite ran against the built artifact.

The import paths are fixed. More usefully, the release workflow now runs deno publish --dry-run before the npm publish, so JSR’s own checks — slow types, module analysis, things npm never inspects — fail while there is still something to be done about them. npm allows 72 hours to unpublish; a JSR version can never be replaced.

Boot takes the longest chain, not the sum

Providers that do not depend on each other now boot concurrently. Three independent providers doing 200 ms of work each took 616 ms and now take 210 ms. The saving is proportional to how wide the dependency graph is: a small application will see little, an application with a dozen independent providers will see most of its startup disappear.

Route ranking is settled when the route table is built rather than recomputed per request, and security and CORS headers are computed once per request instead of twice. Neither is visible on a small route table — on two hundred routes the matcher change measures between 12 and 15 per cent.

The benchmark that can see it shipped in this release too. Earlier matcher work had measured as noise against a six-route table, which was a limit of the instrument rather than of the change.

A request budget

createApp({ limits: { maxConcurrentRequests } }) bounds how many handlers execute at once, per server and per Fetch adapter instance. Beyond the budget a request receives 503 with Retry-After: 1 rather than queueing behind the work already running.

It counts executing handlers rather than open connections, so a long-lived SSE stream or a WebSocket upgrade does not hold a slot for its lifetime, and on Node a client disconnect releases one early.

Smaller additions

createApp({ handleSignals: true }) registers SIGINT and SIGTERM to close and exit; it is off by default, because a library that installs process-wide handlers unasked is a library that fights the process manager. @Sse can emit an id:, giving an EventSource reconnect a Last-Event-ID to resume from. The extension-point types — TransformerFn, PluginApi, ScopeApi, ScopeNode, Hooks and TeardownFn — are exported, so a custom transformer or plugin no longer redeclares them inline. Every request:end is now preceded by a request:start carrying the same request id, which is what makes the event stream something to build a collector on.

Where to get it

npm install @green-tea/core@beta reflect-metadata

The package is published to npm with build provenance, and to JSR, which serves the TypeScript source directly. While there is no stable release, latest and beta resolve to the same version — asking for @beta explicitly means staying on the intended channel the day that stops being true.

Only the newest beta receives security fixes. Anything exploitable goes to security@expressive-tea.io, not to a public issue.

One person contributed code

@hgshreyas contributed the request budget, across two issues and two pull requests, having also contributed the connection cap in the previous release. It is the second consecutive release to carry work from outside the organization, on a project that offers a contributor no visibility in return.

The complete notes

Every change, with the reasoning, is on the 26.9.0-beta.1 release. The what’s new page covers the same ground for readers already running the previous version.

The engineering story behind this release — first person, from the person who wrote it — is on the Green Tea blog.

← All posts