From d3ba4a533d49512dbab76a0764cbbf415204084d Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 17 Jan 2024 23:37:09 -0600 Subject: [PATCH 1/3] Update route method and flattened types to support query params --- src/types/flattened.spec-d.ts | 31 +++++++++++++++++ src/types/flattened.ts | 26 +++++++++------ src/types/routeMethods.spec-d.ts | 37 ++++++++++++++++++++- src/types/routeMethods.ts | 57 ++++++++++++++++++++++---------- 4 files changed, 122 insertions(+), 29 deletions(-) diff --git a/src/types/flattened.spec-d.ts b/src/types/flattened.spec-d.ts index c370773f..19e154d9 100644 --- a/src/types/flattened.spec-d.ts +++ b/src/types/flattened.spec-d.ts @@ -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 = [ @@ -79,4 +80,34 @@ test('returns correct param type for routes', () => { A: [string, string], }, }>() +}) + +test('works with query params', () => { + const routes = [ + { + path: '/:A', + query: query('B=:B', { + B: Boolean, + }), + children: [ + { + name: 'child', + path: path('/:B/:?D', { + D: Boolean, + }), + query: 'A=:A&D=:?D', + component, + }, + ], + }, + ] as const satisfies Routes + + expectTypeOf>().toMatchTypeOf<{ + child: { + A: [string, string], + B: [string, boolean], + D?: [boolean | undefined, string | undefined], + }, + }>() + }) \ No newline at end of file diff --git a/src/types/flattened.ts b/src/types/flattened.ts index 9931107d..80800f6e 100644 --- a/src/types/flattened.ts +++ b/src/types/flattened.ts @@ -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 = Record + TPathParams extends Record = Record, + TQueryParams extends Record = Record > = Identity< TRoute extends Route - ? RouteFlat & RouteChildrenFlat + ? RouteFlat & RouteChildrenFlat : TRoute extends Routes ? UnionToIntersection<{ [K in keyof TRoute]: TRoute[K] extends Route - ? Flattened + ? Flattened : Record }[number]> : Record @@ -21,19 +22,22 @@ TRoute extends Route type RouteFlat< TRoute extends Route, TPrefix extends string, - TParams extends Record = Record -> = TRoute extends Public & { path: infer Path, name: infer Name extends string } - ? Record, MarkOptionalParams>>> + TPathParams extends Record = Record, + TQueryParams extends Record = Record +> = TRoute extends Public & { name: infer Name extends string } + ? Record, MarkOptionalParams, RouteQueryParams>>> : Record + type RouteChildrenFlat< TRoute extends Route, TPrefix extends string, - TParams extends Record = Record -> = TRoute extends { path: infer Path, children: infer Children extends Routes } + TPathParams extends Record = Record, + TQueryParams extends Record = Record +> = TRoute extends { children: infer Children extends Routes } ? TRoute extends Public & { name: infer Name extends string } - ? Flattened, MergeParams>> - : Flattened, MergeParams>> + ? Flattened, RoutePathParams, RouteQueryParams> + : Flattened, RoutePathParams, RouteQueryParams> : Record type Prefix< diff --git a/src/types/routeMethods.spec-d.ts b/src/types/routeMethods.spec-d.ts index 419b2298..312a73ad 100644 --- a/src/types/routeMethods.spec-d.ts +++ b/src/types/routeMethods.spec-d.ts @@ -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 = { @@ -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', @@ -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], + }>() }) \ No newline at end of file diff --git a/src/types/routeMethods.ts b/src/types/routeMethods.ts index 9b659dea..4d582488 100644 --- a/src/types/routeMethods.ts +++ b/src/types/routeMethods.ts @@ -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 = Record -> = Identity[number]>> + TPathParams extends Record = Record, + TQueryParams extends Record = Record +> = Identity[number]>> type RouteMethodsTuple< TRoutes extends Routes, - TParams extends Record + TPathParams extends Record, + TQueryParams extends Record > = { [K in keyof TRoutes]: TRoutes[K] extends { name: infer Name extends string } - ? { [N in Name]: RouteMethodsOrMethod } - : RouteMethodsOrMethod + ? { [N in Name]: RouteMethodsOrMethod } + : RouteMethodsOrMethod } type RouteMethodsOrMethod< TRoute extends Route, - TParams extends Record -> = TRoute extends { path: infer Path, children: infer Children } + TPathParams extends Record, + TQueryParams extends Record +> = TRoute extends { children: infer Children } ? Children extends Routes ? TRoute extends Public - ? RouteMethods>> & CreateRouteMethod - : RouteMethods>> + ? RouteMethods, RouteQueryParams> & CreateRouteMethod, RouteQueryParams>> + : RouteMethods, RouteQueryParams> : never - : TRoute extends { path: infer Path } - ? TRoute extends Public - ? CreateRouteMethod - : never + : TRoute extends Public + ? CreateRouteMethod, RouteQueryParams>> : never type CreateRouteMethod< - TParams extends Record, - TPath extends Route['path'] -> = RouteMethod>>> + TParams extends Record +> = RouteMethod> export type ExtractRouteMethodParams = T extends () => RouteMethodResponse @@ -44,7 +45,21 @@ export type ExtractRouteMethodParams = ? Params : never -export type ExtractParamsFromPath< +export type RoutePathParams< + TRoute extends Route, + TPathParams extends Record +> = TRoute extends { path: infer Path } + ? MergeParams> + : MergeParams + +export type RouteQueryParams< + TRoute extends Route, + TQueryParams extends Record +> = TRoute extends { query: infer Query } + ? MergeParams> + : MergeParams + +type ExtractParamsFromPath< TPath extends Route['path'] > = TPath extends Path ? TPath['params'] @@ -52,6 +67,14 @@ export type ExtractParamsFromPath< ? Path['params'] : never +type ExtractParamsFromQuery< + TQuery extends Route['query'] +> = TQuery extends Query + ? TQuery['params'] + : TQuery extends string + ? Query['params'] + : never + export type MergeParams< TAlpha extends Record, TBeta extends Record From 2f07f3c33952c289789a7b95f33e27b8dd8af902 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 17 Jan 2024 23:41:23 -0600 Subject: [PATCH 2/3] use consistent names for params in tests --- src/types/flattened.spec-d.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/types/flattened.spec-d.ts b/src/types/flattened.spec-d.ts index 19e154d9..942bfebb 100644 --- a/src/types/flattened.spec-d.ts +++ b/src/types/flattened.spec-d.ts @@ -85,17 +85,17 @@ test('returns correct param type for routes', () => { test('works with query params', () => { const routes = [ { - path: '/:A', - query: query('B=:B', { - B: Boolean, + path: '/:param1', + query: query('param2=:param2', { + param2: Boolean, }), children: [ { name: 'child', - path: path('/:B/:?D', { - D: Boolean, + path: path('/:param2/:?param3', { + param3: Boolean, }), - query: 'A=:A&D=:?D', + query: 'param1=:param1¶m3=:?param3', component, }, ], @@ -104,9 +104,9 @@ test('works with query params', () => { expectTypeOf>().toMatchTypeOf<{ child: { - A: [string, string], - B: [string, boolean], - D?: [boolean | undefined, string | undefined], + param1: [string, string], + param2: [string, boolean], + param3?: [boolean | undefined, string | undefined], }, }>() From 4d9578a217b27ae9b9fefecf9ae12486afca82b5 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 17 Jan 2024 23:42:07 -0600 Subject: [PATCH 3/3] Better test name --- src/types/flattened.spec-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/flattened.spec-d.ts b/src/types/flattened.spec-d.ts index 942bfebb..ca68cb2f 100644 --- a/src/types/flattened.spec-d.ts +++ b/src/types/flattened.spec-d.ts @@ -82,7 +82,7 @@ test('returns correct param type for routes', () => { }>() }) -test('works with query params', () => { +test('returns correct type when query params are used', () => { const routes = [ { path: '/:param1',