Repository navigation
fix(env): serve async env.ts values to Void app requests - #792
Open
morinokami wants to merge 1 commit into
Open
morinokami wants to merge 1 commit into
morinokami wants to merge 1 commit into
Conversation
A Void app could not read an env.ts key whose validator is async: every request that read it failed with "Async validator ... cannot be resolved synchronously", in dev and in production builds. The generated entry validates the env before it opens the request scope. _validateEnvOnce cached the resolved values under the env that reads use outside a scope, while reads in the scope look them up under the env the handler received. _validateEnvOnce now seeds the cache of the env it receives. A completed validation is reused for the same raw values, so a schema is still validated once per isolate, also when a dev server passes a new env object for each request. An env with other values is validated again rather than given values validated for another, and keeps its own values: Cloudflare may keep an isolate running when only its bindings change, so requests that hold the old and the new values can overlap. A read through an env that was never validated itself, such as the remote dev copy or the cloudflare:workers env outside a request scope, takes the value from the latest validation while that env holds the raw value the value came from. Only a completed validation is reused. In workerd, a promise that waits on the I/O of a canceled request never settles, so each call runs its own validation until one completes. A call that finds another schema registered after it waited validates again with that schema. The per-env caches no longer stay referenced after their env is gone. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When
env.tsdeclares a key with an async validator, such as a Zod schema with an async refinement, a Void app can't read it:The route answers 500 on every request, in
vp devand in a production build. A cron job that reads the key fails the same way, and so does a Valibot schema built withpipeAsync.The generated entry awaits
_validateEnvOnce(env), which resolves async validators ahead of the synchronousenv.Xreads, and then opens the request scope._validateEnvOncecached the resolved values under the env that reads would use at that moment. Outside a scope, that is the env fromcloudflare:workers. Reads in the scope look values up under the env the handler received, which is another object in workerd. The cron, queue and email handlers validate in the same order.Fix
_validateEnvOncenow seeds the cache of the env object it receives. The request scope opens with that object, or in remote dev mode with a copy of it.vp dev, which passes a new env object for each request.cloudflare:workersenv outside a request scope.defineEnvreplaces the per-env caches. It used to clear them through a list that kept every cache alive, one per env object.Two behaviors change besides the fix:
cloudflare:workersenv used to receive the values of the first validation, whatever it held. It is now served a value only for the raw value it holds.A handler that runs in a Durable Object (a WebSocket route, a Durable State definition) doesn't validate the env. It can now read an async key in an isolate where a request or a cron, queue or email event has validated, but not in one that runs only the Durable Object. Validating from those handlers is left for a follow-up.
Tests
packages/void/test/unit/env-schema.test.ts(12 cases) calls_validateEnvOnceand readsenv:packages/void/test/unit/routing-runtime.test.ts(3 cases) sends requests throughcreateWorkerFetchto a Hono route that reads an async key: with a new env object for each request, with the remote dev copy, and with two overlapping requests that hold different values.12 of the 15 cases fail without this fix. The other three check behavior that
mainalready has: a read outside a request scope with the same values, a validator that throws running again, and an unfinished validation not blocking the next call.Verification
vp test run: 792 files pass, 1 skipped (16,644 tests pass, 30 skipped).vp run check: 33/33 pass.playground/ssrwith theenv.tsand route above,API_TOKEN=sk_livein.env, and a cron job that logs the value:vp devandvp preview: the route answers{"token":"sk_live"}on every request, and invp devthe cron job logs the value. Without this fix, the route answers 500 in both, and the cron job fails.scheduledhandler read the value, also when the cron event is the isolate's first event. WithoutAPI_TOKEN, or with a value the refinement rejects, requests answer 500 with the boot validation error, and the cron event fails.pipeAsyncandcheckAsyncin place of the Zod schema: the route reads the value invp devandvp preview, and answers 500 in both without this fix.database: 'pg'and no database connection, since the route doesn't query it: the route reads the value invp devandvp preview.onRequestreads the key: it reads the value invp devandvp preview. Without this fix, it fails in both.void deployagainst Cloudflare, remote dev mode, and a binding change that reuses an isolate. The last two are covered by the unit tests above.🤖 Generated with Claude Code