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
92 lines (70 loc) • 2.85 kB
text/typescript
import {difference, intersection, isPlainObject, pick} from 'lodash'
import {
type InternalSearchParam,
type MatchError,
type MatchOk,
type MatchResult,
type RouterNode,
type RouterState,
} from './types'
import {arrayify} from './utils/arrayify'
function isRecord(value: unknown): value is Record<string, unknown> {
return isPlainObject(value)
}
function createMatchError(
node: RouterNode,
missingKeys: string[],
unmappableStateKeys: string[],
): MatchError {
return {type: 'error', node, missingKeys, unmappableStateKeys}
}
function createMatchOk(
node: RouterNode,
matchedState: Record<string, string>,
searchParams: InternalSearchParam[],
child?: MatchOk | undefined,
): MatchOk {
return {type: 'ok', node, matchedState, searchParams, child}
}
/** @internal */
export function _findMatchingRoutes(node: RouterNode, _state?: RouterState): MatchResult {
if (!_state) {
return createMatchOk(node, {}, [])
}
const scopedState = node.scope ? (_state[node.scope] as RouterState) : _state
const {_searchParams: searchParams = [], ...state} = scopedState || {}
const requiredParams = node.route.segments
.filter((seg) => seg.type === 'param')
.map((seg) => seg.name)
const stateKeys = isRecord(state) ? Object.keys(state) : []
// These are params found in both the state and the route definition
const consumedParams = intersection(stateKeys, requiredParams)
// these are params found in the route definition but not in the state, can't map them to a route
const missingParams = difference(requiredParams, consumedParams)
// these are params found in the state but not in the route definition
const remainingParams = difference(stateKeys, consumedParams)
if (missingParams.length > 0) {
return createMatchError(node, missingParams, [])
}
const scopedParams = searchParams.map(([key, value]): InternalSearchParam => [[key], value])
const consumedState = pick(state, consumedParams) as Record<string, string>
if (remainingParams.length === 0) {
return createMatchOk(node, consumedState, scopedParams)
}
const children = arrayify(
(typeof node.children === 'function'
? node.children(isRecord(state) ? state : {})
: node.children) || [],
)
if (remainingParams.length > 0 && children.length === 0) {
// our state includes extra keys that's not consumed by child routes
return createMatchError(node, [], remainingParams)
}
const remainingState = pick(state, remainingParams)
const childResult = children.map((childNode) => _findMatchingRoutes(childNode, remainingState))
// Look for a matching route
const found = childResult.find((res): res is MatchOk => res.type === 'ok')
return found
? createMatchOk(node, consumedState, scopedParams, found)
: createMatchError(node, [], remainingParams)
}