npm package: @ollystack/result
Description: A TypeScript-first Result type implementation for handling success and failure states in a type-safe and immutable way.
Author: Olly Campbell
Latest version: 0.0.0

# @ollystack/result

A TypeScript-first Result type implementation for handling success and failure states in a type-safe and immutable way.

## Table of Contents

1. [Motivation](#motivation)
2. [Installation](#installation)
3. [Usage](#usage)
4. [API](#api)

## Motivation

In TypeScript there is a type-safety gap around the return type of functions that throw errors.

For example, if you were to look at the return type of `JSON.parse` you would see that the expected return type is `any`. The problem is that this doesn't take into account for the function being passed bad data.

In that instace you will have an `Error` thrown and it is up to you - the developer - to know that you should wrap your `JSON.parse` function call in a try/catch block so you can handle it.

This package was originally created to provide results for functions like this so that safe wrappers could be created with a standard response type.

It can be used on functions like `JSON.parse` but I have also found it useful on async functions like `fetch` where you can capture a host of different error types (timeouts, status errors, response parsing, etc...).

```typescript
const safeJsonParse =  parse(
    text: string,
    reviver?: (this: any, key: string, value: any) => any
): Result<any, Fail> => {
 try {
    const data = JSON.parse(text, reviver);

    return ok(data)
 } catch(error) {
    return fail({
        key: 'error.json.parse',
        data: error,
        userMessage: 'Failed to parse JSON',
    })
 }
}
```

Now when using `safeJsonParse` you can see in the return type that there is a `Result` for you to handle both the happy path and the failure case.

```typescript
const result = safeJsonParse('...');

if (!result.ok) {
    // Force developer to handle the failure case
    console.error(result.payload.data);
} else {
    // ... Happy path logic can continue here
}
```

## Installation

```bash
npm install @ollystack/result
```

## Usage

### Basic Usage

```typescript
import { ok, nok, fail, Fail } from '@ollystack/result';

// Success case
const success = ok({ data: 'some data' });
if (success.ok) {
    console.log(success.payload.data); // 'some data'
}

// Simple failure case
const simpleFailure = nok('Something went wrong');
if (!simpleFailure.ok) {
    console.log(simpleFailure.payload); // 'Something went wrong'
}

// Structured failure case
const structuredFailure = fail({
    userMessage: 'Unable to process request',
    key: 'PROCESS_ERROR',
    data: { requestId: '123' },
});

if (!structuredFailure.ok) {
    console.log(structuredFailure.payload.userMessage); // 'Unable to process request'
    console.log(structuredFailure.payload.key); // 'PROCESS_ERROR'
    console.log(structuredFailure.payload.data); // { requestId: '123' }
}
```

### Error Handling

The package provides a `Fail` class for structured error handling:

```typescript
import { Fail } from '@ollystack/result';

// Create a new failure
const error = new Fail({
    userMessage: 'Invalid input provided',
    key: 'VALIDATION_ERROR',
    data: { field: 'email' },
});

// Convert to string (useful for logging)
console.log(error.toString()); // 'Invalid input provided'

// Get all error details
const errorDetails = error.toObject();
```

### Type Safety

The package is designed to be fully type-safe:

```typescript
interface User {
    id: string;
    name: string;
}

class ValidationFail extends Fail {}

function validateUser(maybeUser: unknown): Return<User, ValidationFail>;

const result = validateUser('...');

if (result.ok) {
    result.payload; // Is of type User
} else {
    result.payload; // Is of type ValidationFail
}
```

### Extending the Fail class

Extending the Fail class can be useful for functions that could return a host of different "fails"

To take the fetch example:

```typescript
// When status is not in the 200's
class ResponseStatusFail extends Fail {
    statusCode: number;
}

// When the request takes longer than your given timeout
class ResponseTimeoutFail extends Fail {}

// The response fails to parse into JSON
class ResponseParseFail extends Fail {}

type ResponseFail =
    | ResponseStatusFail
    | ResponseTimeoutFail
    | ResponseParseFail;
```

If your error of a result that has a failure payload type of `ResponseFail`, you can check for class instances to narrow down the cause of the error and handle the cases differently.

```typescript
function fetchUser(): Promise<Result<User, ResponseFail>>

const result = await fetchUser();

if(!result.ok) {
    if(
        result.payload instanceof ResponseStatusFail &&
        result.payload.statusCode === 401
    ) {
        // Re-auth
    } else if(
        result.payload instanceof ResponseStatusFail &&
        result.payload.statusCode > 499
    ) {
        // Send error to sentry
    } else if (...) {
        // Etc...
    }
}

```

## API Reference

### Result Types

#### `Return<Data, FailurePayload>`

A basic result type that can represent either success or failure.

```typescript
type Return<Data, FailurePayload> =
    | {
          readonly ok: true;
          readonly payload: Data;
      }
    | {
          readonly ok: false;
          readonly payload: FailurePayload;
      };
```

#### `Result<Data, F extends Fail>`

A specialized result type that uses the `Fail` class for error handling.

### Helper Functions

#### `ok<Data>(data: Data): Return<Data, never>`

Creates an immutable successful result.

```typescript
const result = ok({ id: 1, name: 'Test' });
```

#### `nok<FailurePayload>(payload?: FailurePayload): Return<never, FailurePayload>`

Creates an immutable failed result with an optional payload.

```typescript
const result = nok('Error message');
```

#### `fail(f: Fail | FailContructorParams): Result<never, Fail>`

Creates an immutable failed result using the `Fail` class.

```typescript
const result = fail({
    userMessage: 'Error occurred',
    key: 'ERROR_KEY',
});
```

### Fail Class

#### Constructor

```typescript
new Fail({
    userMessage: string;  // User-friendly error message
    key: string;         // Unique error identifier
    data?: unknown;      // Optional additional data
})
```

#### Methods

-   `toObject()`: Returns a frozen object containing all error properties
-   `toString()`: Returns the user message
-   `static from(f: Fail)`: Creates a new instance from an existing Fail object