UNPKG

@furystack/shades

Version:

A lightweight UI framework for FuryStack with JSX support

123 lines (110 loc) 5.17 kB
import type { NestedRoute } from './nested-router.js' /** * Concatenates parent and child route paths, handling the '/' root specially * to avoid double slashes. * @typeParam Parent - The parent route path * @typeParam Child - The child route path */ export type ConcatPaths<Parent extends string, Child extends string> = Parent extends '/' ? Child : `${Parent}${Child}` /** * Recursively extracts all valid full URL paths from a nested route tree. * @typeParam T - A record of route patterns to NestedRoute definitions */ export type ExtractRoutePaths<T extends Record<string, NestedRoute<any, any, any>>> = { [K in keyof T & string]: | K | (T[K] extends { children: infer C extends Record<string, NestedRoute<any, any, any>> } ? ConcatPaths<K, ExtractRoutePaths<C> & string> : never) }[keyof T & string] /** * Extracts route parameter names from a URL pattern and creates a record type * mapping each parameter name to `string`. * Returns `Record<string, never>` when no parameters are present. * @typeParam T - A URL pattern string potentially containing `:param` segments */ export type ExtractRouteParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string } : T extends `${string}:${infer Param}` ? { [K in Param]: string } : Record<string, never> /** * A recursive type for validating URL constant objects against a set of valid paths. * Leaf values must be valid route paths, and intermediate values can be nested UrlTree objects. * @typeParam TPaths - Union of valid route path strings */ export type UrlTree<TPaths extends string> = { [key: string]: TPaths | UrlTree<TPaths> } /** * A query string validator function for a nested route. Receives the deserialized * query string object and returns either the typed query value or `null` when * the URL's query does not match the route's expected shape. * @typeParam TQuery - The typed query shape the route expects */ export type QueryValidator<TQuery> = (raw: Record<string, unknown>) => TQuery | null /** * A readonly tuple of string literals describing the allowed URL hash values * for a nested route. */ export type HashLiterals = readonly string[] /** * Extracts the typed query shape declared by a {@link NestedRoute}'s `query` validator. * Resolves to `never` when the route has no `query` validator. * @typeParam R - A NestedRoute value type */ export type ExtractRouteQuery<R> = R extends { query: QueryValidator<infer Q> } ? Q : never /** * Extracts the typed hash literal union declared by a {@link NestedRoute}'s `hash` tuple. * Resolves to `never` when the route has no `hash` declaration. * @typeParam R - A NestedRoute value type */ export type ExtractRouteHash<R> = R extends { hash: infer H extends HashLiterals } ? H[number] : never /** * Walks a nested route tree and resolves the route value at the given composed path. * Used to surface per-route `query` / `hash` metadata to call-site helpers. * @typeParam TRoutes - The route tree * @typeParam TPath - A full composed route path (as produced by {@link ExtractRoutePaths}) */ export type RouteAt<TRoutes extends Record<string, NestedRoute<any, any, any>>, TPath extends string> = { [K in keyof TRoutes & string]: TPath extends K ? TRoutes[K] : K extends '/' ? TRoutes[K] extends { children: infer C extends Record<string, NestedRoute<any, any, any>> } ? RouteAt<C, TPath> : never : TPath extends `${K}${infer Rest}` ? TRoutes[K] extends { children: infer C extends Record<string, NestedRoute<any, any, any>> } ? RouteAt<C, Rest> : never : never }[keyof TRoutes & string] /** * Builds the `params` portion of a typed navigation / link argument. * Resolves to `{ params?: Record<string, string> }` when the path has no `:param` * segments and `{ params: {...} }` when at least one is required. * @typeParam TPath - A URL pattern string */ export type TypedParamsArg<TPath extends string> = string extends keyof ExtractRouteParams<TPath> ? { params?: Record<string, string> } : { params: ExtractRouteParams<TPath> } /** * Builds the `query` portion of a typed navigation / link argument. * - `never` (route declared no schema) → property is forbidden. * - All-optional schema → property itself is optional. * - Any required key → property is required. * @typeParam TQuery - The route's typed query shape */ export type TypedQueryArg<TQuery> = [TQuery] extends [never] ? { query?: never } : // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- `{} extends T` is the idiomatic way to detect "all keys optional" on an arbitrary object shape. {} extends TQuery ? { query?: TQuery } : { query: TQuery } /** * Builds the `hash` portion of a typed navigation / link argument. * Hash is always optional at the call site (URL fragments are never required * for correctness of navigation). * @typeParam THash - The route's hash literal union */ export type TypedHashArg<THash> = [THash] extends [never] ? { hash?: never } : { hash?: THash }