npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wellcrafted

v0.34.0

Published

Delightful TypeScript patterns for elegant, type-safe applications

Readme

wellcrafted

npm version TypeScript License: MIT Bundle Size

Delightful TypeScript utilities for elegant, type-safe applications

Transform unpredictable errors into type-safe results

// ❌ Before: Which errors can this throw? 🤷
try {
  await saveUser(user);
} catch (error) {
  // ... good luck debugging in production
}

// ✅ After: Every error is visible and typed
const { data, error } = await saveUser(user);
if (error) {
  switch (error.name) {
    case "ValidationError":
      showToast(`Invalid ${error.field}`);
      break;
    case "AuthError":
      redirectToLogin();
      break;
    // TypeScript ensures you handle all cases!
  }
}

A collection of simple, powerful primitives

🎯 Result Type

Make errors explicit in function signatures

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) return Err("Division by zero");
  return Ok(a / b);
}

🏷️ Brand Types

Create distinct types from primitives

type UserId = string & Brand<"UserId">;
type OrderId = string & Brand<"OrderId">;

// TypeScript prevents mixing them up!
function getUser(id: UserId) { /* ... */ }

📋 Tagged Errors

Structured, serializable errors with a declarative API

import { defineErrors, type InferError } from "wellcrafted/error";

const errors = defineErrors({
  // Static error — no fields needed
  ValidationError: () => ({
    message: "Email is required",
  }),
  // Structured error — fields are spread flat on the error object
  ApiError: (fields: { endpoint: string }) => ({
    ...fields,
    message: `Request to ${fields.endpoint} failed`,
  }),
});
const { ValidationError, ApiError } = errors;

🔄 Query Integration

Seamless TanStack Query integration with dual interfaces

import { createQueryFactories } from "wellcrafted/query";
import { QueryClient } from "@tanstack/query-core";

const queryClient = new QueryClient();
const { defineQuery, defineMutation } = createQueryFactories(queryClient);

// Define operations that return Result types
const userQuery = defineQuery({
  queryKey: ['users', userId],
  queryFn: () => getUserFromAPI(userId) // Returns Result<User, ApiError>
});

// Use reactively in components with automatic state management
// Svelte 5 requires accessor function; React uses options directly
const query = createQuery(() => userQuery.options); // Svelte
// query.data, query.error, query.isPending all managed automatically

// Or use imperatively for direct execution (perfect for event handlers)
const { data, error } = await userQuery.fetch();
// Or shorthand: await userQuery() (same as .ensure())
if (error) {
  showErrorToast(error.message);
  return;
}
// Use data...

Installation

npm install wellcrafted

Quick Start

import { tryAsync } from "wellcrafted/result";
import { defineErrors, type InferError } from "wellcrafted/error";

// Define your errors declaratively
const errors = defineErrors({
  ApiError: (fields: { endpoint: string }) => ({
    ...fields,
    message: `Failed to fetch ${fields.endpoint}`,
  }),
});
const { ApiError, ApiErr } = errors;
type ApiError = InferError<typeof errors, "ApiError">;

// Wrap any throwing operation
const { data, error } = await tryAsync({
  try: () => fetch('/api/user').then(r => r.json()),
  catch: (e) => ApiErr({ endpoint: '/api/user' })
});

if (error) {
  console.error(`${error.name}: ${error.message}`);
} else {
  console.log("User:", data);
}

Core Features

🎯 Explicit Error Handling
All errors visible in function signatures

📦 Serialization-Safe
Plain objects work everywhere

✨ Elegant API
Clean, intuitive patterns

🔍 Zero Magic
~50 lines of core code

🚀 Lightweight
Zero dependencies, < 2KB

🎨 Composable
Mix and match utilities

The Result Pattern Explained

The Result type makes error handling explicit and type-safe:

type Ok<T> = { data: T; error: null };
type Err<E> = { error: E; data: null };
type Result<T, E> = Ok<T> | Err<E>;

This creates a discriminated union where TypeScript automatically narrows types:

if (result.error) {
  // TypeScript knows: error is E, data is null
} else {
  // TypeScript knows: data is T, error is null
}

Basic Patterns

Handle Results with Destructuring

const { data, error } = await someOperation();

if (error) {
  // Handle error with full type safety
  return;
}

// Use data - TypeScript knows it's safe

Wrap Unsafe Operations

