Skip to main content
Option<T> models the explicit presence (Some) or absence (None) of a value. Under the hood, it is an alias for Result<NonNullable<T>, never>, which means it shares the full Result API: .map(), .andThen(), .unwrapOr(), and everything else.
import { nullable } from 'ts-chas/option';

// Instead of:
const user = getUserFromCache('123'); // User | null
if (user) {
  console.log(user.name);
}

// You get:
const maybeUser = nullable(getUserFromCache('123')); // Option<User>
const name = maybeUser.map((u) => u.name).unwrapOr('Anonymous');

Creating options

nullable(value)

Converts any nullable value (T | null | undefined) into an Option<T>:
import { nullable } from 'ts-chas/option';

const a = nullable('hello');    // Some('hello')
const b = nullable(null);       // None
const c = nullable(undefined);  // None

optionFromGuard(value, guard)

Creates an Option from an unknown value and a type guard. Returns Some if the guard passes, None otherwise:
import { optionFromGuard } from 'ts-chas/option';
import { is } from 'ts-chas/guard';

const maybeNum = optionFromGuard(someValue, is.number);
// Some<number> if someValue is a number, None otherwise

The full Result API

Because Option<T> is Result<NonNullable<T>, never>, every Result method is available:
const greeting = maybeUser
  .map((user) => user.firstName)
  .map((name) => `Hello, ${name}!`)
  .unwrapOr('Hello, Guest!');

// Chain fallible operations
const verified = maybeToken
  .andThen((token) => validateToken(token)) // returns Option<Claims>
  .unwrapOr(null);

Converting to Result

Use .orElse() to assign a specific error to the None case, converting an Option to a Result:
import { nullable } from 'ts-chas/option';
import { chas } from 'ts-chas';

const maybeUser = nullable(getUserFromCache(id));

// Provide a concrete error for the None branch
const result = maybeUser.orElse(() => chas.err('User not found in cache'));
// Result<User, string>

Upgrading to Task

Option is ideal for synchronous lookups (like reading a cache or an environment variable). When you need to feed that optional value into an async pipeline, upgrade it to a Task with Task.fromOption():
import { nullable } from 'ts-chas/option';
import { Task } from 'ts-chas/task';

// Check cache first (synchronous Option)
const maybeUser = nullable(localStorage.getItem('cached-user'));

// Promote to Task — provide an error for the None case
const cacheTask = Task.fromOption(maybeUser, () => new Error('Cache miss'));

// Compose with a fetch Task as fallback
const fetchTask = Task.from(
  () => fetch('/api/user').then((r) => r.json()),
  () => new Error('Fetch failed')
);

const userTask = cacheTask.orElse(() => fetchTask);
const result = await userTask.execute();
// Result<string, Error>
Task.fromOption() is the idiomatic way to bridge synchronous optional values into larger async workflows. The None case becomes an Err that your Task pipeline can handle or recover from.

Practical example: cache-then-fetch

import { nullable } from 'ts-chas/option';
import { Task } from 'ts-chas/task';

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

function getUserFromCache(id: string): User | null {
  const raw = sessionStorage.getItem(`user:${id}`);
  return raw ? JSON.parse(raw) : null;
}

function fetchUser(id: string): Task<User, Error> {
  return Task.from(
    () => fetch(`/api/users/${id}`).then((r) => r.json()),
    (e) => new Error(`Fetch failed: ${e}`)
  );
}

async function getUser(id: string) {
  const cachedUser = nullable(getUserFromCache(id));

  const userTask = Task.fromOption(cachedUser, () => new Error('Not in cache'))
    .orElse(() => fetchUser(id));

  const result = await userTask.execute();

  return result.match({
    ok: (user) => user,
    err: (e) => { throw e; },
  });
}