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

171 lines (137 loc) • 4.35 kB
import { type IndexTuple, isIndexSegment, isIndexTuple, isKeyedObject, isKeySegment, type KeyedSegment, type Path, type PathSegment, } from '@sanity/types' import {isRecord} from '../../util' const rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g const reKeySegment = /_key\s*==\s*['"](.*)['"]/ /** @internal */ export function pathToString(path: Path): string { if (!Array.isArray(path)) { throw new Error('Path is not an array') } return path.reduce<string>((target, segment, i) => { if (isIndexSegment(segment)) { return `${target}[${segment}]` } if (isKeySegment(segment) && segment._key) { return `${target}[_key=="${segment._key}"]` } if (isIndexTuple(segment)) { const [from, to] = segment return `${target}[${from}:${to}]` } if (typeof segment === 'string') { const separator = i === 0 ? '' : '.' return `${target}${separator}${segment}` } throw new Error(`Unsupported path segment \`${JSON.stringify(segment)}\``) }, '') } /** @internal */ export function getValueAtPath(rootValue: unknown, path: Path): unknown { const segment = path[0] if (!segment) { return rootValue } const tail = path.slice(1) if (isIndexSegment(segment)) { return getValueAtPath(Array.isArray(rootValue) ? rootValue[segment] : undefined, tail) } if (isKeySegment(segment)) { return getValueAtPath( Array.isArray(rootValue) ? rootValue.find((item) => item._key === segment._key) : undefined, tail, ) } if (typeof segment === 'string') { return getValueAtPath(isRecord(rootValue) ? rootValue[segment] : undefined, tail) } throw new Error(`Unknown segment type ${JSON.stringify(segment)}`) } /** @internal */ export function findIndex(array: unknown[], segment: PathSegment): number { if (typeof segment !== 'number' && !isKeySegment(segment)) { return -1 } return typeof segment === 'number' ? segment : array.findIndex((item) => isKeyedObject(item) && item._key === segment._key) } /** @internal */ export function stringToPath(path: string): Path { const segments = path.match(rePropName) if (!segments) { throw new Error('Invalid path string') } return segments.map(normalizePathSegment) } /** @internal */ export function normalizePathSegment(segment: string): PathSegment { if (isIndexSegment(segment)) { return normalizeIndexSegment(segment) } if (isKeySegment(segment)) { return normalizeKeySegment(segment) } if (isIndexTuple(segment)) { return normalizeIndexTupleSegment(segment) } return segment } /** @internal */ export function normalizeIndexSegment(segment: string): PathSegment { return Number(segment.replace(/[^\d]/g, '')) } /** @internal */ export function normalizeKeySegment(segment: string): KeyedSegment { const segments = segment.match(reKeySegment) if (!segments) { throw new Error('Invalid key segment') } return {_key: segments[1]} } /** @internal */ export function normalizeIndexTupleSegment(segment: string): IndexTuple { const [from, to] = segment.split(':').map((seg) => (seg === '' ? seg : Number(seg))) return [from, to] } /** @internal */ export function pathsAreEqual(pathA: Path, pathB: Path): boolean { if (pathA.length !== pathB.length) { return false } return pathA.every((segmentA, index) => { const segmentB = pathB[index] if (isKeySegment(segmentA) && isKeySegment(segmentB)) { return segmentA._key === segmentB._key } if (isIndexSegment(segmentA)) { return Number(segmentA) === Number(segmentB) } if (isIndexTuple(segmentA) && isIndexTuple(segmentB)) { return segmentA[0] === segmentB[0] && segmentA[1] === segmentB[1] } return segmentA === segmentB }) } /** @internal */ export function getItemKey(arrayItem: unknown): string | undefined { return isKeyedObject(arrayItem) ? arrayItem._key : undefined } /** @internal */ export function getItemKeySegment(arrayItem: unknown): KeyedSegment | undefined { const key = getItemKey(arrayItem) return key ? {_key: key} : undefined } /** @internal */ export function isEmptyObject(item: unknown): boolean { return typeof item === 'object' && item !== null && Object.keys(item).length <= 0 }