A purely functional, dependency-free dependency injection library for TypeScript based on the Reader Monad.
- Purely Functional: Built on the Reader Monad pattern.
- Type-Safe: Full TypeScript support with excellent type inference.
- Zero Dependencies: Lightweight and fast.
- Composable: Utilities for mapping, chaining, and adapting environments.
- Curried: All functions are fully curried for maximum flexibility.
While many libraries offer functional programming and dependency management in TypeScript, functional-reader focuses on being a lightweight, specialized tool.
| Feature / Goal | functional-reader | fp-ts (Reader) | lodash/fp |
|---|---|---|---|
| Reader Monad / Dependency Injection | Core feature | Included module | Not available |
| Zero runtime dependencies | Yes | No (requires fp-ts) | No (lodash dependency) |
| TypeScript support & inference | Excellent | Excellent | Partial (not Reader-aware) |
| Lightweight / Minimal bundle | Yes | No (fp-ts is large) | No (lodash is large) |
| API Simplicity | Minimal, approachable | Broader/complex | Simple, but not monadic |
| Curried / Point-Free APIs | Yes | Yes | Partial |
| Explicit error handling (neverthrow compatible) | Yes | Yes (with effort) | No |
| Do-notation & record chaining helpers | Included | Included | No |
| Pure FP without typeclass bloat | Yes | No (full typeclass) | Yes (pure, but not Reader) |
- Focused: A small, dependency-free implementation of the Reader monad.
- TypeScript-native: Designed for first-class type inference and autocompletion.
- Lightweight: Zero dependencies and minimal bundle size overhead.
- Approachable: A readable API that is easy to learn and integrate.
- Interoperable: Designed to play well with other modern libraries like
neverthrow.
functional-reader is unopinionated, making it easy to integrate into existing TypeScript projects:
| Interop Scenario | Support Description |
|---|---|
| neverthrow | Returns or operates on Result<T, E> out-of-the-box. |
| fp-ts | Compatible API surface; easily migrated or combined. |
| RxJS / Promises | Readers can be evaluated inside observables or async flows. |
| Legacy Code | Since a Reader is just (env) => value, it plugs in directly everywhere. |
| Standard Utilities | Works seamlessly with standard JS utilities (lodash, ramda, etc.). |
Note: functional-reader is distributed directly via this repository to emphasize transparency, autonomy, and source engagement over reliance on third-party registries. This approach allows users to review, audit, and tailor the library to their needs, while avoiding potential risks or restrictions imposed by centralized package platforms.
# Install the latest version from GitHub
npm install github:dominicegginton/functional-reader
# Or install a specific version/tag
npm install github:dominicegginton/functional-reader#v1.0.0Reader<Env, Result>: A computation that requires an environmentEnvto produce a resultResult.
of<Env, A>(a: A): Creates a Reader that always returnsa.defer<Env, A>(f: () => Reader<Env, A>): Creates a Reader that is lazily initialized when it is called.ask<Env>(): Creates a Reader that returns the entire environment.asks<Env, A>(selector: (env: Env) => A): Creates a Reader that selects a part of the environment.asksReader<Env, A>(f: (env: Env) => Reader<Env, A>): Creates a Reader that selects a part of the environment and returns a new Reader.prop<Env, K extends keyof Env>(key: K): Creates a Reader that extracts a specific property from the environment.pick<Env, K extends keyof Env>(keys: readonly K[]): Creates a Reader that selects multiple properties from the environment.
map<A, B>(f: (a: A) => B): Transforms the result of a Reader.mapTo<B>(value: B): Maps the result of a Reader to a constant value.chain<A, Env, B>(f: (a: A) => Reader<Env, B>): Sequences two Readers where the second depends on the first.chainFirst<A, Env, B>(f: (a: A) => Reader<Env, B>): Sequences two Readers where the second depends on the first, but returns the result of the first.flatten<Env, A>(mma: Reader<Env, Reader<Env, A>>): Collapses a nested Reader into a single Reader.local<EnvOuter, EnvInner>(f: (outer: EnvOuter) => EnvInner): Adapts a Reader to a different environment type.provide<Env>(env: Env): Provides an environment to a Reader, resulting in a Reader that requires no environment.ap<Env, A>(fa: Reader<Env, A>): Applies a function contained within a Reader to a value in another Reader.
chainRight<Env, B>(rb: Reader<Env, B>): Sequences two Readers and returns the result of the second.chainLeft<Env, B>(rb: Reader<Env, B>): Sequences two Readers and returns the result of the first.tap<Env, A>(sideEffect: (a: A, env: Env) => void): Performs a side effect without changing the Reader's result.
sequence<Env, A>(readers: Array<Reader<Env, A>>): Combines an array of Readers into one that returns an array of results.traverse<A, Env, B>(f: (a: A) => Reader<Env, B>): Maps over an array of values using a function that returns a Reader.struct<Env, S>(readers: S): Combines an object of Readers into one that returns an object of results.zip<Env, A, B>(rb: Reader<Env, B>): Combines two Readers into a single Reader that returns a tuple of their results.iif<Env, A>(predicate: (env: Env) => boolean, onTrue: Reader<Env, A>, onFalse: Reader<Env, A>): Conditionally chooses between two Readers based on the environment.when<Env, A>(predicate: (env: Env) => boolean, reader: Reader<Env, A>): Conditionally executes a Reader if the predicate is true.alt<Env, A, B>(secondary: Reader<Env, B>): Returns the result of the first Reader if it is not null or undefined, otherwise returns the result of the second Reader.
Do<Env>(): Initializes a "Do" block for chainable record compositions.bindTo<K extends string>(key: K): Wraps a Reader's result into a record with a specified key.bind<K extends string, A, EnvB, B>(key: K, f: (a: A) => Reader<EnvB, B>): Binds the result of a Reader to a key in a "Do" block.
pipe(a, ...fns): Pipes a value through a series of functions.flow(...fns): Composes multiple functions into a single function.