@phillui/ts-toolbox
v1.0.1
Published
A comprehensive TypeScript functional programming library featuring monads, monad transformers, optics, and utility functions
Downloads
4
Maintainers
Readme
TS Toolbox
A comprehensive TypeScript functional programming library featuring monads, monad transformers, optics, and utility functions. Fully typed with extensive test coverage.
Features
🎯 Monads
Type-safe monadic containers for functional composition:
- Maybe - Optional value handling (
Some|None) - Result - Error handling without exceptions (
Ok|Err) - Reader - Dependency injection and environment access
- Writer - Log/state accumulation with monoid support
🔄 Monad Transformers
Stack multiple monadic effects:
- MaybeT - Optional values in any monad
- ResultT - Error handling in any monad
- ReaderT - Environment access in any monad
- WriterT - Log accumulation in any monad
🔍 Optics
Functional data manipulation:
- Lens - Composable getters and setters for nested data
- Prism - Optional getters and setters for sum types
🛠️ Utility Functions
Functional programming essentials:
- Function composition (
pipe,compose) - Higher-order functions (
curry,flip,uncurry) - Logic utilities (
all,any,select) - Type guards (
truthy,falsy,isNotNil) - And many more...
Installation
npm install @phillui/ts-toolbox
# or
pnpm add @phillui/ts-toolbox
# or
yarn add @phillui/ts-toolboxQuick Start
Maybe Monad
import { Maybe } from '@phillui/ts-toolbox';
const safeDivide = (a: number, b: number): Maybe<number> =>
b === 0 ? Maybe.none() : Maybe.some(a / b);
const result = safeDivide(10, 2)
.map(x => x * 2)
.flatMap(x => safeDivide(x, 5))
.getOr(0); // 2Result Monad
import { Result } from '@phillui/ts-toolbox';
const parseNumber = (str: string): Result<number, string> => {
const num = parseInt(str);
return isNaN(num) ? Result.err('Not a number') : Result.ok(num);
};
parseNumber('42')
.map(x => x * 2)
.mapError(e => `Error: ${e}`)
.consume(
value => console.log(`Success: ${value}`),
error => console.log(error)
);Reader Monad
import { Reader } from '@phillui/ts-toolbox';
interface Config {
apiUrl: string;
timeout: number;
}
const getApiUrl = Reader.from<Config, string>(config => config.apiUrl);
const getFullUrl = getApiUrl.map(url => `${url}/api/v1`);
const config: Config = { apiUrl: 'https://example.com', timeout: 5000 };
console.log(getFullUrl.run(config)); // https://example.com/api/v1Writer Monad
import { Writer } from '@phillui/ts-toolbox';
const computation = Writer.of<number, string>(5)
.flatMap(x => Writer.tell(`Processing ${x}`, x * 2))
.flatMap(x => Writer.tell(`Doubled to ${x}`, x + 10));
const [result, logs] = computation.run();
console.log(result); // 20
console.log(logs); // ['Processing 5', 'Doubled to 10']Monad Transformers
import { MaybeT, ResultT, ReaderT, Maybe } from '@phillui/ts-toolbox';
// Stack Maybe over Result
const maybeMonad = {
of: <A>(a: A) => Maybe.some(a),
flatMap: <A, B>(m: Maybe<A>, f: (a: A) => Maybe<B>) => m.flatMap(f),
map: <A, B>(m: Maybe<A>, f: (a: A) => B) => m.map(f)
};
const RT = ResultT(maybeMonad);
const result = RT.of(42)
.map(x => x * 2)
.run(); // Maybe<Result<number, E>>Lenses
import { Lens } from '@phillui/ts-toolbox';
interface User {
name: string;
address: { city: string };
}
const cityLens = Lens.from<User, string>(
user => user.address.city,
city => user => ({ ...user, address: { ...user.address, city } })
);
const user = { name: 'Alice', address: { city: 'NYC' } };
const updated = cityLens.set('LA')(user);
console.log(updated.address.city); // LAUtility Functions
import { pipe, compose, prop, select } from 'ts-toolbox';
// Function composition
const result = pipe(
5,
x => x * 2,
x => x + 3,
x => x.toString()
); // "13"
// Property access
const getName = prop<{ name: string }>('name');
console.log(getName({ name: 'Alice' })); // Alice
// Conditional execution
const value = select({
pred: true,
t: () => 'yes',
f: () => 'no'
}); // "yes"API Documentation
Monads
All monads implement the MonadLike interface with:
map<U>(fn: (value: T) => U): Monad<U>- Transform the contained valueflatMap<U>(fn: (value: T) => Monad<U>): Monad<U>- Chain monadic operations
Maybe
Maybe.some<T>(value: T): Maybe<T>- Create a Some valueMaybe.none<T>(): Maybe<T>- Create a None valueMaybe.isSome(m: Maybe<T>): boolean- Type guard for SomeMaybe.isNone(m: Maybe<T>): boolean- Type guard for NonegetOr(defaultValue: T): T- Extract value or use defaultgetOrElse(fn: () => T): T- Extract value or compute defaultor(alternative: Maybe<T>): Maybe<T>- Alternative if Noneand(other: Maybe<T>): Maybe<T>- Chain if Some
Result
Result.ok<T, E>(value: T): Result<T, E>- Create an Ok resultResult.err<T, E>(error: E): Result<T, E>- Create an Err resultResult.isOk(r: Result<T, E>): boolean- Type guard for OkResult.isErr(r: Result<T, E>): boolean- Type guard for ErrmapError<F>(fn: (error: E) => F): Result<T, F>- Transform errorconsume(onOk: (value: T) => void, onErr: (error: E) => void): void- Handle both casesgetOr(defaultValue: T): T- Extract value or use defaultgetOrElse(fn: (error: E) => T): T- Extract value or compute from erroror(alternative: Result<T, E>): Result<T, E>- Alternative if Errand(other: Result<T, E>): Result<T, E>- Chain if Ok
Reader
Reader.of<R, A>(value: A): Reader<R, A>- Create a constant ReaderReader.from<R, A>(fn: (r: R) => A): Reader<R, A>- Create from functionReader.ask<R>(): Reader<R, R>- Access the environmentrun(env: R): A- Execute with environmentlocal<R2>(fn: (r2: R2) => R): Reader<R2, A>- Transform environment
Writer
Writer.of<T, W>(value: T, log?: W): Writer<T>- Create a WriterWriter.tell<T, W>(log: W, value: T): Writer<T>- Create with logrun(): [T, W[]]- Extract value and accumulated logslisten(): Writer<[T, W[]]>- Access logs without extractionpass(fn: (logs: W[]) => W[]): Writer<T>- Transform logs
Monad Transformers
Transformers combine effects from multiple monads:
MaybeT, ResultT, ReaderT, WriterT
Each transformer provides:
of<A>(value: A)- Lift a value into the transformerlift<A>(m: InnerMonad<A>)- Lift inner monad into transformerfrom<A>(m: FullStack)- Create from complete monad stackmap,flatMap- Standard monadic operationsrun()- Execute the transformer stack
Optics
Lens<S, A>
Lens.from<S, A>(get, set)- Create a lensget(obj: S): A- Extract valueset(value: A): (obj: S) => S- Update valuemodify(fn: (a: A) => A): (obj: S) => S- Transform valuecompose<B>(other: Lens<A, B>): Lens<S, B>- Compose lenses
Prism<S, A>
Prism.from<S, A>(preview, review)- Create a prismpreview(obj: S): Maybe<A>- Try to extract valuereview(value: A): S- Construct containermodify(fn: (a: A) => A): (obj: S) => S- Transform if presentcompose<B>(other: Prism<A, B>): Prism<S, B>- Compose prisms
Utility Functions
Function Composition
pipe(value, ...fns)- Left-to-right compositioncompose(...fns)- Right-to-left compositionapply(fn)(...args)- Apply function to arguments
Higher-Order Functions
constant(value)- Create constant functionid(value)- Identity functionflip(fn)- Swap first two argumentsuncurry(fn)- Convert curried to multi-arg functionnegate(fn)- Boolean negation
Logic Utilities
all(...predicates)- All predicates must passany(...predicates)- Any predicate must passselect({ pred, t, f })- Conditional executiontruthy(value)- Check if truthyfalsy(value)- Check if falsyallTruthy(...values)- All values truthyanyTruthy(...values)- Any value truthy
Data Access
prop(key)- Get property from objectflattenBy(getChildren)- Flatten tree structurebounded(min, max, inclusive)- Create range checkerisNotNil(value)- Type guard for non-null/undefined
Type Safety
All functions and data structures are fully typed with TypeScript. The library uses:
- Higher-Kinded Types (HKT) for monad transformers
- Type guards for runtime type checking
- Generic constraints for type safety
- Branded types where appropriate
Testing
The library has comprehensive test coverage with 428+ tests covering:
- Monad laws (left identity, right identity, associativity)
- Functor laws
- Transformer laws
- Edge cases and error handling
- Complex transformer combinations
Run tests:
pnpm testBuilding
pnpm buildOutputs:
- UMD bundle:
dist/ts-toolbox.umd.js - ES module:
dist/ts-toolbox.es.js - Type definitions:
dist/index.d.ts
License
MIT
Contributing
Contributions welcome! Please ensure:
- All tests pass
- New features include tests
- Code follows existing patterns
- Types are properly defined
Credits
Built with TypeScript, Vite, and Vitest. Uses ts-pattern for pattern matching.
