You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We use envalid under QuickJS (qjs) in a Node-free container image to validate env once at docker entrypoint (before the process becomes ready). The browser consumes a generated runtime-env module and does not run envalid. That setup works well, but only after a small must-run-before-import host shim.
It would help if envalid either:
shipped an official envalid/quickjs (or similar) entry that applies safe host patches, or
documented host prerequisites and offered optional adapters so consumers don’t reverse-engineer load-time assumptions.
Environment
envalid 8.x
QuickJS (qjs), no Node in the final image
Bundled ESM validator (esbuild) run from the container entrypoint
Shared env schema / types with the browser app; validation only in entrypoint
Problems we hit
console at import time. Default reporter does something like console.error.bind(console) when the module loads. QuickJS hosts often only expose console.log (or incomplete console). Import crashes before any cleanEnv / custom reporter runs.
url() needs URL. url() uses new URL(x) (then returns the original string). Host QuickJS may have no URL, or only a partial one.
Error.captureStackTrace. EnvError / EnvMissingError call V8’s Error.captureStackTrace. QuickJS doesn’t have it → throw during error construction unless stubbed.
Because (1) happens at import, shims must load before import 'envalid'. That forces awkward import-order rules in consumer code.
What we shim today (minimal)
Ensure console.error / warn / info / log exist and are bindable
Stub Error.captureStackTrace as no-op
Provide a minimal URL for absolute scheme://… (enough for url(), not full WHATWG)
Proposal
Option A (preferred): import 'envalid/quickjs' (side-effect or explicit installQuickJsHost()), then normal envalid API.
Option C (docs-only): Document that non-Node hosts must provide bindable console.*, URL for url(), and optionally Error.captureStackTrace before importing envalid.
Also useful: delay console.error.bind(console) until first report / first cleanEnv, so a custom reporter can be set without a complete console.
Why this fits envalid
README already targets “Node, Bun, and other compatible JS runtimes.” Tiny QuickJS runtimes are a real pattern for validate env before the process becomes ready, without shipping Node. Official host support (or clear docs) beats every consumer reinventing the same shim.
Non-goals
Full WHATWG URL inside envalid
QuickJS stdlib (std / os) integration
Replacing custom reporters
Happy to PR a tiny envalid/quickjs install helper + a short “non-Node hosts” doc section if that direction works for you.
Why validate at entrypoint (QuickJS) instead of in the browser
We deliberately run envalid once in the container entrypoint, not in the browser:
Bad/missing env fails the new pod before Ready, so Kubernetes can keep serving the previous Ready pod (rolling update / rollback). So the QuickJS host path isn’t “run envalid in a weird engine for fun” — it’s what lets us reuse the same schema in a Node-free image and fail closed at deploy time.
This is a cool use case that I'd never considered, thanks for sharing it!
I think option C is preferable, maybe on this ticket we can document a workable shim and link to it from the readme. Delaying the console.error.bind should be doable as well, just wrapping it in a thunk might do the trick there
lazily calls console.error in the default logger (no load-time .bind)
guards Error.captureStackTrace so some hosts don’t need a stub
After the lazy logger, the shim only needs to run before cleanEnv / url(), not before import 'envalid'. Happy to tweak naming or trim anything that feels heavy!
Activity
svr93 commentedon Aug 21, 2026
P.S.
Why validate at entrypoint (QuickJS) instead of in the browser
We deliberately run
envalidonce in the container entrypoint, not in the browser:Bad/missing env fails the new pod before Ready, so Kubernetes can keep serving the previous Ready pod (rolling update / rollback). So the QuickJS host path isn’t “run envalid in a weird engine for fun” — it’s what lets us reuse the same schema in a Node-free image and fail closed at deploy time.
af commentedon Sep 2, 2026
This is a cool use case that I'd never considered, thanks for sharing it!
I think option C is preferable, maybe on this ticket we can document a workable shim and link to it from the readme. Delaying the console.error.bind should be doable as well, just wrapping it in a thunk might do the trick there
svr93 commentedon Sep 13, 2026
@af Thanks for the guidance — went with Option C.
This PR:
console.errorin the default logger (no load-time .bind)Error.captureStackTraceso some hosts don’t need a stubAfter the lazy logger, the shim only needs to run before cleanEnv / url(), not before
import 'envalid'. Happy to tweak naming or trim anything that feels heavy!docs(#253): WinterTC globals + QuickJS shim; lazy console.error (#255)
af commentedon Sep 20, 2026
Thanks, merged your PR so I think this can now be closed 👍 Hoping to finally cut v9 (or at least a beta) in the next week or so with this change