fxdart 0.7.6 copy "fxdart: ^0.7.6" to clipboard
fxdart: ^0.7.6 copied to clipboard

A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition.

FxDart

fxdart #

A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition — the FxTS programming model, rebuilt on Dart's type system.

Version codecov

Launch FxDart 101 — Interactive Docs Try the Daily Ledger — Live Demo App Dart vs FxDart — 50 Side-by-Side Examples RxDart vs FxDart — 50 Push-vs-Pull Examples

👆 Click aboveFxDart 101 is a guided course with a live in-browser playground for every function; Daily Ledger is a full app built with fxdart; Dart vs FxDart puts native Dart and fxdart solutions side by side, with an honest verdict on each; RxDart vs FxDart runs the same 50-example format against RxDart — push streams vs pull pipelines, including the cases where RxDart is simply the right tool.

Why fxdart? #

  • Lazy evaluation — operators build a pipeline and do no work until a terminal operator runs, so fx(hugeList).map(f).filter(g).take(3) only computes 3 results.
  • Concurrency you can dialconcurrent(n) evaluates the upstream chain n items at a time while preserving order, turning six 1-second requests into a ~2-second batch with one method call.
  • Type-safe pipelines — the fx() chain keeps full static typing end to end; sync operators are plain functions over native Iterables, so everything interops with ordinary Dart code.
  • One mental model for sync and async — the same operator names work on Iterable (sync) and FxAsyncIterable (async), with Stream bridges in both directions.
  • Typed errors — Kotlin Arrow 2.x's Raise/Either approach, ported: straight-line either blocks instead of flatMap pyramids, error accumulation with NonEmptyList, and validation fused directly into the concurrent pipelines above.

Install #

See the installation guide on pub.dev for the latest version.

AI agent skills #

fxdart ships two Agent Skills that teach AI coding assistants — Claude Code, Codex, Devin, Antigravity, OpenCode, pi, and anything reading .agents/skills/ — when and how to use fxdart:

  • skills/fxdart-pipelines/ — collections, concurrent Futures, Streams, and complex flow logic.
  • skills/fxdart-typed-errors/ — the typed-error system: either blocks, error accumulation, and Either-aware pipeline validation.

Install it with the community skills CLI (auto-detects your IDE/agent):

dart pub global activate skills
skills get fxdart

Or with fxdart's built-in zero-dependency installer:

# From a project that depends on fxdart:
dart run fxdart:install_skills              # auto-detects agent dirs in the project
dart run fxdart:install_skills claude codex # or name agents explicitly
dart run fxdart:install_skills all --global # per-user dirs (~/.claude/skills, ~/.agents/skills, ...)

# Or standalone:
dart pub global activate fxdart
fxdart_skills --global claude

Supported agents: claude (.claude/skills/), codex / antigravity / generic (.agents/skills/), devin (.devin/skills/), opencode (.opencode/skills/), pi (.pi/skills/, global ~/.pi/agent/skills/). --list shows install status; --remove uninstalls.

Usage #

Sync pipelines #

Sync operators are data-first functions over lazy Iterables; the fx() chain composes them with full type inference:

import 'package:fxdart/fxdart.dart';

fx([1, 2, 3, 4, 5])
    .map((a) => a + 10)
    .filter((a) => a % 2 == 0)
    .toList(); // [12, 14]

// Equivalent with top-level functions:
toList(filter((a) => a % 2 == 0, map((a) => a + 10, [1, 2, 3, 4, 5])));

// Laziness: only 3 squares are ever computed.
fx(range(1, 1000000)).map((a) => a * a).take(3).toList(); // [1, 4, 9]

Async pipelines #

Async operators work on FxAsyncIterable<T> — a pull-based protocol ported from FxTS's AsyncIterable handling. Lift values in with toAsync / fromStream (or .toAsync() on a chain), and out with .toList() / .toStream():

await fx([1, 2, 3, 4])
    .toAsync()
    .map((a) async => a + 10) // callbacks may be async
    .filter((a) => a % 2 == 0)
    .toList(); // [12, 14]

// Streams bridge both ways.
await fxStream(Stream.fromIterable([1, 2, 3])).map((a) => a * 2).toList();

Concurrency #

concurrent(n) is FxTS's signature feature, ported faithfully: a concurrency marker travels backwards through the pipeline's iterator protocol, so the upstream chain evaluates n items at once while results stay in order.

// 6 requests of 1s complete in ~2s instead of ~6s.
await fx([1, 2, 3, 4, 5, 6])
    .toAsync()
    .map((id) => fetchUser(id))
    .concurrent(3)
    .toList();

concurrentPool(n) is the completion-order variant (faster first results, no ordering guarantee). This back-channel protocol is why fxdart has its own FxAsyncIterable instead of building on push-based Streams, which cannot express it.

Typed errors #

