Skip to content

Instantly share code, notes, and snippets.

@TheLucifurry
Last active March 25, 2024 10:41
Show Gist options
  • Select an option

  • Save TheLucifurry/2f85e28076464eec68ff38adb168fb4b to your computer and use it in GitHub Desktop.

Select an option

Save TheLucifurry/2f85e28076464eec68ff38adb168fb4b to your computer and use it in GitHub Desktop.
import { isEqual, isPlainObject as lodashIsPlainObject } from '../lodash'
type If<V extends boolean, Then, Else> = V extends true ? Then : Else
function isPlainObject(value: unknown): value is object {
return lodashIsPlainObject(value)
}
function getDifference<V>(_source: V, changed: Partial<V>): Partial<V> {
return changed // TODO: реализовать
}
function getMerged<V>(source: V, changed: Partial<V>): V {
return { ...source, ...changed }
}
type ItemEventHandler<K, V> = (key: K, value: V) => void
/**
* Создает ассоциативный массив с контролем изменений
*
* @example ```
* const dm = new DiffMap()
*
* // Запись объекта
* dm.set(0, { a: 123, b: 'original' }, true) // Или `dm.setSource(0, {...})`
* dm.get(0) // => { a: 123, b: 'original' }
*
* // Частичное изменение объекта
* dm.set(0, { b:'changed' }, false)
* dm.getSource(0) // => { a: 123, b: 'original' }
* dm.getDiff(0) // => { b: 'changed' }
* dm.get(0) // => { a: 123, b: 'changed' }
* dm.sizeDiffs // => 1
*
* // Фиксация изменений объекта
* dm.commit(0)
* dm.getDiff(0) // => undefined
* dm.getSource(0) // => { a: 123, b: 'changed' }
* dm.sizeDiffs // => 0
* ```
*/
export class DiffMap<K extends string | number | symbol, V> implements Map<K, V> {
private _src: Map<K, V>
private _diff: Map<K, Partial<V>> = new Map()
private _emitChange = (key: K) => {
this.onchange?.(key, this.get(key)!)
}
constructor(data?: Record<string | number | symbol, V>) {
this._src = new Map(data ? Object.entries(data) : undefined) as Map<K, V>
}
[Symbol.toStringTag]: string = DiffMap.name;
[Symbol.iterator](): IterableIterator<[K, V]> {
throw new Error('Method not implemented.')
}
// Стандартные методы Map
clear = (): void => {
this._src.clear()
this._diff.clear()
}
delete = (key: K) => {
this._diff.delete(key)
return this._src.delete(key)
}
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: unknown): void {
callbackfn.call(thisArg, {} as V, '_' as K, this._src)
throw new Error('Method not implemented.')
}
entries(): IterableIterator<[K, V]> {
throw new Error('Method not implemented.')
}
keys = (): IterableIterator<K> => this._src.keys()
values(): IterableIterator<V> {
throw new Error('Method not implemented.')
}
set = <ICreateSrcMode extends boolean = true>(
key: K,
value: If<ICreateSrcMode, V, Partial<V>>,
createIfNoSource: ICreateSrcMode = true as ICreateSrcMode,
) => {
if (!this._src.has(key)) {
if (createIfNoSource) {
this.setSource(key, value as V)
}
} else {
const sourceValue = this._src.get(key)
if (isPlainObject(value)) {
this._diff.set(key, isPlainObject(sourceValue) ? getDifference(sourceValue, value) : value)
} else {
this._diff.set(key, value)
}
if (isEqual(sourceValue, value)) {
this._diff.delete(key)
}
}
this._emitChange(key)
return this
}
get = (key: K) => {
const srcValue = this._src.get(key)
if (!this._diff.has(key)) {
return srcValue as V | undefined
}
const deltaValue = this._diff.get(key)!
return isPlainObject(srcValue) && isPlainObject(deltaValue)
? getMerged(srcValue, deltaValue)
: deltaValue as V
}
has = (key: K) => this._src.has(key)
get size() {
return this._src.size
}
// Дополнительные методы
/** Событие изменения объекта */
onchange?: ItemEventHandler<K, V> | null
/** Устанавливает исходное значение для объекта, удаляя его изменения */
setSource = (key: K, item: V) => {
this._src.set(key, item)
this._diff.delete(key)
this._emitChange(key)
return this
}
/** Возвращает исходное значение объекта */
getSource = (key: K) => this._src.get(key)
/** Возвращает изменения объекта */
getDiff = (key: K) => this._diff.get(key)
/** Возвращает список изменений */
getDiffs = () => Object.fromEntries(this._diff.entries()) as Record<K, Partial<V>>
/** Проверяет наличие изменений у объекта */
hasDiff = (key: K) => this._diff.has(key)
/** Проверяет наличие любых изменений */
hasDiffs = () => !!this._diff.size
/** Содержит количество измененных объектов */
get sizeDiffs() {
return this._diff.size
}
/** Фиксирует изменения объекта, сохраняя как исходное значение и удаляя измененное */
commit = (key: K) => {
if (this._diff.has(key)) {
this._src.set(key, this.get(key)!)
this._diff.delete(key)
}
}
/** Фиксирует все изменения, помещая в исходные значения, очищая список изменений */
commitAll = () => {
this._diff.forEach((_value, key) => this.commit(key))
}
/** Удаляет изменения объекта */
reset = (key: K) => {
if (this._diff.has(key)) {
this._diff.delete(key)
this._emitChange(key)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment