Lightweight, type-safe functional programming utilities for TypeScript. Result, Maybe, Try, Unit, and friends — ESM-only, no runtime dependencies, designed for first-class interoperability with @deessejs/errors.
Sibling projects:
@deessejs/errorsprovides error types that integrate natively with@deessejs/fp'sResultandTry. Install them together to get a complete error-handling story without glue code. Used internally at deessejs.com.
| Layer | What you get | Why it matters |
|---|---|---|
Result<T, E> |
ok, err, pattern matching, sequencing |
Type-safe error handling without exceptions or nulls. |
Maybe<T> |
some, none, maybe, map, getOrElse |
Optional values that compose. |
Try<T> |
try, tryAsync, conversion to Result |
Wrap throwing functions in a typed shell. |
Unit |
The unit type for void-returning operations | Express "no value" without null or undefined. |
| Functional utilities | pipe, flow, identity, constant, flip, tupled |
Compose functions without ad-hoc helpers. |
| Async utilities | sleep, retry, timeout, Queue |
Time-based primitives that compose with Result. |
| Predicate utilities | Predicate, Refinement, not, and, or |
First-class predicates and type guards. |
| Collection types | Context, Sequence, Collection, async iterator helpers |
Sequence operations over various sources. |
| Generator composition | gen() with yield* |
Async flow control that reads like sync code. |
@deessejs/errors integration |
All Result constructors accept @deessejs/errors |
No string-error footguns — use real error types. |
- Simple by default. No over-engineering, no fancy type gymnastics. Just the primitives you need to write cleaner code.
- ESM-only. Modern packaging, no CJS shim, no
module/mainduplication. - Zero runtime dependencies. The only peer dep is
@deessejs/errors, which is opt-in. The library itself is dependency-free. - TypeScript 6 first-class. Strict types, no
anyleakages, full inference. JSDoc where types alone are not enough. - Lockfile-clean pnpm workspaces. A single
pnpm installrebuilds, lints, types, and tests the whole monorepo. - Real testing. Vitest, with coverage and integration tests against @deessejs/errors.
- Node.js 22.14.0+ (
engines.nodeenforced) - pnpm 10+ for development (
corepack enableif not installed) - TypeScript 6+ for consumers (the package emits
dist/*.d.ts)
# Install @deessejs/fp with its optional sibling, @deessejs/errors.
# See https://github.com/deessejs/errors
npm install @deessejs/fp @deessejs/errors@deessejs/errors is optional — install it if you want Result<T, E> to carry typed errors instead of strings.
import { ok, err, some, none, maybe, pipe } from '@deessejs/fp';
// Result: represent values that may have failed
const divide = (a: number, b: number) =>
b === 0 ? err('Division by zero') : ok(a / b);
const result = divide(10, 2);
result.match({
ok: (value) => console.log(`Result: ${value}`),
err: (error) => console.error(`Error: ${error}`),
});
// Maybe: represent optional values
const user = { name: 'Alice', address: { city: 'Paris' } };
const city = maybe(user.address?.city)
.map((c) => c.toUpperCase())
.getOrElse('Unknown');
// pipe: compose functions without glue
const trim = (s: string) => s.trim();
const uppercase = (s: string) => s.toUpperCase();
const processed = pipe(' hello ', trim, uppercase);| Runtime | Minimum version |
|---|---|
| Node.js | 22.14.0 |
| pnpm | 10 (for development) |
| TypeScript | 6.0 |
ESM-only. Consumers using a CJS resolver need to use dynamic import() or migrate to ESM.
| Command | What it does |
|---|---|
pnpm build |
Build every workspace |
pnpm test |
Run all tests in watch mode |
pnpm test:run |
Run all tests once |
pnpm lint |
Lint every workspace |
pnpm type-check |
Type-check every workspace |
pnpm format |
Format with Prettier |
| Command | What it does |
|---|---|
pnpm --filter @deessejs/fp build |
Build dist/ |
pnpm --filter @deessejs/fp test |
Run vitest in watch mode |
pnpm --filter @deessejs/fp test:run |
Run vitest once |
pnpm --filter @deessejs/fp type-check |
tsc --noEmit |
pnpm --filter @deessejs/fp lint |
Run ESLint |
| Command | What it does |
|---|---|
pnpm --filter web dev |
Start the docs site in dev mode |
pnpm --filter web build |
Build the docs site |
| Package | Required | Notes |
|---|---|---|
@deessejs/errors |
Optional, peer >=1.0.0 |
Required if you want err() to accept typed errors. Listed as a devDependency for testing. |
| Field | Value |
|---|---|
engines.node |
>=22.14.0 |
packageManager |
pnpm@10.30.3 |
.
├── packages/
│ └── fp/ # The library — @deessejs/fp on npm
│ ├── src/ # Source code (ESM)
│ ├── dist/ # Build output (gitignored)
│ ├── vitest.config.ts
│ └── tsconfig.build.json
├── apps/
│ └── web/ # Documentation site (Next.js + Fumadocs)
├── docs/
│ ├── internal/ # Engineering plans, runbooks
│ │ ├── product/
│ │ └── versions/
│ └── CLAUDE.md # Claude / agent guidance
├── pnpm-workspace.yaml
├── turbo.json # Turborepo pipelines
├── .changeset/ # Changesets for versioning
└── README.md
Releases are fully automated via Changesets + npm Trusted Publishing (OIDC). No long-lived NPM_TOKEN is required.
| What | How |
|---|---|
| Bump version | Add a .changeset/<topic>.md file with semver + description |
| Open the release PR | changesets-version.yml opens / updates a "Version Packages" PR from staging to main |
| Publish | Merge the Version Packages PR → publish.yml runs → version bump committed → Trusted Publishing publishes to npm with provenance attestation |
| Hotfix | Push a tag vX.Y.Z to main → same workflow runs for the hotfix path |
| Rollback or deprecate | Planned: see docs/engineering/plans/release-pipeline-github-ui-setup.md |
For the full pipeline design, see docs/engineering/plans/release-pipeline.md.
- ESM-only. The package exports ES modules. Consumers using legacy CJS resolvers must use dynamic
import(). - Strict types.
Result.matchrequires both branches;Maybe.getOrElserequires a fallback. No partial type escapes. - Composition over inheritance. All primitives compose via
pipeandflow. No class hierarchy, noextends. - Zero-runtime abstractions. No decorators, no reflection, no proxy traps. The library is straightforward to read in DevTools and
node --prof. - Smoke-tested before publish. The release workflow runs a dynamic ESM import of the built artifact and verifies that key exports are present. A broken build fails the publish step before reaching npm.
@deessejs/errorsis opt-in. The peer dep stays optional so consumers can adopt@deessejs/fpin isolation. Once@deessejs/errorsis added, everyerr(error)call accepts a typed error.- One source of truth for auth-style errors. The
apps/app/proxy.ts(in the broader deessejs monorepo, not in this repo) enforces email verification at the proxy level. @deessejs/errors errors are caught and translated to HTTP responses centrally.
Open an issue to discuss larger changes. For typos, broken links, and small fixes, PRs are welcome.
Before submitting a PR:
- Run
pnpm --filter @deessejs/fp test:runandpnpm --filter @deessejs/fp lint. - Add a
.changeset/<topic>.mdif the change is user-facing (patch / minor / major). - Update
docs/internal/product/README.mdif the API surface changes.
MIT. See the LICENSE file for details.
- Issues: github.com/deessejs/fp/issues
- Discussions: github.com/deessejs/fp/discussions
- Email: support@deessejs.com
- Documentation: fp.deessejs.com