Consolidate domain error types behind a shared base class
- Dominant language
- TypeScript
- Stars
- 481
- Forks
- 108
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 5
Description
## Goal
Domain errors each define their own shape and each need a hand-written branch in the display paths. `QueryValueError` (`connector/queryValueError.ts`) is the only one enforcing a contract: an abstract `details: object`, plus a name passed as a string literal because the production build minifies class names. `StylingParseError`, `FileEnvelopeError`, `NetworkError`, `ServerConnectionError`, and `ConnectionLinkError` each roll their own.
The cost is concentrated in `utils/createDisplayError.ts`: a ~190-line `if`-chain that must be edited whenever a new error type appears, and which silently degrades to "Something went wrong" when someone forgets. `utils/createErrorDetails.ts` has a smaller version of the same problem. The number of domain error types is expected to keep growing, so the per-type branch is a recurring tax rather than a one-off.
Decide how to consolidate, and how far.
## Directions to explore
Not an exhaustive list — the point of the spike is to find the best mechanism, and type-level approaches (generics, discriminated unions, mapped or template-literal types, exhaustiveness checking) may beat a class hierarchy outright.
- **A shared base class.** Extract a base from `QueryValueError` (name literal + abstract `details`) and have the domain errors extend it, so a single branch in `createErrorDetails` serves every error and each new type gets the details dialog for free.
- **Errors owning their display text.** Put the user-facing title and message on the error itself, inverting `createDisplayError` from a growing `if`-chain into "ask the error, otherwise map platform and third-party errors". Turns a missing case into a compile error instead of a generic message.
- **Type-level enforcement without inheritance.** A registry, a discriminated union over a `kind` field, or a generic wrapper could enforce the details shape and exhaustive handling without asking every error to extend a common ancestor — worth comparing on ergonomics and on how well it survives minification.
Whatever the mechanism, it has to keep enforcing the name-as-literal rule. Minified class names would otherwise surface as `Error` in the details dialog.
## Constraint: import cycles
This shapes where any shared type can live. The display helpers are in `utils/` and the error types in `core/`, and `core` modules commonly import the `@/utils` barrel. Defining `ConnectionLinkError` in `core/urlConnectionParams.ts` created `utils/createDisplayError` → `core/urlConnectionParams` → `@/utils`, which broke 10 unrelated test files with `Cannot access '__vite_ssr_import_2__' before initialization`. It was fixed by moving the class to `core/connectionLinkError.ts`, a leaf module importing nothing from the app.
So: every error type the display paths recognize must be reachable without pulling in app modules, and the same goes for a shared base. Deciding where that base belongs — `utils/`, `core/`, or `@shared` — is part of this spike, not an afterthought.
## Expected Outcome
A recommended mechanism with a rough size estimate, a decision on where the shared type lives, and a tasked-out plan. An ADR if the outcome changes how errors are modelled.
## Related Issues
- Originated from #1828
> [!IMPORTANT]
> Internal only — this issue is maintained by the core team and is not accepting external contributions.
Contributor guide
Research direction
Start by reading utils/createDisplayError.ts and utils/createErrorDetails.ts, then inspect the listed error classes, especially connector/queryValueError.ts and core/connectionLinkError.ts. Compare the proposed inheritance, error-owned display text, and type-level approaches while tracing the import-cycle constraint. Done means a recommended mechanism, shared-type location, rough size estimate, tasked-out plan, and an ADR if error modelling changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100