import { defineErrors, type InferError } from "wellcrafted/error";

// Define errors declaratively
const errors = defineErrors({
  ParseError: (fields: { input: string }) => ({
    ...fields,
    message: `Invalid JSON: ${fields.input.slice(0, 50)}`,
  }),
  NetworkError: (fields: { url: string }) => ({
    ...fields,
    message: `Request to ${fields.url} failed`,
  }),
});
const { ParseErr, NetworkErr } = errors;

// Synchronous
const result = trySync({
  try: () => JSON.parse(jsonString),
  catch: () => ParseErr({ input: jsonString })
});

// Asynchronous
const result = await tryAsync({
  try: () => fetch(url),
  catch: () => NetworkErr({ url })
});

Real-World Service + Query Layer Example

// 1. Service Layer - Pure business logic
import { defineErrors, type InferError } from "wellcrafted/error";
import { tryAsync, Result, Ok } from "wellcrafted/result";

const recorderErrors = defineErrors({
  RecorderServiceError: (fields: { currentState?: string; permissions?: string }) => ({
    ...fields,
    message: fields.permissions
      ? `Missing ${fields.permissions} permission`
      : `Invalid recorder state: ${fields.currentState}`,
  }),
});
const { RecorderServiceError, RecorderServiceErr } = recorderErrors;
type RecorderServiceError = InferError<typeof recorderErrors, "RecorderServiceError">;

export function createRecorderService() {
  let isRecording = false;
  let currentBlob: Blob | null = null;

  return {
    async startRecording(): Promise<Result<void, RecorderServiceError>> {
      if (isRecording) {
        return RecorderServiceErr({ currentState: 'recording' });
      }

      return tryAsync({
        try: async () => {
          const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
          const recorder = new MediaRecorder(stream);
          // ... recording setup
          isRecording = true;
        },
        catch: () => RecorderServiceErr({ permissions: 'microphone' })
      });
    },

    async stopRecording(): Promise<Result<Blob, RecorderServiceError>> {
      if (!isRecording) {
        return RecorderServiceErr({ currentState: 'idle' });
      }

      // Stop recording and return blob...
      isRecording = false;
      return Ok(currentBlob!);
    }
  };
}

// 2. Query Layer - Adds caching, reactivity, and UI error handling
import { createQueryFactories } from "wellcrafted/query";

const { defineQuery, defineMutation } = createQueryFactories(queryClient);

export const recorder = {
  getRecorderState: defineQuery({
    queryKey: ['recorder', 'state'],
    queryFn: async () => {
      const { data, error } = await services.recorder.getState();
      if (error) {
        // Transform service error to UI-friendly error
        return Err({
          title: "❌ Failed to get recorder state",
          description: error.message,
          action: { type: 'retry' }
        });
      }
      return Ok(data);
    },
    refetchInterval: 1000, // Poll for state changes
  }),

  startRecording: defineMutation({
    mutationKey: ['recorder', 'start'],
    mutationFn: async () => {
      const { error } = await services.recorder.startRecording();
      if (error) {
        return Err({
          title: "❌ Failed to start recording",  
          description: error.message,
          action: { type: 'more-details', error }
        });
      }

      // Optimistically update cache
      queryClient.setQueryData(['recorder', 'state'], 'recording');
      return Ok(undefined);
    }
  })
};

// 3. Component Usage - Choose reactive or imperative based on needs
// Reactive: Automatic state management (Svelte 5 requires accessor function)
const recorderState = createQuery(() => recorder.getRecorderState.options);

// Imperative: Direct execution for event handlers
async function handleStartRecording() {
  const { error } = await recorder.startRecording.execute();
  // Or shorthand: await recorder.startRecording()
  if (error) {
    showToast(error.title, { description: error.description });
  }
}

Smart Return Type Narrowing

The catch parameter in trySync and tryAsync enables smart return type narrowing based on your error handling strategy:

Recovery Pattern (Always Succeeds)

// ❌ Before: Mutable variable required
let parsed: unknown;
try {
  parsed = JSON.parse(riskyJson);
} catch {
  parsed = [];
}
// Now use parsed...

// ✅ After: Clean, immutable pattern
const { data: parsed } = trySync({
  try: () => JSON.parse(riskyJson),
  catch: () => Ok([])
});
// parsed is always defined and type-safe!

