JSPM

  • Created
  • Published
  • Downloads 1919
  • Score
    100M100P100Q122992F
  • License MIT

An efficient immutable ordered sequence of elements akin to a Vector

Package Exports

  • @rimbu/list
  • @rimbu/list/custom

Readme

Rimbu Logo

npm version License Types Included Node Bun ESM + CJS

@rimbu/list

Fast, immutable random‑access lists for TypeScript & JavaScript.

@rimbu/list provides an efficient, type‑safe List: an ordered, indexable, immutable sequence. It behaves much like a persistent “vector” or array – supporting random access, slicing, concatenation, mapping and more – while sharing structure between versions so updates stay cheap.

Use it whenever you need ordered collections, random index access, or want to avoid copying arrays on every update in your application state.


Table of Contents

  1. Why @rimbu/list?
  2. Feature Highlights
  3. Quick Start
  4. Core Concepts & Types
  5. Working with Lists
  6. Builders & Contexts
  7. Performance Notes
  8. Installation
  9. Ecosystem & Integration
  10. Contributing
  11. License
  12. Attributions

Why @rimbu/list?

Plain JavaScript arrays are flexible, but they’re:

  • Mutable by default – accidental sharing and in‑place updates cause subtle bugs.
  • Copy‑heavy for updates – you often spread or slice arrays to keep them “immutable”.
  • Unstructured – no built‑in notion of non‑empty variants or persistent builders.

@rimbu/list focuses on:

  • Immutable operations – every change returns a new List, sharing most of its structure.
  • Random accessget(index) and length behave like arrays, with negative indices supported for convenience.
  • Rich slicing and transformationtake, drop, slice, map, flatMap, filter, collect, repeat, rotate, padTo, and more.
  • Non‑empty refinementList.NonEmpty<T> gives stronger type guarantees when emptiness is impossible.
  • Efficient buildersList.Builder<T> lets you do many mutations before freezing into an immutable List.

If you reach for arrays but want persistent, predictable semantics and a strong type story, @rimbu/list is a great fit.


Feature Highlights

  • Immutable, random‑access sequence – ordered collection with length, get, and negative indices (-1 is the last element).
  • Powerful slicing APIstake, drop, slice, and streamRange support ranges and reverse traversal.
  • Transformationsmap, mapPure, flatMap, filter, collect, repeat, rotate, padTo, etc.
  • Non‑empty variantList.NonEmpty<T> and nonEmpty()/assumeNonEmpty() for stronger compile‑time guarantees.
  • BuildersList.Builder<T> for efficient bulk updates before calling build().
  • Context‑based configurationList.createContext({ blockSizeBits }) to tune internal block sizes.
  • Stream integration – many APIs accept or return Stream / StreamSource for composable data flows.

Quick Start

import { List } from '@rimbu/list';

// Construction
const empty = List.empty<number>();
const fromValues = List.of(1, 2, 3);
const fromSources = List.from([1, 2], [3, 4]); // StreamSource inputs

// Basic operations
fromValues.length; // 3
fromValues.isEmpty; // false

// Indexed access (supports negative indices)
fromValues.get(0); // 1
fromValues.get(-1); // 3
fromValues.get(10, 'fallback'); // 'fallback'

// Immutable updates
const with4 = fromValues.append(4); // List.NonEmpty<number>
const rotated = with4.rotate(1); // List(4, 1, 2, 3)

console.log(with4.toArray()); // [1, 2, 3, 4]
console.log(rotated.toString()); // List(4, 1, 2, 3)

Try Rimbu (including @rimbu/list) live in the browser using the Rimbu Sandbox on CodeSandbox.


Core Concepts & Types

Exported Types

From @rimbu/list:

Name Description
List<T> Immutable, random‑access, ordered sequence of values T.
List.NonEmpty<T> Non‑empty refinement of List<T> with stricter guarantees (e.g. first() and last() return).
List.Context Factory/context used to create List instances with specific configuration.
List.Builder<T> Mutable builder for efficiently constructing or mutating a List before freezing it.
ListFactory / ListCreators Internal factory/creator interfaces re‑exported via the List constant.

The main entry point exports a frozen List creators object:

import { List } from '@rimbu/list';

// Factory methods
const l1 = List.empty<number>();
const l2 = List.of(1, 2, 3);
const l3 = List.from([1, 2], [3, 4]);
const chars = List.fromString('abc'); // List.NonEmpty<string>

See the full List docs and API reference for all operations.


Working with Lists

Construction & Emptiness

import { List } from '@rimbu/list';

const empty = List.empty<number>();
const numbers = List.of(1, 2, 3);
const fromArrays = List.from([1, 2], [3, 4]); // StreamSource<number>[]

empty.isEmpty; // true
numbers.nonEmpty(); // true

// Type refinement
if (numbers.nonEmpty()) {
  // here: numbers is List.NonEmpty<number>
  numbers.first(); // number
}

// Assert non‑emptiness (throws at runtime if empty)
const nonEmpty = numbers.assumeNonEmpty(); // List.NonEmpty<number>

Indexing, Slicing, and Ranges

const list = List.of(0, 1, 2, 3, 4, 5);

list.get(0); // 0
list.get(-1); // 5
list.get(10, 'fallback'); // 'fallback'

