Skip to content

Optional QuickJS / non-Node host entrypoint for console + URL (+ Error.captureStackTrace) #253

Closed

Description

@svr93

Summary

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:

  1. shipped an official envalid/quickjs (or similar) entry that applies safe host patches, or
  2. 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

  1. 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.
  2. url() needs URL. url() uses new URL(x) (then returns the original string). Host QuickJS may have no URL, or only a partial one.
  3. 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 B: Keep core unchanged; export something like:

import { ensureHostForEnvalid } from 'envalid/host'
ensureHostForEnvalid({ console, URL, captureStackTrace: 'noop' })

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.

Activity

  1. svr93 commented on Aug 21, 2026

    @svr93
    ContributorAuthor

    P.S.

    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.

  2. af commented on Sep 2, 2026

    @af
    Owner

    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

  3. svr93 commented on Sep 13, 2026

    @svr93
    ContributorAuthor

    @af Thanks for the guidance — went with Option C.

    This PR:

    • documents URL / console against WinterTC’s Minimum Common Web API, with a QuickJS shim under example/ linked from the README
    • 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!

  4. added a commit that references this issue on Sep 20, 2026
  5. af commented on Sep 20, 2026

    @af
    Owner

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions