fluent-iterable 0.0.2

Fluent extension trait for std Iterator, port of @codibre/fluent-iterable vocabulary: joins, grouping, distinctness, cardinality checks
Documentation

fluent-iterable (Rust)

The fluent-iterable vocabulary for Rust — an extension trait over std::iter::Iterator, published on crates.io as fluent-iterable.

JavaScript needed fluent iterator chains, and this monorepo shipped them. Rust's std already owns most of that surface (map, filter, flat_map, any, fold, …) — but not the parts with real algorithmic content: stable grouping, equi-joins, distinctness checks, cardinality short-circuits, nested indices. This crate ports exactly that residue, in the same operation names, as one extension trait:

use fluent_iterable::FluentIter;

let orders = [(1, 100), (2, 200), (1, 50)];
let customers = [(1, "Ada"), (2, "Grace")];

// inner join, like .combine(other, keySelf, keyOther) — lazy: the left
// side streams, resolve with collect()
let lines: Vec<(&i32, &(i32, &str))> = orders
    .iter()
    .combine(customers.iter(), |(o, _)| *o, |(id, _)| *id)
    .collect();

// stable grouping, like .group(keyFn) — lazy, full pass on first pull
let by_customer: Vec<(&i32, Vec<(&i32, &i32)>)> =
    orders.iter().group_by_stable(|(c, _)| *c).collect();

// fold + project, like .reduceAndMap(reducer, initial, mapFn)
let report = orders
    .iter()
    .map(|(_, v)| *v)
    .reduce_and_map(0, |acc, v| acc + v, |sum| format!("total {sum}"));
assert_eq!(report, "total 350");

No wrapper type, no runtime dependencies, nothing to learn beyond your iterators: use fluent_iterable::FluentIter; and chain.

npm ⇄ Rust map

Operations this crate provides (npm name → Rust method):

npm fluent-iterable Rust
.group(keyFn) group_by_stable (lazy; full pass on first pull)
.combine(other, ka, kb) combine (lazy; left streams)
.combineJoin(baseKey) combine_join (lazy)
.distinct() distinct (lazy adapter)
.distinct(keyFn) distinct_by (lazy adapter)
.isDistinct() is_distinct (short-circuits)
.isDistinct(keyFn) is_distinct_by (short-circuits)
.toMap(k, v) to_map_by
.toMapChain(k1, k2) to_map_chain (depth 2)
.toMapChainReduce(…) to_map_chain_reduce (depth 2)
.join(sep) join
.join(sep, mapFn) join_by
.avg() / .avg(mapFn) avg / avg_by (Option<f64>, None empty)
.sort(cmp?) sorted / sort_with (stable, lazy wrapper)
.sortBy(key) / desc sort_by / sort_by_desc (stable, lazy wrapper)
.hasExactly(n) has_exactly
.hasLessThan(n) has_less_than
.hasLessOrExactly(n) has_less_or_exactly
.hasMoreThan(n) has_more_than
.hasMoreOrExactly(n) has_more_or_exactly
.reduceAndMap(r, i, m) reduce_and_map (init first, Rust-style)
.contains(item) contains
.whenEmpty(fb) when_empty (lazy adapter)
.repeat(n) repeat (finite cycle, lazy adapter)

Operations you do not need here — std already has them: map, filter, filter_map, flat_map/flatten, take, skip, take_while, skip_while, count, any/all, sum, min, max, fold (reduce), chain (concat/append), enumerate (withIndex), collect (toArray), slice::chunks (partition), max_by_key (top).

What is deliberately not ported

npm surface why
every *Async operation, flatMerge stable Rust has no AsyncIterator trait; revisit when the language lands it.
emit / catch / finally / execute / waitAll JS emitter/lifecycle surface; Result, Drop and stream crates own this in Rust.
toObject / toObjectChain* / unwind dynamic-property idioms; struct selection via flat_map is the Rust answer.
branch needs fan-out buffering (tee); candidate for v2.
distinct(key, choose, maxOcurrences) extras v1 is first-wins; a chooser/max-occurrences flavor can land later.
toMapChain arbitrary key depth Rust fixes the concrete nested type at depth 2; deeper nesting folds layers.

Intentional semantics upgrades are marked in the rustdoc (avg returning Option<f64> instead of NaN, stable sorts by Ord instead of JS string coercion, explicit initial value for to_map_chain_reduce).

Development (inside this monorepo)

This directory is both a cargo package and a pnpm workspace member, so the usual root commands drive it:

pnpm install                                  # registers the member
pnpm --filter fluent-iterable-rs test         # cargo test
pnpm --filter fluent-iterable-rs lint         # cargo fmt --check + clippy -D warnings
turbo run test --filter=fluent-iterable-rs    # via turbo, with caching
cargo test                                    # or plain cargo, inside this dir

Coverage locally (not wired into the turbo test:coverage task on purpose — CI runners have no cargo-llvm-cov):

cargo install cargo-llvm-cov
cargo llvm-cov --all-targets --lcov --output-path lcov.info

Publishing to crates.io is done from this directory with cargo publish (the package.json exists only to bridge pnpm/turbo; the crate itself has zero npm coupling).