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

98 lines (79 loc) 2.99 kB
import {type SchemaType} from '@sanity/types' function isPlainObject(obj: unknown): boolean { return obj !== null && typeof obj === 'object' && obj.constructor === Object } function isSchemaType(obj: unknown): obj is SchemaType { if (typeof obj !== 'object') return false if (!obj) return false if (!('jsonType' in obj) || typeof obj.jsonType !== 'string') return false if (!('name' in obj) || typeof obj.name !== 'string') return false return true } interface ImmutableReconcile<T> { (prev: T | null, curr: T): T } export interface CreateImmutableReconcileOptions { decorator?: <T>(fn: ImmutableReconcile<T>) => ImmutableReconcile<T> } function identity<T>(t: T) { return t } export function createImmutableReconcile({ decorator = identity, }: CreateImmutableReconcileOptions = {}): <T>(prev: T | null, curr: T) => T { const immutableReconcile = decorator(function _immutableReconcile<T>(prev: T | null, curr: T): T { if (prev === curr) return curr if (prev === null) return curr if (typeof prev !== 'object' || typeof curr !== 'object') return curr if (Array.isArray(prev) && Array.isArray(curr)) { if (prev.length !== curr.length) return curr const reconciled = curr.map((item, index) => immutableReconcile(prev[index], item)) if (reconciled.every((item, index) => item === prev[index])) return prev return reconciled as T } // skip these, they're recursive structures and will cause stack overflows // they're stable anyway if (isSchemaType(prev) || isSchemaType(curr)) return curr // skip these as well if (!isPlainObject(prev) || !isPlainObject(curr)) return curr const prevObj = prev as Record<string, unknown> const currObj = curr as Record<string, unknown> const reconciled: Record<string, unknown> = {} let changed = false const enumerableKeys = new Set(Object.keys(currObj)) for (const key of Object.getOwnPropertyNames(currObj)) { if (key in prevObj) { const reconciledValue = immutableReconcile(prevObj[key], currObj[key]) if (enumerableKeys.has(key)) { reconciled[key] = reconciledValue } else { Object.defineProperty(reconciled, key, { value: reconciledValue, enumerable: false, }) } changed = changed || reconciledValue !== prevObj[key] } else { if (enumerableKeys.has(key)) { reconciled[key] = currObj[key] } else { Object.defineProperty(reconciled, key, { value: currObj[key], enumerable: false, }) } changed = true } } // Check if any keys were removed for (const key of Object.getOwnPropertyNames(prevObj)) { if (!(key in currObj)) { changed = true break } } return changed ? (reconciled as T) : prev }) return immutableReconcile } export const immutableReconcile = createImmutableReconcile()