Skip to main content
Option<T> models an optional value. It is either Some<T> (a value is present) or None (no value). Under the hood, Option<T> is an alias for Result<NonNullable<T>, never>, so it integrates directly with the rest of the ts-chas ecosystem.
import {
  some,
  none,
  nullable,
  optionFromGuard,
  type Option,
} from 'ts-chas/option';

The Some and None types

TypeDescription
Some<T>Represents a present, non-nullable value. Equivalent to Ok<NonNullable<T>>.
NoneRepresents the absence of a value. Equivalent to Err<never>.
Option<T>The union: Some<T> | None
Because Option is built on Result, all Result methods are available on Option values.

Factory functions

nullable(value)

Returns Some(value) if the value is non-null and non-undefined, otherwise None. This is the most common way to create an Option.
import { nullable } from 'ts-chas/option';

const a = nullable('hello');    // Some('hello')
const b = nullable(null);       // None
const c = nullable(undefined);  // None
const token = nullable(localStorage.getItem('token')); // Some(string) | None

optionFromGuard(value, guard)

Returns Some(value) if the type guard passes, None otherwise. Use this to narrow an unknown value into a typed Option.
import { optionFromGuard } from 'ts-chas/option';
import { is } from 'ts-chas/guard';

const raw: unknown = JSON.parse(stored);
const maybeNum = optionFromGuard(raw, is.number);
// Option<number>

if (maybeNum.isSome()) {
  console.log(maybeNum.value * 2); // typed as number
}

some(value)

Explicitly constructs a Some. The value must be non-null and non-undefined.
import { some } from 'ts-chas/option';

const opt = some(42); // Some(42)
opt.value; // 42

none()

Explicitly constructs a None.
import { none } from 'ts-chas/option';

const opt = none(); // None
opt.isNone(); // true

Converting Option to Result

Option is Result<NonNullable<T>, never>. To assign a concrete error type to the None case, use .orElse():
import { nullable } from 'ts-chas/option';
import { chas } from 'ts-chas';

const maybeId = nullable(params.get('id'));
const result = maybeId.orElse(() => chas.err(new Error('Missing id parameter')));
// Result<string, Error>
Use Task.fromOption to lift an Option directly into an async pipeline — see Converting to Task below.

Instance methods

Because Option<T> is a Result, you have the full Result method surface:

.isSome() / .isNone()

Type-narrowing predicates.
import { nullable } from 'ts-chas/option';

const opt = nullable(user.email);

if (opt.isSome()) {
  sendEmail(opt.value); // opt.value is string here
}

.map(fn)

Transforms the value inside Some. Has no effect on None.
import { nullable } from 'ts-chas/option';

const maybeLength = nullable('hello').map(s => s.length);
// Some(5)

const nothing = nullable<string>(null).map(s => s.length);
// None

.andThen(fn)

flatMap for Options. fn receives the Some value and must return another Option.
import { nullable } from 'ts-chas/option';

const maybeUser = nullable(getUser());
const maybeEmail = maybeUser.andThen(user => nullable(user.email));
// Some(email) | None

.orElse(fn)

Provides a fallback Option if the current one is None.
import { nullable } from 'ts-chas/option';

const maybeToken = nullable(localStorage.getItem('token'))
  .orElse(() => nullable(sessionStorage.getItem('token')));
// Tries session storage if local storage is empty

.unwrapOr(default)

Returns the value if Some, or the default if None.
import { nullable } from 'ts-chas/option';

const name = nullable(user.displayName).unwrapOr('Anonymous');

.unwrap()

Returns the value if Some, or throws if None. Prefer .unwrapOr() or .isSome() checks in production code.
const opt = some(42);
opt.unwrap(); // 42

.match({ some, none })

Pattern-matches the option, returning the result of whichever branch applies.
import { nullable } from 'ts-chas/option';

const greeting = nullable(user.name).match({
  ok: name => `Hello, ${name}!`,
  err: () => 'Hello, stranger!',
});
Because Option is a Result, the branches are ok and err — mapping to some and none respectively.

Converting to Task

Use Task.fromOption to lift a synchronous Option into an async Task pipeline. If the option is None, the task fails with the provided error.
import { nullable } from 'ts-chas/option';
import { Task } from 'ts-chas/task';

// Synchronous cache lookup → Option
const checkCache = () => nullable(localStorage.getItem('user'));

// Convert to Task so it fits into an async pipeline
const cacheTask = Task.fromOption(
  checkCache(),
  () => new Error('Cache miss')
);

// Fall back to a network fetch if the cache is empty
const fetchTask = Task.from(
  () => fetch('/api/user').then(r => r.json()),
  () => new Error('Fetch failed')
);

const result = await cacheTask
  .fallback(fetchTask)
  .execute();
// Result<string, Error>

Complete example

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

interface Config {
  apiUrl: string;
  timeout: number;
}

function getStoredConfig(): Config | null {
  try {
    return JSON.parse(localStorage.getItem('config') ?? 'null');
  } catch {
    return null;
  }
}

const configTask = Task.fromOption(
  nullable(getStoredConfig()),
  () => new Error('No config in storage')
);

const fetchConfigTask = Task.from(
  () => fetch('/api/config').then(r => r.json() as Promise<Config>),
  () => new Error('Config fetch failed')
);

// Use cached config; fall back to network
const result = await configTask
  .fallback(fetchConfigTask)
  .tap(config => console.log('Using config:', config.apiUrl))
  .execute();

if (result.isOk()) {
  startApp(result.value);
} else {
  console.error('Could not load config:', result.error.message);
}