From 4928d6ef343963aeeb3dbd4df9932f7f4de694e2 Mon Sep 17 00:00:00 2001 From: Serhii Sol Date: Thu, 24 Aug 2023 16:15:08 +0300 Subject: [PATCH] feat(server): beta.11 - fastify adapter (#191) --- server/README.md | 9 +- server/example/app.module.ts | 5 +- server/example/main.ts | 16 +- .../{app-version.spec.ts => express.spec.ts} | 2 +- .../core/app-version/test/fastify.spec.ts | 34 + ...tom-decorators.spec.ts => express.spec.ts} | 2 +- .../custom-decorators/test/fastify.spec.ts | 44 ++ .../test/{pipes.spec.ts => express.spec.ts} | 2 +- .../core/pipes/test/fastify.spec.ts | 71 ++ .../test/{scopes.spec.ts => express.spec.ts} | 2 +- .../core/scopes/test/fastify.spec.ts | 25 + ...tadata-scanner.spec.ts => express.spec.ts} | 4 +- .../metadata-scanner/test/fastify.spec.ts | 39 + .../http/params/src/app.controller.ts | 6 +- .../http/params/test/express.spec.ts | 2 +- .../http/params/test/fastify.spec.ts | 113 +++ .../http/routes/test/express.spec.ts | 2 +- .../http/routes/test/fastify.spec.ts | 70 ++ .../integration/swagger/test/express.spec.ts | 2 +- .../integration/swagger/test/fastify.spec.ts | 42 ++ server/package-lock.json | 711 +++++++++++++++++- server/package.json | 37 +- server/src/core/helpers/decorators.ts | 2 +- server/src/core/utils.ts | 10 +- .../src/platforms/express/express-adapter.ts | 21 +- .../src/platforms/fastify/fastify-adapter.ts | 82 ++ server/src/platforms/fastify/index.ts | 1 + .../http/helpers/http-application-adapter.ts | 7 +- .../platforms/http/helpers/route-handler.ts | 22 +- server/src/platforms/http/http.module.ts | 2 +- .../helpers/swagger-ui/swagger-resolver.ts | 8 +- server/tsconfig.json | 10 +- tools/git-pre-push | 12 +- 33 files changed, 1352 insertions(+), 65 deletions(-) rename server/integration/core/app-version/test/{app-version.spec.ts => express.spec.ts} (94%) create mode 100644 server/integration/core/app-version/test/fastify.spec.ts rename server/integration/core/custom-decorators/test/{custom-decorators.spec.ts => express.spec.ts} (95%) create mode 100644 server/integration/core/custom-decorators/test/fastify.spec.ts rename server/integration/core/pipes/test/{pipes.spec.ts => express.spec.ts} (98%) create mode 100644 server/integration/core/pipes/test/fastify.spec.ts rename server/integration/core/scopes/test/{scopes.spec.ts => express.spec.ts} (92%) create mode 100644 server/integration/core/scopes/test/fastify.spec.ts rename server/integration/http/metadata-scanner/test/{metadata-scanner.spec.ts => express.spec.ts} (93%) create mode 100644 server/integration/http/metadata-scanner/test/fastify.spec.ts create mode 100644 server/integration/http/params/test/fastify.spec.ts create mode 100644 server/integration/http/routes/test/fastify.spec.ts create mode 100644 server/integration/swagger/test/fastify.spec.ts create mode 100644 server/src/platforms/fastify/fastify-adapter.ts create mode 100644 server/src/platforms/fastify/index.ts diff --git a/server/README.md b/server/README.md index 744bf4a..0db1c27 100644 --- a/server/README.md +++ b/server/README.md @@ -3,16 +3,15 @@ ## Installation Main dependencies ``` -npm install @decorators/di --save -npm install @decorators/server --save +npm install @decorators/server @decorators/di --save ``` -And adapter specific imports +Adapter specific imports ``` -npm install express --save +npm install express body-parser --save ``` Or ``` -npm install fastify --save +npm install fastify @fastify/cookie @fastify/static @fastify/view --save ``` ## Example diff --git a/server/example/app.module.ts b/server/example/app.module.ts index 5bf6565..33b7e03 100644 --- a/server/example/app.module.ts +++ b/server/example/app.module.ts @@ -1,5 +1,6 @@ import { APP_VERSION, GLOBAL_PIPE, Module } from '@server'; import { ExpressAdapter } from '@server/express'; +import { FastifyAdapter } from '@server/fastify'; import { HttpModule } from '@server/http'; import { SwaggerModule } from '@server/swagger'; @@ -8,7 +9,9 @@ import { ServerPipe } from './pipes'; @Module({ modules: [ - HttpModule.create(ExpressAdapter), + HttpModule.create( + process.env.USE_FASTIFY ? FastifyAdapter : ExpressAdapter, + ), SwaggerModule.forRoot({ description: 'Decorators Example App', title: '@decorators/server', diff --git a/server/example/main.ts b/server/example/main.ts index 5c379a7..5dde74f 100644 --- a/server/example/main.ts +++ b/server/example/main.ts @@ -1,3 +1,4 @@ +import * as FastifyView from '@fastify/view'; import { Application } from '@server'; import { HttpModule } from '@server/http'; import { json } from 'body-parser'; @@ -9,9 +10,18 @@ async function bootstrap() { const app = await Application.create(AppModule); const module = await app.inject(HttpModule); - module.set('view engine', 'ejs'); - module.set('views', join(__dirname, 'views')); - module.use(json()); + if (process.env.USE_FASTIFY) { + module.use(FastifyView, { + engine: { + ejs: require('ejs'), + }, + root: join(__dirname, 'views'), + }); + } else { + module.set('view engine', 'ejs'); + module.set('views', join(__dirname, 'views')); + module.use(json()); + } await module.listen(3000); console.info('Server is running on port 3000'); diff --git a/server/integration/core/app-version/test/app-version.spec.ts b/server/integration/core/app-version/test/express.spec.ts similarity index 94% rename from server/integration/core/app-version/test/app-version.spec.ts rename to server/integration/core/app-version/test/express.spec.ts index 216f038..977ef1c 100644 --- a/server/integration/core/app-version/test/app-version.spec.ts +++ b/server/integration/core/app-version/test/express.spec.ts @@ -13,7 +13,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('App Version', () => { +describe('Express :: App Version', () => { let app: Application; let module: HttpModule; diff --git a/server/integration/core/app-version/test/fastify.spec.ts b/server/integration/core/app-version/test/fastify.spec.ts new file mode 100644 index 0000000..0a4b64e --- /dev/null +++ b/server/integration/core/app-version/test/fastify.spec.ts @@ -0,0 +1,34 @@ +import { Application, HttpStatus, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import * as request from 'supertest'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: App Version', () => { + let app: Application; + let module: HttpModule; + + beforeEach(async () => { + app = await Application.create(TestModule); + module = await app.inject(HttpModule); + + await module.listen(); + }); + + afterEach(() => module.close()); + + it('registers `get` request with app version prefix', async () => { + return request(module.getHttpServer()) + .get('/app-version/get') + .expect(HttpStatus.OK); + }); +}); diff --git a/server/integration/core/custom-decorators/test/custom-decorators.spec.ts b/server/integration/core/custom-decorators/test/express.spec.ts similarity index 95% rename from server/integration/core/custom-decorators/test/custom-decorators.spec.ts rename to server/integration/core/custom-decorators/test/express.spec.ts index 335910f..498e90c 100644 --- a/server/integration/core/custom-decorators/test/custom-decorators.spec.ts +++ b/server/integration/core/custom-decorators/test/express.spec.ts @@ -13,7 +13,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('Custom Decorators', () => { +describe('Express :: Custom Decorators', () => { let app: Application; let module: HttpModule; diff --git a/server/integration/core/custom-decorators/test/fastify.spec.ts b/server/integration/core/custom-decorators/test/fastify.spec.ts new file mode 100644 index 0000000..8f7c0ac --- /dev/null +++ b/server/integration/core/custom-decorators/test/fastify.spec.ts @@ -0,0 +1,44 @@ +import { Application, Module, Reflector } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import * as request from 'supertest'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Custom Decorators', () => { + let app: Application; + let module: HttpModule; + + beforeEach(async () => { + app = await Application.create(TestModule); + module = await app.inject(HttpModule); + + await module.listen(); + }); + + afterEach(() => module.close()); + + it('checks availability of reflector', async () => { + expect(await app.inject(Reflector)).toBeDefined(); + }); + + it('decorates `get` request and its params', async () => { + return request(module.getHttpServer()) + .get('/?param=decorated') + .expect('decorated'); + }); + + it('throws error during `get` request', async () => { + return request(module.getHttpServer()) + .get('/?param=failure') + .expect(({ body }) => expect(body.message).toEqual('decorated-error')); + }); +}); diff --git a/server/integration/core/pipes/test/pipes.spec.ts b/server/integration/core/pipes/test/express.spec.ts similarity index 98% rename from server/integration/core/pipes/test/pipes.spec.ts rename to server/integration/core/pipes/test/express.spec.ts index 4f1ccbd..356037d 100644 --- a/server/integration/core/pipes/test/pipes.spec.ts +++ b/server/integration/core/pipes/test/express.spec.ts @@ -14,7 +14,7 @@ import { Sequence } from '../src/sequence'; }) class TestModule { } -describe('Pipes', () => { +describe('Express :: Pipes', () => { let app: Application; let module: HttpModule; let seq: Sequence; diff --git a/server/integration/core/pipes/test/fastify.spec.ts b/server/integration/core/pipes/test/fastify.spec.ts new file mode 100644 index 0000000..6942b03 --- /dev/null +++ b/server/integration/core/pipes/test/fastify.spec.ts @@ -0,0 +1,71 @@ +import { Application, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import * as request from 'supertest'; + +import { AppModule } from '../src/app.module'; +import { Sequence } from '../src/sequence'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Pipes', () => { + let app: Application; + let module: HttpModule; + let seq: Sequence; + + beforeEach(async () => { + app = await Application.create(TestModule); + module = await app.inject(HttpModule); + seq = await app.inject(Sequence); + + jest.spyOn(seq, 'push'); + + await module.listen(); + }); + + afterEach(() => module.close()); + + it('executes pipes', async () => { + return request(module.getHttpServer()) + .get('/') + .expect(() => { + expect(seq.push).toBeCalledWith('server'); + expect(seq.push).toBeCalledWith('controller'); + expect(seq.push).toBeCalledWith('method'); + expect(seq.push).toBeCalledWith('method'); + expect(seq.push).toBeCalledWith('controller'); + expect(seq.push).toBeCalledWith('server'); + }); + }); + + it('executes pipes with method error', async () => { + return request(module.getHttpServer()) + .get('/with-method-error') + .expect((res) => { + expect(res.body.message).toBe('method-error'); + expect(seq.push).toBeCalledWith('server'); + expect(seq.push).toBeCalledWith('controller'); + expect(seq.push).toBeCalledWith('method'); + expect(seq.push).toBeCalledWith('method'); + expect(seq.push).toBeCalledWith('controller'); + expect(seq.push).toBeCalledWith('server'); + }); + }); + + it('executes pipes with pipe error', async () => { + return request(module.getHttpServer()) + .get('/with-pipe-error') + .expect((res) => { + expect(res.body.message).toBe('pipe-error'); + expect(seq.push).toBeCalledWith('server'); + expect(seq.push).toBeCalledWith('controller'); + expect(seq.push).toBeCalledWith('server'); + }); + }); +}); diff --git a/server/integration/core/scopes/test/scopes.spec.ts b/server/integration/core/scopes/test/express.spec.ts similarity index 92% rename from server/integration/core/scopes/test/scopes.spec.ts rename to server/integration/core/scopes/test/express.spec.ts index 1660d4e..2845f36 100644 --- a/server/integration/core/scopes/test/scopes.spec.ts +++ b/server/integration/core/scopes/test/express.spec.ts @@ -12,7 +12,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('Scopes', () => { +describe('Express :: Scopes', () => { let app: Application; beforeEach(async () => { diff --git a/server/integration/core/scopes/test/fastify.spec.ts b/server/integration/core/scopes/test/fastify.spec.ts new file mode 100644 index 0000000..57b9e99 --- /dev/null +++ b/server/integration/core/scopes/test/fastify.spec.ts @@ -0,0 +1,25 @@ +import { Application, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Scopes', () => { + let app: Application; + + beforeEach(async () => { + app = await Application.create(TestModule); + }); + + it('creates app without errors', () => { + expect(app).toBeDefined(); + }); +}); diff --git a/server/integration/http/metadata-scanner/test/metadata-scanner.spec.ts b/server/integration/http/metadata-scanner/test/express.spec.ts similarity index 93% rename from server/integration/http/metadata-scanner/test/metadata-scanner.spec.ts rename to server/integration/http/metadata-scanner/test/express.spec.ts index 349bae0..0c94df1 100644 --- a/server/integration/http/metadata-scanner/test/metadata-scanner.spec.ts +++ b/server/integration/http/metadata-scanner/test/express.spec.ts @@ -13,7 +13,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('Metadata Scanner', () => { +describe('Express :: Metadata Scanner', () => { let app: Application; let scanner: MetadataScanner; @@ -33,7 +33,7 @@ describe('Metadata Scanner', () => { methodName: 'post', })]), type: 'post', - url: '', + url: '/', })])); }); }); diff --git a/server/integration/http/metadata-scanner/test/fastify.spec.ts b/server/integration/http/metadata-scanner/test/fastify.spec.ts new file mode 100644 index 0000000..0eb20aa --- /dev/null +++ b/server/integration/http/metadata-scanner/test/fastify.spec.ts @@ -0,0 +1,39 @@ +import { Application, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import { MetadataScanner } from '@server/http'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Metadata Scanner', () => { + let app: Application; + let scanner: MetadataScanner; + + beforeEach(async () => { + app = await Application.create(TestModule); + scanner = await app.inject(MetadataScanner); + }); + + it('provides access to the metadata', () => { + const routesMetadata = scanner.scan(); + + expect(routesMetadata).toEqual(expect.arrayContaining([expect.objectContaining({ + methodName: 'post', + params: expect.arrayContaining([expect.objectContaining({ + argName: 'body', + index: 0, + methodName: 'post', + })]), + type: 'post', + url: '/', + })])); + }); +}); diff --git a/server/integration/http/params/src/app.controller.ts b/server/integration/http/params/src/app.controller.ts index c1c7579..1656354 100644 --- a/server/integration/http/params/src/app.controller.ts +++ b/server/integration/http/params/src/app.controller.ts @@ -56,7 +56,11 @@ export class AppController { response( @Response() res: object, ) { - return res['req']['url']; + if (res['req']) { + return res['req']['url']; + } + + return res['request']['url']; } @Post('with-class-validator') diff --git a/server/integration/http/params/test/express.spec.ts b/server/integration/http/params/test/express.spec.ts index 6502810..fc2354c 100644 --- a/server/integration/http/params/test/express.spec.ts +++ b/server/integration/http/params/test/express.spec.ts @@ -15,7 +15,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('Express Params', () => { +describe('Express :: Params', () => { let app: Application; let module: HttpModule; diff --git a/server/integration/http/params/test/fastify.spec.ts b/server/integration/http/params/test/fastify.spec.ts new file mode 100644 index 0000000..5f5b317 --- /dev/null +++ b/server/integration/http/params/test/fastify.spec.ts @@ -0,0 +1,113 @@ +import * as cookie from '@fastify/cookie'; +import { Application, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import * as request from 'supertest'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Params', () => { + let app: Application; + let module: HttpModule; + + beforeEach(async () => { + app = await Application.create(TestModule); + module = await app.inject(HttpModule); + + module.use(cookie, {}); + + await module.listen(); + }); + + afterEach(() => module.close()); + + it('receives `body` params', async () => { + return request(module.getHttpServer()) + .post('/body') + .send({ example: 'param' }) + .expect({ example: 'param', param: 'param' }); + }); + + it('receives `cookies` params', async () => { + return request(module.getHttpServer()) + .post('/cookies') + .set('Cookie', ['example=param']) + .expect({ cookie: 'param', example: 'param' }); + }); + + it('receives `headers` params', async () => { + return request(module.getHttpServer()) + .post('/headers') + .set({ example: 'param' }) + .expect(({ body }) => expect(body).toEqual(expect.objectContaining({ example: 'param', header: 'param' }))); + }); + + it('receives `params` params', async () => { + return request(module.getHttpServer()) + .post('/params/param') + .expect({ example: 'param', param: 'param' }); + }); + + it('receives `query` params', async () => { + return request(module.getHttpServer()) + .post('/query?example=param') + .expect({ example: 'param', param: 'param' }); + }); + + it('receives `request` param', async () => { + return request(module.getHttpServer()) + .post('/request') + .expect('/request'); + }); + + it('receives `response` param', async () => { + return request(module.getHttpServer()) + .post('/response') + .expect('/response'); + }); + + describe('with class validator', () => { + it('passes validation', () => { + return request(module.getHttpServer()) + .post('/with-class-validator') + .send({ example: 'param' }) + .expect({ example: 'param' }); + }); + + it('fails validation', () => { + return request(module.getHttpServer()) + .post('/with-class-validator') + .send({ example: 100 }) + .expect(({ body }) => { + expect(body.message).toBeDefined(); + expect(body.errors).toBeDefined(); + }); + }); + }); + + describe('with custom validator', () => { + it('passes validation', () => { + return request(module.getHttpServer()) + .post('/with-custom-validator') + .send({ example: 'param' }) + .expect('param'); + }); + + it('fails validation', () => { + return request(module.getHttpServer()) + .post('/with-custom-validator') + .send({ example: 100 }) + .expect(({ body }) => { + expect(body.message).toBeDefined(); + }); + }); + }); +}); diff --git a/server/integration/http/routes/test/express.spec.ts b/server/integration/http/routes/test/express.spec.ts index 5963463..c8e5330 100644 --- a/server/integration/http/routes/test/express.spec.ts +++ b/server/integration/http/routes/test/express.spec.ts @@ -13,7 +13,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('Express Routes', () => { +describe('Express :: Routes', () => { let app: Application; let module: HttpModule; diff --git a/server/integration/http/routes/test/fastify.spec.ts b/server/integration/http/routes/test/fastify.spec.ts new file mode 100644 index 0000000..1d0f86a --- /dev/null +++ b/server/integration/http/routes/test/fastify.spec.ts @@ -0,0 +1,70 @@ +import { Application, HttpStatus, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import * as request from 'supertest'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Routes', () => { + let app: Application; + let module: HttpModule; + + beforeEach(async () => { + app = await Application.create(TestModule); + module = await app.inject(HttpModule); + + await module.listen(); + }); + + afterEach(() => module.close()); + + it('registers `delete` request', async () => { + return request(module.getHttpServer()) + .delete('/delete') + .expect(HttpStatus.OK, 'delete'); + }); + + it('registers `get` request', async () => { + return request(module.getHttpServer()) + .get('/get') + .expect(HttpStatus.NO_CONTENT); + }); + + it('registers `head` request', async () => { + return request(module.getHttpServer()) + .head('/head') + .expect(HttpStatus.OK); + }); + + it('registers `options` request', async () => { + return request(module.getHttpServer()) + .options('/options') + .expect(HttpStatus.OK); + }); + + it('registers `patch` request', async () => { + return request(module.getHttpServer()) + .patch('/patch') + .expect(HttpStatus.OK, 'patch'); + }); + + it('registers `post` request', async () => { + return request(module.getHttpServer()) + .post('/post') + .expect(HttpStatus.CREATED, 'post'); + }); + + it('registers `put` request', async () => { + return request(module.getHttpServer()) + .put('/put') + .expect(HttpStatus.ACCEPTED, 'put'); + }); +}); diff --git a/server/integration/swagger/test/express.spec.ts b/server/integration/swagger/test/express.spec.ts index 9f32ecb..a58687b 100644 --- a/server/integration/swagger/test/express.spec.ts +++ b/server/integration/swagger/test/express.spec.ts @@ -15,7 +15,7 @@ import { AppModule } from '../src/app.module'; }) class TestModule { } -describe('Express Swagger Route', () => { +describe('Express :: Swagger Route', () => { let app: Application; let module: HttpModule; diff --git a/server/integration/swagger/test/fastify.spec.ts b/server/integration/swagger/test/fastify.spec.ts new file mode 100644 index 0000000..76bb0b0 --- /dev/null +++ b/server/integration/swagger/test/fastify.spec.ts @@ -0,0 +1,42 @@ +import { Application, HttpStatus, Module } from '@server'; +import { FastifyAdapter } from '@server/fastify'; +import { HttpModule } from '@server/http'; +import { SwaggerModule } from '@server/swagger'; +import * as request from 'supertest'; + +import { AppModule } from '../src/app.module'; + +@Module({ + modules: [ + HttpModule.create(FastifyAdapter), + SwaggerModule.forRoot(), + AppModule, + ], +}) +class TestModule { } + +describe('Fastify :: Swagger Route', () => { + let app: Application; + let module: HttpModule; + + beforeEach(async () => { + app = await Application.create(TestModule); + module = await app.inject(HttpModule); + + await module.listen(); + }); + + afterEach(() => module.close()); + + it('registers swagger file', async () => { + return request(module.getHttpServer()) + .get('/swagger/swagger.json') + .expect(HttpStatus.OK); + }); + + it('registers swagger-ui page', async () => { + return request(module.getHttpServer()) + .get('/swagger') + .expect(301); + }); +}); diff --git a/server/package-lock.json b/server/package-lock.json index 53c5956..3e0c579 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,15 +1,18 @@ { "name": "@decorators/server", - "version": "1.0.0-beta.10", + "version": "1.0.0-beta.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@decorators/server", - "version": "1.0.0-beta.10", + "version": "1.0.0-beta.11", "license": "MIT", "devDependencies": { "@decorators/di": "../di", + "@fastify/cookie": "^9.0.4", + "@fastify/static": "^6.10.2", + "@fastify/view": "^8.0.0", "@types/express": "4.17.8", "@types/jest": "^29.5.3", "@types/supertest": "^2.0.12", @@ -26,6 +29,7 @@ "eslint-plugin-jsdoc": "^40.1.1", "eslint-plugin-simple-import-sort": "^10.0.0", "express": "^4.18.2", + "fastify": "^4.21.0", "jest": "^29.5.0", "openapi-types": "^12.1.3", "reflect-metadata": "^0.1.13", @@ -39,9 +43,35 @@ }, "peerDependencies": { "@decorators/di": "^3.1.0", + "@fastify/cookie": "^9.0.4", + "@fastify/static": "^6.10.2", + "@fastify/view": "^8.0.0", + "body-parser": "^1.20.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "express": "^4.18.2", + "fastify": "^4.21.0", "reflect-metadata": "^0.1.13" + }, + "peerDependenciesMeta": { + "@fastify/cookie": { + "optional": true + }, + "@fastify/static": { + "optional": true + }, + "@fastify/view": { + "optional": true + }, + "body-parser": { + "optional": true + }, + "express": { + "optional": true + }, + "fastify": { + "optional": true + } } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -753,6 +783,178 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", + "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", + "dev": true, + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@fastify/cookie": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-9.0.4.tgz", + "integrity": "sha512-behLOTH2u7fSZ6+TWeW8XUCmpEstwl8ysxzyb4QRxnKyt80O2S4yVfNbBZQcG9rktjeZXfR7LLl9xXKL4vdjlQ==", + "dev": true, + "dependencies": { + "cookie": "^0.5.0", + "fastify-plugin": "^4.0.0" + } + }, + "node_modules/@fastify/cookie/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==", + "dev": true + }, + "node_modules/@fastify/error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.3.0.tgz", + "integrity": "sha512-dj7vjIn1Ar8sVXj2yAXiMNCJDmS9MQ9XMlIecX2dIzzhjSHCyKo4DdXjXMs7wKW2kj6yvVRSpuQjOZ3YLrh56w==", + "dev": true + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "dev": true, + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/send": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", + "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", + "dev": true, + "dependencies": { + "@lukeed/ms": "^2.0.1", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "2.0.0", + "mime": "^3.0.0" + } + }, + "node_modules/@fastify/send/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.10.2.tgz", + "integrity": "sha512-UoaMvIHSBLCZBYOVZwFRYqX2ufUhd7FFMYGDeSf0Z+D8jhYtwljjmuQGuanUP8kS4y/ZEV1a8mfLha3zNwsnnQ==", + "dev": true, + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "glob": "^8.0.1", + "p-limit": "^3.1.0", + "readable-stream": "^4.0.0" + } + }, + "node_modules/@fastify/static/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@fastify/static/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@fastify/static/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@fastify/view": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@fastify/view/-/view-8.0.0.tgz", + "integrity": "sha512-XfAffgqRj+AtEtkZeAAkMwTtu32Ve6xWkhxWQ9JOwXm2qQM6Fj+xphxnLvqpvQ0hJAYFYGiTOpB5ZS2VI5u00Q==", + "dev": true, + "dependencies": { + "fastify-plugin": "^4.0.0", + "hashlru": "^2.3.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -1322,6 +1524,15 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@lukeed/ms": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz", + "integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1857,6 +2068,24 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1916,6 +2145,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1980,6 +2248,12 @@ "node": ">= 8" } }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2132,6 +2406,15 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -2144,6 +2427,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/avvio": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.1.tgz", + "integrity": "sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1" + } + }, "node_modules/babel-jest": { "version": "29.6.2", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", @@ -2241,6 +2535,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -2355,6 +2669,30 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3360,6 +3698,24 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3514,6 +3870,18 @@ "node": ">= 0.8" } }, + "node_modules/fast-content-type-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.0.0.tgz", + "integrity": "sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==", + "dev": true + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3554,18 +3922,108 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-json-stringify": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.8.0.tgz", + "integrity": "sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==", + "dev": true, + "dependencies": { + "@fastify/deepmerge": "^1.0.0", + "ajv": "^8.10.0", + "ajv-formats": "^2.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-uri": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", + "integrity": "sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==", + "dev": true + }, + "node_modules/fastify": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.21.0.tgz", + "integrity": "sha512-tsu4bcwE4HetxqW8prA5fbC9bKHMYDp7jGEDWyzK1l90a3uOaLoIcQbdGcWeODNLVJviQnzh1wvIjTZE3MJFEg==", + "dev": true, + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.2.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.2.1", + "fast-content-type-parse": "^1.0.0", + "fast-json-stringify": "^5.7.0", + "find-my-way": "^7.6.0", + "light-my-request": "^5.9.1", + "pino": "^8.12.0", + "process-warning": "^2.2.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.5.0", + "semver": "^7.5.0", + "tiny-lru": "^11.0.1" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -3671,6 +4129,20 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/find-my-way": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.6.2.tgz", + "integrity": "sha512-0OjHn1b1nCX3eVbm9ByeEHiscPYiHLfhei1wOUU9qffQkk98wE0Lo8VrVYfSGMgnSnDh86DxedduAnBf4nwUEw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4115,6 +4587,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashlru": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", + "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", + "dev": true + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -4167,6 +4645,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -5311,6 +5809,26 @@ "integrity": "sha512-4NjVXVUmpZ9Zsqq6FXa2+MKI+KAI3tOqA0pxXgXGluhpj4ge5didmbWJpMBqGB3AVGv1SnEtKdGTbxjSEG1kCQ==", "dev": true }, + "node_modules/light-my-request": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.10.0.tgz", + "integrity": "sha512-ZU2D9GmAcOUculTTdH9/zryej6n8TzT+fNGdNtm6SDp5MMMpHrJJkvAdE3c6d8d2chE9i+a//dS9CWZtisknqA==", + "dev": true, + "dependencies": { + "cookie": "^0.5.0", + "process-warning": "^2.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/light-my-request/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -5649,6 +6167,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==", + "dev": true + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5877,6 +6401,44 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.15.0.tgz", + "integrity": "sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "dev": true, + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "dev": true + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -5985,6 +6547,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", + "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -6071,6 +6648,12 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6101,6 +6684,31 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -6133,6 +6741,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.3.tgz", @@ -6189,6 +6806,15 @@ "node": ">=10" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6199,6 +6825,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "node_modules/rimraf": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", @@ -6338,12 +6970,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dev": true, + "dependencies": { + "ret": "~0.2.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6437,6 +7093,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6499,6 +7161,15 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6540,6 +7211,15 @@ "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6576,6 +7256,15 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6814,6 +7503,24 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thread-stream": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.3.0.tgz", + "integrity": "sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==", + "dev": true, + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tiny-lru": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.0.1.tgz", + "integrity": "sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/server/package.json b/server/package.json index e1d2bc3..06cc004 100644 --- a/server/package.json +++ b/server/package.json @@ -6,6 +6,9 @@ "description": "node decorators - decorators for express library", "devDependencies": { "@decorators/di": "../di", + "@fastify/cookie": "^9.0.4", + "@fastify/static": "^6.10.2", + "@fastify/view": "^8.0.0", "@types/express": "4.17.8", "@types/jest": "^29.5.3", "@types/supertest": "^2.0.12", @@ -22,6 +25,7 @@ "eslint-plugin-jsdoc": "^40.1.1", "eslint-plugin-simple-import-sort": "^10.0.0", "express": "^4.18.2", + "fastify": "^4.21.0", "jest": "^29.5.0", "openapi-types": "^12.1.3", "reflect-metadata": "^0.1.13", @@ -36,6 +40,7 @@ "exports": { ".": "./lib/index.js", "./express": "./lib/platforms/express/index.js", + "./fastify": "./lib/platforms/fastify/index.js", "./http": "./lib/platforms/http/index.js", "./swagger": "./lib/platforms/swagger/index.js" }, @@ -49,10 +54,36 @@ "name": "@decorators/server", "peerDependencies": { "@decorators/di": "^3.1.0", + "@fastify/cookie": "^9.0.4", + "@fastify/static": "^6.10.2", + "@fastify/view": "^8.0.0", + "body-parser": "^1.20.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "express": "^4.18.2", + "fastify": "^4.21.0", "reflect-metadata": "^0.1.13" }, + "peerDependenciesMeta": { + "@fastify/cookie": { + "optional": true + }, + "@fastify/static": { + "optional": true + }, + "@fastify/view": { + "optional": true + }, + "body-parser": { + "optional": true + }, + "express": { + "optional": true + }, + "fastify": { + "optional": true + } + }, "repository": { "type": "git", "url": "https://github.com/serhiisol/node-decorators.git" @@ -60,6 +91,7 @@ "scripts": { "build": "rimraf ./lib && tsc", "eslint": "eslint \"**/*.ts\"", + "prepush": "npm run eslint && npm run test", "test": "jest" }, "types": "lib/index.d.ts", @@ -68,6 +100,9 @@ "express": [ "./lib/platforms/express/index.d.ts" ], + "fastify": [ + "./lib/platforms/fastify/index.d.ts" + ], "http": [ "./lib/platforms/http/index.d.ts" ], @@ -76,5 +111,5 @@ ] } }, - "version": "1.0.0-beta.10" + "version": "1.0.0-beta.11" } diff --git a/server/src/core/helpers/decorators.ts b/server/src/core/helpers/decorators.ts index fa2ab4f..d88e7f5 100644 --- a/server/src/core/helpers/decorators.ts +++ b/server/src/core/helpers/decorators.ts @@ -46,7 +46,7 @@ export function methodDecoratorFactory(metadata: object) { * ... */ export function createParamDecorator(factory: (context: Context) => Promise | any) { - return paramDecoratorFactory({ factory }); + return paramDecoratorFactory({ factory: (context: Context) => () => factory(context) }); } /** diff --git a/server/src/core/utils.ts b/server/src/core/utils.ts index 917108d..c62ab92 100644 --- a/server/src/core/utils.ts +++ b/server/src/core/utils.ts @@ -1,11 +1,7 @@ import { ClassConstructor, Handler } from './types'; export function addLeadingSlash(url: string): string { - if (url.startsWith('/')) { - return url; - } - - return url ? `/${url}` : url; + return url.startsWith('/') ? url : `/${url}`; } export function buildUrl(...paths: string[]) { @@ -24,8 +20,8 @@ export function toStandardType(param: unknown) { return param === 'true'; } - if (!isNaN(Number(param)) && !isNaN(parseFloat(param as string))) { - return parseFloat(param as string); + if (typeof param === 'string' && !isNaN(Number(param)) && !isNaN(parseFloat(param))) { + return parseFloat(param); } return param; diff --git a/server/src/platforms/express/express-adapter.ts b/server/src/platforms/express/express-adapter.ts index 1dfbe58..8892cfa 100644 --- a/server/src/platforms/express/express-adapter.ts +++ b/server/src/platforms/express/express-adapter.ts @@ -6,6 +6,7 @@ import { HttpApplicationAdapter, ParameterType } from '../http/helpers'; export class ExpressAdapter implements HttpApplicationAdapter { server?: Server; + type = 'express'; constructor(public app: express.Express = express()) { } @@ -15,14 +16,14 @@ export class ExpressAdapter implements HttpApplicationAdapter { getParam(type: ParameterType, name: string, req: express.Request, res: express.Response) { switch (type) { - case ParameterType.BODY: return name ? req.body?.[name] : req.body; - case ParameterType.COOKIE: return name ? req.cookies?.[name] : req.cookies; - case ParameterType.HEADER: return name ? req.headers?.[name] : req.headers; - case ParameterType.PARAM: return name ? req.params?.[name] : req.params; - case ParameterType.QUERY: return name ? req.query?.[name] : req.query; - case ParameterType.REQUEST: return req; - case ParameterType.RESPONSE: return res; - default: return req; + case ParameterType.BODY: return () => name ? req.body?.[name] : req.body; + case ParameterType.COOKIE: return () => name ? req.cookies?.[name] : req.cookies; + case ParameterType.HEADER: return () => name ? req.headers?.[name] : req.headers; + case ParameterType.PARAM: return () => name ? req.params?.[name] : req.params; + case ParameterType.QUERY: return () => name ? req.query?.[name] : req.query; + case ParameterType.REQUEST: return () => req; + case ParameterType.RESPONSE: return () => res; + default: return () => req; } } @@ -34,8 +35,8 @@ export class ExpressAdapter implements HttpApplicationAdapter { this.server = this.app.listen(port); } - render(response: express.Response, template: string, message: object): Promise { - return new Promise((resolve, reject) => response.render(template, message, + render(response: express.Response, template: string, message: object) { + return new Promise((resolve, reject) => response.render(template, message, (err, html) => err ? reject(err) : resolve(html), )); } diff --git a/server/src/platforms/fastify/fastify-adapter.ts b/server/src/platforms/fastify/fastify-adapter.ts new file mode 100644 index 0000000..7963039 --- /dev/null +++ b/server/src/platforms/fastify/fastify-adapter.ts @@ -0,0 +1,82 @@ +import * as FastifyStatic from '@fastify/static'; +import * as Fastify from 'fastify'; +import { Server } from 'http'; + +import { Handler } from '../../core'; +import { HttpApplicationAdapter, ParameterType } from '../http/helpers'; + +export class FastifyAdapter implements HttpApplicationAdapter { + server?: Server; + type = 'fastify'; + + constructor(public app: Fastify.FastifyInstance = Fastify()) { } + + close() { + this.server?.close(); + } + + getParam(type: ParameterType, name: string, req: Fastify.FastifyRequest, res: Fastify.FastifyReply) { + switch (type) { + case ParameterType.BODY: return () => name ? req.body?.[name] : req.body; + case ParameterType.COOKIE: return () => name ? req.cookies?.[name] : req.cookies; + case ParameterType.HEADER: return () => name ? req.headers?.[name] : req.headers; + case ParameterType.PARAM: return () => name ? req.params?.[name] : req.params; + case ParameterType.QUERY: return () => name ? req.query?.[name] : req.query; + case ParameterType.REQUEST: return () => req; + case ParameterType.RESPONSE: return () => res; + default: return () => req; + } + } + + isHeadersSent(response: Fastify.FastifyReply) { + return response.sent; + } + + async listen(port: number) { + await this.app.listen({ port }); + + this.server = this.app.server; + } + + render(_response: Fastify.FastifyReply, template: string, message: object) { + return new Promise((resolve, reject) => (this.app as any).view(template, message, + (err: Error, html: string) => err ? reject(err) : resolve(html), + )); + } + + reply(response: Fastify.FastifyReply, message: unknown, statusCode?: number) { + const isJson = typeof message === 'object'; + + if (statusCode) { + response.code(statusCode) as unknown; + } + + if (isJson) { + response.header('Content-Type', 'application/json') as unknown; + } + + return response.send(message); + } + + route(url: string, type: string, handler: Handler) { + this.app[type]?.(url, handler); + } + + serveStatic(prefix: string, path: string, options?: object) { + this.app.register(FastifyStatic, { + decorateReply: false, + prefix, + redirect: true, + root: path, + ...options, + } as any) as unknown; + } + + setHeader(response: Fastify.FastifyReply, name: string, value: string) { + response.header(name, value) as unknown; + } + + use(...args: any[]) { + this.app.register(args[0], args[1]) as unknown; + } +} diff --git a/server/src/platforms/fastify/index.ts b/server/src/platforms/fastify/index.ts new file mode 100644 index 0000000..d3fbb85 --- /dev/null +++ b/server/src/platforms/fastify/index.ts @@ -0,0 +1 @@ +export { FastifyAdapter } from './fastify-adapter'; diff --git a/server/src/platforms/http/helpers/http-application-adapter.ts b/server/src/platforms/http/helpers/http-application-adapter.ts index 46260b0..6d4fbc5 100644 --- a/server/src/platforms/http/helpers/http-application-adapter.ts +++ b/server/src/platforms/http/helpers/http-application-adapter.ts @@ -5,15 +5,16 @@ import { ParameterType } from './constants'; export abstract class HttpApplicationAdapter { abstract server?: Server; + abstract type: string; abstract close(): void; - abstract getParam(type: ParameterType, name?: string, ...args: any[]): Promise | unknown; + abstract getParam(type: ParameterType, name?: string, ...args: any[]): Promise<() => unknown> | (() => unknown); abstract isHeadersSent(response: unknown): Promise | boolean; abstract listen(port: number): Promise | void; abstract render(response: unknown, template: string, message: unknown): Promise | string; abstract reply(response: unknown, message: unknown, statusCode?: number): Promise | unknown; abstract route(url: string, type: string, handler: Handler): void; - abstract serveStatic(prefix: string, path: string, options?: unknown): void; - abstract set(setting: string, value: unknown): void; + abstract serveStatic(prefix: string, path: string, options?: object): void; + abstract set?(setting: string, value: unknown): void; abstract setHeader(response: unknown, name: string, value: string): void; abstract use(...args: any[]): void; } diff --git a/server/src/platforms/http/helpers/route-handler.ts b/server/src/platforms/http/helpers/route-handler.ts index 492db90..ecf39ea 100644 --- a/server/src/platforms/http/helpers/route-handler.ts +++ b/server/src/platforms/http/helpers/route-handler.ts @@ -25,16 +25,16 @@ export class RouteHandler { const handler = controller[methodName].bind(controller); return async (...args: unknown[]) => { - const req = this.adapter.getParam(ParameterType.REQUEST, null, ...args); - const res = this.adapter.getParam(ParameterType.RESPONSE, null, ...args); + const req = await this.adapter.getParam(ParameterType.REQUEST, null, ...args); + const res = await this.adapter.getParam(ParameterType.RESPONSE, null, ...args); const verifiedParams = []; const context = new HttpContext( controller.constructor, controller[methodName], this.adapter, - req, - res, + req(), + res(), verifiedParams, ); @@ -53,11 +53,13 @@ export class RouteHandler { message = await handler(...verifiedParams); - if (await this.adapter.isHeadersSent(res) || !template) { + if (await this.adapter.isHeadersSent(res()) || !template) { return message; } - return this.adapter.render(res, template, message); + this.adapter.setHeader(res(), 'Content-Type', 'text/html'); + + return this.adapter.render(res(), template, message); }; message = await this.runHandler(() => @@ -83,13 +85,13 @@ export class RouteHandler { async params(metadata: ParamMetadata[], context: HttpContext, args: unknown[]) { const params$ = metadata .sort((a, b) => a.index - b.index) - .map(async param => param.factory - ? await param.factory(context) - : await this.adapter.getParam(param.paramType as ParameterType, param.paramName, ...args), + .map(param => param.factory + ? param.factory(context) + : this.adapter.getParam(param.paramType as ParameterType, param.paramName, ...args), ); const params = await Promise.all(params$); - return params.map(toStandardType); + return params.map(paramFn => toStandardType(paramFn())); } status(message: unknown, status: number) { diff --git a/server/src/platforms/http/http.module.ts b/server/src/platforms/http/http.module.ts index a18a109..0155bc8 100644 --- a/server/src/platforms/http/http.module.ts +++ b/server/src/platforms/http/http.module.ts @@ -41,7 +41,7 @@ export class HttpModule { } set(setting: string, value: unknown) { - this.adapter.set(setting, value); + this.adapter.set?.(setting, value); } use(...args: unknown[]) { diff --git a/server/src/platforms/swagger/helpers/swagger-ui/swagger-resolver.ts b/server/src/platforms/swagger/helpers/swagger-ui/swagger-resolver.ts index 5e59381..561a0b6 100644 --- a/server/src/platforms/swagger/helpers/swagger-ui/swagger-resolver.ts +++ b/server/src/platforms/swagger/helpers/swagger-ui/swagger-resolver.ts @@ -43,14 +43,14 @@ export class SwaggerResolver { } private simpleGetRoute(path: string, response: unknown, headers: Record = {}) { - this.adapter.route(path, 'get', (...args) => { - const res = this.adapter.getParam(ParameterType.RESPONSE, null, ...args); + this.adapter.route(path, 'get', async (...args) => { + const res = await this.adapter.getParam(ParameterType.RESPONSE, null, ...args); Object.entries(headers).forEach(([name, value]) => - this.adapter.setHeader(res, name, value), + this.adapter.setHeader(res(), name, value), ); - this.adapter.reply(res, response); + this.adapter.reply(res(), response); }); } } diff --git a/server/tsconfig.json b/server/tsconfig.json index 0488ee8..8d90621 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -20,6 +20,9 @@ "@server/express": [ "./src/platforms/express/index.ts" ], + "@server/fastify": [ + "./src/platforms/fastify/index.ts" + ], "@server/http": [ "./src/platforms/http/index.ts" ], @@ -28,7 +31,12 @@ ] }, "pretty": true, - "target": "ES2019" + "target": "ES2019", + "types": [ + "@fastify/cookie", + "@fastify/view", + "@types/jest" + ] }, "include": [ "./src" diff --git a/tools/git-pre-push b/tools/git-pre-push index 40fffee..9e78362 100644 --- a/tools/git-pre-push +++ b/tools/git-pre-push @@ -4,21 +4,21 @@ folder_contains_changes() { git diff --name-only HEAD HEAD~1 | grep -q "^$1" } -folder_contains_tests() { - cat package.json | grep -q 'test' +folder_contains_script() { + cat package.json | grep -q 'prepush' } -run_tests() { +run_script() { if [ -f package.json ]; then - if folder_contains_tests; then - npm test + if folder_contains_script; then + npm run prepush fi fi } for folder in */; do if folder_contains_changes "$folder"; then - (cd "$folder" && run_tests) + (cd "$folder" && run_script) if [ $? -eq 1 ]; then exit 1