Skip to content

Commit 3481b35

Browse files
authored
Merge pull request #94 from frouriojs/develop
feat: add response schema and hooks to controller
2 parents 70eae15 + e281324 commit 3481b35

File tree

48 files changed

+531
-410
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+531
-410
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
## [0.31.0](https://github.com/frouriojs/frourio-express/compare/v0.30.0...v0.31.0) (2022-11-24)
6+
7+
### Features
8+
9+
- feat: add response schema and hooks to controller (https://github.com/frouriojs/frourio-express/pull/94)
10+
511
## [0.30.0](https://github.com/frouriojs/frourio-express/compare/v0.29.0...v0.30.0) (2022-11-18)
612

713
### Features

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# frourio-express
2+
23
<br />
34
<img src="https://frouriojs.github.io/frourio/assets/images/ogp.png" width="1280" alt="frourio-express" />
45

@@ -33,15 +34,15 @@ We are always forced to write "Two TypeScript".
3334
We waste a lot of time on dynamic testing using the browser and server.
3435

3536
<div align="center">
36-
<img src="https://frourio.io/img/TwoTS.svg" width="1200" alt="Why frourio ?" />
37+
<img src="https://frourio.com/img/TwoTS.svg" width="1200" alt="Why frourio ?" />
3738
</div>
3839
<br />
3940
<br />
4041

4142
Frourio-express is a framework for developing web apps quickly and safely in **"One TypeScript"**.
4243

4344
<div align="center">
44-
<img src="https://frourio.io/img/OneTS.svg" width="1200" alt="Architecture of create-frourio-app" />
45+
<img src="https://frourio.com/img/OneTS.svg" width="1200" alt="Architecture of create-frourio-app" />
4546
</div>
4647
<br />
4748
<br />

__test__/index.spec.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -207,18 +207,6 @@ test('POST: formdata', async () => {
207207

208208
test('PUT: zod validations', async () => {
209209
const port = '3000'
210-
const res1 = await fetchClient.$put({
211-
query: {
212-
requiredNum: 0,
213-
requiredNumArr: [],
214-
id: '1',
215-
disable: 'true',
216-
bool: false,
217-
boolArray: []
218-
},
219-
body: { port }
220-
})
221-
expect(res1.port).toBe(port)
222210

223211
await Promise.all([
224212
expect(
@@ -248,6 +236,20 @@ test('PUT: zod validations', async () => {
248236
})
249237
).rejects.toHaveProperty('response.status', 400)
250238
])
239+
240+
await expect(
241+
fetchClient.put({
242+
query: {
243+
requiredNum: 0,
244+
requiredNumArr: [],
245+
id: '1',
246+
disable: 'true',
247+
bool: false,
248+
boolArray: []
249+
},
250+
body: { port }
251+
})
252+
).resolves.toHaveProperty('status', 201)
251253
})
252254

253255
test('POST: multi file upload', async () => {

jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { compilerOptions } from './tsconfig.json'
55
const config: Config.InitialOptions = {
66
preset: 'ts-jest',
77
testEnvironment: 'node',
8-
globals: { Blob: {}, 'ts-jest': {} },
8+
globals: { Blob: {} },
99
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
1010
coveragePathIgnorePatterns: ['\\$api.ts', 'dist']
1111
}

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"main": "dist/index.js",
88
"types": "dist/index.d.ts",
99
"bin": "bin/index.js",
10-
"homepage": "https://frourio.io",
10+
"homepage": "https://frourio.com",
1111
"repository": {
1212
"type": "git",
1313
"url": "git+https://github.com/frouriojs/frourio-express.git"
@@ -86,8 +86,8 @@
8686
"devDependencies": {
8787
"@aspida/axios": "^1.11.0",
8888
"@aspida/node-fetch": "^1.11.0",
89-
"@types/express": "^4.17.7",
90-
"@types/jest": "^26.0.23",
89+
"@types/express": "^4.17.14",
90+
"@types/jest": "^29.2.3",
9191
"@types/multer": "^1.4.7",
9292
"@types/node-fetch": "^2.5.10",
9393
"@types/rimraf": "^3.0.0",
@@ -114,6 +114,6 @@
114114
"rimraf": "^3.0.2",
115115
"ts-jest": "^29.0.3",
116116
"ts-node": "^10.9.1",
117-
"typescript": "^4.8.4"
117+
"typescript": "^4.9.3"
118118
}
119119
}

servers/all/$server.ts

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import type { Express, RequestHandler, Request } from 'express'
88
import express from 'express'
99
import type { Options } from 'multer'
1010
import multer from 'multer'
11-
import type { Schema } from 'fast-json-stringify'
1211
import fastJson from 'fast-json-stringify'
1312
import * as Validators from './validators'
1413
import type { ReadStream } from 'fs'
1514
import type { HttpStatusOk, AspidaMethodParams } from 'aspida'
15+
import type { Schema } from 'fast-json-stringify'
1616
import type { z } from 'zod'
1717
import hooksFn0 from './api/hooks'
1818
import hooksFn1 from './api/empty/hooks'
@@ -35,11 +35,11 @@ import controllerFn9 from './api/users/_userId@number/_name/controller'
3535

3636
export type FrourioOptions = {
3737
basePath?: string
38-
transformer?: ClassTransformOptions | undefined
39-
validator?: ValidatorOptions | undefined
40-
plainToInstance?: ((cls: new (...args: any[]) => object, object: unknown, options: ClassTransformOptions) => object) | undefined
41-
validateOrReject?: ((instance: object, options: ValidatorOptions) => Promise<void>) | undefined
42-
multer?: Options | undefined
38+
transformer?: ClassTransformOptions
39+
validator?: ValidatorOptions
40+
plainToInstance?: ((cls: new (...args: any[]) => object, object: unknown, options: ClassTransformOptions) => object)
41+
validateOrReject?: ((instance: object, options: ValidatorOptions) => Promise<void>)
42+
multer?: Options
4343
}
4444

4545
export type MulterFile = Express.Multer.File
@@ -97,6 +97,13 @@ type ServerHandlerPromise<T extends AspidaMethodParams, U extends Record<string,
9797

9898
export type ServerMethodHandler<T extends AspidaMethodParams, U extends Record<string, any> = {}> = ServerHandler<T, U> | ServerHandlerPromise<T, U> | {
9999
validators?: Partial<{ [Key in keyof RequestParams<T>]?: z.ZodType<RequestParams<T>[Key]>}>
100+
schemas?: { response?: { [V in HttpStatusOk]?: Schema }}
101+
hooks?: {
102+
onRequest?: RequestHandler | RequestHandler[]
103+
preParsing?: RequestHandler | RequestHandler[]
104+
preValidation?: RequestHandler | RequestHandler[]
105+
preHandler?: RequestHandler | RequestHandler[]
106+
}
100107
handler: ServerHandler<T, U> | ServerHandlerPromise<T, U>
101108
}
102109

@@ -255,9 +262,48 @@ const asyncMethodToHandler = (
255262
}
256263
}
257264

265+
const methodToHandlerWithSchema = (
266+
methodCallback: ServerHandler<any, any>,
267+
schema: { [K in HttpStatusOk]?: Schema }
268+
): RequestHandler => {
269+
const stringifySet = Object.entries(schema).reduce(
270+
(prev, [key, val]) => ({ ...prev, [key]: fastJson(val!) }),
271+
{} as Record<HttpStatusOk, ReturnType<typeof fastJson> | undefined>
272+
)
273+
274+
return (req, res, next) => {
275+
try {
276+
const data = methodCallback(req as any) as any
277+
const stringify = stringifySet[data.status as HttpStatusOk]
278+
279+
if (stringify) {
280+
res.set('content-type', 'application/json; charset=utf-8')
281+
282+
if (data.headers) {
283+
for (const key in data.headers) {
284+
res.setHeader(key, data.headers[key])
285+
}
286+
}
287+
288+
res.status(data.status).send(stringify(data.body))
289+
} else {
290+
if (data.headers) {
291+
for (const key in data.headers) {
292+
res.setHeader(key, data.headers[key])
293+
}
294+
}
295+
296+
res.status(data.status).send(data.body)
297+
}
298+
} catch (e) {
299+
next(e)
300+
}
301+
}
302+
}
303+
258304
const asyncMethodToHandlerWithSchema = (
259305
methodCallback: ServerHandlerPromise<any, any>,
260-
schema: { [K in HttpStatusOk]?: Schema | undefined }
306+
schema: { [K in HttpStatusOk]?: Schema }
261307
): RequestHandler => {
262308
const stringifySet = Object.entries(schema).reduce(
263309
(prev, [key, val]) => ({ ...prev, [key]: fastJson(val!) }),
@@ -355,8 +401,10 @@ export default (app: Express, options: FrourioOptions = {}) => {
355401
parseNumberTypeQueryParams([['requiredNum', false, false], ['optionalNum', true, false], ['optionalNumArr', true, true], ['emptyNum', true, false], ['requiredNumArr', false, true]]),
356402
parseBooleanTypeQueryParams([['bool', false, false], ['optionalBool', true, false], ['boolArray', false, true], ['optionalBoolArray', true, true]]),
357403
parseJSONBoby,
404+
...controller0.put.hooks.preValidation,
358405
...Object.entries(controller0.put.validators).map(([key, validator]) => validatorCompiler(key as 'query' | 'headers' | 'body', validator)),
359-
methodToHandler(controller0.put.handler)
406+
controller0.put.hooks.preHandler,
407+
methodToHandlerWithSchema(controller0.put.handler, controller0.put.schemas.response)
360408
])
361409

362410
app.get(`${basePath}/500`, [

servers/all/api/$relay.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import type { ServerMethodHandler } from '../$server'
77
import type { Methods } from './'
88

99
type Hooks = {
10-
onRequest?: RequestHandler | RequestHandler[] | undefined
11-
preParsing?: RequestHandler | RequestHandler[] | undefined
12-
preValidation?: RequestHandler | RequestHandler[] | undefined
13-
preHandler?: RequestHandler | RequestHandler[] | undefined
10+
onRequest?: RequestHandler | RequestHandler[]
11+
preParsing?: RequestHandler | RequestHandler[]
12+
preValidation?: RequestHandler | RequestHandler[]
13+
preHandler?: RequestHandler | RequestHandler[]
1414
}
1515

16-
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema | undefined } | undefined }>(methods: () => T) {
16+
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema }}>(methods: () => T) {
1717
return methods
1818
}
1919

2020
export function defineHooks<T extends Hooks>(hooks: (app: Express) => T): (app: Express) => T
2121
export function defineHooks<T extends Record<string, any>, U extends Hooks>(deps: T, cb: (d: T, app: Express) => U): Injectable<T, [Express], U>
22-
export function defineHooks<T extends Record<string, any>>(hooks: (app: Express) => Hooks | T, cb?: ((deps: T, app: Express) => Hooks) | undefined) {
22+
export function defineHooks<T extends Record<string, any>>(hooks: (app: Express) => Hooks | T, cb?: ((deps: T, app: Express) => Hooks)) {
2323
return cb && typeof hooks !== 'function' ? depend(hooks, cb) : hooks
2424
}
2525

@@ -29,6 +29,6 @@ type ServerMethods = {
2929

3030
export function defineController<M extends ServerMethods>(methods: (app: Express) => M): (app: Express) => M
3131
export function defineController<M extends ServerMethods, T extends Record<string, any>>(deps: T, cb: (d: T, app: Express) => M): Injectable<T, [Express], M>
32-
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((app: Express) => M) | T, cb?: ((deps: T, app: Express) => M) | undefined) {
32+
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((app: Express) => M) | T, cb?: ((deps: T, app: Express) => M)) {
3333
return cb && typeof methods !== 'function' ? depend(methods, cb) : methods
3434
}

servers/all/api/500/$relay.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import type { ServerMethodHandler } from '../../$server'
77
import type { Methods } from './'
88

99
type Hooks = {
10-
onRequest?: RequestHandler | RequestHandler[] | undefined
11-
preParsing?: RequestHandler | RequestHandler[] | undefined
12-
preValidation?: RequestHandler | RequestHandler[] | undefined
13-
preHandler?: RequestHandler | RequestHandler[] | undefined
10+
onRequest?: RequestHandler | RequestHandler[]
11+
preParsing?: RequestHandler | RequestHandler[]
12+
preValidation?: RequestHandler | RequestHandler[]
13+
preHandler?: RequestHandler | RequestHandler[]
1414
}
1515

16-
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema | undefined } | undefined }>(methods: () => T) {
16+
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema }}>(methods: () => T) {
1717
return methods
1818
}
1919

2020
export function defineHooks<T extends Hooks>(hooks: (app: Express) => T): (app: Express) => T
2121
export function defineHooks<T extends Record<string, any>, U extends Hooks>(deps: T, cb: (d: T, app: Express) => U): Injectable<T, [Express], U>
22-
export function defineHooks<T extends Record<string, any>>(hooks: (app: Express) => Hooks | T, cb?: ((deps: T, app: Express) => Hooks) | undefined) {
22+
export function defineHooks<T extends Record<string, any>>(hooks: (app: Express) => Hooks | T, cb?: ((deps: T, app: Express) => Hooks)) {
2323
return cb && typeof hooks !== 'function' ? depend(hooks, cb) : hooks
2424
}
2525

@@ -29,6 +29,6 @@ type ServerMethods = {
2929

3030
export function defineController<M extends ServerMethods>(methods: (app: Express) => M): (app: Express) => M
3131
export function defineController<M extends ServerMethods, T extends Record<string, any>>(deps: T, cb: (d: T, app: Express) => M): Injectable<T, [Express], M>
32-
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((app: Express) => M) | T, cb?: ((deps: T, app: Express) => M) | undefined) {
32+
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((app: Express) => M) | T, cb?: ((deps: T, app: Express) => M)) {
3333
return cb && typeof methods !== 'function' ? depend(methods, cb) : methods
3434
}

servers/all/api/controller.ts

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,15 @@ const responseSchema = defineResponseSchema(() => ({
1414
200: {
1515
type: 'object',
1616
properties: {
17-
id: {
18-
type: 'string'
19-
},
20-
emptyNum: {
21-
type: 'number'
22-
},
23-
requiredNum: {
24-
type: 'number'
25-
},
26-
requiredNumArr: {
27-
type: 'array',
28-
items: {
29-
type: 'number'
30-
}
31-
},
32-
bool: {
33-
type: 'boolean'
34-
},
35-
optionalBool: {
36-
type: 'boolean'
37-
},
38-
boolArray: {
39-
type: 'array',
40-
items: {
41-
type: 'boolean'
42-
}
43-
},
44-
optionalBoolArray: {
45-
type: 'array',
46-
items: {
47-
type: 'boolean'
48-
}
49-
},
50-
disable: {
51-
type: 'string'
52-
}
17+
id: { type: 'string' },
18+
emptyNum: { type: 'number' },
19+
requiredNum: { type: 'number' },
20+
requiredNumArr: { type: 'array', items: { type: 'number' } },
21+
bool: { type: 'boolean' },
22+
optionalBool: { type: 'boolean' },
23+
boolArray: { type: 'array', items: { type: 'boolean' } },
24+
optionalBoolArray: { type: 'array', items: { type: 'boolean' } },
25+
disable: { type: 'string' }
5326
}
5427
}
5528
}
@@ -87,6 +60,21 @@ export default defineController(
8760
}),
8861
body: z.object({ port: z.string() })
8962
},
63+
schemas: {
64+
response: {
65+
201: {
66+
type: 'object',
67+
properties: { id: { type: 'number' }, port: { type: 'string' } }
68+
}
69+
}
70+
},
71+
hooks: {
72+
preValidation: [],
73+
preHandler: (req, _, done) => {
74+
console.log(req.method)
75+
done()
76+
}
77+
},
9078
handler: v => ({
9179
status: 201,
9280
body: { id: +v.query.id, port: v.body.port }

servers/all/api/empty/$relay.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import type { ServerMethodHandler } from '../../$server'
77
import type { Methods } from './'
88

99
type Hooks = {
10-
onRequest?: RequestHandler | RequestHandler[] | undefined
11-
preParsing?: RequestHandler | RequestHandler[] | undefined
12-
preValidation?: RequestHandler | RequestHandler[] | undefined
13-
preHandler?: RequestHandler | RequestHandler[] | undefined
10+
onRequest?: RequestHandler | RequestHandler[]
11+
preParsing?: RequestHandler | RequestHandler[]
12+
preValidation?: RequestHandler | RequestHandler[]
13+
preHandler?: RequestHandler | RequestHandler[]
1414
}
1515

16-
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema | undefined } | undefined }>(methods: () => T) {
16+
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema }}>(methods: () => T) {
1717
return methods
1818
}
1919

2020
export function defineHooks<T extends Hooks>(hooks: (app: Express) => T): (app: Express) => T
2121
export function defineHooks<T extends Record<string, any>, U extends Hooks>(deps: T, cb: (d: T, app: Express) => U): Injectable<T, [Express], U>
22-
export function defineHooks<T extends Record<string, any>>(hooks: (app: Express) => Hooks | T, cb?: ((deps: T, app: Express) => Hooks) | undefined) {
22+
export function defineHooks<T extends Record<string, any>>(hooks: (app: Express) => Hooks | T, cb?: ((deps: T, app: Express) => Hooks)) {
2323
return cb && typeof hooks !== 'function' ? depend(hooks, cb) : hooks
2424
}
2525

@@ -29,6 +29,6 @@ type ServerMethods = {
2929

3030
export function defineController<M extends ServerMethods>(methods: (app: Express) => M): (app: Express) => M
3131
export function defineController<M extends ServerMethods, T extends Record<string, any>>(deps: T, cb: (d: T, app: Express) => M): Injectable<T, [Express], M>
32-
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((app: Express) => M) | T, cb?: ((deps: T, app: Express) => M) | undefined) {
32+
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((app: Express) => M) | T, cb?: ((deps: T, app: Express) => M)) {
3333
return cb && typeof methods !== 'function' ? depend(methods, cb) : methods
3434
}

0 commit comments

Comments
 (0)