Files
bknd/PLAN.md
T
Cameron Pak 4defc4d1e7 refactor(cli): use errore for typed error handling in run command
Replace untyped catch blocks and scattered process.exit() with
errore tagged errors and error-as-values pattern. Compile-time
safety for error handling paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:30:21 -06:00

3.1 KiB

Proactive Error Handling with errore in run.ts

Ref: https://errore.org/

Goal

Replace scattered process.exit(), untyped catch (e: any), and inline error handling in run.ts with errore's error-as-values pattern. Compile-time exhaustive error handling via matchError in a single top-level action().

Constraints

  • makeAppFromEnv and makeConfigApp are exported — do not change their return types
  • platform.ts (startServer, serveStatic, getConfigPath) — unchanged this PR
  • Keep it simple: errors defined in run.ts, not a separate file

Phase 1: Add errore dependency

  • app/package.json: add "errore" to dependencies
  • Run install

Phase 2: Define tagged errors in run.ts

import errore from "errore"

class ConfigLoadError extends errore.createTaggedError({
  name: "ConfigLoadError",
  message: "Failed to load config: $reason",
}) {}

class NeedsBunError extends errore.createTaggedError({
  name: "NeedsBunError",
  message: "Config requires Bun runtime",
}) {}

class NeedsTypeStrippingError extends errore.createTaggedError({
  name: "NeedsTypeStrippingError",
  message: "Node $version needs --experimental-strip-types for .ts config",
}) {}

class ReexecFailedError extends errore.createTaggedError({
  name: "ReexecFailedError",
  message: "Re-exec failed: $reason",
}) {}

Phase 3: Refactor internal functions to return errors

loadConfigFileConfigLoadError | NeedsBunError | NeedsTypeStrippingError | CliBkndConfig

  • Use errore.tryAsync to wrap import()
  • On catch: detect Bun requirement → NeedsBunError
  • On catch: other failure → ConfigLoadError
  • Before import: check needsTypeStripping()NeedsTypeStrippingError

reexecWithTypeStripping / reexecUnderBunReexecFailedError | never

  • Success: process.exit(0) (unavoidable — must stop parent after child completes)
  • Failure: return ReexecFailedError instead of process.exit(1)
  • Caller decides how to handle failure

makeAppFromEnv internal flow (return type unchanged)

  • Internally use error-as-values from loadConfigFile
  • On NeedsBunError / NeedsTypeStrippingError: call re-exec, handle ReexecFailedError
  • On ConfigLoadError: console.error + process.exit(1)
  • Keep exported signature: Promise<App> (exits on unrecoverable errors)

action() — cleaner top-level

  • makeAppFromEnv handles its own error cases internally (since we can't change its return type)
  • action() stays thin: call makeAppFromEnvstartServer

Phase 4: Verify

  • No behavior change for end users
  • All catch (e: any) replaced with typed errore patterns
  • process.exit(1) consolidated to fewer, explicit locations
  • Re-exec failure paths return typed errors before exiting

Files Changed

File Change
app/package.json add errore dep
app/src/cli/commands/run/run.ts tagged errors, refactored error flow

Commit Message

refactor(cli): use errore for typed error handling in run command

Replace untyped catch blocks and scattered process.exit() with
errore tagged errors and error-as-values pattern. Compile-time
safety for error handling paths.