├── .gitattributes ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── .github └── workflows │ ├── publish.yml │ └── pr.yml ├── deno.lock ├── unwrap.test.ts ├── mod.ts ├── deno.json ├── LICENSE ├── option.test.ts ├── unwrap.ts ├── map.test.ts ├── assert.test.ts ├── from.test.ts ├── from.ts ├── map.ts ├── option.ts ├── assert.ts └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "denoland.vscode-deno" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "[typescript]": { 4 | "editor.defaultFormatter": "denoland.vscode-deno", 5 | "editor.formatOnSave": true 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | !.vscode/settings.json 3 | !.vscode/tasks.json 4 | !.vscode/launch.json 5 | !.vscode/extensions.json 6 | !.vscode/*.code-snippets 7 | 8 | # Local History for Visual Studio Code 9 | .history/ 10 | 11 | # Built Visual Studio Code Extensions 12 | *.vsix 13 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | id-token: write # The OIDC ID token is used for authentication with JSR. 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: npx jsr publish -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR Validation 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | run-test: 10 | name: Test using deno 11 | runs-on: ubuntu-latest 12 | 13 | permissions: 14 | contents: read 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Install Deno 21 | uses: denoland/setup-deno@v1 22 | with: 23 | deno-version: v1.44 24 | 25 | - name: Run tests 26 | run: deno test 27 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "packages": { 4 | "specifiers": { 5 | "jsr:@std/assert@^0.226.0": "jsr:@std/assert@0.226.0", 6 | "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0" 7 | }, 8 | "jsr": { 9 | "@std/assert@0.226.0": { 10 | "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", 11 | "dependencies": [ 12 | "jsr:@std/internal@^1.0.0" 13 | ] 14 | }, 15 | "@std/internal@1.0.0": { 16 | "integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" 17 | } 18 | } 19 | }, 20 | "remote": {}, 21 | "workspace": { 22 | "dependencies": [ 23 | "jsr:@std/assert@^0.226.0" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /unwrap.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from '@std/assert'; 2 | 3 | import { none, some } from './option.ts'; 4 | import { unwrap } from './unwrap.ts'; 5 | 6 | Deno.test('.unwrap', async (t) => { 7 | await t.step('unwraps a Some value', () => { 8 | const option = some(42); 9 | const value = unwrap(option, 0); 10 | assertEquals(value, 42); 11 | 12 | const value2 = unwrap(option, () => 0); 13 | assertEquals(value2, 42); 14 | }); 15 | 16 | await t.step('unwraps a None value', () => { 17 | const option = none(); 18 | const value = unwrap(option, 0); 19 | assertEquals(value, 0); 20 | 21 | const value2 = unwrap(option, () => 0); 22 | assertEquals(value2, 0); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | * 4 | * Exports all the modules in the `nnou/option` library. 5 | * 6 | * This module is used to import all the modules in the `nnou/option` library. 7 | * 8 | * @example Using this module to safely use a value that may or may not be present. 9 | * 10 | * ```ts 11 | * import { maybe } from '@nnou/option'; 12 | * 13 | * function logValue(value: number | null | undefined) { 14 | * const result = maybe(value); 15 | * if (result.hasValue) { 16 | * console.log(result.value); 17 | * } else { 18 | * console.log('No value'); 19 | * } 20 | * } 21 | * ``` 22 | */ 23 | 24 | export * from './assert.ts'; 25 | export * from './option.ts'; 26 | export * from './unwrap.ts'; 27 | export * from './map.ts'; 28 | export * from './from.ts'; 29 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nnou/option", 3 | "version": "1.4.0", 4 | "tasks": { 5 | "dev": "deno run --watch main.ts" 6 | }, 7 | "fmt": { 8 | "indentWidth": 4, 9 | "lineWidth": 125, 10 | "semiColons": true, 11 | "singleQuote": true, 12 | "useTabs": false 13 | }, 14 | "imports": { 15 | "@std/assert": "jsr:@std/assert@^0.226.0" 16 | }, 17 | "description": "A simple option type for typescript", 18 | "exports": { 19 | ".": "./mod.ts", 20 | "./core": "./option.ts", 21 | "./assert": "./assert.ts", 22 | "./map": "./map.ts", 23 | "./unwrap": "./unwrap.ts", 24 | "./from": "./from.ts" 25 | }, 26 | "publish": { 27 | "include": [ 28 | "LICENSE", 29 | "README.md", 30 | "**/*.ts" 31 | ], 32 | "exclude": [ 33 | "**/*.test.ts" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 David Pires 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /option.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from '@std/assert'; 2 | 3 | import { maybe, none, some } from './option.ts'; 4 | import { assertNone, assertSome } from './assert.ts'; 5 | 6 | Deno.test('.none', async (t) => { 7 | await t.step('creates a None value', () => { 8 | const result = none(); 9 | assertNone(result); 10 | }); 11 | }); 12 | 13 | Deno.test('.some', async (t) => { 14 | await t.step('creates a Some value', () => { 15 | const result = some(42); 16 | assertSome(result); 17 | }); 18 | 19 | await t.step('we can read value', () => { 20 | const result = some('Hello, World!'); 21 | assertSome(result); 22 | assertEquals(result.value, 'Hello, World!'); 23 | }); 24 | }); 25 | 26 | Deno.test('.maybe', async (t) => { 27 | await t.step('when value is null, returns none', () => { 28 | const result = maybe(null); 29 | assertNone(result); 30 | }); 31 | 32 | await t.step('when value is undefined, returns none', () => { 33 | const result = maybe(undefined); 34 | assertNone(result); 35 | }); 36 | 37 | await t.step('when value is not null or undefined, returns some', () => { 38 | const result = maybe(42); 39 | assertSome(result); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /unwrap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module unwrap 3 | * 4 | * Helper function to unwrap an Option value, 5 | */ 6 | 7 | import type { Option } from './option.ts'; 8 | 9 | /** 10 | * Unwraps an Option value, returning the contained value or a default. 11 | * 12 | * @example using unwrap 13 | * ```ts 14 | * import { Option, unwrap } from '@nnou/option'; 15 | * 16 | * const option: Option = { hasValue: true, value: 42 }; 17 | * const value = unwrap(option, 0); // value is 42 18 | * ``` 19 | * 20 | * @param option - The Option to unwrap. 21 | * @param or - The default value to return if the Option is empty. 22 | * @returns The contained value or a default. 23 | */ 24 | export function unwrap(option: Option, or: NonNullable): T; 25 | /** 26 | * Unwraps an Option value, returning the contained value or the result of a function. 27 | * 28 | * @example using unwrap 29 | * ```ts 30 | * import { Option, unwrap } from '@nnou/option'; 31 | * 32 | * const option: Option = { hasValue: true, value: 42 }; 33 | * const value = unwrap(option, () => 0); // value is 42 34 | * ``` 35 | * 36 | * @param option - The Option to unwrap. 37 | * @param or - The function to call to get the default value if the Option is empty. 38 | * @returns The contained value or the result of the function. 39 | */ 40 | export function unwrap(option: Option, or: () => NonNullable): T; 41 | export function unwrap(option: Option, or: (() => NonNullable) | NonNullable): T { 42 | if (option.hasValue) { 43 | return option.value; 44 | } 45 | 46 | return or instanceof Function ? or() : or; 47 | } 48 | -------------------------------------------------------------------------------- /map.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from '@std/assert'; 2 | 3 | import { maybe, none, some } from './option.ts'; 4 | import { flatten, map, mapOr } from './map.ts'; 5 | 6 | Deno.test('.map', async (t) => { 7 | await t.step('maps a Some value', () => { 8 | const option = some(42); 9 | const mapped = map(option, (value) => value * 2); 10 | assertEquals(mapped, some(84)); 11 | }); 12 | 13 | await t.step('maps a None value', () => { 14 | const option = none(); 15 | const mapped = map(option, (value) => value * 2); 16 | assertEquals(mapped, none()); 17 | }); 18 | 19 | await t.step('maps a Some value to a None value', () => { 20 | const option = some(42); 21 | const mapped = map(option, () => none()); 22 | assertEquals(mapped, none()); 23 | }); 24 | 25 | await t.step('maps a Some value to a Some value', () => { 26 | const option = some(42); 27 | const mapped = map(option, (value) => maybe(value * 2)); 28 | assertEquals(mapped, some(84)); 29 | }); 30 | }); 31 | 32 | Deno.test('.mapOr', async (t) => { 33 | await t.step('maps a Some value', () => { 34 | const option = some(42); 35 | const mapped = mapOr(option, (value) => value * 2, () => 0); 36 | assertEquals(mapped, 84); 37 | }); 38 | 39 | await t.step('maps a None value', () => { 40 | const option = maybe(); 41 | const mapped = mapOr(option, (value) => value * 2, () => 0); 42 | assertEquals(mapped, 0); 43 | }); 44 | }); 45 | 46 | Deno.test('.flatten', async (t) => { 47 | await t.step('flattens a Some value', () => { 48 | const option = some(some(42)); 49 | const flattened = flatten(option); 50 | assertEquals(flattened, some(42)); 51 | }); 52 | 53 | await t.step('flattens a None value', () => { 54 | const option = none(); 55 | const flattened = flatten(option); 56 | assertEquals(flattened, none()); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /assert.test.ts: -------------------------------------------------------------------------------- 1 | import { assertNone, assertSome, assertSomeValue, isOption } from './assert.ts'; 2 | 3 | import { assertEquals, assertThrows } from '@std/assert'; 4 | 5 | Deno.test('.assertSome', async (t) => { 6 | await t.step('throws when is None', () => { 7 | assertThrows(() => { 8 | assertSome({ hasValue: false }); 9 | }); 10 | }); 11 | 12 | await t.step('does not throw when is Some', () => { 13 | assertSome({ hasValue: true, value: 42 }); 14 | }); 15 | }); 16 | 17 | Deno.test('.assertNone', async (t) => { 18 | await t.step('throws when is Some', () => { 19 | assertThrows(() => { 20 | assertNone({ hasValue: true, value: 42 }); 21 | }); 22 | }); 23 | 24 | await t.step('does not throw when is None', () => { 25 | assertNone({ hasValue: false }); 26 | }); 27 | }); 28 | 29 | Deno.test('.assertSomeValue', async (t) => { 30 | await t.step('throws when is None', () => { 31 | assertThrows(() => { 32 | assertSomeValue({ hasValue: false }, 42); 33 | }); 34 | }); 35 | 36 | await t.step('throws when value is not equal', () => { 37 | assertThrows(() => { 38 | assertSomeValue({ hasValue: true, value: 42 }, 0); 39 | }); 40 | }); 41 | 42 | await t.step('does not throw when value is equal', () => { 43 | assertSomeValue({ hasValue: true, value: 42 }, 42); 44 | }); 45 | }); 46 | 47 | Deno.test('.isOption', async (t) => { 48 | await t.step('returns false when is not an Option', () => { 49 | const result: unknown = 42; 50 | const isOptionResult = isOption(result); 51 | assertEquals(isOptionResult, false); 52 | }); 53 | 54 | await t.step('returns true when is a Some', () => { 55 | const result: unknown = { hasValue: true, value: 42 }; 56 | const isOptionResult = isOption(result); 57 | assertEquals(isOptionResult, true); 58 | }); 59 | 60 | await t.step('returns true when is a None', () => { 61 | const result: unknown = { hasValue: false }; 62 | const isOptionResult = isOption(result); 63 | assertEquals(isOptionResult, true); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /from.test.ts: -------------------------------------------------------------------------------- 1 | import { assertNone, assertSomeValue } from './assert.ts'; 2 | import { assertRejects } from '@std/assert'; 3 | 4 | import { fromAsyncResult, fromPromise, fromResult } from './from.ts'; 5 | 6 | Deno.test('.fromPromise', async (t) => { 7 | await t.step('converts a promise to an option', async () => { 8 | const promise = Promise.resolve(42); 9 | const option = await fromPromise(promise); 10 | 11 | assertSomeValue(option, 42); 12 | }); 13 | 14 | await t.step('converts a promise to an option with error handling', async () => { 15 | const promise = Promise.reject(new Error()); 16 | const option = await fromPromise(promise); 17 | 18 | assertNone(option); 19 | }); 20 | 21 | await t.step('converts a promise to an option with error handling disabled', async () => { 22 | const promise = Promise.reject(new Error()); 23 | await assertRejects(() => fromPromise(promise, false)); 24 | }); 25 | }); 26 | 27 | Deno.test('.fromResult', async (t) => { 28 | await t.step('converts a result to an option', () => { 29 | const result = { ok: true, value: 42 }; 30 | const option = fromResult(result); 31 | 32 | assertSomeValue(option, 42); 33 | }); 34 | 35 | await t.step('converts a result to an option', () => { 36 | const result = { ok: false, error: 'My error' } as const; 37 | const option = fromResult(result); 38 | 39 | assertNone(option); 40 | }); 41 | }); 42 | 43 | Deno.test('.fromAsyncResult', async (t) => { 44 | await t.step('converts an async result to an option', async () => { 45 | const result = Promise.resolve({ ok: true, value: 42 }); 46 | const option = await fromAsyncResult(result); 47 | 48 | assertSomeValue(option, 42); 49 | }); 50 | 51 | await t.step('converts an async result to an option with error handling', async () => { 52 | const result = Promise.resolve({ ok: false, error: 'My error' } as const); 53 | const option = await fromAsyncResult(result); 54 | 55 | assertNone(option); 56 | }); 57 | 58 | await t.step('converts an async result to an option with error handling disabled', async () => { 59 | const result = Promise.reject(new Error()); 60 | await assertRejects(() => fromAsyncResult(result, false)); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /from.ts: -------------------------------------------------------------------------------- 1 | import { maybe, none, type Option, type OptionAsync } from './option.ts'; 2 | 3 | /** 4 | * Resolve a promise and return a new `Option` instance. 5 | * 6 | * @example Resolving a promise. 7 | * 8 | * ```ts 9 | * const result: Promise = Promise.resolve(42); 10 | * const option: Option = await fromPromise(result); 11 | * 12 | * if (option.hasValue) { 13 | * console.log(option.value); // 42 14 | * } else { 15 | * console.log('No value'); 16 | * } 17 | * ``` 18 | * 19 | * @param promise - The promise to resolve. 20 | * @param catchError - Whether to catch errors or not. If `true`, the function will return `None` if the promise rejects. If `false`, the function will throw the error. 21 | * @returns 22 | */ 23 | export async function fromPromise( 24 | promise: Promise, 25 | catchError: boolean = true, 26 | ): OptionAsync { 27 | try { 28 | const value = await promise; 29 | return maybe(value); 30 | } catch (error) { 31 | if (catchError) { 32 | return none(); 33 | } 34 | 35 | throw error; 36 | } 37 | } 38 | 39 | /** 40 | * Convert a result in format { ok: boolean, value: T } to an option. 41 | * 42 | * @example Converting a result to an option. 43 | * 44 | * ```ts 45 | * const result = { ok: true, value: 42 }; 46 | * const option: Option = fromResult(result); 47 | * 48 | * if (option.hasValue) { 49 | * console.log(option.value); // 42 50 | * } else { 51 | * console.log('No value'); 52 | * } 53 | * ``` 54 | * 55 | * @param result - The result to convert. 56 | * @returns Some if the result is ok, None otherwise. 57 | */ 58 | export function fromResult( 59 | result: { ok: false } | { ok: true; value: T }, 60 | ): Option { 61 | return result.ok ? maybe(result.value) : none(); 62 | } 63 | 64 | /** 65 | * Convert an async result in format { ok: boolean, value: T } to an option. 66 | * 67 | * @example Converting a result to an option. 68 | * 69 | * ```ts 70 | * const result = Promise.resolve({ ok: true, value: 42 }); 71 | * const option: Option = await fromAsyncResult(result); 72 | * 73 | * if (option.hasValue) { 74 | * console.log(option.value); // 42 75 | * } else { 76 | * console.log('No value'); 77 | * } 78 | * ``` 79 | * 80 | * @param result - The async result to convert. 81 | * @param catchError - Whether to catch errors or not. If `true`, the function will return `None` if the promise rejects. If `false`, the function will throw the error. 82 | * @returns Some if the result is ok, None otherwise. 83 | */ 84 | export async function fromAsyncResult( 85 | result: Promise<{ ok: false } | { ok: true; value: T }>, 86 | catchError: boolean = true, 87 | ): OptionAsync { 88 | try { 89 | const value = await result; 90 | return fromResult(value); 91 | } catch (error) { 92 | if (catchError) { 93 | return none(); 94 | } 95 | 96 | throw error; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /map.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module map 3 | * 4 | * Helper function to map an Option value to another value. 5 | */ 6 | 7 | import { maybe, none, type Option } from './option.ts'; 8 | import { isOption } from './assert.ts'; 9 | 10 | /** 11 | * Maps an Option value to another Option value. If the Option is empty, a None 12 | * 13 | * @example using map 14 | * ```ts 15 | * import { Option, map, maybe } from '@nnou/option'; 16 | * 17 | * const option: Option = maybe(42); 18 | * const mapped = map(option, (value) => maybe(value * 2)); // mapped is Some(84) 19 | * ``` 20 | * 21 | * @param option - The Option to map. 22 | * @param fn - The function to call to map the value. 23 | */ 24 | export function map(option: Option, fn: (value: T) => Option): Option; 25 | /** 26 | * Maps an Option value to another value. If the Option is empty, a None 27 | * 28 | * @example using map 29 | * ```ts 30 | * import { Option, map, maybe } from '@nnou/option'; 31 | * 32 | * const option: Option = maybe(42); 33 | * const mapped = map(option, (value) => value * 2); // mapped is 84 34 | * ``` 35 | * 36 | * @param option - The Option to map. 37 | * @param fn - The function to call to map the value. 38 | */ 39 | export function map( 40 | option: Option, 41 | fn: (value: T) => NonNullable, 42 | ): Option; 43 | export function map( 44 | option: Option, 45 | fn: (value: unknown) => NonNullable | Option, 46 | ): Option { 47 | if (!option.hasValue) { 48 | return none(); 49 | } 50 | 51 | const v = fn(option.value); 52 | return isOption(v) ? v : maybe(v); 53 | } 54 | 55 | /** 56 | * Maps an Option value to another value. If the Option is empty, a None 57 | * 58 | * @example using mapOr 59 | * ```ts 60 | * import { Option, mapOr, maybe } from '@nnou/option'; 61 | * 62 | * const option: Option = maybe(42); 63 | * const mapped = mapOr(option, (value) => value * 2, () => 0); // mapped is 84 64 | * ``` 65 | * 66 | * @param option - The Option to map. 67 | * @param some - The function to call to map the value. 68 | * @param none - The function to call if the Option is empty. 69 | */ 70 | export function mapOr( 71 | option: Option, 72 | some: (value: T) => NonNullable, 73 | none: () => NonNullable, 74 | ): Z { 75 | return option.hasValue ? some(option.value) : none(); 76 | } 77 | 78 | /** 79 | * Flattens an Option> to an Option. 80 | * 81 | * @example using flatten 82 | * ```ts 83 | * import { Option, flatten, maybe } from '@nnou/option'; 84 | * 85 | * const option: Option> = maybe(maybe(42)); 86 | * const flattened = flatten(option); // flattened is Some(42) 87 | * ``` 88 | * 89 | * @param option - The Option> to flatten. 90 | * @returns Flattened Option. 91 | */ 92 | export function flatten(option: Option>): Option { 93 | return option.hasValue ? option.value : none(); 94 | } 95 | -------------------------------------------------------------------------------- /option.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module core 3 | * 4 | * Contains the core types and functions of the `nnou/option` library. 5 | * 6 | * @example Using the `core` module to create and use `Option` instances. 7 | * 8 | * ```ts 9 | * import { maybe, none, some } from '@nnou/option/core'; 10 | * 11 | * const result = maybe(42); 12 | * 13 | * if (result.hasValue) { 14 | * console.log(result.value); // 42 15 | * } else { 16 | * console.log('No value'); 17 | * } 18 | * 19 | * const result = none(); // No value 20 | * const result = some(42); // 42 21 | * ``` 22 | */ 23 | 24 | /** 25 | * Represents a value that is present. 26 | */ 27 | export type Some = { 28 | hasValue: true; 29 | value: NonNullable; 30 | }; 31 | 32 | /** 33 | * Represents a value that is not be present. 34 | */ 35 | export type None = { 36 | hasValue: false; 37 | }; 38 | 39 | /** 40 | * Represents a value that may or may not be present. It is used to avoid `null` or `undefined` values. 41 | * 42 | * @example Using `Option` to represent a value that may not be present. 43 | * 44 | * ```ts 45 | * const result: Option = maybe(42); 46 | * 47 | * if (result.hasValue) { 48 | * console.log(result.value); // 42 49 | * } else { 50 | * console.log('No value'); 51 | * } 52 | * ``` 53 | */ 54 | export type Option = Some> | None; 55 | 56 | /** Alias for Promise> */ 57 | export type OptionAsync = Promise>; 58 | 59 | /** 60 | * Creates a new `Some` instance. 61 | * 62 | * @example Creating a new `Some` instance. 63 | * 64 | * ```ts 65 | * const result: Option = some(42); 66 | * 67 | * if (result.hasValue) { 68 | * console.log(result.value); // 42 69 | * } else { 70 | * console.log('No value'); 71 | * } 72 | * ``` 73 | * 74 | * @param value The value to wrap. 75 | * @returns A new `Some` instance. 76 | */ 77 | export function some(value: NonNullable): Some> { 78 | return { 79 | hasValue: true, 80 | value, 81 | }; 82 | } 83 | 84 | /** 85 | * Creates a new `None` instance. 86 | * 87 | * @example Creating a new `None` instance. 88 | * 89 | * ```ts 90 | * const result: Option = none(); 91 | * 92 | * if (result.hasValue) { 93 | * console.log(result.value); 94 | * } else { 95 | * console.log('No value'); 96 | * } 97 | * ``` 98 | * 99 | * @returns A new `None` instance. 100 | */ 101 | export function none(): None { 102 | return { hasValue: false }; 103 | } 104 | 105 | /** 106 | * Creates a new `Option` instance. 107 | * 108 | * @example Creating a new `Option` instance. 109 | * 110 | * ```ts 111 | * const result: Option = maybe(42); 112 | * 113 | * if (result.hasValue) { 114 | * console.log(result.value); // 42 115 | * } else { 116 | * console.log('No value'); 117 | * } 118 | * ``` 119 | * 120 | * @param value The value to wrap. 121 | * @returns A new `Option` instance. 122 | */ 123 | export function maybe(value?: T): Option> { 124 | if (typeof value === 'undefined' || value === null) { 125 | return none(); 126 | } 127 | 128 | return some(value); 129 | } 130 | -------------------------------------------------------------------------------- /assert.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module assert 3 | * 4 | * This module provides functions to assert the type of `Option` instances. 5 | * 6 | * @example Using the `assert` module to assert the type of `Option` instances. 7 | * 8 | * ```ts 9 | * import { assertSome, assertNone, assertSomeValue } from '@nnou/option/assert'; 10 | * 11 | * const result = { hasValue: true, value: 42 }; 12 | * 13 | * assertSome(result); // No error 14 | * console.log(result.value); // 42 15 | * 16 | * assertNone(result); // Error: Expected no value to be present 17 | * 18 | * assertSomeValue(result, 42); // No error 19 | * assertSomeValue(result, 43); // Error: Expected value to be 43, but got 42 20 | * ``` 21 | */ 22 | 23 | import type { None, Option, Some } from './option.ts'; 24 | 25 | /** 26 | * Assert that the given `Option` instance is a `Some` instance. 27 | * If the given `Option` instance is a `None` instance, an error is thrown. 28 | * 29 | * @example Asserting that an `Option` instance is a `Some` instance. 30 | * 31 | * ```ts 32 | * const result: Option = maybe(42); 33 | * 34 | * assertSome(result); 35 | * console.log(result.value); // 42 36 | * ``` 37 | * 38 | * @param option The `Option` instance to assert. 39 | */ 40 | export function assertSome(option: Option): asserts option is Some { 41 | if (!option.hasValue) { 42 | throw new Error('Expected a value to be present'); 43 | } 44 | } 45 | 46 | /** 47 | * Assert that the given `Option` instance is a `None` instance. 48 | * If the given `Option` instance is a `Some` instance, an error is thrown. 49 | * 50 | * @example Asserting that an `Option` instance is a `None` instance. 51 | * 52 | * ```ts 53 | * const result: Option = maybe(); 54 | * 55 | * assertNone(result); 56 | * console.log('No value'); 57 | * ``` 58 | * 59 | * @param option The `Option` instance to assert. 60 | */ 61 | export function assertNone(option: Option): asserts option is None { 62 | if (option.hasValue) { 63 | throw new Error('Expected no value to be present'); 64 | } 65 | } 66 | 67 | /** 68 | * Assert that the given `Option` instance is a `Some` instance and that the value is equal to the given value. 69 | * If the given `Option` instance is a `None` instance, an error is thrown. 70 | * If the value of the `Some` instance is not equal to the given value, an error is thrown. 71 | * 72 | * @example Asserting that an `Option` instance is a `Some` instance and that the value is equal to the given value. 73 | * 74 | * ```ts 75 | * const result: Option = maybe(42); 76 | * 77 | * assertSomeValue(result, 42); 78 | * console.log(result.value); // 42 79 | * ``` 80 | * 81 | * @param option The `Option` instance to assert. 82 | * @param value The value to compare the `Some` value to. 83 | */ 84 | export function assertSomeValue(option: Option, value: NonNullable): asserts option is Some { 85 | assertSome(option); 86 | if (option.value !== value) { 87 | throw new Error(`Expected value to be ${value}, but got ${option.value}`); 88 | } 89 | } 90 | 91 | /** 92 | * Check if the given value is an `Option` instance. 93 | * 94 | * @example Checking if a value is an `Option` instance. 95 | * 96 | * ```ts 97 | * import { isOption } from '@nnou/option'; 98 | * 99 | * const result: unknown = maybe(42); 100 | * 101 | * if (isOption(result)) { 102 | * console.log(result.value); // 42 103 | * } 104 | * ``` 105 | * 106 | * @param value - The value to check. 107 | * @returns - `true` if the value is an `Option` instance, `false` otherwise. 108 | */ 109 | export function isOption(value: unknown): value is Option { 110 | return typeof value === 'object' && 111 | value !== null && 112 | 'hasValue' in value && 113 | typeof value.hasValue === 'boolean'; 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # No Null Or Undefined - Option 2 | 3 | Provides a simple `Option` type that can be used to represent a value that may or may not be present. It is similar to the `Option` type in Rust or the `Optional` type in Java. It's main purpose is to avoid `null` or `undefined` values and the need to check for them. 4 | 5 | Is done in TypeScript, is tree-shakable and has no dependencies. 6 | 7 | ## Quick start 8 | 9 | > see [jsr documentation](https://jsr.io/@nnou/option) for details on how to install 10 | 11 | ```typescript 12 | import { maybe } from '@nnou/option'; 13 | 14 | function handleMessage(message?: string | null): void { 15 | const messageOption = maybe(message); 16 | 17 | if (messageOption.hasValue) { 18 | console.log(messageOption.value); 19 | } else { 20 | console.log('No message'); 21 | } 22 | } 23 | ``` 24 | 25 | ## Core Methods 26 | 27 | ### `some(value: NonNullable): Some` 28 | 29 | Creates an `Option` with a value. 30 | 31 | ```typescript 32 | import { some, Option } from '@nnou/option'; 33 | 34 | const option = some(42); // Some 35 | const option: Option = some(42); 36 | ``` 37 | 38 | ### `none(): None` 39 | 40 | Creates an `Option` without a value. 41 | 42 | ```typescript 43 | import { none, Option } from '@nnou/option'; 44 | 45 | const option = none(); // None 46 | const option: Option = none(); 47 | ``` 48 | 49 | ### `maybe(value?: T): Option` 50 | 51 | Creates an `Option` with a value if it is not `null` or `undefined`. 52 | 53 | ```typescript 54 | import { maybe } from '@nnou/option'; 55 | 56 | const option = maybe(42); // Option 57 | const option = maybe(null); // Option 58 | const option = maybe(); // Option 59 | ``` 60 | 61 | ## Assert Methods 62 | 63 | ### `isOption(value: T): value is Option` 64 | 65 | Checks if a value is an `Option`. 66 | 67 | ```typescript 68 | import { isOption } from '@nnou/option'; 69 | 70 | const value: unknown = ...; 71 | 72 | if (isOption(value)) { 73 | // value is an Option 74 | } 75 | ``` 76 | 77 | ### `assertNone(value: Option): asserts value is None` 78 | 79 | Checks if an `Option` is `None`. 80 | 81 | ```typescript 82 | import { assertNone, none, some } from '@nnou/option'; 83 | 84 | const option = none(); 85 | 86 | assertNone(option); // OK 87 | 88 | const option = some(42); 89 | 90 | assertNone(option); // Error 91 | ``` 92 | 93 | ### `assertSome(value: Option): asserts value is Some` 94 | 95 | Checks if an `Option` is `Some`. 96 | 97 | ```typescript 98 | import { assertSome, none, some } from '@nnou/option'; 99 | 100 | const option = some(42); 101 | 102 | assertSome(option); // OK 103 | 104 | const option = none(); 105 | 106 | assertSome(option); // Error 107 | ``` 108 | 109 | ### `assertSomeValue(option: Option, value: NonNullable): asserts option is Some` 110 | 111 | Checks if an `Option` is `Some` with a specific value. 112 | 113 | ```typescript 114 | import { assertSomeValue, none, some } from '@nnou/option'; 115 | 116 | const option = some(42); 117 | 118 | assertSomeValue(option, 42); // OK 119 | 120 | const option = some(42); 121 | 122 | assertSomeValue(option, 43); // Error 123 | 124 | const option = none(); 125 | 126 | assertSomeValue(option, 42); // Error 127 | ``` 128 | 129 | ## Unwrap Methods 130 | 131 | ### `unwrap(option: Option, or: (() => NonNullable) | NonNullable): T` 132 | 133 | Unwraps an `Option` and returns the value if it is `Some` or a default value if it is `None`. 134 | 135 | ```typescript 136 | import { unwrap, none, some } from '@nnou/option'; 137 | 138 | const option = some(42); 139 | 140 | const value = unwrap(option, 0); // 42 141 | 142 | const option = none(); 143 | 144 | const value = unwrap(option, 0); // 0 145 | const value = unwrap(option, () => 0); // 0 146 | ``` 147 | 148 | ## Map Methods 149 | 150 | ### `map(option: Option, fn: (value: T) => U): Option` 151 | 152 | Maps an `Option` to another `Option`. 153 | 154 | ```typescript 155 | import { map, none, some } from '@nnou/option'; 156 | 157 | const option = some(42); 158 | 159 | const mappedOption = map(option, value => value.toString()); // Option 160 | 161 | const option = none(); 162 | 163 | const mappedOption = map(option, value => value.toString()); // Option 164 | ``` 165 | 166 | ### `mapOr(option: Option, defaultValue: U, fn: (value: T) => U): U` 167 | 168 | Maps an `Option` to a value. 169 | 170 | ```typescript 171 | import { mapOr, none, some } from '@nnou/option'; 172 | 173 | const option = some(42); 174 | 175 | const value = mapOr(option, (v) => v, () => 10); // '42' 176 | 177 | const option = none(); 178 | 179 | const value = mapOr(option, (v) => v, () => 10); // 10 180 | ``` 181 | 182 | ### `flatten(option: Option>): Option` 183 | Flattens an `Option` of an `Option`. 184 | 185 | ```typescript 186 | import { flatten, none, some } from '@nnou/option'; 187 | 188 | const option = some(some(42)); 189 | 190 | const flattenedOption = flatten(option); // Some 191 | 192 | const option = none(); 193 | 194 | const flattenedOption = flatten(option); // None 195 | ``` 196 | 197 | ## From Methods 198 | 199 | ### `fromPromise(promise: Promise, catchError = true): Promise>` 200 | 201 | Creates an `Option` from a `Promise`. 202 | 203 | ```typescript 204 | import { fromPromise } from '@nnou/option'; 205 | 206 | const promise = Promise.resolve(42); 207 | 208 | const option = await fromPromise(promise); // Option 209 | 210 | const promise = Promise.reject(); 211 | 212 | const option = await fromPromise(promise); // None 213 | 214 | const promise = Promise.reject(); 215 | 216 | const option = await fromPromise(promise, false); // Error 217 | ``` 218 | 219 | ### `fromResult(result: { ok: false } | { ok: true; value: T }): Option` 220 | 221 | Creates an `Option` from a `Result`. 222 | 223 | ```typescript 224 | import { fromResult } from '@nnou/option'; 225 | 226 | const result = { ok: true, value: 42 }; 227 | 228 | const option = fromResult(result); // Option 229 | 230 | const result = { ok: false }; 231 | 232 | const option = fromResult(result); // None 233 | ``` 234 | 235 | ### `fromAsyncResult(result: Promise<{ ok: false } | { ok: true; value: T }>, catchError = true): Promise>` 236 | 237 | Creates an `Option` from a `Promise` of a `Result`. 238 | 239 | ```typescript 240 | import { fromAsyncResult } from '@nnou/option'; 241 | 242 | const result = Promise.resolve({ ok: true, value: 42 }); 243 | 244 | const option = await fromAsyncResult(result); // Option 245 | 246 | const result = Promise.resolve({ ok: false }); 247 | 248 | const option = await fromAsyncResult(result); // None 249 | 250 | const result = Promise.reject(); 251 | 252 | const option = await fromAsyncResult(result); // None 253 | 254 | const result = Promise.reject(); 255 | 256 | const option = await fromAsyncResult(result, false); // Error 257 | ``` 258 | --------------------------------------------------------------------------------