The either builder runs a block in a Raise<E> scope: each r.bind unwraps a success or short-circuits the whole block with a typed failure — the Kotlin Arrow 2.x model, ported (no TaskEither/IO wrapper tower, no Option; Dart's T? plus the nullable builder covers absence):

Either<String, int> parsePort(String raw) => either((r) {
  final n = r.ensureNotNull(int.tryParse(raw), () => '"$raw" is not a number');
  r.ensure(n > 0 && n < 65536, () => '$n is out of range');
  return n;
});

// Validation accumulates EVERY failure into a NonEmptyList, not just the first:
final user = either<Nel<String>, User>((r) => r.zipOrAccumulate2(
    (r) => validateName(r, input), (r) => validateAge(r, input), User.new));

// And it fuses with pipelines — fail-slow, 8 records in flight, order kept:
final result = await fxStream(records)
    .mapOrAccumulate<String, User>((r, rec) => parseUser(r, rec), concurrency: 8);

Every subject has a detailed tutorial with an in-browser playground: overview · Either · either & the Raise scope · nullable · NonEmptyList · accumulation · Either × pipelines

API overview #

Category Functions
Generate range, repeat, cycle, entries, keys, values
Transform (lazy) map, mapEffect, flatMap, flat, scan, scan1, peek, pluck
Filter (lazy) filter, reject, compact, uniq, uniqBy, difference(By), intersection(By), compress
Slice (lazy) take, takeRight, takeWhile, takeUntilInclusive, drop, dropRight, dropWhile, dropUntil, slice, chunk, split
Combine (lazy) append, prepend, concat, zip, zip3, zipWith, zipWithIndex, transpose, reverse, fork
Aggregate reduce, fold, reduceLazy, toList, sum, sumBy, average, averageBy, min, minBy, max, maxBy, size, join, groupBy, indexBy, countBy, sort, sortBy, toSorted, partition, each, consume
Access head, last, nth, find, findIndex, includes, isEmpty, every, some
Object (Map) omit, pick, omitBy, pickBy, prop, props, evolve, fromEntries, compactObject, resolveProps, isMatch, matches
Function pipe, pipe1, pipeLazy, identity, always, tap, apply, juxt, memoize, negate, not, when, unless, throwError, throwIf, cases, add, gt, gte, lt, lte, delay, sleep, unicodeToArray, .curried/.uncurried (extension getters, arity 2–5)
Predicates isNull, isNotNull, isNil, isBoolean, isNumber, isString, isDate, isList, isMap
Async every lazy/aggregate operator has an *Async twin (mapAsync, toListAsync, ...), plus toAsync, fromStream, concurrentAsync, concurrentPoolAsync, asyncEmpty
Typed errors Either (Left/Right, fold, map, flatMap, recover, Either.catching), either/eitherAsync, nullable/nullableAsync, NonEmptyList/Nel, accumulate, zipOrAccumulate2..5, mapOrAccumulate, bindNel, toEitherNel, rights, lefts, separateEither, sequenceEither; chain terminals rights(), lefts(), separated(), sequence(), mapOrAccumulate()
Util debounce, throttle, shuffle, createSeededRandom
Chains fx() (sync, extends Iterable), fxAsync(), fxStream(); Fx<num>/FxAsync<num> gain sum/average/min/max

Differences from FxTS #

Dart has no function overloads, variadic generics, or conditional types, so some APIs deliberately deviate:

FxTS fxdart
curried data-last (map(f) inside pipe) fx() chain (typed) or dynamic pipe(value, [closures])
one map dispatching sync/async map (Iterable) / mapAsync (FxAsyncIterable); chains use plain names
reduce(f, seed, iter) overload fold(seed, f, iter) (unseeded reduce(f, iter) unchanged)
tuples (zip, entries, partition) Dart records: (A, B)
TS objects (omit, pick, evolve, ...) Map-based equivalents
undefined null (head/find/nth return T?)
toArray / toArrayAsync toList / toListAsync (Dart has no array type)
AsyncIterable / for await FxAsyncIterable + toStream() / fromStream() bridges
variadic zip/juxt/cases fixed arities (zip/zip3) or list/record parameters
curry(f) .curried / .uncurried extension getters — see WHY_CURRIED.md

FxTS's curry needs arity reflection and recursive conditional types, which Dart lacks — so fxdart curries through per-arity extensions instead, resolved statically and fully typed:

int add(int a, int b) => a + b;
final addOne = add.curried(1); // int Function(int)
fx([1, 2, 3]).map(addOne).toList(); // [2, 3, 4]

WHY_CURRIED.md tells the full design story: why the direct port is impossible, how static extension resolution stands in for overloading, why the getter is named curried, and how the same port-the-meaning philosophy resolves the other unportable APIs. Those keep @Deprecated stubs (curry, isUndefined, isArray, isObject, takeUntil) so migrating code gets analyzer guidance instead of silent breakage.

Testing #

The FxTS spec suite has been ported alongside the library: 850+ tests covering sync/async behavior, error propagation, laziness, and concurrency timing across every operator.

dart test

Coverage is measured on every push and pull request and reported to Codecov. To reproduce locally:

dart run coverage:test_with_coverage   # writes coverage/lcov.info

Acknowledgments #

Great thanks to Indong Yoo, CTO of Marpple, the creator of FxTS (and FxJS before it), whose functional programming model — lazy iteration with first-class, order-preserving concurrency — this library ports to Dart. All core ideas, operator semantics, and the original test suite come from the marpple/FxTS repository.

Author #

👤 Bansook Nam

🤝 Contributing #

Contributions, issues and feature requests are welcome! Feel free to check the issues page.

📝 License #

Copyright © 2023 Bansook Nam.

This project is MIT licensed.

3
likes
0
points
1.22k
downloads

Publisher

verified publisherbansook.xyz

Weekly Downloads

A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition.

Repository (GitHub)
View/report issues

License

unknown (license)

More

Packages that depend on fxdart