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

FunctionDescription
firstInArrayReturns the first element of a non-empty array.
lastInArrayReturns the last element of a non-empty array.

Mutations

FunctionDescription
popArrayPops an item from a non-empty mutable array, guaranteed to return T.
shiftArrayShifts an item from a non-empty mutable array, guaranteed to return T.

Operations

FunctionDescription
appendToArrayAppends an item to an array, returning a new non-empty readonly array.
prependToArrayPrepends an item to an array, returning a new non-empty readonly array.

Transformations

FunctionDescription
dedupeArrayReturns 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).
filterArrayFilters an array using a predicate or refinement function, returning a new readonly array.
mapArrayMaps an array using a mapper function.
partitionArrayPartitions an array into two arrays based on a predicate or refinement function.

Type Guards

FunctionDescription
isNonEmptyArrayChecks if an array is non-empty and narrows its type to NonEmptyArray.
isNonEmptyReadonlyArrayChecks if a readonly array is non-empty and narrows its type to NonEmptyReadonlyArray.

Types

Type AliasDescription
NonEmptyArrayAn array with at least one element.
NonEmptyReadonlyArrayA readonly array with at least one element.

Was this page helpful?