Skip to content

docs: rewrite README around defineErrors and add philosophy articles#108

Merged
braden-w merged 6 commits intomainfrom
braden-w/define-errors-article
Mar 10, 2026
Merged

docs: rewrite README around defineErrors and add philosophy articles#108
braden-w merged 6 commits intomainfrom
braden-w/define-errors-article

Conversation

@braden-w
Copy link
Collaborator

@braden-w braden-w commented Mar 10, 2026

The README was selling wellcrafted as a grab-bag of utilities ("Delightful TypeScript utilities for elegant, type-safe applications") when the actual value proposition is defineErrors — typed, serializable, composable error variants that no other Result library provides. This rewrites the README to lead with that, and adds five philosophy articles that explain the design decisions behind the API.

The old README had a scattershot structure: emoji-heavy feature cards, a before/after try-catch comparison, and examples that used generic names like ValidationError and ApiError without showing the composition story. More concretely, the hero example had a logic bug — it returned NotFound when a user already existed during creation. The new README opens with defineErrors, shows a realistic createUser flow, then builds up to cross-layer composition.

┌─────────────────────────────────────────────────────────────────────┐
│  Old README                                                         │
│                                                                     │
│  "Delightful TypeScript utilities for elegant, type-safe apps"      │
│                                                                     │
│  🎯 Result Type → 🏷️ Brand Types → 📋 Tagged Errors → 🔄 Query    │
│  (feature cards with emojis, no narrative thread)                   │
│                                                                     │
└─────────────────────────────────┬───────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  New README                                                         │
│                                                                     │
│  "Define your errors. Type the rest."                               │
│                                                                     │
│  defineErrors → trySync/tryAsync → composing across layers          │
│  → Result type → comparison table → philosophy                      │
│  (narrative arc: define, wrap, compose, compare)                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

README changes

The README now follows a deliberate progression:

  1. Hero exampledefineErrors with a realistic createUser that shows AlreadyExists and CreateFailed variants, tryAsync wrapping, and switch discrimination
  2. "Why define errors at all?" — argues that errors cluster into logical groups per domain, positioning defineErrors as the TypeScript equivalent of Rust's thiserror
  3. Wrapping unsafe codetrySync/tryAsync with catch handlers that return defineErrors factories
  4. Composing errors across layers — service errors wrapping raw failures via cause, then an API handler mapping domain errors to HTTP responses
  5. Comparison table — honest positioning against neverthrow, better-result, fp-ts, and Effect, including wellcrafted's weakness (manual if (error) composition vs method chains/generators)
  6. Philosophy section — explains the { data, error } shape as deliberately idiomatic, not novel

Philosophy articles

Five articles in docs/philosophy/:

"We Wrote a Builder Pattern, Then Deleted It" (error-api-evolution.mdx) traces the full design journey — hand-written object literals → createTaggedError factory → typed generics with overloads → fluent builder → defineErrors. The key data point: an audit of 321 call sites revealed that no error type mixed static and dynamic messages, which killed the builder's raison d'être.

"We Didn't Invent name and message — JavaScript Did" (why-name-and-message.mdx) argues that using name and message isn't arbitrary — it follows Error's existing convention. Covers why plain objects over classes (serialization), why Object.freeze over ownership (immutability), and how Rust's patterns map to JavaScript idioms without new abstractions.

"defineErrors Is thiserror Without Macros" (rust-inspiration.mdx, updated) — retitled from a generic heading to a direct argument. Expanded with side-by-side Rust/TypeScript definition code, a table of five divergence points (proc macros → template literals, ownership → Object.freeze, etc.), and a section on how TypeScript's const generic inference replaces what proc macros do in Rust.

"You Tried Typed Errors and Gave Up. The Problem Wasn't You." (from-effect-to-pragmatic-errors.mdx) — argues that the silent majority who tried FP error handling in TypeScript and walked away weren't wrong. TypeScript lacks the five language features (? operator, macros, pattern matching, ADTs, traits) that make FP error handling ergonomic in Rust and Haskell. wellcrafted sits in the pragmatic middle ground.

"wellcrafted Is for the Pragmatic Almost-Functional Programmer" (for-the-pragmatic-fp-developer.mdx) — targets developers who wanted typed errors from Rust/Effect but found the FP machinery didn't fit TypeScript. States the compromises plainly, positions wellcrafted as the smallest useful subset of FP error handling ideas, and includes an honest ecosystem comparison table.

braden-w added 4 commits March 3, 2026 10:17
…s Error convention

New philosophy article arguing that wellcrafted errors use `name` and `message`
because JavaScript's Error class already established that convention. Covers
JSON serializability, pragmatic Rust adaptation, and plain objects over classes.
Also adds error-api-evolution to docs nav and cross-references from existing docs.
…gument

Retitle from topic heading to argument: "defineErrors Is thiserror Without
Macros". Expand template literals section with side-by-side definition code.
Add closing section on how TypeScript's type inference replaces proc macros.
…omparison table

Lead with "Define your errors. Type the rest." tagline. Add "Why define errors
at all?" section explaining that errors cluster into logical groups per domain.
Fix hero example logic (AlreadyExists, not NotFound for create flow). Expand
comparison table to include better-result and fp-ts alongside neverthrow and
Effect, honestly showing wellcrafted's manual composition tradeoff. Add
Philosophy section on staying idiomatic to JavaScript. Reference ecosystem
consistency with Supabase, SvelteKit, and TanStack Query patterns.
@braden-w braden-w changed the title docs: rewrite README and add philosophy articles docs: rewrite README around defineErrors and add philosophy articles Mar 10, 2026
…cript

Reframes the "I tried Effect and came back" narrative as a broader argument
about why the silent majority of developers who tried typed errors gave up.
Core thesis: TypeScript lacks the five language features (? operator, macros,
pattern matching, ADTs, traits) that make FP error handling ergonomic in Rust
and Haskell. wellcrafted sits in the pragmatic middle ground.
Targets the "almost-functional programmer" who wanted typed errors from
Rust/Effect but found the FP machinery didn't fit TypeScript. States the
compromises plainly and positions wellcrafted as the smallest useful subset
of FP error handling ideas.
@braden-w braden-w merged commit 07e4aa1 into main Mar 10, 2026
2 checks passed
@braden-w braden-w deleted the braden-w/define-errors-article branch March 10, 2026 19:25
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