API reference / @evolu/common / Array
Array
Array types, type guards, operations, transformations, accessors, and (rare) mutations
Example
// 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 (isNonEmptyReadonlyArray(arr)) {
firstInArray(arr);
}
// Operations
const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
// Transformations
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); // [[2, 4], [1, 3, 5]]
// Accessors
const first = firstInArray(["a", "b", "c"]); // "a"
const last = lastInArray(["a", "b", "c"]); // "c"
// Mutations
const mutable: NonEmptyArray<number> = [1, 2, 3];
shiftArray(mutable); // 1 (guaranteed to exist)
mutable; // [2, 3]
Functions are intentionally data-first to be prepared for the upcoming JavaScript pipe operator.
// Data-first is natural for single operations.
const timestamps = mapArray(messages, (m) => m.timestamp);
// But data-first can be hard to read for nested calls.
const result = firstInArray(
mapArray(dedupeArray(appendToArray(value, 2)), (x) => x * 2),
);
// With the upcoming pipe operator, it's clear.
// const result = value
// |> appendToArray(%, 2)
// |> dedupeArray(%)
// |> mapArray(%, (x) => x * 2)
// |> firstInArray(%);
// Until the pipe operator lands, use nested calls or name each step:
const appended = appendToArray(value, 2);
const deduped = dedupeArray(appended);
const mapped = mapArray(deduped, (x) => x * 2);
const result = firstInArray(mapped);
Why data-first?
Evolu optimizes for consistent code style. We can't have both data-first single operations and curried data-last helpers without sacrificing consistency. We chose data-first because:
- It's natural for single operations (for example
mapArray(messages, (m) => m.timestamp)). - It aligns with the upcoming JavaScript pipe operator.
Note: Feel free to use Array instance methods (mutation) if you think it's better (performance, local scope, etc.).
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 |
|---|---|
| popArray | Pops an item from a non-empty mutable array, guaranteed to return T. |
| shiftArray | Shifts an item from a non-empty mutable array, guaranteed to return T. |
Operations
| Function | Description |
|---|---|
| appendToArray | Appends an item to an array, returning a new non-empty readonly array. |
| prependToArray | Prepends an item to an array, returning a new non-empty readonly array. |
Transformations
| Function | Description |
|---|---|
| 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. |
| mapArray | Maps an array using a mapper function. |
| partitionArray | Partitions an array into two arrays based on a predicate or refinement function. |
Type Guards
| Function | Description |
|---|---|
| isNonEmptyArray | Checks if an array is non-empty and narrows its type to NonEmptyArray. |
| isNonEmptyReadonlyArray | Checks if a readonly array is non-empty and narrows its type to NonEmptyReadonlyArray. |
Types
| Type Alias | Description |
|---|---|
| NonEmptyArray | An array with at least one element. |
| NonEmptyReadonlyArray | A readonly array with at least one element. |