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
| Type | Description |
|---|
Some<T> | Represents a present, non-nullable value. Equivalent to Ok<NonNullable<T>>. |
None | Represents 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);
}