Uniq — Unique Collections
2.04 kB minified + gzipped
JavaScript’s native Set is the standard data structure for storing unique values and performing
O(1) membership checks. However, using native Set instances in TypeScript pipelines and
state management introduces two recurring pitfalls:
- In-place mutation: Calling
.add()or.delete()mutates theSetdirectly. If you forget to clone the set (new Set(existing)) before modifying it, you silently mutate shared state across function boundaries and break reference-equality checks in UI frameworks. - No pipeline combinators: Native
Setlacks.map(),.filter(), and.reduce(), forcing you to spread sets into temporary arrays (new Set([...items].map(fn))) whenever you need to transform or filter elements.
Uniq solves this by providing pure, curried, data-last operations over standard
ReadonlySet<A> instances. Every modification returns a new set when membership changes — or returns
the original set reference unchanged when inserting an existing item or removing an absent one.
flowchart TB
Original["Original ReadonlySet"] --> Operation["Uniq.insert(item)"]
Operation --> Check{"Is item already in set?"}
Check -->|"Yes (No membership change)"| Same["Original Set Reference (===)"]
Check -->|"No (New member)"| Cloned["New ReadonlySet (with item added)"]
Creating Collections
Section titled “Creating Collections”You can construct an immutable ReadonlySet<A> from an array, a single item, or an empty seed:
Checking Membership and Size
Section titled “Checking Membership and Size”To inspect the contents of a set inside a pipeline, use Uniq.has, Uniq.size, Uniq.is.empty,
Uniq.is.nonEmpty, and Uniq.is.subsetOf:
Adding, Removing, and Toggling Items
Section titled “Adding, Removing, and Toggling Items”Uniq.insert adds an element, Uniq.remove deletes an element, and Uniq.toggle adds the element
if absent or removes it if already present (ideal for multi-select UI filters and tag pickers). When
insert or remove results in no change to the set, Uniq returns the exact same ReadonlySet
reference to avoid unnecessary allocations:
Transforming and Filtering Collections
Section titled “Transforming and Filtering Collections”Uniq.map transforms every element into a new ReadonlySet<B> (automatically deduplicating any
colliding outputs), and Uniq.filter keeps only elements that satisfy a predicate. When you want to
transform elements and discard invalid ones in a single pass, Uniq.filterMap applies a
Maybe-returning callback and collects only the unwrapped Some values:
Classic Set Algebra
Section titled “Classic Set Algebra”Uniq provides pure, data-last implementations of union, intersection, and difference for
reconciling permissions, merging feature flags, or computing state diffs.
Folding and Converting to Arrays
Section titled “Folding and Converting to Arrays”Use Uniq.reduce to fold a set into a single value, and Uniq.to.array when you need to serialize
a set to JSON or pass it to an API expecting readonly A[]:
Non-Empty Sets: Uniq.NonEmpty
Section titled “Non-Empty Sets: Uniq.NonEmpty”When your domain requires a set guaranteed to contain at least one element, use the branded type
Uniq.NonEmpty<A>. You can construct one directly with Uniq.NonEmpty.singleton, narrow a set with
the Uniq.is.nonEmpty type guard, or refine an existing ReadonlySet<A> into
Maybe<Uniq.NonEmpty<A>> via Uniq.NonEmpty.from.set:
With a Uniq.NonEmpty<A>, Uniq.NonEmpty.reduce folds the set using its first element as the
initial accumulator (requiring no seed value), Uniq.NonEmpty.map transforms elements while
preserving the Uniq.NonEmpty<B> guarantee, and Uniq.NonEmpty.to.array exports an
Arr.NonEmpty<A> tuple:
For more details on non-empty collection guarantees, see the Non-Empty Collections Guide.
Pipeline Composition
Section titled “Pipeline Composition”Here is an end-to-end access-control pipeline that normalizes raw scope strings from an OAuth token, adds a default baseline scope, strips revoked scopes, and exports the effective permission list as an array:
Problems it solves
Section titled “Problems it solves”- Managing unique collections in immutable state trees: In React, Vue, or Zustand state models,
mutating native
Setobjects in place bypasses change detection and causes UI rendering bugs.Uniqenforces structural immutability, returning fresh references on updates while reusing existing references when operations produce no changes. - Permission algebra and role reconciliation: In role-based access control (RBAC), applications
frequently need to compute missing permissions (
Uniq.difference), combine group privileges (Uniq.union), or find overlapping scopes (Uniq.intersection).Uniqprovides immutable, data-last set algebra operations. - Entitlement and prerequisite checks (
Uniq.is.subsetOf): Verifying that a user’s permission set covers all required scopes for an API endpoint or that a cart’s tags meet promotional prerequisites in a single declarative comparison. - Interactive multi-select and tag toggling (
Uniq.toggle,Uniq.insert,Uniq.remove): In search filter components and tag editors, toggling or updating selected items insidepipepipelines without managing temporary arrays or mutable set instances. - Pipeline set mapping and filtering (
Uniq.map,Uniq.filter,Uniq.filterMap,Uniq.reduce): Transforming or filtering unique collections while maintaining uniqueness and non-emptiness (Uniq.NonEmpty) without spreading sets into intermediate arrays.