// When catch always returns Ok<T>, the function returns Ok<T>
// This means no error checking needed - you can safely destructure and use data directly

Propagation Pattern (May Fail)

const parseErrors = defineErrors({
  ParseError: () => ({
    message: "Invalid JSON",
  }),
});
const { ParseErr } = parseErrors;

// When catch can return Err<E>, function returns Result<T, E>
const mayFail = trySync({
  try: () => JSON.parse(riskyJson),
  catch: () => ParseErr()
});
// mayFail: Result<object, ParseError> - Must check for errors
if (isOk(mayFail)) {
  console.log(mayFail.data); // Only safe after checking
}

Mixed Strategy (Conditional Recovery)

const smartParse = trySync({
  try: () => JSON.parse(input),
  catch: () => {
    // Recover from empty input
    if (input.trim() === "") {
      return Ok({}); // Return Ok<T> for fallback
    }
    // Propagate other errors
    return ParseErr();
  }
});
// smartParse: Result<object, ParseError> - Mixed handling = Result type

This eliminates unnecessary error checking when you always recover, while still requiring proper error handling when failures are possible.

Why wellcrafted?

JavaScript's try-catch has fundamental problems:

  1. Invisible Errors: Function signatures don't show what errors can occur
  2. Lost in Transit: JSON.stringify(new Error()) loses critical information
  3. No Type Safety: TypeScript can't help with catch (error) blocks
  4. Inconsistent: Libraries throw different things (strings, errors, objects, undefined)

wellcrafted solves these with simple, composable primitives that make errors:

  • Explicit in function signatures
  • Serializable across all boundaries
  • Type-safe with full TypeScript support
  • Consistent with structured error objects

Service Pattern Best Practices

Based on real-world usage, here's the recommended pattern for creating services with wellcrafted:

Factory Function Pattern

import { defineErrors, type InferError } from "wellcrafted/error";
import { Result, Ok } from "wellcrafted/result";

// 1. Define service-specific errors with typed fields and message
const recorderErrors = defineErrors({
  RecorderServiceError: (fields: { isRecording: boolean }) => ({
    ...fields,
    message: fields.isRecording ? "Already recording" : "Not currently recording",
  }),
});
const { RecorderServiceError, RecorderServiceErr } = recorderErrors;
type RecorderServiceError = InferError<typeof recorderErrors, "RecorderServiceError">;

// 2. Create service with factory function
export function createRecorderService() {
  // Private state in closure
  let isRecording = false;

  // Return object with methods
  return {
    startRecording(): Result<void, RecorderServiceError> {
      if (isRecording) {
        return RecorderServiceErr({ isRecording });
      }

      isRecording = true;
      return Ok(undefined);
    },

    stopRecording(): Result<Blob, RecorderServiceError> {
      if (!isRecording) {
        return RecorderServiceErr({ isRecording });
      }

      isRecording = false;
      return Ok(new Blob(["audio data"]));
    }
  };
}

// 3. Export type
export type RecorderService = ReturnType<typeof createRecorderService>;

// 4. Create singleton instance
export const RecorderServiceLive = createRecorderService();

Platform-Specific Services

For services that need different implementations per platform:

// types.ts - shared interface
export type FileService = {
  readFile(path: string): Promise<Result<string, FileServiceError>>;
  writeFile(path: string, content: string): Promise<Result<void, FileServiceError>>;
};

// desktop.ts
export function createFileServiceDesktop(): FileService {
  return {
    async readFile(path) {
      // Desktop implementation using Node.js APIs
    },
    async writeFile(path, content) {
      // Desktop implementation
    }
  };
}

// web.ts  
export function createFileServiceWeb(): FileService {
  return {
    async readFile(path) {
      // Web implementation using File API
    },
    async writeFile(path, content) {
      // Web implementation
    }
  };
}

// index.ts - runtime selection
export const FileServiceLive = typeof window !== 'undefined' 
  ? createFileServiceWeb()
  : createFileServiceDesktop();

Common Use Cases

export async function GET(request: Request) {
  const result = await userService.getUser(params.id);
  
  if (result.error) {
    switch (result.error.name) {
      case "UserNotFoundError":
        return new Response("Not found", { status: 404 });
      case "DatabaseError":
        return new Response("Server error", { status: 500 });
    }
  }
  
  return Response.json(result.data);
}
import { defineErrors, type InferError } from "wellcrafted/error";

