@@ -42,34 +42,85 @@ function createNamedFunctionalController(
4242 } [ className ] as new ( ) => any ;
4343}
4444
45- export interface FunctionalRouteInput {
46- params ?: unknown ;
47- query ?: unknown ;
48- body ?: unknown ;
49- headers ?: unknown ;
45+ export interface FunctionalRouteInput <
46+ TParams = unknown ,
47+ TQuery = unknown ,
48+ TBody = unknown ,
49+ THeaders = unknown ,
50+ > {
51+ params ?: TParams ;
52+ query ?: TQuery ;
53+ body ?: TBody ;
54+ headers ?: THeaders ;
5055}
5156
52- export interface FunctionalRouteHandlerArgs {
53- input : FunctionalRouteInput ;
57+ type EmptyFunctionalRouteInput = FunctionalRouteInput <
58+ undefined ,
59+ undefined ,
60+ undefined ,
61+ undefined
62+ > ;
63+
64+ type InferSafeParseData < TResult > = Extract < TResult , { success : true } > extends {
65+ data : infer TData ;
66+ }
67+ ? TData
68+ : unknown ;
69+
70+ type InferSchemaValue < TSchema > = [ TSchema ] extends [ undefined ]
71+ ? unknown
72+ : TSchema extends { parseAsync ( value : any ) : Promise < infer TResult > }
73+ ? TResult
74+ : TSchema extends { parse ( value : any ) : infer TResult }
75+ ? TResult
76+ : TSchema extends { safeParseAsync ( value : any ) : Promise < infer TResult > }
77+ ? InferSafeParseData < TResult >
78+ : TSchema extends { safeParse ( value : any ) : infer TResult }
79+ ? InferSafeParseData < TResult >
80+ : unknown ;
81+
82+ type InferFunctionalRouteInput < TInput extends FunctionalRouteInput > = ( [ TInput [ 'params' ] ] extends [ undefined ]
83+ ? { params ?: unknown }
84+ : { params : InferSchemaValue < TInput [ 'params' ] > } ) &
85+ ( [ TInput [ 'query' ] ] extends [ undefined ]
86+ ? { query ?: unknown }
87+ : { query : InferSchemaValue < TInput [ 'query' ] > } ) &
88+ ( [ TInput [ 'body' ] ] extends [ undefined ]
89+ ? { body ?: unknown }
90+ : { body : InferSchemaValue < TInput [ 'body' ] > } ) &
91+ ( [ TInput [ 'headers' ] ] extends [ undefined ]
92+ ? { headers ?: unknown }
93+ : { headers : InferSchemaValue < TInput [ 'headers' ] > } ) ;
94+
95+ export interface FunctionalRouteHandlerArgs <
96+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
97+ > {
98+ input : InferFunctionalRouteInput < TInput > ;
5499 ctx : any ;
55100 next ?: NextFunction ;
56101}
57102
58- export interface FunctionalRouteDefinition {
103+ export interface FunctionalRouteDefinition <
104+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
105+ TOutput = unknown ,
106+ > {
59107 method : string ;
60108 path : string | RegExp ;
61- options : FunctionalRouteOptions ;
62- handle : ( args : FunctionalRouteHandlerArgs ) => Promise < unknown > | unknown ;
109+ options : FunctionalRouteOptions < TInput , TOutput > ;
110+ handle : ( args : FunctionalRouteHandlerArgs < TInput > ) => Promise < unknown > | unknown ;
63111}
64112
65- export interface FunctionalRouteOptions {
113+ export interface FunctionalRouteOptions <
114+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
115+ TOutput = unknown ,
116+ > {
66117 routerName ?: string ;
67118 middleware ?: any [ ] ;
68119 summary ?: string ;
69120 description ?: string ;
70121 ignoreGlobalPrefix ?: boolean ;
71- input ?: FunctionalRouteInput ;
72- output ?: unknown ;
122+ input ?: TInput ;
123+ output ?: TOutput ;
73124}
74125
75126export type FunctionalControllerOptions = {
@@ -92,17 +143,37 @@ export interface FunctionalApiModuleMeta {
92143 versionPrefix ?: string ;
93144}
94145
95- export interface RouteBuilder {
96- input ( schema : FunctionalRouteOptions [ 'input' ] ) : RouteBuilder ;
97- output ( schema : FunctionalRouteOptions [ 'output' ] ) : RouteBuilder ;
98- middleware ( mw : any [ ] ) : RouteBuilder ;
99- meta ( options : Omit < FunctionalRouteOptions , 'input' | 'output' > ) : RouteBuilder ;
100- handle ( fn : FunctionalRouteDefinition [ 'handle' ] ) : FunctionalRouteDefinition ;
146+ export interface RouteBuilder <
147+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
148+ TOutput = unknown ,
149+ > {
150+ input <
151+ TParams = undefined ,
152+ TQuery = undefined ,
153+ TBody = undefined ,
154+ THeaders = undefined ,
155+ > (
156+ schema : FunctionalRouteInput < TParams , TQuery , TBody , THeaders >
157+ ) : RouteBuilder <
158+ FunctionalRouteInput < TParams , TQuery , TBody , THeaders > ,
159+ TOutput
160+ > ;
161+ output < TNextOutput > ( schema : TNextOutput ) : RouteBuilder < TInput , TNextOutput > ;
162+ middleware ( mw : any [ ] ) : RouteBuilder < TInput , TOutput > ;
163+ meta (
164+ options : Omit < FunctionalRouteOptions < TInput , TOutput > , 'input' | 'output' >
165+ ) : RouteBuilder < TInput , TOutput > ;
166+ handle (
167+ fn : FunctionalRouteDefinition < TInput , TOutput > [ 'handle' ]
168+ ) : FunctionalRouteDefinition < TInput , TOutput > ;
101169}
102170
103- interface RouteBuilderInternal extends RouteBuilder {
171+ interface RouteBuilderInternal <
172+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
173+ TOutput = unknown ,
174+ > extends RouteBuilder < TInput , TOutput > {
104175 __isRouteBuilder : true ;
105- __build : ( ) => FunctionalRouteDefinition ;
176+ __build : ( ) => FunctionalRouteDefinition < TInput , TOutput > ;
106177}
107178
108179const HTTP_METHODS = [
@@ -116,12 +187,15 @@ const HTTP_METHODS = [
116187 RequestMethod . ALL ,
117188] as const ;
118189
119- function createRouteBuilder (
190+ function createRouteBuilder <
191+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
192+ TOutput = unknown ,
193+ > (
120194 method : string ,
121195 path : string | RegExp = '/'
122- ) : RouteBuilderInternal {
123- const route : Omit < FunctionalRouteDefinition , 'handle' > & {
124- handle ?: FunctionalRouteDefinition [ 'handle' ] ;
196+ ) : RouteBuilderInternal < TInput , TOutput > {
197+ const route : Omit < FunctionalRouteDefinition < TInput , TOutput > , 'handle' > & {
198+ handle ?: FunctionalRouteDefinition < TInput , TOutput > [ 'handle' ] ;
125199 } = {
126200 method,
127201 path,
@@ -130,15 +204,15 @@ function createRouteBuilder(
130204 } ,
131205 } ;
132206
133- const builder : RouteBuilderInternal = {
207+ const builder : RouteBuilderInternal < TInput , TOutput > = {
134208 __isRouteBuilder : true ,
135209 input ( schema ) {
136- route . options . input = schema ;
137- return builder ;
210+ route . options . input = schema as any ;
211+ return builder as any ;
138212 } ,
139213 output ( schema ) {
140- route . options . output = schema ;
141- return builder ;
214+ route . options . output = schema as any ;
215+ return builder as any ;
142216 } ,
143217 middleware ( mw ) {
144218 route . options . middleware = mw || [ ] ;
@@ -161,14 +235,16 @@ function createRouteBuilder(
161235 'Functional route is missing handler, call .handle(fn) to finish route definition'
162236 ) ;
163237 }
164- return route as FunctionalRouteDefinition ;
238+ return route as FunctionalRouteDefinition < TInput , TOutput > ;
165239 } ,
166240 } ;
167241
168242 return builder ;
169243}
170244
171- function getInputFromContext ( ctx : any ) : FunctionalRouteInput {
245+ function getInputFromContext (
246+ ctx : any
247+ ) : FunctionalRouteInput < unknown , unknown , unknown , unknown > {
172248 return {
173249 params : ctx ?. params ,
174250 query : ctx ?. query ,
@@ -219,12 +295,12 @@ async function runSchemaValidation(
219295 return value ;
220296}
221297
222- async function validateInput (
223- schema : FunctionalRouteDefinition [ 'options' ] [ 'input' ] ,
298+ async function validateInput < TInput extends FunctionalRouteInput > (
299+ schema : FunctionalRouteDefinition < TInput > [ 'options' ] [ 'input' ] ,
224300 input : FunctionalRouteInput
225- ) : Promise < FunctionalRouteInput > {
301+ ) : Promise < InferFunctionalRouteInput < TInput > > {
226302 if ( ! schema ) {
227- return input ;
303+ return input as InferFunctionalRouteInput < TInput > ;
228304 }
229305
230306 return {
@@ -240,16 +316,22 @@ async function validateInput(
240316 input . headers ,
241317 'input.headers'
242318 ) ,
243- } ;
319+ } as InferFunctionalRouteInput < TInput > ;
244320}
245321
246- function normalizeRouteDefinition (
322+ function normalizeRouteDefinition <
323+ TInput extends FunctionalRouteInput = EmptyFunctionalRouteInput ,
324+ TOutput = unknown ,
325+ > (
247326 routeName : string ,
248- routeValue : FunctionalRouteDefinition | RouteBuilderInternal
249- ) : FunctionalRouteDefinition {
250- const route = ( routeValue as RouteBuilderInternal ) ?. __isRouteBuilder
251- ? ( routeValue as RouteBuilderInternal ) . __build ( )
252- : ( routeValue as FunctionalRouteDefinition ) ;
327+ routeValue :
328+ | FunctionalRouteDefinition < TInput , TOutput >
329+ | RouteBuilderInternal < TInput , TOutput >
330+ ) : FunctionalRouteDefinition < TInput , TOutput > {
331+ const route = ( routeValue as RouteBuilderInternal < TInput , TOutput > )
332+ ?. __isRouteBuilder
333+ ? ( routeValue as RouteBuilderInternal < TInput , TOutput > ) . __build ( )
334+ : ( routeValue as FunctionalRouteDefinition < TInput , TOutput > ) ;
253335
254336 if ( ! route || typeof route !== 'object' ) {
255337 throw new Error (
@@ -270,10 +352,28 @@ function normalizeRouteDefinition(
270352 middleware : [ ] ,
271353 ...route . options ,
272354 } ,
273- } ;
355+ } as FunctionalRouteDefinition < TInput , TOutput > ;
274356}
275357
276- export function defineApi (
358+ type NormalizeDefinedRoute < T > =
359+ T extends RouteBuilderInternal < infer TInput , infer TOutput >
360+ ? FunctionalRouteDefinition < TInput , TOutput >
361+ : T extends FunctionalRouteDefinition < infer TInput , infer TOutput >
362+ ? FunctionalRouteDefinition < TInput , TOutput >
363+ : never ;
364+
365+ type NormalizeDefinedRoutes <
366+ TRoutes extends Record < string , FunctionalRouteDefinition | RouteBuilderInternal > ,
367+ > = {
368+ [ K in keyof TRoutes ] : NormalizeDefinedRoute < TRoutes [ K ] > ;
369+ } ;
370+
371+ export function defineApi <
372+ TRoutes extends Record <
373+ string ,
374+ FunctionalRouteDefinition < any , any > | RouteBuilderInternal < any , any >
375+ > ,
376+ > (
277377 prefix : string ,
278378 factory : ( api : {
279379 get ( path ?: string | RegExp ) : RouteBuilder ;
@@ -284,12 +384,12 @@ export function defineApi(
284384 options ( path ?: string | RegExp ) : RouteBuilder ;
285385 head ( path ?: string | RegExp ) : RouteBuilder ;
286386 all ( path ?: string | RegExp ) : RouteBuilder ;
287- } ) => Record < string , FunctionalRouteDefinition | RouteBuilderInternal > ,
387+ } ) => TRoutes ,
288388 controllerOptions : FunctionalControllerOptions = {
289389 middleware : [ ] ,
290390 sensitive : true ,
291391 }
292- ) : Record < string , FunctionalRouteDefinition > {
392+ ) : NormalizeDefinedRoutes < TRoutes > {
293393 const routeFactory = {
294394 get ( path : string | RegExp = '/' ) {
295395 return createRouteBuilder ( RequestMethod . GET , path ) ;
@@ -318,7 +418,7 @@ export function defineApi(
318418 } ;
319419
320420 const definedRoutes = factory ( routeFactory ) ;
321- const normalizedRoutes : Record < string , FunctionalRouteDefinition > = { } ;
421+ const normalizedRoutes = { } as Record < string , FunctionalRouteDefinition < any , any > > ;
322422 const routeNames = Object . keys ( definedRoutes || { } ) ;
323423
324424 const FunctionalApiController = createNamedFunctionalController (
@@ -442,5 +542,5 @@ export function defineApi(
442542 writable : false ,
443543 } ) ;
444544
445- return normalizedRoutes ;
545+ return normalizedRoutes as NormalizeDefinedRoutes < TRoutes > ;
446546}
0 commit comments