massaman
v0.2.0
Published
A comprehensive functional programming library and utilities for TypeScript
Downloads
583
Maintainers
Readme
Features
- Rust's
matchin TypeScript: exhaustive pattern matching via re-exported ts-pattern. - Rust's
Resultin TypeScript: compose fallible operations without throws (attempt,attemptAsync,ok,err,isOk,unwrap). - Functional programming, fully typed:
flow,flowAsync,tap,when,ifElse, and point-free combinators with end-to-end inference. - The missing pieces around es-toolkit: variadic-narrowing predicates, async composition, and consistent error normalization on top of the array/object/string/math utilities you already love.
- Tree-shakeable: 12 focused subpath exports, ESM-only,
sideEffects: false. Import only what you use.
Install
npm install massamanUsage
From ts-pattern
The pattern subpath is a transparent re-export of ts-pattern. Exhaustive matching with full TypeScript inference.
import { match, P } from 'massaman/match'
const status = match(action)
.with({ type: 'load' }, () => 'loading')
.with({ type: 'success' }, () => 'done')
.with({ type: 'error', msg: P.string }, ({ msg }) => `failed: ${msg}`)
.exhaustive()From es-toolkit
Most of the surface (array, object, string, function, math) is a transparent re-export of es-toolkit. Same names, same behavior, same docs.
import { chunk, groupBy } from 'massaman'
chunk([1, 2, 3, 4, 5], 2)
// [[1, 2], [3, 4], [5]]
groupBy(['apple', 'avocado', 'banana', 'blueberry'], (s) => s[0])
// { a: ['apple', 'avocado'], b: ['banana', 'blueberry'] }Custom: Result and async composition
Built on top of those re-exports, massaman/control adds Result-style error handling that never throws across a boundary.
import { attemptAsync, isOk } from 'massaman/control'
const result = await attemptAsync(() => fetch('/api/user').then((r) => r.json()))
if (isOk(result)) {
console.log(result.value)
} else {
console.error(result.error.message)
}Why?
Two Rust patterns I keep wanting in TypeScript: match for exhaustive branching, and Result for errors that compose without throws.
In Rust:
match action {
Action::Load => "loading",
Action::Success => "done",
Action::Error(msg) => &format!("failed: {}", msg),
}
let parsed: Result<User, Error> = serde_json::from_str(raw);
match parsed {
Ok(user) => println!("got: {}", user.name),
Err(err) => eprintln!("failed: {}", err),
}The equivalent in TypeScript using massaman:
import { match, P } from 'massaman/match'
import { attempt, isOk } from 'massaman/control'
match(action)
.with({ type: 'load' }, () => 'loading')
.with({ type: 'success' }, () => 'done')
.with({ type: 'error', msg: P.string }, ({ msg }) => `failed: ${msg}`)
.exhaustive()
const parsed = attempt(() => JSON.parse(raw) as User)
if (isOk(parsed)) {
console.log(`got: ${parsed.value.name}`)
} else {
console.error(`failed: ${parsed.error.message}`)
}massaman brings both patterns to TypeScript, layered over es-toolkit and ts-pattern, plus a thin set of utilities filling the gaps: async-aware composition, variadic-narrowing predicates, and consistent error normalization.
More on the philosophy at zrosenbauer.com/gui/blog.
Contributing
See CONTRIBUTING.md for development setup, conventions, and PR process.
