Utility functions and types for working with Immutable collections, including type guards, hash functions, and Record factory for creating typed immutable objects.
import {
hash, Record,
isImmutable, isCollection, isKeyed, isIndexed, isAssociative, isOrdered,
isValueObject, isSeq, isList, isMap, isOrderedMap, isStack, isSet, isOrderedSet, isRecord,
isPlainObject, PairSorting, version
} from 'immutable';Computes a 31-bit integer hash for any value using Immutable's hashing algorithm.
function hash(value: unknown): number;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 guard functions for runtime type checking of Immutable collections.
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 };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;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}>;function isPlainObject(maybeObject: unknown): maybeObject is {[key: string]: unknown};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
}
}
}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>;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]]>;
}namespace Record {
function isRecord(maybeRecord: unknown): maybeRecord is Record<{[key: string]: unknown}>;
function getDescriptiveName(record: Record<unknown>): string;
}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')
);Enum for comparison results used in sorting operations.
enum PairSorting {
LeftThenRight = -1,
RightThenLeft = 1
}Package version string.
const version: string;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;
});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`;
}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;
}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;
}