or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

collections.mdfunctional-api.mdindex.mdsequences.mdutilities.md
tile.json

utilities.mddocs/

Utilities

Overview

Utility functions and types for working with Immutable collections, including type guards, hash functions, and Record factory for creating typed immutable objects.

Core Imports

import { 
  hash, Record,
  isImmutable, isCollection, isKeyed, isIndexed, isAssociative, isOrdered,
  isValueObject, isSeq, isList, isMap, isOrderedMap, isStack, isSet, isOrderedSet, isRecord,
  isPlainObject, PairSorting, version
} from 'immutable';

Hash Function

hash

Computes a 31-bit integer hash for any value using Immutable's hashing algorithm.

function hash(value: unknown): number;

Usage Examples

import { hash, List, Map } from 'immutable';

// Primitive values
console.log(hash(42)); // Consistent hash for number
console.log(hash('hello')); // Consistent hash for string
console.log(hash(true)); // Consistent hash for boolean

// Collections with same values have same hash
const list1 = List([1, 2, 3]);
const list2 = List([1, 2, 3]);
console.log(hash(list1) === hash(list2)); // true

// Different values have different hashes (usually)
const map1 = Map({ a: 1, b: 2 });
const map2 = Map({ a: 1, b: 3 });
console.log(hash(map1) !== hash(map2)); // true

// Hash is consistent across calls
const value = List([Map({ x: 1 }), 'test']);
console.log(hash(value) === hash(value)); // true

// Custom objects can implement hashCode()
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  
  hashCode() {
    return hash(this.x) ^ hash(this.y);
  }
  
  equals(other) {
    return other instanceof Point && 
           this.x === other.x && 
           this.y === other.y;
  }
}

const point = new Point(1, 2);
console.log(hash(point)); // Uses custom hashCode()

Type Guards

Type guard functions for runtime type checking of Immutable collections.

Core Type Guards

function isImmutable(maybeImmutable: unknown): maybeImmutable is Collection<unknown, unknown>;
function isCollection(maybeCollection: unknown): maybeCollection is Collection<unknown, unknown>;
function isValueObject(maybeValue: unknown): maybeValue is { equals(other: unknown): boolean; hashCode(): number };

Collection Category Guards

function isKeyed(maybeKeyed: unknown): maybeKeyed is Collection.Keyed<unknown, unknown>;
function isIndexed(maybeIndexed: unknown): maybeIndexed is Collection.Indexed<unknown>;
function isAssociative(maybeAssociative: unknown): maybeAssociative is Collection.Keyed<unknown, unknown> | Collection.Indexed<unknown>;
function isOrdered(maybeOrdered: unknown): boolean;

Specific Collection Guards

function isSeq(maybeSeq: unknown): maybeSeq is Seq<unknown, unknown>;
function isList(maybeList: unknown): maybeList is List<unknown>;
function isMap(maybeMap: unknown): maybeMap is Map<unknown, unknown>;
function isOrderedMap(maybeOrderedMap: unknown): maybeOrderedMap is OrderedMap<unknown, unknown>;
function isStack(maybeStack: unknown): maybeStack is Stack<unknown>;
function isSet(maybeSet: unknown): maybeSet is Set<unknown>;
function isOrderedSet(maybeOrderedSet: unknown): maybeOrderedSet is OrderedSet<unknown>;
function isRecord(maybeRecord: unknown): maybeRecord is Record<{[key: string]: unknown}>;

Utility Guards

function isPlainObject(maybeObject: unknown): maybeObject is {[key: string]: unknown};

Usage Examples

import { 
  List, Map, Set, Stack, Record, Seq,
  isImmutable, isCollection, isList, isMap, isSet, isRecord,
  isKeyed, isIndexed, isOrdered, isPlainObject
} from 'immutable';

// Basic type checking
const list = List([1, 2, 3]);
const map = Map({ a: 1 });
const plainObj = { a: 1 };
const array = [1, 2, 3];

console.log(isImmutable(list)); // true
console.log(isImmutable(plainObj)); // false
console.log(isCollection(map)); // true
console.log(isCollection(array)); // false

// Specific collection type guards
console.log(isList(list)); // true
console.log(isList(map)); // false
console.log(isMap(map)); // true
console.log(isSet(Set([1, 2]))); // true

// Category guards
console.log(isKeyed(map)); // true (Map is keyed)
console.log(isIndexed(list)); // true (List is indexed)
console.log(isKeyed(list)); // false
console.log(isIndexed(map)); // false

// Ordered collections
console.log(isOrdered(List([1, 2, 3]))); // true
console.log(isOrdered(Map({ a: 1 }))); // false (Map is unordered)

// Plain object detection
console.log(isPlainObject({ a: 1, b: 2 })); // true
console.log(isPlainObject(new Date())); // false
console.log(isPlainObject(map)); // false

