Skip to content

Commit 043a97b

Browse files
authored
feat: App interface (#445)
* fix: app generics applied in use handler * fix: added interfaces for app and router, replaced app descriptions to interface, debug use function * fix: set method jsdoc * chore: @app minor bump
1 parent 112ea09 commit 043a97b

File tree

6 files changed

+127
-69
lines changed

6 files changed

+127
-69
lines changed

packages/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tinyhttp/app",
3-
"version": "2.4.0",
3+
"version": "2.5.0",
44
"description": "0-legacy, tiny & fast web framework as a replacement of Express",
55
"type": "module",
66
"homepage": "https://tinyhttp.v1rtl.site",

packages/app/src/app.ts

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { onErrorHandler } from './onError.js'
1010
import { getURLParams } from './request.js'
1111
import type { Request, URLParams } from './request.js'
1212
import type { Response } from './response.js'
13-
import type { AppConstructor, AppRenderOptions, AppSettings, TemplateEngine } from './types.js'
13+
import type { AppConstructor, AppInterface, AppRenderOptions, AppSettings, TemplateEngine } from './types.js'
1414
import { View } from './view.js'
1515

1616
/**
@@ -57,7 +57,11 @@ const applyHandler =
5757
* const app = App<any, CoolReq, Response>()
5858
* ```
5959
*/
60-
export class App<Req extends Request = Request, Res extends Response = Response> extends Router<App, Req, Res> {
60+
61+
export class App<Req extends Request = Request, Res extends Response = Response>
62+
extends Router<App, Req, Res>
63+
implements AppInterface<Req, Res>
64+
{
6165
middleware: Middleware<Req, Res>[] = []
6266
locals: Record<string, unknown> = {}
6367
noMatchHandler: Handler
@@ -87,64 +91,32 @@ export class App<Req extends Request = Request, Res extends Response = Response>
8791
this.cache = {}
8892
}
8993

90-
/**
91-
* Set app setting
92-
* @param setting setting name
93-
* @param value setting value
94-
*/
9594
set<K extends keyof AppSettings>(setting: K, value: AppSettings[K]): this {
9695
this.settings[setting] = value
9796

9897
return this
9998
}
10099

101-
/**
102-
* Enable app setting
103-
* @param setting Setting name
104-
*/
105100
enable<K extends keyof AppSettings>(setting: K): this {
106101
this.settings[setting] = true as AppSettings[K]
107102

108103
return this
109104
}
110105

111-
/**
112-
* Check if setting is enabled
113-
* @param setting Setting name
114-
* @returns
115-
*/
116-
enabled<K extends keyof AppSettings>(setting: K): boolean {
106+
enabled<K extends keyof AppSettings>(setting: K) {
117107
return Boolean(this.settings[setting])
118108
}
119109

120-
/**
121-
* Disable app setting
122-
* @param setting Setting name
123-
*/
124110
disable<K extends keyof AppSettings>(setting: K): this {
125111
this.settings[setting] = false as AppSettings[K]
126112

127113
return this
128114
}
129115

130-
/**
131-
* Return the app's absolute pathname
132-
* based on the parent(s) that have
133-
* mounted it.
134-
*
135-
* For example if the application was
136-
* mounted as `"/admin"`, which itself
137-
* was mounted as `"/blog"` then the
138-
* return value would be `"/blog/admin"`.
139-
*
140-
*/
141-
path(): string {
116+
path() {
142117
return this.parent ? this.parent.path() + this.mountpath : ''
143118
}
144119

145-
/**
146-
* Register a template engine with extension
147-
*/
148120
engine<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(
149121
ext: string,
150122
fn: TemplateEngine<RenderOptions>
@@ -153,19 +125,12 @@ export class App<Req extends Request = Request, Res extends Response = Response>
153125
return this
154126
}
155127

156-
/**
157-
* Render a template
158-
* @param name What to render
159-
* @param data data that is passed to a template
160-
* @param options Template engine options
161-
* @param cb Callback that consumes error and html
162-
*/
163128
render<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(
164129
name: string,
165130
data: Record<string, unknown> = {},
166131
options: AppRenderOptions<RenderOptions> = {} as AppRenderOptions<RenderOptions>,
167132
cb: (err: unknown, html?: unknown) => void = () => {}
168-
): void {
133+
) {
169134
let view: View | undefined
170135

171136
const { _locals, ...opts } = options
@@ -211,7 +176,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
211176
cb(err)
212177
}
213178
}
214-
use(...args: UseMethodParams<Req, Res, App>): this {
179+
use(...args: UseMethodParams<Req, Res, AppInterface<any, any>>): this {
215180
const base = args[0]
216181

217182
const fns = args.slice(1).flat()
@@ -278,7 +243,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
278243
return this
279244
}
280245

281-
route(path: string): App {
246+
route(path: string): AppInterface<any, any> {
282247
const app = new App({ settings: this.settings })
283248

284249
this.use(path, app)
@@ -300,17 +265,11 @@ export class App<Req extends Request = Request, Res extends Response = Response>
300265
})
301266
}
302267

303-
/**
304-
* Extends Req / Res objects, pushes 404 and 500 handlers, dispatches middleware
305-
* @param req Req object
306-
* @param res Res object
307-
* @param next 'Next' function
308-
*/
309268
handler<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(
310269
req: Req,
311270
res: Res,
312271
next?: NextFunction
313-
): void {
272+
) {
314273
/* Set X-Powered-By header */
315274
const { xPoweredBy } = this.settings
316275
if (xPoweredBy) res.setHeader('X-Powered-By', typeof xPoweredBy === 'string' ? xPoweredBy : 'tinyhttp')
@@ -425,13 +384,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
425384
loop()
426385
}
427386

428-
/**
429-
* Creates HTTP server and dispatches middleware
430-
* @param port server listening port
431-
* @param cb callback to be invoked after server starts listening
432-
* @param host server listening host
433-
*/
434-
listen(port?: number, cb?: () => void, host?: string): Server {
387+
listen(port?: number, cb?: () => void, host?: string) {
435388
return createServer().on('request', this.attach).listen(port, host, cb)
436389
}
437390
}

packages/app/src/types.ts

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import type { Server } from 'node:http'
12
/* c8 ignore start*/
23
import type { Trust } from '@tinyhttp/proxy-addr'
3-
import type { Handler, NextFunction } from '@tinyhttp/router'
4+
import type { Handler, NextFunction, RouterInterface, UseMethodParams } from '@tinyhttp/router'
45
import type { ErrorHandler } from './onError.js'
56
import type { Request } from './request.js'
67
import type { Response } from './response.js'
@@ -50,5 +51,89 @@ export type AppConstructor<Req extends Request = Request, Res extends Response =
5051
onError: ErrorHandler
5152
settings: AppSettings
5253
applyExtensions: Handler<Req, Res>
54+
new (options: AppConstructor<Req, Res>): AppInterface<Req, Res>
5355
}>
54-
/* c8 ignore stop */
56+
57+
export interface AppInterface<Req extends Request, Res extends Response>
58+
extends RouterInterface<AppInterface<Req, Res>, Req, Res> {
59+
/**
60+
* Set app setting
61+
* @param setting setting name
62+
* @param value setting value
63+
*/
64+
set<K extends keyof AppSettings>(setting: K, value: AppSettings[K]): AppInterface<Req, Res>
65+
66+
/**
67+
* Enable app setting
68+
* @param setting Setting name
69+
*/
70+
enable<K extends keyof AppSettings>(setting: K): AppInterface<Req, Res>
71+
72+
/**
73+
* Check if setting is enabled
74+
* @param setting Setting name
75+
* @returns
76+
*/
77+
enabled<K extends keyof AppSettings>(setting: K): boolean
78+
79+
/**
80+
* Disable app setting
81+
* @param setting Setting name
82+
*/
83+
disable<K extends keyof AppSettings>(setting: K): AppInterface<Req, Res>
84+
85+
/**
86+
* Return the app's absolute pathname
87+
* based on the parent(s) that have
88+
* mounted it.
89+
*
90+
* For example if the application was
91+
* mounted as `"/admin"`, which itself
92+
* was mounted as `"/blog"` then the
93+
* return value would be `"/blog/admin"`.
94+
*
95+
*/
96+
path(): string
97+
98+
/**
99+
* Register a template engine with extension
100+
*/
101+
engine<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(
102+
ext: string,
103+
fn: TemplateEngine<RenderOptions>
104+
): AppInterface<Req, Res>
105+
106+
/**
107+
* Render a template
108+
* @param name What to render
109+
* @param data data that is passed to a template
110+
* @param options Template engine options
111+
* @param cb Callback that consumes error and html
112+
*/
113+
render<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(
114+
name: string,
115+
data: Record<string, unknown>,
116+
options: AppRenderOptions<RenderOptions>,
117+
cb: (err: unknown, html?: unknown) => void
118+
): void
119+
120+
use(...args: UseMethodParams<Req, Res, AppInterface<any, any>>): AppInterface<Req, Res>
121+
122+
route(path: string): AppInterface<Req, Res>
123+
124+
/**
125+
* Extends Req / Res objects, pushes 404 and 500 handlers, dispatches middleware
126+
* @param req Req object
127+
* @param res Res object
128+
* @param next 'Next' function
129+
*/
130+
handler(req: Req, res: Res, next?: NextFunction): void
131+
132+
/**
133+
* Creates HTTP server and dispatches middleware
134+
* @param port server listening port
135+
* @param cb callback to be invoked after server starts listening
136+
* @param host server listening host
137+
*/
138+
listen(port?: number, cb?: () => void, host?: string): Server
139+
}

packages/jsonp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tinyhttp/jsonp",
3-
"version": "2.1.12",
3+
"version": "2.1.13",
44
"type": "module",
55
"description": "JSONP response middleware",
66
"homepage": "https://tinyhttp.v1rtl.site",

packages/router/src/index.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,32 @@ export type RouterMethod<Req = any, Res = any> = (
9090

9191
type RouterMethodParams<Req = any, Res = any> = Parameters<RouterMethod<Req, Res>>
9292

93-
export type UseMethod<Req = any, Res = any, App extends Router = any> = (
93+
export type UseMethod<Req = any, Res = any, App extends RouterInterface<App, Req, Res> = any> = (
9494
path: RouterPathOrHandler<Req, Res> | App,
9595
handler?: RouterHandler<Req, Res> | App,
9696
...handlers: (RouterHandler<Req, Res> | App)[]
9797
) => any
9898

99-
export type UseMethodParams<Req = any, Res = any, App extends Router = any> = Parameters<UseMethod<Req, Res, App>>
99+
export type UseMethodParams<Req = any, Res = any, App extends RouterInterface<App, Req, Res> = any> = Parameters<
100+
UseMethod<Req, Res, App>
101+
>
102+
103+
export type RouterConstructor<App extends RouterInterface<any, any, any> = any, Req = any, Res = any> = {
104+
new (): RouterInterface<App, Req, Res>
105+
}
106+
107+
export interface RouterInterface<App extends RouterInterface<App, Req, Res>, Req, Res> {
108+
add(
109+
method: Method
110+
): (
111+
path: string | string[] | Handler<Req, Res>,
112+
handler?: RouterHandler<Req, Res> | undefined,
113+
...handlers: RouterHandler<Req, Res>[]
114+
) => RouterInterface<App, Req, Res>
115+
msearch(...args: RouterMethodParams<Req, Res>): RouterInterface<App, Req, Res>
116+
all(...args: RouterMethodParams<Req, Res>): RouterInterface<App, Req, Res>
117+
use(...args: UseMethodParams<Req, Res, App>): RouterInterface<App, Req, Res>
118+
}
100119

101120
/** HELPER METHODS */
102121

@@ -155,7 +174,7 @@ export const pushMiddleware =
155174
/**
156175
* tinyhttp Router. Manages middleware and has HTTP methods aliases, e.g. `app.get`, `app.put`
157176
*/
158-
export class Router<App extends Router = any, Req = any, Res = any> {
177+
export class Router<App extends Router = any, Req = any, Res = any> implements RouterInterface<App, Req, Res> {
159178
middleware: Middleware[] = []
160179
mountpath = '/'
161180
parent!: App

tests/core/request.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Agent } from 'node:http'
22
import { makeFetch } from 'supertest-fetch'
33
import { assert, afterEach, describe, expect, it, vi } from 'vitest'
4+
import type { Request } from '../../packages/app/src'
45
import { App } from '../../packages/app/src/app'
56
import * as req from '../../packages/req/src'
67
import { InitAppAndTest } from '../../test_helpers/initAppAndTest'
@@ -94,10 +95,10 @@ describe('Request properties', () => {
9495
next()
9596
}
9697
const makeApp = () =>
97-
new App()
98+
new App<Request & { urls: string[] }>()
9899
.get('/', echo)
99100
.use('/a1/b', echo)
100-
.use('/a2/b', mw, mw, mw, (req, res) => res.send({ urls: (req as any).urls, params: req.params }))
101+
.use('/a2/b', mw, mw, mw, (req, res) => res.send({ urls: req.urls, params: req.params }))
101102
.use('/a3/:pat1/:pat2', echo)
102103
.use('/a4/:pat1/*', echo)
103104

0 commit comments

Comments
 (0)