fxdart 0.5.3
fxdart: ^0.5.3 copied to clipboard
A functional programming library for Dart, ported from FxTS. Lazy evaluation, concurrent async iteration, and pipeline-style composition.
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.
👆 Click above — FxDart 101 is a guided course with a live in-browser playground for every function; Daily Ledger is a full app built with fxdart.
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 dial —
concurrent(n)evaluates the upstream chainnitems 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 nativeIterables, so everything interops with ordinary Dart code. - One mental model for sync and async — the same operator names work on
Iterable(sync) andFxAsyncIterable(async), withStreambridges in both directions.
Install #
See the installation guide on pub.dev for the latest version.
AI agent skills #
fxdart ships an Agent Skill (skills/fxdart-pipelines/)
that teaches AI coding assistants — Claude Code, Codex, Devin, Antigravity,
OpenCode, pi, and anything reading .agents/skills/ — when and how to use
fxdart for collections, concurrent Futures, Streams, and complex flow logic.
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.
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 |
| 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
- Website: https://github.com/bansooknam
- Github: @bansooknam
🤝 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.