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
109 lines (88 loc) • 3.25 kB
text/typescript
import {_findMatchingRoutes} from './_findMatchingRoutes'
import {encodeURIComponentExcept} from './encodeURIComponentExcept'
import {type InternalSearchParam, type MatchOk, type RouterNode, type RouterState} from './types'
import {debug} from './utils/debug'
/** @internal */
export function _resolvePathFromState(node: RouterNode, _state: RouterState): string {
debug('Resolving path from state %o', _state)
const match = _findMatchingRoutes(node, _state)
if (match.type === 'error') {
const unmappable = match.unmappableStateKeys
if (unmappable.length > 0) {
throw new Error(
`Unable to find matching route for state. Could not map the following state key${
unmappable.length == 1 ? '' : 's'
} to a valid url: ${unmappable.map(quote).join(', ')}`,
)
}
const missingKeys = match.missingKeys
throw new Error(
`Unable to find matching route for state. State object is missing the following key${
missingKeys.length == 1 ? '' : 's'
} defined in route: ${missingKeys.map(quote).join(', ')}`,
)
}
const {path, searchParams} = pathFromMatchResult(match)
const search = searchParams.length > 0 ? encodeParams(searchParams) : ''
return `/${path.join('/')}${search ? `?${search}` : ''}`
}
function bracketify(value: string): string {
return `[${value}]`
}
function encodeParams(params: InternalSearchParam[]): string {
return params
.flatMap(([key, value]) => {
if (value === undefined) {
return []
}
return [encodeSearchParamKey(serializeScopedPath(key)), encodeSearchParamValue(value)].join(
'=',
)
})
.join('&')
}
function serializeScopedPath(scopedPath: string[]): string {
const [head, ...tail] = scopedPath
return tail.length > 0 ? [head, ...tail.map(bracketify)].join('') : head
}
function encodeSearchParamValue(value: string): string {
return encodeURIComponentExcept(value, '/')
}
function encodeSearchParamKey(value: string): string {
return encodeURIComponentExcept(value, '[]')
}
function pathFromMatchResult(match: MatchOk): {
path: string[]
searchParams: InternalSearchParam[]
} {
const matchedState = match.matchedState
const base = match.node.route.segments.map((segment) => {
if (segment.type === 'dir') {
return segment.name
}
const transform = match.node.transform && match.node.transform[segment.name]
return transform
? transform.toPath(matchedState[segment.name] as any)
: matchedState[segment.name]
})
const childMatch = match.child ? pathFromMatchResult(match.child) : undefined
const searchParams = childMatch?.searchParams
? [...match.searchParams, ...childMatch.searchParams]
: match.searchParams
return {
searchParams: addNodeScope(match.node, searchParams),
path: [...(base || []), ...(childMatch?.path || [])],
}
}
function addNodeScope(
node: RouterNode,
searchParams: InternalSearchParam[],
): InternalSearchParam[] {
const scope = node.scope
return scope && !node.__unsafe_disableScopedSearchParams
? searchParams.map(([namespaces, value]) => [[scope, ...namespaces], value])
: searchParams
}
function quote(value: string): string {
return `"${value}"`
}