fxdart 0.8.0 copy "fxdart: ^0.8.0" to clipboard
fxdart: ^0.8.0 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

// 6 requests of 1s complete in ~2s โ€” not ~6s.
await fx(userIds).toAsync().map(fetchUser).concurrent(3).toList();

๐Ÿš€ Try it in your browser #

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 any badge above. Each one is a live, runnable site:

Site What it is
๐Ÿ“š FxDart 101 A guided course with an in-browser playground for every function
๐Ÿ“’ Daily Ledger A full app built with fxdart, running live
โš–๏ธ Dart vs FxDart 50 problems solved both ways, with an honest verdict on each
โšก RxDart vs FxDart The same 50-example format vs RxDart โ€” push streams vs pull pipelines, including the cases where RxDart is simply the right tool

๐Ÿ“– Contents #

โœจ Why fxdart? ยท ๐Ÿ“ฆ Install ยท ๐Ÿค– AI agent skills ยท ๐Ÿ› ๏ธ Usage ยท ๐Ÿ“‡ API overview ยท ๐Ÿ”€ Differences from FxTS ยท ๐Ÿงช Testing ยท ๐Ÿ™ Acknowledgments


โœจ 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 ever computes 3 results.

๐Ÿ”€ Concurrency you can dial #

concurrent(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.

โšก A push side too, when time matters #

Pull pipelines model data over demand; fxEvents() models events over time on plain Dart Streams โ€” debounce, throttle, sample, switchMap, combineLatest and friends โ€” then hands you back to the typed pull world with .pull().

๐ŸงŠ Dart names work too #

Every FxTS name that Dart's collections already have a word for is also callable by that word: where, expand, flattened, nonNulls, sorted, indexed, firstWhereOrNull. No dialect to learn before you can read the code.


๐Ÿ“ฆ 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:

Skill Covers
๐Ÿ”— skills/fxdart-pipelines/ Collections, concurrent Futures, Streams, and complex flow logic
๐ŸŽฏ skills/fxdart-typed-errors/ The typed-error system: either blocks, error accumulation, Either-aware pipeline validation

Option A โ€” the community skills CLI (auto-detects your IDE/agent):

dart pub global activate skills
skills get fxdart

Option B โ€” 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:

Agent Install dir
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) โ€” 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.

๐Ÿ“ก Events (the push side) #

Some problems really are events over time, not data over demand. fxEvents() wraps a plain Dart Stream in a chainable, Rx-flavoured API โ€” a thin wrapper, never an extension, so it coexists with rxdart without member conflicts:

final results = await fxEvents(keystrokes)
    .debounce(const Duration(milliseconds: 160))
    .switchMap((q) => search(q).asStream()) // cancels the superseded search
    .toList();

// Cross back into the typed pull world at any point:
await fxEvents(ticks).sampleOn(clock).pull().map(load).concurrent(4).toList();

LiveValue holds a current-value stream, and FxSubscriptions cancels a bag of subscriptions together. See โšก RxDart vs FxDart for 50 worked examples โ€” including the cases where RxDart is the better fit.

๐ŸŽฏ 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, mapWithIndex, mapEffect, flatMap, flatMapWithIndex, flat, scan, scan1, peek, pluck, attach, using
๐Ÿ” Filter (lazy) filter, filterWithIndex, reject, compact, uniq, uniqBy, uniqAdjacent, uniqAdjacentBy, difference, differenceBy, intersection, intersectionBy, compress
โœ‚๏ธ Slice (lazy) take, takeRight, takeWhile, takeWhileRight, takeUntilInclusive, drop, dropRight, dropWhile, dropWhileRight, dropUntil, slice, chunk, windowed, pairwise, split
๐Ÿงฉ Combine (lazy) append, prepend, concat, zip, zip3, zipWith, zipWithIndex, transpose, reverse, fork, tee, tee3
๐Ÿ“Š Aggregate reduce, fold, foldWithIndex, foldRight, foldRightWithIndex, reduceLazy, toList, sum, sumBy, sumStrings, average, averageBy, min, minBy, max, maxBy, size, join, groupBy, indexBy, countBy, sort, sortBy, sortByDesc, toSorted, partition, each, consume
๐ŸŽฏ Access head, last, nth, find, findIndex, includes, isEmpty, defaultIfEmpty, ifEmpty, every, some
๐Ÿ—‚๏ธ Object (Map) omit, pick, omitBy, pickBy, prop, props, evolve, fromEntries, mapKeys, mapValues, mapEntries, compactObject, resolveProps, isMatch, matches
๐Ÿงฎ Function pipe, pipe1, pipeLazy, identity, always, noop, tap, apply, juxt, memoize, negate, not, when, unless, throwError, throwIf, cases, add, gt, gte, lt, lte, delay, sleep, unicodeToList, .curried/.uncurried (extension getters, arity 2โ€“5)
โœ… Predicates isNull, isNotNull, isNil, isBoolean/isBool, isNumber/isNum, isString, isDate/isDateTime, isList, isMap
๐ŸงŠ Dart-idiomatic aliases Every FxTS name that Dart's own collections already have a word for is also callable by that word: where, whereNot, expand, flattened, nonNulls, distinct, distinctBy, sorted, indexed, skip, skipWhile, takeLast, count, countWhere, any, forEach, firstOrNull, lastOrNull, firstWhereOrNull, elementAtOrNull, indexWhere
โณ Async Every lazy/aggregate operator has an *Async twin (mapAsync, toListAsync, โ€ฆ), plus toAsync, fromStream, mapConcurrent, concurrentAsync, concurrentPoolAsync, asyncEmpty
โšก Events (push) fxEvents() / FxEvents โ€” an Rx-flavoured chain over plain Streams: debounce, throttle, sample, sampleOn, delay, spaceBy, startWith, startOn, stopOn, chunk, chunkEvery, chunkOn, switchMap, mergeMap, concatMap, exhaustMap, asyncMap, merge, mergeWith, race, raceWith, zip, zipWith, combineLatest, combineLatestAll, withLatestFrom, waitAll, share, retry, onErrorResume, onErrorReturn, pull (back into the typed pull world). Plus LiveValue and FxSubscriptions
๐ŸŽฏ Typed errors Either (Left/Right, fold, map, flatMap, recover, Either.catching, toEitherNel), either/eitherAsync, eitherCatching, nullable/nullableAsync, catching, foldRaise, NonEmptyList/Nel; in a Raise scope: r.bind, r.bindNel, r.ensure, r.ensureNotNull, r.accumulate, r.zipOrAccumulate2..5, r.mapOrAccumulate; free functions rights, lefts, separateEither, sequenceEither, mapOrAccumulate, flattenOrAccumulate; chain terminals rights(), lefts(), separated(), sequence(), mapOrAccumulate()
๐Ÿงฐ Util debounce, throttle, retry, shuffle, createSeededRandom
โš™๏ธ Config FxDart.config / FxConfig โ€” process-wide switches, read when a pipeline starts iterating
โ›“๏ธ Chains fx() (sync, extends Iterable), fxAsync(), fxStream(), fxEvents(); 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

๐Ÿ› Why .curried instead of curry? #

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 APIs 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, and grown well past it: 1,800+ tests across 170 files, covering sync/async behavior, error propagation, laziness, typed errors, the events layer, 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
140
points
1.22k
downloads
screenshot

Documentation

API reference

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

MIT (license)

More

Packages that depend on fxdart