Non-Empty Collections — Compile-Time Guarantees
Functions that operate on collections often assume at least one element exists (such as head,
min, max, or bulk database inserts). Because standard arrays (readonly T[]) can be empty,
functions must either return undefined, throw runtime errors, or require defensive length checks.
NonEmpty provides compile-time branded types—NonEmptyArr<A>, NonEmptyStr, NonEmptySet<A>,
NonEmptyMap<K, V>, NonEmptyRec<K, V>, and NonEmptyDict<V>—guaranteeing that a collection
contains at least one element:
Every access to the head of a potentially empty collection is unsafe. pipelined solves this by
introducing compile-time type-level guarantees that collections, strings, sets, or maps contain at
least one element: Arr.NonEmpty, Rec.NonEmpty, Uniq.NonEmpty, Dict.NonEmpty, and
Str.NonEmpty.
Type Structure
Section titled “Type Structure”Under the hood, non-empty collections leverage TypeScript’s type system:
Arr.NonEmpty<A>: Represented as a structural read-only tuple structurereadonly [A, ...A[]]. Because it extends the standardreadonly A[]interface, it is assignable to standard arrays without conversion.- Branded Non-Empty Types:
Rec.NonEmpty,Uniq.NonEmpty,Dict.NonEmpty, andStr.NonEmptyutilize compile-time phantom brand tags. This guarantees the presence of elements at compile-time while remaining directly assignable to their standard, unbranded counterparts:Uniq.NonEmpty<A>is assignable toReadonlySet<A>Dict.NonEmpty<K, V>is assignable toReadonlyMap<K, V>Rec.NonEmpty<A, K>is assignable toReadonly<Record<K, A>>Str.NonEmptyis assignable tostring
Creating Non-Empty Collections
Section titled “Creating Non-Empty Collections”When receiving collections or strings at the boundaries of your system (such as reading from database queries, parsing API payloads, or processing user inputs), you can refine them using type guards or lift them from known values:
Singletons
Section titled “Singletons”If you already have a value, you can construct a non-empty array, record, set, or map directly:
Refinement (Type Guards)
Section titled “Refinement (Type Guards)”You can refine standard collections or strings using module-level is.nonEmpty type guards. Inside
the conditional blocks, TypeScript automatically narrows the types:
Safe Conversions (Maybe)
Section titled “Safe Conversions (Maybe)”To safely convert potentially empty collections or strings into optional values, use the
module-specific from* helpers. They return Some if the collection/string contains
elements/characters, and None if it is empty:
Modifying Non-Empty Arrays
Section titled “Modifying Non-Empty Arrays”Adding elements to standard arrays naturally guarantees a non-empty result. The module helpers
Arr.prepend and Arr.append accept standard, potentially empty arrays and return a guaranteed
Arr.NonEmpty:
Transformations and Reductions
Section titled “Transformations and Reductions”Standard library mapping functions operating on standard collections (such as Arr.map or
Rec.map) return standard collections, discarding any compile-time guarantee that the collection is
non-empty.
To transform the elements of a non-empty collection while preserving its non-empty type guarantee,
always use the dedicated mapping helpers under the NonEmpty drawer of the module (e.g.,
Arr.NonEmpty.map, Rec.NonEmpty.map):
When reducing collections down to a single value, you can use the NonEmpty.reduce helpers. Unlike
standard reductions, these do not require an initial accumulator seed value because the collections
are guaranteed to contain at least one element:
Problems it solves
Section titled “Problems it solves”- Enforcing non-empty batch payloads at API boundaries: Endpoints and service methods (such as
bulk database mutations, multi-item checkouts, or batch deletion jobs) require at least one item
to execute validly. Requiring
Arr.NonEmpty<A>orRec.NonEmpty<K, V>guarantees presence at compile time, eliminating defensiveif (arr.length === 0)checks across internal layers. - Guaranteeing error payloads in validation failures: When a validation or multi-rule check
fails, the failure container must contain at least one error message. Returning an empty error
list is an impossible state that confuses UI renderers.
Validationutilizes non-empty collections to ensure failure variants always contain actionable errors. - Navigation breadcrumbs and hierarchical trees: Data models like navigation trails or
organizational hierarchies always possess at least one root node. Modeling breadcrumbs as
Arr.NonEmpty<Crumb>guarantees that extracting the active leaf (Arr.NonEmpty.last) or root (Arr.NonEmpty.head) is always safe and immediate. - Guaranteed non-empty record keys (
Rec.NonEmpty.keys): Extracting keys or values from a verified non-empty dictionary returnsArr.NonEmpty<K>, preserving non-empty guarantees across subsequent transformation stages. - Eliminating mathematically undefined operations on empty sets: Operations like finding
extremes (
Math.min(...[]) === Infinity), reducing collections without fallback values, or computing statistical averages (sum / lengthproducingNaNon empty arrays) are mathematically undefined on empty collections.NonEmptycollections guarantee at least one element exists, makinghead,min,max, and reduction operations structurally safe and non-optional.