UNPKG

typesafe-ts

Version:

TypeScript utilities for type-safe error handling and optional values

213 lines (210 loc) 8.76 kB
/* Copyright (c) 2025 Allan Deutsch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * The `none` value is used to represent an empty value in an Optional. */ const none_value = Symbol("None"); /** * 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. */ class OptionalImpl { value; constructor(value) { this.value = value; } is_some() { return this.value !== none_value; } value_or(value_if_empty) { return this.is_some() ? this.value : value_if_empty; } map(mapper_fn) { if (this.is_some()) { return OptionalImpl.some(mapper_fn(this.value)); } return this; } or_else(fn) { if (this.is_some()) return this; return fn(); } and_then(fn) { if (this.is_some()) { return fn(this.value); } return this; } *[Symbol.iterator]() { if (this.is_some()) { yield this.value; } } get [Symbol.toStringTag]() { return "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(value) { return new OptionalImpl(value); } /** * 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() { return new OptionalImpl(none_value); } } Object.freeze(OptionalImpl); /** * 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 const optional = { some: OptionalImpl.some, none: 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: (fn) => { const result = fn(); if (result === null || result === undefined) { return OptionalImpl.none(); } return OptionalImpl.some(result); }, /** * 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: (value) => { if (value === null || value === undefined) { return OptionalImpl.none(); } return OptionalImpl.some(value); }, /** * 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: async (fn) => { const result = await fn(); if (result === null || result === undefined) { return OptionalImpl.none(); } return OptionalImpl.some(result); }, }; Object.freeze(optional); export default optional; //# sourceMappingURL=optional.js.map