// Runtime type narrowing
function processValue(value: unknown) {
  if (isList(value)) {
    // TypeScript knows value is List<unknown>
    return value.push('new item');
  }
  
  if (isMap(value)) {
    // TypeScript knows value is Map<unknown, unknown>
    return value.set('new', 'value');
  }
  
  if (isPlainObject(value)) {
    // TypeScript knows value is {[key: string]: unknown}
    return { ...value, processed: true };
  }
  
  return value;
}

// Generic collection handling
function handleCollection(collection: unknown) {
  if (isCollection(collection)) {
    console.log(`Collection size: ${collection.size}`);
    
    if (isKeyed(collection)) {
      console.log('This is a keyed collection');
      // Can use keyed-specific methods
    }
    
    if (isIndexed(collection)) {
      console.log('This is an indexed collection');
      // Can use indexed-specific methods
    }
  }
}

Record Factory

Record

Creates a factory for immutable record types with default values and strong typing.

interface RecordFactory<TProps> {
  <Ks extends keyof TProps>(values?: Partial<Pick<TProps, Ks>> | Iterable<[Ks, TProps[Ks]]>): Record<TProps> & Readonly<TProps>;
  displayName: string;
}

function Record<TProps>(defaultValues: TProps, name?: string): RecordFactory<TProps>;

Record Instance Interface

interface Record<TProps> {
  // Value equality
  equals(other: unknown): boolean;
  hashCode(): number;
  
  // Property access
  get<K extends keyof TProps>(key: K, notSetValue?: TProps[K]): TProps[K];
  has(key: keyof TProps): boolean;
  
  // Deep operations
  getIn(keyPath: Iterable<unknown>, notSetValue?: unknown): unknown;
  hasIn(keyPath: Iterable<unknown>): boolean;
  setIn(keyPath: Iterable<unknown>, value: unknown): this;
  updateIn(keyPath: Iterable<unknown>, updater: (value: unknown) => unknown): this;
  deleteIn(keyPath: Iterable<unknown>): this;
  mergeIn(keyPath: Iterable<unknown>, ...collections: unknown[]): this;
  mergeDeepIn(keyPath: Iterable<unknown>, ...collections: unknown[]): this;
  
  // Persistent changes
  set<K extends keyof TProps>(key: K, value: TProps[K]): this;
  update<K extends keyof TProps>(key: K, updater: (value: TProps[K]) => TProps[K]): this;
  delete<K extends keyof TProps>(key: K): this; // Resets to default
  remove<K extends keyof TProps>(key: K): this; // Alias for delete
  clear(): this; // Resets all to defaults
  merge(...collections: Array<Partial<TProps> | Iterable<[keyof TProps, TProps[keyof TProps]]>>): this;
  mergeDeep(...collections: Array<Partial<TProps>>): this;
  mergeWith(merger: (oldVal: unknown, newVal: unknown, key: keyof TProps) => unknown, ...collections: Array<Partial<TProps>>): this;
  mergeDeepWith(merger: (oldVal: unknown, newVal: unknown, key: unknown) => unknown, ...collections: Array<Partial<TProps>>): this;
  
  // Conversion
  toJS(): { [K in keyof TProps]: unknown };
  toJSON(): TProps;
  toObject(): TProps;
  toSeq(): Seq.Keyed<keyof TProps, TProps[keyof TProps]>;
  
  // Transient operations
  withMutations(mutator: (mutable: this) => unknown): this;
  asMutable(): this;
  asImmutable(): this;
  wasAltered(): boolean;
  
  // Iterator
  [Symbol.iterator](): IterableIterator<[keyof TProps, TProps[keyof TProps]]>;
}

Static Methods

namespace Record {
  function isRecord(maybeRecord: unknown): maybeRecord is Record<{[key: string]: unknown}>;
  function getDescriptiveName(record: Record<unknown>): string;
}

Usage Examples

import { Record, List } from 'immutable';

// Define a record type
interface PersonProps {
  id: number;
  name: string;
  email: string;
  age: number;
  active: boolean;
  tags: List<string>;
}

const Person = Record<PersonProps>({
  id: 0,
  name: '',
  email: '',
  age: 0,
  active: true,
  tags: List()
}, 'Person');

// Create instances
const defaultPerson = Person(); // All default values
const alice = Person({
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
  tags: List(['developer', 'remote'])
});

// Property access (TypeScript typed)
console.log(alice.name); // 'Alice' (direct property access)
console.log(alice.get('email')); // 'alice@example.com'
console.log(alice.has('id')); // true

// Persistent updates
const updatedAlice = alice
  .set('age', 31)
  .update('tags', tags => tags.push('senior'));

// Merging
const bobData = { id: 2, name: 'Bob', email: 'bob@example.com' };
const bob = alice.merge(bobData);

// Deep operations (when properties are collections)
const withNestedData = Person({
  id: 3,
  name: 'Charlie',
  email: 'charlie@example.com',
  tags: List(['frontend', 'react'])
});

