# wellcrafted

[![npm version](https://badge.fury.io/js/wellcrafted.svg)](https://www.npmjs.com/package/wellcrafted)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Bundle Size](https://img.shields.io/bundlephobia/minzip/wellcrafted)](https://bundlephobia.com/package/wellcrafted)

*Delightful TypeScript utilities for elegant, type-safe applications*

## Transform unpredictable errors into type-safe results

```typescript
// ❌ 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.context.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
```typescript
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
```typescript
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

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

### 📋 Tagged Errors
Structured, serializable errors with convenient factory functions
```typescript
import { createTaggedError } from "wellcrafted/error";

const { ApiError, ApiErr } = createTaggedError("ApiError");
// ApiError() creates error object, ApiErr() creates Err-wrapped error
```

## Installation

```bash
npm install wellcrafted
```

## Quick Start

```typescript
import { tryAsync } from "wellcrafted/result";
import { createTaggedError } from "wellcrafted/error";

// Define your error with factory function
const { ApiError, ApiErr } = createTaggedError("ApiError");
type ApiError = ReturnType<typeof ApiError>;

// Wrap any throwing operation
const { data, error } = await tryAsync({
  try: () => fetch('/api/user').then(r => r.json()),
  mapErr: (error) => ApiErr({
    message: "Failed to fetch user",
    context: { endpoint: '/api/user' },
    cause: error
  })
});

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

## Core Features

<table>
<tr>
<td>

**🎯 Explicit Error Handling**  
All errors visible in function signatures

</td>
<td>

**📦 Serialization-Safe**  
Plain objects work everywhere

</td>
<td>

**✨ Elegant API**  
Clean, intuitive patterns

</td>
</tr>
<tr>
<td>

**🔍 Zero Magic**  
~50 lines of core code

</td>
<td>

**🚀 Lightweight**  
Zero dependencies, < 2KB

</td>
<td>

**🎨 Composable**  
Mix and match utilities

</td>
</tr>
</table>

## The Result Pattern Explained

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

```typescript
// The entire implementation
type Ok<T> = { data: T; error: null };
type Err<E> = { error: E; data: null };
type Result<T, E> = Ok<T> | Err<E>;
```

**The Magic**: This creates a discriminated union where TypeScript automatically narrows types:

```typescript
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

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

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

// Use data - TypeScript knows it's safe
```

### Wrap Unsafe Operations

```typescript
// Synchronous
const result = trySync({
  try: () => JSON.parse(jsonString),
  mapErr: (error) => Err({
    name: "ParseError",
    message: "Invalid JSON",
    context: { input: jsonString },
    cause: error
  })
});

// Asynchronous  
const result = await tryAsync({
  try: () => fetch(url),
  mapErr: (error) => Err({
    name: "NetworkError",
    message: "Request failed",
    context: { url },
    cause: error
  })
});
```

### Service Layer Example

```typescript
import { Result, Ok, tryAsync } from "wellcrafted/result";
import { createTaggedError } from "wellcrafted/error";

// Define service-specific errors
const { ValidationError, ValidationErr } = createTaggedError("ValidationError");
const { DatabaseError, DatabaseErr } = createTaggedError("DatabaseError");

type ValidationError = ReturnType<typeof ValidationError>;
type DatabaseError = ReturnType<typeof DatabaseError>;

// Factory function pattern - no classes!
export function createUserService(db: Database) {
  return {
    async createUser(input: CreateUserInput): Promise<Result<User, ValidationError | DatabaseError>> {
      // Direct return with Err variant
      if (!input.email.includes('@')) {
        return ValidationErr({
          message: "Invalid email format",
          context: { field: 'email', value: input.email },
          cause: undefined
        });
      }

      return tryAsync({
        try: () => db.save(input),
        mapErr: (error) => DatabaseErr({
          message: "Failed to save user",
          context: { operation: 'createUser', input },
          cause: error
        })
      });
    },

    async getUser(id: string): Promise<Result<User | null, DatabaseError>> {
      return tryAsync({
        try: () => db.findById(id),
        mapErr: (error) => DatabaseErr({
          message: "Failed to fetch user",
          context: { userId: id },
          cause: error
        })
      });
    }
  };
}

// Export type for the service
export type UserService = ReturnType<typeof createUserService>;

// Create a live instance (dependency injection at build time)
export const UserServiceLive = createUserService(databaseInstance);
```

## 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

```typescript
import { createTaggedError } from "wellcrafted/error";

// 1. Define service-specific errors
const { RecorderServiceError, RecorderServiceErr } = createTaggedError("RecorderServiceError");
type RecorderServiceError = ReturnType<typeof 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({
          message: "Already recording",
          context: { isRecording },
          cause: undefined
        });
      }
      
      isRecording = true;
      return Ok(undefined);
    },
    
    stopRecording(): Result<Blob, RecorderServiceError> {
      if (!isRecording) {
        return RecorderServiceErr({
          message: "Not currently recording", 
          context: { isRecording },
          cause: undefined
        });
      }
      
      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:

```typescript
// 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

<details>
<summary><b>API Route Handler</b></summary>

```typescript
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);
}
```
</details>

<details>
<summary><b>Form Validation</b></summary>

```typescript
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 Err({
      name: "FormError",
      message: "Validation failed",
      context: { fields: errors },
      cause: undefined
    });
  }
  
  return Ok(data as LoginData);
}
```
</details>

<details>
<summary><b>React Hook</b></summary>

```typescript
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;
}
```
</details>

## Comparison with Alternatives

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

## 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
- **`partitionResults(results)`** - Split array into oks/errs

### Error Functions
- **`createTaggedError(name)`** - Creates error factory functions
  - Returns two functions: `{ErrorName}` and `{ErrorName}Err`
  - The first creates plain error objects
  - The second creates Err-wrapped errors

### Types
- **`Result<T, E>`** - Union of Ok<T> | Err<E>
- **`TaggedError<T>`** - Structured error type
- **`Brand<T, B>`** - Branded type wrapper

## Learn More

- 📖 [Full Documentation](https://github.com/your-repo/wellcrafted/wiki)
- 🚀 [Examples](https://github.com/your-repo/wellcrafted/tree/main/examples)
- 💬 [Discussions](https://github.com/your-repo/wellcrafted/discussions)

## License

MIT

---

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