UNPKG

@sanity/mutator

Version:

A set of models to make it easier to utilize the powerful real time collaborative features of Sanity

160 lines (128 loc) 4.36 kB
import {type Probe} from '../jsonpath/Probe' /** * An immutable probe/writer for plain JS objects that will never mutate * the provided _value in place. Each setter returns a new (wrapped) version * of the value. */ export class ImmutableAccessor implements Probe { _value: unknown path: (string | number)[] constructor(value: unknown, path?: (string | number)[]) { this._value = value this.path = path || [] } containerType(): 'array' | 'object' | 'primitive' { if (Array.isArray(this._value)) { return 'array' } else if (this._value !== null && typeof this._value === 'object') { return 'object' } return 'primitive' } // Common reader, supported by all containers get(): unknown { return this._value } // Array reader length(): number { if (!Array.isArray(this._value)) { throw new Error("Won't return length of non-indexable _value") } return this._value.length } getIndex(i: number): ImmutableAccessor | false | null { if (!Array.isArray(this._value)) { return false } if (i >= this.length()) { return null } return new ImmutableAccessor(this._value[i], this.path.concat(i)) } // Object reader hasAttribute(key: string): boolean { return isRecord(this._value) ? this._value.hasOwnProperty(key) : false } attributeKeys(): string[] { return isRecord(this._value) ? Object.keys(this._value) : [] } getAttribute(key: string): ImmutableAccessor | null { if (!isRecord(this._value)) { throw new Error('getAttribute only applies to plain objects') } if (!this.hasAttribute(key)) { return null } return new ImmutableAccessor(this._value[key], this.path.concat(key)) } // Common writer, supported by all containers set(value: unknown): ImmutableAccessor { return value === this._value ? this : new ImmutableAccessor(value, this.path) } // array writer interface setIndex(i: number, value: unknown): ImmutableAccessor { if (!Array.isArray(this._value)) { throw new Error('setIndex only applies to arrays') } if (Object.is(value, this._value[i])) { return this } const nextValue = this._value.slice() nextValue[i] = value return new ImmutableAccessor(nextValue, this.path) } setIndexAccessor(i: number, accessor: ImmutableAccessor): ImmutableAccessor { return this.setIndex(i, accessor.get()) } unsetIndices(indices: number[]): ImmutableAccessor { if (!Array.isArray(this._value)) { throw new Error('unsetIndices only applies to arrays') } const length = this._value.length const nextValue = [] // Copy every _value _not_ in the indices array over to the newValue for (let i = 0; i < length; i++) { if (indices.indexOf(i) === -1) { nextValue.push(this._value[i]) } } return new ImmutableAccessor(nextValue, this.path) } insertItemsAt(pos: number, items: unknown[]): ImmutableAccessor { if (!Array.isArray(this._value)) { throw new Error('insertItemsAt only applies to arrays') } let nextValue if (this._value.length === 0 && pos === 0) { nextValue = items } else { nextValue = this._value.slice(0, pos).concat(items).concat(this._value.slice(pos)) } return new ImmutableAccessor(nextValue, this.path) } // Object writer interface setAttribute(key: string, value: unknown): ImmutableAccessor { if (!isRecord(this._value)) { throw new Error('Unable to set attribute of non-object container') } if (Object.is(value, this._value[key])) { return this } const nextValue = Object.assign({}, this._value, {[key]: value}) return new ImmutableAccessor(nextValue, this.path) } setAttributeAccessor(key: string, accessor: ImmutableAccessor): ImmutableAccessor { return this.setAttribute(key, accessor.get()) } unsetAttribute(key: string): ImmutableAccessor { if (!isRecord(this._value)) { throw new Error('Unable to unset attribute of non-object container') } const nextValue = Object.assign({}, this._value) delete nextValue[key] return new ImmutableAccessor(nextValue, this.path) } } function isRecord(value: unknown): value is {[key: string]: unknown} { return value !== null && typeof value === 'object' }