const updated = withNestedData.updateIn(['tags', 0], tag => tag.toUpperCase());

// Value equality
const alice2 = Person({
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
  tags: List(['developer', 'remote'])
});

console.log(alice.equals(alice2)); // true
console.log(alice === alice2); // false

// Reset to defaults
const resetPerson = alice.clear(); // All properties back to defaults
const partialReset = alice.delete('age'); // Just age back to default (0)

// Conversion
const plain = alice.toObject(); // Plain JavaScript object
const json = alice.toJSON(); // For JSON serialization

// Factory properties
console.log(Person.displayName); // 'Person'
console.log(Record.getDescriptiveName(alice)); // 'Person'

// Type guard
console.log(Record.isRecord(alice)); // true
console.log(Record.isRecord({})); // false

// Nested records
interface AddressProps {
  street: string;
  city: string;
  country: string;
}

const Address = Record<AddressProps>({
  street: '',
  city: '',
  country: ''
}, 'Address');

interface UserProps {
  id: number;
  profile: Record<PersonProps>;
  address: Record<AddressProps>;
}

const User = Record<UserProps>({
  id: 0,
  profile: Person(),
  address: Address()
}, 'User');

const user = User({
  id: 1,
  profile: Person({ name: 'Alice', email: 'alice@example.com' }),
  address: Address({ street: '123 Main St', city: 'Boston', country: 'USA' })
});

// Deep updates on nested records
const updatedUser = user.setIn(['profile', 'age'], 25);
const updatedAddress = user.updateIn(['address', 'street'], street => 
  street.replace('Main St', 'Oak Ave')
);

Constants and Enums

PairSorting

Enum for comparison results used in sorting operations.

enum PairSorting {
  LeftThenRight = -1,
  RightThenLeft = 1
}

version

Package version string.

const version: string;

Usage Examples

import { PairSorting, version, List } from 'immutable';

// Version information
console.log(`Immutable.js version: ${version}`); // "4.3.7"

// Custom comparator using PairSorting
const customSort = (a: number, b: number): PairSorting => {
  if (a < b) return PairSorting.LeftThenRight;  // -1
  if (a > b) return PairSorting.RightThenRight; // 1
  return 0 as PairSorting;
};

const list = List([3, 1, 4, 1, 5, 9]);
const sorted = list.sort(customSort); // List [ 1, 1, 3, 4, 5, 9 ]

// More complex sorting
interface Item {
  name: string;
  priority: number;
}

const items = List([
  { name: 'Task A', priority: 2 },
  { name: 'Task B', priority: 1 },
  { name: 'Task C', priority: 3 }
]);

const sortedByPriority = items.sort((a, b) => {
  if (a.priority < b.priority) return PairSorting.LeftThenRight;
  if (a.priority > b.priority) return PairSorting.RightThenLeft;
  return 0 as PairSorting;
});

Utility Patterns

Collection Type Detection

import { 
  Collection, isKeyed, isIndexed, isList, isMap, isSet, isStack, isSeq 
} from 'immutable';

function describeCollection(collection: Collection<unknown, unknown>): string {
  const descriptions = [];
  
  if (isKeyed(collection)) descriptions.push('keyed');
  if (isIndexed(collection)) descriptions.push('indexed');
  if (isOrdered(collection)) descriptions.push('ordered');
  
  let type = 'Collection';
  if (isList(collection)) type = 'List';
  else if (isMap(collection)) type = 'Map';
  else if (isSet(collection)) type = 'Set';
  else if (isStack(collection)) type = 'Stack';
  else if (isSeq(collection)) type = 'Seq';
  
  return `${type} (${descriptions.join(', ')}) with ${collection.size} items`;
}

Generic Collection Processing

import { Collection, isKeyed, isIndexed } from 'immutable';

function processAnyCollection<K, V>(
  collection: Collection<K, V>,
  processor: (value: V, key: K) => V
): Collection<K, V> {
  return collection.map(processor);
}

function deepProcess<C extends Collection<unknown, unknown>>(
  collection: C,
  predicate: (value: unknown) => boolean,
  transform: (value: unknown) => unknown
): C {
  return collection.map(value => {
    if (isCollection(value)) {
      return deepProcess(value, predicate, transform);
    }
    return predicate(value) ? transform(value) : value;
  }) as C;
}

Safe Property Access

import { Record, isRecord, get } from 'immutable';

function safeGet<T>(obj: unknown, key: string | number, defaultValue: T): T {
  if (isRecord(obj) || isCollection(obj)) {
    return get(obj, key, defaultValue);
  }
  
  if (isPlainObject(obj) && typeof key === 'string') {
    return obj[key] ?? defaultValue;
  }
  
  if (Array.isArray(obj) && typeof key === 'number') {
    return obj[key] ?? defaultValue;
  }
  
  return defaultValue;
}