UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

78 lines (65 loc) 2.35 kB
/** * Reconciles two versions of a state tree by iterating over the next and deep comparing against the next towards the previous. * Wherever identical values are found, the previous value is kept, preserving object identities for arrays and objects where possible * @param previous - the previous value * @param next - the next/current value */ export function immutableReconcile<T>(previous: unknown, next: T): T { return _immutableReconcile(previous, next, new WeakSet()) } function _immutableReconcile<T>( previous: unknown, next: T, /** * Keep track of visited nodes to prevent infinite recursion in case of circular structures */ parents: WeakSet<any>, ): T { if (previous === next) return previous as T if (parents.has(next)) { return next } // eslint-disable-next-line no-eq-null if (previous == null || next == null) return next const prevType = typeof previous const nextType = typeof next // Different types if (prevType !== nextType) return next if (Array.isArray(next)) { parents.add(next) assertType<unknown[]>(previous) assertType<unknown[]>(next) let allEqual = previous.length === next.length const result = [] for (let index = 0; index < next.length; index++) { const nextItem = _immutableReconcile(previous[index], next[index], parents) if (nextItem !== previous[index]) { allEqual = false } result[index] = nextItem } parents.delete(next) return (allEqual ? previous : result) as any } if (typeof next === 'object') { parents.add(next) assertType<Record<string, unknown>>(previous) assertType<Record<string, unknown>>(next) const nextKeys = Object.keys(next) let allEqual = Object.keys(previous).length === nextKeys.length const result: Record<string, unknown> = {} for (const key of nextKeys) { const nextValue = _immutableReconcile(previous[key], next[key]!, parents) if (nextValue !== previous[key]) { allEqual = false } result[key] = nextValue } parents.delete(next) return (allEqual ? previous : result) as T } return next } // just some typescript trickery get type assertion // eslint-disable-next-line @typescript-eslint/no-empty-function, no-empty-function function assertType<T>(value: unknown): asserts value is T {}