Complete reference for all exported functions, organized by module.
Import styles
// Root barrel — everything
import { pipe, Ok, Err, Some, None } from '@tecnomancy/alchemy';
// Subpath — one module
import { Ok, Err, flatMap } from '@tecnomancy/alchemy/result';
import { pipe, compose } from '@tecnomancy/alchemy/composition';import { ... } from '@tecnomancy/alchemy/result'
Represents a value that is either a success (Ok) or a failure (Err).
Use Result instead of throwing exceptions for expected failure paths.
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };Creates a successful Result.
Signature: <T, E = Error>(value: T) => Result<T, E>
Example:
Ok(42); // { ok: true, value: 42 }
Ok('hello'); // { ok: true, value: 'hello' }Creates a failed Result.
Signature: <T, E = Error>(error: E) => Result<T, E>
Example:
Err('not found'); // { ok: false, error: 'not found' }
Err(new Error('failed')); // { ok: false, error: Error }Returns true if the result is Ok, narrowing the type.
Signature: <T, E>(result: Result<T, E>) => result is { ok: true; value: T }
Example:
const r = Ok(42);
if (isOk(r)) {
console.log(r.value); // TypeScript knows r.value exists
}Returns true if the result is Err, narrowing the type.
Signature: <T, E>(result: Result<T, E>) => result is { ok: false; error: E }
Example:
const r = Err('oops');
if (isErr(r)) {
console.log(r.error); // TypeScript knows r.error exists
}Applies fn to the value if Ok; passes Err through unchanged.
Signature: <T, R, E>(fn: (value: T) => R) => (result: Result<T, E>) => Result<R, E>
Example:
mapResult((x: number) => x * 2)(Ok(5)); // Ok(10)
mapResult((x: number) => x * 2)(Err('x')); // Err('x')See also: mapResultAsync, mapErr
Applies fn to the error if Err; passes Ok through unchanged.
Signature: <T, E, F>(fn: (error: E) => F) => (result: Result<T, E>) => Result<T, F>
Example:
mapErr((e: Error) => e.message)(Err(new Error('oops'))); // Err('oops')
mapErr((e: Error) => e.message)(Ok(42)); // Ok(42)Chains operations that can themselves fail. Avoids nested Result<Result<T>>.
Signature: <T, R, E>(fn: (value: T) => Result<R, E>) => (result: Result<T, E>) => Result<R, E>
Example:
const safeDivide = (n: number) =>
n === 0 ? Err('division by zero') : Ok(100 / n);
pipe(Ok(5), flatMap(safeDivide)); // Ok(20)
pipe(Ok(0), flatMap(safeDivide)); // Err('division by zero')
pipe(Err('x'), flatMap(safeDivide)); // Err('x') — short-circuitsSee also: flatMapAsync, mapResult
Pattern-matches over a Result, consuming both branches.
Signature: <T, E, R>(onOk: (value: T) => R, onErr: (error: E) => R) => (result: Result<T, E>) => R
Example:
const message = match(
(value: number) => `got ${value}`,
(error: string) => `failed: ${error}`,
);
message(Ok(42)); // 'got 42'
message(Err('x')); // 'failed: x'Extracts the value if Ok, or returns defaultValue if Err.
Signature: <T, E>(defaultValue: T) => (result: Result<T, E>) => T
Example:
unwrapOr(0)(Ok(42)); // 42
unwrapOr(0)(Err('x')); // 0Extracts the value if Ok, or computes a fallback from the error if Err.
Signature: <T, E>(fn: (error: E) => T) => (result: Result<T, E>) => T
Example:
unwrapOrElse((e: string) => e.length)(Ok(42)); // 42
unwrapOrElse((e: string) => e.length)(Err('hi')); // 2Extracts the value or throws the error. Prefer unwrapOr or match in production.
Signature: <T, E>(result: Result<T, E>) => T
Example:
unwrap(Ok(42)); // 42
unwrap(Err('oops')); // throws 'oops'Applies fn to both values if both results are Ok; returns the first Err otherwise.
Signature: <T1, T2, R, E>(fn: (v1: T1, v2: T2) => R) => (r1: Result<T1, E>, r2: Result<T2, E>) => Result<R, E>
Example:
combineTwo((a: number, b: number) => a + b)(Ok(10), Ok(5)); // Ok(15)
combineTwo((a: number, b: number) => a + b)(Err('x'), Ok(5)); // Err('x')Collects an array of Results into a single Result of array. Returns the first Err found.
Signature: <T, E>(results: Array<Result<T, E>>) => Result<T[], E>
Example:
combineAll([Ok(1), Ok(2), Ok(3)]); // Ok([1, 2, 3])
combineAll([Ok(1), Err('x'), Ok(3)]); // Err('x')See also: collectErrors
Applies a wrapped function Result<(T) => R, E> to a Result<T, E>. Both must be Ok for the application to occur.
Signature: <T, R, E>(resultFn: Result<(value: T) => R, E>) => (result: Result<T, E>) => Result<R, E>
Example:
const addOne = Ok((x: number) => x + 1);
ap(addOne)(Ok(5)); // Ok(6)
ap(addOne)(Err('x')); // Err('x')
ap(Err('fn'))(Ok(5)); // Err('fn')Alias for combineTwo. Lifts a binary function over two Results.
See also: combineTwo, liftA3
Lifts a ternary function over three Results. Returns the first Err if any result fails.
Signature: <A, B, C, R, E>(fn: (a: A, b: B, c: C) => R) => (ra: Result<A, E>, rb: Result<B, E>, rc: Result<C, E>) => Result<R, E>
Example:
const sum3 = liftA3((a: number, b: number, c: number) => a + b + c);
sum3(Ok(1), Ok(2), Ok(3)); // Ok(6)
sum3(Ok(1), Err('x'), Ok(3)); // Err('x')Maps both branches of a Result simultaneously.
Signature: <T, E, R, F>(onOk: (value: T) => R, onErr: (error: E) => F) => (result: Result<T, E>) => Result<R, F>
Example:
bimap(
(n: number) => n * 2,
(e: string) => e.toUpperCase(),
)(Ok(5)); // Ok(10)
bimap(
(n: number) => n * 2,
(e: string) => e.toUpperCase(),
)(Err('x')); // Err('X')Alias for mapErr. Applies fn to the error if Err; passes Ok through unchanged.
Signature: <T, E, F>(fn: (error: E) => F) => (result: Result<T, E>) => Result<T, F>
See also: mapErr
Flips Ok(x) to Err(x) and Err(e) to Ok(e).
Signature: <T, E>(result: Result<T, E>) => Result<E, T>
Example:
swap(Ok(42)); // Err(42)
swap(Err('oops')); // Ok('oops')Converts Ok(x) to Some(x) and Err to None, discarding the error.
Signature: <T, E>(result: Result<T, E>) => Option<T>
Example:
toOption(Ok(42)); // Some(42)
toOption(Err('oops')); // NoneSee also: resultToOption, optionToResult
Collects all values if all are Ok, or all errors if any are Err. Unlike combineAll, does not short-circuit.
Signature: <T, E>(results: Array<Result<T, E>>) => Result<T[], E[]>
Example:
collectErrors([Ok(1), Err('e1'), Ok(2), Err('e2')]);
// Err(['e1', 'e2'])
collectErrors([Ok(1), Ok(2)]);
// Ok([1, 2])Wraps a potentially-throwing function to return Result instead of throwing.
Signature: <T extends unknown[], R, E = Error>(fn: (...args: T) => R) => (...args: T) => Result<R, E>
Example:
const safeParse = tryCatch(JSON.parse);
safeParse('{"a":1}'); // Ok({ a: 1 })
safeParse('bad'); // Err(SyntaxError)Async version of tryCatch. Wraps a Promise-returning function.
Signature: <T extends unknown[], R, E = Error>(fn: (...args: T) => Promise<R>) => (...args: T) => Promise<Result<R, E>>
Example:
const safeUpload = tryCatchAsync(uploadFile);
const result = await safeUpload(blob); // Result<UploadResult, Error>Converts a Promise<T> to a Promise<Result<T, E>>.
Signature: <T, E = Error>(promise: Promise<T>) => Promise<Result<T, E>>
Example:
const result = await fromPromise(fetch('/api/users'));
// Ok(Response) | Err(Error)Async version of mapResult.
Signature: <T, R, E>(fn: (value: T) => Promise<R>) => (result: Result<T, E>) => Promise<Result<R, E>>
Example:
await mapResultAsync(async (id: string) => fetchUser(id))(Ok('123'));
// Ok(user) or passes Err throughAsync version of flatMap.
Signature: <T, R, E>(fn: (value: T) => Promise<Result<R, E>>) => (result: Result<T, E>) => Promise<Result<R, E>>
Example:
const fetchUser = async (id: string): Promise<Result<User, Error>> => ...;
await flatMapAsync(fetchUser)(Ok('user-123'));Runs all validators in sequence. Returns Ok if all pass, or the first Err.
Signature: <T, E>(validators: Array<Validator<T, E>>) => (value: T) => Result<T, E>
Example:
const isPositive = (x: number) => x > 0 ? Ok(x) : Err('not positive');
const isEven = (x: number) => x % 2 === 0 ? Ok(x) : Err('not even');
validateAll([isPositive, isEven])(4); // Ok(4)
validateAll([isPositive, isEven])(3); // Err('not even')
validateAll([isPositive, isEven])(-2); // Err('not positive')Returns Ok if at least one validator passes, or Err with all collected errors.
Signature: <T, E>(validators: Array<Validator<T, E>>) => (value: T) => Result<T, E[]>
Example:
const isZero = (x: number) => x === 0 ? Ok(x) : Err('not zero');
const isPositive = (x: number) => x > 0 ? Ok(x) : Err('not positive');
validateAny([isZero, isPositive])(5); // Ok(5)
validateAny([isZero, isPositive])(0); // Ok(0)
validateAny([isZero, isPositive])(-1); // Err(['not zero', 'not positive'])Executes a side-effect function when the result is Ok, then returns the original result unchanged. Useful for logging inside a pipeline without breaking the chain.
Signature: <T, E>(fn: (value: T) => void) => (result: Result<T, E>) => Result<T, E>
Example:
pipe(
Ok(42),
tapResult(v => console.log('value:', v)), // logs 'value: 42'
mapResult(v => v * 2),
); // Ok(84)
tapResult(console.log)(Err('fail')); // no-op, returns Err('fail')Executes a side-effect function when the result is Err, then returns the original result unchanged. Useful for error logging in pipelines.
Signature: <T, E>(fn: (error: E) => void) => (result: Result<T, E>) => Result<T, E>
Example:
pipe(
Err('something went wrong'),
tapError(e => console.error('error:', e)), // logs 'error: something went wrong'
unwrapOr(0),
); // 0
tapError(console.error)(Ok(42)); // no-op, returns Ok(42)Recovers from an Err by applying a function that returns a new Result. If the result is Ok, it passes through unchanged.
Signature: <T, E, F>(fn: (error: E) => Result<T, F>) => (result: Result<T, E>) => Result<T, F>
Example:
const parseNumber = (s: string) => isNaN(Number(s)) ? Err('not a number') : Ok(Number(s));
const fallback = (e: string) => e === 'not a number' ? Ok(0) : Err(e);
orElse(fallback)(parseNumber('abc')); // Ok(0)
orElse(fallback)(parseNumber('42')); // Ok(42)Lifts a nullable value into a Result. Returns Ok(value) when the value is non-null/non-undefined, or Err(onNone) otherwise.
Signature: <E>(onNone: E) => <T>(value: T | null | undefined) => Result<T, E>
Example:
fromNullableResult('not found')(42); // Ok(42)
fromNullableResult('not found')(null); // Err('not found')
fromNullableResult('not found')(undefined); // Err('not found')import { ... } from '@tecnomancy/alchemy/option'
Represents a value that may or may not be present. Use Option to eliminate null/undefined from your domain types.
type Option<T> = { _tag: 'Some'; value: T } | { _tag: 'None' };Wraps a present value.
Signature: <T>(value: T) => Option<T>
Example:
Some(42); // { _tag: 'Some', value: 42 }
Some('hello'); // { _tag: 'Some', value: 'hello' }The absent value singleton.
Example:
None; // { _tag: 'None' }Converts null/undefined to None; any other value to Some.
Signature: <T>(value: T | null | undefined) => Option<T>
Example:
fromNullable(null); // None
fromNullable(undefined); // None
fromNullable(0); // Some(0)
fromNullable(''); // Some('')Wraps a potentially-throwing function. Returns None on exception.
Signature: <T>(fn: () => T) => Option<T>
Example:
fromTryCatch(() => JSON.parse('{"a":1}')); // Some({ a: 1 })
fromTryCatch(() => JSON.parse('bad')); // NoneReturns true and narrows to Some<T> if the option has a value.
Signature: <T>(opt: Option<T>) => opt is Some<T>
Returns true and narrows to None if the option is absent.
Signature: <T>(opt: Option<T>) => opt is None
Applies fn to the inner value if Some; passes None through.
Signature: <T, R>(fn: (value: T) => R) => (opt: Option<T>) => Option<R>
Example:
mapOption((x: number) => x * 2)(Some(5)); // Some(10)
mapOption((x: number) => x * 2)(None); // NoneChains operations that may return None. Prevents Some(Some(x)).
Signature: <T, R>(fn: (value: T) => Option<R>) => (opt: Option<T>) => Option<R>
Example:
const safeSqrt = (n: number): Option<number> =>
n >= 0 ? Some(Math.sqrt(n)) : None;
flatMapOption(safeSqrt)(Some(16)); // Some(4)
flatMapOption(safeSqrt)(Some(-1)); // None
flatMapOption(safeSqrt)(None); // NonePattern-matches over an Option.
Signature: <T, R>(onSome: (value: T) => R, onNone: () => R) => (opt: Option<T>) => R
Example:
const greet = matchOption(
(name: string) => `Hello, ${name}!`,
() => 'Hello, stranger!',
);
greet(Some('Alice')); // 'Hello, Alice!'
greet(None); // 'Hello, stranger!'Extracts the value if Some, or returns defaultValue if None.
Signature: <T>(defaultValue: T) => (opt: Option<T>) => T
Example:
unwrapOptionOr(0)(Some(42)); // 42
unwrapOptionOr(0)(None); // 0Extracts the value or computes a fallback lazily.
Signature: <T>(fn: () => T) => (opt: Option<T>) => T
Extracts the value or throws. Prefer unwrapOptionOr in production.
Signature: <T>(opt: Option<T>) => T
Returns the option if Some, or calls fallback() to produce an alternative.
Signature: <T>(fallback: () => Option<T>) => (opt: Option<T>) => Option<T>
Example:
const cached = orElseOption(() => fromNullable(db.get(key)));
cached(Some(value)); // Some(value) — cached hit, db not called
cached(None); // tries dbConverts Option<T> to T | null.
Signature: <T>(opt: Option<T>) => T | null
Example:
toNullable(Some(42)); // 42
toNullable(None); // nullConverts Option<T> to an array with zero or one element.
Signature: <T>(opt: Option<T>) => T[]
Example:
toArray(Some(42)); // [42]
toArray(None); // []Combines two Options into a tuple. Returns None if either is None.
Signature: <A, B>(a: Option<A>, b: Option<B>) => Option<[A, B]>
Example:
zipOption(Some(1), Some('a')); // Some([1, 'a'])
zipOption(None, Some('a')); // NoneConverts Array<Option<T>> to Option<T[]>. Returns None if any element is None.
Signature: <T>(opts: Array<Option<T>>) => Option<T[]>
Example:
sequenceOption([Some(1), Some(2), Some(3)]); // Some([1, 2, 3])
sequenceOption([Some(1), None, Some(3)]); // NoneFilters out None values and unwraps Some values.
Signature: <T>(opts: Array<Option<T>>) => T[]
Example:
compactOptions([Some(1), None, Some(2), None]); // [1, 2]Converts Option<T> to Result<T, E>, using onNone to produce the error.
Signature: <T, E>(onNone: () => E) => (opt: Option<T>) => Result<T, E>
Example:
optionToResult(() => 'not found')(Some(42)); // Ok(42)
optionToResult(() => 'not found')(None); // Err('not found')Converts Result<T, E> to Option<T>, discarding the error.
Signature: <T, E>(result: Result<T, E>) => Option<T>
Example:
resultToOption(Ok(42)); // Some(42)
resultToOption(Err('oops')); // Noneimport { ... } from '@tecnomancy/alchemy/composition'
Passes a value through a series of functions left-to-right. Every intermediate type is inferred — no type loss up to 10 steps.
Signature: pipe<A, B, C, ...>(value: A, f1: (a: A) => B, f2: (b: B) => C, ...) => ...
Example:
pipe(
' hello world ',
s => s.trim(), // string
s => s.split(' '), // string[]
arr => arr.length, // number
n => n > 1, // boolean
); // trueSee also: compose, flow, pipeAsync
Composes functions right-to-left into a single function (the rightmost is applied first).
Signature: compose<A, B, C, ...>(fn_last: ..., ..., fn_first: (a: A) => B) => (a: A) => ...
Example:
const process = compose(
(n: number) => `Result: ${n}`, // applied last
(arr: number[]) => arr.reduce((a, b) => a + b, 0), // applied second
(s: string) => s.split(',').map(Number), // applied first
);
process('1,2,3,4,5'); // 'Result: 15'Creates a left-to-right function pipeline (point-free). Unlike pipe, flow takes only functions and returns a new function — no initial value is passed in.
Signature: flow<A, B, C, ...>(f1: (a: A) => B, f2: (b: B) => C, ...) => (a: A) => ...
Example:
const process = flow(
(s: string) => s.split(' '), // string → string[]
(arr: string[]) => arr.length, // string[] → number
(n: number) => n > 1, // number → boolean
);
process('hello world'); // true
process('hello'); // falseSee also: pipe (value-first), compose (right-to-left)
Returns its argument unchanged.
Signature: <T>(value: T) => T
Example:
identity(42); // 42
[1, 2, 3].map(identity); // [1, 2, 3]Returns a function that always returns value, ignoring all arguments.
Signature: <T>(value: T) => (...args: unknown[]) => T
Example:
const always42 = constant(42);
always42('anything', 'ignored'); // 42Executes a side-effect function with value and returns value unchanged. Useful for debugging inside a pipe.
Signature: <T>(fn: (value: T) => void) => (value: T) => T
Example:
pipe(
[1, 2, 3],
map(x => x * 2),
tap(console.log), // logs [2, 4, 6] — does not change the array
filter(x => x > 2),
); // [4, 6]Converts a multi-argument function into a chain of single-argument functions.
Signature: <T extends unknown[], R>(fn: (...args: T) => R) => (...args: readonly unknown[]) => unknown
Example:
const add = (a: number, b: number, c: number) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6Pre-fills arguments of a function.
Signature: <T extends unknown[], R>(fn: (...args: T) => R, ...partialArgs: unknown[]) => (...remainingArgs: unknown[]) => R
Example:
const add = (a: number, b: number, c: number) => a + b + c;
const add5 = partial(add, 5);
add5(3, 2); // 10Swaps the first two arguments of a binary function.
Signature: <A, B, R>(fn: (a: A, b: B) => R) => (b: B, a: A) => R
Example:
const divide = (a: number, b: number) => a / b;
flip(divide)(2, 10); // 5 (same as divide(10, 2))Extracts a property from an object (curried).
Signature: <T extends object, K extends keyof T>(key: K) => (obj: T) => T[K] | undefined
Example:
const getName = prop('name');
getName({ name: 'Alice', age: 30 }); // 'Alice'Caches function results. Uses JSON.stringify(args) as the cache key by default.
Signature: <T extends unknown[], R>(fn: (...args: T) => R, keyFn?: (...args: T) => string) => (...args: T) => R
Example:
const expensiveCalc = memoize((n: number) => n * n);
expensiveCalc(5); // computed
expensiveCalc(5); // cachedSee also: memoizeAsync
Returns a function that executes fn at most once. Subsequent calls return the result of the first call.
Signature: <T extends unknown[], R>(fn: (...args: T) => R) => (...args: T) => R
Example:
const init = once(() => { console.log('init'); return 42; });
init(); // logs 'init', returns 42
init(); // returns 42 (no log)Returns a function that only executes fn starting from the nth call.
Signature: <T extends unknown[], R>(n: number, fn: (...args: T) => R) => (...args: T) => R | undefined
Example:
const log = after(3, () => console.log('reached'));
log(); // undefined
log(); // undefined
log(); // logs 'reached'Returns a function that executes fn only for the first n - 1 calls.
Signature: <T extends unknown[], R>(n: number, fn: (...args: T) => R) => (...args: T) => R | undefined
Example:
const log = before(3, () => console.log('ok'));
log(); // logs 'ok'
log(); // logs 'ok'
log(); // no-opInverts a predicate function.
Signature: <T extends unknown[]>(predicate: (...args: T) => boolean) => (...args: T) => boolean
Example:
const isEven = (x: number) => x % 2 === 0;
const isOdd = negate(isEven);
isOdd(3); // trueSee also: not in the predicates module
import { ... } from '@tecnomancy/alchemy/async'
Composes async functions left-to-right.
Signature: pipeAsync<A, B, C, ...>(fn1: (a: A) => Promise<B>, fn2: (b: B) => Promise<C>, ...) => (a: A) => Promise<...>
Example:
const processUser = pipeAsync(
fetchUser, // (id: string) => Promise<User>
enrichWithRoles, // (user: User) => Promise<UserWithRoles>
formatForClient, // (user: UserWithRoles) => Promise<ClientUser>
);
await processUser('user-123');Composes async functions right-to-left. Does not mutate the input array.
Signature: Same overloads as pipeAsync, reversed.
Example:
const process = composeAsync(formatUser, enrichUser, fetchUser);
await process('user-123');
// equivalent to: fetchUser → enrichUser → formatUserApplies an async function to each element sequentially.
Signature: <T, R>(fn: (item: T) => Promise<R>) => (arr: T[]) => Promise<R[]>
Example:
await mapAsync(async (id: string) => fetchUser(id))(['1', '2', '3']);
// users fetched one at a timeSee also: mapParallel, mapConcurrent
Applies an async function to all elements in parallel (Promise.all).
Signature: <T, R>(fn: (item: T) => Promise<R>) => (arr: T[]) => Promise<R[]>
Example:
await mapParallel(fetchUser)(['1', '2', '3']);
// all 3 fetches run simultaneouslyApplies an async function with a concurrency limit.
Signature: <T, R>(concurrency: number, fn: (item: T) => Promise<R>) => (arr: T[]) => Promise<R[]>
Example:
await mapConcurrent(5, fetchUser)(ids); // at most 5 concurrent requestsFilters an array using an async predicate, sequentially.
Signature: <T>(predicate: (item: T) => Promise<boolean>) => (arr: T[]) => Promise<T[]>
Example:
const isActive = async (user: User) => (await fetchStatus(user.id)) === 'active';
await filterAsync(isActive)(users);Reduces an array with an async reducer, sequentially.
Signature: <T, R>(fn: (acc: R, item: T) => Promise<R>, initial: R) => (arr: T[]) => Promise<R>
Example:
const sumBalances = async (acc: number, user: User) =>
acc + await fetchBalance(user.id);
await reduceAsync(sumBalances, 0)(users);Retries an async function with exponential backoff. Returns Result<T, Error> — never throws.
Signature: <T>(maxAttempts: number, delayMs?: number, backoff?: number) => (fn: () => Promise<T>) => Promise<Result<T, Error>>
Example:
const result = await retry(3, 1000)(fetchUnstableApi);
// tries up to 3 times with 1s, 2s, 4s delays
if (!result.ok) console.error(result.error.message);Rejects a promise if it does not resolve within ms milliseconds.
Signature: <T>(ms: number) => (promise: Promise<T>) => Promise<T>
Example:
await timeout(5000)(fetch('/slow-endpoint'));
// throws Error('Timeout after 5000ms') if too slowReturns a promise that resolves after ms milliseconds.
Signature: (ms: number) => Promise<void>
Example:
await sleep(1000); // pauses for 1 secondReturns a debounced version of an async function. Only the last call within the delay window executes.
Signature: <T extends unknown[], R>(delayMs: number) => (fn: (...args: T) => Promise<R>) => (...args: T) => Promise<R>
Example:
const save = debounceAsync(500)(async (data: string) => api.save(data));
save('a'); // cancelled
save('b'); // cancelled
save('c'); // executes after 500msReturns a throttled version of an async function. At most one execution per delay window; calls within the window return the pending promise.
Signature: <T extends unknown[], R>(delayMs: number) => (fn: (...args: T) => Promise<R>) => (...args: T) => Promise<R>
Example:
const logEvent = throttleAsync(1000)(async (e: string) => api.log(e));
logEvent('click'); // executes
logEvent('click'); // returns same promiseMemoizes an async function. Returns Result<T, Error> on each call — never rethrows.
Signature: <T extends unknown[], R>(fn: (...args: T) => Promise<R>, keyFn?: (...args: T) => string) => (...args: T) => Promise<Result<R, Error>>
Example:
const fetchUser = memoizeAsync(async (id: string) => api.getUser(id));
await fetchUser('123'); // Ok(user) — network call
await fetchUser('123'); // Ok(user) — from cacheRuns an array of async thunks sequentially, collecting results.
Signature: <T>(fns: Array<() => Promise<T>>) => Promise<T[]>
Example:
await sequence([
async () => step1(),
async () => step2(),
]); // ['result1', 'result2'] — in orderRuns an array of async thunks in parallel, collecting results.
Signature: <T>(fns: Array<() => Promise<T>>) => Promise<T[]>
Example:
await parallel([
async () => fetchUser('1'),
async () => fetchUser('2'),
]);| Function | Description |
|---|---|
mapConcurrentResult(concurrency, fn)(arr) |
Concurrent map with bounded parallelism; accumulates all errors in a Result |
mapAsyncResult(fn)(arr) |
Sequential async map; accumulates all errors in a Result |
reduceAsyncResult(fn, initial)(arr) |
Sequential async reduce that short-circuits on first Err |
Example:
import { mapConcurrentResult, mapAsyncResult, collectErrors } from '@tecnomancy/alchemy';
// mapAsyncResult — sequential, collects all errors
const results = await mapAsyncResult(
async (id: string) => fetchUser(id), // (id) => Promise<Result<User, Error>>
)(['1', '2', '3']);
collectErrors(results); // Ok([u1, u2, u3]) or Err([...errors])
// mapConcurrentResult — concurrent with limit
const results2 = await mapConcurrentResult(
5,
async (id: string) => processItem(id),
)(ids);import { ... } from '@tecnomancy/alchemy/array'
All array functions are curried and data-last — designed to compose inside pipe.
| Function | Signature | Description |
|---|---|---|
map(fn)(arr) |
<T, R>(fn: (item: T) => R) => (arr: T[]) => R[] |
Applies fn to every element |
filter(pred)(arr) |
<T>(pred: (item: T) => boolean) => (arr: T[]) => T[] |
Keeps elements satisfying pred |
reduce(fn, init)(arr) |
<T, R>(fn: (acc: R, item: T) => R, init: R) => (arr: T[]) => R |
Folds array to single value |
flatMapArray(fn)(arr) |
<T, R>(fn: (item: T) => R[]) => (arr: T[]) => R[] |
Maps and flattens one level |
sortBy(fn)(arr) |
<T>(fn: (item: T) => string | number) => (arr: T[]) => T[] |
Sorts by key, no mutation |
take(n)(arr) |
(n: number) => <T>(arr: T[]) => T[] |
First n elements |
skip(n)(arr) |
(n: number) => <T>(arr: T[]) => T[] |
Drop first n elements |
chunk(size)(arr) |
(size: number) => <T>(arr: T[]) => T[][] |
Splits into chunks |
compact(arr) |
<T>(arr: Array<T | null | undefined | false | 0 | ''>) => T[] |
Removes falsy values |
| Function | Description |
|---|---|
find(pred)(arr) |
First element matching pred, or undefined |
some(pred)(arr) |
true if any element matches |
every(pred)(arr) |
true if all elements match |
groupBy(fn)(arr) |
Groups elements by key function into a record |
countBy(fn)(arr) |
Counts elements per key |
partition(pred)(arr) |
Splits into [matches, non-matches] |
| Function | Description |
|---|---|
unique(arr) |
Removes duplicates, preserves insertion order |
flatten(arr) |
Flattens one level: T[][] → T[] |
flattenDeep(arr) |
Recursively flattens all levels |
zip(arr1, arr2) |
Pairs elements into tuples, truncates to shorter length |
unzip(arr) |
Splits [T, U][] into [T[], U[]] |
hasItems(arr) |
true if array is non-empty |
| Function | Description |
|---|---|
head(arr) |
First element as Option<T>; None if empty |
tail(arr) |
All-but-first as Option<T[]>; None if empty |
last(arr) |
Last element as Option<T>; None if empty |
init(arr) |
All-but-last as Option<T[]>; None if empty |
nth(n)(arr) |
Element at index n (negative supported); None if out of bounds |
Example:
import { head, tail, last, nth } from '@tecnomancy/alchemy';
head([1, 2, 3]); // Some(1)
head([]); // None
last([1, 2, 3]); // Some(3)
nth(-1)([1, 2, 3]); // Some(3) — last element via negative index
nth(10)([1, 2, 3]); // None — out of boundsExample:
pipe(
[1, 2, 3, 4, 5, 6],
filter(n => n % 2 === 0), // [2, 4, 6]
map(n => n * 10), // [20, 40, 60]
reduce((acc, n) => acc + n, 0), // 120
);
groupBy((u: User) => u.role)(users);
// { admin: [User, ...], viewer: [User, ...] }
const [evens, odds] = partition(n => n % 2 === 0)([1, 2, 3, 4]);import { ... } from '@tecnomancy/alchemy/object'
| Function | Description |
|---|---|
pick(keys)(obj) |
Returns a new object with only the specified keys |
omit(keys)(obj) |
Returns a new object with the specified keys removed |
merge(base)(override) |
Shallow merge; override takes precedence |
deepMerge(base)(override) |
Recursive merge; arrays are replaced, not merged |
defaults(base)(obj) |
Fills missing keys in obj from base; existing keys are preserved |
Example:
const user = { id: 1, name: 'Alice', password: 'secret' };
pick(['id', 'name'])(user); // { id: 1, name: 'Alice' }
omit(['password'])(user); // { id: 1, name: 'Alice' }| Function | Description |
|---|---|
mapValues(fn)(obj) |
Transforms every value, preserving keys |
mapKeys(fn)(obj) |
Transforms every key, preserving values |
filterValues(pred)(obj) |
Keeps entries whose value satisfies pred |
filterKeys(pred)(obj) |
Keeps entries whose key satisfies pred |
Example:
mapValues((v: number) => v * 2)({ a: 1, b: 2 }); // { a: 2, b: 4 }
filterValues((v: unknown) => v != null)({ a: 1, b: null }); // { a: 1 }| Function | Description |
|---|---|
keys(obj) |
Typed Object.keys |
values(obj) |
Typed Object.values |
entries(obj) |
Typed Object.entries |
fromEntries(pairs) |
Typed Object.fromEntries |
hasKey(key)(obj) |
true if key exists in obj |
isEmpty(obj) |
true if object has no own enumerable properties |
isObject(value) |
Type guard: plain object, not array, not null |
| Function | Description |
|---|---|
getPath(path)(obj) |
Reads a nested value by path; returns undefined if any segment is missing |
getPathOr(fallback, path)(obj) |
Like getPath but returns fallback when the path is absent or the value is undefined |
setPath(path, value)(obj) |
Returns a new object with the nested value replaced; does not mutate |
updatePath(path, fn)(obj) |
Applies fn to the nested value and returns a new object |
deletePath(path)(obj) |
Removes the nested key and returns a new object; does not mutate |
hasPath(path)(obj) |
Returns true if the nested path exists (even when value is null) |
Example:
const config = { db: { host: 'localhost', port: 5432 } };
setPath(['db', 'port'], 5433)(config);
// { db: { host: 'localhost', port: 5433 } }
getPath(['db', 'port'])(config); // 5432| Function | Description |
|---|---|
clone(obj) |
Deep clone of plain objects, arrays, and Dates |
deepClone(value) |
Deep clone with cycle detection (supports Date, RegExp, Map, Set) |
freeze(obj) |
Recursively Object.freezes the object |
equals(obj1)(obj2) |
Deep structural equality comparison |
deepEquals(a)(b) |
Structural equality with cycle detection (supports Date, RegExp, Map, Set) |
import { ... } from '@tecnomancy/alchemy/string'
| Function | Example |
|---|---|
toUpperCase(str) |
'hello' → 'HELLO' |
toLowerCase(str) |
'HELLO' → 'hello' |
capitalize(str) |
'hello world' → 'Hello world' |
camelCase(str) |
'hello world' → 'helloWorld' |
pascalCase(str) |
'hello world' → 'HelloWorld' |
snakeCase(str) |
'helloWorld' → 'hello_world' |
kebabCase(str) |
'helloWorld' → 'hello-world' |
titleCase(str) |
'hello world' → 'Hello World' |
| Function | Description |
|---|---|
trim(str) |
Removes leading and trailing whitespace |
trimStart(str) |
Removes leading whitespace |
trimEnd(str) |
Removes trailing whitespace |
dedent(str) |
Removes common leading whitespace from all lines |
indent(spaces, char?)(str) |
Adds indentation to every line |
| Function | Description |
|---|---|
split(separator)(str) |
Curried String.prototype.split |
join(separator)(arr) |
Curried Array.prototype.join |
words(str) |
Extracts all words: 'hello world' → ['hello', 'world'] |
lines(str) |
Splits by newline |
| Function | Description |
|---|---|
startsWith(search)(str) |
Curried startsWith |
endsWith(search)(str) |
Curried endsWith |
includes(search)(str) |
Curried includes |
matches(regex)(str) |
Tests a regex against str |
isEmptyString(str) |
true if length is zero |
isBlank(str) |
true if empty or only whitespace |
length(str) |
Returns str.length |
| Function | Description |
|---|---|
replace(search, replacement)(str) |
Replaces first match |
replaceAll(search, replacement)(str) |
Replaces all matches |
slice(start, end?)(str) |
Curried slice |
substring(start, end?)(str) |
Curried substring |
padStart(length, fill?)(str) |
Pads to length from the start |
padEnd(length, fill?)(str) |
Pads to length from the end |
repeat(count)(str) |
Repeats the string count times |
reverse(str) |
Reverses character order |
truncate(maxLength, suffix?)(str) |
Cuts at maxLength, appends suffix |
Replaces {{key}} placeholders using a values object.
Example:
template({ name: 'Alice', role: 'admin' })('{{name}} is {{role}}');
// 'Alice is admin'Printf-style formatting with %s (string) and %d (number) placeholders.
Example:
format('Hello %s, you are %d years old')('Alice', 30);
// 'Hello Alice, you are 30 years old'| Function | Description |
|---|---|
isEmail(str) |
Basic email format check |
isUrl(str) |
URL validity via new URL() |
isNumeric(str) |
true if string represents a finite number |
isAlpha(str) |
Only alphabetic characters |
isAlphanumeric(str) |
Only alphanumeric characters |
isHexColor(str) |
#RRGGBB or #RGB format |
isUUID(str) |
UUID v1–v5 format |
import { ... } from '@tecnomancy/alchemy/predicates'
| Function | Description |
|---|---|
not(pred)(value) |
Inverts a predicate |
and(...preds)(value) |
true if ALL predicates pass |
or(...preds)(value) |
true if ANY predicate passes |
xor(...preds)(value) |
true if EXACTLY ONE predicate passes |
nand(...preds)(value) |
true if NOT ALL predicates pass |
nor(...preds)(value) |
true if NO predicate passes |
Example:
const isValidAge = and(isNumber, between(0, 150));
isValidAge(25); // true
isValidAge(-1); // false
isValidAge('x'); // false
const isAdminOrOwner = or(isAdmin, isOwner);| Function | Description |
|---|---|
gt(threshold)(value) |
value > threshold |
gte(threshold)(value) |
value >= threshold |
lt(threshold)(value) |
value < threshold |
lte(threshold)(value) |
value <= threshold |
eq(target)(value) |
value === target |
neq(target)(value) |
value !== target |
between(min, max)(value) |
min <= value <= max |
| Function | Narrows to |
|---|---|
isString(value) |
string |
isNumber(value) |
number |
isBoolean(value) |
boolean |
isFunction(value) |
(...args: unknown[]) => unknown |
isArray(value) |
unknown[] |
isNull(value) |
null |
isUndefined(value) |
undefined |
isNil(value) |
null | undefined |
isDate(value) |
Date (also validates — isNaN dates return false) |
isRegExp(value) |
RegExp |
isPromise(value) |
Promise<unknown> |
| Function | Description |
|---|---|
isNotNull(value) |
Type guard: narrows T | null | undefined to T |
isNotEmpty(str) |
true if string is non-empty after trimming |
hasProperty(key)(obj) |
true if key exists and its value is not null/undefined |
| Function | Description |
|---|---|
isEven(n) |
n % 2 === 0 |
isOdd(n) |
n % 2 !== 0 |
isPositive(n) |
n > 0 |
isNegative(n) |
n < 0 |
isZero(n) |
n === 0 |
isInteger(n) |
Number.isInteger(n) |
isNotANumber(n) |
Number.isNaN(n) |
isFiniteNumber(n) |
Number.isFinite(n) |
isInfinite(n) |
!isFinite(n) && !isNaN(n) |