type Record = { value: T } export class DataModel { private records: Set> private indexes: Map> constructor(values: T[] = []) { this.records = new Set() this.indexes = new Map() values.forEach(ref => { const record = this.toRecord(ref) this.records.add(record) this.indexes.set(ref, record) }) } private toRecord(item: T): Record { return { value: item } } private fromRecord(item: Record): T { return item.value } toJSON(): T[] { const acc: T[] = [] this.records.forEach(record => { acc.push(this.fromRecord(record)) }) return acc } fork(): DataModel { return new DataModel(this.toJSON()) } /// IDEMPOTENT /// count(): number { return this.records.size } find(filter?: (item: T) => true): T[] { const json = this.toJSON() if(undefined === filter) return json return json.filter(filter) } findOne(filter?: (item: T) => true): T | null { if(undefined === filter) return null return this.toJSON().find(filter) || null } /// MODIFIER /// private _update(oldItem: T, newItem: T, upsert: boolean = false) { const record = this.indexes.get(oldItem) if(undefined === record) { if(upsert) return this.create(newItem) return null } record.value = newItem return this } update(oldItem: T, newItem: T, upsert: boolean = false): DataModel { const ret = this._update(oldItem, newItem, upsert) if(null === ret) return this return this.fork() } updateMany(updates: [T, T][], upsert: boolean = false): DataModel { if(0 === updates.length) return this updates.forEach(([ oldItem, newItem ]) => { this.update(oldItem, newItem, upsert) }) return this.fork() } private _create(item: T): DataModel { const record = { value: item } this.records.add(record) this.indexes.set(item, record) return this } create(item: T): DataModel { return this._create(item).fork() } createMany(items: T[]): DataModel { items.forEach(this._create.bind(this)) return this.fork() } private _drop(item: T) { const ref = this.indexes.get(item) if(undefined === ref) return null this.records.delete(ref) this.indexes.delete(item) return this } drop(item: T): DataModel { const ret = this._drop(item) if(null === ret) return this return this.fork() } dropMany(items: T[]): DataModel { const count = this.count() const clone = this.fork() items.forEach(clone._drop.bind(clone)) if(count === clone.count()) return this return clone } } export function Model(values: T[] = []): DataModel { return new DataModel(values) }