const formErrors = defineErrors({
  FormError: (fields: { fields: Record<string, string[]> }) => ({
    ...fields,
    message: `Validation failed for: ${Object.keys(fields.fields).join(", ")}`,
  }),
});
const { FormErr } = formErrors;
type FormError = InferError<typeof formErrors, "FormError">;

function validateLoginForm(data: unknown): Result<LoginData, FormError> {
  const errors: Record<string, string[]> = {};

  if (!isValidEmail(data?.email)) {
    errors.email = ["Invalid email format"];
  }

  if (Object.keys(errors).length > 0) {
    return FormErr({ fields: errors });
  }

  return Ok(data as LoginData);
}
function useUser(id: number) {
  const [state, setState] = useState<{
    loading: boolean;
    user?: User;
    error?: ApiError;
  }>({ loading: true });

  useEffect(() => {
    fetchUser(id).then(result => {
      if (result.error) {
        setState({ loading: false, error: result.error });
      } else {
        setState({ loading: false, user: result.data });
      }
    });
  }, [id]);

  return state;
}

Comparison with Alternatives

| | wellcrafted | fp-ts | Effect | neverthrow | |---|---|---|---|---| | Bundle Size | < 2KB | ~30KB | ~50KB | ~5KB | | Learning Curve | Minimal | Steep | Steep | Moderate | | Syntax | Native async/await | Pipe operators | Generators | Method chains | | Type Safety | ✅ Full | ✅ Full | ✅ Full | ✅ Full | | Serializable Errors | ✅ Built-in | ❌ Classes | ❌ Classes | ❌ Classes | | Runtime Overhead | Zero | Minimal | Moderate | Minimal |

Advanced Usage

For comprehensive examples, service layer patterns, framework integrations, and migration guides, see the full documentation →

API Reference

Result Functions

  • Ok(data) - Create success result
  • Err(error) - Create failure result
  • isOk(result) - Type guard for success
  • isErr(result) - Type guard for failure
  • trySync(options) - Wrap throwing function
  • tryAsync(options) - Wrap async function
  • unwrap(result) - Extract data or throw error
  • resolve(value) - Handle values that may or may not be Results
  • partitionResults(results) - Split array into oks/errs

Query Functions

  • createQueryFactories(client) - Create query/mutation factories for TanStack Query
  • defineQuery(options) - Define a query with dual interface (.options + callable/.ensure()/.fetch())
  • defineMutation(options) - Define a mutation with dual interface (.options + callable/.execute())

Error Functions

  • defineErrors(definitions) - Define multiple error factories in a single declaration
    • Each key becomes an error name; the value is a factory function returning fields + message
    • Returns {ErrorName} (plain error) and {ErrorName}Err (Err-wrapped) for each key
    • Factory functions receive typed fields and return { ...fields, message }
    • No-field errors use () => ({ message: '...' })
    • name is a reserved key — prevented at compile time
  • extractErrorMessage(error) - Extract readable message from unknown error

Types

  • Result<T, E> - Union of Ok | Err
  • Ok<T> - Success result type
  • Err<E> - Error result type
  • InferError<TErrors, TName> - Extract error type from defineErrors result
  • Brand<T, B> - Branded type wrapper
  • ExtractOkFromResult<R> - Extract Ok variant from Result union
  • ExtractErrFromResult<R> - Extract Err variant from Result union
  • UnwrapOk<R> - Extract success value type from Result
  • UnwrapErr<R> - Extract error value type from Result

Development Setup

Peer Directory Requirement

Wellcrafted shares AI agent skills (.agents/skills/, .claude/skills/) with the Epicenter repo via relative symlinks. Epicenter is the source of truth for skill definitions — they're authored and maintained there, and wellcrafted consumes them to stay in sync.

Both repos must be sibling directories under the same parent:

Code/
├── epicenter/       # Source of truth for skills
│   └── .agents/skills/
└── wellcrafted/     # Symlinks to epicenter
    ├── .agents/skills/<name> → ../../../epicenter/.agents/skills/<name>
    └── .claude/skills/<name> → ../../.agents/skills/<name>

If symlinks appear broken after cloning, ensure epicenter is cloned alongside wellcrafted:

cd "$(git rev-parse --show-toplevel)/.."
git clone https://github.com/EpicenterHQ/epicenter.git

License

MIT


Made with ❤️ by developers who believe error handling should be delightful.