Skip to content

fix(auth): reload the auth config and its local imports in dev - #554

Open
Brooooooklyn wants to merge 4 commits into
mainfrom
fix/auth-config-reload
Open

Brooooooklyn wants to merge 4 commits into
mainfrom
fix/auth-config-reload

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Fixes #550

Problem

loadVoidAuthConfig() used a native import(). Node keeps the first evaluation of a file, and of every file it imports, until the process exits. So while the dev server ran, the generated auth schema kept using the first auth.ts it saw.

save auth.ts ──► watcher ──► import(auth.ts) ──► cached copy ✗
save helper  ──► (not watched)                                ✗
fix syntax   ──► import(auth.ts) ──► old copy                 ✗

Fix

In dev, Void declares a small Node-only Vite environment, void_auth_config (only for serve; no HMR client), and loads auth.ts through its module runner:

                         void_auth_config (Vite env, dev only)
save a file ──► in its module graph? ──yes──► mark stale ──► regenerate
                                                                     │
               next load: stale? ──yes──► moduleGraph.invalidateAll()  │
                              │                                       ▼
                              └──────────────► runner.import(auth.ts) ──► .void/better-auth-schema.ts

One flag (authConfigStale) drives it. It's set by a save of a file in the graph, or by a failed load.

  • Dependencies come from Vite's module graph. Void keeps no list of its own.
  • Packages stay external, so they load once.
  • Builds and CLI commands (void prepare, db generate, void build) keep the plain import() from main. They run once per process, so caching doesn't matter there.
  • Start-up auth migrations load through the same runner. A restarted dev server (r, or Void's own restart) creates the new tables and columns.

Edge cases

Case Behavior
auth.ts edit / local helper edit schema regenerates ✓
syntax error in a helper, then fix error logged, then regenerates ✓
import of a file that doesn't exist yet, then created the new file triggers a clean load ✓
file saved while a load is running loads run in order, so the next load picks it up ✓
other saves while the config is broken error logged; the regeneration waits for the fix, as main does when the auth config fails to load
route or unrelated save config not reloaded ✓

Vite's own update pass skips this environment (hotUpdate returns []), so saves don't log a page reload that nothing receives.

Same pattern as Vite and React Router

                      own env, consumer 'server'   runner.import   invalidateAll on change
Vite runnerImport()   ✓ (inline)                   ✓               n/a (one-shot)
React Router routes   ✓ (__config_loader)          ✓               ✓
this PR               ✓ (void_auth_config)         ✓               ✓

All APIs used are public exports of vite 8 (Environment API, "release candidate": stable within a major). A plain server.restart() would not fix #550: Vite restarts in the same process, so Node's import() cache would survive it.

History

The first push used a process-wide module.registerHooks resolve hook. That needed its own dependency list, content hashes and CommonJS special cases. After review it's replaced by this Vite-based version, which is smaller and only exists in dev.

Restart still needed for

New auth tables and columns in the local database. They're created when the dev server starts or restarts, as before. The docs line says so.

Checks

  • plugin-routing.test.ts drives a real Vite dev server (no watcher, events sent by hand, plus a slow watchChange plugin so the test can't depend on Vite's handler order). It covers:

    • edits to the config and to a helper;
    • a syntax error, then the fix;
    • a missing file created later;
    • a route save that doesn't reload the config.

    Other tests cover hotUpdate, and that the environment is declared only for serve (not preview or build).

  • plugin-migration.test.ts: start-up migrations get the dev server's importer.

  • auth-config-loader.test.ts: the exports, the importer, and devAuthConfigImporter on a real server.

  • Each guard was removed once (mutation check) and a test failed each time: stale on save, stale on failure, invalidateAll, the created-file reload, and the runner import.

  • On a built CLI in a scratch app, vp dev:

    • helper edit, config edit, syntax error then fix, and a missing file created later all updated the schema;
    • routes still served;
    • an in-process restart added the new column to the local D1 table;
    • no extra log lines.
  • void package typecheck, lint and fmt pass. Affected tests (vp run test --base origin/main, 736 files) pass.

The second commit gives the platform status browser test the same 30s timeout as its sibling browser tests. It timed out once under a full parallel run.

Not changed here (older behavior)

  • If auth.ts itself has a syntax error, auth config discovery (findVoidAuthConfig) can't parse it and treats it as "no config", without saying so.
  • If auth.ts imports a file that doesn't exist yet, the Worker (void_worker) keeps returning 500 even after the file is created; main does the same. The generated schema does recover.

🤖 Generated with Claude Code

Brooooooklyn and others added 2 commits October 23, 2026 00:16
A native import() keeps the first evaluation of auth.ts, and of every local
file it imports, for the life of the process, so the dev server kept
regenerating auth files from the config it saw first.

The loader still uses Node's own import, so module rules are unchanged
(type stripping, top-level await, --conditions; no tsconfig read, NODE_ENV
untouched). A resolve hook gives each load a fresh query on the entry and on
every local ES module it imports. Packages load once. A load whose files are
all unchanged reuses the previous result.

Node keeps CommonJS files and anything reached through require() cached by
path. They are reported, and the dev server warns that saving one needs a
restart. The watcher also reloads on saves to the config's local imports,
retries after a failed load, and catches a file saved while a load runs.

Fixes #550

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…Chromium

Under a full parallel run, launching Chromium can pass the default timeout.
The sibling browser tests already allow 30 seconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-23T06:18:44.633618Z 5fbd01a New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-auth-config-reload-void-cloud.voidzero-docs.workers.dev (commit 5fbd01a)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://36c665c8-void-cloud.voidzero-docs.workers.dev 5fbd01a 2026-10-23T06:19:45.517Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://ce893198-void-cloud.voidzero-docs.workers.dev 834ecce 2026-10-23T04:23:33.261Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://379475a3-void-cloud.voidzero-docs.workers.dev a806b25 2026-10-22T16:20:15.037Z Visit the dashboard ↗

@cpojer

cpojer commented Oct 23, 2026

Copy link
Copy Markdown
Member

This looks pretty sketchy tbh. Could this not be a Vite plugin or similar?

Replace the process-wide Node resolve hook with a dev-only Vite
environment, `void_auth_config`: a Node module runner with HMR off. The
dev server loads auth.ts through it; Vite's module graph lists the files
the config imports, so a save of any of them invalidates those modules
and the config is read again. Builds and CLI commands keep the plain
import() from before.

- Start-up auth migrations load through the same runner, so a restarted
  dev server also creates new auth tables and columns.
- After a failed load, or a save during a load, the next script save
  retries from a clean environment. The retry runs apart from the save,
  so a config that still fails does not stop page reloads.
- Vite's update pass skips the environment, so saves log no page reload
  that nothing receives.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@cpojer Fair point, that was a lot of machinery. Replaced in 834ecce: no Node hooks now.

In dev, Void declares a small Node-only Vite environment (void_auth_config, serve only, no HMR client) and loads auth.ts through its module runner. Vite's module graph says which files the config imports, so a save invalidates those modules and the config is read again:

save auth.ts / helper ─► file in void_auth_config graph? ─► invalidate ─► runner.import(auth.ts) ─► regenerate auth schema

Builds and CLI commands (void prepare, db generate) keep the plain import() from main. Start-up auth migrations use the same runner, so restarting the dev server also creates new auth columns. The PR description has the details.

@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@cpojer

cpojer commented Oct 23, 2026

Copy link
Copy Markdown
Member

This is a lot less sketchy.

Replace the retry state (retry and loading flags, a script extension list,
a second load path and the gating around it) with one flag. A save of a
file in the auth environment's module graph, or a failed load, marks the
config stale; the next load invalidates the whole graph first. After a
failed load, a created file triggers that load, as the missing import may
now exist.

The runner checks each import against the server graph, so clearing the
runner cache is not needed. Loads run in order, so a save during a load
is picked up by the load queued after it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auth config regeneration in dev can reuse a cached config

2 participants