Skip to content

Commit

Permalink
Merge pull request #58 from kitbagjs/combine-params
Browse files Browse the repository at this point in the history
Update route method and flattened types to support query params
  • Loading branch information
pleek91 authored Jan 18, 2024
2 parents db03c74 + 4d9578a commit 2221550
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 29 deletions.
31 changes: 31 additions & 0 deletions src/types/flattened.spec-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { expectTypeOf, test } from 'vitest'
import { Flattened, Routes } from '.'
import { component, path } from '@/utilities'
import { query } from '@/utilities/query'

test('Returns the correct route keys', () => {
const routes = [
Expand Down Expand Up @@ -79,4 +80,34 @@ test('returns correct param type for routes', () => {
A: [string, string],
},
}>()
})

test('returns correct type when query params are used', () => {
const routes = [
{
path: '/:param1',
query: query('param2=:param2', {
param2: Boolean,
}),
children: [
{
name: 'child',
path: path('/:param2/:?param3', {
param3: Boolean,
}),
query: 'param1=:param1&param3=:?param3',
component,
},
],
},
] as const satisfies Routes

expectTypeOf<Flattened<typeof routes>>().toMatchTypeOf<{
child: {
param1: [string, string],
param2: [string, boolean],
param3?: [boolean | undefined, string | undefined],
},
}>()

})
26 changes: 15 additions & 11 deletions src/types/flattened.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { ExtractParamsFromPath, MarkOptionalParams, MergeParams } from '@/types/routeMethods'
import { MarkOptionalParams, MergeParams, RoutePathParams, RouteQueryParams } from '@/types/routeMethods'
import { Public, Route, Routes } from '@/types/routes'
import { Identity, UnionToIntersection } from '@/types/utilities'

export type Flattened<
TRoute extends Route | Routes,
TPrefix extends string = '',
TParams extends Record<string, unknown> = Record<never, never>
TPathParams extends Record<string, unknown> = Record<never, never>,
TQueryParams extends Record<string, unknown> = Record<never, never>
> = Identity<
TRoute extends Route
? RouteFlat<TRoute, TPrefix, TParams> & RouteChildrenFlat<TRoute, TPrefix, TParams>
? RouteFlat<TRoute, TPrefix, TPathParams, TQueryParams> & RouteChildrenFlat<TRoute, TPrefix, TPathParams, TQueryParams>
: TRoute extends Routes
? UnionToIntersection<{
[K in keyof TRoute]: TRoute[K] extends Route
? Flattened<TRoute[K], TPrefix, TParams>
? Flattened<TRoute[K], TPrefix, TPathParams, TQueryParams>
: Record<never, never>
}[number]>
: Record<never, never>
Expand All @@ -21,19 +22,22 @@ TRoute extends Route
type RouteFlat<
TRoute extends Route,
TPrefix extends string,
TParams extends Record<string, unknown> = Record<never, never>
> = TRoute extends Public<TRoute> & { path: infer Path, name: infer Name extends string }
? Record<Prefix<Name, TPrefix>, MarkOptionalParams<MergeParams<TParams, ExtractParamsFromPath<Path>>>>
TPathParams extends Record<string, unknown> = Record<never, never>,
TQueryParams extends Record<string, unknown> = Record<never, never>
> = TRoute extends Public<TRoute> & { name: infer Name extends string }
? Record<Prefix<Name, TPrefix>, MarkOptionalParams<MergeParams<RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>>>>
: Record<never, never>


type RouteChildrenFlat<
TRoute extends Route,
TPrefix extends string,
TParams extends Record<string, unknown> = Record<never, never>
> = TRoute extends { path: infer Path, children: infer Children extends Routes }
TPathParams extends Record<string, unknown> = Record<never, never>,
TQueryParams extends Record<string, unknown> = Record<never, never>
> = TRoute extends { children: infer Children extends Routes }
? TRoute extends Public<TRoute> & { name: infer Name extends string }
? Flattened<Children, Prefix<Name, TPrefix>, MergeParams<TParams, ExtractParamsFromPath<Path>>>
: Flattened<Children, Prefix<'', TPrefix>, MergeParams<TParams, ExtractParamsFromPath<Path>>>
? Flattened<Children, Prefix<Name, TPrefix>, RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>>
: Flattened<Children, Prefix<'', TPrefix>, RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>>
: Record<never, never>

type Prefix<
Expand Down
37 changes: 36 additions & 1 deletion src/types/routeMethods.spec-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { test, expectTypeOf } from 'vitest'
import { ParamGetSet } from '@/types/params'
import { Routes } from '@/types/routes'
import { createRouter, path } from '@/utilities'
import { query } from '@/utilities/query'
import { component } from '@/utilities/testHelpers'

const boolean: ParamGetSet<boolean> = {
Expand Down Expand Up @@ -425,7 +426,7 @@ test('param names must be alphanumeric', () => {
}>()
})

test('route method returns correct type when called', async () => {
test('route method returns correct type when called', () => {
const routes = [
{
name: 'foo',
Expand All @@ -437,4 +438,38 @@ test('route method returns correct type when called', async () => {
const router = createRouter(routes)

expectTypeOf(router.routes.foo()).toMatchTypeOf<{ url: string }>()
})

test('params includes query params', () => {
const routes = [
{
name: 'parent',
path: '/:param1',
query: 'param2=:param2',
children: [
{
name: 'child',
path: path('/:param2', {
param2: Boolean,
}),
query: query('param1=:param1', {
param1: Boolean,
}),
component,
},
],
},
] as const satisfies Routes

const router = createRouter(routes)

expectTypeOf(router.routes.parent).parameter(0).toEqualTypeOf<{
param1: string,
param2: string,
}>()

expectTypeOf(router.routes.parent.child).parameter(0).toEqualTypeOf<{
param1: [string, boolean],
param2: [boolean, string],
}>()
})
57 changes: 40 additions & 17 deletions src/types/routeMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,41 @@ import { RouteMethod, RouteMethodResponse } from '@/types/routeMethod'
import { Public, Route, Routes } from '@/types/routes'
import { Identity, TupleCanBeAllUndefined, UnionToIntersection } from '@/types/utilities'
import { Path } from '@/utilities/path'
import { Query } from '@/utilities/query'

export type RouteMethods<
TRoutes extends Routes = Routes,
TParams extends Record<string, unknown> = Record<never, never>
> = Identity<UnionToIntersection<RouteMethodsTuple<TRoutes, TParams>[number]>>
TPathParams extends Record<string, unknown> = Record<never, never>,
TQueryParams extends Record<string, unknown> = Record<never, never>
> = Identity<UnionToIntersection<RouteMethodsTuple<TRoutes, TPathParams, TQueryParams>[number]>>

type RouteMethodsTuple<
TRoutes extends Routes,
TParams extends Record<string, unknown>
TPathParams extends Record<string, unknown>,
TQueryParams extends Record<string, unknown>
> = {
[K in keyof TRoutes]: TRoutes[K] extends { name: infer Name extends string }
? { [N in Name]: RouteMethodsOrMethod<TRoutes[K], TParams> }
: RouteMethodsOrMethod<TRoutes[K], TParams>
? { [N in Name]: RouteMethodsOrMethod<TRoutes[K], TPathParams, TQueryParams> }
: RouteMethodsOrMethod<TRoutes[K], TPathParams, TQueryParams>
}

type RouteMethodsOrMethod<
TRoute extends Route,
TParams extends Record<string, unknown>
> = TRoute extends { path: infer Path, children: infer Children }
TPathParams extends Record<string, unknown>,
TQueryParams extends Record<string, unknown>
> = TRoute extends { children: infer Children }
? Children extends Routes
? TRoute extends Public<TRoute>
? RouteMethods<Children, MergeParams<TParams, ExtractParamsFromPath<Path>>> & CreateRouteMethod<TParams, Path>
: RouteMethods<Children, MergeParams<TParams, ExtractParamsFromPath<Path>>>
? RouteMethods<Children, RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>> & CreateRouteMethod<MergeParams<RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>>>
: RouteMethods<Children, RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>>
: never
: TRoute extends { path: infer Path }
? TRoute extends Public<TRoute>
? CreateRouteMethod<TParams, Path>
: never
: TRoute extends Public<TRoute>
? CreateRouteMethod<MergeParams<RoutePathParams<TRoute, TPathParams>, RouteQueryParams<TRoute, TQueryParams>>>
: never

type CreateRouteMethod<
TParams extends Record<string, unknown>,
TPath extends Route['path']
> = RouteMethod<MarkOptionalParams<MergeParams<TParams, ExtractParamsFromPath<TPath>>>>
TParams extends Record<string, unknown[]>
> = RouteMethod<MarkOptionalParams<TParams>>

export type ExtractRouteMethodParams<T extends RouteMethod> =
T extends () => RouteMethodResponse
Expand All @@ -44,14 +45,36 @@ export type ExtractRouteMethodParams<T extends RouteMethod> =
? Params
: never

export type ExtractParamsFromPath<
export type RoutePathParams<
TRoute extends Route,
TPathParams extends Record<string, unknown>
> = TRoute extends { path: infer Path }
? MergeParams<TPathParams, ExtractParamsFromPath<Path>>
: MergeParams<TPathParams, {}>

export type RouteQueryParams<
TRoute extends Route,
TQueryParams extends Record<string, unknown>
> = TRoute extends { query: infer Query }
? MergeParams<TQueryParams, ExtractParamsFromQuery<Query>>
: MergeParams<TQueryParams, {}>

type ExtractParamsFromPath<
TPath extends Route['path']
> = TPath extends Path
? TPath['params']
: TPath extends string
? Path<TPath, object>['params']
: never

type ExtractParamsFromQuery<
TQuery extends Route['query']
> = TQuery extends Query
? TQuery['params']
: TQuery extends string
? Query<TQuery, object>['params']
: never

export type MergeParams<
TAlpha extends Record<string, unknown>,
TBeta extends Record<string, unknown>
Expand Down

0 comments on commit 2221550

Please sign in to comment.