> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cbrock.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-Module Patterns

> Combine Result, Option, Guard, and Task together.

The modules in ts-chas are built to interoperate. This page shows the conversion paths you'll use most often, why you'd reach for each one, and exactly how to write them.

***

## Result ↔ Option

`Option<T>` is an alias for `Result<NonNullable<T>, never>`, so converting between the two is lossless in terms of your value. You're only deciding how you want to represent the absence of data.

**Use `result.toOption()` when** you've already handled the error in context and you want to treat an `Err` as simply "nothing here". The error payload is discarded.

```typescript theme={null}
import { chas } from 'ts-chas';

const parsed = chas.ok(42);
const option = parsed.toOption(); // Option<number> — Some(42)

const failed = chas.err('not found');
const empty = failed.toOption(); // Option::None — error is dropped
```

**Use `.orElse()` when** you're integrating an optional value into a pipeline that requires a concrete error type. You provide the error so callers know why the value was absent.

```typescript theme={null}
import { nullable } from 'ts-chas/option';
import { chas } from 'ts-chas';

const maybeId = nullable(localStorage.getItem('userId'));

// Promote the Option into a Result with a descriptive error
const result = maybeId.orElse(() => chas.err('userId not found in storage'));
// Result<string, string>
```

***

## Guard → Result

Guards are type predicates by default, but every guard chain also exposes a `.parse()` method that returns a `Result`. This is the right choice whenever you want structured validation errors that your callers can handle programmatically rather than relying on thrown exceptions.

**Use `.parse()` directly on any guard** to validate a single value:

```typescript theme={null}
import { is } from 'ts-chas/guard';

const emailResult = is.string.trim().email.min(5).parse(userInput);
// Result<string, GuardErr>

if (emailResult.isErr()) {
  console.error(emailResult.error.message);
  // e.g. "Expected a valid email address"
}
```

**Use `is.function(...).implResult()` when** you have a function whose inputs and outputs should be validated, and you want validation failures to surface as typed `Result` errors instead of unchecked exceptions:

```typescript theme={null}
import { is } from 'ts-chas/guard';

// Validate inputs and output — the function itself only runs if all guards pass
const validateAge = is
  .function({
    input: [is.number.gte(18).error('Must be 18 or older')],
    output: is.string,
  })
  .implResult((age) => `You are ${age} years old`);

const ok = validateAge(25);   // Result.Ok('You are 25 years old')
const err = validateAge(15);  // Result.Err(GuardErr { message: 'Must be 18 or older' })
```

***

## Option → Task

`Option` models synchronous optional values: a cache lookup, an environment variable, a local-storage read. `Task` orchestrates asynchronous pipelines. `Task.fromOption()` bridges the two so your cache check integrates cleanly into an async flow without branching on `.isSome()`.

**Use `Task.fromOption()` when** you want to treat a cache hit as the success path and fall through to an async fetch on a miss:

```typescript theme={null}
import { nullable } from 'ts-chas/option';
import { Task } from 'ts-chas/task';

const checkCache = () => nullable(localStorage.getItem('user'));

// Promote the synchronous Option into a lazy Task
const cacheTask = Task.fromOption(
  checkCache(),
  () => new Error('Cache miss')
);

const fetchTask = Task.from(
  () => fetch('/api/user').then((r) => r.text()),
  () => new Error('Fetch failed')
);

// If the cache is empty, fall back to the network request
const userTask = cacheTask.orElse(() => fetchTask);
const result = await userTask.execute();
// Result<string, Error>
```

The `.orElse()` callback only runs when `cacheTask` produces an `Err` (i.e., when the cache was empty). A cache hit short-circuits the whole chain.

***

## Result / ResultAsync → ResultAsync

You often work in a context where some helpers return synchronous `Result` and others return `ResultAsync`. `ResultAsync.fromResult()` lifts a synchronous `Result` into the async context so you can chain `.andThen()`, `.map()`, and other async operators uniformly.

```typescript theme={null}
import { chas } from 'ts-chas';

function parseConfig(raw: string): chas.Result<Config, string> {
  try {
    return chas.ok(JSON.parse(raw) as Config);
  } catch {
    return chas.err('Invalid JSON in config');
  }
}

// Lift into ResultAsync so you can continue an async chain
const configAsync = chas.ResultAsync.fromResult(parseConfig(rawConfigString));

const pipeline = configAsync
  .andThen((config) => fetchUserAsync(config.userId)) // ResultAsync
  .map((user) => user.displayName);

const result = await pipeline; // Result<string, string>
```

***

## `chas.go()` — mixing Result and ResultAsync

`.andThen()` chains are clean for two or three steps, but deeper pipelines become hard to read. `chas.go()` lets you write the same logic imperatively using `async function*` generators. Each `yield*` either extracts the `Ok` value or short-circuits by returning the `Err`. You can freely mix synchronous `Result` and `ResultAsync` in the same generator.

```typescript theme={null}
import { chas } from 'ts-chas';

declare function fetchUserAsync(id: string): chas.ResultAsync<User, NetworkError>;
declare function parseConfig(prefs: string): chas.Result<Config, ParseError>;
declare function saveProfileAsync(user: User, config: Config): chas.ResultAsync<void, DbError>;

const result = await chas.go(async function* () {
  // yield* unwraps Ok or short-circuits with the Err
  const user = yield* fetchUserAsync('user-123');       // ResultAsync
  const config = yield* parseConfig(user.prefs);        // Result
  yield* saveProfileAsync(user, config);                // ResultAsync

  return { user, config };
});
// ResultAsync<{ user: User; config: Config }, NetworkError | ParseError | DbError>

if (result.isOk()) {
  console.log(`Saved profile for ${result.value.user.name}`);
}
```

The final type collects the union of all possible error types from every `yield*` call, so TypeScript forces you to handle every failure mode.

***

## Guard + Tagged Errors

When you receive an `unknown` value from an external boundary (a caught exception, a message bus event, an inter-frame message), you need to check whether it's a specific tagged error before acting on it. `is.tagged()` from `ts-chas/guard` gives you a type-safe guard for exactly this.

**Use `is.tagged()` when** you're catching an unknown thrown value and need to branch on a specific error variant:

```typescript theme={null}
import { is } from 'ts-chas/guard';
import { chas } from 'ts-chas';

const AppError = chas.defineErrs({
  NotFound: (resource: string) => ({ resource }),
  Unauthorized: () => ({}),
});

// Check an unknown value against a specific error tag
function handleUnknown(value: unknown): string {
  if (is.tagged(AppError.NotFound)(value)) {
    // `value` is now typed as NotFoundErr
    return `Resource not found: ${value.resource}`;
  }
  if (is.tagged(AppError.Unauthorized)(value)) {
    return 'You are not authorized';
  }
  return 'Unknown error';
}
```

You can also pass a plain string tag when you don't have the factory in scope:

```typescript theme={null}
if (is.tagged('NotFound')(value)) {
  // value is typed as { _tag: string } & Error
}
```
