typesafe-ts
Version:
TypeScript utilities for type-safe error handling and optional values
312 lines • 13.9 kB
TypeScript
/**
* An Optional that has been confirmed to contain a value.
* @template ValueType
* @property {ValueType} value - The contained value.
*/
interface OptionalWithValue<ValueType> {
value: ValueType;
}
/**
* The `none` value is used to represent an empty value in an Optional.
*/
declare const none_value: unique symbol;
type NoneType = typeof none_value;
/**
* Nullable<T> is used to provide better error messages for `optional.from_nullable()`.
*
* The tuple wrapping technique (`[T] extends [...]`) is used here to prevent
* distributive conditional types, which would otherwise cause the conditional
* to be applied to each member of a union type individually. By wrapping T in a
* tuple, we ensure the check is performed on the whole type at once.
*/
type Nullable<T> = [T] extends [null | undefined] ? "Error: Value provided to `optional.from_nullable()` is always nullable. To create an empty optional, use `optional.none()`." : [T] extends [NonNullable<T>] ? "Error: Value provided to `optional.from_nullable()` isn't nullable. To create an optional with a value, use `optional.some(value)`." : T;
/**
* The public interface of an Optional value. An Optional either contains a value of type `ValueType` or is empty.
*
* Optionals should be constructed using `optional.some()` or `optional.none<ValueType>()`.
* These functions return objects implementing the `Optional` interface.
*
* The value of an Optional can only be accessed after checking `is_some()`.
*
* When working with Optional values, it is preferred to use methods such as `map`,
* `and_then`, and `or_else` for sequential operations, and only check `is_some()` at the end.
* These methods allow consumers to write type-safe code using functional patterns that are
* more expressive than handling empty states at every step.
*
* @template ValueType - The type of the value that the Optional may contain.
*/
export interface Optional<ValueType> {
/**
* Check if the Optional contains some value.
* This should be used in an `if` or `switch` to expose the value property.
*
* example:
* ```ts
* const maybeValue = optional.some("Hello");
* if (maybeValue.is_some()) {
* // maybeValue has been widened to OptionalWithValue<string> and has a `.value` property.
* console.log(maybeValue.value); // prints "Hello"
* } else {
* // Type error: Property 'Value' does not exist on type 'Optional<string>'.
* console.log(maybeValue.value);
* }
*
* @return true if the Optional contains a value and widens the type to include a `value` property.
*/
is_some(): this is OptionalWithValue<ValueType>;
/**
* Get the value of the Optional if contains some value.
* Otherwise, returns the provided default value.
*
* example:
* ```ts
* const maybeValue = optional.none<string>();
* console.log(maybeValue.value_or("Default")); // prints "Default"
* ```
*
* @param value_if_empty - The value to return if the Optional is empty.
* @return The value of the Optional if it contains a value, otherwise `value_if_empty`.
*/
value_or(value_if_empty: ValueType): ValueType;
/**
* Transforms the value of the Optional using the provided function.
* If the Optional is empty, the mapping function will not be run.
*
* example:
* ```ts
* // with a value
* const maybe = optional.some("Hello").map((v) => v.toUpperCase());
* if (maybe.is_some()) {
* console.log(maybe.value); // prints "HELLO"
* }
* // without a value
* const empty = optional.none<string>().map((v) => v.toUpperCase());
* if (!empty.is_some()) {
* console.log("No value"); // prints "No value"
* }
* ```
*
* @param mapper_fn - The function to transform the value, if present. Will not be called if the Optional is empty.
* @returns {Optional<NewValueType>} A new Optional containing the result of transforming the value or empty if this Optional is empty.
*/
map<NewValueType>(mapper_fn: (value: ValueType) => NewValueType): Optional<NewValueType>;
/**
* Chains another Optional operation if this optional contains some value.
* If this Optional is empty, the provided function will not be called.
*
* example:
* ```ts
* const maybe = optional.some("Hello").and_then((v) => optional.some(v.length));
* if (maybe.is_some()) {
* console.log(maybe.value); // prints 5
* }
* ```
*
* @param fn - A function that accepts an argument of the ValueType of this optional and that returns another Optional.
* @return A new Optional containing the value from the provided function, or an empty Optional if this Optional is empty.
*/
and_then<NewValueType>(fn: (value: ValueType) => Optional<NewValueType>): Optional<NewValueType>;
/**
* Provides a fallback Optional if this Optional is empty.
* If this Optional contains a value, it will return itself and the fallback function will not be called.
*
* example:
* ```ts
* const maybe = optional.none<string>().or_else(() => optional.some("Default"));
* if (maybe.is_some()) {
* console.log(maybe.value); // prints "Default"
* }
* ```
* @param fn - A function that returns an Optional to use as a fallback if this Optional is empty.
* @return The current Optional if it contains a value, otherwise the Optional returned by the provided function.
*/
or_else(fn: () => Optional<ValueType>): Optional<ValueType>;
/**
* Returns a generator that yields the contained value if this Optional has a value.
* If this Optional is empty, the generator yields nothing (completes immediately).
* This allows for easy iteration over present values in for-of loops and other iterator contexts.
*
* @returns A generator that yields the value if present, otherwise yields nothing
*
* @example
* ```typescript
* const someValue = optional.some(42);
* for (const value of someValue) {
* console.log(value); // 42
* }
*
* const emptyValue = optional.none<number>();
* for (const value of emptyValue) {
* console.log("This won't execute");
* }
*
* // Useful for collecting present values from multiple optionals
* const optionals = [
* optional.some(1),
* optional.none<number>(),
* optional.some(3)
* ];
*
* const values = [];
* for (const opt of optionals) {
* for (const value of opt) {
* values.push(value);
* }
* }
* console.log(values); // [1, 3]
* ```
*/
[Symbol.iterator](): Generator<ValueType, void, unknown>;
get [Symbol.toStringTag](): "Optional";
}
/**
* A concrete implementation of the Optional interface.
* `Optional` uses the `is_some()` [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) method to narrow the interface to `OptionalWithValue<ValueType>` when it contains a value.
* Because of that behavior, it's best not to directly expose/use the constructor of this class.
* Instead, prefer using `optional.some()` or `optional.none<ValueType>()` to create instantiations typed with only the public interface of `Optional<ValueType>`.
*
* @template ValueType - The type of the value that the Optional may contain.
*/
declare class OptionalImpl<ValueType> implements Optional<ValueType> {
value: ValueType | NoneType;
constructor(value: ValueType | NoneType);
is_some(): this is OptionalWithValue<ValueType>;
value_or(value_if_empty: ValueType): ValueType;
map<NewValueType>(mapper_fn: (value: ValueType) => NewValueType): Optional<NewValueType>;
or_else(fn: () => Optional<ValueType>): Optional<ValueType>;
and_then<NewValueType>(fn: (value: ValueType) => Optional<NewValueType>): Optional<NewValueType>;
[Symbol.iterator](): Generator<ValueType, void, unknown>;
get [Symbol.toStringTag](): "Optional";
/**
* Construct an Optional that contains a value of type ValueType.
* This is the preferred way to create an Optional instance with a value.
* * @param value - The value to wrap in an Optional.
* @return An Optional that contains the provided value.
* @example basic usage:
* ```ts
* const maybeValue = optional.some("Hello");
* if (maybeValue.is_some()) {
* console.log(maybeValue.value); // prints "Hello"
* }
* ```
*
* @example Using monadic operations:
* ```ts
* const maybeValue = optional.some("Hello");
* const transformedResult = maybeValue.map((v) => v + " World")
* .and_then((v) => optional.some(v.length))
* .value_or(0);
* console.log(transformedResult); // prints 11
*/
static some<ValueType>(this: void, value: ValueType): Optional<ValueType>;
/**
* Construct an empty Optional.
* This is the recommended way to create an Optional instance that does not contain a value.
* @type ValueType - The type of the value that the Optional would contain if it had a value. Typically you will want to explicitly specify this type.
*
* @return An Optional that does not contain a value.
* @example basic usage:
* ```ts
* const maybeValue = optional.none<string>();
* ```
*/
static none<ValueType>(this: void): Optional<ValueType>;
}
/**
* The `optional` namespace provides functions to create Optional instances.
* @property some - Creates an Optional containing the provided value.
* @property none - Creates an empty Optional. It is recommended to explicitly specify the type of empty `Optional`s.
*/
export declare const optional: {
some: typeof OptionalImpl.some;
none: typeof OptionalImpl.none;
/**
* Executes a function and wraps the result in an Optional type.
* If the function returns null or undefined, returns a none Optional.
* Otherwise, returns a some Optional with the return value.
*
* @deprecated Use `optional.from_nullable()` for direct nullable values instead.
* In the next major release, `from()` will only accept nullable values directly
* instead of invoking functions, matching the behavior of `from_nullable()`.
*
* @template T - The return type of the function (excluding null/undefined)
* @param fn - A function that may return null or undefined
* @returns An Optional containing either the function's return value or none
*
* @example
* ```typescript
* // DEPRECATED: Current usage
* const userResult = optional.from(() => findUser("123"));
* const element = optional.from(() => document.getElementById('myId'));
*
* // PREFERRED: Use from_nullable with values to avoid the function call overhead
* const userResult = optional.from_nullable(findUser("123"));
* const element = optional.from_nullable(document.getElementById('myId'));
*
* // FUTURE: In next major release, from_nullable() will be renamed to from()
* const userResult = optional.from(findUser("123")); // Will be the new behavior
* ```
*/
from: <T>(fn: () => T | null | undefined) => Optional<NonNullable<T>>;
/**
* Creates an Optional from a value that may be null or undefined.
* If the value is null or undefined, returns a none Optional.
* Otherwise, returns a some Optional with the value.
*
* @template T - The type of the value (must include null and/or undefined)
* @param value - A value that may be null or undefined
* @returns An Optional containing either the value or none
*
* @example
* ```typescript
* // Working with nullable values
* function findUser(id: string): User | null {
* return users.find(u => u.id === id) || null;
* }
*
* const user = optional.from_nullable(findUser("123"));
* if (user.is_some()) {
* console.log(user.value.name);
* }
*
* // Direct nullable value conversion
* const maybeNumber: number | undefined = parseFloat(input);
* const result = optional.from_nullable(maybeNumber);
* ```
*/
from_nullable: <T>(value: Nullable<T>) => Optional<NonNullable<T>>;
/**
* Executes an async function and wraps the result in a Promise<Optional>.
* If the function resolves to null or undefined, returns a none Optional.
* Otherwise, returns a some Optional with the resolved value.
*
* @template T - The resolved type of the async function (excluding null/undefined)
* @param fn - An async function that may resolve to null or undefined
* @returns A Promise resolving to an Optional containing either the resolved value or none
*
* @example
* ```typescript
* // Working with async functions that might return null
* async function fetchUser(id: string): Promise<User | null> {
* const response = await fetch(`/api/users/${id}`);
* return response.ok ? response.json() : null;
* }
*
* const userResult = await optional.from_async(() => fetchUser("123"));
* if (userResult.is_some()) {
* console.log(userResult.value.name);
* } else {
* console.log("User not found");
* }
*
* // Converting Promise-based APIs that might return null
* const userData = await optional.from_async(async () => {
* const response = await fetch('/api/data');
* return response.ok ? await response.json() : null;
* });
* ```
*/
from_async: <T>(fn: () => Promise<T | null | undefined>) => Promise<Optional<NonNullable<T>>>;
};
export default optional;
//# sourceMappingURL=optional.d.ts.map