Skip to content

fix(appkit): don't crash typegen when the warehouse is unreachable#406

Draft
atilafassina wants to merge 2 commits into
mainfrom
typegen-uncaught
Draft

fix(appkit): don't crash typegen when the warehouse is unreachable#406
atilafassina wants to merge 2 commits into
mainfrom
typegen-uncaught

Conversation

@atilafassina
Copy link
Copy Markdown
Contributor

@atilafassina atilafassina commented Jun 2, 2026

Problem

Type generation crashed with an uncaught error whenever the SQL warehouse was unavailable. When the warehouse is down, every DESCRIBE QUERY fails; those per-query failures are caught (Promise.allSettledresult: unknown), but generateFromEntryPoint then unconditionally threw an aggregate "Type generation failed" error. That throw escaped uncaught at both call sites — the Vite plugin (un-awaited generate()) and the CLI (sync cmd.parse()) — taking down the dev server / build / CLI (on Node 20 an unhandled rejection is fatal).

The aggregate throw was introduced in f2d44ae7 (#254) to fail builds on genuinely-broken SQL, but it could not tell "one query has a syntax error against a reachable warehouse" from "the whole warehouse is down, so every query failed."

Fix (PR 1 of 2 — user-visible behavior)

Distinguish the two failure classes; only a genuine SQL error is treated as fatal.

Failure dev build prod build
Warehouse unreachable (connectivity) reuse last-known-good cached type, else unknown; warn; exit 0 same — exit 0
SQL error (reachable warehouse, DESCRIBE … FAILED) unknown; warn; exit 0 throws → fails build

Changes

  • query-registry.ts — classify each describe outcome: ok (cached), syntax (state === "FAILED" → collected, not cached), connectivity (executeStatement rejects → reuse last-known-good cached type or unknown; never cached, never fatal), empty (described, no columns → soft unknown, not cached). The console table reports the classes distinctly.
  • index.ts — replaced the unconditional throw with a typed TypegenSyntaxError thrown only for syntax errors, and after the .d.ts is always written. Connectivity failures degrade silently.
  • types.ts — added QuerySyntaxError + QueryGenerationResult ({ schemas, syntaxErrors }).
  • Stop caching result: unknown — only successful describes with a result schema are persisted, so a transient outage never poisons the cache and a fixed query recovers on the next run.

Verification

  • pnpm -r typecheck — clean across all 7 projects
  • appkit test suite — 2037 / 2037 pass (incl. 177 type-generator)
  • Biome clean
  • Regression tests: rejection → degrade-not-crash; syntax → prod-throws / dev-warns; an analytics vite-plugin.test.ts mirroring the serving one.

Deferred to PR 2 (hardening + tests)

  • await the Vite buildStart / watcher and use parseAsync().catch() in the CLI, so a prod syntax error fails the build cleanly rather than via an unhandled rejection. (The warehouse-down crash is fully fixed in this PR — dev and prod; this is the remaining cosmetic path for genuine SQL errors.)

This pull request and its description were written by Isaac.

Type generation threw an uncaught error whenever the SQL warehouse was
down. Every DESCRIBE QUERY failed, all queries degraded to an unknown
result, and generateFromEntryPoint unconditionally threw an aggregate
"Type generation failed" error that escaped uncaught at the Vite plugin
(un-awaited generate()) and the CLI (sync cmd.parse()) call sites.

Distinguish connectivity failures from genuine SQL errors:

- Connectivity (executeStatement rejects): degrade silently. Reuse the
  last-known-good cached type if present, otherwise emit an unknown
  result. Never fatal, so a transient outage no longer fails a build.
- SQL error (reachable warehouse, DESCRIBE FAILED): surface via a typed
  TypegenSyntaxError so the existing prod-throws / dev-warns gate
  applies. Eligible to fail prod builds only.

Also stop caching unknown results: only successful describes with a
result schema are persisted, so a transient outage never poisons the
cache and a fixed query recovers on the next run.

PR1 of 2 (user-visible behavior). PR2 will await the Vite
buildStart/watcher, use parseAsync().catch() in the CLI, and add
degrade/throw regression tests.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Add the regression coverage the warehouse-down crash slipped through: the
prior suite tested rejection->unknown as graceful but never connected it to
the aggregate throw in generateFromEntryPoint.

query-registry (generate-queries.test.ts):
- connectivity reuses the last-known-good cached type
- empty result (described, no columns) -> unknown, not syntax, not cached
- syntax (FAILED) -> recorded in syntaxErrors, not cached
- cache HIT serves the stored type without a warehouse call
- legacy retry-flagged entry is re-described, not reused
- mixed run records only the syntax failure; failures are not persisted

generateFromEntryPoint (index.test.ts):
- syntax errors throw TypegenSyntaxError
- connectivity-only failures do NOT throw (the warehouse-down regression)
- the .d.ts is written before the throw

Layers 1+2 of the test plan; Layer 3 (analytics vite-plugin) and Layer 4
(CLI exit codes) land in PR2 with their await/parseAsync refactors.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant