function freeze(items: T[]): T[] { for (let item of items) { Object.freeze(item); } return items; } export class ImmutableArray implements Iterable { private static readonly EMPTY = new ImmutableArray([]); private readonly items: T[]; public [Symbol.iterator](): Iterator { return this.items.values(); } public get length() { return this.items.length; } public get values(): T[] { return [...this.items]; } private constructor(items: T[]) { this.items = items; } public static empty(): ImmutableArray { return ImmutableArray.EMPTY; } public static of(items?: T[]): ImmutableArray { if (!items || items.length === 0) { return ImmutableArray.EMPTY; } else { return new ImmutableArray(freeze([...items])); } } public map(projection: (item: T) => R): ImmutableArray { return new ImmutableArray(freeze(this.items.map(v => projection(v!)))); } public filter(predicate: (item: T) => boolean): ImmutableArray { return new ImmutableArray(this.items.filter(v => predicate(v!))); } public find(predicate: (item: T, index: number) => boolean): T { return this.items.find(predicate); } public push(...items: T[]): ImmutableArray { if (!items || items.length === 0) { return this; } return new ImmutableArray([...this.items, ...freeze(items)]); } public remove(...items: T[]): ImmutableArray { if (!items || items.length === 0) { return this; } const copy = this.items.slice(); for (let item of items) { const index = copy.indexOf(item); if (index >= 0) { copy.splice(index, 1); } } return new ImmutableArray(copy); } public removeAll(predicate: (item: T, index: number) => boolean): ImmutableArray { const copy = this.items.slice(); let hasChange = false; for (let i = 0; i < copy.length; ) { if (predicate(copy[i], i)) { copy.splice(i, 1); hasChange = true; } else { ++i; } } return hasChange ? new ImmutableArray(copy) : this; } public replace(oldItem: T, newItem: T): ImmutableArray { const index = this.items.indexOf(oldItem); if (index >= 0) { if (newItem) { Object.freeze(newItem); } const copy = [...this.items.slice(0, index), newItem, ...this.items.slice(index + 1)]; return new ImmutableArray(copy); } else { return this; } } public replaceAll(predicate: (item: T, index: number) => boolean, replacer: (item: T) => T): ImmutableArray { const copy = this.items.slice(); let hasChange = false; for (let i = 0; i < copy.length; i++) { if (predicate(copy[i], i)) { const newItem = replacer(copy[i]); if (newItem) { Object.freeze(newItem); } if (copy[i] !== newItem) { copy[i] = newItem; hasChange = true; } } } return hasChange ? new ImmutableArray(copy) : this; } }