semantic-release npm latest version
typescript-monads helps you write safer code by using abstractions over messy control flow and state.
You can use this library in the browser, node, or a bundler
npm install typescript-monads
<head> <script src="https://unpkg.com/typescript-monads"></script> <!-- or use a specific version to avoid a http redirect --> <script src="https://unpkg.com/typescript-monads@5.3.0/index.min.js"></script> </head>
var someRemoteValue; typescriptMonads.maybe(someRemoteValue).tapSome(console.log)
The Maybe monad represents values that may or may not exist. It's a safe way to handle potentially null or undefined values without resorting to null checks throughout your code.
import { maybe, none } from 'typescript-monads' // Creating Maybe instances const someValue = maybe(42) // Maybe with a value const noValue = maybe(null) // Maybe with no value (None) const alsoNoValue = none<number>() // Explicitly create a None // Safe value access someValue.valueOr(0) // 42 noValue.valueOr(0) // 0 someValue.valueOrCompute(() => expensiveCalculation()) // 42 (computation skipped) noValue.valueOrCompute(() => expensiveCalculation()) // result of computation // Conditional execution with pattern matching someValue.match({ some: val => console.log(`Got a value: ${val}`), none: () => console.log('No value present') }) // logs: "Got a value: 42" // Side effects with tap someValue.tap({ some: val => console.log(`Got ${val}`), none: () => console.log('Nothing to see') }) // Conditional side effects someValue.tapSome(val => console.log(`Got ${val}`)) noValue.tapNone(() => console.log('Nothing here')) // Chaining operations (only executed for Some values) maybe(5) .map(n => n * 2) // maybe(10) .filter(n => n > 5) // maybe(10) .flatMap(n => maybe(n + 1)) // maybe(11) // Transforming to other types maybe(5).toResult('No value found') // Ok(5) maybe(null).toResult('No value found') // Fail('No value found') // Working with RxJS (with rxjs optional dependency) import { maybeToObservable } from 'typescript-monads' maybeToObservable(maybe(5)) // Observable that emits 5 and completes maybeToObservable(none()) // Observable that completes without emitting
The List monad is a lazily evaluated collection with chainable operations. It provides many of the common list processing operations found in functional programming languages.
import { List } from 'typescript-monads' // Creating Lists const fromValues = List.of(1, 2, 3, 4, 5) const fromIterable = List.from([1, 2, 3, 4, 5]) const numbersFromRange = List.range(1, 10) // 1 to 10 const infiniteNumbers = List.integers() // All integers (use with take!) const empty = List.empty<number>() // Basic operations fromValues.toArray() // [1, 2, 3, 4, 5] fromValues.headOrUndefined() // 1 fromValues.headOr(0) // 1 empty.headOr(0) // 0 // Transformations fromValues .map(n => n * 2) // [2, 4, 6, 8, 10] .filter(n => n > 5) // [6, 8, 10] .take(2) // [6, 8] .drop(1) // [8] // LINQ-style operations fromValues.sum() // 15 fromValues.all(n => n > 0) // true fromValues.any(n => n % 2 === 0) // true fromValues.where(n => n % 2 === 0) // [2, 4] // Conversion fromValues.toDictionary() // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 } const users = List.of( { id: 'a', name: 'Alice' }, { id: 'b', name: 'Bob' } ) users.toDictionary('id') // { 'a': { id: 'a', name: 'Alice' }, 'b': { id: 'b', name: 'Bob' } }
The Either monad represents values that can be one of two possible types. It's often used to represent a value that can be either a success (Right) or a failure (Left).
import { either } from 'typescript-monads' // Creating Either instances const rightValue = either<string, number>(undefined, 42) // Right value const leftValue = either<string, number>('error', undefined) // Left value // Checking which side is present rightValue.isRight() // true rightValue.isLeft() // false leftValue.isRight() // false leftValue.isLeft() // true // Pattern matching rightValue.match({ right: val => `Success: ${val}`, left: err => `Error: ${err}` }) // "Success: 42" // Side effects with tap rightValue.tap({ right: val => console.log(`Got right: ${val}`), left: err => console.log(`Got left: ${err}`) }) // Transformation (only applies to Right values) rightValue.map(n => n * 2) // Either with Right(84) leftValue.map(n => n * 2) // Either with Left('error') unchanged // Chaining (flatMap only applies to Right values) rightValue.flatMap(n => either(undefined, n + 10)) // Either with Right(52) leftValue.flatMap(n => either(undefined, n + 10)) // Either with Left('error') unchanged
The Reader monad represents a computation that depends on some external configuration or environment. It's useful for dependency injection.
import { reader } from 'typescript-monads' // Define a configuration type interface Config { apiUrl: string apiKey: string } // Create readers that depend on this configuration const getApiUrl = reader<Config, string>(config => config.apiUrl) const getApiKey = reader<Config, string>(config => config.apiKey) // Compose readers to build more complex operations const getAuthHeader = getApiKey.map(key => `Bearer ${key}`) // Create a reader for making an API request const fetchData = reader<Config, Promise<Response>>(config => { return fetch(`${config.apiUrl}/data`, { headers: { 'Authorization': `Bearer ${config.apiKey}` } }) }) // Execute the reader with a specific configuration const config: Config = { apiUrl: 'https://api.example.com', apiKey: 'secret-key-123' } const apiUrl = getApiUrl.run(config) // 'https://api.example.com' const authHeader = getAuthHeader.run(config) // 'Bearer secret-key-123' fetchData.run(config).then(response => { // Handle API response })
The Result monad represents operations that can either succeed with a value or fail with an error. It's similar to Either but with more specific semantics for success/failure.
import { ok, fail, catchResult } from 'typescript-monads' // Creating Result instances const success = ok<number, string>(42) // Success with value 42 const failure = fail<number, string>('error') // Failure with error 'error' // Safely catching exceptions const result = catchResult<number, Error>( () => { // Code that might throw if (Math.random() > 0.5) { throw new Error('Failed') } return 42 } ) // Checking result type success.isOk() // true success.isFail() // false failure.isOk() // false failure.isFail() // true // Extracting values safely success.unwrapOr(0) // 42 failure.unwrapOr(0) // 0 success.unwrap() // 42 // failure.unwrap() // Throws error // Convert to Maybe success.maybeOk() // Maybe with Some(42) failure.maybeOk() // Maybe with None success.maybeFail() // Maybe with None failure.maybeFail() // Maybe with Some('error') // Pattern matching success.match({ ok: val => `Success: ${val}`, fail: err => `Error: ${err}` }) // "Success: 42" // Transformations success.map(n => n * 2) // Ok(84) failure.map(n => n * 2) // Fail('error') unchanged failure.mapFail(e => `${e}!`) // Fail('error!') // Chaining success.flatMap(n => ok(n + 10)) // Ok(52) failure.flatMap(n => ok(n + 10)) // Fail('error') unchanged // Side effects success.tap({ ok: val => console.log(`Success: ${val}`), fail: err => console.log(`Error: ${err}`) }) success.tapOk(val => console.log(`Success: ${val}`)) failure.tapFail(err => console.log(`Error: ${err}`)) // Chaining with side effects success .tapOkThru(val => console.log(`Success: ${val}`)) .map(n => n * 2) // Converting to promises import { resultToPromise } from 'typescript-monads' resultToPromise(success) // Promise that resolves to 42 resultToPromise(failure) // Promise that rejects with 'error'
The State monad represents computations that can read and transform state. It's useful for threading state through a series of operations.
import { state } from 'typescript-monads' // Define a state type interface AppState { count: number name: string } // Initial state const initialState: AppState = { count: 0, name: '' } // Create operations that work with the state const incrementCount = state<AppState, number>(s => [{ ...s, count: s.count + 1 }, s.count + 1] ) const setName = (name: string) => state<AppState, void>(s => [{ ...s, name }, undefined] ) const getCount = state<AppState, number>(s => [s, s.count]) // Compose operations const operation = incrementCount .flatMap(() => setName('Alice')) .flatMap(() => getCount) // Run the state operation const result = operation.run(initialState) console.log(result.state) // { count: 1, name: 'Alice' } console.log(result.value) // 1
The Logger monad lets you collect logs during a computation. It's useful for debugging or creating audit trails.
import { logger, tell } from 'typescript-monads' // Create a logger with initial logs and value const initialLogger = logger<string, number>(['Starting process'], 0) // Add logs and transform value const result = initialLogger .flatMap(val => { return logger(['Incrementing value'], val + 1) }) .flatMap(val => { return logger(['Doubling value'], val * 2) }) // Extract all logs and final value result.runUsing(({ logs, value }) => { console.log('Logs:', logs) // ['Starting process', 'Incrementing value', 'Doubling value'] console.log('Final value:', value) // 2 }) // Start with a single log entry const startLogger = tell<string>('Beginning') // Add a value to the logger const withValue = logger.startWith<string, number>('Starting with value:', 42) // Extract results using pattern matching const output = result.runUsing(({ logs, value }) => { return { history: logs.join('\n'), result: value } })
This library offers RxJS integration with the Maybe and Result monads:
import { maybeToObservable } from 'typescript-monads' import { resultToObservable } from 'typescript-monads' import { of } from 'rxjs' import { flatMap } from 'rxjs/operators' // Convert Maybe to Observable of(maybe(5)).pipe( flatMap(maybeToObservable) ).subscribe(val => console.log(val)) // logs 5 and completes // Convert Result to Observable of(ok(42)).pipe( flatMap(resultToObservable) ).subscribe( val => console.log(`Success: ${val}`), err => console.error(`Error: ${err}`) )
You can convert Result monads to promises:
import { resultToPromise } from 'typescript-monads' // Convert Result to Promise resultToPromise(ok(42)) .then(val => console.log(`Success: ${val}`)) .catch(err => console.error(`Error: ${err}`)) // Catch exceptions and convert to Result async function fetchData() { try { const response = await fetch('https://api.example.com/data') if (!response.ok) { throw new Error(`HTTP error ${response.status}`) } return ok(await response.json()) } catch (error) { return fail(error) } }
Contributions are welcome! Please feel free to submit a Pull Request.
MIT