API reference › @evolu/common › Array
Array helpers that help TypeScript help you.
- Non-empty arrays: compile-time guarantee of at least one element
- Readonly arrays: prevents accidental mutation
Instead of checking array length at runtime, use NonEmptyReadonlyArray
so TypeScript rejects empty arrays at compile time. Functions like
firstInArray require a non-empty array — TypeScript won't let us pass
an empty one. mapArray preserves non-emptiness (native map doesn't),
while appendToArray and prependToArray guarantee the result is
non-empty.
All helpers return readonly arrays for safety. Consider how dangerous native
sort() is — it mutates the original array and returns it, making bugs hard
to track:
const sortScores = (arr: number[]) => arr.sort((a, b) => a - b);
const scores = [3, 1, 2];
const leaderboard = sortScores(scores);
leaderboard; // [1, 2, 3]
scores; // [1, 2, 3] — original order lost!
Imagine every method doing that.
On a ReadonlyArray, .sort() doesn't even exist. Use sortArray
instead:
const sortScores = (arr: ReadonlyArray<number>) =>
sortArray(arr, (a, b) => a - b);
const scores: ReadonlyArray<number> = [3, 1, 2];
const leaderboard = sortScores(scores);
leaderboard; // [1, 2, 3]
scores; // [3, 1, 2] — safe!
Even better, require a NonEmptyReadonlyArray — there's nothing to sort if the array is empty anyway:
const sortScores = (arr: NonEmptyReadonlyArray<number>) =>
sortArray(arr, (a, b) => a - b);
Sorting an empty array isn't expensive, but functions can have side effects like database queries or network requests. Using non-empty arrays whenever possible is a good convention.
For performance-critical cases where mutation is needed, Evolu provides
shiftFromArray and popFromArray — but only because they improve
type safety by returning a guaranteed T rather than an optional value.
When to use native methods
These helpers only exist where they add type-level value. Native methods like
find, some, every, includes, indexOf, and findIndex work well on
readonly arrays without mutation — use them directly.
Examples
// Types - compile-time guarantee of at least one element
const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
// ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
const _invalid: NonEmptyReadonlyArray<number> = [];
// Type guards
const arr: ReadonlyArray<number> = [1, 2, 3];
if (isNonEmptyArray(arr)) {
firstInArray(arr);
}
// Transformations
const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
const readonly: ReadonlyArray<number> = [1, 2, 3];
const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
const [evens, odds] = partitionArray([1, 2, 3, 4, 5], (x) => x % 2 === 0);
// Accessors
const first = firstInArray(["a", "b", "c"]); // "a"
const last = lastInArray(["a", "b", "c"]); // "c"
// Mutations
const mutable: NonEmptyArray<number> = [1, 2, 3];
shiftFromArray(mutable); // 1 (guaranteed to exist)
mutable; // [2, 3]
Composition
All array helpers use a data-first style (the array is the first argument) because it's natural for single operations:
const timestamps = mapArray(messages, (m) => m.timestamp);
Data-first style also reads well for a few operations, often fitting on a line:
const cheapest = firstInArray(sortArray(prices, orderNumber));
const uniqueNames = dedupeArray(mapArray(users, (u) => u.name));
const latestDone = lastInArray(filterArray(jobs, isCompletedJob));
For more operations, create a function like getOldestActiveUser or a
generic helper.
Some libraries provide dual APIs with data-last for pipe-based composition. Evolu prefers simplicity (in Latin, simplex means "one") so we don't have to choose between seemingly equivalent options (Buridan's ass dilemma).
Evolu doesn't provide pipe because few operations compose well without it,
and for more operations, well-named functions communicate intent better.
Types
| Name | Description |
|---|---|
| NonEmptyArray | An array with at least one element. |
| NonEmptyReadonlyArray | A readonly array with at least one element. |
| ZipArrayResult | Extracts element types from a tuple of arrays, producing a tuple type. |
| isNonEmptyArray | Checks if an array is non-empty and narrows its type to NonEmptyArray or NonEmptyReadonlyArray based on the input. |
Constants
| Variable | Description |
|---|---|
| emptyArray | An empty readonly array. |
Transformations
| Function | Description |
|---|---|
| appendToArray | Appends an item to an array, returning a new non-empty readonly array. |
| concatArrays | Concatenates two arrays, returning a new readonly array. |
| dedupeArray | Returns a new readonly array with duplicate items removed. If by is provided, it will be used to derive the key for uniqueness; otherwise values are used directly. Dedupes by reference equality of values (or extracted keys when by is used). |
| filterArray | Filters an array using a predicate or refinement function, returning a new readonly array. |
| flatMapArray | Maps each element to an array and flattens the result. |
| mapArray | Maps an array using a mapper function, returning a new readonly array. |
| partitionArray | Partitions an array into two readonly arrays based on a predicate or refinement function. |
| prependToArray | Prepends an item to an array, returning a new non-empty readonly array. |
| reverseArray | Returns a new reversed readonly array. |
| sortArray | Returns a new sorted readonly array. |
| spliceArray | Returns a new readonly array with elements removed and/or replaced. |
| zipArray | Combines multiple arrays into an array of tuples. |
Accessors
| Function | Description |
|---|---|
| firstInArray | Returns the first element of a non-empty array. |
| lastInArray | Returns the last element of a non-empty array. |
Mutations
| Function | Description |
|---|---|
| popFromArray | Pops (removes and returns) the last element from a non-empty mutable array. |
| shiftFromArray | Shifts (removes and returns) the first element from a non-empty mutable array. |
Constructors
| Function | Description |
|---|---|
| arrayFrom | Better Array.from. |
| arrayFromAsync | Better Array.fromAsync. |