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);
expect(leaderboard).toEqual([1, 2, 3]);
expect(scores).toEqual([1, 2, 3]);
expect(leaderboard).toBe(scores);
Imagine every method doing that.
On a ReadonlyArray, .sort() doesn't even exist. Use sortArray
instead:
import { sortArray } from "@evolu/common";
const sortScores = (arr: ReadonlyArray<number>) =>
sortArray(arr, (a, b) => a - b);
const scores: ReadonlyArray<number> = [3, 1, 2];
const leaderboard = sortScores(scores);
expect(leaderboard).toEqual([1, 2, 3]);
expect(scores).toEqual([3, 1, 2]);
expect(leaderboard).not.toBe(scores);
Even better, require a NonEmptyReadonlyArray — there's nothing to sort if the array is empty anyway:
import { sortArray, type NonEmptyReadonlyArray } from "@evolu/common";
const sortScores = (arr: NonEmptyReadonlyArray<number>) =>
sortArray(arr, (a, b) => a - b);
const leaderboard = sortScores([3, 1, 2]);
expect(leaderboard).toEqual([1, 2, 3]);
expectTypeOf(leaderboard).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
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.
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.
import { type NonEmptyReadonlyArray } from "@evolu/common";
const valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
// @ts-expect-error An empty array is not non-empty.
const invalid: NonEmptyReadonlyArray<number> = [];
expect(valid.find((value) => value === 2)).toBe(2);
Composition
All array helpers use a data-first style (the array is the first argument) because it's natural for single operations:
import { mapArray } from "@evolu/common";
interface Message {
readonly timestamp: number;
}
const messages: ReadonlyArray<Message> = [{ timestamp: 10 }, { timestamp: 20 }];
const timestamps = mapArray(messages, (m) => m.timestamp);
expect(timestamps).toEqual([10, 20]);
Data-first style also reads well for a few operations, often fitting on a line:
import {
dedupeArray,
filterArray,
firstInArray,
isNonEmptyArray,
lastInArray,
mapArray,
orderNumber,
sortArray,
} from "@evolu/common";
const cheapest = firstInArray(sortArray([30, 10, 20], orderNumber));
expect(cheapest).toBe(10);
const users = [{ name: "Ada" }, { name: "Linus" }, { name: "Ada" }];
const uniqueNames = dedupeArray(mapArray(users, (u) => u.name));
expect(uniqueNames).toEqual(["Ada", "Linus"]);
const jobs = [
{ id: 1, done: false },
{ id: 2, done: true },
];
const completedJobs = filterArray(jobs, (job) => job.done);
if (!isNonEmptyArray(completedJobs)) throw new Error("Expected a job");
const latestDone = lastInArray(completedJobs);
expect(latestDone).toEqual({ id: 2, done: true });
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 |
|---|---|
| AtLeastTwoReadonlyArray | A readonly array with at least two elements. |
| 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. |
Constructors
| Function | Description |
|---|---|
| arrayFrom | Better Array.from. |
| arrayFromAsync | Better Array.fromAsync. |
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. Items are compared using Set equality (SameValueZero): primitives by value, and objects and arrays by reference. If by is provided, it derives the comparison key. |
| 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. |