// Take / drop from front or back (negative amount counts from end)
list.take(3).toArray(); // [0, 1, 2]
list.drop(2).toArray(); // [2, 3, 4, 5]
list.take(-2).toArray(); // [4, 5]

// Slice using IndexRange
list.slice({ start: 1, amount: 3 }).toArray(); // [1, 2, 3]
list.slice({ start: -3, amount: 2 }, { reversed: true }).toArray(); // [4, 3]

Streams & Iteration

import { Stream } from '@rimbu/stream';

const list = List.of(10, 20, 30);

// Iterable
[...list]; // [10, 20, 30]

// Streams
list.stream().toArray(); // [10, 20, 30]
list.stream({ reversed: true }).toArray(); // [30, 20, 10]

// From streams
const fromStream = List.from(Stream.range({ start: 0, amount: 5 }));
fromStream.toArray(); // [0, 1, 2, 3, 4]

Transformations

const list = List.of(1, 2, 3, 4);

// map / mapPure
list.map((v, i) => `#${i}: ${v}`).toArray();
// ['#0: 1', '#1: 2', '#2: 3', '#3: 4']

list.mapPure((v) => v * 2).toArray();
// [2, 4, 6, 8]

// filter
list.filter((v) => v % 2 === 0).toArray(); // [2, 4]

// flatMap
list.flatMap((v) => [v, v + 1]).toArray();
// [1, 2, 2, 3, 3, 4, 4, 5]

// collect (map + filter with skip/halt)
const collected = list.collect((v, i, skip, halt) => {
  if (v > 3) halt();
  if (v === 2) return skip;
  return v * 10;
});
collected.toArray(); // [10, 30]

Bulk Operations & Utilities

const base = List.of(0, 1, 2, 3);

// Concat with other StreamSource values
base.concat([10, 11], new Set([12])).toArray();
// [0, 1, 2, 3, 10, 11, 12]

// Repeat (negative repeats reverse)
base.repeat(2).toArray();
// [0, 1, 2, 3, 0, 1, 2, 3]
base.repeat(-1).toArray();
// [3, 2, 1, 0]

// Rotate
base.rotate(2).toArray(); // [2, 3, 0, 1]
base.rotate(-1).toArray(); // [1, 2, 3, 0]

// Pad to length
List.of(0, 1).padTo(4, 9).toArray();
// [0, 1, 9, 9]

// Splice / insert / remove by index
base.splice({ index: 2, remove: 1, insert: [9] }).toArray();
// [0, 1, 9, 3]
base.insert(1, [9, 9]).toArray();
// [0, 9, 9, 1, 2, 3]
base.remove(1, { amount: 2 }).toArray();
// [0, 3]

Builders & Contexts

Using List.Builder

import { List } from '@rimbu/list';

const builder = List.builder<number>();

builder.append(1);
builder.prepend(0);
builder.insert(1, 5);

builder.length; // 3

const finalList = builder.build(); // List<number>
finalList.toArray(); // [0, 5, 1]

Builders are ideal when you:

  • Need to build a List incrementally in a hot path.
  • Want to perform many updates before freezing into an immutable value.

Contexts and List.createContext

import { List } from '@rimbu/list';

// Create a custom context (tuning the internal block size)
const SmallBlocks = List.createContext({ blockSizeBits: 3 });

const a = SmallBlocks.of(1, 2, 3);
const b = SmallBlocks.from([4, 5]);

const combined = a.concat(b);
combined.context === SmallBlocks; // true

Contexts let you:

  • Share configuration (like blockSizeBits) across many Lists.
  • Keep structural sharing efficient within the same context.

The default exported List creators object also provides:

  • List.defaultContext() – access the shared default context.

Performance Notes

  • Lists in Rimbu are built on persistent tree‑based structures – most operations are \(O(\log_B n)\) where \(B\) is the internal block size.
  • Random access, updates, concatenation, and slicing are designed to be efficient while sharing structure between versions.
  • Many bulk operations accept StreamSource inputs, so you can build Lists from arrays, iterables, or Rimbu streams without unnecessary copying.

For more details and benchmarks, see the main Rimbu documentation at rimbu.org.


Installation

Node / Bun / npm / Yarn / Deno

npm install @rimbu/list
# or
yarn add @rimbu/list
# or
bun add @rimbu/list
# or
deno add npm:@rimbu/list

Browser / ESM

@rimbu/list ships both ESM and CJS builds. Use it with any modern bundler (Vite, Webpack, esbuild, Bun, etc.) or directly in Node ESM projects.


Ecosystem & Integration

  • Part of the broader Rimbu collections ecosystem – interoperates with @rimbu/hashed, @rimbu/bimap, @rimbu/ordered, @rimbu/collection-types, and @rimbu/stream.
  • Ideal for representing ordered application state, timelines, logs, UI lists, sequences of events, and more.
  • Works seamlessly with other Rimbu collections and utilities for building rich, immutable data models.

Explore more at the Rimbu documentation and the List API docs.


Contributing

We welcome contributions! See the Contributing guide for details.

Contributors

Made with contributors-img.


License

MIT © Rimbu contributors. See LICENSE for details.


Attributions

Created and maintained by Arvid Nicolaas. Logo © Rimbu.