From 2800fe1254370fe2949705ba458256eed2f1b967 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Mon, 18 Nov 2024 09:56:38 +0530 Subject: [PATCH 01/27] feat(core): integrating http server --- integrations/sample-app/app/boot/sp/app.ts | 2 + .../sample-app/app/http/controllers/icon.ts | 13 +++ integrations/sample-app/app/main.ts | 2 +- package-lock.json | 23 ++++ .../foundation/custom-server/decorators.ts | 40 +++++++ .../rest/foundation/custom-server/explorer.ts | 103 ++++++++++++++++++ packages/core/lib/rest/foundation/index.ts | 2 + packages/core/lib/rest/foundation/server.ts | 37 ++++++- packages/core/package.json | 2 + 9 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 integrations/sample-app/app/http/controllers/icon.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/decorators.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/explorer.ts diff --git a/integrations/sample-app/app/boot/sp/app.ts b/integrations/sample-app/app/boot/sp/app.ts index 06f914d..ea6d26f 100644 --- a/integrations/sample-app/app/boot/sp/app.ts +++ b/integrations/sample-app/app/boot/sp/app.ts @@ -4,6 +4,7 @@ import { IntentApplicationContext, ServiceProvider, } from '@intentjs/core'; +import { IntentController } from 'app/http/controllers/icon'; import { QueueJobs } from 'app/jobs/job'; import { UserDbRepository } from 'app/repositories/userDbRepository'; import { UserService } from 'app/services'; @@ -28,6 +29,7 @@ export class AppServiceProvider extends ServiceProvider { * Read more - https://tryintent.com/docs/providers#class-based-providers */ this.bindWithClass('USER_DB_REPO', UserDbRepository); + this.bind(IntentController); this.bind(QueueJobs); } diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts new file mode 100644 index 0000000..46b2231 --- /dev/null +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -0,0 +1,13 @@ +import { IController, IGet, Injectable, Req, Request } from '@intentjs/core'; + +@IController('/icon') +@Injectable() +export class IntentController { + constructor() {} + + @IGet('/sample') + async getHello(@Req() req: Request) { + console.log(req); + return { hello: 'world' }; + } +} diff --git a/integrations/sample-app/app/main.ts b/integrations/sample-app/app/main.ts index 2191f6d..1800246 100644 --- a/integrations/sample-app/app/main.ts +++ b/integrations/sample-app/app/main.ts @@ -7,4 +7,4 @@ IntentHttpServer.init() .useContainer(ApplicationContainer) .useKernel(HttpKernel) .handleErrorsWith(ApplicationExceptionFilter) - .start(); + .startCustomServer(); diff --git a/package-lock.json b/package-lock.json index 7d66b91..7cefd24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2193,6 +2193,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hono/node-server": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.7.tgz", + "integrity": "sha512-kTfUMsoloVKtRA2fLiGSd9qBddmru9KadNyhJCwgKBxTiNkaAJEwkVN9KV/rS4HtmmNRtUh6P+YpmjRMl0d9vQ==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "dev": true, @@ -10504,6 +10516,15 @@ "node": ">=8" } }, + "node_modules/hono": { + "version": "4.6.10", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.10.tgz", + "integrity": "sha512-IXXNfRAZEahFnWBhUUlqKEGF9upeE6hZoRZszvNkyAz/CYp+iVbxm3viMvStlagRJohjlBRGOQ7f4jfcV0XMGg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.2", "dev": true, @@ -17825,6 +17846,7 @@ "version": "0.1.35", "license": "MIT", "dependencies": { + "@hono/node-server": "^1.13.7", "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", "@nestjs/platform-express": "^10.4.1", @@ -17841,6 +17863,7 @@ "express": "^4.21.0", "fs-extra": "^11.1.1", "helmet": "^7.1.0", + "hono": "^4.6.10", "ioredis": "^5.3.2", "knex": "^3.1.0", "ms": "^2.1.3", diff --git a/packages/core/lib/rest/foundation/custom-server/decorators.ts b/packages/core/lib/rest/foundation/custom-server/decorators.ts new file mode 100644 index 0000000..719a747 --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/decorators.ts @@ -0,0 +1,40 @@ +export const CONTROLLER_KEY = '@intentjs/controller_path'; +export const CONTROLLER_OPTIONS = '@intentjs/controller_options'; + +export const METHOD_KEY = '@intentjs/controller_method_key'; +export const METHOD_PATH = '@intentjs/controller_method_path'; + +export type ControllerOptions = { + host?: string; +}; + +export enum HttpMethods { + GET, + POST, + PUT, + PATCH, + OPTIONS, + HEAD, + DELETE, + ANY, +} + +export function IController(path?: string, options?: ControllerOptions) { + return function (target: Function) { + // if (descriptor) { + // Reflect.defineMetadata(CONTROLLER_KEY, path, descriptor.value); + // return descriptor; + // } + + Reflect.defineMetadata(CONTROLLER_KEY, path || '', target); + Reflect.defineMetadata(CONTROLLER_OPTIONS, options, target); + }; +} + +export function IGet(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.GET, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} diff --git a/packages/core/lib/rest/foundation/custom-server/explorer.ts b/packages/core/lib/rest/foundation/custom-server/explorer.ts new file mode 100644 index 0000000..e59d291 --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/explorer.ts @@ -0,0 +1,103 @@ +import { DiscoveryService, MetadataScanner } from '@nestjs/core'; +import { Hono } from 'hono'; +import { serve } from '@hono/node-server'; +import { + CONTROLLER_KEY, + HttpMethods, + METHOD_KEY, + METHOD_PATH, +} from './decorators'; +import { join } from 'path'; + +export class CustomServer { + private hono = new Hono(); + + build( + discoveryService: DiscoveryService, + metadataScanner: MetadataScanner, + ): this { + const providers = discoveryService.getProviders(); + for (const provider of providers) { + const { instance } = provider; + // if ( + // !instance || + // typeof instance === 'string' || + // !Object.getPrototypeOf(instance) + // ) { + // return; + // } + + const methodNames = metadataScanner.getAllMethodNames(instance); + for (const methodName of methodNames) { + this.exploreRoutes(instance, methodName); + } + } + + return this; + } + + exploreRoutes(instance: any, key: string) { + const controllerKey = Reflect.getMetadata( + CONTROLLER_KEY, + instance.constructor, + ); + if (!controllerKey) return; + const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); + const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); + const methodRef = instance[key].bind(instance); + + if (pathMethod === HttpMethods.GET) { + this.hono.get(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + + if (pathMethod === HttpMethods.POST) { + this.hono.post(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + if (pathMethod === HttpMethods.PUT) { + this.hono.put(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + if (pathMethod === HttpMethods.PATCH) { + this.hono.patch(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + if (pathMethod === HttpMethods.DELETE) { + this.hono.delete(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + + if (pathMethod === HttpMethods.OPTIONS) { + this.hono.options(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + + if (pathMethod === HttpMethods.ANY) { + this.hono.all(join(controllerKey, methodPath), async c => { + const res = await methodRef(c); + return c.json(res); + }); + } + } + + // listen(port: number, hostname?: string); + listen(port: number) { + serve({ + port, + fetch: this.hono.fetch, + }); + } +} diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index e819376..11678b5 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -10,3 +10,5 @@ export * from './server'; export * from './statusCodes'; export * from './methods'; export * from './request-mixin'; +export * from './custom-server/decorators'; +export * from './custom-server/explorer'; diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 2d97e2d..00a69d3 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -1,9 +1,18 @@ -import { HttpAdapterHost, NestFactory } from '@nestjs/core'; +import { + DiscoveryService, + HttpAdapterHost, + MetadataScanner, + NestFactory, +} from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { useContainer } from 'class-validator'; import { ConfigService } from '../../config/service'; import { IntentExceptionFilter } from '../../exceptions'; -import { IntentAppContainer, ModuleBuilder } from '../../foundation'; +import { + ContainerFactory, + IntentAppContainer, + ModuleBuilder, +} from '../../foundation'; import { Type } from '../../interfaces'; import { Obj, Package } from '../../utils'; import { Kernel } from '../foundation/kernel'; @@ -11,6 +20,7 @@ import { requestMiddleware } from '../middlewares/functional/requestSerializer'; import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; +import { CustomServer } from './custom-server/explorer'; export class IntentHttpServer { private kernel: Kernel; @@ -37,8 +47,26 @@ export class IntentHttpServer { return this; } + async startCustomServer() { + const module = ModuleBuilder.build(this.container, this.kernel); + const app = await NestFactory.createApplicationContext(module); + const ds = app.get(DiscoveryService, { strict: false }); + const ms = app.get(MetadataScanner, { strict: false }); + + const customServer = new CustomServer(); + customServer.build(ds, ms); + const config = app.get(ConfigService, { strict: false }); + + const port = config.get('app.port'); + const hostname = config.get('app.hostname'); + const environment = config.get('app.env'); + const debug = config.get('app.debug'); + + customServer.listen(+port); + } + async start() { - console['mute'](); + // console['mute'](); const module = ModuleBuilder.build(this.container, this.kernel); const app = await NestFactory.create(module, { bodyParser: true, @@ -71,11 +99,10 @@ export class IntentHttpServer { await app.listen(+port || 5001, hostname); - console['resume'](); + // console['resume'](); console.clear(); - console.log(` ${pc.green(pc.bold('Intent'))} ${pc.green('v1.5.0')}`); console.log(); printBulletPoints([ diff --git a/packages/core/package.json b/packages/core/package.json index b9d41f8..0e99a5b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -65,6 +65,7 @@ "typescript": "^5.5.2" }, "dependencies": { + "@hono/node-server": "^1.13.7", "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", "@nestjs/platform-express": "^10.4.1", @@ -81,6 +82,7 @@ "express": "^4.21.0", "fs-extra": "^11.1.1", "helmet": "^7.1.0", + "hono": "^4.6.10", "ioredis": "^5.3.2", "knex": "^3.1.0", "ms": "^2.1.3", From ea36cf18b6bad2450af66de56ea46d8d66e16673 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Fri, 22 Nov 2024 11:17:38 +0530 Subject: [PATCH 02/27] feat(core): http server integration --- .../sample-app/app/http/controllers/icon.ts | 4 + .../sample-app/app/http/guards/custom.ts | 20 +++ package-lock.json | 8 +- package.json | 3 +- .../contexts/http-execution-context.ts | 13 ++ .../foundation/custom-server/decorators.ts | 17 +++ .../custom-server/execution-context.ts | 22 ++++ .../rest/foundation/custom-server/explorer.ts | 121 +++++++++++------- .../foundation/custom-server/http-handler.ts | 31 +++++ .../rest/foundation/custom-server/request.ts | 10 ++ .../custom-server/response-handler.ts | 8 ++ .../rest/foundation/custom-server/server.ts | 1 + .../lib/rest/foundation/guards/base-guard.ts | 31 +++++ .../lib/rest/foundation/guards/baseGuard.ts | 30 ----- packages/core/lib/rest/foundation/index.ts | 2 +- packages/core/lib/rest/foundation/kernel.ts | 2 +- packages/core/lib/rest/foundation/server.ts | 10 +- 17 files changed, 250 insertions(+), 83 deletions(-) create mode 100644 integrations/sample-app/app/http/guards/custom.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/execution-context.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/http-handler.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/request.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/response-handler.ts create mode 100644 packages/core/lib/rest/foundation/custom-server/server.ts create mode 100644 packages/core/lib/rest/foundation/guards/base-guard.ts delete mode 100644 packages/core/lib/rest/foundation/guards/baseGuard.ts diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 46b2231..21e4f41 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -1,11 +1,15 @@ import { IController, IGet, Injectable, Req, Request } from '@intentjs/core'; +import { IUseGuards } from '@intentjs/core/dist/lib/rest/foundation/custom-server/decorators'; +import { CustomGuard } from '../guards/custom'; @IController('/icon') @Injectable() +@IUseGuards(CustomGuard) export class IntentController { constructor() {} @IGet('/sample') + @IUseGuards(CustomGuard) async getHello(@Req() req: Request) { console.log(req); return { hello: 'world' }; diff --git a/integrations/sample-app/app/http/guards/custom.ts b/integrations/sample-app/app/http/guards/custom.ts new file mode 100644 index 0000000..3c1832d --- /dev/null +++ b/integrations/sample-app/app/http/guards/custom.ts @@ -0,0 +1,20 @@ +import { Injectable, IntentGuard, Reflector } from '@intentjs/core'; +import { HonoRequest } from 'hono'; + +@Injectable() +export class CustomGuard extends IntentGuard { + guard( + req: HonoRequest, + res: Response, + reflector: Reflector, + ): boolean | Promise { + console.log('urnning inside guard'); + console.log({ + method: req.method, + url: req.url, + headers: req.header(), + query: req.query(), + }); + return false; + } +} diff --git a/package-lock.json b/package-lock.json index 7cefd24..ed7faca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "integrations/*" ], "dependencies": { - "concurrently": "^9.1.0" + "concurrently": "^9.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0" }, "devDependencies": { "@commitlint/cli": "^19.5.0", @@ -17262,6 +17263,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uWebSockets.js": { + "version": "20.48.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#51ae1d1fd92dff77cbbdc7c431021f85578da1a6", + "license": "Apache-2.0" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "dev": true, diff --git a/package.json b/package.json index 3d2a9e5..5f18ce4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "prepare": "husky" }, "dependencies": { - "concurrently": "^9.1.0" + "concurrently": "^9.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0" }, "devDependencies": { "@commitlint/cli": "^19.5.0", diff --git a/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts b/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts new file mode 100644 index 0000000..a549af2 --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts @@ -0,0 +1,13 @@ +import { Context, HonoRequest } from 'hono'; + +export class HttpExecutionContext { + constructor(private readonly honoContext: Context) {} + + getRequest(): HonoRequest { + return this.honoContext.req; + } + + getResponse(): any { + return this.honoContext.res; + } +} diff --git a/packages/core/lib/rest/foundation/custom-server/decorators.ts b/packages/core/lib/rest/foundation/custom-server/decorators.ts index 719a747..dc83156 100644 --- a/packages/core/lib/rest/foundation/custom-server/decorators.ts +++ b/packages/core/lib/rest/foundation/custom-server/decorators.ts @@ -1,9 +1,15 @@ +import { Type } from '../../../interfaces'; +import { SetMetadata } from '../../../reflections'; +import { IntentGuard } from '../guards/base-guard'; + export const CONTROLLER_KEY = '@intentjs/controller_path'; export const CONTROLLER_OPTIONS = '@intentjs/controller_options'; export const METHOD_KEY = '@intentjs/controller_method_key'; export const METHOD_PATH = '@intentjs/controller_method_path'; +export const GUARD_KEY = '@intentjs/controller_guards'; + export type ControllerOptions = { host?: string; }; @@ -38,3 +44,14 @@ export function IGet(path?: string, options?: ControllerOptions) { return descriptor; }; } + +export const IUseGuards = (...guards: Type[]) => { + return function (target: object, key?: string | symbol, descriptor?: any) { + if (key) { + Reflect.defineMetadata(GUARD_KEY, guards, target, key); + return; + } + Reflect.defineMetadata(GUARD_KEY, guards, target); + return; + }; +}; diff --git a/packages/core/lib/rest/foundation/custom-server/execution-context.ts b/packages/core/lib/rest/foundation/custom-server/execution-context.ts new file mode 100644 index 0000000..c9a9697 --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/execution-context.ts @@ -0,0 +1,22 @@ +import { GenericClass } from '../../../interfaces'; +import { HttpExecutionContext } from './contexts/http-execution-context'; + +export class ExecutionContext { + constructor( + private protocolContext: HttpExecutionContext, + private readonly handlerClass: GenericClass, + private readonly handlerMethod: Function, + ) {} + + getClass(): GenericClass { + return this.handlerClass; + } + + getHandler(): Function { + return this.handlerMethod; + } + + switchToHttp(): HttpExecutionContext { + return this.protocolContext; + } +} diff --git a/packages/core/lib/rest/foundation/custom-server/explorer.ts b/packages/core/lib/rest/foundation/custom-server/explorer.ts index e59d291..5725688 100644 --- a/packages/core/lib/rest/foundation/custom-server/explorer.ts +++ b/packages/core/lib/rest/foundation/custom-server/explorer.ts @@ -1,21 +1,30 @@ -import { DiscoveryService, MetadataScanner } from '@nestjs/core'; -import { Hono } from 'hono'; +import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; +import { Context, Hono } from 'hono'; import { serve } from '@hono/node-server'; import { CONTROLLER_KEY, + GUARD_KEY, HttpMethods, METHOD_KEY, METHOD_PATH, } from './decorators'; import { join } from 'path'; +import { IntentGuard } from '../guards/base-guard'; +import { Type } from '../../../interfaces'; +import { HttpRouteHandler } from './http-handler'; +import { ResponseHandler } from './response-handler'; +import { ExecutionContext } from './execution-context'; +import { HttpExecutionContext } from './contexts/http-execution-context'; +import { exec } from 'child_process'; export class CustomServer { private hono = new Hono(); - build( + async build( discoveryService: DiscoveryService, metadataScanner: MetadataScanner, - ): this { + moduleRef: ModuleRef, + ): Promise { const providers = discoveryService.getProviders(); for (const provider of providers) { const { instance } = provider; @@ -29,14 +38,14 @@ export class CustomServer { const methodNames = metadataScanner.getAllMethodNames(instance); for (const methodName of methodNames) { - this.exploreRoutes(instance, methodName); + await this.exploreRoutes(instance, methodName, moduleRef); } } - return this; + return this.hono; } - exploreRoutes(instance: any, key: string) { + async exploreRoutes(instance: any, key: string, moduleRef: ModuleRef) { const controllerKey = Reflect.getMetadata( CONTROLLER_KEY, instance.constructor, @@ -45,51 +54,69 @@ export class CustomServer { const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); const methodRef = instance[key].bind(instance); + const controllerGuards = Reflect.getMetadata( + GUARD_KEY, + instance.constructor, + ); - if (pathMethod === HttpMethods.GET) { - this.hono.get(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); - } + const methodGuards = Reflect.getMetadata(GUARD_KEY, instance, key); - if (pathMethod === HttpMethods.POST) { - this.hono.post(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); - } - if (pathMethod === HttpMethods.PUT) { - this.hono.put(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); - } - if (pathMethod === HttpMethods.PATCH) { - this.hono.patch(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); - } - if (pathMethod === HttpMethods.DELETE) { - this.hono.delete(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); - } + const composedGuardTypes = [ + ...(controllerGuards || []), + ...(methodGuards || []), + ] as Type[]; - if (pathMethod === HttpMethods.OPTIONS) { - this.hono.options(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); + const composedGuards = []; + for (const guardType of composedGuardTypes) { + composedGuards.push(await moduleRef.create(guardType)); } - if (pathMethod === HttpMethods.ANY) { - this.hono.all(join(controllerKey, methodPath), async c => { - const res = await methodRef(c); - return c.json(res); - }); + const middlewares = []; + + const handler = new HttpRouteHandler( + middlewares, + composedGuards, + methodRef, + ); + + const responseHandler = new ResponseHandler(); + + const cb = async (c: Context) => { + const now = performance.now(); + const httpContext = new HttpExecutionContext(c); + const executionContext = new ExecutionContext( + httpContext, + instance, + methodRef, + ); + + const resFromHandler = await handler.handle(executionContext); + + const [type, res] = await responseHandler.handle(c, resFromHandler); + + console.log( + 'time to handle one http request ===> ', + performance.now() - now, + ); + + if (type === 'json') return c.json(res); + return c.json(res); + }; + + if (pathMethod === HttpMethods.GET) { + this.hono.get(join(controllerKey, methodPath), cb); + } else if (pathMethod === HttpMethods.POST) { + this.hono.post(join(controllerKey, methodPath), cb); + } else if (pathMethod === HttpMethods.PUT) { + this.hono.put(join(controllerKey, methodPath), cb); + } else if (pathMethod === HttpMethods.PATCH) { + this.hono.patch(join(controllerKey, methodPath), cb); + } else if (pathMethod === HttpMethods.DELETE) { + this.hono.delete(join(controllerKey, methodPath), cb); + } else if (pathMethod === HttpMethods.OPTIONS) { + this.hono.options(join(controllerKey, methodPath), cb); + } else if (pathMethod === HttpMethods.ANY) { + this.hono.all(join(controllerKey, methodPath), cb); } } diff --git a/packages/core/lib/rest/foundation/custom-server/http-handler.ts b/packages/core/lib/rest/foundation/custom-server/http-handler.ts new file mode 100644 index 0000000..c157efe --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/http-handler.ts @@ -0,0 +1,31 @@ +import { Context } from 'hono'; +import { IntentGuard } from '../guards/base-guard'; +import { IntentMiddleware } from '../middlewares/middleware'; +import { ExecutionContext } from './execution-context'; + +export class HttpRouteHandler { + constructor( + protected readonly middlewares: IntentMiddleware[], + protected readonly guards: IntentGuard[], + protected readonly handler: Function, + ) {} + + async handle(context: ExecutionContext) { + // for (const middleware of this.middlewares) { + // await middleware.use({}, {}); + // } + + /** + * Handle the Guards + */ + for (const guard of this.guards) { + await guard.handle(context); + } + + /** + * Handle the request + */ + const res = await this.handler(context); + return res; + } +} diff --git a/packages/core/lib/rest/foundation/custom-server/request.ts b/packages/core/lib/rest/foundation/custom-server/request.ts new file mode 100644 index 0000000..aa34698 --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/request.ts @@ -0,0 +1,10 @@ +import { Context } from 'hono'; + +export class IntentRequestObj { + constructor( + public readonly param: Function, + public readonly query: Function, + public readonly queries: Function, + public readonly header: Function, + ) {} +} diff --git a/packages/core/lib/rest/foundation/custom-server/response-handler.ts b/packages/core/lib/rest/foundation/custom-server/response-handler.ts new file mode 100644 index 0000000..2686e6e --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/response-handler.ts @@ -0,0 +1,8 @@ +import { Context } from 'hono'; + +export class ResponseHandler { + async handle(c: Context, res: any) { + console.log(c, res); + return ['json', res]; + } +} diff --git a/packages/core/lib/rest/foundation/custom-server/server.ts b/packages/core/lib/rest/foundation/custom-server/server.ts new file mode 100644 index 0000000..1bda2cf --- /dev/null +++ b/packages/core/lib/rest/foundation/custom-server/server.ts @@ -0,0 +1 @@ +export class Server {} diff --git a/packages/core/lib/rest/foundation/guards/base-guard.ts b/packages/core/lib/rest/foundation/guards/base-guard.ts new file mode 100644 index 0000000..5e37d1a --- /dev/null +++ b/packages/core/lib/rest/foundation/guards/base-guard.ts @@ -0,0 +1,31 @@ +import { Response } from 'hono/dist/types/client/types'; +import { Reflector } from '../../../reflections'; +import { ExecutionContext } from '../custom-server/execution-context'; +import { HonoRequest } from 'hono'; + +export abstract class IntentGuard { + async handle(context: ExecutionContext): Promise { + /** + * Get Express Request Object + */ + const request = context.switchToHttp().getRequest(); + + /** + * Get Express Response Object + */ + const response = context.switchToHttp().getResponse(); + + /** + * Initialise a new Reflector class. + */ + const reflector = new Reflector(context.getClass(), context.getHandler()); + + return this.guard(request, response, reflector); + } + + abstract guard( + req: HonoRequest, + res: Response, + reflector: Reflector, + ): boolean | Promise; +} diff --git a/packages/core/lib/rest/foundation/guards/baseGuard.ts b/packages/core/lib/rest/foundation/guards/baseGuard.ts deleted file mode 100644 index 692e0e2..0000000 --- a/packages/core/lib/rest/foundation/guards/baseGuard.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CanActivate, ExecutionContext } from '@nestjs/common'; -import { Request, Response } from '../interface'; -import { Reflector } from '../../../reflections'; - -export abstract class IntentGuard implements CanActivate { - async canActivate(context: ExecutionContext): Promise { - /** - * Get Express Request Object - */ - const expressRequest = context.switchToHttp().getRequest(); - - /** - * Get Express Response Object - */ - const expressResponse = context.switchToHttp().getResponse(); - - /** - * Initialise a new Reflector class. - */ - const reflector = new Reflector(context.getClass(), context.getHandler()); - - return this.guard(expressRequest, expressResponse, reflector); - } - - abstract guard( - req: Request, - res: Response, - reflector: Reflector, - ): boolean | Promise; -} diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index 11678b5..73eee24 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -1,4 +1,4 @@ -export * from './guards/baseGuard'; +export * from './guards/base-guard'; export * from './guards/decorator'; export * from './methods'; export * from './interface'; diff --git a/packages/core/lib/rest/foundation/kernel.ts b/packages/core/lib/rest/foundation/kernel.ts index d446598..c672732 100644 --- a/packages/core/lib/rest/foundation/kernel.ts +++ b/packages/core/lib/rest/foundation/kernel.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { IntentApplication, Type } from '../../interfaces'; -import { IntentGuard } from './guards/baseGuard'; +import { IntentGuard } from './guards/base-guard'; import { MiddlewareConfigurator } from './middlewares/configurator'; import { IntentMiddleware } from './middlewares/middleware'; diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 00a69d3..b7abc53 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -2,6 +2,7 @@ import { DiscoveryService, HttpAdapterHost, MetadataScanner, + ModuleRef, NestFactory, } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; @@ -21,6 +22,7 @@ import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; import { CustomServer } from './custom-server/explorer'; +import { serve } from '@hono/node-server'; export class IntentHttpServer { private kernel: Kernel; @@ -52,9 +54,10 @@ export class IntentHttpServer { const app = await NestFactory.createApplicationContext(module); const ds = app.get(DiscoveryService, { strict: false }); const ms = app.get(MetadataScanner, { strict: false }); + const mr = app.get(ModuleRef, { strict: false }); const customServer = new CustomServer(); - customServer.build(ds, ms); + const hono = await customServer.build(ds, ms, mr); const config = app.get(ConfigService, { strict: false }); const port = config.get('app.port'); @@ -62,7 +65,10 @@ export class IntentHttpServer { const environment = config.get('app.env'); const debug = config.get('app.debug'); - customServer.listen(+port); + serve({ + port, + fetch: hono.fetch, + }); } async start() { From 29c976e7d013b61c907e7409b4d83b18548c8811 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Fri, 22 Nov 2024 11:17:56 +0530 Subject: [PATCH 03/27] feat(core): http server integration --- .../sample-app/app/http/controllers/icon.ts | 11 +- .../sample-app/app/http/guards/custom.ts | 15 +-- integrations/sample-app/app/main.ts | 2 +- package-lock.json | 68 ++++++---- package.json | 1 + packages/core/lib/foundation/app-container.ts | 1 - .../contexts/http-execution-context.ts | 15 ++- .../foundation/custom-server/decorators.ts | 8 ++ .../rest/foundation/custom-server/explorer.ts | 84 ++++++++----- .../foundation/custom-server/http-handler.ts | 1 - .../rest/foundation/custom-server/request.ts | 2 - .../custom-server/response-handler.ts | 5 +- .../rest/foundation/custom-server/server.ts | 1 - .../lib/rest/foundation/guards/base-guard.ts | 5 +- packages/core/lib/rest/foundation/server.ts | 117 ++++++++++-------- packages/core/package.json | 2 - 16 files changed, 197 insertions(+), 141 deletions(-) delete mode 100644 packages/core/lib/rest/foundation/custom-server/server.ts diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 21e4f41..7f2931a 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -1,5 +1,8 @@ import { IController, IGet, Injectable, Req, Request } from '@intentjs/core'; -import { IUseGuards } from '@intentjs/core/dist/lib/rest/foundation/custom-server/decorators'; +import { + IPost, + IUseGuards, +} from '@intentjs/core/dist/lib/rest/foundation/custom-server/decorators'; import { CustomGuard } from '../guards/custom'; @IController('/icon') @@ -14,4 +17,10 @@ export class IntentController { console.log(req); return { hello: 'world' }; } + + @IPost('/sample') + @IUseGuards(CustomGuard) + async postHello(@Req() req: Request) { + return { hello: 'world' }; + } } diff --git a/integrations/sample-app/app/http/guards/custom.ts b/integrations/sample-app/app/http/guards/custom.ts index 3c1832d..0852ddf 100644 --- a/integrations/sample-app/app/http/guards/custom.ts +++ b/integrations/sample-app/app/http/guards/custom.ts @@ -1,19 +1,16 @@ import { Injectable, IntentGuard, Reflector } from '@intentjs/core'; -import { HonoRequest } from 'hono'; +import { Request, Response } from 'hyper-express'; @Injectable() export class CustomGuard extends IntentGuard { - guard( - req: HonoRequest, + async guard( + req: Request, res: Response, reflector: Reflector, - ): boolean | Promise { + ): Promise { console.log('urnning inside guard'); - console.log({ - method: req.method, - url: req.url, - headers: req.header(), - query: req.query(), + await req.multipart(async (field) => { + console.log('field ===> ', field.name, field.file); }); return false; } diff --git a/integrations/sample-app/app/main.ts b/integrations/sample-app/app/main.ts index 1800246..2191f6d 100644 --- a/integrations/sample-app/app/main.ts +++ b/integrations/sample-app/app/main.ts @@ -7,4 +7,4 @@ IntentHttpServer.init() .useContainer(ApplicationContainer) .useKernel(HttpKernel) .handleErrorsWith(ApplicationExceptionFilter) - .startCustomServer(); + .start(); diff --git a/package-lock.json b/package-lock.json index ed7faca..1597228 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ ], "dependencies": { "concurrently": "^9.1.0", + "hyper-express": "^6.17.2", "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0" }, "devDependencies": { @@ -2194,18 +2195,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@hono/node-server": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.7.tgz", - "integrity": "sha512-kTfUMsoloVKtRA2fLiGSd9qBddmru9KadNyhJCwgKBxTiNkaAJEwkVN9KV/rS4HtmmNRtUh6P+YpmjRMl0d9vQ==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "dev": true, @@ -10517,15 +10506,6 @@ "node": ">=8" } }, - "node_modules/hono": { - "version": "4.6.10", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.10.tgz", - "integrity": "sha512-IXXNfRAZEahFnWBhUUlqKEGF9upeE6hZoRZszvNkyAz/CYp+iVbxm3viMvStlagRJohjlBRGOQ7f4jfcV0XMGg==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, "node_modules/hosted-git-info": { "version": "7.0.2", "dev": true, @@ -10661,6 +10641,41 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/hyper-express": { + "version": "6.17.2", + "resolved": "https://registry.npmjs.org/hyper-express/-/hyper-express-6.17.2.tgz", + "integrity": "sha512-zibRNaNA3ExaYIiypHPEbFCZ3fOT2zSCnn3v78lUOrvQGqeb7VVzKuQKmJUsbkzpp7tKZoDxM4l5avpQHm35dA==", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.7", + "busboy": "^1.0.0", + "cookie": "^0.4.1", + "cookie-signature": "^1.1.0", + "mime-types": "^2.1.33", + "range-parser": "^1.2.1", + "type-is": "^1.6.18", + "typed-emitter": "^2.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0" + } + }, + "node_modules/hyper-express/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/hyper-express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "license": "MIT", @@ -17081,6 +17096,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "license": "MIT", + "optionalDependencies": { + "rxjs": "*" + } + }, "node_modules/typedarray": { "version": "0.0.6", "license": "MIT" @@ -17852,7 +17876,6 @@ "version": "0.1.35", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.13.7", "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", "@nestjs/platform-express": "^10.4.1", @@ -17869,7 +17892,6 @@ "express": "^4.21.0", "fs-extra": "^11.1.1", "helmet": "^7.1.0", - "hono": "^4.6.10", "ioredis": "^5.3.2", "knex": "^3.1.0", "ms": "^2.1.3", diff --git a/package.json b/package.json index 5f18ce4..02449b0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "concurrently": "^9.1.0", + "hyper-express": "^6.17.2", "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0" }, "devDependencies": { diff --git a/packages/core/lib/foundation/app-container.ts b/packages/core/lib/foundation/app-container.ts index 48ebf41..a8f78c3 100644 --- a/packages/core/lib/foundation/app-container.ts +++ b/packages/core/lib/foundation/app-container.ts @@ -1,5 +1,4 @@ import { Provider } from '@nestjs/common'; -import { NestExpressApplication } from '@nestjs/platform-express'; import { IntentApplication, IntentApplicationContext, diff --git a/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts b/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts index a549af2..7f66773 100644 --- a/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts @@ -1,13 +1,16 @@ -import { Context, HonoRequest } from 'hono'; +import { Request, Response } from 'hyper-express'; export class HttpExecutionContext { - constructor(private readonly honoContext: Context) {} + constructor( + private readonly request: Request, + private readonly response: Response, + ) {} - getRequest(): HonoRequest { - return this.honoContext.req; + getRequest(): Request { + return this.request; } - getResponse(): any { - return this.honoContext.res; + getResponse(): Response { + return this.response; } } diff --git a/packages/core/lib/rest/foundation/custom-server/decorators.ts b/packages/core/lib/rest/foundation/custom-server/decorators.ts index dc83156..c950d6f 100644 --- a/packages/core/lib/rest/foundation/custom-server/decorators.ts +++ b/packages/core/lib/rest/foundation/custom-server/decorators.ts @@ -45,6 +45,14 @@ export function IGet(path?: string, options?: ControllerOptions) { }; } +export function IPost(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.POST, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + export const IUseGuards = (...guards: Type[]) => { return function (target: object, key?: string | symbol, descriptor?: any) { if (key) { diff --git a/packages/core/lib/rest/foundation/custom-server/explorer.ts b/packages/core/lib/rest/foundation/custom-server/explorer.ts index 5725688..c83cdee 100644 --- a/packages/core/lib/rest/foundation/custom-server/explorer.ts +++ b/packages/core/lib/rest/foundation/custom-server/explorer.ts @@ -1,6 +1,4 @@ import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; -import { Context, Hono } from 'hono'; -import { serve } from '@hono/node-server'; import { CONTROLLER_KEY, GUARD_KEY, @@ -15,16 +13,16 @@ import { HttpRouteHandler } from './http-handler'; import { ResponseHandler } from './response-handler'; import { ExecutionContext } from './execution-context'; import { HttpExecutionContext } from './contexts/http-execution-context'; -import { exec } from 'child_process'; +import HyperExpress, { Request, Response } from 'hyper-express'; export class CustomServer { - private hono = new Hono(); + private hyper = new HyperExpress.Server(); async build( discoveryService: DiscoveryService, metadataScanner: MetadataScanner, moduleRef: ModuleRef, - ): Promise { + ): Promise { const providers = discoveryService.getProviders(); for (const provider of providers) { const { instance } = provider; @@ -42,7 +40,7 @@ export class CustomServer { } } - return this.hono; + return this.hyper; } async exploreRoutes(instance: any, key: string, moduleRef: ModuleRef) { @@ -81,9 +79,9 @@ export class CustomServer { const responseHandler = new ResponseHandler(); - const cb = async (c: Context) => { + const cb = async (hReq: Request, hRes: Response) => { const now = performance.now(); - const httpContext = new HttpExecutionContext(c); + const httpContext = new HttpExecutionContext(hReq, hRes); const executionContext = new ExecutionContext( httpContext, instance, @@ -92,39 +90,57 @@ export class CustomServer { const resFromHandler = await handler.handle(executionContext); - const [type, res] = await responseHandler.handle(c, resFromHandler); + const [type, res] = await responseHandler.handle( + hReq, + hRes, + resFromHandler, + ); + + if (type === 'json') { + hRes + .header('Content-Type', 'application/json') + .send(JSON.stringify(res)); + } console.log( 'time to handle one http request ===> ', performance.now() - now, ); - - if (type === 'json') return c.json(res); - return c.json(res); }; - if (pathMethod === HttpMethods.GET) { - this.hono.get(join(controllerKey, methodPath), cb); - } else if (pathMethod === HttpMethods.POST) { - this.hono.post(join(controllerKey, methodPath), cb); - } else if (pathMethod === HttpMethods.PUT) { - this.hono.put(join(controllerKey, methodPath), cb); - } else if (pathMethod === HttpMethods.PATCH) { - this.hono.patch(join(controllerKey, methodPath), cb); - } else if (pathMethod === HttpMethods.DELETE) { - this.hono.delete(join(controllerKey, methodPath), cb); - } else if (pathMethod === HttpMethods.OPTIONS) { - this.hono.options(join(controllerKey, methodPath), cb); - } else if (pathMethod === HttpMethods.ANY) { - this.hono.all(join(controllerKey, methodPath), cb); - } - } + const fullPath = join(controllerKey, methodPath); + switch (pathMethod) { + case HttpMethods.GET: + this.hyper.get(fullPath, cb); + break; + + case HttpMethods.POST: + this.hyper.post(fullPath, cb); + break; - // listen(port: number, hostname?: string); - listen(port: number) { - serve({ - port, - fetch: this.hono.fetch, - }); + case HttpMethods.DELETE: + this.hyper.delete(fullPath, cb); + break; + + case HttpMethods.HEAD: + this.hyper.head(fullPath, cb); + break; + + case HttpMethods.PUT: + this.hyper.put(fullPath, cb); + break; + + case HttpMethods.PATCH: + this.hyper.patch(fullPath, cb); + break; + + case HttpMethods.OPTIONS: + this.hyper.options(fullPath, cb); + break; + + case HttpMethods.ANY: + this.hyper.any(fullPath, cb); + break; + } } } diff --git a/packages/core/lib/rest/foundation/custom-server/http-handler.ts b/packages/core/lib/rest/foundation/custom-server/http-handler.ts index c157efe..945d485 100644 --- a/packages/core/lib/rest/foundation/custom-server/http-handler.ts +++ b/packages/core/lib/rest/foundation/custom-server/http-handler.ts @@ -1,4 +1,3 @@ -import { Context } from 'hono'; import { IntentGuard } from '../guards/base-guard'; import { IntentMiddleware } from '../middlewares/middleware'; import { ExecutionContext } from './execution-context'; diff --git a/packages/core/lib/rest/foundation/custom-server/request.ts b/packages/core/lib/rest/foundation/custom-server/request.ts index aa34698..dee2268 100644 --- a/packages/core/lib/rest/foundation/custom-server/request.ts +++ b/packages/core/lib/rest/foundation/custom-server/request.ts @@ -1,5 +1,3 @@ -import { Context } from 'hono'; - export class IntentRequestObj { constructor( public readonly param: Function, diff --git a/packages/core/lib/rest/foundation/custom-server/response-handler.ts b/packages/core/lib/rest/foundation/custom-server/response-handler.ts index 2686e6e..11e0565 100644 --- a/packages/core/lib/rest/foundation/custom-server/response-handler.ts +++ b/packages/core/lib/rest/foundation/custom-server/response-handler.ts @@ -1,8 +1,7 @@ -import { Context } from 'hono'; +import { Request, Response } from 'hyper-express'; export class ResponseHandler { - async handle(c: Context, res: any) { - console.log(c, res); + async handle(hReq: Request, hRes: Response, res: any) { return ['json', res]; } } diff --git a/packages/core/lib/rest/foundation/custom-server/server.ts b/packages/core/lib/rest/foundation/custom-server/server.ts deleted file mode 100644 index 1bda2cf..0000000 --- a/packages/core/lib/rest/foundation/custom-server/server.ts +++ /dev/null @@ -1 +0,0 @@ -export class Server {} diff --git a/packages/core/lib/rest/foundation/guards/base-guard.ts b/packages/core/lib/rest/foundation/guards/base-guard.ts index 5e37d1a..7b2a6e6 100644 --- a/packages/core/lib/rest/foundation/guards/base-guard.ts +++ b/packages/core/lib/rest/foundation/guards/base-guard.ts @@ -1,7 +1,6 @@ -import { Response } from 'hono/dist/types/client/types'; import { Reflector } from '../../../reflections'; import { ExecutionContext } from '../custom-server/execution-context'; -import { HonoRequest } from 'hono'; +import { Request, Response } from 'hyper-express'; export abstract class IntentGuard { async handle(context: ExecutionContext): Promise { @@ -24,7 +23,7 @@ export abstract class IntentGuard { } abstract guard( - req: HonoRequest, + req: Request, res: Response, reflector: Reflector, ): boolean | Promise; diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index b7abc53..6b45d0d 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -1,19 +1,13 @@ import { DiscoveryService, - HttpAdapterHost, MetadataScanner, ModuleRef, NestFactory, } from '@nestjs/core'; -import { NestExpressApplication } from '@nestjs/platform-express'; import { useContainer } from 'class-validator'; import { ConfigService } from '../../config/service'; import { IntentExceptionFilter } from '../../exceptions'; -import { - ContainerFactory, - IntentAppContainer, - ModuleBuilder, -} from '../../foundation'; +import { IntentAppContainer, ModuleBuilder } from '../../foundation'; import { Type } from '../../interfaces'; import { Obj, Package } from '../../utils'; import { Kernel } from '../foundation/kernel'; @@ -22,7 +16,6 @@ import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; import { CustomServer } from './custom-server/explorer'; -import { serve } from '@hono/node-server'; export class IntentHttpServer { private kernel: Kernel; @@ -49,67 +42,25 @@ export class IntentHttpServer { return this; } - async startCustomServer() { + async start() { const module = ModuleBuilder.build(this.container, this.kernel); const app = await NestFactory.createApplicationContext(module); const ds = app.get(DiscoveryService, { strict: false }); const ms = app.get(MetadataScanner, { strict: false }); const mr = app.get(ModuleRef, { strict: false }); - const customServer = new CustomServer(); - const hono = await customServer.build(ds, ms, mr); - const config = app.get(ConfigService, { strict: false }); - - const port = config.get('app.port'); - const hostname = config.get('app.hostname'); - const environment = config.get('app.env'); - const debug = config.get('app.debug'); - - serve({ - port, - fetch: hono.fetch, - }); - } - - async start() { - // console['mute'](); - const module = ModuleBuilder.build(this.container, this.kernel); - const app = await NestFactory.create(module, { - bodyParser: true, - logger: false, - }); - - if (this.errorHandler) { - const { httpAdapter } = app.get(HttpAdapterHost); - app.useGlobalFilters(new this.errorHandler(httpAdapter)); - } - - app.useBodyParser('json'); - app.useBodyParser('raw'); - app.useBodyParser('urlencoded'); - - app.use(requestMiddleware); - useContainer(app.select(module), { fallbackOnErrors: true }); - await this.container.boot(app); - + const customServer = new CustomServer(); + const server = await customServer.build(ds, ms, mr); const config = app.get(ConfigService, { strict: false }); - this.configureErrorReporter(config.get('app.sentry')); - const port = config.get('app.port'); const hostname = config.get('app.hostname'); const environment = config.get('app.env'); const debug = config.get('app.debug'); - await app.listen(+port || 5001, hostname); - - // console['resume'](); - - console.clear(); - - console.log(); + await server.listen(port, hostname || '0.0.0.0'); printBulletPoints([ ['➜', 'environment', environment], @@ -124,11 +75,69 @@ export class IntentHttpServer { : `http://${hostname}`, ); url.port = port; + this.configureErrorReporter(config.get('app.sentry')); console.log(); console.log(` ${pc.white('Listening on')}: ${pc.cyan(url.toString())}`); } + // async start() { + // // console['mute'](); + // const module = ModuleBuilder.build(this.container, this.kernel); + // const app = await NestFactory.create(module, { + // bodyParser: true, + // logger: false, + // }); + + // if (this.errorHandler) { + // const { httpAdapter } = app.get(HttpAdapterHost); + // app.useGlobalFilters(new this.errorHandler(httpAdapter)); + // } + + // app.useBodyParser('json'); + // app.useBodyParser('raw'); + // app.useBodyParser('urlencoded'); + + // app.use(requestMiddleware); + + // useContainer(app.select(module), { fallbackOnErrors: true }); + + // await this.container.boot(app); + + // const config = app.get(ConfigService, { strict: false }); + + // this.configureErrorReporter(config.get('app.sentry')); + + // const port = config.get('app.port'); + // const hostname = config.get('app.hostname'); + // const environment = config.get('app.env'); + // const debug = config.get('app.debug'); + + // await app.listen(+port || 5001, hostname); + // // console['resume'](); + + // console.clear(); + + // console.log(); + + // printBulletPoints([ + // ['➜', 'environment', environment], + // ['➜', 'debug', debug], + // ['➜', 'hostname', hostname], + // ['➜', 'port', port], + // ]); + + // const url = new URL( + // ['127.0.0.1', '0.0.0.0'].includes(hostname) + // ? 'http://localhost' + // : `http://${hostname}`, + // ); + // url.port = port; + + // console.log(); + // console.log(` ${pc.white('Listening on')}: ${pc.cyan(url.toString())}`); + // } + configureErrorReporter(config: Record) { if (!config) return; diff --git a/packages/core/package.json b/packages/core/package.json index 0e99a5b..b9d41f8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -65,7 +65,6 @@ "typescript": "^5.5.2" }, "dependencies": { - "@hono/node-server": "^1.13.7", "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", "@nestjs/platform-express": "^10.4.1", @@ -82,7 +81,6 @@ "express": "^4.21.0", "fs-extra": "^11.1.1", "helmet": "^7.1.0", - "hono": "^4.6.10", "ioredis": "^5.3.2", "knex": "^3.1.0", "ms": "^2.1.3", From 781ac413c1f861d6b7958c07f747981c66ef5356 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 24 Nov 2024 01:01:02 +0530 Subject: [PATCH 04/27] feat(core): hyper-express integration --- integrations/sample-app/app/errors/filter.ts | 23 +- .../sample-app/app/http/controllers/icon.ts | 72 +- .../sample-app/app/http/guards/custom.ts | 7 +- integrations/sample-app/app/main.ts | 14 +- .../core/lib/console/commands/route-list.ts | 22 + .../lib/exceptions/base-exception-handler.ts | 71 + .../lib/exceptions/forbidden-exception.ts | 11 + .../core/lib/exceptions/genericException.ts | 2 +- .../core/lib/exceptions/http-exception.ts | 52 + packages/core/lib/exceptions/index.ts | 13 +- .../lib/exceptions/intentExceptionFilter.ts | 44 - .../lib/exceptions/invalid-credentials.ts | 8 + ...alidValueType.ts => invalid-value-type.ts} | 0 .../{invalidValue.ts => invalid-value.ts} | 0 .../core/lib/exceptions/invalidCredentials.ts | 7 - packages/core/lib/exceptions/unauthorized.ts | 5 +- ...lidationfailed.ts => validation-failed.ts} | 3 +- packages/core/lib/interfaces/exceptions.ts | 1 + .../core/lib/reflections/apply-decorators.ts | 22 + packages/core/lib/reflections/index.ts | 2 +- .../{setMetadata.ts => set-metadata.ts} | 0 packages/core/lib/rest/decorators.ts | 96 +- .../core/lib/rest/foundation/controller.ts | 1 - .../contexts/http-execution-context.ts | 16 - .../foundation/custom-server/decorators.ts | 65 - .../rest/foundation/custom-server/explorer.ts | 146 - .../foundation/custom-server/http-handler.ts | 30 - .../rest/foundation/custom-server/request.ts | 8 - .../custom-server/response-handler.ts | 7 - .../lib/rest/foundation/guards/base-guard.ts | 12 +- packages/core/lib/rest/foundation/index.ts | 6 - .../core/lib/rest/foundation/interface.ts | 4 +- packages/core/lib/rest/foundation/methods.ts | 77 - .../foundation/middlewares/configurator.ts | 2 +- .../rest/foundation/middlewares/middleware.ts | 3 +- .../lib/rest/foundation/response-custom.ts | 65 - packages/core/lib/rest/foundation/server.ts | 104 +- .../core/lib/rest/http-server/constants.ts | 1 + .../contexts}/execution-context.ts | 2 +- .../contexts/http-execution-context.ts | 77 + .../core/lib/rest/http-server/decorators.ts | 125 + .../core/lib/rest/http-server/http-handler.ts | 45 + packages/core/lib/rest/http-server/index.ts | 10 + .../core/lib/rest/http-server/interfaces.ts | 6 + packages/core/lib/rest/http-server/methods.ts | 11 + .../lib/rest/http-server/param-decorators.ts | 96 + packages/core/lib/rest/http-server/request.ts | 9 + .../core/lib/rest/http-server/response.ts | 112 + .../lib/rest/http-server/route-explorer.ts | 165 ++ packages/core/lib/rest/http-server/server.ts | 52 + .../status-codes.ts} | 0 .../lib/rest/http-server/streamable-file.ts | 57 + packages/core/lib/rest/index.ts | 3 +- packages/core/lib/serviceProvider.ts | 2 + packages/core/lib/utils/extension-to-mime.ts | 2405 +++++++++++++++++ packages/core/lib/utils/helpers.ts | 4 + packages/core/lib/validator/validator.ts | 2 +- 57 files changed, 3558 insertions(+), 647 deletions(-) create mode 100644 packages/core/lib/console/commands/route-list.ts create mode 100644 packages/core/lib/exceptions/base-exception-handler.ts create mode 100644 packages/core/lib/exceptions/forbidden-exception.ts create mode 100644 packages/core/lib/exceptions/http-exception.ts delete mode 100644 packages/core/lib/exceptions/intentExceptionFilter.ts create mode 100644 packages/core/lib/exceptions/invalid-credentials.ts rename packages/core/lib/exceptions/{invalidValueType.ts => invalid-value-type.ts} (100%) rename packages/core/lib/exceptions/{invalidValue.ts => invalid-value.ts} (100%) delete mode 100644 packages/core/lib/exceptions/invalidCredentials.ts rename packages/core/lib/exceptions/{validationfailed.ts => validation-failed.ts} (79%) create mode 100644 packages/core/lib/interfaces/exceptions.ts create mode 100644 packages/core/lib/reflections/apply-decorators.ts rename packages/core/lib/reflections/{setMetadata.ts => set-metadata.ts} (100%) delete mode 100644 packages/core/lib/rest/foundation/controller.ts delete mode 100644 packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts delete mode 100644 packages/core/lib/rest/foundation/custom-server/decorators.ts delete mode 100644 packages/core/lib/rest/foundation/custom-server/explorer.ts delete mode 100644 packages/core/lib/rest/foundation/custom-server/http-handler.ts delete mode 100644 packages/core/lib/rest/foundation/custom-server/request.ts delete mode 100644 packages/core/lib/rest/foundation/custom-server/response-handler.ts delete mode 100644 packages/core/lib/rest/foundation/methods.ts delete mode 100644 packages/core/lib/rest/foundation/response-custom.ts create mode 100644 packages/core/lib/rest/http-server/constants.ts rename packages/core/lib/rest/{foundation/custom-server => http-server/contexts}/execution-context.ts (86%) create mode 100644 packages/core/lib/rest/http-server/contexts/http-execution-context.ts create mode 100644 packages/core/lib/rest/http-server/decorators.ts create mode 100644 packages/core/lib/rest/http-server/http-handler.ts create mode 100644 packages/core/lib/rest/http-server/index.ts create mode 100644 packages/core/lib/rest/http-server/interfaces.ts create mode 100644 packages/core/lib/rest/http-server/methods.ts create mode 100644 packages/core/lib/rest/http-server/param-decorators.ts create mode 100644 packages/core/lib/rest/http-server/request.ts create mode 100644 packages/core/lib/rest/http-server/response.ts create mode 100644 packages/core/lib/rest/http-server/route-explorer.ts create mode 100644 packages/core/lib/rest/http-server/server.ts rename packages/core/lib/rest/{foundation/statusCodes.ts => http-server/status-codes.ts} (100%) create mode 100644 packages/core/lib/rest/http-server/streamable-file.ts create mode 100644 packages/core/lib/utils/extension-to-mime.ts diff --git a/integrations/sample-app/app/errors/filter.ts b/integrations/sample-app/app/errors/filter.ts index 274db54..527a3a1 100644 --- a/integrations/sample-app/app/errors/filter.ts +++ b/integrations/sample-app/app/errors/filter.ts @@ -1,15 +1,10 @@ import { - Catch, + ExecutionContext, HttpException, - HttpStatus, IntentExceptionFilter, - Request, - Response, Type, - ValidationFailed, } from '@intentjs/core'; -@Catch() export class ApplicationExceptionFilter extends IntentExceptionFilter { doNotReport(): Array> { return []; @@ -19,19 +14,7 @@ export class ApplicationExceptionFilter extends IntentExceptionFilter { return '*'; } - handleHttp(exception: any, req: Request, res: Response) { - if (exception instanceof ValidationFailed) { - return res - .status(422) - .json({ message: 'validation failed', errors: exception.getErrors() }); - } - - return res.status(this.getStatus(exception)).json(exception); - } - - getStatus(exception: any): HttpStatus { - return exception instanceof HttpException - ? exception.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; + handleHttp(context: ExecutionContext, exception: any) { + return super.handleHttp(context, exception); } } diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 7f2931a..3335bcf 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -1,26 +1,74 @@ -import { IController, IGet, Injectable, Req, Request } from '@intentjs/core'; import { - IPost, - IUseGuards, -} from '@intentjs/core/dist/lib/rest/foundation/custom-server/decorators'; + Accepts, + Controller, + Get, + Host, + IP, + Param, + Post, + Query, + Req, + Request, + UseGuard, +} from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; -@IController('/icon') -@Injectable() -@IUseGuards(CustomGuard) +@Controller('/icon') +@UseGuard(CustomGuard) export class IntentController { constructor() {} - @IGet('/sample') - @IUseGuards(CustomGuard) - async getHello(@Req() req: Request) { + @Get('/:name') + @UseGuard(CustomGuard) + async getHello( + @Req() req: Request, + @Query() query: Record, + @Query('b') bQuery: string, + @Param('name') name: string, + @Param() pathParams: string, + @Host() hostname: string, + @IP() ips: string, + @Accepts() accepts: string, + ) { + // console.log(query, bQuery, name, pathParams, hostname, accepts, ips); + // throw new Error('hello there'); + return { hello: 'world' }; + } + + @Get('/plain-with-query-param') + @UseGuard(CustomGuard) + async plainWithQueryParam(@Req() req: Request) { + console.log(req); + return { hello: 'world' }; + } + + @Get('/:id') + @UseGuard(CustomGuard) + async plainWithPathParam(@Req() req: Request) { console.log(req); return { hello: 'world' }; } - @IPost('/sample') - @IUseGuards(CustomGuard) + @Post('/json') + async postJson(@Req() req: Request) { + return { hello: 'world' }; + } + + @Post('/multipart') + @UseGuard(CustomGuard) async postHello(@Req() req: Request) { return { hello: 'world' }; } + + @Post('/form-data') + @UseGuard(CustomGuard) + async postFormData(@Req() req: Request) { + return { hello: 'world' }; + } + + @Post('/binary') + @UseGuard(CustomGuard) + async postBinary(@Req() req: Request) { + return { hello: 'world' }; + } } diff --git a/integrations/sample-app/app/http/guards/custom.ts b/integrations/sample-app/app/http/guards/custom.ts index 0852ddf..8d3b53b 100644 --- a/integrations/sample-app/app/http/guards/custom.ts +++ b/integrations/sample-app/app/http/guards/custom.ts @@ -1,5 +1,5 @@ -import { Injectable, IntentGuard, Reflector } from '@intentjs/core'; -import { Request, Response } from 'hyper-express'; +import { Injectable, IntentGuard, Reflector, Response } from '@intentjs/core'; +import { Request } from 'hyper-express'; @Injectable() export class CustomGuard extends IntentGuard { @@ -8,10 +8,9 @@ export class CustomGuard extends IntentGuard { res: Response, reflector: Reflector, ): Promise { - console.log('urnning inside guard'); await req.multipart(async (field) => { console.log('field ===> ', field.name, field.file); }); - return false; + return true; } } diff --git a/integrations/sample-app/app/main.ts b/integrations/sample-app/app/main.ts index 2191f6d..dcb76c8 100644 --- a/integrations/sample-app/app/main.ts +++ b/integrations/sample-app/app/main.ts @@ -3,8 +3,12 @@ import { ApplicationContainer } from './boot/container'; import { ApplicationExceptionFilter } from './errors/filter'; import { IntentHttpServer } from '@intentjs/core'; -IntentHttpServer.init() - .useContainer(ApplicationContainer) - .useKernel(HttpKernel) - .handleErrorsWith(ApplicationExceptionFilter) - .start(); +const server = IntentHttpServer.init(); + +server.useContainer(ApplicationContainer); + +server.useKernel(HttpKernel); + +server.handleErrorsWith(ApplicationExceptionFilter); + +server.start(); diff --git a/packages/core/lib/console/commands/route-list.ts b/packages/core/lib/console/commands/route-list.ts new file mode 100644 index 0000000..323cc66 --- /dev/null +++ b/packages/core/lib/console/commands/route-list.ts @@ -0,0 +1,22 @@ +import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; +import { Command } from '../decorators'; +import { columnify } from '../../utils/columnify'; +import { RouteExplorer } from '../../rest'; + +@Command('routes:list') +export class ListRouteCommand { + constructor(private moduleRef: ModuleRef) {} + + async handle() { + const ds = this.moduleRef.get(DiscoveryService, { strict: false }); + const ms = this.moduleRef.get(MetadataScanner, { strict: false }); + + const routeExplorer = new RouteExplorer(); + const routes = routeExplorer.explorePlainRoutes(ds, ms); + + const formattedRows = columnify(routes, { padStart: 2 }); + console.log(formattedRows); + + return 1; + } +} diff --git a/packages/core/lib/exceptions/base-exception-handler.ts b/packages/core/lib/exceptions/base-exception-handler.ts new file mode 100644 index 0000000..de9faf4 --- /dev/null +++ b/packages/core/lib/exceptions/base-exception-handler.ts @@ -0,0 +1,71 @@ +import { ConfigService } from '../config/service'; +import { Log } from '../logger'; +import { Package } from '../utils'; +import { Type } from '../interfaces'; +import { HttpException } from './http-exception'; +import { ValidationFailed } from './validation-failed'; +import { ExecutionContext } from '../rest/http-server'; +import { HttpStatus } from '../rest/http-server/status-codes'; + +export abstract class IntentExceptionFilter { + doNotReport(): Array> { + return []; + } + + report(): Array> | string { + return '*'; + } + + catch(context: ExecutionContext, exception: any) { + const ctx = context.switchToHttp(); + + this.reportToSentry(exception); + + Log().error('', exception); + + return this.handleHttp(context, exception); + } + + handleHttp(context: ExecutionContext, exception: any) { + const res = context.switchToHttp().getResponse(); + + const debugMode = ConfigService.get('app.debug'); + + if (exception instanceof ValidationFailed) { + return res.status(HttpStatus.UNPROCESSABLE_ENTITY).json({ + message: 'validation failed', + errors: exception.getErrors(), + }); + } + + if (exception instanceof HttpException) { + return res.status(exception.getStatus()).json({ + message: exception.message, + status: exception.getStatus(), + stack: debugMode && exception.stack, + }); + } + + return res.status(this.getStatus(exception)).json(exception); + } + + reportToSentry(exception: any): void { + const sentryConfig = ConfigService.get('app.sentry'); + if (!sentryConfig?.dsn) return; + + const exceptionConstructor = exception?.constructor; + const sentry = Package.load('@sentry/node'); + if ( + exceptionConstructor && + !this.doNotReport().includes(exceptionConstructor) + ) { + sentry.captureException(exception); + } + } + + getStatus(exception: any): HttpStatus { + return exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + } +} diff --git a/packages/core/lib/exceptions/forbidden-exception.ts b/packages/core/lib/exceptions/forbidden-exception.ts new file mode 100644 index 0000000..fca3733 --- /dev/null +++ b/packages/core/lib/exceptions/forbidden-exception.ts @@ -0,0 +1,11 @@ +import { HttpStatus } from '../rest'; +import { HttpException } from './http-exception'; + +export class ForbiddenException extends HttpException { + constructor( + response: string | Record, + status: number | HttpStatus = HttpStatus.FORBIDDEN, + ) { + super(response, status); + } +} diff --git a/packages/core/lib/exceptions/genericException.ts b/packages/core/lib/exceptions/genericException.ts index 11b08fa..c1ef0d9 100644 --- a/packages/core/lib/exceptions/genericException.ts +++ b/packages/core/lib/exceptions/genericException.ts @@ -1,4 +1,4 @@ -import { HttpException } from '@nestjs/common'; +import { HttpException } from './http-exception'; export class GenericException extends HttpException { constructor(message?: string) { diff --git a/packages/core/lib/exceptions/http-exception.ts b/packages/core/lib/exceptions/http-exception.ts new file mode 100644 index 0000000..d17fd33 --- /dev/null +++ b/packages/core/lib/exceptions/http-exception.ts @@ -0,0 +1,52 @@ +import { HttpStatus } from '../rest'; +import { Obj, Str } from '../utils'; + +export interface HttpExceptionOptions { + cause?: string; + description?: string; +} + +export class HttpException extends Error { + public cause: unknown; + + constructor( + private readonly response: string | Record, + private readonly status: number | HttpStatus, + private readonly options?: HttpExceptionOptions, + ) { + super(); + this.initMessage(); + } + + public initMessage() { + if (Str.isString(this.response)) { + this.message = this.response as string; + } else if ( + Obj.isObj(this.response) && + Str.isString((this.response as Record).message) + ) { + this.message = (this.response as Record).message; + } else if (this.constructor) { + this.message = + this.constructor.name.match(/[A-Z][a-z]+|[0-9]+/g)?.join(' ') ?? + 'Error'; + } + } + + public initCause() { + this.cause = this.options?.cause ?? ''; + return; + } + + public initName(): void { + this.name = this.constructor.name; + } + + public getResponse(): string | object { + return this.response; + } + + public getStatus(): number { + return this.status; + } +} diff --git a/packages/core/lib/exceptions/index.ts b/packages/core/lib/exceptions/index.ts index 55f4532..ffea587 100644 --- a/packages/core/lib/exceptions/index.ts +++ b/packages/core/lib/exceptions/index.ts @@ -1,8 +1,9 @@ export * from './unauthorized'; -export * from './invalidCredentials'; -export * from './validationfailed'; +export * from './invalid-credentials'; +export * from './validation-failed'; export * from './genericException'; -export * from './intentExceptionFilter'; -export * from './invalidValue'; -export * from './invalidValueType'; -export { HttpException, Catch } from '@nestjs/common'; +export * from './base-exception-handler'; +export * from './invalid-value'; +export * from './invalid-value-type'; +export * from './http-exception'; +export * from './forbidden-exception'; diff --git a/packages/core/lib/exceptions/intentExceptionFilter.ts b/packages/core/lib/exceptions/intentExceptionFilter.ts deleted file mode 100644 index 4f8c328..0000000 --- a/packages/core/lib/exceptions/intentExceptionFilter.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ArgumentsHost, HttpException, Type } from '@nestjs/common'; -import { BaseExceptionFilter } from '@nestjs/core'; -import { ConfigService } from '../config/service'; -import { Log } from '../logger'; -import { Request, Response } from '../rest/foundation'; -import { Package } from '../utils'; - -export abstract class IntentExceptionFilter extends BaseExceptionFilter { - abstract handleHttp(exception: any, req: Request, res: Response); - - doNotReport(): Array> { - return []; - } - - report(): Array> | string { - return '*'; - } - - catch(exception: any, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - const request = ctx.getRequest() as Request; - const response = ctx.getResponse(); - - this.reportToSentry(exception); - - Log().error('', exception); - - return this.handleHttp(exception, request, response); - } - - reportToSentry(exception: any): void { - const sentryConfig = ConfigService.get('app.sentry'); - if (!sentryConfig?.dsn) return; - - const exceptionConstructor = exception?.constructor; - const sentry = Package.load('@sentry/node'); - if ( - exceptionConstructor && - !this.doNotReport().includes(exceptionConstructor) - ) { - sentry.captureException(exception); - } - } -} diff --git a/packages/core/lib/exceptions/invalid-credentials.ts b/packages/core/lib/exceptions/invalid-credentials.ts new file mode 100644 index 0000000..5e1529d --- /dev/null +++ b/packages/core/lib/exceptions/invalid-credentials.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '../rest'; +import { HttpException } from './http-exception'; + +export class InvalidCredentials extends HttpException { + constructor() { + super('Invalid Credentials', HttpStatus.UNAUTHORIZED); + } +} diff --git a/packages/core/lib/exceptions/invalidValueType.ts b/packages/core/lib/exceptions/invalid-value-type.ts similarity index 100% rename from packages/core/lib/exceptions/invalidValueType.ts rename to packages/core/lib/exceptions/invalid-value-type.ts diff --git a/packages/core/lib/exceptions/invalidValue.ts b/packages/core/lib/exceptions/invalid-value.ts similarity index 100% rename from packages/core/lib/exceptions/invalidValue.ts rename to packages/core/lib/exceptions/invalid-value.ts diff --git a/packages/core/lib/exceptions/invalidCredentials.ts b/packages/core/lib/exceptions/invalidCredentials.ts deleted file mode 100644 index 5930b7a..0000000 --- a/packages/core/lib/exceptions/invalidCredentials.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { HttpException } from '@nestjs/common'; - -export class InvalidCredentials extends HttpException { - constructor() { - super('Invalid Credentials', 403); - } -} diff --git a/packages/core/lib/exceptions/unauthorized.ts b/packages/core/lib/exceptions/unauthorized.ts index 56f68f2..9d9036c 100644 --- a/packages/core/lib/exceptions/unauthorized.ts +++ b/packages/core/lib/exceptions/unauthorized.ts @@ -1,7 +1,8 @@ -import { HttpException } from '@nestjs/common'; +import { HttpStatus } from '../rest'; +import { HttpException } from './http-exception'; export class Unauthorized extends HttpException { constructor() { - super('Unauthorized.', 401); + super('Unauthorized.', HttpStatus.UNAUTHORIZED); } } diff --git a/packages/core/lib/exceptions/validationfailed.ts b/packages/core/lib/exceptions/validation-failed.ts similarity index 79% rename from packages/core/lib/exceptions/validationfailed.ts rename to packages/core/lib/exceptions/validation-failed.ts index f007366..39d37b0 100644 --- a/packages/core/lib/exceptions/validationfailed.ts +++ b/packages/core/lib/exceptions/validation-failed.ts @@ -1,4 +1,5 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '../rest'; +import { HttpException } from './http-exception'; export class ValidationFailed extends HttpException { private errors: Record; diff --git a/packages/core/lib/interfaces/exceptions.ts b/packages/core/lib/interfaces/exceptions.ts new file mode 100644 index 0000000..d64206a --- /dev/null +++ b/packages/core/lib/interfaces/exceptions.ts @@ -0,0 +1 @@ +export interface HttpExceptionOptions {} diff --git a/packages/core/lib/reflections/apply-decorators.ts b/packages/core/lib/reflections/apply-decorators.ts new file mode 100644 index 0000000..52c899c --- /dev/null +++ b/packages/core/lib/reflections/apply-decorators.ts @@ -0,0 +1,22 @@ +export function applyDecorators( + ...decorators: Array +) { + return ( + target: TFunction | object, + propertyKey?: string | symbol, + descriptor?: TypedPropertyDescriptor, + ) => { + for (const decorator of decorators) { + if (target instanceof Function && !descriptor) { + (decorator as ClassDecorator)(target); + continue; + } + + (decorator as MethodDecorator | PropertyDecorator)( + target, + propertyKey, + descriptor, + ); + } + }; +} diff --git a/packages/core/lib/reflections/index.ts b/packages/core/lib/reflections/index.ts index 25b3a4d..edaebea 100644 --- a/packages/core/lib/reflections/index.ts +++ b/packages/core/lib/reflections/index.ts @@ -1,2 +1,2 @@ export * from './reflector'; -export * from './setMetadata'; +export * from './set-metadata'; diff --git a/packages/core/lib/reflections/setMetadata.ts b/packages/core/lib/reflections/set-metadata.ts similarity index 100% rename from packages/core/lib/reflections/setMetadata.ts rename to packages/core/lib/reflections/set-metadata.ts diff --git a/packages/core/lib/rest/decorators.ts b/packages/core/lib/rest/decorators.ts index 658d019..59505cc 100644 --- a/packages/core/lib/rest/decorators.ts +++ b/packages/core/lib/rest/decorators.ts @@ -1,49 +1,47 @@ -import { ExecutionContext, createParamDecorator } from '@nestjs/common'; -import { plainToInstance } from 'class-transformer'; -import { Request } from './foundation'; - -export { Req, Res } from '@nestjs/common'; - -export const Body = createParamDecorator( - (data: any, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - if (request.dto()) return request.dto(); - - const types = Reflect.getMetadata( - 'design:paramtypes', - ctx.getClass().prototype, - ctx.getHandler().name, - ); - - const paramIndex = Reflect.getMetadata( - 'intent::body_decorator_index', - ctx.getHandler(), - ); - - const paramType = types[paramIndex]; - - /** - * Check the type of paramType, - * if the value is `function`, then we will assume that it's a DTO. - * otherwise if it is another data type, we will convert the value and then send it back. - */ - const typeParamType = typeof paramType; - - if (typeParamType === 'function') { - const dto = plainToInstance(paramType, request.all()); - request.setDto(dto); - request.body(); - } - - return request.all(); - }, - [ - (target: any, propKey: string | symbol, index: number): void => { - Reflect.defineMetadata( - 'intent::body_decorator_index', - index, - target[propKey], - ); - }, - ], -); +// import { ExecutionContext, createParamDecorator } from '@nestjs/common'; +// import { plainToInstance } from 'class-transformer'; +// import { Request } from './foundation'; + +// export const Body = createParamDecorator( +// (data: any, ctx: ExecutionContext) => { +// const request = ctx.switchToHttp().getRequest(); +// if (request.dto()) return request.dto(); + +// const types = Reflect.getMetadata( +// 'design:paramtypes', +// ctx.getClass().prototype, +// ctx.getHandler().name, +// ); + +// const paramIndex = Reflect.getMetadata( +// 'intent::body_decorator_index', +// ctx.getHandler(), +// ); + +// const paramType = types[paramIndex]; + +// /** +// * Check the type of paramType, +// * if the value is `function`, then we will assume that it's a DTO. +// * otherwise if it is another data type, we will convert the value and then send it back. +// */ +// const typeParamType = typeof paramType; + +// if (typeParamType === 'function') { +// const dto = plainToInstance(paramType, request.all()); +// request.setDto(dto); +// request.body(); +// } + +// return request.all(); +// }, +// [ +// (target: any, propKey: string | symbol, index: number): void => { +// Reflect.defineMetadata( +// 'intent::body_decorator_index', +// index, +// target[propKey], +// ); +// }, +// ], +// ); diff --git a/packages/core/lib/rest/foundation/controller.ts b/packages/core/lib/rest/foundation/controller.ts deleted file mode 100644 index 484e3ec..0000000 --- a/packages/core/lib/rest/foundation/controller.ts +++ /dev/null @@ -1 +0,0 @@ -export { Controller } from '@nestjs/common'; diff --git a/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts b/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts deleted file mode 100644 index 7f66773..0000000 --- a/packages/core/lib/rest/foundation/custom-server/contexts/http-execution-context.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Request, Response } from 'hyper-express'; - -export class HttpExecutionContext { - constructor( - private readonly request: Request, - private readonly response: Response, - ) {} - - getRequest(): Request { - return this.request; - } - - getResponse(): Response { - return this.response; - } -} diff --git a/packages/core/lib/rest/foundation/custom-server/decorators.ts b/packages/core/lib/rest/foundation/custom-server/decorators.ts deleted file mode 100644 index c950d6f..0000000 --- a/packages/core/lib/rest/foundation/custom-server/decorators.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Type } from '../../../interfaces'; -import { SetMetadata } from '../../../reflections'; -import { IntentGuard } from '../guards/base-guard'; - -export const CONTROLLER_KEY = '@intentjs/controller_path'; -export const CONTROLLER_OPTIONS = '@intentjs/controller_options'; - -export const METHOD_KEY = '@intentjs/controller_method_key'; -export const METHOD_PATH = '@intentjs/controller_method_path'; - -export const GUARD_KEY = '@intentjs/controller_guards'; - -export type ControllerOptions = { - host?: string; -}; - -export enum HttpMethods { - GET, - POST, - PUT, - PATCH, - OPTIONS, - HEAD, - DELETE, - ANY, -} - -export function IController(path?: string, options?: ControllerOptions) { - return function (target: Function) { - // if (descriptor) { - // Reflect.defineMetadata(CONTROLLER_KEY, path, descriptor.value); - // return descriptor; - // } - - Reflect.defineMetadata(CONTROLLER_KEY, path || '', target); - Reflect.defineMetadata(CONTROLLER_OPTIONS, options, target); - }; -} - -export function IGet(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.GET, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} - -export function IPost(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.POST, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} - -export const IUseGuards = (...guards: Type[]) => { - return function (target: object, key?: string | symbol, descriptor?: any) { - if (key) { - Reflect.defineMetadata(GUARD_KEY, guards, target, key); - return; - } - Reflect.defineMetadata(GUARD_KEY, guards, target); - return; - }; -}; diff --git a/packages/core/lib/rest/foundation/custom-server/explorer.ts b/packages/core/lib/rest/foundation/custom-server/explorer.ts deleted file mode 100644 index c83cdee..0000000 --- a/packages/core/lib/rest/foundation/custom-server/explorer.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; -import { - CONTROLLER_KEY, - GUARD_KEY, - HttpMethods, - METHOD_KEY, - METHOD_PATH, -} from './decorators'; -import { join } from 'path'; -import { IntentGuard } from '../guards/base-guard'; -import { Type } from '../../../interfaces'; -import { HttpRouteHandler } from './http-handler'; -import { ResponseHandler } from './response-handler'; -import { ExecutionContext } from './execution-context'; -import { HttpExecutionContext } from './contexts/http-execution-context'; -import HyperExpress, { Request, Response } from 'hyper-express'; - -export class CustomServer { - private hyper = new HyperExpress.Server(); - - async build( - discoveryService: DiscoveryService, - metadataScanner: MetadataScanner, - moduleRef: ModuleRef, - ): Promise { - const providers = discoveryService.getProviders(); - for (const provider of providers) { - const { instance } = provider; - // if ( - // !instance || - // typeof instance === 'string' || - // !Object.getPrototypeOf(instance) - // ) { - // return; - // } - - const methodNames = metadataScanner.getAllMethodNames(instance); - for (const methodName of methodNames) { - await this.exploreRoutes(instance, methodName, moduleRef); - } - } - - return this.hyper; - } - - async exploreRoutes(instance: any, key: string, moduleRef: ModuleRef) { - const controllerKey = Reflect.getMetadata( - CONTROLLER_KEY, - instance.constructor, - ); - if (!controllerKey) return; - const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); - const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); - const methodRef = instance[key].bind(instance); - const controllerGuards = Reflect.getMetadata( - GUARD_KEY, - instance.constructor, - ); - - const methodGuards = Reflect.getMetadata(GUARD_KEY, instance, key); - - const composedGuardTypes = [ - ...(controllerGuards || []), - ...(methodGuards || []), - ] as Type[]; - - const composedGuards = []; - for (const guardType of composedGuardTypes) { - composedGuards.push(await moduleRef.create(guardType)); - } - - const middlewares = []; - - const handler = new HttpRouteHandler( - middlewares, - composedGuards, - methodRef, - ); - - const responseHandler = new ResponseHandler(); - - const cb = async (hReq: Request, hRes: Response) => { - const now = performance.now(); - const httpContext = new HttpExecutionContext(hReq, hRes); - const executionContext = new ExecutionContext( - httpContext, - instance, - methodRef, - ); - - const resFromHandler = await handler.handle(executionContext); - - const [type, res] = await responseHandler.handle( - hReq, - hRes, - resFromHandler, - ); - - if (type === 'json') { - hRes - .header('Content-Type', 'application/json') - .send(JSON.stringify(res)); - } - - console.log( - 'time to handle one http request ===> ', - performance.now() - now, - ); - }; - - const fullPath = join(controllerKey, methodPath); - switch (pathMethod) { - case HttpMethods.GET: - this.hyper.get(fullPath, cb); - break; - - case HttpMethods.POST: - this.hyper.post(fullPath, cb); - break; - - case HttpMethods.DELETE: - this.hyper.delete(fullPath, cb); - break; - - case HttpMethods.HEAD: - this.hyper.head(fullPath, cb); - break; - - case HttpMethods.PUT: - this.hyper.put(fullPath, cb); - break; - - case HttpMethods.PATCH: - this.hyper.patch(fullPath, cb); - break; - - case HttpMethods.OPTIONS: - this.hyper.options(fullPath, cb); - break; - - case HttpMethods.ANY: - this.hyper.any(fullPath, cb); - break; - } - } -} diff --git a/packages/core/lib/rest/foundation/custom-server/http-handler.ts b/packages/core/lib/rest/foundation/custom-server/http-handler.ts deleted file mode 100644 index 945d485..0000000 --- a/packages/core/lib/rest/foundation/custom-server/http-handler.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IntentGuard } from '../guards/base-guard'; -import { IntentMiddleware } from '../middlewares/middleware'; -import { ExecutionContext } from './execution-context'; - -export class HttpRouteHandler { - constructor( - protected readonly middlewares: IntentMiddleware[], - protected readonly guards: IntentGuard[], - protected readonly handler: Function, - ) {} - - async handle(context: ExecutionContext) { - // for (const middleware of this.middlewares) { - // await middleware.use({}, {}); - // } - - /** - * Handle the Guards - */ - for (const guard of this.guards) { - await guard.handle(context); - } - - /** - * Handle the request - */ - const res = await this.handler(context); - return res; - } -} diff --git a/packages/core/lib/rest/foundation/custom-server/request.ts b/packages/core/lib/rest/foundation/custom-server/request.ts deleted file mode 100644 index dee2268..0000000 --- a/packages/core/lib/rest/foundation/custom-server/request.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class IntentRequestObj { - constructor( - public readonly param: Function, - public readonly query: Function, - public readonly queries: Function, - public readonly header: Function, - ) {} -} diff --git a/packages/core/lib/rest/foundation/custom-server/response-handler.ts b/packages/core/lib/rest/foundation/custom-server/response-handler.ts deleted file mode 100644 index 11e0565..0000000 --- a/packages/core/lib/rest/foundation/custom-server/response-handler.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Request, Response } from 'hyper-express'; - -export class ResponseHandler { - async handle(hReq: Request, hRes: Response, res: any) { - return ['json', res]; - } -} diff --git a/packages/core/lib/rest/foundation/guards/base-guard.ts b/packages/core/lib/rest/foundation/guards/base-guard.ts index 7b2a6e6..1f22dce 100644 --- a/packages/core/lib/rest/foundation/guards/base-guard.ts +++ b/packages/core/lib/rest/foundation/guards/base-guard.ts @@ -1,9 +1,10 @@ import { Reflector } from '../../../reflections'; -import { ExecutionContext } from '../custom-server/execution-context'; -import { Request, Response } from 'hyper-express'; +import { Request } from 'hyper-express'; +import { ForbiddenException } from '../../../exceptions/forbidden-exception'; +import { ExecutionContext, Response } from '../../http-server'; export abstract class IntentGuard { - async handle(context: ExecutionContext): Promise { + async handle(context: ExecutionContext): Promise { /** * Get Express Request Object */ @@ -19,7 +20,10 @@ export abstract class IntentGuard { */ const reflector = new Reflector(context.getClass(), context.getHandler()); - return this.guard(request, response, reflector); + const validationFromGuard = await this.guard(request, response, reflector); + if (!validationFromGuard) { + throw new ForbiddenException('Forbidden Resource'); + } } abstract guard( diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index 73eee24..94ba4ee 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -1,14 +1,8 @@ export * from './guards/base-guard'; export * from './guards/decorator'; -export * from './methods'; export * from './interface'; export * from './kernel'; -export * from './controller'; export * from './middlewares/middleware'; export * from './middlewares/configurator'; export * from './server'; -export * from './statusCodes'; -export * from './methods'; export * from './request-mixin'; -export * from './custom-server/decorators'; -export * from './custom-server/explorer'; diff --git a/packages/core/lib/rest/foundation/interface.ts b/packages/core/lib/rest/foundation/interface.ts index cd36d02..19fe6e4 100644 --- a/packages/core/lib/rest/foundation/interface.ts +++ b/packages/core/lib/rest/foundation/interface.ts @@ -1,10 +1,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ import { IncomingHttpHeaders } from 'http'; -import { Request as ERequest, Response as EResponse } from 'express'; +import { Request as ERequest } from 'express'; import { Type } from '../../interfaces'; -export type Response = EResponse; - export interface Request extends ERequest { logger: Function; setDto: Function; diff --git a/packages/core/lib/rest/foundation/methods.ts b/packages/core/lib/rest/foundation/methods.ts deleted file mode 100644 index d92148e..0000000 --- a/packages/core/lib/rest/foundation/methods.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - Get as NGet, - Post as NPost, - Put as NPut, - Patch as NPatch, - Delete as NDelete, - All as NAll, - Options as NOptions, - Head as NHead, - applyDecorators, -} from '@nestjs/common'; - -export const Get = (path?: string | string[]) => applyDecorators(NGet(path)); - -/** - * POST Method - * @param path - * @returns - */ -export const Post = (path?: string | string[]) => applyDecorators(NPost(path)); - -/** - * PUT Method - * @param path - * @returns - */ -export const Put = (path?: string | string[]) => applyDecorators(NPut(path)); - -/** - * PATCH Method - * @param path - * @returns - */ -export const Patch = (path?: string | string[]) => - applyDecorators(NPatch(path)); - -/** - * DELETE Method - * @param path - * @returns - */ -export const Delete = (path?: string | string[]) => - applyDecorators(NDelete(path)); - -/** - * ALL Method - * @param path - * @returns - */ -export const All = (path?: string | string[]) => applyDecorators(NAll(path)); - -/** - * Options Method - * @param path - * @returns - */ -export const Options = (path?: string | string[]) => - applyDecorators(NOptions(path)); - -/** - * HEAD Method - * @param path - * @returns - */ -export const Head = (path?: string | string[]) => applyDecorators(NHead(path)); - -export enum RequestMethod { - GET = 0, - POST = 1, - PUT = 2, - DELETE = 3, - PATCH = 4, - ALL = 5, - OPTIONS = 6, - HEAD = 7, - SEARCH = 8, -} diff --git a/packages/core/lib/rest/foundation/middlewares/configurator.ts b/packages/core/lib/rest/foundation/middlewares/configurator.ts index 90a8e64..d95b90f 100644 --- a/packages/core/lib/rest/foundation/middlewares/configurator.ts +++ b/packages/core/lib/rest/foundation/middlewares/configurator.ts @@ -1,5 +1,5 @@ import { Type } from '../../../interfaces'; -import { RequestMethod } from '../methods'; +import { RequestMethod } from '../../http-server/methods'; import { IntentMiddleware } from './middleware'; /** diff --git a/packages/core/lib/rest/foundation/middlewares/middleware.ts b/packages/core/lib/rest/foundation/middlewares/middleware.ts index 5cc60f7..1fb9ae5 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware.ts @@ -1,6 +1,7 @@ import { NestMiddleware } from '@nestjs/common'; import { NextFunction } from 'express'; -import { Request, Response } from '../interface'; +import { Request } from 'hyper-express'; +import { Response } from '../../http-server'; export abstract class IntentMiddleware implements NestMiddleware { async use(req: Request, res: Response, next: NextFunction): Promise { diff --git a/packages/core/lib/rest/foundation/response-custom.ts b/packages/core/lib/rest/foundation/response-custom.ts deleted file mode 100644 index 6e6139f..0000000 --- a/packages/core/lib/rest/foundation/response-custom.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ClassSerializerContextOptions, StreamableFile } from '@nestjs/common'; -import { StreamableFileOptions } from '@nestjs/common/file-stream/interfaces'; -import { instanceToPlain, plainToInstance } from 'class-transformer'; -import { Response as EResponse } from 'express'; -import { ReadStream } from 'fs'; - -export class Response { - private $headers: Record; - private $data: Record; - private $statusCode: number; - - constructor(private response: EResponse) { - this.$data = undefined; - this.$headers = {}; - this.$statusCode = 200; - } - - send(data: any, statusCode: number = 200): this { - this.$data = data; - this.response.status(statusCode); - return this; - } - - header(key: string, value: string): this { - // this.$headers[key] = value; - this.response.setHeader(key, value); - return this; - } - - status(status: number = 200): this { - this.response.status(status); - return this; - } - - stream(stream: ReadStream, options: StreamableFileOptions = {}): this { - this.$data = new StreamableFile(stream, options); - this.status(200); - return this; - } - - data(): any { - if (this.$data instanceof StreamableFile) { - return this.$data; - } - - return this.$data; - } - - transformToPlain( - plainOrClass: any, - options: ClassSerializerContextOptions, - ): Record { - if (!plainOrClass) { - return plainOrClass; - } - if (!options.type) { - return instanceToPlain(plainOrClass, options); - } - if (plainOrClass instanceof options.type) { - return instanceToPlain(plainOrClass, options); - } - const instance = plainToInstance(options.type, plainOrClass); - return instanceToPlain(instance, options); - } -} diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 6b45d0d..519a2e9 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -15,7 +15,10 @@ import { requestMiddleware } from '../middlewares/functional/requestSerializer'; import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; -import { CustomServer } from './custom-server/explorer'; +import { Server } from 'hyper-express'; +import { HyperServer, RouteExplorer } from '../http-server'; + +const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; export class IntentHttpServer { private kernel: Kernel; @@ -45,23 +48,48 @@ export class IntentHttpServer { async start() { const module = ModuleBuilder.build(this.container, this.kernel); const app = await NestFactory.createApplicationContext(module); + const ds = app.get(DiscoveryService, { strict: false }); const ms = app.get(MetadataScanner, { strict: false }); const mr = app.get(ModuleRef, { strict: false }); + const errorHandler = await mr.create(this.errorHandler); useContainer(app.select(module), { fallbackOnErrors: true }); - const customServer = new CustomServer(); - const server = await customServer.build(ds, ms, mr); + const routeExplorer = new RouteExplorer(); + const routes = await routeExplorer.exploreFullRoutes( + ds, + ms, + mr, + errorHandler, + ); + + const customServer = new HyperServer(); + const server = await customServer.build(routes); + const config = app.get(ConfigService, { strict: false }); + this.configureErrorReporter(config.get('app.sentry')); const port = config.get('app.port'); const hostname = config.get('app.hostname'); - const environment = config.get('app.env'); - const debug = config.get('app.debug'); + + await this.container.boot(app); await server.listen(port, hostname || '0.0.0.0'); + for (const signal of signals) { + process.on(signal, () => this.shutdown(server, signal)); + } + + this.printToConsole(config); + } + + printToConsole(config: ConfigService) { + const port = config.get('app.port'); + const hostname = config.get('app.hostname'); + const environment = config.get('app.env'); + const debug = config.get('app.debug'); + printBulletPoints([ ['➜', 'environment', environment], ['➜', 'debug', debug], @@ -75,68 +103,22 @@ export class IntentHttpServer { : `http://${hostname}`, ); url.port = port; - this.configureErrorReporter(config.get('app.sentry')); console.log(); console.log(` ${pc.white('Listening on')}: ${pc.cyan(url.toString())}`); } - // async start() { - // // console['mute'](); - // const module = ModuleBuilder.build(this.container, this.kernel); - // const app = await NestFactory.create(module, { - // bodyParser: true, - // logger: false, - // }); - - // if (this.errorHandler) { - // const { httpAdapter } = app.get(HttpAdapterHost); - // app.useGlobalFilters(new this.errorHandler(httpAdapter)); - // } - - // app.useBodyParser('json'); - // app.useBodyParser('raw'); - // app.useBodyParser('urlencoded'); - - // app.use(requestMiddleware); - - // useContainer(app.select(module), { fallbackOnErrors: true }); - - // await this.container.boot(app); - - // const config = app.get(ConfigService, { strict: false }); - - // this.configureErrorReporter(config.get('app.sentry')); + async shutdown(server: Server, signal: string): Promise { + console.log(`\nReceived ${signal}, starting graceful shutdown...`); - // const port = config.get('app.port'); - // const hostname = config.get('app.hostname'); - // const environment = config.get('app.env'); - // const debug = config.get('app.debug'); - - // await app.listen(+port || 5001, hostname); - // // console['resume'](); - - // console.clear(); - - // console.log(); - - // printBulletPoints([ - // ['➜', 'environment', environment], - // ['➜', 'debug', debug], - // ['➜', 'hostname', hostname], - // ['➜', 'port', port], - // ]); - - // const url = new URL( - // ['127.0.0.1', '0.0.0.0'].includes(hostname) - // ? 'http://localhost' - // : `http://${hostname}`, - // ); - // url.port = port; - - // console.log(); - // console.log(` ${pc.white('Listening on')}: ${pc.cyan(url.toString())}`); - // } + if (server) { + await new Promise(res => + server.close(() => { + res(1); + }), + ); + } + } configureErrorReporter(config: Record) { if (!config) return; diff --git a/packages/core/lib/rest/http-server/constants.ts b/packages/core/lib/rest/http-server/constants.ts new file mode 100644 index 0000000..74ffba1 --- /dev/null +++ b/packages/core/lib/rest/http-server/constants.ts @@ -0,0 +1 @@ +export const ROUTE_ARGS = '__route_args__'; diff --git a/packages/core/lib/rest/foundation/custom-server/execution-context.ts b/packages/core/lib/rest/http-server/contexts/execution-context.ts similarity index 86% rename from packages/core/lib/rest/foundation/custom-server/execution-context.ts rename to packages/core/lib/rest/http-server/contexts/execution-context.ts index c9a9697..5fd367d 100644 --- a/packages/core/lib/rest/foundation/custom-server/execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/execution-context.ts @@ -1,5 +1,5 @@ import { GenericClass } from '../../../interfaces'; -import { HttpExecutionContext } from './contexts/http-execution-context'; +import { HttpExecutionContext } from './http-execution-context'; export class ExecutionContext { constructor( diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts new file mode 100644 index 0000000..00d04fe --- /dev/null +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -0,0 +1,77 @@ +import { Request } from 'hyper-express'; +import { Response } from '../response'; +import { RouteArgType, RouteParamtypes } from '../param-decorators'; + +export class HttpExecutionContext { + constructor( + private readonly request: Request, + private readonly response: Response, + ) {} + + getRequest(): Request { + return this.request; + } + + getResponse(): Response { + return this.response; + } + + getInjectableValueFromArgType(routeArg: RouteArgType): any { + const { type, data } = routeArg; + switch (type) { + case RouteParamtypes.REQUEST: + return this.getRequest(); + case RouteParamtypes.RESPONSE: + return this.getResponse(); + + case RouteParamtypes.QUERY: + if (data) { + return this.request.query_parameters[data as string]; + } + return { ...this.request.query_parameters }; + + case RouteParamtypes.ACCEPTS: + return this.request.accepts(); + + case RouteParamtypes.NEXT: + + case RouteParamtypes.BODY: + if (data) { + return this.request.body[data as string]; + } + return { ...this.request.body }; + + case RouteParamtypes.PARAM: + if (data) { + return this.request.path_parameters[data as string]; + } + + return { ...this.request.path_parameters }; + + case RouteParamtypes.SESSION: + + case RouteParamtypes.FILE: + + case RouteParamtypes.FILES: + + case RouteParamtypes.IP: + return this.request.ip; + + case RouteParamtypes.RAW_BODY: + return this.request.raw; + + case RouteParamtypes.USER_AGENT: + return this.request.header('user-agent'); + + case RouteParamtypes.HOST: + return this.request.url; + + case RouteParamtypes.HEADERS: + if (data) { + return this.request.header(data as string); + } + + return { ...(this.request.headers || {}) }; + } + } +} diff --git a/packages/core/lib/rest/http-server/decorators.ts b/packages/core/lib/rest/http-server/decorators.ts new file mode 100644 index 0000000..3d1cd4e --- /dev/null +++ b/packages/core/lib/rest/http-server/decorators.ts @@ -0,0 +1,125 @@ +import { Injectable } from '@nestjs/common'; +import { applyDecorators } from '../../reflections/apply-decorators'; +import { Type } from '../../interfaces'; +import { IntentGuard } from '../foundation'; + +export const CONTROLLER_KEY = '@intentjs/controller_path'; +export const CONTROLLER_OPTIONS = '@intentjs/controller_options'; + +export const METHOD_KEY = '@intentjs/controller_method_key'; +export const METHOD_PATH = '@intentjs/controller_method_path'; + +export const GUARD_KEY = '@intentjs/controller_guards'; + +export type ControllerOptions = { + host?: string; +}; + +export enum HttpMethods { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', + OPTIONS = 'OPTIONS', + HEAD = 'HEAD', + DELETE = 'DELETE', + ANY = 'ANY', +} + +export function Controller(path?: string, options?: ControllerOptions) { + return applyDecorators(Injectable(), ControllerMetadata(path, options)); +} + +export function ControllerMetadata(path?: string, options?: ControllerOptions) { + return function (target: Function) { + Reflect.defineMetadata(CONTROLLER_KEY, path || '', target); + Reflect.defineMetadata(CONTROLLER_OPTIONS, options, target); + }; +} + +function createRouteDecorators( + method: HttpMethods, + path?: string, + options?: ControllerOptions, +): MethodDecorator { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, method, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export type RouteDecoratorType = ( + path?: string, + options?: ControllerOptions, +) => MethodDecorator; + +export const Get: RouteDecoratorType = (path, options) => + createRouteDecorators(HttpMethods.GET, path, options); + +export function Post(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.POST, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export function Put(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.PUT, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export function Patch(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.PATCH, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export function Delete(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.DELETE, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export function Options(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.OPTIONS, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export function Head(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.HEAD, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export function Any(path?: string, options?: ControllerOptions) { + return function (target: object, key?: string | symbol, descriptor?: any) { + Reflect.defineMetadata(METHOD_KEY, HttpMethods.ANY, target, key); + Reflect.defineMetadata(METHOD_PATH, path, target, key); + return descriptor; + }; +} + +export const UseGuard = (...guards: Type[]) => { + return function (target: object, key?: string | symbol, descriptor?: any) { + if (key) { + Reflect.defineMetadata(GUARD_KEY, guards, target, key); + return; + } + Reflect.defineMetadata(GUARD_KEY, guards, target); + return; + }; +}; diff --git a/packages/core/lib/rest/http-server/http-handler.ts b/packages/core/lib/rest/http-server/http-handler.ts new file mode 100644 index 0000000..99b4c37 --- /dev/null +++ b/packages/core/lib/rest/http-server/http-handler.ts @@ -0,0 +1,45 @@ +import { IntentExceptionFilter } from '../../exceptions'; +import { IntentGuard, IntentMiddleware } from '../foundation'; +import { ExecutionContext } from './contexts/execution-context'; +import { RouteArgType, RouteParamtypes } from './param-decorators'; +import { Response } from './response'; + +export class HttpRouteHandler { + constructor( + protected readonly middlewares: IntentMiddleware[], + protected readonly guards: IntentGuard[], + protected readonly handler: Function, + protected readonly exceptionFilter: IntentExceptionFilter, + ) {} + + async handle(context: ExecutionContext): Promise<[any, Response]> { + // for (const middleware of this.middlewares) { + // await middleware.use({}, {}); + // } + + try { + /** + * Handle the Guards + */ + for (const guard of this.guards) { + await guard.handle(context); + } + + /** + * Handle the request + */ + const responseFromHandler = await this.handler; + + if (responseFromHandler instanceof Response) { + return [, responseFromHandler]; + } else { + const response = context.switchToHttp().getResponse(); + response.body(responseFromHandler); + return [, response]; + } + } catch (e) { + const res = this.exceptionFilter.catch(context, e); + return [undefined, res]; + } + } +} diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts new file mode 100644 index 0000000..77ef361 --- /dev/null +++ b/packages/core/lib/rest/http-server/index.ts @@ -0,0 +1,10 @@ +export * from './contexts/execution-context'; +export * from './contexts/http-execution-context'; +export * from './decorators'; +export * from './http-handler'; +export * from './response'; +export * from './route-explorer'; +export * from './server'; +export * from './streamable-file'; +export * from './status-codes'; +export * from './param-decorators'; diff --git a/packages/core/lib/rest/http-server/interfaces.ts b/packages/core/lib/rest/http-server/interfaces.ts new file mode 100644 index 0000000..4184bcf --- /dev/null +++ b/packages/core/lib/rest/http-server/interfaces.ts @@ -0,0 +1,6 @@ +export type HttpRoute = { + method: string; + path: string; + httpHandler?: any; + middlewares?: any[]; +}; diff --git a/packages/core/lib/rest/http-server/methods.ts b/packages/core/lib/rest/http-server/methods.ts new file mode 100644 index 0000000..8a92b2c --- /dev/null +++ b/packages/core/lib/rest/http-server/methods.ts @@ -0,0 +1,11 @@ +export enum RequestMethod { + GET = 0, + POST = 1, + PUT = 2, + DELETE = 3, + PATCH = 4, + ALL = 5, + OPTIONS = 6, + HEAD = 7, + SEARCH = 8, +} diff --git a/packages/core/lib/rest/http-server/param-decorators.ts b/packages/core/lib/rest/http-server/param-decorators.ts new file mode 100644 index 0000000..fdbc508 --- /dev/null +++ b/packages/core/lib/rest/http-server/param-decorators.ts @@ -0,0 +1,96 @@ +import { ROUTE_ARGS } from './constants'; +import { ExecutionContext } from './contexts/execution-context'; + +export enum RouteParamtypes { + REQUEST = 0, + RESPONSE = 1, + NEXT = 2, + BODY = 3, + QUERY = 4, + PARAM = 5, + HEADERS = 6, + SESSION = 7, + FILE = 8, + FILES = 9, + HOST = 10, + IP = 11, + RAW_BODY = 12, + USER_AGENT = 13, + ACCEPTS = 14, +} + +export type RouteArgType = { + type: RouteParamtypes; + data: object | string | number; + handler: CustomRouteParamDecoratorHandler; +}; + +function createRouteParamDecorator(paramType: RouteParamtypes) { + return (data?: object | string | number): ParameterDecorator => + (target, key, index) => { + const args = + Reflect.getMetadata(ROUTE_ARGS, target.constructor, key) || []; + args[index] = { + type: paramType, + data: data, + }; + + Reflect.defineMetadata(ROUTE_ARGS, args, target.constructor, key); + }; +} + +type CustomRouteParamDecoratorHandler = ( + data: any, + context: ExecutionContext, +) => any; + +export function createParamDecorator( + handler: CustomRouteParamDecoratorHandler, +): ParameterDecorator { + return (data?: string | object | number) => (target, key, index) => { + const args = Reflect.getMetadata(ROUTE_ARGS, target.constructor, key) || []; + args[index] = { + data: data, + handler, + }; + + Reflect.defineMetadata(ROUTE_ARGS, args, target.constructor, key); + console.log(Reflect.getMetadata(ROUTE_ARGS, target.constructor, key) || []); + }; +} + +export const Req: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.REQUEST, +); + +export const Res: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.RESPONSE, +); + +export const Query: (key?: string) => ParameterDecorator = + createRouteParamDecorator(RouteParamtypes.QUERY); + +export const Param: (key?: string) => ParameterDecorator = + createRouteParamDecorator(RouteParamtypes.PARAM); + +export const Body: (key?: string) => ParameterDecorator = + createRouteParamDecorator(RouteParamtypes.BODY); + +export const Header: (key?: string) => ParameterDecorator = + createRouteParamDecorator(RouteParamtypes.HEADERS); + +export const IP: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.IP, +); + +export const UserAgent: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.USER_AGENT, +); + +export const Host: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.USER_AGENT, +); + +export const Accepts: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.ACCEPTS, +); diff --git a/packages/core/lib/rest/http-server/request.ts b/packages/core/lib/rest/http-server/request.ts new file mode 100644 index 0000000..9832db6 --- /dev/null +++ b/packages/core/lib/rest/http-server/request.ts @@ -0,0 +1,9 @@ +import uWS from 'uWebSockets.js'; +import { HttpMethods } from './decorators'; + +export class Request { + constructor( + private raw: uWS.HttpRequest, + private method: HttpMethods, + ) {} +} diff --git a/packages/core/lib/rest/http-server/response.ts b/packages/core/lib/rest/http-server/response.ts new file mode 100644 index 0000000..5095aed --- /dev/null +++ b/packages/core/lib/rest/http-server/response.ts @@ -0,0 +1,112 @@ +import { Response as HResponse } from 'hyper-express'; +import { StreamableFile } from './streamable-file'; +import { HttpStatus } from './status-codes'; +import { + EXTENSTION_TO_MIME, + SupportedExtentions, +} from '../../utils/extension-to-mime'; +import { isUndefined, Obj } from '../../utils'; + +export class Response { + private statusCode: HttpStatus; + private bodyData: any | StreamableFile; + private responseHeaders: Map; + + constructor() { + this.responseHeaders = new Map(); + } + + status(statusCode: HttpStatus): Response { + this.statusCode = statusCode; + return this; + } + + header(key: string, value: string): Response { + this.responseHeaders.set(key, value); + return this; + } + + type(type: SupportedExtentions): Response { + this.header('Content-Type', EXTENSTION_TO_MIME[type]); + return this; + } + + body(body: any): Response { + if (Obj.isObj(body) || Array.isArray(body)) { + this.type('json'); + this.bodyData = JSON.stringify(body); + } else { + this.bodyData = body; + } + return this; + } + + text(text: string): Response { + this.type('text'); + this.bodyData = text; + return this; + } + + stream(stream: StreamableFile): Response { + this.bodyData = stream; + return this; + } + + json(json: Record | Record[]): Response { + this.type('json'); + this.bodyData = JSON.stringify(json); + return this; + } + + html(html: string): Response { + this.type('html'); + this.responseHeaders.set('Content-Type', 'text/html'); + this.bodyData = html; + return this; + } + + notFound(): Response { + this.statusCode = HttpStatus.NOT_FOUND; + return this; + } + + redirect(): Response { + this.statusCode = HttpStatus.FOUND; + this.responseHeaders['']; + return this; + } + + reply(res: HResponse) { + this.statusCode && res.status(this.statusCode); + for (const [key, value] of this.responseHeaders.entries()) { + res.setHeader(key, value); + } + if (this.bodyData instanceof StreamableFile) { + const headers = this.bodyData.getHeaders(); + if ( + isUndefined(res.getHeader('Content-Type')) && + !isUndefined(headers.type) + ) { + res.setHeader('Content-Type', headers.type); + } + if ( + isUndefined(res.getHeader('Content-Disposition')) && + !isUndefined(headers.type) + ) { + res.setHeader('Content-Disposition', headers.disposition); + } + + if ( + isUndefined(res.getHeader('Content-Length')) && + !isUndefined(headers.length) + ) { + res.setHeader('Content-Length', headers.length + ''); + } + + // this.bodyData.getStream().once('error') + res.stream(this.bodyData.getStream()); + } + + return res.send(this.bodyData); + } +} diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts new file mode 100644 index 0000000..b63837d --- /dev/null +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -0,0 +1,165 @@ +import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; +import { + CONTROLLER_KEY, + GUARD_KEY, + METHOD_KEY, + METHOD_PATH, +} from './decorators'; +import { join } from 'path'; +import { HttpRoute } from './interfaces'; +import { Request, Response as HResponse } from 'hyper-express'; +import { HttpExecutionContext } from './contexts/http-execution-context'; +import { HttpRouteHandler } from './http-handler'; +import { Response } from './response'; +import { ExecutionContext } from './contexts/execution-context'; +import { IntentExceptionFilter } from '../../exceptions'; +import { IntentGuard } from '../foundation'; +import { Type } from '../../interfaces'; +import { ROUTE_ARGS } from './constants'; +import { RouteArgType } from './param-decorators'; + +export class RouteExplorer { + async exploreFullRoutes( + discoveryService: DiscoveryService, + metadataScanner: MetadataScanner, + moduleRef: ModuleRef, + errorHandler: IntentExceptionFilter, + ): Promise { + const routes = []; + const providers = discoveryService.getProviders(); + for (const provider of providers) { + const { instance } = provider; + // if ( + // !instance || + // typeof instance === 'string' || + // !Object.getPrototypeOf(instance) + // ) { + // return; + // } + + const methodNames = metadataScanner.getAllMethodNames(instance); + for (const methodName of methodNames) { + const route = await this.scanFullRoute( + instance, + methodName, + moduleRef, + errorHandler, + ); + route && routes.push(route); + } + } + + return routes; + } + + explorePlainRoutes( + discoveryService: DiscoveryService, + metadataScanner: MetadataScanner, + ): HttpRoute[] { + const routes = []; + const providers = discoveryService.getProviders(); + for (const provider of providers) { + const { instance } = provider; + // if ( + // !instance || + // typeof instance === 'string' || + // !Object.getPrototypeOf(instance) + // ) { + // return; + // } + + const methodNames = metadataScanner.getAllMethodNames(instance); + for (const methodName of methodNames) { + const route = this.scanPlainRoute(instance, methodName); + route && routes.push(route); + } + } + + return routes; + } + + scanPlainRoute(instance: any, key: string): Record { + const controllerKey = Reflect.getMetadata( + CONTROLLER_KEY, + instance.constructor, + ); + + if (!controllerKey) return; + + const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); + const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); + + const fullHttpPath = join(controllerKey, methodPath); + return { method: pathMethod, path: fullHttpPath }; + } + + async scanFullRoute( + instance: any, + key: string, + moduleRef: ModuleRef, + errorHandler: IntentExceptionFilter, + ): Promise { + const controllerKey = Reflect.getMetadata( + CONTROLLER_KEY, + instance.constructor, + ); + if (!controllerKey) return; + const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); + const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); + const methodRef = instance[key]; + const controllerGuards = Reflect.getMetadata( + GUARD_KEY, + instance.constructor, + ); + + const methodGuards = Reflect.getMetadata(GUARD_KEY, instance, key); + + const composedGuardTypes = [ + ...(controllerGuards || []), + ...(methodGuards || []), + ] as Type[]; + + const composedGuards = []; + for (const guardType of composedGuardTypes) { + composedGuards.push(await moduleRef.create(guardType)); + } + + const middlewares = []; + + const routeArgs = Reflect.getMetadata( + ROUTE_ARGS, + instance.constructor, + key, + ) as RouteArgType[]; + + const cb = async (hReq: Request, hRes: HResponse) => { + const httpContext = new HttpExecutionContext(hReq, new Response()); + const context = new ExecutionContext(httpContext, instance, methodRef); + + const args = []; + for (const routeArg of routeArgs) { + if (routeArg.handler) { + args.push(routeArg.handler(routeArg.data, context)); + } else { + args.push(httpContext.getInjectableValueFromArgType(routeArg)); + } + } + + const handler = new HttpRouteHandler( + middlewares, + composedGuards, + instance[key].apply(instance, args), + errorHandler, + ); + + const [error, res] = await handler.handle(context); + return res.reply(hRes); + }; + + return { + method: pathMethod, + path: join(controllerKey, methodPath), + httpHandler: cb, + }; + } +} diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts new file mode 100644 index 0000000..7c3a3a9 --- /dev/null +++ b/packages/core/lib/rest/http-server/server.ts @@ -0,0 +1,52 @@ +import { HttpMethods } from './decorators'; +import HyperExpress from 'hyper-express'; +import { HttpRoute } from './interfaces'; + +export class HyperServer { + protected hyper: HyperExpress.Server; + + constructor() { + this.hyper = new HyperExpress.Server(); + } + + async build(routes: HttpRoute[]): Promise { + for (const route of routes) { + const { path, httpHandler } = route; + switch (route.method) { + case HttpMethods.GET: + this.hyper.get(path, httpHandler); + break; + + case HttpMethods.POST: + this.hyper.post(path, httpHandler); + break; + + case HttpMethods.DELETE: + this.hyper.delete(path, httpHandler); + break; + + case HttpMethods.HEAD: + this.hyper.head(path, httpHandler); + break; + + case HttpMethods.PUT: + this.hyper.put(path, httpHandler); + break; + + case HttpMethods.PATCH: + this.hyper.patch(path, httpHandler); + break; + + case HttpMethods.OPTIONS: + this.hyper.options(path, httpHandler); + break; + + case HttpMethods.ANY: + this.hyper.any(path, httpHandler); + break; + } + } + + return this.hyper; + } +} diff --git a/packages/core/lib/rest/foundation/statusCodes.ts b/packages/core/lib/rest/http-server/status-codes.ts similarity index 100% rename from packages/core/lib/rest/foundation/statusCodes.ts rename to packages/core/lib/rest/http-server/status-codes.ts diff --git a/packages/core/lib/rest/http-server/streamable-file.ts b/packages/core/lib/rest/http-server/streamable-file.ts new file mode 100644 index 0000000..d3334ab --- /dev/null +++ b/packages/core/lib/rest/http-server/streamable-file.ts @@ -0,0 +1,57 @@ +import { Readable } from 'stream'; +import { types } from 'util'; + +export type StreamableFileOptions = { + /** + * The value that will be used for the `Content-Type` response header. + */ + type?: string; + + /** + * The value that will be used for the `Content-Disposition` response header. + */ + disposition?: string; + + /** + * The value that will be used for the `Content-Length` response header. + */ + length?: number; +}; + +export class StreamableFile { + private readonly stream: Readable; + + constructor(buffer: Uint8Array, options?: StreamableFileOptions); + constructor(readable: Readable, options?: StreamableFileOptions); + constructor( + bufferOrReadable: Uint8Array | Readable, + readonly options: StreamableFileOptions = {}, + ) { + if (types.isUint8Array(bufferOrReadable)) { + this.stream = new Readable(); + this.stream.push(bufferOrReadable); + this.stream.push(null); + this.options.length ??= bufferOrReadable.length; + } else if (bufferOrReadable.pipe) { + this.stream = bufferOrReadable; + } + } + + getStream(): Readable { + return this.stream; + } + + getHeaders() { + const { + type = 'application/octet-stream', + disposition = undefined, + length = undefined, + } = this.options; + + return { + type, + disposition, + length, + }; + } +} diff --git a/packages/core/lib/rest/index.ts b/packages/core/lib/rest/index.ts index 42c715f..de144d6 100644 --- a/packages/core/lib/rest/index.ts +++ b/packages/core/lib/rest/index.ts @@ -1,9 +1,8 @@ export * from './interceptors/timeout'; export * from './interfaces'; -export * from './decorators'; export { Res } from '@nestjs/common'; -export { Response } from 'express'; export * from './foundation'; export * from './middlewares/cors'; export * from './middlewares/helmet'; export * from './foundation'; +export * from './http-server'; diff --git a/packages/core/lib/serviceProvider.ts b/packages/core/lib/serviceProvider.ts index 54b50c7..c2cc680 100644 --- a/packages/core/lib/serviceProvider.ts +++ b/packages/core/lib/serviceProvider.ts @@ -20,6 +20,7 @@ import { StorageService } from './storage/service'; import { BuildProjectCommand } from './dev-server/build'; import { DevServerCommand } from './dev-server/serve'; import { CONFIG_FACTORY, ConfigBuilder, ConfigService } from './config'; +import { ListRouteCommand } from './console/commands/route-list'; export const IntentProvidersFactory = ( config: any[], @@ -43,6 +44,7 @@ export const IntentProvidersFactory = ( QueueService, QueueConsoleCommands, QueueMetadata, + ListRouteCommand, // CodegenCommand, // CodegenService, ViewConfigCommand, diff --git a/packages/core/lib/utils/extension-to-mime.ts b/packages/core/lib/utils/extension-to-mime.ts new file mode 100644 index 0000000..ecf9bf4 --- /dev/null +++ b/packages/core/lib/utils/extension-to-mime.ts @@ -0,0 +1,2405 @@ +export type SupportedExtentions = + | '1km' + | '3dml' + | '3ds' + | '3g2' + | '3gp' + | '3gpp' + | '3mf' + | '7z' + | 'aab' + | 'aac' + | 'aam' + | 'aas' + | 'abw' + | 'ac' + | 'acc' + | 'ace' + | 'acu' + | 'acutc' + | 'adp' + | 'adts' + | 'aep' + | 'afm' + | 'afp' + | 'age' + | 'ahead' + | 'ai' + | 'aif' + | 'aifc' + | 'aiff' + | 'air' + | 'ait' + | 'ami' + | 'aml' + | 'amlx' + | 'amr' + | 'apk' + | 'apng' + | 'appcache' + | 'appinstaller' + | 'application' + | 'appx' + | 'appxbundle' + | 'apr' + | 'arc' + | 'arj' + | 'asc' + | 'asf' + | 'asm' + | 'aso' + | 'asx' + | 'atc' + | 'atom' + | 'atomcat' + | 'atomdeleted' + | 'atomsvc' + | 'atx' + | 'au' + | 'avci' + | 'avcs' + | 'avi' + | 'avif' + | 'aw' + | 'azf' + | 'azs' + | 'azv' + | 'azw' + | 'b16' + | 'bat' + | 'bcpio' + | 'bdf' + | 'bdm' + | 'bdoc' + | 'bed' + | 'bh2' + | 'bin' + | 'blb' + | 'blorb' + | 'bmi' + | 'bmml' + | 'bmp' + | 'book' + | 'box' + | 'boz' + | 'bpk' + | 'bsp' + | 'btf' + | 'btif' + | 'buffer' + | 'bz' + | 'bz2' + | 'c' + | 'c11amc' + | 'c11amz' + | 'c4d' + | 'c4f' + | 'c4g' + | 'c4p' + | 'c4u' + | 'cab' + | 'caf' + | 'cap' + | 'car' + | 'cat' + | 'cb7' + | 'cba' + | 'cbr' + | 'cbt' + | 'cbz' + | 'cc' + | 'cco' + | 'cct' + | 'ccxml' + | 'cdbcmsg' + | 'cdf' + | 'cdfx' + | 'cdkey' + | 'cdmia' + | 'cdmic' + | 'cdmid' + | 'cdmio' + | 'cdmiq' + | 'cdx' + | 'cdxml' + | 'cdy' + | 'cer' + | 'cfs' + | 'cgm' + | 'chat' + | 'chm' + | 'chrt' + | 'cif' + | 'cii' + | 'cil' + | 'cjs' + | 'cla' + | 'class' + | 'cld' + | 'clkk' + | 'clkp' + | 'clkt' + | 'clkw' + | 'clkx' + | 'clp' + | 'cmc' + | 'cmdf' + | 'cml' + | 'cmp' + | 'cmx' + | 'cod' + | 'coffee' + | 'com' + | 'conf' + | 'cpio' + | 'cpl' + | 'cpp' + | 'cpt' + | 'crd' + | 'crl' + | 'crt' + | 'crx' + | 'cryptonote' + | 'csh' + | 'csl' + | 'csml' + | 'csp' + | 'css' + | 'cst' + | 'csv' + | 'cu' + | 'curl' + | 'cwl' + | 'cww' + | 'cxt' + | 'cxx' + | 'dae' + | 'daf' + | 'dart' + | 'dataless' + | 'davmount' + | 'dbf' + | 'dbk' + | 'dcr' + | 'dcurl' + | 'dd2' + | 'ddd' + | 'ddf' + | 'dds' + | 'deb' + | 'def' + | 'deploy' + | 'der' + | 'dfac' + | 'dgc' + | 'dib' + | 'dic' + | 'dir' + | 'dis' + | 'disposition-notification' + | 'dist' + | 'distz' + | 'djv' + | 'djvu' + | 'dll' + | 'dmg' + | 'dmp' + | 'dms' + | 'dna' + | 'doc' + | 'docm' + | 'docx' + | 'dot' + | 'dotm' + | 'dotx' + | 'dp' + | 'dpg' + | 'dpx' + | 'dra' + | 'drle' + | 'dsc' + | 'dssc' + | 'dtb' + | 'dtd' + | 'dts' + | 'dtshd' + | 'dump' + | 'dvb' + | 'dvi' + | 'dwd' + | 'dwf' + | 'dwg' + | 'dxf' + | 'dxp' + | 'dxr' + | 'ear' + | 'ecelp4800' + | 'ecelp7470' + | 'ecelp9600' + | 'ecma' + | 'edm' + | 'edx' + | 'efif' + | 'ei6' + | 'elc' + | 'emf' + | 'eml' + | 'emma' + | 'emotionml' + | 'emz' + | 'eol' + | 'eot' + | 'eps' + | 'epub' + | 'es3' + | 'esa' + | 'esf' + | 'et3' + | 'etx' + | 'eva' + | 'evy' + | 'exe' + | 'exi' + | 'exp' + | 'exr' + | 'ext' + | 'ez' + | 'ez2' + | 'ez3' + | 'f' + | 'f4v' + | 'f77' + | 'f90' + | 'fbs' + | 'fcdt' + | 'fcs' + | 'fdf' + | 'fdt' + | 'fe_launch' + | 'fg5' + | 'fgd' + | 'fh' + | 'fh4' + | 'fh5' + | 'fh7' + | 'fhc' + | 'fig' + | 'fits' + | 'flac' + | 'fli' + | 'flo' + | 'flv' + | 'flw' + | 'flx' + | 'fly' + | 'fm' + | 'fnc' + | 'fo' + | 'for' + | 'fpx' + | 'frame' + | 'fsc' + | 'fst' + | 'ftc' + | 'fti' + | 'fvt' + | 'fxp' + | 'fxpl' + | 'fzs' + | 'g2w' + | 'g3' + | 'g3w' + | 'gac' + | 'gam' + | 'gbr' + | 'gca' + | 'gdl' + | 'gdoc' + | 'ged' + | 'geo' + | 'geojson' + | 'gex' + | 'ggb' + | 'ggt' + | 'ghf' + | 'gif' + | 'gim' + | 'glb' + | 'gltf' + | 'gml' + | 'gmx' + | 'gnumeric' + | 'gph' + | 'gpx' + | 'gqf' + | 'gqs' + | 'gram' + | 'gramps' + | 'gre' + | 'grv' + | 'grxml' + | 'gsf' + | 'gsheet' + | 'gslides' + | 'gtar' + | 'gtm' + | 'gtw' + | 'gv' + | 'gxf' + | 'gxt' + | 'gz' + | 'h' + | 'h261' + | 'h263' + | 'h264' + | 'hal' + | 'hbci' + | 'hbs' + | 'hdd' + | 'hdf' + | 'heic' + | 'heics' + | 'heif' + | 'heifs' + | 'hej2' + | 'held' + | 'hh' + | 'hjson' + | 'hlp' + | 'hpgl' + | 'hpid' + | 'hps' + | 'hqx' + | 'hsj2' + | 'htc' + | 'htke' + | 'htm' + | 'html' + | 'hvd' + | 'hvp' + | 'hvs' + | 'i2g' + | 'icc' + | 'ice' + | 'icm' + | 'ico' + | 'ics' + | 'ief' + | 'ifb' + | 'ifm' + | 'iges' + | 'igl' + | 'igm' + | 'igs' + | 'igx' + | 'iif' + | 'img' + | 'imp' + | 'ims' + | 'in' + | 'ini' + | 'ink' + | 'inkml' + | 'install' + | 'iota' + | 'ipfix' + | 'ipk' + | 'irm' + | 'irp' + | 'iso' + | 'itp' + | 'its' + | 'ivp' + | 'ivu' + | 'jad' + | 'jade' + | 'jam' + | 'jar' + | 'jardiff' + | 'java' + | 'jhc' + | 'jisp' + | 'jls' + | 'jlt' + | 'jng' + | 'jnlp' + | 'joda' + | 'jp2' + | 'jpe' + | 'jpeg' + | 'jpf' + | 'jpg' + | 'jpg2' + | 'jpgm' + | 'jpgv' + | 'jph' + | 'jpm' + | 'jpx' + | 'js' + | 'json' + | 'json5' + | 'jsonld' + | 'jsonml' + | 'jsx' + | 'jt' + | 'jxr' + | 'jxra' + | 'jxrs' + | 'jxs' + | 'jxsc' + | 'jxsi' + | 'jxss' + | 'kar' + | 'karbon' + | 'kdbx' + | 'key' + | 'kfo' + | 'kia' + | 'kml' + | 'kmz' + | 'kne' + | 'knp' + | 'kon' + | 'kpr' + | 'kpt' + | 'kpxx' + | 'ksp' + | 'ktr' + | 'ktx' + | 'ktx2' + | 'ktz' + | 'kwd' + | 'kwt' + | 'lasxml' + | 'latex' + | 'lbd' + | 'lbe' + | 'les' + | 'less' + | 'lgr' + | 'lha' + | 'link66' + | 'list' + | 'list3820' + | 'listafp' + | 'litcoffee' + | 'lnk' + | 'log' + | 'lostxml' + | 'lrf' + | 'lrm' + | 'ltf' + | 'lua' + | 'luac' + | 'lvp' + | 'lwp' + | 'lzh' + | 'm13' + | 'm14' + | 'm1v' + | 'm21' + | 'm2a' + | 'm2v' + | 'm3a' + | 'm3u' + | 'm3u8' + | 'm4a' + | 'm4p' + | 'm4s' + | 'm4u' + | 'm4v' + | 'ma' + | 'mads' + | 'maei' + | 'mag' + | 'maker' + | 'man' + | 'manifest' + | 'map' + | 'mar' + | 'markdown' + | 'mathml' + | 'mb' + | 'mbk' + | 'mbox' + | 'mc1' + | 'mcd' + | 'mcurl' + | 'md' + | 'mdb' + | 'mdi' + | 'mdx' + | 'me' + | 'mesh' + | 'meta4' + | 'metalink' + | 'mets' + | 'mfm' + | 'mft' + | 'mgp' + | 'mgz' + | 'mid' + | 'midi' + | 'mie' + | 'mif' + | 'mime' + | 'mj2' + | 'mjp2' + | 'mjs' + | 'mk3d' + | 'mka' + | 'mkd' + | 'mks' + | 'mkv' + | 'mlp' + | 'mmd' + | 'mmf' + | 'mml' + | 'mmr' + | 'mng' + | 'mny' + | 'mobi' + | 'mods' + | 'mov' + | 'movie' + | 'mp2' + | 'mp21' + | 'mp2a' + | 'mp3' + | 'mp4' + | 'mp4a' + | 'mp4s' + | 'mp4v' + | 'mpc' + | 'mpd' + | 'mpe' + | 'mpeg' + | 'mpf' + | 'mpg' + | 'mpg4' + | 'mpga' + | 'mpkg' + | 'mpm' + | 'mpn' + | 'mpp' + | 'mpt' + | 'mpy' + | 'mqy' + | 'mrc' + | 'mrcx' + | 'ms' + | 'mscml' + | 'mseed' + | 'mseq' + | 'msf' + | 'msg' + | 'msh' + | 'msi' + | 'msix' + | 'msixbundle' + | 'msl' + | 'msm' + | 'msp' + | 'msty' + | 'mtl' + | 'mts' + | 'mus' + | 'musd' + | 'musicxml' + | 'mvb' + | 'mvt' + | 'mwf' + | 'mxf' + | 'mxl' + | 'mxmf' + | 'mxml' + | 'mxs' + | 'mxu' + | 'n-gage' + | 'n3' + | 'nb' + | 'nbp' + | 'nc' + | 'ncx' + | 'nfo' + | 'ngdat' + | 'nitf' + | 'nlu' + | 'nml' + | 'nnd' + | 'nns' + | 'nnw' + | 'npx' + | 'nq' + | 'nsc' + | 'nsf' + | 'nt' + | 'ntf' + | 'numbers' + | 'nzb' + | 'oa2' + | 'oa3' + | 'oas' + | 'obd' + | 'obgx' + | 'obj' + | 'oda' + | 'odb' + | 'odc' + | 'odf' + | 'odft' + | 'odg' + | 'odi' + | 'odm' + | 'odp' + | 'ods' + | 'odt' + | 'oga' + | 'ogex' + | 'ogg' + | 'ogv' + | 'ogx' + | 'omdoc' + | 'onepkg' + | 'onetmp' + | 'onetoc' + | 'onetoc2' + | 'opf' + | 'opml' + | 'oprc' + | 'opus' + | 'org' + | 'osf' + | 'osfpvg' + | 'osm' + | 'otc' + | 'otf' + | 'otg' + | 'oth' + | 'oti' + | 'otp' + | 'ots' + | 'ott' + | 'ova' + | 'ovf' + | 'owl' + | 'oxps' + | 'oxt' + | 'p' + | 'p10' + | 'p12' + | 'p7b' + | 'p7c' + | 'p7m' + | 'p7r' + | 'p7s' + | 'p8' + | 'pac' + | 'pages' + | 'pas' + | 'paw' + | 'pbd' + | 'pbm' + | 'pcap' + | 'pcf' + | 'pcl' + | 'pclxl' + | 'pct' + | 'pcurl' + | 'pcx' + | 'pdb' + | 'pde' + | 'pdf' + | 'pem' + | 'pfa' + | 'pfb' + | 'pfm' + | 'pfr' + | 'pfx' + | 'pgm' + | 'pgn' + | 'pgp' + | 'php' + | 'pic' + | 'pkg' + | 'pki' + | 'pkipath' + | 'pkpass' + | 'pl' + | 'plb' + | 'plc' + | 'plf' + | 'pls' + | 'pm' + | 'pml' + | 'png' + | 'pnm' + | 'portpkg' + | 'pot' + | 'potm' + | 'potx' + | 'ppam' + | 'ppd' + | 'ppm' + | 'pps' + | 'ppsm' + | 'ppsx' + | 'ppt' + | 'pptm' + | 'pptx' + | 'pqa' + | 'prc' + | 'pre' + | 'prf' + | 'provx' + | 'ps' + | 'psb' + | 'psd' + | 'psf' + | 'pskcxml' + | 'pti' + | 'ptid' + | 'pub' + | 'pvb' + | 'pwn' + | 'pya' + | 'pyo' + | 'pyox' + | 'pyv' + | 'qam' + | 'qbo' + | 'qfx' + | 'qps' + | 'qt' + | 'qwd' + | 'qwt' + | 'qxb' + | 'qxd' + | 'qxl' + | 'qxt' + | 'ra' + | 'ram' + | 'raml' + | 'rapd' + | 'rar' + | 'ras' + | 'rcprofile' + | 'rdf' + | 'rdz' + | 'relo' + | 'rep' + | 'res' + | 'rgb' + | 'rif' + | 'rip' + | 'ris' + | 'rl' + | 'rlc' + | 'rld' + | 'rm' + | 'rmi' + | 'rmp' + | 'rms' + | 'rmvb' + | 'rnc' + | 'rng' + | 'roa' + | 'roff' + | 'rp9' + | 'rpm' + | 'rpss' + | 'rpst' + | 'rq' + | 'rs' + | 'rsat' + | 'rsd' + | 'rsheet' + | 'rss' + | 'rtf' + | 'rtx' + | 'run' + | 'rusd' + | 's' + | 's3m' + | 'saf' + | 'sass' + | 'sbml' + | 'sc' + | 'scd' + | 'scm' + | 'scq' + | 'scs' + | 'scss' + | 'scurl' + | 'sda' + | 'sdc' + | 'sdd' + | 'sdkd' + | 'sdkm' + | 'sdp' + | 'sdw' + | 'sea' + | 'see' + | 'seed' + | 'sema' + | 'semd' + | 'semf' + | 'senmlx' + | 'sensmlx' + | 'ser' + | 'setpay' + | 'setreg' + | 'sfd-hdstx' + | 'sfs' + | 'sfv' + | 'sgi' + | 'sgl' + | 'sgm' + | 'sgml' + | 'sh' + | 'shar' + | 'shex' + | 'shf' + | 'shtml' + | 'sid' + | 'sieve' + | 'sig' + | 'sil' + | 'silo' + | 'sis' + | 'sisx' + | 'sit' + | 'sitx' + | 'siv' + | 'skd' + | 'skm' + | 'skp' + | 'skt' + | 'sldm' + | 'sldx' + | 'slim' + | 'slm' + | 'sls' + | 'slt' + | 'sm' + | 'smf' + | 'smi' + | 'smil' + | 'smv' + | 'smzip' + | 'snd' + | 'snf' + | 'so' + | 'spc' + | 'spdx' + | 'spf' + | 'spl' + | 'spot' + | 'spp' + | 'spq' + | 'spx' + | 'sql' + | 'src' + | 'srt' + | 'sru' + | 'srx' + | 'ssdl' + | 'sse' + | 'ssf' + | 'ssml' + | 'st' + | 'stc' + | 'std' + | 'stf' + | 'sti' + | 'stk' + | 'stl' + | 'stpx' + | 'stpxz' + | 'stpz' + | 'str' + | 'stw' + | 'styl' + | 'stylus' + | 'sub' + | 'sus' + | 'susp' + | 'sv4cpio' + | 'sv4crc' + | 'svc' + | 'svd' + | 'svg' + | 'svgz' + | 'swa' + | 'swf' + | 'swi' + | 'swidtag' + | 'sxc' + | 'sxd' + | 'sxg' + | 'sxi' + | 'sxm' + | 'sxw' + | 't' + | 't3' + | 't38' + | 'taglet' + | 'tao' + | 'tap' + | 'tar' + | 'tcap' + | 'tcl' + | 'td' + | 'teacher' + | 'tei' + | 'teicorpus' + | 'tex' + | 'texi' + | 'texinfo' + | 'text' + | 'tfi' + | 'tfm' + | 'tfx' + | 'tga' + | 'thmx' + | 'tif' + | 'tiff' + | 'tk' + | 'tmo' + | 'toml' + | 'torrent' + | 'tpl' + | 'tpt' + | 'tr' + | 'tra' + | 'trig' + | 'trm' + | 'ts' + | 'tsd' + | 'tsv' + | 'ttc' + | 'ttf' + | 'ttl' + | 'ttml' + | 'twd' + | 'twds' + | 'txd' + | 'txf' + | 'txt' + | 'u32' + | 'u3d' + | 'u8dsn' + | 'u8hdr' + | 'u8mdn' + | 'u8msg' + | 'ubj' + | 'udeb' + | 'ufd' + | 'ufdl' + | 'ulx' + | 'umj' + | 'unityweb' + | 'uo' + | 'uoml' + | 'uri' + | 'uris' + | 'urls' + | 'usda' + | 'usdz' + | 'ustar' + | 'utz' + | 'uu' + | 'uva' + | 'uvd' + | 'uvf' + | 'uvg' + | 'uvh' + | 'uvi' + | 'uvm' + | 'uvp' + | 'uvs' + | 'uvt' + | 'uvu' + | 'uvv' + | 'uvva' + | 'uvvd' + | 'uvvf' + | 'uvvg' + | 'uvvh' + | 'uvvi' + | 'uvvm' + | 'uvvp' + | 'uvvs' + | 'uvvt' + | 'uvvu' + | 'uvvv' + | 'uvvx' + | 'uvvz' + | 'uvx' + | 'uvz' + | 'vbox' + | 'vbox-extpack' + | 'vcard' + | 'vcd' + | 'vcf' + | 'vcg' + | 'vcs' + | 'vcx' + | 'vdi' + | 'vds' + | 'vhd' + | 'vis' + | 'viv' + | 'vmdk' + | 'vob' + | 'vor' + | 'vox' + | 'vrml' + | 'vsd' + | 'vsf' + | 'vss' + | 'vst' + | 'vsw' + | 'vtf' + | 'vtt' + | 'vtu' + | 'vxml' + | 'w3d' + | 'wad' + | 'wadl' + | 'war' + | 'wasm' + | 'wav' + | 'wax' + | 'wbmp' + | 'wbs' + | 'wbxml' + | 'wcm' + | 'wdb' + | 'wdp' + | 'weba' + | 'webapp' + | 'webm' + | 'webmanifest' + | 'webp' + | 'wg' + | 'wgsl' + | 'wgt' + | 'wif' + | 'wks' + | 'wm' + | 'wma' + | 'wmd' + | 'wmf' + | 'wml' + | 'wmlc' + | 'wmls' + | 'wmlsc' + | 'wmv' + | 'wmx' + | 'wmz' + | 'woff' + | 'woff2' + | 'wpd' + | 'wpl' + | 'wps' + | 'wqd' + | 'wri' + | 'wrl' + | 'wsc' + | 'wsdl' + | 'wspolicy' + | 'wtb' + | 'wvx' + | 'x32' + | 'x3d' + | 'x3db' + | 'x3dbz' + | 'x3dv' + | 'x3dvz' + | 'x3dz' + | 'x_b' + | 'x_t' + | 'xaml' + | 'xap' + | 'xar' + | 'xav' + | 'xbap' + | 'xbd' + | 'xbm' + | 'xca' + | 'xcs' + | 'xdf' + | 'xdm' + | 'xdp' + | 'xdssc' + | 'xdw' + | 'xel' + | 'xenc' + | 'xer' + | 'xfdf' + | 'xfdl' + | 'xht' + | 'xhtm' + | 'xhtml' + | 'xhvml' + | 'xif' + | 'xla' + | 'xlam' + | 'xlc' + | 'xlf' + | 'xlm' + | 'xls' + | 'xlsb' + | 'xlsm' + | 'xlsx' + | 'xlt' + | 'xltm' + | 'xltx' + | 'xlw' + | 'xm' + | 'xml' + | 'xns' + | 'xo' + | 'xop' + | 'xpi' + | 'xpl' + | 'xpm' + | 'xpr' + | 'xps' + | 'xpw' + | 'xpx' + | 'xsd' + | 'xsf' + | 'xsl' + | 'xslt' + | 'xsm' + | 'xspf' + | 'xul' + | 'xvm' + | 'xvml' + | 'xwd' + | 'xyz' + | 'xz' + | 'yaml' + | 'yang' + | 'yin' + | 'yml' + | 'ymp' + | 'z1' + | 'z2' + | 'z3' + | 'z4' + | 'z5' + | 'z6' + | 'z7' + | 'z8' + | 'zaz' + | 'zip' + | 'zir' + | 'zirz' + | 'zmm'; + +export const EXTENSTION_TO_MIME = { + // '123':'application/vnd.lotus-1-2-3', + '1km': 'application/vnd.1000minds.decision-model+xml', + '3dml': 'text/vnd.in3d.3dml', + '3ds': 'image/x-3ds', + '3g2': 'video/3gpp2', + '3gp': 'video/3gpp', + '3gpp': 'video/3gpp', + '3mf': 'model/3mf', + '7z': 'application/x-7z-compressed', + aab: 'application/x-authorware-bin', + aac: 'audio/x-aac', + aam: 'application/x-authorware-map', + aas: 'application/x-authorware-seg', + abw: 'application/x-abiword', + ac: 'application/vnd.nokia.n-gage.ac+xml', + acc: 'application/vnd.americandynamics.acc', + ace: 'application/x-ace-compressed', + acu: 'application/vnd.acucobol', + acutc: 'application/vnd.acucorp', + adp: 'audio/adpcm', + adts: 'audio/aac', + aep: 'application/vnd.audiograph', + afm: 'application/x-font-type1', + afp: 'application/vnd.ibm.modcap', + age: 'application/vnd.age', + ahead: 'application/vnd.ahead.space', + ai: 'application/postscript', + aif: 'audio/x-aiff', + aifc: 'audio/x-aiff', + aiff: 'audio/x-aiff', + air: 'application/vnd.adobe.air-application-installer-package+zip', + ait: 'application/vnd.dvb.ait', + ami: 'application/vnd.amiga.ami', + aml: 'application/automationml-aml+xml', + amlx: 'application/automationml-amlx+zip', + amr: 'audio/amr', + apk: 'application/vnd.android.package-archive', + apng: 'image/apng', + appcache: 'text/cache-manifest', + appinstaller: 'application/appinstaller', + application: 'application/x-ms-application', + appx: 'application/appx', + appxbundle: 'application/appxbundle', + apr: 'application/vnd.lotus-approach', + arc: 'application/x-freearc', + arj: 'application/x-arj', + asc: 'application/pgp-signature', + asf: 'video/x-ms-asf', + asm: 'text/x-asm', + aso: 'application/vnd.accpac.simply.aso', + asx: 'video/x-ms-asf', + atc: 'application/vnd.acucorp', + atom: 'application/atom+xml', + atomcat: 'application/atomcat+xml', + atomdeleted: 'application/atomdeleted+xml', + atomsvc: 'application/atomsvc+xml', + atx: 'application/vnd.antix.game-component', + au: 'audio/basic', + avci: 'image/avci', + avcs: 'image/avcs', + avi: 'video/x-msvideo', + avif: 'image/avif', + aw: 'application/applixware', + azf: 'application/vnd.airzip.filesecure.azf', + azs: 'application/vnd.airzip.filesecure.azs', + azv: 'image/vnd.airzip.accelerator.azv', + azw: 'application/vnd.amazon.ebook', + b16: 'image/vnd.pco.b16', + bat: 'application/x-msdownload', + bcpio: 'application/x-bcpio', + bdf: 'application/x-font-bdf', + bdm: 'application/vnd.syncml.dm+wbxml', + bdoc: 'application/x-bdoc', + bed: 'application/vnd.realvnc.bed', + bh2: 'application/vnd.fujitsu.oasysprs', + bin: 'application/octet-stream', + blb: 'application/x-blorb', + blorb: 'application/x-blorb', + bmi: 'application/vnd.bmi', + bmml: 'application/vnd.balsamiq.bmml+xml', + bmp: 'image/x-ms-bmp', + book: 'application/vnd.framemaker', + box: 'application/vnd.previewsystems.box', + boz: 'application/x-bzip2', + bpk: 'application/octet-stream', + bsp: 'model/vnd.valve.source.compiled-map', + btf: 'image/prs.btif', + btif: 'image/prs.btif', + buffer: 'application/octet-stream', + bz: 'application/x-bzip', + bz2: 'application/x-bzip2', + c: 'text/x-c', + c11amc: 'application/vnd.cluetrust.cartomobile-config', + c11amz: 'application/vnd.cluetrust.cartomobile-config-pkg', + c4d: 'application/vnd.clonk.c4group', + c4f: 'application/vnd.clonk.c4group', + c4g: 'application/vnd.clonk.c4group', + c4p: 'application/vnd.clonk.c4group', + c4u: 'application/vnd.clonk.c4group', + cab: 'application/vnd.ms-cab-compressed', + caf: 'audio/x-caf', + cap: 'application/vnd.tcpdump.pcap', + car: 'application/vnd.curl.car', + cat: 'application/vnd.ms-pki.seccat', + cb7: 'application/x-cbr', + cba: 'application/x-cbr', + cbr: 'application/x-cbr', + cbt: 'application/x-cbr', + cbz: 'application/x-cbr', + cc: 'text/x-c', + cco: 'application/x-cocoa', + cct: 'application/x-director', + ccxml: 'application/ccxml+xml', + cdbcmsg: 'application/vnd.contact.cmsg', + cdf: 'application/x-netcdf', + cdfx: 'application/cdfx+xml', + cdkey: 'application/vnd.mediastation.cdkey', + cdmia: 'application/cdmi-capability', + cdmic: 'application/cdmi-container', + cdmid: 'application/cdmi-domain', + cdmio: 'application/cdmi-object', + cdmiq: 'application/cdmi-queue', + cdx: 'chemical/x-cdx', + cdxml: 'application/vnd.chemdraw+xml', + cdy: 'application/vnd.cinderella', + cer: 'application/pkix-cert', + cfs: 'application/x-cfs-compressed', + cgm: 'image/cgm', + chat: 'application/x-chat', + chm: 'application/vnd.ms-htmlhelp', + chrt: 'application/vnd.kde.kchart', + cif: 'chemical/x-cif', + cii: 'application/vnd.anser-web-certificate-issue-initiation', + cil: 'application/vnd.ms-artgalry', + cjs: 'application/node', + cla: 'application/vnd.claymore', + class: 'application/java-vm', + cld: 'model/vnd.cld', + clkk: 'application/vnd.crick.clicker.keyboard', + clkp: 'application/vnd.crick.clicker.palette', + clkt: 'application/vnd.crick.clicker.template', + clkw: 'application/vnd.crick.clicker.wordbank', + clkx: 'application/vnd.crick.clicker', + clp: 'application/x-msclip', + cmc: 'application/vnd.cosmocaller', + cmdf: 'chemical/x-cmdf', + cml: 'chemical/x-cml', + cmp: 'application/vnd.yellowriver-custom-menu', + cmx: 'image/x-cmx', + cod: 'application/vnd.rim.cod', + coffee: 'text/coffeescript', + com: 'application/x-msdownload', + conf: 'text/plain', + cpio: 'application/x-cpio', + cpl: 'application/cpl+xml', + cpp: 'text/x-c', + cpt: 'application/mac-compactpro', + crd: 'application/x-mscardfile', + crl: 'application/pkix-crl', + crt: 'application/x-x509-ca-cert', + crx: 'application/x-chrome-extension', + cryptonote: 'application/vnd.rig.cryptonote', + csh: 'application/x-csh', + csl: 'application/vnd.citationstyles.style+xml', + csml: 'chemical/x-csml', + csp: 'application/vnd.commonspace', + css: 'text/css', + cst: 'application/x-director', + csv: 'text/csv', + cu: 'application/cu-seeme', + curl: 'text/vnd.curl', + cwl: 'application/cwl', + cww: 'application/prs.cww', + cxt: 'application/x-director', + cxx: 'text/x-c', + dae: 'model/vnd.collada+xml', + daf: 'application/vnd.mobius.daf', + dart: 'application/vnd.dart', + dataless: 'application/vnd.fdsn.seed', + davmount: 'application/davmount+xml', + dbf: 'application/vnd.dbf', + dbk: 'application/docbook+xml', + dcr: 'application/x-director', + dcurl: 'text/vnd.curl.dcurl', + dd2: 'application/vnd.oma.dd2+xml', + ddd: 'application/vnd.fujixerox.ddd', + ddf: 'application/vnd.syncml.dmddf+xml', + dds: 'image/vnd.ms-dds', + deb: 'application/x-debian-package', + def: 'text/plain', + deploy: 'application/octet-stream', + der: 'application/x-x509-ca-cert', + dfac: 'application/vnd.dreamfactory', + dgc: 'application/x-dgc-compressed', + dib: 'image/bmp', + dic: 'text/x-c', + dir: 'application/x-director', + dis: 'application/vnd.mobius.dis', + 'disposition-notification': 'message/disposition-notification', + dist: 'application/octet-stream', + distz: 'application/octet-stream', + djv: 'image/vnd.djvu', + djvu: 'image/vnd.djvu', + dll: 'application/x-msdownload', + dmg: 'application/x-apple-diskimage', + dmp: 'application/vnd.tcpdump.pcap', + dms: 'application/octet-stream', + dna: 'application/vnd.dna', + doc: 'application/msword', + docm: 'application/vnd.ms-word.document.macroenabled.12', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + dot: 'application/msword', + dotm: 'application/vnd.ms-word.template.macroenabled.12', + dotx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + dp: 'application/vnd.osgi.dp', + dpg: 'application/vnd.dpgraph', + dpx: 'image/dpx', + dra: 'audio/vnd.dra', + drle: 'image/dicom-rle', + dsc: 'text/prs.lines.tag', + dssc: 'application/dssc+der', + dtb: 'application/x-dtbook+xml', + dtd: 'application/xml-dtd', + dts: 'audio/vnd.dts', + dtshd: 'audio/vnd.dts.hd', + dump: 'application/octet-stream', + dvb: 'video/vnd.dvb.file', + dvi: 'application/x-dvi', + dwd: 'application/atsc-dwd+xml', + dwf: 'model/vnd.dwf', + dwg: 'image/vnd.dwg', + dxf: 'image/vnd.dxf', + dxp: 'application/vnd.spotfire.dxp', + dxr: 'application/x-director', + ear: 'application/java-archive', + ecelp4800: 'audio/vnd.nuera.ecelp4800', + ecelp7470: 'audio/vnd.nuera.ecelp7470', + ecelp9600: 'audio/vnd.nuera.ecelp9600', + ecma: 'application/ecmascript', + edm: 'application/vnd.novadigm.edm', + edx: 'application/vnd.novadigm.edx', + efif: 'application/vnd.picsel', + ei6: 'application/vnd.pg.osasli', + elc: 'application/octet-stream', + emf: 'image/emf', + eml: 'message/rfc822', + emma: 'application/emma+xml', + emotionml: 'application/emotionml+xml', + emz: 'application/x-msmetafile', + eol: 'audio/vnd.digital-winds', + eot: 'application/vnd.ms-fontobject', + eps: 'application/postscript', + epub: 'application/epub+zip', + es3: 'application/vnd.eszigno3+xml', + esa: 'application/vnd.osgi.subsystem', + esf: 'application/vnd.epson.esf', + et3: 'application/vnd.eszigno3+xml', + etx: 'text/x-setext', + eva: 'application/x-eva', + evy: 'application/x-envoy', + exe: 'application/x-msdownload', + exi: 'application/exi', + exp: 'application/express', + exr: 'image/aces', + ext: 'application/vnd.novadigm.ext', + ez: 'application/andrew-inset', + ez2: 'application/vnd.ezpix-album', + ez3: 'application/vnd.ezpix-package', + f: 'text/x-fortran', + f4v: 'video/x-f4v', + f77: 'text/x-fortran', + f90: 'text/x-fortran', + fbs: 'image/vnd.fastbidsheet', + fcdt: 'application/vnd.adobe.formscentral.fcdt', + fcs: 'application/vnd.isac.fcs', + fdf: 'application/vnd.fdf', + fdt: 'application/fdt+xml', + fe_launch: 'application/vnd.denovo.fcselayout-link', + fg5: 'application/vnd.fujitsu.oasysgp', + fgd: 'application/x-director', + fh: 'image/x-freehand', + fh4: 'image/x-freehand', + fh5: 'image/x-freehand', + fh7: 'image/x-freehand', + fhc: 'image/x-freehand', + fig: 'application/x-xfig', + fits: 'image/fits', + flac: 'audio/x-flac', + fli: 'video/x-fli', + flo: 'application/vnd.micrografx.flo', + flv: 'video/x-flv', + flw: 'application/vnd.kde.kivio', + flx: 'text/vnd.fmi.flexstor', + fly: 'text/vnd.fly', + fm: 'application/vnd.framemaker', + fnc: 'application/vnd.frogans.fnc', + fo: 'application/vnd.software602.filler.form+xml', + for: 'text/x-fortran', + fpx: 'image/vnd.fpx', + frame: 'application/vnd.framemaker', + fsc: 'application/vnd.fsc.weblaunch', + fst: 'image/vnd.fst', + ftc: 'application/vnd.fluxtime.clip', + fti: 'application/vnd.anser-web-funds-transfer-initiation', + fvt: 'video/vnd.fvt', + fxp: 'application/vnd.adobe.fxp', + fxpl: 'application/vnd.adobe.fxp', + fzs: 'application/vnd.fuzzysheet', + g2w: 'application/vnd.geoplan', + g3: 'image/g3fax', + g3w: 'application/vnd.geospace', + gac: 'application/vnd.groove-account', + gam: 'application/x-tads', + gbr: 'application/rpki-ghostbusters', + gca: 'application/x-gca-compressed', + gdl: 'model/vnd.gdl', + gdoc: 'application/vnd.google-apps.document', + ged: 'text/vnd.familysearch.gedcom', + geo: 'application/vnd.dynageo', + geojson: 'application/geo+json', + gex: 'application/vnd.geometry-explorer', + ggb: 'application/vnd.geogebra.file', + ggt: 'application/vnd.geogebra.tool', + ghf: 'application/vnd.groove-help', + gif: 'image/gif', + gim: 'application/vnd.groove-identity-message', + glb: 'model/gltf-binary', + gltf: 'model/gltf+json', + gml: 'application/gml+xml', + gmx: 'application/vnd.gmx', + gnumeric: 'application/x-gnumeric', + gph: 'application/vnd.flographit', + gpx: 'application/gpx+xml', + gqf: 'application/vnd.grafeq', + gqs: 'application/vnd.grafeq', + gram: 'application/srgs', + gramps: 'application/x-gramps-xml', + gre: 'application/vnd.geometry-explorer', + grv: 'application/vnd.groove-injector', + grxml: 'application/srgs+xml', + gsf: 'application/x-font-ghostscript', + gsheet: 'application/vnd.google-apps.spreadsheet', + gslides: 'application/vnd.google-apps.presentation', + gtar: 'application/x-gtar', + gtm: 'application/vnd.groove-tool-message', + gtw: 'model/vnd.gtw', + gv: 'text/vnd.graphviz', + gxf: 'application/gxf', + gxt: 'application/vnd.geonext', + gz: 'application/gzip', + h: 'text/x-c', + h261: 'video/h261', + h263: 'video/h263', + h264: 'video/h264', + hal: 'application/vnd.hal+xml', + hbci: 'application/vnd.hbci', + hbs: 'text/x-handlebars-template', + hdd: 'application/x-virtualbox-hdd', + hdf: 'application/x-hdf', + heic: 'image/heic', + heics: 'image/heic-sequence', + heif: 'image/heif', + heifs: 'image/heif-sequence', + hej2: 'image/hej2k', + held: 'application/atsc-held+xml', + hh: 'text/x-c', + hjson: 'application/hjson', + hlp: 'application/winhlp', + hpgl: 'application/vnd.hp-hpgl', + hpid: 'application/vnd.hp-hpid', + hps: 'application/vnd.hp-hps', + hqx: 'application/mac-binhex40', + hsj2: 'image/hsj2', + htc: 'text/x-component', + htke: 'application/vnd.kenameaapp', + htm: 'text/html', + html: 'text/html', + hvd: 'application/vnd.yamaha.hv-dic', + hvp: 'application/vnd.yamaha.hv-voice', + hvs: 'application/vnd.yamaha.hv-script', + i2g: 'application/vnd.intergeo', + icc: 'application/vnd.iccprofile', + ice: 'x-conference/x-cooltalk', + icm: 'application/vnd.iccprofile', + ico: 'image/x-icon', + ics: 'text/calendar', + ief: 'image/ief', + ifb: 'text/calendar', + ifm: 'application/vnd.shana.informed.formdata', + iges: 'model/iges', + igl: 'application/vnd.igloader', + igm: 'application/vnd.insors.igm', + igs: 'model/iges', + igx: 'application/vnd.micrografx.igx', + iif: 'application/vnd.shana.informed.interchange', + img: 'application/octet-stream', + imp: 'application/vnd.accpac.simply.imp', + ims: 'application/vnd.ms-ims', + in: 'text/plain', + ini: 'text/plain', + ink: 'application/inkml+xml', + inkml: 'application/inkml+xml', + install: 'application/x-install-instructions', + iota: 'application/vnd.astraea-software.iota', + ipfix: 'application/ipfix', + ipk: 'application/vnd.shana.informed.package', + irm: 'application/vnd.ibm.rights-management', + irp: 'application/vnd.irepository.package+xml', + iso: 'application/x-iso9660-image', + itp: 'application/vnd.shana.informed.formtemplate', + its: 'application/its+xml', + ivp: 'application/vnd.immervision-ivp', + ivu: 'application/vnd.immervision-ivu', + jad: 'text/vnd.sun.j2me.app-descriptor', + jade: 'text/jade', + jam: 'application/vnd.jam', + jar: 'application/java-archive', + jardiff: 'application/x-java-archive-diff', + java: 'text/x-java-source', + jhc: 'image/jphc', + jisp: 'application/vnd.jisp', + jls: 'image/jls', + jlt: 'application/vnd.hp-jlyt', + jng: 'image/x-jng', + jnlp: 'application/x-java-jnlp-file', + joda: 'application/vnd.joost.joda-archive', + jp2: 'image/jp2', + jpe: 'image/jpeg', + jpeg: 'image/jpeg', + jpf: 'image/jpx', + jpg: 'image/jpeg', + jpg2: 'image/jp2', + jpgm: 'video/jpm', + jpgv: 'video/jpeg', + jph: 'image/jph', + jpm: 'video/jpm', + jpx: 'image/jpx', + js: 'text/javascript', + json: 'application/json', + json5: 'application/json5', + jsonld: 'application/ld+json', + jsonml: 'application/jsonml+json', + jsx: 'text/jsx', + jt: 'model/jt', + jxr: 'image/jxr', + jxra: 'image/jxra', + jxrs: 'image/jxrs', + jxs: 'image/jxs', + jxsc: 'image/jxsc', + jxsi: 'image/jxsi', + jxss: 'image/jxss', + kar: 'audio/midi', + karbon: 'application/vnd.kde.karbon', + kdbx: 'application/x-keepass2', + key: 'application/x-iwork-keynote-sffkey', + kfo: 'application/vnd.kde.kformula', + kia: 'application/vnd.kidspiration', + kml: 'application/vnd.google-earth.kml+xml', + kmz: 'application/vnd.google-earth.kmz', + kne: 'application/vnd.kinar', + knp: 'application/vnd.kinar', + kon: 'application/vnd.kde.kontour', + kpr: 'application/vnd.kde.kpresenter', + kpt: 'application/vnd.kde.kpresenter', + kpxx: 'application/vnd.ds-keypoint', + ksp: 'application/vnd.kde.kspread', + ktr: 'application/vnd.kahootz', + ktx: 'image/ktx', + ktx2: 'image/ktx2', + ktz: 'application/vnd.kahootz', + kwd: 'application/vnd.kde.kword', + kwt: 'application/vnd.kde.kword', + lasxml: 'application/vnd.las.las+xml', + latex: 'application/x-latex', + lbd: 'application/vnd.llamagraphics.life-balance.desktop', + lbe: 'application/vnd.llamagraphics.life-balance.exchange+xml', + les: 'application/vnd.hhe.lesson-player', + less: 'text/less', + lgr: 'application/lgr+xml', + lha: 'application/x-lzh-compressed', + link66: 'application/vnd.route66.link66+xml', + list: 'text/plain', + list3820: 'application/vnd.ibm.modcap', + listafp: 'application/vnd.ibm.modcap', + litcoffee: 'text/coffeescript', + lnk: 'application/x-ms-shortcut', + log: 'text/plain', + lostxml: 'application/lost+xml', + lrf: 'application/octet-stream', + lrm: 'application/vnd.ms-lrm', + ltf: 'application/vnd.frogans.ltf', + lua: 'text/x-lua', + luac: 'application/x-lua-bytecode', + lvp: 'audio/vnd.lucent.voice', + lwp: 'application/vnd.lotus-wordpro', + lzh: 'application/x-lzh-compressed', + m13: 'application/x-msmediaview', + m14: 'application/x-msmediaview', + m1v: 'video/mpeg', + m21: 'application/mp21', + m2a: 'audio/mpeg', + m2v: 'video/mpeg', + m3a: 'audio/mpeg', + m3u: 'audio/x-mpegurl', + m3u8: 'application/vnd.apple.mpegurl', + m4a: 'audio/x-m4a', + m4p: 'application/mp4', + m4s: 'video/iso.segment', + m4u: 'video/vnd.mpegurl', + m4v: 'video/x-m4v', + ma: 'application/mathematica', + mads: 'application/mads+xml', + maei: 'application/mmt-aei+xml', + mag: 'application/vnd.ecowin.chart', + maker: 'application/vnd.framemaker', + man: 'text/troff', + manifest: 'text/cache-manifest', + map: 'application/json', + mar: 'application/octet-stream', + markdown: 'text/markdown', + mathml: 'application/mathml+xml', + mb: 'application/mathematica', + mbk: 'application/vnd.mobius.mbk', + mbox: 'application/mbox', + mc1: 'application/vnd.medcalcdata', + mcd: 'application/vnd.mcd', + mcurl: 'text/vnd.curl.mcurl', + md: 'text/markdown', + mdb: 'application/x-msaccess', + mdi: 'image/vnd.ms-modi', + mdx: 'text/mdx', + me: 'text/troff', + mesh: 'model/mesh', + meta4: 'application/metalink4+xml', + metalink: 'application/metalink+xml', + mets: 'application/mets+xml', + mfm: 'application/vnd.mfmp', + mft: 'application/rpki-manifest', + mgp: 'application/vnd.osgeo.mapguide.package', + mgz: 'application/vnd.proteus.magazine', + mid: 'audio/midi', + midi: 'audio/midi', + mie: 'application/x-mie', + mif: 'application/vnd.mif', + mime: 'message/rfc822', + mj2: 'video/mj2', + mjp2: 'video/mj2', + mjs: 'text/javascript', + mk3d: 'video/x-matroska', + mka: 'audio/x-matroska', + mkd: 'text/x-markdown', + mks: 'video/x-matroska', + mkv: 'video/x-matroska', + mlp: 'application/vnd.dolby.mlp', + mmd: 'application/vnd.chipnuts.karaoke-mmd', + mmf: 'application/vnd.smaf', + mml: 'text/mathml', + mmr: 'image/vnd.fujixerox.edmics-mmr', + mng: 'video/x-mng', + mny: 'application/x-msmoney', + mobi: 'application/x-mobipocket-ebook', + mods: 'application/mods+xml', + mov: 'video/quicktime', + movie: 'video/x-sgi-movie', + mp2: 'audio/mpeg', + mp21: 'application/mp21', + mp2a: 'audio/mpeg', + mp3: 'audio/mpeg', + mp4: 'video/mp4', + mp4a: 'audio/mp4', + mp4s: 'application/mp4', + mp4v: 'video/mp4', + mpc: 'application/vnd.mophun.certificate', + mpd: 'application/dash+xml', + mpe: 'video/mpeg', + mpeg: 'video/mpeg', + mpf: 'application/media-policy-dataset+xml', + mpg: 'video/mpeg', + mpg4: 'video/mp4', + mpga: 'audio/mpeg', + mpkg: 'application/vnd.apple.installer+xml', + mpm: 'application/vnd.blueice.multipass', + mpn: 'application/vnd.mophun.application', + mpp: 'application/vnd.ms-project', + mpt: 'application/vnd.ms-project', + mpy: 'application/vnd.ibm.minipay', + mqy: 'application/vnd.mobius.mqy', + mrc: 'application/marc', + mrcx: 'application/marcxml+xml', + ms: 'text/troff', + mscml: 'application/mediaservercontrol+xml', + mseed: 'application/vnd.fdsn.mseed', + mseq: 'application/vnd.mseq', + msf: 'application/vnd.epson.msf', + msg: 'application/vnd.ms-outlook', + msh: 'model/mesh', + msi: 'application/x-msdownload', + msix: 'application/msix', + msixbundle: 'application/msixbundle', + msl: 'application/vnd.mobius.msl', + msm: 'application/octet-stream', + msp: 'application/octet-stream', + msty: 'application/vnd.muvee.style', + mtl: 'model/mtl', + mts: 'model/vnd.mts', + mus: 'application/vnd.musician', + musd: 'application/mmt-usd+xml', + musicxml: 'application/vnd.recordare.musicxml+xml', + mvb: 'application/x-msmediaview', + mvt: 'application/vnd.mapbox-vector-tile', + mwf: 'application/vnd.mfer', + mxf: 'application/mxf', + mxl: 'application/vnd.recordare.musicxml', + mxmf: 'audio/mobile-xmf', + mxml: 'application/xv+xml', + mxs: 'application/vnd.triscape.mxs', + mxu: 'video/vnd.mpegurl', + 'n-gage': 'application/vnd.nokia.n-gage.symbian.install', + n3: 'text/n3', + nb: 'application/mathematica', + nbp: 'application/vnd.wolfram.player', + nc: 'application/x-netcdf', + ncx: 'application/x-dtbncx+xml', + nfo: 'text/x-nfo', + ngdat: 'application/vnd.nokia.n-gage.data', + nitf: 'application/vnd.nitf', + nlu: 'application/vnd.neurolanguage.nlu', + nml: 'application/vnd.enliven', + nnd: 'application/vnd.noblenet-directory', + nns: 'application/vnd.noblenet-sealer', + nnw: 'application/vnd.noblenet-web', + npx: 'image/vnd.net-fpx', + nq: 'application/n-quads', + nsc: 'application/x-conference', + nsf: 'application/vnd.lotus-notes', + nt: 'application/n-triples', + ntf: 'application/vnd.nitf', + numbers: 'application/x-iwork-numbers-sffnumbers', + nzb: 'application/x-nzb', + oa2: 'application/vnd.fujitsu.oasys2', + oa3: 'application/vnd.fujitsu.oasys3', + oas: 'application/vnd.fujitsu.oasys', + obd: 'application/x-msbinder', + obgx: 'application/vnd.openblox.game+xml', + obj: 'model/obj', + oda: 'application/oda', + odb: 'application/vnd.oasis.opendocument.database', + odc: 'application/vnd.oasis.opendocument.chart', + odf: 'application/vnd.oasis.opendocument.formula', + odft: 'application/vnd.oasis.opendocument.formula-template', + odg: 'application/vnd.oasis.opendocument.graphics', + odi: 'application/vnd.oasis.opendocument.image', + odm: 'application/vnd.oasis.opendocument.text-master', + odp: 'application/vnd.oasis.opendocument.presentation', + ods: 'application/vnd.oasis.opendocument.spreadsheet', + odt: 'application/vnd.oasis.opendocument.text', + oga: 'audio/ogg', + ogex: 'model/vnd.opengex', + ogg: 'audio/ogg', + ogv: 'video/ogg', + ogx: 'application/ogg', + omdoc: 'application/omdoc+xml', + onepkg: 'application/onenote', + onetmp: 'application/onenote', + onetoc: 'application/onenote', + onetoc2: 'application/onenote', + opf: 'application/oebps-package+xml', + opml: 'text/x-opml', + oprc: 'application/vnd.palm', + opus: 'audio/ogg', + org: 'text/x-org', + osf: 'application/vnd.yamaha.openscoreformat', + osfpvg: 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + osm: 'application/vnd.openstreetmap.data+xml', + otc: 'application/vnd.oasis.opendocument.chart-template', + otf: 'font/otf', + otg: 'application/vnd.oasis.opendocument.graphics-template', + oth: 'application/vnd.oasis.opendocument.text-web', + oti: 'application/vnd.oasis.opendocument.image-template', + otp: 'application/vnd.oasis.opendocument.presentation-template', + ots: 'application/vnd.oasis.opendocument.spreadsheet-template', + ott: 'application/vnd.oasis.opendocument.text-template', + ova: 'application/x-virtualbox-ova', + ovf: 'application/x-virtualbox-ovf', + owl: 'application/rdf+xml', + oxps: 'application/oxps', + oxt: 'application/vnd.openofficeorg.extension', + p: 'text/x-pascal', + p10: 'application/pkcs10', + p12: 'application/x-pkcs12', + p7b: 'application/x-pkcs7-certificates', + p7c: 'application/pkcs7-mime', + p7m: 'application/pkcs7-mime', + p7r: 'application/x-pkcs7-certreqresp', + p7s: 'application/pkcs7-signature', + p8: 'application/pkcs8', + pac: 'application/x-ns-proxy-autoconfig', + pages: 'application/x-iwork-pages-sffpages', + pas: 'text/x-pascal', + paw: 'application/vnd.pawaafile', + pbd: 'application/vnd.powerbuilder6', + pbm: 'image/x-portable-bitmap', + pcap: 'application/vnd.tcpdump.pcap', + pcf: 'application/x-font-pcf', + pcl: 'application/vnd.hp-pcl', + pclxl: 'application/vnd.hp-pclxl', + pct: 'image/x-pict', + pcurl: 'application/vnd.curl.pcurl', + pcx: 'image/x-pcx', + pdb: 'application/x-pilot', + pde: 'text/x-processing', + pdf: 'application/pdf', + pem: 'application/x-x509-ca-cert', + pfa: 'application/x-font-type1', + pfb: 'application/x-font-type1', + pfm: 'application/x-font-type1', + pfr: 'application/font-tdpfr', + pfx: 'application/x-pkcs12', + pgm: 'image/x-portable-graymap', + pgn: 'application/x-chess-pgn', + pgp: 'application/pgp-encrypted', + php: 'application/x-httpd-php', + pic: 'image/x-pict', + pkg: 'application/octet-stream', + pki: 'application/pkixcmp', + pkipath: 'application/pkix-pkipath', + pkpass: 'application/vnd.apple.pkpass', + pl: 'application/x-perl', + plb: 'application/vnd.3gpp.pic-bw-large', + plc: 'application/vnd.mobius.plc', + plf: 'application/vnd.pocketlearn', + pls: 'application/pls+xml', + pm: 'application/x-perl', + pml: 'application/vnd.ctc-posml', + png: 'image/png', + pnm: 'image/x-portable-anymap', + portpkg: 'application/vnd.macports.portpkg', + pot: 'application/vnd.ms-powerpoint', + potm: 'application/vnd.ms-powerpoint.template.macroenabled.12', + potx: 'application/vnd.openxmlformats-officedocument.presentationml.template', + ppam: 'application/vnd.ms-powerpoint.addin.macroenabled.12', + ppd: 'application/vnd.cups-ppd', + ppm: 'image/x-portable-pixmap', + pps: 'application/vnd.ms-powerpoint', + ppsm: 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + ppsx: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + ppt: 'application/vnd.ms-powerpoint', + pptm: 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + pqa: 'application/vnd.palm', + prc: 'model/prc', + pre: 'application/vnd.lotus-freelance', + prf: 'application/pics-rules', + provx: 'application/provenance+xml', + ps: 'application/postscript', + psb: 'application/vnd.3gpp.pic-bw-small', + psd: 'image/vnd.adobe.photoshop', + psf: 'application/x-font-linux-psf', + pskcxml: 'application/pskc+xml', + pti: 'image/prs.pti', + ptid: 'application/vnd.pvi.ptid1', + pub: 'application/x-mspublisher', + pvb: 'application/vnd.3gpp.pic-bw-var', + pwn: 'application/vnd.3m.post-it-notes', + pya: 'audio/vnd.ms-playready.media.pya', + pyo: 'model/vnd.pytha.pyox', + pyox: 'model/vnd.pytha.pyox', + pyv: 'video/vnd.ms-playready.media.pyv', + qam: 'application/vnd.epson.quickanime', + qbo: 'application/vnd.intu.qbo', + qfx: 'application/vnd.intu.qfx', + qps: 'application/vnd.publishare-delta-tree', + qt: 'video/quicktime', + qwd: 'application/vnd.quark.quarkxpress', + qwt: 'application/vnd.quark.quarkxpress', + qxb: 'application/vnd.quark.quarkxpress', + qxd: 'application/vnd.quark.quarkxpress', + qxl: 'application/vnd.quark.quarkxpress', + qxt: 'application/vnd.quark.quarkxpress', + ra: 'audio/x-realaudio', + ram: 'audio/x-pn-realaudio', + raml: 'application/raml+yaml', + rapd: 'application/route-apd+xml', + rar: 'application/x-rar-compressed', + ras: 'image/x-cmu-raster', + rcprofile: 'application/vnd.ipunplugged.rcprofile', + rdf: 'application/rdf+xml', + rdz: 'application/vnd.data-vision.rdz', + relo: 'application/p2p-overlay+xml', + rep: 'application/vnd.businessobjects', + res: 'application/x-dtbresource+xml', + rgb: 'image/x-rgb', + rif: 'application/reginfo+xml', + rip: 'audio/vnd.rip', + ris: 'application/x-research-info-systems', + rl: 'application/resource-lists+xml', + rlc: 'image/vnd.fujixerox.edmics-rlc', + rld: 'application/resource-lists-diff+xml', + rm: 'application/vnd.rn-realmedia', + rmi: 'audio/midi', + rmp: 'audio/x-pn-realaudio-plugin', + rms: 'application/vnd.jcp.javame.midlet-rms', + rmvb: 'application/vnd.rn-realmedia-vbr', + rnc: 'application/relax-ng-compact-syntax', + rng: 'application/xml', + roa: 'application/rpki-roa', + roff: 'text/troff', + rp9: 'application/vnd.cloanto.rp9', + rpm: 'application/x-redhat-package-manager', + rpss: 'application/vnd.nokia.radio-presets', + rpst: 'application/vnd.nokia.radio-preset', + rq: 'application/sparql-query', + rs: 'application/rls-services+xml', + rsat: 'application/atsc-rsat+xml', + rsd: 'application/rsd+xml', + rsheet: 'application/urc-ressheet+xml', + rss: 'application/rss+xml', + rtf: 'text/rtf', + rtx: 'text/richtext', + run: 'application/x-makeself', + rusd: 'application/route-usd+xml', + s: 'text/x-asm', + s3m: 'audio/s3m', + saf: 'application/vnd.yamaha.smaf-audio', + sass: 'text/x-sass', + sbml: 'application/sbml+xml', + sc: 'application/vnd.ibm.secure-container', + scd: 'application/x-msschedule', + scm: 'application/vnd.lotus-screencam', + scq: 'application/scvp-cv-request', + scs: 'application/scvp-cv-response', + scss: 'text/x-scss', + scurl: 'text/vnd.curl.scurl', + sda: 'application/vnd.stardivision.draw', + sdc: 'application/vnd.stardivision.calc', + sdd: 'application/vnd.stardivision.impress', + sdkd: 'application/vnd.solent.sdkm+xml', + sdkm: 'application/vnd.solent.sdkm+xml', + sdp: 'application/sdp', + sdw: 'application/vnd.stardivision.writer', + sea: 'application/x-sea', + see: 'application/vnd.seemail', + seed: 'application/vnd.fdsn.seed', + sema: 'application/vnd.sema', + semd: 'application/vnd.semd', + semf: 'application/vnd.semf', + senmlx: 'application/senml+xml', + sensmlx: 'application/sensml+xml', + ser: 'application/java-serialized-object', + setpay: 'application/set-payment-initiation', + setreg: 'application/set-registration-initiation', + 'sfd-hdstx': 'application/vnd.hydrostatix.sof-data', + sfs: 'application/vnd.spotfire.sfs', + sfv: 'text/x-sfv', + sgi: 'image/sgi', + sgl: 'application/vnd.stardivision.writer-global', + sgm: 'text/sgml', + sgml: 'text/sgml', + sh: 'application/x-sh', + shar: 'application/x-shar', + shex: 'text/shex', + shf: 'application/shf+xml', + shtml: 'text/html', + sid: 'image/x-mrsid-image', + sieve: 'application/sieve', + sig: 'application/pgp-signature', + sil: 'audio/silk', + silo: 'model/mesh', + sis: 'application/vnd.symbian.install', + sisx: 'application/vnd.symbian.install', + sit: 'application/x-stuffit', + sitx: 'application/x-stuffitx', + siv: 'application/sieve', + skd: 'application/vnd.koan', + skm: 'application/vnd.koan', + skp: 'application/vnd.koan', + skt: 'application/vnd.koan', + sldm: 'application/vnd.ms-powerpoint.slide.macroenabled.12', + sldx: 'application/vnd.openxmlformats-officedocument.presentationml.slide', + slim: 'text/slim', + slm: 'text/slim', + sls: 'application/route-s-tsid+xml', + slt: 'application/vnd.epson.salt', + sm: 'application/vnd.stepmania.stepchart', + smf: 'application/vnd.stardivision.math', + smi: 'application/smil+xml', + smil: 'application/smil+xml', + smv: 'video/x-smv', + smzip: 'application/vnd.stepmania.package', + snd: 'audio/basic', + snf: 'application/x-font-snf', + so: 'application/octet-stream', + spc: 'application/x-pkcs7-certificates', + spdx: 'text/spdx', + spf: 'application/vnd.yamaha.smaf-phrase', + spl: 'application/x-futuresplash', + spot: 'text/vnd.in3d.spot', + spp: 'application/scvp-vp-response', + spq: 'application/scvp-vp-request', + spx: 'audio/ogg', + sql: 'application/x-sql', + src: 'application/x-wais-source', + srt: 'application/x-subrip', + sru: 'application/sru+xml', + srx: 'application/sparql-results+xml', + ssdl: 'application/ssdl+xml', + sse: 'application/vnd.kodak-descriptor', + ssf: 'application/vnd.epson.ssf', + ssml: 'application/ssml+xml', + st: 'application/vnd.sailingtracker.track', + stc: 'application/vnd.sun.xml.calc.template', + std: 'application/vnd.sun.xml.draw.template', + stf: 'application/vnd.wt.stf', + sti: 'application/vnd.sun.xml.impress.template', + stk: 'application/hyperstudio', + stl: 'model/stl', + stpx: 'model/step+xml', + stpxz: 'model/step-xml+zip', + stpz: 'model/step+zip', + str: 'application/vnd.pg.format', + stw: 'application/vnd.sun.xml.writer.template', + styl: 'text/stylus', + stylus: 'text/stylus', + sub: 'text/vnd.dvb.subtitle', + sus: 'application/vnd.sus-calendar', + susp: 'application/vnd.sus-calendar', + sv4cpio: 'application/x-sv4cpio', + sv4crc: 'application/x-sv4crc', + svc: 'application/vnd.dvb.service', + svd: 'application/vnd.svd', + svg: 'image/svg+xml', + svgz: 'image/svg+xml', + swa: 'application/x-director', + swf: 'application/x-shockwave-flash', + swi: 'application/vnd.aristanetworks.swi', + swidtag: 'application/swid+xml', + sxc: 'application/vnd.sun.xml.calc', + sxd: 'application/vnd.sun.xml.draw', + sxg: 'application/vnd.sun.xml.writer.global', + sxi: 'application/vnd.sun.xml.impress', + sxm: 'application/vnd.sun.xml.math', + sxw: 'application/vnd.sun.xml.writer', + t: 'text/troff', + t3: 'application/x-t3vm-image', + t38: 'image/t38', + taglet: 'application/vnd.mynfc', + tao: 'application/vnd.tao.intent-module-archive', + tap: 'image/vnd.tencent.tap', + tar: 'application/x-tar', + tcap: 'application/vnd.3gpp2.tcap', + tcl: 'application/x-tcl', + td: 'application/urc-targetdesc+xml', + teacher: 'application/vnd.smart.teacher', + tei: 'application/tei+xml', + teicorpus: 'application/tei+xml', + tex: 'application/x-tex', + texi: 'application/x-texinfo', + texinfo: 'application/x-texinfo', + text: 'text/plain', + tfi: 'application/thraud+xml', + tfm: 'application/x-tex-tfm', + tfx: 'image/tiff-fx', + tga: 'image/x-tga', + thmx: 'application/vnd.ms-officetheme', + tif: 'image/tiff', + tiff: 'image/tiff', + tk: 'application/x-tcl', + tmo: 'application/vnd.tmobile-livetv', + toml: 'application/toml', + torrent: 'application/x-bittorrent', + tpl: 'application/vnd.groove-tool-template', + tpt: 'application/vnd.trid.tpt', + tr: 'text/troff', + tra: 'application/vnd.trueapp', + trig: 'application/trig', + trm: 'application/x-msterminal', + ts: 'video/mp2t', + tsd: 'application/timestamped-data', + tsv: 'text/tab-separated-values', + ttc: 'font/collection', + ttf: 'font/ttf', + ttl: 'text/turtle', + ttml: 'application/ttml+xml', + twd: 'application/vnd.simtech-mindmapper', + twds: 'application/vnd.simtech-mindmapper', + txd: 'application/vnd.genomatix.tuxedo', + txf: 'application/vnd.mobius.txf', + txt: 'text/plain', + u32: 'application/x-authorware-bin', + u3d: 'model/u3d', + u8dsn: 'message/global-delivery-status', + u8hdr: 'message/global-headers', + u8mdn: 'message/global-disposition-notification', + u8msg: 'message/global', + ubj: 'application/ubjson', + udeb: 'application/x-debian-package', + ufd: 'application/vnd.ufdl', + ufdl: 'application/vnd.ufdl', + ulx: 'application/x-glulx', + umj: 'application/vnd.umajin', + unityweb: 'application/vnd.unity', + uo: 'application/vnd.uoml+xml', + uoml: 'application/vnd.uoml+xml', + uri: 'text/uri-list', + uris: 'text/uri-list', + urls: 'text/uri-list', + usda: 'model/vnd.usda', + usdz: 'model/vnd.usdz+zip', + ustar: 'application/x-ustar', + utz: 'application/vnd.uiq.theme', + uu: 'text/x-uuencode', + uva: 'audio/vnd.dece.audio', + uvd: 'application/vnd.dece.data', + uvf: 'application/vnd.dece.data', + uvg: 'image/vnd.dece.graphic', + uvh: 'video/vnd.dece.hd', + uvi: 'image/vnd.dece.graphic', + uvm: 'video/vnd.dece.mobile', + uvp: 'video/vnd.dece.pd', + uvs: 'video/vnd.dece.sd', + uvt: 'application/vnd.dece.ttml+xml', + uvu: 'video/vnd.uvvu.mp4', + uvv: 'video/vnd.dece.video', + uvva: 'audio/vnd.dece.audio', + uvvd: 'application/vnd.dece.data', + uvvf: 'application/vnd.dece.data', + uvvg: 'image/vnd.dece.graphic', + uvvh: 'video/vnd.dece.hd', + uvvi: 'image/vnd.dece.graphic', + uvvm: 'video/vnd.dece.mobile', + uvvp: 'video/vnd.dece.pd', + uvvs: 'video/vnd.dece.sd', + uvvt: 'application/vnd.dece.ttml+xml', + uvvu: 'video/vnd.uvvu.mp4', + uvvv: 'video/vnd.dece.video', + uvvx: 'application/vnd.dece.unspecified', + uvvz: 'application/vnd.dece.zip', + uvx: 'application/vnd.dece.unspecified', + uvz: 'application/vnd.dece.zip', + vbox: 'application/x-virtualbox-vbox', + 'vbox-extpack': 'application/x-virtualbox-vbox-extpack', + vcard: 'text/vcard', + vcd: 'application/x-cdlink', + vcf: 'text/x-vcard', + vcg: 'application/vnd.groove-vcard', + vcs: 'text/x-vcalendar', + vcx: 'application/vnd.vcx', + vdi: 'application/x-virtualbox-vdi', + vds: 'model/vnd.sap.vds', + vhd: 'application/x-virtualbox-vhd', + vis: 'application/vnd.visionary', + viv: 'video/vnd.vivo', + vmdk: 'application/x-virtualbox-vmdk', + vob: 'video/x-ms-vob', + vor: 'application/vnd.stardivision.writer', + vox: 'application/x-authorware-bin', + vrml: 'model/vrml', + vsd: 'application/vnd.visio', + vsf: 'application/vnd.vsf', + vss: 'application/vnd.visio', + vst: 'application/vnd.visio', + vsw: 'application/vnd.visio', + vtf: 'image/vnd.valve.source.texture', + vtt: 'text/vtt', + vtu: 'model/vnd.vtu', + vxml: 'application/voicexml+xml', + w3d: 'application/x-director', + wad: 'application/x-doom', + wadl: 'application/vnd.sun.wadl+xml', + war: 'application/java-archive', + wasm: 'application/wasm', + wav: 'audio/x-wav', + wax: 'audio/x-ms-wax', + wbmp: 'image/vnd.wap.wbmp', + wbs: 'application/vnd.criticaltools.wbs+xml', + wbxml: 'application/vnd.wap.wbxml', + wcm: 'application/vnd.ms-works', + wdb: 'application/vnd.ms-works', + wdp: 'image/vnd.ms-photo', + weba: 'audio/webm', + webapp: 'application/x-web-app-manifest+json', + webm: 'video/webm', + webmanifest: 'application/manifest+json', + webp: 'image/webp', + wg: 'application/vnd.pmi.widget', + wgsl: 'text/wgsl', + wgt: 'application/widget', + wif: 'application/watcherinfo+xml', + wks: 'application/vnd.ms-works', + wm: 'video/x-ms-wm', + wma: 'audio/x-ms-wma', + wmd: 'application/x-ms-wmd', + wmf: 'image/wmf', + wml: 'text/vnd.wap.wml', + wmlc: 'application/vnd.wap.wmlc', + wmls: 'text/vnd.wap.wmlscript', + wmlsc: 'application/vnd.wap.wmlscriptc', + wmv: 'video/x-ms-wmv', + wmx: 'video/x-ms-wmx', + wmz: 'application/x-msmetafile', + woff: 'font/woff', + woff2: 'font/woff2', + wpd: 'application/vnd.wordperfect', + wpl: 'application/vnd.ms-wpl', + wps: 'application/vnd.ms-works', + wqd: 'application/vnd.wqd', + wri: 'application/x-mswrite', + wrl: 'model/vrml', + wsc: 'message/vnd.wfa.wsc', + wsdl: 'application/wsdl+xml', + wspolicy: 'application/wspolicy+xml', + wtb: 'application/vnd.webturbo', + wvx: 'video/x-ms-wvx', + x32: 'application/x-authorware-bin', + x3d: 'model/x3d+xml', + x3db: 'model/x3d+fastinfoset', + x3dbz: 'model/x3d+binary', + x3dv: 'model/x3d-vrml', + x3dvz: 'model/x3d+vrml', + x3dz: 'model/x3d+xml', + x_b: 'model/vnd.parasolid.transmit.binary', + x_t: 'model/vnd.parasolid.transmit.text', + xaml: 'application/xaml+xml', + xap: 'application/x-silverlight-app', + xar: 'application/vnd.xara', + xav: 'application/xcap-att+xml', + xbap: 'application/x-ms-xbap', + xbd: 'application/vnd.fujixerox.docuworks.binder', + xbm: 'image/x-xbitmap', + xca: 'application/xcap-caps+xml', + xcs: 'application/calendar+xml', + xdf: 'application/xcap-diff+xml', + xdm: 'application/vnd.syncml.dm+xml', + xdp: 'application/vnd.adobe.xdp+xml', + xdssc: 'application/dssc+xml', + xdw: 'application/vnd.fujixerox.docuworks', + xel: 'application/xcap-el+xml', + xenc: 'application/xenc+xml', + xer: 'application/patch-ops-error+xml', + xfdf: 'application/xfdf', + xfdl: 'application/vnd.xfdl', + xht: 'application/xhtml+xml', + xhtm: 'application/vnd.pwg-xhtml-print+xml', + xhtml: 'application/xhtml+xml', + xhvml: 'application/xv+xml', + xif: 'image/vnd.xiff', + xla: 'application/vnd.ms-excel', + xlam: 'application/vnd.ms-excel.addin.macroenabled.12', + xlc: 'application/vnd.ms-excel', + xlf: 'application/xliff+xml', + xlm: 'application/vnd.ms-excel', + xls: 'application/vnd.ms-excel', + xlsb: 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + xlsm: 'application/vnd.ms-excel.sheet.macroenabled.12', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + xlt: 'application/vnd.ms-excel', + xltm: 'application/vnd.ms-excel.template.macroenabled.12', + xltx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + xlw: 'application/vnd.ms-excel', + xm: 'audio/xm', + xml: 'text/xml', + xns: 'application/xcap-ns+xml', + xo: 'application/vnd.olpc-sugar', + xop: 'application/xop+xml', + xpi: 'application/x-xpinstall', + xpl: 'application/xproc+xml', + xpm: 'image/x-xpixmap', + xpr: 'application/vnd.is-xpr', + xps: 'application/vnd.ms-xpsdocument', + xpw: 'application/vnd.intercon.formnet', + xpx: 'application/vnd.intercon.formnet', + xsd: 'application/xml', + xsf: 'application/prs.xsf+xml', + xsl: 'application/xslt+xml', + xslt: 'application/xslt+xml', + xsm: 'application/vnd.syncml+xml', + xspf: 'application/xspf+xml', + xul: 'application/vnd.mozilla.xul+xml', + xvm: 'application/xv+xml', + xvml: 'application/xv+xml', + xwd: 'image/x-xwindowdump', + xyz: 'chemical/x-xyz', + xz: 'application/x-xz', + yaml: 'text/yaml', + yang: 'application/yang', + yin: 'application/yin+xml', + yml: 'text/yaml', + ymp: 'text/x-suse-ymp', + z1: 'application/x-zmachine', + z2: 'application/x-zmachine', + z3: 'application/x-zmachine', + z4: 'application/x-zmachine', + z5: 'application/x-zmachine', + z6: 'application/x-zmachine', + z7: 'application/x-zmachine', + z8: 'application/x-zmachine', + zaz: 'application/vnd.zzazz.deck+xml', + zip: 'application/zip', + zir: 'application/vnd.zul', + zirz: 'application/vnd.zul', + zmm: 'application/vnd.handheld-entertainment+xml', +}; diff --git a/packages/core/lib/utils/helpers.ts b/packages/core/lib/utils/helpers.ts index 8758167..96083b3 100644 --- a/packages/core/lib/utils/helpers.ts +++ b/packages/core/lib/utils/helpers.ts @@ -134,3 +134,7 @@ export const getTime = () => { return `${formattedHours}:${formattedMinutes}:${formattedSeconds} ${ampm}`; }; + +export const isUndefined = (val: any) => { + return val === undefined; +}; diff --git a/packages/core/lib/validator/validator.ts b/packages/core/lib/validator/validator.ts index c0c56db..6c9d81e 100644 --- a/packages/core/lib/validator/validator.ts +++ b/packages/core/lib/validator/validator.ts @@ -2,7 +2,7 @@ import { Type } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { ValidationError, validate } from 'class-validator'; import { ConfigService } from '../config/service'; -import { ValidationFailed } from '../exceptions/validationfailed'; +import { ValidationFailed } from '../exceptions/validation-failed'; import { Obj } from '../utils'; export class Validator { From ce63cd1550c87a53f24ff97834594d60b0bfa66d Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 24 Nov 2024 01:10:51 +0530 Subject: [PATCH 05/27] feat(core): global guards support --- .../sample-app/app/http/guards/global.ts | 17 +++++++++++++++++ integrations/sample-app/app/http/kernel.ts | 4 +++- .../rest/foundation/middlewares/configurator.ts | 13 +++++-------- packages/core/lib/rest/foundation/server.ts | 11 +++++------ .../core/lib/rest/http-server/route-explorer.ts | 11 +++++++++++ 5 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 integrations/sample-app/app/http/guards/global.ts diff --git a/integrations/sample-app/app/http/guards/global.ts b/integrations/sample-app/app/http/guards/global.ts new file mode 100644 index 0000000..50cb29a --- /dev/null +++ b/integrations/sample-app/app/http/guards/global.ts @@ -0,0 +1,17 @@ +import { Injectable, IntentGuard, Reflector, Response } from '@intentjs/core'; +import { Request } from 'hyper-express'; + +@Injectable() +export class GlobalGuard extends IntentGuard { + async guard( + req: Request, + res: Response, + reflector: Reflector, + ): Promise { + console.log('inside global guard'); + // await req.multipart(async (field) => { + // console.log('field ===> ', field.name, field.file); + // }); + return true; + } +} diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index ad0e668..ea1a10d 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -10,6 +10,8 @@ import { } from '@intentjs/core'; import { UserController } from './controllers/app'; import { AuthController } from './controllers/auth'; +import { CustomGuard } from './guards/custom'; +import { GlobalGuard } from './guards/global'; export class HttpKernel extends Kernel { /** @@ -50,7 +52,7 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/guards */ public guards(): Type[] { - return []; + return [GlobalGuard]; } /** diff --git a/packages/core/lib/rest/foundation/middlewares/configurator.ts b/packages/core/lib/rest/foundation/middlewares/configurator.ts index d95b90f..d270764 100644 --- a/packages/core/lib/rest/foundation/middlewares/configurator.ts +++ b/packages/core/lib/rest/foundation/middlewares/configurator.ts @@ -2,9 +2,11 @@ import { Type } from '../../../interfaces'; import { RequestMethod } from '../../http-server/methods'; import { IntentMiddleware } from './middleware'; -/** - * - */ +type MiddlewareRuleApplicationInfo = + | string + | Type + | { path: string; method: RequestMethod }; + export class MiddlewareConfigurator { private rules: { [key: string]: MiddlewareRule } = {}; @@ -27,11 +29,6 @@ export class MiddlewareConfigurator { } } -type MiddlewareRuleApplicationInfo = - | string - | Type - | { path: string; method: RequestMethod }; - export class MiddlewareRule { public appliedFor: Array = []; public excludedFor: Array = diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 519a2e9..9c5786e 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -49,6 +49,8 @@ export class IntentHttpServer { const module = ModuleBuilder.build(this.container, this.kernel); const app = await NestFactory.createApplicationContext(module); + const globalGuards = this.kernel.guards(); + const ds = app.get(DiscoveryService, { strict: false }); const ms = app.get(MetadataScanner, { strict: false }); const mr = app.get(ModuleRef, { strict: false }); @@ -57,12 +59,9 @@ export class IntentHttpServer { useContainer(app.select(module), { fallbackOnErrors: true }); const routeExplorer = new RouteExplorer(); - const routes = await routeExplorer.exploreFullRoutes( - ds, - ms, - mr, - errorHandler, - ); + const routes = await routeExplorer + .useGlobalGuards(globalGuards) + .exploreFullRoutes(ds, ms, mr, errorHandler); const customServer = new HyperServer(); const server = await customServer.build(routes); diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index b63837d..6c0ff31 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -19,6 +19,8 @@ import { ROUTE_ARGS } from './constants'; import { RouteArgType } from './param-decorators'; export class RouteExplorer { + guards: Type[] = []; + async exploreFullRoutes( discoveryService: DiscoveryService, metadataScanner: MetadataScanner, @@ -120,6 +122,10 @@ export class RouteExplorer { ] as Type[]; const composedGuards = []; + for (const globalGuard of this.guards) { + composedGuards.push(await moduleRef.create(globalGuard)); + } + for (const guardType of composedGuardTypes) { composedGuards.push(await moduleRef.create(guardType)); } @@ -162,4 +168,9 @@ export class RouteExplorer { httpHandler: cb, }; } + + useGlobalGuards(guards: Type[]): RouteExplorer { + this.guards.push(...guards); + return this; + } } From 2e735174fcbaf3315c1022bf0c02f45890025675 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 24 Nov 2024 16:41:33 +0530 Subject: [PATCH 06/27] feat(core): added support for multiple parsers, request and response, and route param decorators --- .husky/pre-commit | 1 - .../sample-app/app/http/controllers/icon.ts | 46 ++- .../sample-app/app/http/guards/custom.ts | 5 +- .../sample-app/app/http/guards/global.ts | 4 +- integrations/sample-app/app/http/kernel.ts | 2 +- integrations/sample-app/config/http.ts | 22 ++ integrations/sample-app/config/index.ts | 2 + package-lock.json | 14 + packages/core/lib/interfaces/config.ts | 15 + .../lib/rest/foundation/guards/base-guard.ts | 2 +- packages/core/lib/rest/foundation/server.ts | 6 +- .../core/lib/rest/http-server/constants.ts | 7 + .../contexts/http-execution-context.ts | 14 +- .../core/lib/rest/http-server/decorators.ts | 92 ++--- .../core/lib/rest/http-server/http-handler.ts | 11 +- packages/core/lib/rest/http-server/request.ts | 334 +++++++++++++++++- .../core/lib/rest/http-server/response.ts | 23 +- .../lib/rest/http-server/route-explorer.ts | 39 +- packages/core/lib/rest/http-server/server.ts | 12 +- .../storage/file-handlers/uploaded-file.ts | 20 ++ packages/core/lib/utils/string.ts | 2 +- packages/core/package.json | 2 + 22 files changed, 553 insertions(+), 122 deletions(-) create mode 100644 integrations/sample-app/config/http.ts create mode 100644 packages/core/lib/storage/file-handlers/uploaded-file.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index d6cb288..e69de29 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +0,0 @@ -npm run build diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 3335bcf..7a3b014 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -1,5 +1,6 @@ import { Accepts, + Body, Controller, Get, Host, @@ -8,10 +9,10 @@ import { Post, Query, Req, - Request, UseGuard, } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; +import { Request } from '@intentjs/core/dist/lib/rest/http-server/request'; @Controller('/icon') @UseGuard(CustomGuard) @@ -22,15 +23,14 @@ export class IntentController { @UseGuard(CustomGuard) async getHello( @Req() req: Request, - @Query() query: Record, - @Query('b') bQuery: string, - @Param('name') name: string, - @Param() pathParams: string, - @Host() hostname: string, - @IP() ips: string, - @Accepts() accepts: string, + // @Query() query: Record, + // @Query('b') bQuery: string, + // @Param('name') name: string, + // @Param() pathParams: string, + // @Host() hostname: string, + // @IP() ips: string, + // @Accepts() accepts: string, ) { - // console.log(query, bQuery, name, pathParams, hostname, accepts, ips); // throw new Error('hello there'); return { hello: 'world' }; } @@ -50,8 +50,32 @@ export class IntentController { } @Post('/json') - async postJson(@Req() req: Request) { - return { hello: 'world' }; + async postJson( + @Req() req: Request, + @Query() query: Record, + @Query('b') bQuery: string, + @Param('name') name: string, + @Param() pathParams: string, + @Host() hostname: string, + @IP() ips: string, + @Accepts() accepts: string, + @Body() body: any, + ) { + console.log('inside post method'); + console.log( + await req.file('file1'), + await req.file('file2'), + // query, + // bQuery, + // name, + // pathParams, + // hostname, + // accepts, + // ips, + // 'inside post method', + // body, + ); + return { hello: 'world from POST /json' }; } @Post('/multipart') diff --git a/integrations/sample-app/app/http/guards/custom.ts b/integrations/sample-app/app/http/guards/custom.ts index 8d3b53b..ebada16 100644 --- a/integrations/sample-app/app/http/guards/custom.ts +++ b/integrations/sample-app/app/http/guards/custom.ts @@ -1,5 +1,5 @@ import { Injectable, IntentGuard, Reflector, Response } from '@intentjs/core'; -import { Request } from 'hyper-express'; +import { Request } from '@intentjs/core/dist/lib/rest/http-server/request'; @Injectable() export class CustomGuard extends IntentGuard { @@ -8,9 +8,6 @@ export class CustomGuard extends IntentGuard { res: Response, reflector: Reflector, ): Promise { - await req.multipart(async (field) => { - console.log('field ===> ', field.name, field.file); - }); return true; } } diff --git a/integrations/sample-app/app/http/guards/global.ts b/integrations/sample-app/app/http/guards/global.ts index 50cb29a..1f0f638 100644 --- a/integrations/sample-app/app/http/guards/global.ts +++ b/integrations/sample-app/app/http/guards/global.ts @@ -9,9 +9,7 @@ export class GlobalGuard extends IntentGuard { reflector: Reflector, ): Promise { console.log('inside global guard'); - // await req.multipart(async (field) => { - // console.log('field ===> ', field.name, field.file); - // }); + return true; } } diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index ea1a10d..6bb01b3 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -52,7 +52,7 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/guards */ public guards(): Type[] { - return [GlobalGuard]; + return []; } /** diff --git a/integrations/sample-app/config/http.ts b/integrations/sample-app/config/http.ts new file mode 100644 index 0000000..8f37395 --- /dev/null +++ b/integrations/sample-app/config/http.ts @@ -0,0 +1,22 @@ +import { HttpConfig, registerNamespace } from '@intentjs/core'; + +export default registerNamespace( + 'http', + (): HttpConfig => ({ + /** + * ----------------------------------------------------- + * Parser + * ----------------------------------------------------- + * + * This value is the name of your application. This value is + * used when the framework needs to place the application's + * name in a notification or any other location as required. + */ + parsers: ['json', 'formdata', 'plain', 'urlencoded'], + + server: { + max_body_buffer: 100000000, + max_body_length: 100000000, + }, + }), +); diff --git a/integrations/sample-app/config/index.ts b/integrations/sample-app/config/index.ts index 20db8d2..5ee538f 100644 --- a/integrations/sample-app/config/index.ts +++ b/integrations/sample-app/config/index.ts @@ -7,8 +7,10 @@ import mailer from './mailer'; import database from './database'; import cache from './cache'; import queue from './queue'; +import http from './http'; export default [ + http, app, auth, cache, diff --git a/package-lock.json b/package-lock.json index 1597228..72339a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5152,6 +5152,16 @@ "@types/node": "*" } }, + "node_modules/@types/busboy": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "dev": true, @@ -6960,6 +6970,8 @@ }, "node_modules/busboy": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { "streamsearch": "^1.1.0" }, @@ -17882,6 +17894,7 @@ "@react-email/components": "^0.0.25", "archy": "^1.0.0", "axios": "^1.7.7", + "busboy": "^1.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cli-table3": "^0.6.3", @@ -17910,6 +17923,7 @@ "@nestjs/testing": "^10.3.9", "@stylistic/eslint-plugin-ts": "^2.6.1", "@types/archy": "^0.0.36", + "@types/busboy": "^1.5.4", "@types/express": "^4.17.21", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^9.0.7", diff --git a/packages/core/lib/interfaces/config.ts b/packages/core/lib/interfaces/config.ts index 30c419d..cd50100 100644 --- a/packages/core/lib/interfaces/config.ts +++ b/packages/core/lib/interfaces/config.ts @@ -3,6 +3,8 @@ import { CorsOptionsDelegate, } from '@nestjs/common/interfaces/external/cors-options.interface'; import { GenericClass } from '.'; +import { HyperServer } from '../rest'; +import { ServerConstructorOptions } from 'hyper-express'; export interface SentryConfig { dsn: string; @@ -23,3 +25,16 @@ export interface AppConfig { }; sentry?: SentryConfig; } + +export type RequestParsers = + | 'json' + | 'urlencoded' + | 'formdata' + | 'plain' + | 'html' + | 'binary'; + +export interface HttpConfig { + parsers: RequestParsers[]; + server?: ServerConstructorOptions; +} diff --git a/packages/core/lib/rest/foundation/guards/base-guard.ts b/packages/core/lib/rest/foundation/guards/base-guard.ts index 1f22dce..bcb75f4 100644 --- a/packages/core/lib/rest/foundation/guards/base-guard.ts +++ b/packages/core/lib/rest/foundation/guards/base-guard.ts @@ -1,7 +1,7 @@ import { Reflector } from '../../../reflections'; -import { Request } from 'hyper-express'; import { ForbiddenException } from '../../../exceptions/forbidden-exception'; import { ExecutionContext, Response } from '../../http-server'; +import { Request } from '../../http-server/request'; export abstract class IntentGuard { async handle(context: ExecutionContext): Promise { diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 9c5786e..8139dc7 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -55,6 +55,7 @@ export class IntentHttpServer { const ms = app.get(MetadataScanner, { strict: false }); const mr = app.get(ModuleRef, { strict: false }); const errorHandler = await mr.create(this.errorHandler); + const config = app.get(ConfigService, { strict: false }); useContainer(app.select(module), { fallbackOnErrors: true }); @@ -63,10 +64,11 @@ export class IntentHttpServer { .useGlobalGuards(globalGuards) .exploreFullRoutes(ds, ms, mr, errorHandler); + const serverOptions = config.get('http.server'); + const customServer = new HyperServer(); - const server = await customServer.build(routes); + const server = await customServer.build(routes, serverOptions); - const config = app.get(ConfigService, { strict: false }); this.configureErrorReporter(config.get('app.sentry')); const port = config.get('app.port'); diff --git a/packages/core/lib/rest/http-server/constants.ts b/packages/core/lib/rest/http-server/constants.ts index 74ffba1..0908633 100644 --- a/packages/core/lib/rest/http-server/constants.ts +++ b/packages/core/lib/rest/http-server/constants.ts @@ -1 +1,8 @@ export const ROUTE_ARGS = '__route_args__'; +export const CONTROLLER_KEY = '@intentjs/controller_path'; +export const CONTROLLER_OPTIONS = '@intentjs/controller_options'; + +export const METHOD_KEY = '@intentjs/controller_method_key'; +export const METHOD_PATH = '@intentjs/controller_method_path'; + +export const GUARD_KEY = '@intentjs/controller_guards'; diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index 00d04fe..d25e8e6 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -1,6 +1,6 @@ -import { Request } from 'hyper-express'; import { Response } from '../response'; import { RouteArgType, RouteParamtypes } from '../param-decorators'; +import { Request } from '../request'; export class HttpExecutionContext { constructor( @@ -26,9 +26,9 @@ export class HttpExecutionContext { case RouteParamtypes.QUERY: if (data) { - return this.request.query_parameters[data as string]; + return this.request.query[data as string]; } - return { ...this.request.query_parameters }; + return this.request.query; case RouteParamtypes.ACCEPTS: return this.request.accepts(); @@ -43,10 +43,10 @@ export class HttpExecutionContext { case RouteParamtypes.PARAM: if (data) { - return this.request.path_parameters[data as string]; + return this.request.params[data as string]; } - return { ...this.request.path_parameters }; + return { ...this.request.params }; case RouteParamtypes.SESSION: @@ -61,14 +61,14 @@ export class HttpExecutionContext { return this.request.raw; case RouteParamtypes.USER_AGENT: - return this.request.header('user-agent'); + return this.request.headers['user-agent']; case RouteParamtypes.HOST: return this.request.url; case RouteParamtypes.HEADERS: if (data) { - return this.request.header(data as string); + return this.request.headers[data as string]; } return { ...(this.request.headers || {}) }; diff --git a/packages/core/lib/rest/http-server/decorators.ts b/packages/core/lib/rest/http-server/decorators.ts index 3d1cd4e..c81029c 100644 --- a/packages/core/lib/rest/http-server/decorators.ts +++ b/packages/core/lib/rest/http-server/decorators.ts @@ -2,14 +2,13 @@ import { Injectable } from '@nestjs/common'; import { applyDecorators } from '../../reflections/apply-decorators'; import { Type } from '../../interfaces'; import { IntentGuard } from '../foundation'; - -export const CONTROLLER_KEY = '@intentjs/controller_path'; -export const CONTROLLER_OPTIONS = '@intentjs/controller_options'; - -export const METHOD_KEY = '@intentjs/controller_method_key'; -export const METHOD_PATH = '@intentjs/controller_method_path'; - -export const GUARD_KEY = '@intentjs/controller_guards'; +import { + CONTROLLER_KEY, + CONTROLLER_OPTIONS, + GUARD_KEY, + METHOD_KEY, + METHOD_PATH, +} from './constants'; export type ControllerOptions = { host?: string; @@ -57,61 +56,40 @@ export type RouteDecoratorType = ( export const Get: RouteDecoratorType = (path, options) => createRouteDecorators(HttpMethods.GET, path, options); -export function Post(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.POST, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const Post: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.POST, path, options); -export function Put(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.PUT, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const Put: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.PUT, path, options); -export function Patch(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.PATCH, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const Patch: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.PATCH, path, options); -export function Delete(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.DELETE, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const Delete: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.DELETE, path, options); -export function Options(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.OPTIONS, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const Options: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.OPTIONS, path, options); -export function Head(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.HEAD, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const Head: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.HEAD, path, options); -export function Any(path?: string, options?: ControllerOptions) { - return function (target: object, key?: string | symbol, descriptor?: any) { - Reflect.defineMetadata(METHOD_KEY, HttpMethods.ANY, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); - return descriptor; - }; -} +export const ANY: RouteDecoratorType = ( + path: string, + options?: ControllerOptions, +) => createRouteDecorators(HttpMethods.ANY, path, options); export const UseGuard = (...guards: Type[]) => { return function (target: object, key?: string | symbol, descriptor?: any) { diff --git a/packages/core/lib/rest/http-server/http-handler.ts b/packages/core/lib/rest/http-server/http-handler.ts index 99b4c37..98b48d6 100644 --- a/packages/core/lib/rest/http-server/http-handler.ts +++ b/packages/core/lib/rest/http-server/http-handler.ts @@ -12,11 +12,10 @@ export class HttpRouteHandler { protected readonly exceptionFilter: IntentExceptionFilter, ) {} - async handle(context: ExecutionContext): Promise<[any, Response]> { + async handle(context: ExecutionContext, args: any[]): Promise { // for (const middleware of this.middlewares) { // await middleware.use({}, {}); // } - try { /** * Handle the Guards @@ -28,18 +27,18 @@ export class HttpRouteHandler { /** * Handle the request */ - const responseFromHandler = await this.handler; + const responseFromHandler = await this.handler(...args); if (responseFromHandler instanceof Response) { - return [, responseFromHandler]; + return responseFromHandler; } else { const response = context.switchToHttp().getResponse(); response.body(responseFromHandler); - return [, response]; + return response; } } catch (e) { const res = this.exceptionFilter.catch(context, e); - return [undefined, res]; + return res; } } } diff --git a/packages/core/lib/rest/http-server/request.ts b/packages/core/lib/rest/http-server/request.ts index 9832db6..b6d8765 100644 --- a/packages/core/lib/rest/http-server/request.ts +++ b/packages/core/lib/rest/http-server/request.ts @@ -1,9 +1,337 @@ import uWS from 'uWebSockets.js'; -import { HttpMethods } from './decorators'; +import { Request as HyperRequest, MultipartHandler } from 'hyper-express'; +import { isEmpty, Str } from '../../utils'; +import { EXTENSTION_TO_MIME } from '../../utils/extension-to-mime'; +import { Type } from '../../interfaces'; +import { Validator } from '../../validator'; +import { tmpdir } from 'os'; +import { ulid } from 'ulid'; +import { UploadedFile } from '../../storage/file-handlers/uploaded-file'; +import { join } from 'path'; +import { ConfigService } from '../../config'; + +export const createRequestFromHyper = async (hReq: HyperRequest) => { + const headers = { ...hReq.headers }; + + const enabledParsers = ConfigService.get('http.parsers') || []; + const contentType = headers['content-type'] || ''; + + let body = undefined; + if ( + enabledParsers.includes('urlencoded') && + contentType.includes('application/x-www-form-urlencoded') + ) { + body = await hReq.urlencoded(); + } else if ( + enabledParsers.includes('json') && + contentType.includes('application/json') + ) { + body = await hReq.json(); + } else if ( + enabledParsers.includes('formdata') && + contentType.includes('multipart/form-data') + ) { + body = {}; + const multipartData = await processMultipartData(hReq); + for (const [key, value] of multipartData.entries()) { + body[key] = value; + } + } else if ( + enabledParsers.includes('plain') && + contentType.includes('text/plain') + ) { + body = await hReq.text(); + } else if ( + (enabledParsers.includes('html') && contentType.includes('text/html')) || + (enabledParsers.includes('xml') && contentType.includes('application/xml')) + ) { + body = (await hReq.buffer()).toString(); + } else { + body = await hReq.buffer(); + } + + return new Request( + hReq.raw, + hReq.method, + hReq.url, + headers, + { ...hReq.query_parameters }, + { ...hReq.path_parameters }, + body, + hReq.text.bind(hReq), + hReq.buffer.bind(hReq), + hReq.path, + hReq.protocol, + hReq.ips, + hReq.multipart.bind(hReq), + ); +}; export class Request { + private uploadedFiles = new Map(); + constructor( - private raw: uWS.HttpRequest, - private method: HttpMethods, + public readonly raw: uWS.HttpRequest, + public readonly method: string, + public readonly url: string, + public readonly headers: Record, + public readonly query: Record, + public readonly params: Record, + public readonly body: any, + public readonly text: () => Promise, + public readonly buffer: () => Promise, + public readonly path: string, + public readonly protocol: string, + public readonly ip: string[], + public readonly multipart: (handler: MultipartHandler) => Promise, ) {} + + $dto: null; + $user: null; + logger() {} + + setDto(dto: any): void { + this.$dto = dto; + } + + dto(): any { + return this.$dto; + } + + all(): Record { + return { + ...(this.query || {}), + ...(this.params || {}), + ...(this.body || {}), + }; + } + + input(name: string, defaultValue?: T): T { + const payload = this.all(); + return name in payload ? payload[name] : defaultValue; + } + + string(name: string): string { + const value = this.input(name); + return value && value.toString(); + } + + number(name: string): number { + const value = this.input(name); + return +value; + } + + boolean(name: string): boolean { + const payload = this.all(); + const val = payload[name] as string; + return [true, 'yes', 'on', '1', 1, 'true'].includes(val.toLowerCase()); + } + + hasHeader(name: string): boolean { + return name in this.headers; + } + + bearerToken(): string { + const authHeader = this.headers['authorization']; + const asArray = authHeader?.split(' '); + if (!isEmpty(asArray)) return asArray[1]; + return undefined; + } + + httpHost(): string { + return this.protocol; + } + + isHttp(): boolean { + return this.httpHost() === 'http'; + } + + isHttps(): boolean { + return this.httpHost() === 'https'; + } + + fullUrl(): string { + return this.url; + } + + isMethod(method: string): boolean { + return this.method.toLowerCase() === method.toLowerCase(); + } + + contentType(): string[] { + return this.headers['content-type']?.split(',') || []; + } + + getAcceptableContentTypes(): string { + return this.headers['accept']; + } + + accepts(): string[] { + const getAcceptableContentTypes = this.headers['accept'] || []; + return (this.headers['accept'] || '').split(','); + } + + expectsJson(): boolean { + return this.accepts().includes(EXTENSTION_TO_MIME['json']); + } + + async validate(schema: Type): Promise { + const payload = this.all(); + const validator = Validator.compareWith(schema); + const dto = await validator + .addMeta({ ...payload, _headers: { ...this.headers } }) + .validate({ ...payload }); + this.setDto(dto); + return true; + } + + setUser(user: any): void { + this.$user = user; + } + + user(): T { + return this.$user as T; + } + + only(...keys: string[]): Record { + return {}; + } + + except(...keys: string[]): Record { + console.log(keys); + return {}; + } + + isPath(pathPattern: string): boolean { + return false; + } + + has(...keys: string[]): boolean { + const payload = this.all(); + for (const key of keys) { + if (!(key in payload)) return false; + } + + return true; + } + + hasAny(...keys: string[]): boolean { + const payload = this.all(); + for (const key of keys) { + if (key in payload) return true; + } + + return false; + } + + missing(...keys: string[]): boolean { + const payload = this.all(); + for (const key of keys) { + if (key in payload) return false; + } + + return true; + } + + hasHeaders(...keys: string[]): boolean { + for (const key of keys) { + if (!(key in this.headers)) return false; + } + + return true; + } + + hasIncludes(): boolean { + const includes = this.includes(); + return includes === ''; + } + + includes(): string { + return this.string('include'); + } + + files(keys: string): Record { + return {}; + } + + async file( + key: string, + ): Promise { + const fileAtKey = this.body[key]; + const values = [] as UploadedFile[]; + const isArray = Array.isArray(fileAtKey); + if (isArray) { + for (const file of fileAtKey) { + if (file instanceof UploadedFile) { + values.push(file); + } + } + + return values as T; + } + + if (!(fileAtKey instanceof UploadedFile)) return undefined; + + return fileAtKey as T; + } } + +const processMultipartData = async ( + req: HyperRequest, +): Promise> => { + const fields = new Map(); + const tempDirectory = tmpdir(); + const generateTempFilename = (filename: string) => `${ulid()}-${filename}`; + + try { + await req.multipart(async field => { + /** + * check if the field name is an array based, or an associative index + */ + const isArray = Str.is(field.name, '*[*]'); + const strippedFieldName = Str.before(field.name, '['); + const existingFieldValue = fields.get(strippedFieldName); + + if (isArray && !existingFieldValue) { + fields.set(strippedFieldName, []); + } + + if (field.file) { + const tempFileName = generateTempFilename(field.file.name); + const tempFilePath = join(tempDirectory, tempFileName); + let fileSize = 0; + field.file.stream.on('data', chunk => { + fileSize += chunk.length; + }); + + await field.write(tempFilePath); + + const uploadedFile = new UploadedFile( + field.file.name, + fileSize, + field.mime_type, + tempFileName, + tempFilePath, + ); + if (Array.isArray(existingFieldValue)) { + fields.set( + strippedFieldName, + existingFieldValue.concat(uploadedFile), + ); + } else { + fields.set(strippedFieldName, uploadedFile); + } + } else { + if (Array.isArray(existingFieldValue)) { + fields.set(strippedFieldName, existingFieldValue.concat(field.value)); + } else { + fields.set(strippedFieldName, field.value); + } + } + }); + } catch (e) { + console.error(e); + } + + return fields; +}; diff --git a/packages/core/lib/rest/http-server/response.ts b/packages/core/lib/rest/http-server/response.ts index 5095aed..5bb97e9 100644 --- a/packages/core/lib/rest/http-server/response.ts +++ b/packages/core/lib/rest/http-server/response.ts @@ -1,4 +1,4 @@ -import { Response as HResponse } from 'hyper-express'; +import { Response as HResponse, Request as HRequest } from 'hyper-express'; import { StreamableFile } from './streamable-file'; import { HttpStatus } from './status-codes'; import { @@ -14,6 +14,7 @@ export class Response { constructor() { this.responseHeaders = new Map(); + this.statusCode = HttpStatus.OK; } status(statusCode: HttpStatus): Response { @@ -76,11 +77,27 @@ export class Response { return this; } - reply(res: HResponse) { - this.statusCode && res.status(this.statusCode); + reply(req: HRequest, res: HResponse) { + const { method } = req; + + /** + * Set the status code of the response + */ + if (!this.statusCode && method === 'POST') { + res.status(HttpStatus.CREATED); + } else if (this.statusCode) { + res.status(this.statusCode); + } else { + res.status(HttpStatus.OK); + } + + /** + * Set the headers + */ for (const [key, value] of this.responseHeaders.entries()) { res.setHeader(key, value); } + if (this.bodyData instanceof StreamableFile) { const headers = this.bodyData.getHeaders(); if ( diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index 6c0ff31..b779909 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -1,10 +1,4 @@ import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; -import { - CONTROLLER_KEY, - GUARD_KEY, - METHOD_KEY, - METHOD_PATH, -} from './decorators'; import { join } from 'path'; import { HttpRoute } from './interfaces'; import { Request, Response as HResponse } from 'hyper-express'; @@ -15,8 +9,15 @@ import { ExecutionContext } from './contexts/execution-context'; import { IntentExceptionFilter } from '../../exceptions'; import { IntentGuard } from '../foundation'; import { Type } from '../../interfaces'; -import { ROUTE_ARGS } from './constants'; +import { + CONTROLLER_KEY, + GUARD_KEY, + METHOD_KEY, + METHOD_PATH, + ROUTE_ARGS, +} from './constants'; import { RouteArgType } from './param-decorators'; +import { createRequestFromHyper } from './request'; export class RouteExplorer { guards: Type[] = []; @@ -108,7 +109,7 @@ export class RouteExplorer { if (!controllerKey) return; const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); - const methodRef = instance[key]; + const methodRef = instance[key].bind(instance); const controllerGuards = Reflect.getMetadata( GUARD_KEY, instance.constructor, @@ -138,10 +139,18 @@ export class RouteExplorer { key, ) as RouteArgType[]; + const handler = new HttpRouteHandler( + middlewares, + composedGuards, + methodRef, + errorHandler, + ); + const cb = async (hReq: Request, hRes: HResponse) => { - const httpContext = new HttpExecutionContext(hReq, new Response()); - const context = new ExecutionContext(httpContext, instance, methodRef); + const req = await createRequestFromHyper(hReq); + const httpContext = new HttpExecutionContext(req, new Response()); + const context = new ExecutionContext(httpContext, instance, methodRef); const args = []; for (const routeArg of routeArgs) { if (routeArg.handler) { @@ -151,15 +160,9 @@ export class RouteExplorer { } } - const handler = new HttpRouteHandler( - middlewares, - composedGuards, - instance[key].apply(instance, args), - errorHandler, - ); + const res = await handler.handle(context, args); - const [error, res] = await handler.handle(context); - return res.reply(hRes); + res.reply(hReq, hRes); }; return { diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index 7c3a3a9..ee3fd15 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -1,15 +1,19 @@ import { HttpMethods } from './decorators'; import HyperExpress from 'hyper-express'; import { HttpRoute } from './interfaces'; +import { HttpConfig } from '../../interfaces'; export class HyperServer { protected hyper: HyperExpress.Server; - constructor() { - this.hyper = new HyperExpress.Server(); - } + constructor() {} + + async build( + routes: HttpRoute[], + config: HyperExpress.ServerConstructorOptions, + ): Promise { + this.hyper = new HyperExpress.Server(config || {}); - async build(routes: HttpRoute[]): Promise { for (const route of routes) { const { path, httpHandler } = route; switch (route.method) { diff --git a/packages/core/lib/storage/file-handlers/uploaded-file.ts b/packages/core/lib/storage/file-handlers/uploaded-file.ts new file mode 100644 index 0000000..5514aad --- /dev/null +++ b/packages/core/lib/storage/file-handlers/uploaded-file.ts @@ -0,0 +1,20 @@ +import { readFileSync } from 'fs-extra'; +import { Str } from '../../utils'; + +export class UploadedFile { + constructor( + public readonly filename: string, + public readonly size: number, + public readonly mimeType: string, + public readonly tempName: string, + public readonly tempPath: string, + ) {} + + get extension(): string { + return Str.afterLast(this.filename, '.'); + } + + async toBuffer(): Promise { + return readFileSync(this.tempPath); + } +} diff --git a/packages/core/lib/utils/string.ts b/packages/core/lib/utils/string.ts index 51589b6..4144b90 100644 --- a/packages/core/lib/utils/string.ts +++ b/packages/core/lib/utils/string.ts @@ -416,7 +416,7 @@ export class Str { if (str === pattern) return true; let regex = '^'; for (const p of pattern) { - regex += p === '*' ? '.*' : p; + regex += p === '*' ? '.*' : p === '[' || p === ']' ? `\\${p}` : p; } const regexp = new RegExp(regex + '$', 'g'); return regexp.test(str); diff --git a/packages/core/package.json b/packages/core/package.json index b9d41f8..e6c071b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,6 +43,7 @@ "@nestjs/testing": "^10.3.9", "@stylistic/eslint-plugin-ts": "^2.6.1", "@types/archy": "^0.0.36", + "@types/busboy": "^1.5.4", "@types/express": "^4.17.21", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^9.0.7", @@ -71,6 +72,7 @@ "@react-email/components": "^0.0.25", "archy": "^1.0.0", "axios": "^1.7.7", + "busboy": "^1.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cli-table3": "^0.6.3", From 0e3eb06c940f1a37adf4d56505985b67a75bc2f3 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 24 Nov 2024 16:51:31 +0530 Subject: [PATCH 07/27] chore(core): remove unused dependency of express --- package-lock.json | 153 ++++++++++++++++-- .../functional/requestSerializer.ts | 11 -- packages/core/package.json | 4 - 3 files changed, 136 insertions(+), 32 deletions(-) delete mode 100644 packages/core/lib/rest/middlewares/functional/requestSerializer.ts diff --git a/package-lock.json b/package-lock.json index 72339a4..85b60b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3113,6 +3113,8 @@ "node_modules/@nestjs/platform-express": { "version": "10.4.7", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "body-parser": "1.20.3", "cors": "2.8.5", @@ -3131,7 +3133,9 @@ }, "node_modules/@nestjs/platform-express/node_modules/tslib": { "version": "2.7.0", - "license": "0BSD" + "license": "0BSD", + "optional": true, + "peer": true }, "node_modules/@nestjs/testing": { "version": "10.4.7", @@ -6259,7 +6263,9 @@ }, "node_modules/append-field": { "version": "1.0.0", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/aproba": { "version": "2.0.0", @@ -6334,7 +6340,9 @@ }, "node_modules/array-flatten": { "version": "1.1.1", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/array-ify": { "version": "1.0.0", @@ -6835,6 +6843,8 @@ "node_modules/body-parser": { "version": "1.20.3", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -6857,13 +6867,17 @@ "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/bowser": { "version": "2.11.0", @@ -6966,6 +6980,7 @@ }, "node_modules/buffer-from": { "version": "1.1.2", + "devOptional": true, "license": "MIT" }, "node_modules/busboy": { @@ -6990,6 +7005,8 @@ "node_modules/bytes": { "version": "3.1.2", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -7057,6 +7074,7 @@ }, "node_modules/call-bind": { "version": "1.0.7", + "devOptional": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -7570,6 +7588,7 @@ }, "node_modules/content-disposition": { "version": "0.5.4", + "devOptional": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -7581,6 +7600,8 @@ "node_modules/content-type": { "version": "1.0.5", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -7748,13 +7769,17 @@ "node_modules/cookie": { "version": "0.7.1", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { "version": "1.0.6", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/cookiejar": { "version": "2.1.4", @@ -7763,6 +7788,7 @@ }, "node_modules/core-util-is": { "version": "1.0.3", + "devOptional": true, "license": "MIT" }, "node_modules/cors": { @@ -8076,6 +8102,7 @@ }, "node_modules/define-data-property": { "version": "1.1.4", + "devOptional": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -8134,6 +8161,8 @@ "node_modules/depd": { "version": "2.0.0", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -8146,6 +8175,8 @@ "node_modules/destroy": { "version": "1.2.0", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -8364,7 +8395,9 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/ejs": { "version": "3.1.10", @@ -8406,6 +8439,8 @@ "node_modules/encodeurl": { "version": "2.0.0", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -8596,6 +8631,7 @@ }, "node_modules/es-define-property": { "version": "1.0.0", + "devOptional": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" @@ -8606,6 +8642,7 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8710,7 +8747,9 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -9233,6 +9272,8 @@ "node_modules/etag": { "version": "1.8.1", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -9327,6 +9368,8 @@ "node_modules/express": { "version": "4.21.1", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9367,17 +9410,23 @@ "node_modules/express/node_modules/debug": { "version": "2.6.9", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/express/node_modules/ms": { "version": "2.0.0", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.10", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/ext-list": { "version": "2.2.2", @@ -9626,6 +9675,8 @@ "node_modules/finalhandler": { "version": "1.3.1", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -9642,13 +9693,17 @@ "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/find-up": { "version": "4.1.0", @@ -9828,6 +9883,8 @@ "node_modules/forwarded": { "version": "0.2.0", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -9835,6 +9892,8 @@ "node_modules/fresh": { "version": "0.5.2", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -9982,6 +10041,7 @@ }, "node_modules/get-intrinsic": { "version": "1.2.4", + "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -10361,6 +10421,7 @@ }, "node_modules/gopd": { "version": "1.0.1", + "devOptional": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" @@ -10447,6 +10508,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", + "devOptional": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -10457,6 +10519,7 @@ }, "node_modules/has-proto": { "version": "1.0.3", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10467,6 +10530,7 @@ }, "node_modules/has-symbols": { "version": "1.0.3", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10573,6 +10637,8 @@ "node_modules/http-errors": { "version": "2.0.0", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -10690,6 +10756,7 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -10938,6 +11005,8 @@ "node_modules/ipaddr.js": { "version": "1.9.1", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.10" } @@ -11323,6 +11392,7 @@ }, "node_modules/isarray": { "version": "1.0.0", + "devOptional": true, "license": "MIT" }, "node_modules/isexe": { @@ -13021,6 +13091,8 @@ "node_modules/merge-descriptors": { "version": "1.0.3", "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -13039,6 +13111,7 @@ }, "node_modules/methods": { "version": "1.1.2", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -13058,6 +13131,8 @@ "node_modules/mime": { "version": "1.6.0", "license": "MIT", + "optional": true, + "peer": true, "bin": { "mime": "cli.js" }, @@ -13118,6 +13193,7 @@ }, "node_modules/minimist": { "version": "1.2.8", + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13282,6 +13358,8 @@ "node_modules/multer": { "version": "1.4.4-lts.1", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "append-field": "^1.0.0", "busboy": "^1.0.0", @@ -13301,6 +13379,8 @@ "node >= 0.8" ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -13311,6 +13391,8 @@ "node_modules/multer/node_modules/mkdirp": { "version": "0.5.6", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -13321,6 +13403,8 @@ "node_modules/multer/node_modules/readable-stream": { "version": "2.3.8", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13333,11 +13417,15 @@ }, "node_modules/multer/node_modules/safe-buffer": { "version": "5.1.2", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/multer/node_modules/string_decoder": { "version": "1.1.1", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -13810,6 +13898,7 @@ }, "node_modules/object-inspect": { "version": "1.13.3", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13925,6 +14014,8 @@ "node_modules/on-finished": { "version": "2.4.1", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -14276,6 +14367,8 @@ "node_modules/parseurl": { "version": "1.3.3", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -14649,6 +14742,7 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "devOptional": true, "license": "MIT" }, "node_modules/proggy": { @@ -14727,6 +14821,8 @@ "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -14778,6 +14874,7 @@ }, "node_modules/qs": { "version": "6.13.0", + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -14841,6 +14938,8 @@ "node_modules/raw-body": { "version": "2.5.2", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -15647,6 +15746,7 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", + "devOptional": true, "license": "MIT" }, "node_modules/sample-app": { @@ -15727,6 +15827,8 @@ "node_modules/send": { "version": "0.19.0", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -15749,17 +15851,23 @@ "node_modules/send/node_modules/debug": { "version": "2.6.9", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -15776,6 +15884,8 @@ "node_modules/serve-static": { "version": "1.16.2", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -15792,6 +15902,7 @@ }, "node_modules/set-function-length": { "version": "1.2.2", + "devOptional": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -15821,7 +15932,9 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -15860,6 +15973,7 @@ }, "node_modules/side-channel": { "version": "1.0.6", + "devOptional": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -16141,6 +16255,8 @@ "node_modules/statuses": { "version": "2.0.1", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -16749,6 +16865,8 @@ "node_modules/toidentifier": { "version": "1.0.1", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.6" } @@ -17119,6 +17237,7 @@ }, "node_modules/typedarray": { "version": "0.0.6", + "devOptional": true, "license": "MIT" }, "node_modules/typescript": { @@ -17227,6 +17346,8 @@ "node_modules/unpipe": { "version": "1.0.0", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -17283,6 +17404,8 @@ "node_modules/utils-merge": { "version": "1.0.1", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.4.0" } @@ -17890,11 +18013,9 @@ "dependencies": { "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", - "@nestjs/platform-express": "^10.4.1", "@react-email/components": "^0.0.25", "archy": "^1.0.0", "axios": "^1.7.7", - "busboy": "^1.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cli-table3": "^0.6.3", @@ -17902,7 +18023,6 @@ "dotenv": "^16.4.5", "enquirer": "^2.4.1", "eta": "^3.5.0", - "express": "^4.21.0", "fs-extra": "^11.1.1", "helmet": "^7.1.0", "ioredis": "^5.3.2", @@ -17924,7 +18044,6 @@ "@stylistic/eslint-plugin-ts": "^2.6.1", "@types/archy": "^0.0.36", "@types/busboy": "^1.5.4", - "@types/express": "^4.17.21", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^9.0.7", "@types/jest": "^29.5.13", diff --git a/packages/core/lib/rest/middlewares/functional/requestSerializer.ts b/packages/core/lib/rest/middlewares/functional/requestSerializer.ts deleted file mode 100644 index 465b751..0000000 --- a/packages/core/lib/rest/middlewares/functional/requestSerializer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NextFunction, Request, Response } from 'express'; -import { RequestMixin } from '../../foundation/request-mixin'; - -export const requestMiddleware = ( - req: Request, - res: Response, - next: NextFunction, -) => { - Object.assign(req, RequestMixin(req)); - next(); -}; diff --git a/packages/core/package.json b/packages/core/package.json index e6c071b..2c4c611 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,7 +44,6 @@ "@stylistic/eslint-plugin-ts": "^2.6.1", "@types/archy": "^0.0.36", "@types/busboy": "^1.5.4", - "@types/express": "^4.17.21", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^9.0.7", "@types/jest": "^29.5.13", @@ -68,11 +67,9 @@ "dependencies": { "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", - "@nestjs/platform-express": "^10.4.1", "@react-email/components": "^0.0.25", "archy": "^1.0.0", "axios": "^1.7.7", - "busboy": "^1.6.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cli-table3": "^0.6.3", @@ -80,7 +77,6 @@ "dotenv": "^16.4.5", "enquirer": "^2.4.1", "eta": "^3.5.0", - "express": "^4.21.0", "fs-extra": "^11.1.1", "helmet": "^7.1.0", "ioredis": "^5.3.2", From f42ad5a894d1614c98e17e453ae5668005040d4d Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 24 Nov 2024 16:55:35 +0530 Subject: [PATCH 08/27] fix(core): fixed the imports and removed the unused references of express server --- .../sample-app/app/http/controllers/app.ts | 4 +- .../sample-app/app/http/guards/global.ts | 9 ++++- .../lib/rest/foundation/guards/decorator.ts | 1 - packages/core/lib/rest/foundation/index.ts | 2 - .../core/lib/rest/foundation/interface.ts | 38 ------------------- packages/core/lib/rest/foundation/server.ts | 1 - .../contexts/http-execution-context.ts | 1 + packages/core/lib/rest/http-server/index.ts | 1 + packages/core/lib/rest/index.ts | 2 - packages/core/lib/rest/interfaces.ts | 23 ----------- packages/core/lib/transformers/interfaces.ts | 2 +- 11 files changed, 12 insertions(+), 72 deletions(-) delete mode 100644 packages/core/lib/rest/foundation/guards/decorator.ts delete mode 100644 packages/core/lib/rest/foundation/interface.ts delete mode 100644 packages/core/lib/rest/interfaces.ts diff --git a/integrations/sample-app/app/http/controllers/app.ts b/integrations/sample-app/app/http/controllers/app.ts index 7d784b8..32aeae2 100644 --- a/integrations/sample-app/app/http/controllers/app.ts +++ b/integrations/sample-app/app/http/controllers/app.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Req, Request } from '@intentjs/core'; +import { Controller, Get, Req } from '@intentjs/core'; import { UserService } from 'app/services'; @Controller() @@ -7,7 +7,7 @@ export class UserController { @Get() async getHello(@Req() req: Request) { - console.log(req.all()); + console.log(req.body); return this.service.getHello(); } } diff --git a/integrations/sample-app/app/http/guards/global.ts b/integrations/sample-app/app/http/guards/global.ts index 1f0f638..a7248a1 100644 --- a/integrations/sample-app/app/http/guards/global.ts +++ b/integrations/sample-app/app/http/guards/global.ts @@ -1,5 +1,10 @@ -import { Injectable, IntentGuard, Reflector, Response } from '@intentjs/core'; -import { Request } from 'hyper-express'; +import { + Injectable, + IntentGuard, + Reflector, + Request, + Response, +} from '@intentjs/core'; @Injectable() export class GlobalGuard extends IntentGuard { diff --git a/packages/core/lib/rest/foundation/guards/decorator.ts b/packages/core/lib/rest/foundation/guards/decorator.ts deleted file mode 100644 index 26140cd..0000000 --- a/packages/core/lib/rest/foundation/guards/decorator.ts +++ /dev/null @@ -1 +0,0 @@ -export { UseGuards } from '@nestjs/common'; diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index 94ba4ee..91c7574 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -1,6 +1,4 @@ export * from './guards/base-guard'; -export * from './guards/decorator'; -export * from './interface'; export * from './kernel'; export * from './middlewares/middleware'; export * from './middlewares/configurator'; diff --git a/packages/core/lib/rest/foundation/interface.ts b/packages/core/lib/rest/foundation/interface.ts deleted file mode 100644 index 19fe6e4..0000000 --- a/packages/core/lib/rest/foundation/interface.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import { IncomingHttpHeaders } from 'http'; -import { Request as ERequest } from 'express'; -import { Type } from '../../interfaces'; - -export interface Request extends ERequest { - logger: Function; - setDto: Function; - dto: () => any; - all: () => Record; - input: (name: string, defaultValue?: T) => T; - string: (name: string) => string; - number: (name: string) => number; - boolean: (name: string) => boolean; - hasHeader: (name: string) => boolean; - bearerToken: () => string; - // host: () => string; - httpHost: () => string; - isHttp: () => boolean; - isHttps: () => boolean; - fullUrl: () => string; - isMethod: (method: string) => boolean; - getAcceptableContentTypes: () => IncomingHttpHeaders; - // accepts: (...contentTypes: string[]) => boolean; - expectsJson: () => boolean; - validate: (schema: Type) => Promise; - setUser: (user: any) => void; - use: () => T; - only: (...keys: string[]) => Record; - except: (...keys: string[]) => Record; - isPath: (pattern: string) => boolean; - has: (...keys: string[]) => boolean; - hasAny: (...keys: string[]) => boolean; - missing: (...keys: string[]) => boolean; - hasHeaders: (...headers: string[]) => boolean; - hasIncludes: () => boolean; - includes: () => string[]; -} diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 8139dc7..08e53cd 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -11,7 +11,6 @@ import { IntentAppContainer, ModuleBuilder } from '../../foundation'; import { Type } from '../../interfaces'; import { Obj, Package } from '../../utils'; import { Kernel } from '../foundation/kernel'; -import { requestMiddleware } from '../middlewares/functional/requestSerializer'; import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index d25e8e6..f386634 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -21,6 +21,7 @@ export class HttpExecutionContext { switch (type) { case RouteParamtypes.REQUEST: return this.getRequest(); + case RouteParamtypes.RESPONSE: return this.getResponse(); diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index 77ef361..10aab66 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -8,3 +8,4 @@ export * from './server'; export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; +export * from './request'; diff --git a/packages/core/lib/rest/index.ts b/packages/core/lib/rest/index.ts index de144d6..2a3c325 100644 --- a/packages/core/lib/rest/index.ts +++ b/packages/core/lib/rest/index.ts @@ -1,6 +1,4 @@ export * from './interceptors/timeout'; -export * from './interfaces'; -export { Res } from '@nestjs/common'; export * from './foundation'; export * from './middlewares/cors'; export * from './middlewares/helmet'; diff --git a/packages/core/lib/rest/interfaces.ts b/packages/core/lib/rest/interfaces.ts deleted file mode 100644 index ecb97ba..0000000 --- a/packages/core/lib/rest/interfaces.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; -import { AbstractHttpAdapter, BaseExceptionFilter } from '@nestjs/core'; -import { Request as BaseRequest } from 'express'; - -export interface IRequest extends BaseRequest { - /** - * Get all inputs from the request object - */ - all(): Record; - - /** - * Get the current user from the request object - */ - user: Record; -} - -export interface ServerOptions { - addValidationContainer?: boolean; - port?: number; - globalPrefix?: string; - exceptionFilter?: (httpAdapter: AbstractHttpAdapter) => BaseExceptionFilter; - cors?: CorsOptions; -} diff --git a/packages/core/lib/transformers/interfaces.ts b/packages/core/lib/transformers/interfaces.ts index bf6c16e..7aa1948 100644 --- a/packages/core/lib/transformers/interfaces.ts +++ b/packages/core/lib/transformers/interfaces.ts @@ -1,4 +1,4 @@ -import { Request } from '../rest'; +import { Request } from '../rest/http-server/request'; export interface TransformableContextOptions { req?: Request; From b66e70d7c866b34d15a230c5f09011bfb73dac3e Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 24 Nov 2024 22:57:09 +0530 Subject: [PATCH 09/27] fix(core): bug fixes in http-server --- .../sample-app/app/http/controllers/icon.ts | 26 ++++-- integrations/sample-app/app/http/kernel.ts | 10 ++- .../sample-app/app/http/middlewares/sample.ts | 17 ++++ packages/core/lib/rest/foundation/index.ts | 1 + .../foundation/middlewares/configurator.ts | 8 +- .../middlewares/middleware-composer.ts | 79 +++++++++++++++++++ .../rest/foundation/middlewares/middleware.ts | 8 +- packages/core/lib/rest/foundation/server.ts | 19 ++++- .../core/lib/rest/http-server/decorators.ts | 12 +-- packages/core/lib/rest/http-server/index.ts | 1 + .../core/lib/rest/http-server/interfaces.ts | 11 +++ packages/core/lib/rest/http-server/methods.ts | 11 --- packages/core/lib/rest/http-server/request.ts | 23 +++--- .../core/lib/rest/http-server/response.ts | 6 +- .../lib/rest/http-server/route-explorer.ts | 63 +++++++++------ packages/core/lib/rest/http-server/server.ts | 9 ++- packages/core/lib/utils/extension-to-mime.ts | 2 +- 17 files changed, 221 insertions(+), 85 deletions(-) create mode 100644 integrations/sample-app/app/http/middlewares/sample.ts create mode 100644 packages/core/lib/rest/foundation/middlewares/middleware-composer.ts delete mode 100644 packages/core/lib/rest/http-server/methods.ts diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 7a3b014..d968f82 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -23,14 +23,26 @@ export class IntentController { @UseGuard(CustomGuard) async getHello( @Req() req: Request, - // @Query() query: Record, - // @Query('b') bQuery: string, - // @Param('name') name: string, - // @Param() pathParams: string, - // @Host() hostname: string, - // @IP() ips: string, - // @Accepts() accepts: string, + @Query() query: Record, + @Query('b') bQuery: string, + @Param('name') name: string, + @Param() pathParams: string, + @Host() hostname: string, + @IP() ips: string, + @Accepts() accepts: string, ) { + // console.log( + // await req.file('file1'), + // await req.file('file2'), + // query, + // bQuery, + // name, + // pathParams, + // hostname, + // accepts, + // ips, + // 'inside post method', + // ); // throw new Error('hello there'); return { hello: 'world' }; } diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index 6bb01b3..1e2df82 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -1,6 +1,7 @@ import { CorsMiddleware, HelmetMiddleware, + HttpMethods, IntentApplication, IntentGuard, IntentMiddleware, @@ -10,8 +11,7 @@ import { } from '@intentjs/core'; import { UserController } from './controllers/app'; import { AuthController } from './controllers/auth'; -import { CustomGuard } from './guards/custom'; -import { GlobalGuard } from './guards/global'; +import { SampleMiddleware } from './middlewares/sample'; export class HttpKernel extends Kernel { /** @@ -41,7 +41,11 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/middlewares */ public routeMiddlewares(configurator: MiddlewareConfigurator) { - return; + configurator + .use(SampleMiddleware) + .for({ path: '/icon/sample', method: HttpMethods.POST }); + + configurator.use(SampleMiddleware).for('/icon/plain'); } /** diff --git a/integrations/sample-app/app/http/middlewares/sample.ts b/integrations/sample-app/app/http/middlewares/sample.ts new file mode 100644 index 0000000..b441d00 --- /dev/null +++ b/integrations/sample-app/app/http/middlewares/sample.ts @@ -0,0 +1,17 @@ +import { + IntentMiddleware, + MiddlewareNext, + Request, + Response, +} from '@intentjs/core'; + +export class SampleMiddleware extends IntentMiddleware { + boot( + req: Request, + res: Response, + next: MiddlewareNext, + ): void | Promise { + console.log('inside middleware'); + next(); + } +} diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index 91c7574..7154a8e 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -4,3 +4,4 @@ export * from './middlewares/middleware'; export * from './middlewares/configurator'; export * from './server'; export * from './request-mixin'; +export { MiddlewareNext } from 'hyper-express'; diff --git a/packages/core/lib/rest/foundation/middlewares/configurator.ts b/packages/core/lib/rest/foundation/middlewares/configurator.ts index d270764..79c1363 100644 --- a/packages/core/lib/rest/foundation/middlewares/configurator.ts +++ b/packages/core/lib/rest/foundation/middlewares/configurator.ts @@ -1,11 +1,11 @@ import { Type } from '../../../interfaces'; -import { RequestMethod } from '../../http-server/methods'; +import { HttpMethods } from '../../http-server'; import { IntentMiddleware } from './middleware'; type MiddlewareRuleApplicationInfo = | string | Type - | { path: string; method: RequestMethod }; + | { path: string; method: HttpMethods }; export class MiddlewareConfigurator { private rules: { [key: string]: MiddlewareRule } = {}; @@ -31,7 +31,7 @@ export class MiddlewareConfigurator { export class MiddlewareRule { public appliedFor: Array = []; - public excludedFor: Array = + public excludedFor: Array = []; constructor(public middleware: Type) {} @@ -42,7 +42,7 @@ export class MiddlewareRule { } exclude( - ...path: Array + ...path: Array ): this { this.excludedFor.push(...path); return this; diff --git a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts new file mode 100644 index 0000000..4406834 --- /dev/null +++ b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts @@ -0,0 +1,79 @@ +import { ModuleRef } from '@nestjs/core'; +import { Type } from '../../../interfaces'; +import { MiddlewareConfigurator } from './configurator'; +import { IntentMiddleware } from './middleware'; +import { CONTROLLER_KEY } from '../../http-server/constants'; + +export class MiddlewareComposer { + private middlewareRoute = new Map(); + private middlewareMap: Record = {}; + + constructor( + private moduleRef: ModuleRef, + private middlewareConfigurator: MiddlewareConfigurator, + private middlewares: Type[], + ) {} + + async handle() { + /** + * Prepares a map like + * + * "GET:/route" => Middleware Collection + * "*:/route" => Middleware Collection + */ + const routeMethodBasedMiddlewares = {}; + for (const rule of this.middlewareConfigurator.getAllRules()) { + const applyMiddlewareFor = []; + + for (const appliedFor of rule.appliedFor) { + let routeUniqueKey = undefined; + if (typeof appliedFor === 'string') { + console.log(appliedFor); + routeUniqueKey = `*:${appliedFor}`; + await this.setMiddlewareForRoute(routeUniqueKey, rule.middleware); + } else if ( + typeof appliedFor === 'object' && + appliedFor.path && + appliedFor.method + ) { + routeUniqueKey = `${appliedFor.method}:${appliedFor.path}`; + this.setMiddlewareForRoute(routeUniqueKey, rule.middleware); + } else { + const controller = Reflect.getMetadata(CONTROLLER_KEY, appliedFor); + } + } + + console.log(this.middlewareRoute); + + // const methodNames = this.metadataScanner.getAllMethodNames(instance); + // for (const methodName of methodNames) { + // const route = await this.scanFullRoute( + // instance, + // methodName, + // errorHandler, + // ); + // route && routes.push(route); + // } + } + + console.log(this.middlewares, this.middlewareConfigurator); + } + + async setMiddlewareForRoute( + routeKey: string, + middleware: Type, + ) { + const existingMiddlewares = this.middlewareRoute.get(routeKey) as any[]; + if (existingMiddlewares) { + const middlewareInstance = await this.moduleRef.create(middleware); + this.middlewareRoute.set( + routeKey, + existingMiddlewares.concat(middlewareInstance), + ); + return; + } + + const middlewareInstance = await this.moduleRef.create(middleware); + this.middlewareRoute.set(routeKey, [middlewareInstance]); + } +} diff --git a/packages/core/lib/rest/foundation/middlewares/middleware.ts b/packages/core/lib/rest/foundation/middlewares/middleware.ts index 1fb9ae5..ca0a1a2 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware.ts @@ -1,16 +1,16 @@ import { NestMiddleware } from '@nestjs/common'; import { NextFunction } from 'express'; -import { Request } from 'hyper-express'; -import { Response } from '../../http-server'; +import { Request, Response } from '../../http-server'; +import { MiddlewareNext } from 'hyper-express'; export abstract class IntentMiddleware implements NestMiddleware { - async use(req: Request, res: Response, next: NextFunction): Promise { + async use(req: Request, res: Response, next: MiddlewareNext): Promise { await this.boot(req, res, next); } abstract boot( req: Request, res: Response, - next: NextFunction, + next: MiddlewareNext, ): void | Promise; } diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 08e53cd..b8bcb17 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -16,6 +16,8 @@ import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; import { Server } from 'hyper-express'; import { HyperServer, RouteExplorer } from '../http-server'; +import { MiddlewareConfigurator } from './middlewares/configurator'; +import { MiddlewareComposer } from './middlewares/middleware-composer'; const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; @@ -58,10 +60,23 @@ export class IntentHttpServer { useContainer(app.select(module), { fallbackOnErrors: true }); - const routeExplorer = new RouteExplorer(); + const middlewareConfigurator = new MiddlewareConfigurator(); + this.kernel.routeMiddlewares(middlewareConfigurator); + + const composer = new MiddlewareComposer( + mr, + middlewareConfigurator, + this.kernel.middlewares(), + ); + + composer.handle(); + + const routeExplorer = new RouteExplorer(ds, ms, mr); const routes = await routeExplorer .useGlobalGuards(globalGuards) - .exploreFullRoutes(ds, ms, mr, errorHandler); + .useGlobalMiddlewares(this.kernel.middlewares()) + .useRouteMiddlewares(middlewareConfigurator) + .exploreFullRoutes(errorHandler); const serverOptions = config.get('http.server'); diff --git a/packages/core/lib/rest/http-server/decorators.ts b/packages/core/lib/rest/http-server/decorators.ts index c81029c..c5106db 100644 --- a/packages/core/lib/rest/http-server/decorators.ts +++ b/packages/core/lib/rest/http-server/decorators.ts @@ -9,22 +9,12 @@ import { METHOD_KEY, METHOD_PATH, } from './constants'; +import { HttpMethods } from './interfaces'; export type ControllerOptions = { host?: string; }; -export enum HttpMethods { - GET = 'GET', - POST = 'POST', - PUT = 'PUT', - PATCH = 'PATCH', - OPTIONS = 'OPTIONS', - HEAD = 'HEAD', - DELETE = 'DELETE', - ANY = 'ANY', -} - export function Controller(path?: string, options?: ControllerOptions) { return applyDecorators(Injectable(), ControllerMetadata(path, options)); } diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index 10aab66..e5c9f94 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -9,3 +9,4 @@ export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; export * from './request'; +export * from './interfaces'; diff --git a/packages/core/lib/rest/http-server/interfaces.ts b/packages/core/lib/rest/http-server/interfaces.ts index 4184bcf..5d24717 100644 --- a/packages/core/lib/rest/http-server/interfaces.ts +++ b/packages/core/lib/rest/http-server/interfaces.ts @@ -4,3 +4,14 @@ export type HttpRoute = { httpHandler?: any; middlewares?: any[]; }; + +export enum HttpMethods { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', + OPTIONS = 'OPTIONS', + HEAD = 'HEAD', + DELETE = 'DELETE', + ANY = 'ANY', +} diff --git a/packages/core/lib/rest/http-server/methods.ts b/packages/core/lib/rest/http-server/methods.ts deleted file mode 100644 index 8a92b2c..0000000 --- a/packages/core/lib/rest/http-server/methods.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum RequestMethod { - GET = 0, - POST = 1, - PUT = 2, - DELETE = 3, - PATCH = 4, - ALL = 5, - OPTIONS = 6, - HEAD = 7, - SEARCH = 8, -} diff --git a/packages/core/lib/rest/http-server/request.ts b/packages/core/lib/rest/http-server/request.ts index b6d8765..937cc49 100644 --- a/packages/core/lib/rest/http-server/request.ts +++ b/packages/core/lib/rest/http-server/request.ts @@ -11,20 +11,20 @@ import { join } from 'path'; import { ConfigService } from '../../config'; export const createRequestFromHyper = async (hReq: HyperRequest) => { - const headers = { ...hReq.headers }; + const headers = hReq.headers; + const enabledParsers = (ConfigService.get('http.parsers') as string[]) || []; - const enabledParsers = ConfigService.get('http.parsers') || []; const contentType = headers['content-type'] || ''; - let body = undefined; + if ( enabledParsers.includes('urlencoded') && - contentType.includes('application/x-www-form-urlencoded') + contentType === 'application/x-www-form-urlencoded' ) { body = await hReq.urlencoded(); } else if ( enabledParsers.includes('json') && - contentType.includes('application/json') + contentType === 'application/json' ) { body = await hReq.json(); } else if ( @@ -36,14 +36,11 @@ export const createRequestFromHyper = async (hReq: HyperRequest) => { for (const [key, value] of multipartData.entries()) { body[key] = value; } - } else if ( - enabledParsers.includes('plain') && - contentType.includes('text/plain') - ) { + } else if (enabledParsers.includes('plain') && contentType === 'text/plain') { body = await hReq.text(); } else if ( - (enabledParsers.includes('html') && contentType.includes('text/html')) || - (enabledParsers.includes('xml') && contentType.includes('application/xml')) + (enabledParsers.includes('html') && contentType === 'text/html') || + (enabledParsers.includes('xml') && contentType === 'application/xml') ) { body = (await hReq.buffer()).toString(); } else { @@ -56,9 +53,8 @@ export const createRequestFromHyper = async (hReq: HyperRequest) => { hReq.url, headers, { ...hReq.query_parameters }, - { ...hReq.path_parameters }, + hReq.path_parameters, body, - hReq.text.bind(hReq), hReq.buffer.bind(hReq), hReq.path, hReq.protocol, @@ -78,7 +74,6 @@ export class Request { public readonly query: Record, public readonly params: Record, public readonly body: any, - public readonly text: () => Promise, public readonly buffer: () => Promise, public readonly path: string, public readonly protocol: string, diff --git a/packages/core/lib/rest/http-server/response.ts b/packages/core/lib/rest/http-server/response.ts index 5bb97e9..d88e9b7 100644 --- a/packages/core/lib/rest/http-server/response.ts +++ b/packages/core/lib/rest/http-server/response.ts @@ -94,8 +94,8 @@ export class Response { /** * Set the headers */ - for (const [key, value] of this.responseHeaders.entries()) { - res.setHeader(key, value); + for (const key of this.responseHeaders.keys()) { + res.setHeader(key, this.responseHeaders.get(key)); } if (this.bodyData instanceof StreamableFile) { @@ -121,7 +121,7 @@ export class Response { } // this.bodyData.getStream().once('error') - res.stream(this.bodyData.getStream()); + return res.stream(this.bodyData.getStream()); } return res.send(this.bodyData); diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index b779909..b631888 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -1,13 +1,17 @@ import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; import { join } from 'path'; import { HttpRoute } from './interfaces'; -import { Request, Response as HResponse } from 'hyper-express'; +import { Request, Response as HResponse, MiddlewareNext } from 'hyper-express'; import { HttpExecutionContext } from './contexts/http-execution-context'; import { HttpRouteHandler } from './http-handler'; import { Response } from './response'; import { ExecutionContext } from './contexts/execution-context'; import { IntentExceptionFilter } from '../../exceptions'; -import { IntentGuard } from '../foundation'; +import { + IntentGuard, + IntentMiddleware, + MiddlewareConfigurator, +} from '../foundation'; import { Type } from '../../interfaces'; import { CONTROLLER_KEY, @@ -21,15 +25,20 @@ import { createRequestFromHyper } from './request'; export class RouteExplorer { guards: Type[] = []; + middlewares: Type[] = []; + middlewareConfigurator: MiddlewareConfigurator; + + constructor( + private discoveryService: DiscoveryService, + private metadataScanner: MetadataScanner, + private moduleRef: ModuleRef, + ) {} async exploreFullRoutes( - discoveryService: DiscoveryService, - metadataScanner: MetadataScanner, - moduleRef: ModuleRef, errorHandler: IntentExceptionFilter, ): Promise { const routes = []; - const providers = discoveryService.getProviders(); + const providers = this.discoveryService.getProviders(); for (const provider of providers) { const { instance } = provider; // if ( @@ -40,12 +49,11 @@ export class RouteExplorer { // return; // } - const methodNames = metadataScanner.getAllMethodNames(instance); + const methodNames = this.metadataScanner.getAllMethodNames(instance); for (const methodName of methodNames) { const route = await this.scanFullRoute( instance, methodName, - moduleRef, errorHandler, ); route && routes.push(route); @@ -99,7 +107,6 @@ export class RouteExplorer { async scanFullRoute( instance: any, key: string, - moduleRef: ModuleRef, errorHandler: IntentExceptionFilter, ): Promise { const controllerKey = Reflect.getMetadata( @@ -124,20 +131,18 @@ export class RouteExplorer { const composedGuards = []; for (const globalGuard of this.guards) { - composedGuards.push(await moduleRef.create(globalGuard)); + composedGuards.push(await this.moduleRef.create(globalGuard)); } for (const guardType of composedGuardTypes) { - composedGuards.push(await moduleRef.create(guardType)); + composedGuards.push(await this.moduleRef.create(guardType)); } const middlewares = []; - const routeArgs = Reflect.getMetadata( - ROUTE_ARGS, - instance.constructor, - key, - ) as RouteArgType[]; + const routeArgs = + Reflect.getMetadata(ROUTE_ARGS, instance.constructor, key) || + ([] as RouteArgType[]); const handler = new HttpRouteHandler( middlewares, @@ -146,18 +151,20 @@ export class RouteExplorer { errorHandler, ); - const cb = async (hReq: Request, hRes: HResponse) => { + const cb = async (hReq: Request, hRes: HResponse, next: MiddlewareNext) => { const req = await createRequestFromHyper(hReq); const httpContext = new HttpExecutionContext(req, new Response()); const context = new ExecutionContext(httpContext, instance, methodRef); + const args = []; + for (const routeArg of routeArgs) { - if (routeArg.handler) { - args.push(routeArg.handler(routeArg.data, context)); - } else { - args.push(httpContext.getInjectableValueFromArgType(routeArg)); - } + args.push( + routeArg.handler + ? routeArg.handler(routeArg.data, context) + : httpContext.getInjectableValueFromArgType(routeArg), + ); } const res = await handler.handle(context, args); @@ -176,4 +183,16 @@ export class RouteExplorer { this.guards.push(...guards); return this; } + + useGlobalMiddlewares(middlewares: Type[]): RouteExplorer { + this.middlewares = middlewares; + return this; + } + + useRouteMiddlewares( + middlewareConfigurator: MiddlewareConfigurator, + ): RouteExplorer { + this.middlewareConfigurator = middlewareConfigurator; + return this; + } } diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index ee3fd15..909c6f5 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -1,7 +1,7 @@ -import { HttpMethods } from './decorators'; import HyperExpress from 'hyper-express'; -import { HttpRoute } from './interfaces'; -import { HttpConfig } from '../../interfaces'; +import { HttpMethods, HttpRoute } from './interfaces'; +import { HttpStatus } from '@nestjs/common'; +import './request-ext'; export class HyperServer { protected hyper: HyperExpress.Server; @@ -51,6 +51,9 @@ export class HyperServer { } } + // this.hyper.set_not_found_handler((req: HyperExpress.Request, res: HyperExpress.Response) => { + // return res.status(HttpStatus.NOT_FOUND).type('text').send() + // }) return this.hyper; } } diff --git a/packages/core/lib/utils/extension-to-mime.ts b/packages/core/lib/utils/extension-to-mime.ts index ecf9bf4..197291f 100644 --- a/packages/core/lib/utils/extension-to-mime.ts +++ b/packages/core/lib/utils/extension-to-mime.ts @@ -2402,4 +2402,4 @@ export const EXTENSTION_TO_MIME = { zir: 'application/vnd.zul', zirz: 'application/vnd.zul', zmm: 'application/vnd.handheld-entertainment+xml', -}; +} as const; From d7c7cccc8a290466a3b7a58f34c92a639eb10edf Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Mon, 25 Nov 2024 01:23:14 +0530 Subject: [PATCH 10/27] fix(core): http-server bugs --- .../sample-app/app/http/controllers/icon.ts | 20 +- .../sample-app/app/http/guards/custom.ts | 9 +- integrations/sample-app/app/http/kernel.ts | 10 +- .../sample-app/app/http/middlewares/global.ts | 13 + .../sample-app/app/http/middlewares/sample.ts | 6 +- .../core/lib/console/commands/route-list.ts | 3 +- .../lib/exceptions/base-exception-handler.ts | 4 +- .../core/lib/foundation/module-builder.ts | 57 +-- .../lib/rest/foundation/controller-scanner.ts | 35 ++ .../middlewares/middleware-composer.ts | 121 +++++-- .../rest/foundation/middlewares/middleware.ts | 14 +- packages/core/lib/rest/foundation/server.ts | 29 +- .../contexts/http-execution-context.ts | 4 +- packages/core/lib/rest/http-server/index.ts | 2 +- packages/core/lib/rest/http-server/request.ts | 332 ------------------ .../lib/rest/http-server/request/extension.ts | 187 ++++++++++ .../lib/rest/http-server/request/index.ts | 4 + .../rest/http-server/request/interfaces.ts | 41 +++ .../rest/http-server/request/middleware.ts | 47 +++ .../http-server/request/multipart-handler.ts | 65 ++++ .../lib/rest/http-server/route-explorer.ts | 37 +- packages/core/lib/rest/http-server/server.ts | 34 +- packages/core/lib/rest/middlewares/cors.ts | 7 +- packages/core/lib/rest/middlewares/helmet.ts | 6 +- packages/core/lib/serviceProvider.ts | 2 +- packages/core/lib/validator/index.ts | 2 +- 26 files changed, 614 insertions(+), 477 deletions(-) create mode 100644 integrations/sample-app/app/http/middlewares/global.ts create mode 100644 packages/core/lib/rest/foundation/controller-scanner.ts delete mode 100644 packages/core/lib/rest/http-server/request.ts create mode 100644 packages/core/lib/rest/http-server/request/extension.ts create mode 100644 packages/core/lib/rest/http-server/request/index.ts create mode 100644 packages/core/lib/rest/http-server/request/interfaces.ts create mode 100644 packages/core/lib/rest/http-server/request/middleware.ts create mode 100644 packages/core/lib/rest/http-server/request/multipart-handler.ts diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index d968f82..5829451 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -17,19 +17,23 @@ import { Request } from '@intentjs/core/dist/lib/rest/http-server/request'; @Controller('/icon') @UseGuard(CustomGuard) export class IntentController { - constructor() {} + public service: any; + + constructor() { + this.service = null; + } @Get('/:name') @UseGuard(CustomGuard) async getHello( @Req() req: Request, - @Query() query: Record, - @Query('b') bQuery: string, - @Param('name') name: string, - @Param() pathParams: string, - @Host() hostname: string, - @IP() ips: string, - @Accepts() accepts: string, + // @Query() query: Record, + // @Query('b') bQuery: string, + // @Param('name') name: string, + // @Param() pathParams: string, + // @Host() hostname: string, + // @IP() ips: string, + // @Accepts() accepts: string, ) { // console.log( // await req.file('file1'), diff --git a/integrations/sample-app/app/http/guards/custom.ts b/integrations/sample-app/app/http/guards/custom.ts index ebada16..c896646 100644 --- a/integrations/sample-app/app/http/guards/custom.ts +++ b/integrations/sample-app/app/http/guards/custom.ts @@ -1,5 +1,10 @@ -import { Injectable, IntentGuard, Reflector, Response } from '@intentjs/core'; -import { Request } from '@intentjs/core/dist/lib/rest/http-server/request'; +import { + Injectable, + IntentGuard, + Reflector, + Request, + Response, +} from '@intentjs/core'; @Injectable() export class CustomGuard extends IntentGuard { diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index 1e2df82..7fc8a9f 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -12,6 +12,8 @@ import { import { UserController } from './controllers/app'; import { AuthController } from './controllers/auth'; import { SampleMiddleware } from './middlewares/sample'; +import { IntentController } from './controllers/icon'; +import { GlobalMiddleware } from './middlewares/global'; export class HttpKernel extends Kernel { /** @@ -30,7 +32,7 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/middlewares */ public middlewares(): Type[] { - return [CorsMiddleware, HelmetMiddleware]; + return [GlobalMiddleware]; } /** @@ -43,7 +45,11 @@ export class HttpKernel extends Kernel { public routeMiddlewares(configurator: MiddlewareConfigurator) { configurator .use(SampleMiddleware) - .for({ path: '/icon/sample', method: HttpMethods.POST }); + .for({ path: '/icon/sample', method: HttpMethods.POST }) + .for(IntentController) + .exclude('/icon/:name'); + + configurator.use(GlobalMiddleware).exclude('/icon/:name'); configurator.use(SampleMiddleware).for('/icon/plain'); } diff --git a/integrations/sample-app/app/http/middlewares/global.ts b/integrations/sample-app/app/http/middlewares/global.ts new file mode 100644 index 0000000..426b3cf --- /dev/null +++ b/integrations/sample-app/app/http/middlewares/global.ts @@ -0,0 +1,13 @@ +import { + IntentMiddleware, + MiddlewareNext, + Request, + Response, +} from '@intentjs/core'; + +export class GlobalMiddleware extends IntentMiddleware { + use(req: Request, res: Response, next: MiddlewareNext): void | Promise { + console.log('inside global middleware'); + next(); + } +} diff --git a/integrations/sample-app/app/http/middlewares/sample.ts b/integrations/sample-app/app/http/middlewares/sample.ts index b441d00..faedab8 100644 --- a/integrations/sample-app/app/http/middlewares/sample.ts +++ b/integrations/sample-app/app/http/middlewares/sample.ts @@ -6,11 +6,7 @@ import { } from '@intentjs/core'; export class SampleMiddleware extends IntentMiddleware { - boot( - req: Request, - res: Response, - next: MiddlewareNext, - ): void | Promise { + use(req: Request, res: Response, next: MiddlewareNext): void | Promise { console.log('inside middleware'); next(); } diff --git a/packages/core/lib/console/commands/route-list.ts b/packages/core/lib/console/commands/route-list.ts index 323cc66..ab0a19d 100644 --- a/packages/core/lib/console/commands/route-list.ts +++ b/packages/core/lib/console/commands/route-list.ts @@ -10,8 +10,7 @@ export class ListRouteCommand { async handle() { const ds = this.moduleRef.get(DiscoveryService, { strict: false }); const ms = this.moduleRef.get(MetadataScanner, { strict: false }); - - const routeExplorer = new RouteExplorer(); + const routeExplorer = new RouteExplorer(ds, ms, this.moduleRef); const routes = routeExplorer.explorePlainRoutes(ds, ms); const formattedRows = columnify(routes, { padStart: 2 }); diff --git a/packages/core/lib/exceptions/base-exception-handler.ts b/packages/core/lib/exceptions/base-exception-handler.ts index de9faf4..8c9b2cc 100644 --- a/packages/core/lib/exceptions/base-exception-handler.ts +++ b/packages/core/lib/exceptions/base-exception-handler.ts @@ -16,7 +16,7 @@ export abstract class IntentExceptionFilter { return '*'; } - catch(context: ExecutionContext, exception: any) { + async catch(context: ExecutionContext, exception: any): Promise { const ctx = context.switchToHttp(); this.reportToSentry(exception); @@ -26,7 +26,7 @@ export abstract class IntentExceptionFilter { return this.handleHttp(context, exception); } - handleHttp(context: ExecutionContext, exception: any) { + async handleHttp(context: ExecutionContext, exception: any): Promise { const res = context.switchToHttp().getResponse(); const debugMode = ConfigService.get('app.debug'); diff --git a/packages/core/lib/foundation/module-builder.ts b/packages/core/lib/foundation/module-builder.ts index 96b4b64..3cfda85 100644 --- a/packages/core/lib/foundation/module-builder.ts +++ b/packages/core/lib/foundation/module-builder.ts @@ -1,66 +1,17 @@ -import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; -import { APP_GUARD } from '@nestjs/core'; -import { Type } from '../interfaces'; -import { IntentGuard, Kernel } from '../rest'; -import { MiddlewareConfigurator } from '../rest/foundation/middlewares/configurator'; +import { Module } from '@nestjs/common'; +import { Kernel } from '../rest'; import { IntentAppContainer } from './app-container'; export class ModuleBuilder { static build(container: IntentAppContainer, kernel?: Kernel) { const providers = container.scanProviders(); - const controllers = kernel?.controllers() || []; - /** - * Scan for global middlewares - */ - const globalMiddlewares = kernel?.middlewares() || []; - const globalGuards = ModuleBuilder.buildGlobalGuardProviders( - kernel?.guards() || [], - ); - @Module({ imports: container.scanImports(), - providers: [...providers, ...globalGuards], - controllers: controllers, + providers: [...providers], }) - class AppModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - if (!kernel) return; - /** - * Apply global middleware for all routes if any found. - */ - if (globalMiddlewares.length) { - consumer.apply(...globalMiddlewares).forRoutes(''); - } - - const middlewareConfigurator = new MiddlewareConfigurator(); - kernel.routeMiddlewares(middlewareConfigurator); - /** - * Apply route specific middlewares - */ - if (middlewareConfigurator.hasAnyRule()) { - for (const rule of middlewareConfigurator.getAllRules()) { - consumer - .apply(rule.middleware) - .exclude(...rule.excludedFor) - .forRoutes(...rule.appliedFor); - } - } - } - } + class AppModule {} return AppModule; } - - static buildGlobalGuardProviders(guards: Type[]) { - const providers = []; - for (const guard of guards) { - providers.push({ - provide: APP_GUARD, - useClass: guard, - }); - } - - return providers; - } } diff --git a/packages/core/lib/rest/foundation/controller-scanner.ts b/packages/core/lib/rest/foundation/controller-scanner.ts new file mode 100644 index 0000000..a25fd76 --- /dev/null +++ b/packages/core/lib/rest/foundation/controller-scanner.ts @@ -0,0 +1,35 @@ +import { join } from 'path'; +import { Type } from '../../interfaces'; +import { HttpRoute } from '../http-server'; +import { + CONTROLLER_KEY, + METHOD_KEY, + METHOD_PATH, +} from '../http-server/constants'; + +export class ControllerScanner { + handle(cls: Type): HttpRoute[] { + const controllerKey = Reflect.getMetadata(CONTROLLER_KEY, cls); + + const methodNames = Object.getOwnPropertyNames(cls['prototype']); + + if (!controllerKey) return; + + const routes = []; + for (const key of methodNames) { + const pathMethod = Reflect.getMetadata(METHOD_KEY, cls['prototype'], key); + const methodPath = Reflect.getMetadata( + METHOD_PATH, + cls['prototype'], + key, + ); + + if (!pathMethod) continue; + + const fullHttpPath = join(controllerKey, methodPath); + routes.push({ method: pathMethod, path: fullHttpPath }); + } + + return routes; + } +} diff --git a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts index 4406834..d492ef7 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts @@ -3,9 +3,12 @@ import { Type } from '../../../interfaces'; import { MiddlewareConfigurator } from './configurator'; import { IntentMiddleware } from './middleware'; import { CONTROLLER_KEY } from '../../http-server/constants'; +import { ControllerScanner } from '../controller-scanner'; export class MiddlewareComposer { - private middlewareRoute = new Map(); + private middlewareRoute = new Map(); + private excludedMiddlewareRoutes = new Map(); + private middlewareMap: Record = {}; constructor( @@ -14,55 +17,115 @@ export class MiddlewareComposer { private middlewares: Type[], ) {} - async handle() { + async globalMiddlewares(): Promise { + const globalMiddlewares = []; + for (const middleware of this.middlewares) { + const middlewareInstance = await this.moduleRef.create(middleware); + globalMiddlewares.push(middlewareInstance); + } + return globalMiddlewares; + } + + async getRouteMiddlewares(): Promise> { /** * Prepares a map like * * "GET:/route" => Middleware Collection * "*:/route" => Middleware Collection */ - const routeMethodBasedMiddlewares = {}; for (const rule of this.middlewareConfigurator.getAllRules()) { - const applyMiddlewareFor = []; + for (const excludedPath of rule.excludedFor) { + console.log('excluded ==> ', excludedPath); + if ( + typeof excludedPath === 'object' && + excludedPath.path && + excludedPath.method + ) { + this.excludeMiddlewareForRoute( + `${excludedPath.method}:${excludedPath.path}`, + rule.middleware, + ); + } else if (typeof excludedPath === 'string') { + await this.excludeMiddlewareForRoute( + `*:${excludedPath}`, + rule.middleware, + ); + } + } + } + for (const rule of this.middlewareConfigurator.getAllRules()) { for (const appliedFor of rule.appliedFor) { - let routeUniqueKey = undefined; if (typeof appliedFor === 'string') { - console.log(appliedFor); - routeUniqueKey = `*:${appliedFor}`; - await this.setMiddlewareForRoute(routeUniqueKey, rule.middleware); - } else if ( - typeof appliedFor === 'object' && - appliedFor.path && - appliedFor.method - ) { - routeUniqueKey = `${appliedFor.method}:${appliedFor.path}`; - this.setMiddlewareForRoute(routeUniqueKey, rule.middleware); + await this.setMiddlewareForRoute(appliedFor, '*', rule.middleware); + } else if (typeof appliedFor === 'object' && appliedFor.path) { + this.setMiddlewareForRoute( + appliedFor.path, + appliedFor.method, + rule.middleware, + ); } else { - const controller = Reflect.getMetadata(CONTROLLER_KEY, appliedFor); + const routes = new ControllerScanner().handle( + appliedFor as Type, + ); + for (const route of routes) { + this.setMiddlewareForRoute( + route.path, + route.method || '*', + rule.middleware, + ); + } } } + } + + return this.middlewareRoute; + } + + async getExcludedMiddlewaresForRoutes(): Promise> { + return this.excludedMiddlewareRoutes; + } - console.log(this.middlewareRoute); - - // const methodNames = this.metadataScanner.getAllMethodNames(instance); - // for (const methodName of methodNames) { - // const route = await this.scanFullRoute( - // instance, - // methodName, - // errorHandler, - // ); - // route && routes.push(route); - // } + async excludeMiddlewareForRoute( + routeKey: string, + middleware: Type, + ) { + const existingMiddlewares = this.excludedMiddlewareRoutes.get( + routeKey, + ) as any[]; + if (existingMiddlewares) { + this.excludedMiddlewareRoutes.set( + routeKey, + existingMiddlewares.concat(middleware.name), + ); + return; } - console.log(this.middlewares, this.middlewareConfigurator); + this.excludedMiddlewareRoutes.set(routeKey, [middleware.name]); } async setMiddlewareForRoute( - routeKey: string, + routePath: string, + routeMethod: string, middleware: Type, ) { + const routeKey = `${routeMethod}:${routePath}`; + + /** + * Check if the middleware is excluded for the specified route + */ + const excludedMiddlewareNames = + this.excludedMiddlewareRoutes.get(routeKey) || []; + const excludedMiddlewareNamesWithoutMethod = + this.excludedMiddlewareRoutes.get(`*:${routePath}`) || []; + + const excludedMiddlewares = new Set([ + ...excludedMiddlewareNames, + ...excludedMiddlewareNamesWithoutMethod, + ]); + + if (excludedMiddlewares.has(middleware.name)) return; + const existingMiddlewares = this.middlewareRoute.get(routeKey) as any[]; if (existingMiddlewares) { const middlewareInstance = await this.moduleRef.create(middleware); diff --git a/packages/core/lib/rest/foundation/middlewares/middleware.ts b/packages/core/lib/rest/foundation/middlewares/middleware.ts index ca0a1a2..bb91dcc 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware.ts @@ -1,14 +1,16 @@ -import { NestMiddleware } from '@nestjs/common'; -import { NextFunction } from 'express'; import { Request, Response } from '../../http-server'; import { MiddlewareNext } from 'hyper-express'; -export abstract class IntentMiddleware implements NestMiddleware { - async use(req: Request, res: Response, next: MiddlewareNext): Promise { - await this.boot(req, res, next); +export abstract class IntentMiddleware { + async handle( + req: Request, + res: Response, + next: MiddlewareNext, + ): Promise { + await this.use(req, res, next); } - abstract boot( + abstract use( req: Request, res: Response, next: MiddlewareNext, diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index b8bcb17..6d3e847 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -14,8 +14,15 @@ import { Kernel } from '../foundation/kernel'; import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; -import { Server } from 'hyper-express'; -import { HyperServer, RouteExplorer } from '../http-server'; +import { Response as HyperResponse, Server } from 'hyper-express'; +import { + ExecutionContext, + HttpExecutionContext, + HyperServer, + Request, + Response, + RouteExplorer, +} from '../http-server'; import { MiddlewareConfigurator } from './middlewares/configurator'; import { MiddlewareComposer } from './middlewares/middleware-composer'; @@ -69,13 +76,17 @@ export class IntentHttpServer { this.kernel.middlewares(), ); - composer.handle(); + const globalMiddlewares = await composer.globalMiddlewares(); + const routeMiddlewares = await composer.getRouteMiddlewares(); + const excludedMiddlewares = + await composer.getExcludedMiddlewaresForRoutes(); const routeExplorer = new RouteExplorer(ds, ms, mr); const routes = await routeExplorer .useGlobalGuards(globalGuards) - .useGlobalMiddlewares(this.kernel.middlewares()) - .useRouteMiddlewares(middlewareConfigurator) + .useGlobalMiddlewares(globalMiddlewares) + .useRouteMiddlewares(routeMiddlewares) + .useExcludeMiddlewareRoutes(excludedMiddlewares) .exploreFullRoutes(errorHandler); const serverOptions = config.get('http.server'); @@ -83,6 +94,14 @@ export class IntentHttpServer { const customServer = new HyperServer(); const server = await customServer.build(routes, serverOptions); + server.set_error_handler((hReq: any, hRes: HyperResponse, error: Error) => { + const res = new Response(); + const httpContext = new HttpExecutionContext(hReq, new Response()); + const context = new ExecutionContext(httpContext, null, null); + errorHandler.catch(context, error); + res.reply(hReq, hRes); + }); + this.configureErrorReporter(config.get('app.sentry')); const port = config.get('app.port'); diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index f386634..f6b8c36 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -27,9 +27,9 @@ export class HttpExecutionContext { case RouteParamtypes.QUERY: if (data) { - return this.request.query[data as string]; + return this.request.query_parameters[data as string]; } - return this.request.query; + return { ...this.request.query_parameters }; case RouteParamtypes.ACCEPTS: return this.request.accepts(); diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index e5c9f94..fa594e7 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -8,5 +8,5 @@ export * from './server'; export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; -export * from './request'; export * from './interfaces'; +export * from './request'; diff --git a/packages/core/lib/rest/http-server/request.ts b/packages/core/lib/rest/http-server/request.ts deleted file mode 100644 index 937cc49..0000000 --- a/packages/core/lib/rest/http-server/request.ts +++ /dev/null @@ -1,332 +0,0 @@ -import uWS from 'uWebSockets.js'; -import { Request as HyperRequest, MultipartHandler } from 'hyper-express'; -import { isEmpty, Str } from '../../utils'; -import { EXTENSTION_TO_MIME } from '../../utils/extension-to-mime'; -import { Type } from '../../interfaces'; -import { Validator } from '../../validator'; -import { tmpdir } from 'os'; -import { ulid } from 'ulid'; -import { UploadedFile } from '../../storage/file-handlers/uploaded-file'; -import { join } from 'path'; -import { ConfigService } from '../../config'; - -export const createRequestFromHyper = async (hReq: HyperRequest) => { - const headers = hReq.headers; - const enabledParsers = (ConfigService.get('http.parsers') as string[]) || []; - - const contentType = headers['content-type'] || ''; - let body = undefined; - - if ( - enabledParsers.includes('urlencoded') && - contentType === 'application/x-www-form-urlencoded' - ) { - body = await hReq.urlencoded(); - } else if ( - enabledParsers.includes('json') && - contentType === 'application/json' - ) { - body = await hReq.json(); - } else if ( - enabledParsers.includes('formdata') && - contentType.includes('multipart/form-data') - ) { - body = {}; - const multipartData = await processMultipartData(hReq); - for (const [key, value] of multipartData.entries()) { - body[key] = value; - } - } else if (enabledParsers.includes('plain') && contentType === 'text/plain') { - body = await hReq.text(); - } else if ( - (enabledParsers.includes('html') && contentType === 'text/html') || - (enabledParsers.includes('xml') && contentType === 'application/xml') - ) { - body = (await hReq.buffer()).toString(); - } else { - body = await hReq.buffer(); - } - - return new Request( - hReq.raw, - hReq.method, - hReq.url, - headers, - { ...hReq.query_parameters }, - hReq.path_parameters, - body, - hReq.buffer.bind(hReq), - hReq.path, - hReq.protocol, - hReq.ips, - hReq.multipart.bind(hReq), - ); -}; - -export class Request { - private uploadedFiles = new Map(); - - constructor( - public readonly raw: uWS.HttpRequest, - public readonly method: string, - public readonly url: string, - public readonly headers: Record, - public readonly query: Record, - public readonly params: Record, - public readonly body: any, - public readonly buffer: () => Promise, - public readonly path: string, - public readonly protocol: string, - public readonly ip: string[], - public readonly multipart: (handler: MultipartHandler) => Promise, - ) {} - - $dto: null; - $user: null; - logger() {} - - setDto(dto: any): void { - this.$dto = dto; - } - - dto(): any { - return this.$dto; - } - - all(): Record { - return { - ...(this.query || {}), - ...(this.params || {}), - ...(this.body || {}), - }; - } - - input(name: string, defaultValue?: T): T { - const payload = this.all(); - return name in payload ? payload[name] : defaultValue; - } - - string(name: string): string { - const value = this.input(name); - return value && value.toString(); - } - - number(name: string): number { - const value = this.input(name); - return +value; - } - - boolean(name: string): boolean { - const payload = this.all(); - const val = payload[name] as string; - return [true, 'yes', 'on', '1', 1, 'true'].includes(val.toLowerCase()); - } - - hasHeader(name: string): boolean { - return name in this.headers; - } - - bearerToken(): string { - const authHeader = this.headers['authorization']; - const asArray = authHeader?.split(' '); - if (!isEmpty(asArray)) return asArray[1]; - return undefined; - } - - httpHost(): string { - return this.protocol; - } - - isHttp(): boolean { - return this.httpHost() === 'http'; - } - - isHttps(): boolean { - return this.httpHost() === 'https'; - } - - fullUrl(): string { - return this.url; - } - - isMethod(method: string): boolean { - return this.method.toLowerCase() === method.toLowerCase(); - } - - contentType(): string[] { - return this.headers['content-type']?.split(',') || []; - } - - getAcceptableContentTypes(): string { - return this.headers['accept']; - } - - accepts(): string[] { - const getAcceptableContentTypes = this.headers['accept'] || []; - return (this.headers['accept'] || '').split(','); - } - - expectsJson(): boolean { - return this.accepts().includes(EXTENSTION_TO_MIME['json']); - } - - async validate(schema: Type): Promise { - const payload = this.all(); - const validator = Validator.compareWith(schema); - const dto = await validator - .addMeta({ ...payload, _headers: { ...this.headers } }) - .validate({ ...payload }); - this.setDto(dto); - return true; - } - - setUser(user: any): void { - this.$user = user; - } - - user(): T { - return this.$user as T; - } - - only(...keys: string[]): Record { - return {}; - } - - except(...keys: string[]): Record { - console.log(keys); - return {}; - } - - isPath(pathPattern: string): boolean { - return false; - } - - has(...keys: string[]): boolean { - const payload = this.all(); - for (const key of keys) { - if (!(key in payload)) return false; - } - - return true; - } - - hasAny(...keys: string[]): boolean { - const payload = this.all(); - for (const key of keys) { - if (key in payload) return true; - } - - return false; - } - - missing(...keys: string[]): boolean { - const payload = this.all(); - for (const key of keys) { - if (key in payload) return false; - } - - return true; - } - - hasHeaders(...keys: string[]): boolean { - for (const key of keys) { - if (!(key in this.headers)) return false; - } - - return true; - } - - hasIncludes(): boolean { - const includes = this.includes(); - return includes === ''; - } - - includes(): string { - return this.string('include'); - } - - files(keys: string): Record { - return {}; - } - - async file( - key: string, - ): Promise { - const fileAtKey = this.body[key]; - const values = [] as UploadedFile[]; - const isArray = Array.isArray(fileAtKey); - if (isArray) { - for (const file of fileAtKey) { - if (file instanceof UploadedFile) { - values.push(file); - } - } - - return values as T; - } - - if (!(fileAtKey instanceof UploadedFile)) return undefined; - - return fileAtKey as T; - } -} - -const processMultipartData = async ( - req: HyperRequest, -): Promise> => { - const fields = new Map(); - const tempDirectory = tmpdir(); - const generateTempFilename = (filename: string) => `${ulid()}-${filename}`; - - try { - await req.multipart(async field => { - /** - * check if the field name is an array based, or an associative index - */ - const isArray = Str.is(field.name, '*[*]'); - const strippedFieldName = Str.before(field.name, '['); - const existingFieldValue = fields.get(strippedFieldName); - - if (isArray && !existingFieldValue) { - fields.set(strippedFieldName, []); - } - - if (field.file) { - const tempFileName = generateTempFilename(field.file.name); - const tempFilePath = join(tempDirectory, tempFileName); - let fileSize = 0; - field.file.stream.on('data', chunk => { - fileSize += chunk.length; - }); - - await field.write(tempFilePath); - - const uploadedFile = new UploadedFile( - field.file.name, - fileSize, - field.mime_type, - tempFileName, - tempFilePath, - ); - if (Array.isArray(existingFieldValue)) { - fields.set( - strippedFieldName, - existingFieldValue.concat(uploadedFile), - ); - } else { - fields.set(strippedFieldName, uploadedFile); - } - } else { - if (Array.isArray(existingFieldValue)) { - fields.set(strippedFieldName, existingFieldValue.concat(field.value)); - } else { - fields.set(strippedFieldName, field.value); - } - } - }); - } catch (e) { - console.error(e); - } - - return fields; -}; diff --git a/packages/core/lib/rest/http-server/request/extension.ts b/packages/core/lib/rest/http-server/request/extension.ts new file mode 100644 index 0000000..d1df1fc --- /dev/null +++ b/packages/core/lib/rest/http-server/request/extension.ts @@ -0,0 +1,187 @@ +import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; +import { isEmpty } from '../../../utils'; +import { Type } from '../../../interfaces'; +import { Validator } from '../../../validator'; +import { EXTENSTION_TO_MIME } from '../../../utils/extension-to-mime'; +import { Request } from './interfaces'; + +export const INTENT_REQUEST_EXTENSIONS = { + $dto: null, + $user: null, + uploadedFiles: new Map(), + + setDto(dto: any): void { + this.$dto = dto; + }, + + dto(): any { + return this.$dto; + }, + + all(): Record { + return { + ...(this.query_parameters || {}), + ...(this.path_parameters || {}), + ...(this.body || {}), + }; + }, + + input(name: string, defaultValue?: T): T { + const payload = this.all(); + return name in payload ? payload[name] : defaultValue; + }, + + string(name: string): string { + const value = this.input(name); + return value && value.toString(); + }, + + number(name: string): number { + const value = this.input(name); + return +value; + }, + + boolean(name: string): boolean { + const payload = this.all(); + const val = payload[name] as string; + return [true, 'yes', 'on', '1', 1, 'true'].includes(val?.toLowerCase()); + }, + + hasHeader(name: string): boolean { + return name in this.headers; + }, + + bearerToken(): string { + const authHeader = this.headers['authorization']; + const asArray = authHeader?.split(' '); + if (!isEmpty(asArray)) return asArray[1]; + return undefined; + }, + + httpHost(): string { + return this.protocol; + }, + + isHttp(): boolean { + return this.httpHost() === 'http'; + }, + + isHttps(): boolean { + return this.httpHost() === 'https'; + }, + + fullUrl(): string { + return this.url; + }, + + isMethod(method: string): boolean { + return this.method.toLowerCase() === method.toLowerCase(); + }, + + contentType(): string { + return this.headers['content-type']; + }, + + getAcceptableContentTypes(): string { + return this.headers['accept']; + }, + + accepts(): string[] { + return (this.headers['accept'] || '').split(','); + }, + + expectsJson(): boolean { + return this.accepts().includes(EXTENSTION_TO_MIME['json']); + }, + + async validate(schema: Type): Promise { + const payload = this.all(); + const validator = Validator.compareWith(schema); + const dto = await validator + .addMeta({ ...payload, _headers: { ...this.headers } }) + .validate({ ...payload }); + this.setDto(dto); + return true; + }, + + setUser(user: any): void { + this.$user = user; + }, + + user(): T { + return this.$user as T; + }, + + only(...keys: string[]): Record { + const payload = this.all(); + return Object.fromEntries( + keys.filter(key => key in payload).map(key => [key, payload[key]]), + ); + }, + + except(...keys: string[]): Record { + const payload = this.all(); + return Object.fromEntries( + Object.entries(payload).filter(([key]) => !keys.includes(key)), + ); + }, + + isPath(pathPattern: string): boolean { + return this.path === pathPattern; + }, + + has(...keys: string[]): boolean { + const payload = this.all(); + return keys.every(key => key in payload); + }, + + hasAny(...keys: string[]): boolean { + const payload = this.all(); + return keys.some(key => key in payload); + }, + + missing(...keys: string[]): boolean { + const payload = this.all(); + return keys.every(key => !(key in payload)); + }, + + hasHeaders(...keys: string[]): boolean { + return keys.every(key => key in this.headers); + }, + + hasIncludes(): boolean { + return Boolean(this.includes()); + }, + + includes(): string { + return this.string('include'); + }, + + files(keys: string): Record { + if (!this.body) return {}; + // return Object.fromEntries( + // Object.entries(this.body).filter( + // ([key]) => + // key === keys && + // (this.body[key] instanceof UploadedFile || + // Array.isArray(this.body[key])), + // ), + // ); + }, + + async file( + key: string, + ): Promise { + const fileAtKey = this.body?.[key]; + if (!fileAtKey) return undefined as T; + + if (Array.isArray(fileAtKey)) { + const values = fileAtKey.filter(file => file instanceof UploadedFile); + return values as T; + } + + if (!(fileAtKey instanceof UploadedFile)) return undefined as T; + + return fileAtKey as T; + }, +} as Request; diff --git a/packages/core/lib/rest/http-server/request/index.ts b/packages/core/lib/rest/http-server/request/index.ts new file mode 100644 index 0000000..c44f94c --- /dev/null +++ b/packages/core/lib/rest/http-server/request/index.ts @@ -0,0 +1,4 @@ +export * from './extension'; +export * from './interfaces'; +export * from './middleware'; +export * from './multipart-handler'; diff --git a/packages/core/lib/rest/http-server/request/interfaces.ts b/packages/core/lib/rest/http-server/request/interfaces.ts new file mode 100644 index 0000000..623e42c --- /dev/null +++ b/packages/core/lib/rest/http-server/request/interfaces.ts @@ -0,0 +1,41 @@ +import { Request as HyperRequest } from 'hyper-express'; +import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; +import { Type } from '../../../interfaces'; + +export interface Request extends HyperRequest { + $dto: any; + $user: any; + uploadedFiles: Map; + setDto(dto: any): void; + dto(): any; + all(): Record; + input(name: string, defaultValue?: T): T; + string(name: string): string; + number(name: string): number; + boolean(name: string): boolean; + hasHeader(name: string): boolean; + bearerToken(): string; + httpHost(): string; + isHttp(): boolean; + isHttps(): boolean; + fullUrl(): string; + isMethod(method: string): boolean; + contentType(): string; + getAcceptableContentTypes(): string; + // accepts(): string[]; + expectsJson(): boolean; + validate(schema: Type): Promise; + setUser(user: any): void; + user(): T; + only(...keys: string[]): Record; + except(...keys: string[]): Record; + isPath(pathPattern: string): boolean; + has(...keys: string[]): boolean; + hasAny(...keys: string[]): boolean; + missing(...keys: string[]): boolean; + hasHeaders(...keys: string[]): boolean; + hasIncludes(): boolean; + includes(): string; + files(keys: string): Record; + file(key: string): Promise; +} diff --git a/packages/core/lib/rest/http-server/request/middleware.ts b/packages/core/lib/rest/http-server/request/middleware.ts new file mode 100644 index 0000000..24323be --- /dev/null +++ b/packages/core/lib/rest/http-server/request/middleware.ts @@ -0,0 +1,47 @@ +import { Request as HyperRequest } from 'hyper-express'; +import { processMultipartData } from './multipart-handler'; +import { INTENT_REQUEST_EXTENSIONS } from './extension'; +import { ConfigService } from '../../../config/service'; + +export const requestMiddleware = async ( + req: HyperRequest, + res: any, + next: () => void, +) => { + Object.assign(req, INTENT_REQUEST_EXTENSIONS); + + const enabledParsers = (ConfigService.get('http.parsers') as string[]) || []; + const contentType = req.headers['content-type'] || ''; + + try { + if (enabledParsers.includes('json') && contentType === 'application/json') { + req.body = await req.json(); + } else if ( + enabledParsers.includes('urlencoded') && + contentType === 'application/x-www-form-urlencoded' + ) { + req.body = await req.urlencoded(); + } else if ( + enabledParsers.includes('formdata') && + contentType.includes('multipart/form-data') + ) { + req.body = await processMultipartData(req); + } else if ( + enabledParsers.includes('plain') && + contentType === 'text/plain' + ) { + req.body = await req.text(); + } else if ( + (enabledParsers.includes('html') && contentType === 'text/html') || + (enabledParsers.includes('xml') && contentType === 'application/xml') + ) { + req.body = (await req.buffer()).toString(); + } else { + req.body = await req.buffer(); + } + } catch (error) { + console.error('Request parsing error:', error); + } + + next(); +}; diff --git a/packages/core/lib/rest/http-server/request/multipart-handler.ts b/packages/core/lib/rest/http-server/request/multipart-handler.ts new file mode 100644 index 0000000..58242fd --- /dev/null +++ b/packages/core/lib/rest/http-server/request/multipart-handler.ts @@ -0,0 +1,65 @@ +import { Request as HyperRequest } from 'hyper-express'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { ulid } from 'ulid'; +import { Str } from '../../../utils'; +import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; + +export const processMultipartData = async ( + req: HyperRequest, +): Promise> => { + const fields = new Map(); + const tempDirectory = tmpdir(); + const generateTempFilename = (filename: string) => `${ulid()}-${filename}`; + + try { + await req.multipart(async field => { + const isArray = Str.is(field.name, '*[*]'); + const strippedFieldName = Str.before(field.name, '['); + const existingFieldValue = fields.get(strippedFieldName); + + if (isArray && !existingFieldValue) { + fields.set(strippedFieldName, []); + } + + if (field.file) { + const tempFileName = generateTempFilename(field.file.name); + const tempFilePath = join(tempDirectory, tempFileName); + let fileSize = 0; + + field.file.stream.on('data', chunk => { + fileSize += chunk.length; + }); + + await field.write(tempFilePath); + + const uploadedFile = new UploadedFile( + field.file.name, + fileSize, + field.mime_type, + tempFileName, + tempFilePath, + ); + + if (Array.isArray(existingFieldValue)) { + fields.set( + strippedFieldName, + existingFieldValue.concat(uploadedFile), + ); + } else { + fields.set(strippedFieldName, uploadedFile); + } + } else { + if (Array.isArray(existingFieldValue)) { + fields.set(strippedFieldName, existingFieldValue.concat(field.value)); + } else { + fields.set(strippedFieldName, field.value); + } + } + }); + } catch (e) { + console.error(e); + } + + return fields; +}; diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index b631888..a9f2cff 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -1,17 +1,13 @@ import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; import { join } from 'path'; import { HttpRoute } from './interfaces'; -import { Request, Response as HResponse, MiddlewareNext } from 'hyper-express'; +import { Response as HResponse, MiddlewareNext } from 'hyper-express'; import { HttpExecutionContext } from './contexts/http-execution-context'; import { HttpRouteHandler } from './http-handler'; import { Response } from './response'; import { ExecutionContext } from './contexts/execution-context'; import { IntentExceptionFilter } from '../../exceptions'; -import { - IntentGuard, - IntentMiddleware, - MiddlewareConfigurator, -} from '../foundation'; +import { IntentGuard, IntentMiddleware } from '../foundation'; import { Type } from '../../interfaces'; import { CONTROLLER_KEY, @@ -21,12 +17,14 @@ import { ROUTE_ARGS, } from './constants'; import { RouteArgType } from './param-decorators'; -import { createRequestFromHyper } from './request'; +import { Request } from './request'; export class RouteExplorer { guards: Type[] = []; - middlewares: Type[] = []; - middlewareConfigurator: MiddlewareConfigurator; + + globalMiddlewares: IntentMiddleware[] = []; + routeMiddlewares: Map; + excludedRouteMiddlewares: Map; constructor( private discoveryService: DiscoveryService, @@ -152,13 +150,10 @@ export class RouteExplorer { ); const cb = async (hReq: Request, hRes: HResponse, next: MiddlewareNext) => { - const req = await createRequestFromHyper(hReq); - - const httpContext = new HttpExecutionContext(req, new Response()); + const httpContext = new HttpExecutionContext(hReq, new Response()); const context = new ExecutionContext(httpContext, instance, methodRef); const args = []; - for (const routeArg of routeArgs) { args.push( routeArg.handler @@ -168,7 +163,6 @@ export class RouteExplorer { } const res = await handler.handle(context, args); - res.reply(hReq, hRes); }; @@ -184,15 +178,22 @@ export class RouteExplorer { return this; } - useGlobalMiddlewares(middlewares: Type[]): RouteExplorer { - this.middlewares = middlewares; + useGlobalMiddlewares(globalMiddlewares: IntentMiddleware[]): RouteExplorer { + this.globalMiddlewares = globalMiddlewares; + return this; + } + + useExcludeMiddlewareRoutes( + routeMiddlewares: Map, + ): RouteExplorer { + this.excludedRouteMiddlewares = routeMiddlewares; return this; } useRouteMiddlewares( - middlewareConfigurator: MiddlewareConfigurator, + routeMiddlewares: Map, ): RouteExplorer { - this.middlewareConfigurator = middlewareConfigurator; + this.routeMiddlewares = routeMiddlewares; return this; } } diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index 909c6f5..88e8812 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -1,10 +1,14 @@ import HyperExpress from 'hyper-express'; import { HttpMethods, HttpRoute } from './interfaces'; -import { HttpStatus } from '@nestjs/common'; -import './request-ext'; +import { IntentMiddleware } from '../foundation'; +import { requestMiddleware } from './request/middleware'; +import { Request } from './request'; export class HyperServer { protected hyper: HyperExpress.Server; + globalMiddlewares: IntentMiddleware[] = []; + routeMiddlewares: Map; + excludedRouteMiddlewares: Map; constructor() {} @@ -13,6 +17,13 @@ export class HyperServer { config: HyperExpress.ServerConstructorOptions, ): Promise { this.hyper = new HyperExpress.Server(config || {}); + this.hyper.use(requestMiddleware); + + // this.hyper.use(async (hReq, res, next) => { + // for (const middleware of this.globalMiddlewares) { + // await middleware.handle(req, res, next); + // } + // }); for (const route of routes) { const { path, httpHandler } = route; @@ -56,4 +67,23 @@ export class HyperServer { // }) return this.hyper; } + + useGlobalMiddlewares(globalMiddlewares: IntentMiddleware[]): HyperServer { + this.globalMiddlewares = globalMiddlewares; + return this; + } + + useExcludeMiddlewareRoutes( + routeMiddlewares: Map, + ): HyperServer { + this.excludedRouteMiddlewares = routeMiddlewares; + return this; + } + + useRouteMiddlewares( + routeMiddlewares: Map, + ): HyperServer { + this.routeMiddlewares = routeMiddlewares; + return this; + } } diff --git a/packages/core/lib/rest/middlewares/cors.ts b/packages/core/lib/rest/middlewares/cors.ts index 419bbd9..4a9efb9 100644 --- a/packages/core/lib/rest/middlewares/cors.ts +++ b/packages/core/lib/rest/middlewares/cors.ts @@ -1,8 +1,8 @@ import cors, { CorsOptions } from 'cors'; -import { NextFunction } from 'express'; import { ConfigService } from '../../config/service'; import { Injectable } from '../../foundation'; -import { IntentMiddleware, Request, Response } from '../foundation'; +import { IntentMiddleware, MiddlewareNext } from '../foundation'; +import { Request, Response } from '../http-server'; @Injectable() export class CorsMiddleware extends IntentMiddleware { @@ -10,7 +10,8 @@ export class CorsMiddleware extends IntentMiddleware { super(); } - boot(req: Request, res: Response, next: NextFunction): void | Promise { + use(req: Request, res: Response, next: MiddlewareNext): void | Promise { + console.log(this.config.get('app.cors')); cors(this.config.get('app.cors') as CorsOptions); next(); } diff --git a/packages/core/lib/rest/middlewares/helmet.ts b/packages/core/lib/rest/middlewares/helmet.ts index 80546bb..dece344 100644 --- a/packages/core/lib/rest/middlewares/helmet.ts +++ b/packages/core/lib/rest/middlewares/helmet.ts @@ -1,8 +1,8 @@ -import { NextFunction } from 'express'; import helmet from 'helmet'; import { Injectable } from '../../foundation'; -import { IntentMiddleware, Request, Response } from '../foundation'; +import { IntentMiddleware, MiddlewareNext } from '../foundation'; import { ConfigService } from '../../config'; +import { Request, Response } from '../http-server'; @Injectable() export class HelmetMiddleware extends IntentMiddleware { @@ -10,7 +10,7 @@ export class HelmetMiddleware extends IntentMiddleware { super(); } - boot(req: Request, res: Response, next: NextFunction): void | Promise { + use(req: Request, res: Response, next: MiddlewareNext): void | Promise { helmet(this.config.get('app.helmet') as any); next(); } diff --git a/packages/core/lib/serviceProvider.ts b/packages/core/lib/serviceProvider.ts index c2cc680..3229af6 100644 --- a/packages/core/lib/serviceProvider.ts +++ b/packages/core/lib/serviceProvider.ts @@ -44,7 +44,7 @@ export const IntentProvidersFactory = ( QueueService, QueueConsoleCommands, QueueMetadata, - ListRouteCommand, + // ListRouteCommand, // CodegenCommand, // CodegenService, ViewConfigCommand, diff --git a/packages/core/lib/validator/index.ts b/packages/core/lib/validator/index.ts index f564f12..3b6d5cd 100644 --- a/packages/core/lib/validator/index.ts +++ b/packages/core/lib/validator/index.ts @@ -5,8 +5,8 @@ import { applyDecorators, createParamDecorator, } from '@nestjs/common'; -import { Request } from '../rest/foundation'; import { IntentValidationGuard } from './validationGuard'; +import { Request } from '../rest'; export * from './validator'; export * from './decorators'; From 4f168b7a305fbcae66c5d6956d45ea7b322e61f8 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Wed, 27 Nov 2024 08:27:44 +0530 Subject: [PATCH 11/27] fix(core): circular dependency issues --- integrations/sample-app/app/errors/filter.ts | 5 + integrations/sample-app/app/http/kernel.ts | 2 +- .../sample-app/app/http/middlewares/global.ts | 13 +- .../sample-app/app/http/middlewares/sample.ts | 8 +- package-lock.json | 240 +- packages/core/dependency-graph.svg | 9456 +++++++++++++++++ packages/core/lib/config/command.ts | 5 +- packages/core/lib/config/service.ts | 4 +- packages/core/lib/console/argumentParser.ts | 2 +- .../{interfaces/index.ts => interfaces.ts} | 2 +- .../lib/exceptions/base-exception-handler.ts | 2 +- .../lib/exceptions/forbidden-exception.ts | 2 +- .../core/lib/exceptions/genericException.ts | 5 +- .../core/lib/exceptions/http-exception.ts | 5 +- .../lib/exceptions/invalid-credentials.ts | 2 +- packages/core/lib/exceptions/unauthorized.ts | 2 +- .../core/lib/exceptions/validation-failed.ts | 2 +- packages/core/lib/explorer.ts | 2 +- packages/core/lib/foundation/app-container.ts | 8 +- packages/core/lib/foundation/decorators.ts | 1 + packages/core/lib/foundation/index.ts | 2 +- .../core/lib/foundation/module-builder.ts | 5 +- .../core/lib/foundation/service-provider.ts | 8 +- packages/core/lib/interfaces/config.ts | 3 +- packages/core/lib/interfaces/exceptions.ts | 1 - packages/core/lib/interfaces/index.ts | 17 +- packages/core/lib/interfaces/utils.ts | 11 + packages/core/lib/reflections/reflector.ts | 2 +- .../lib/rest/foundation/controller-scanner.ts | 2 +- .../lib/rest/foundation/guards/base-guard.ts | 5 +- packages/core/lib/rest/foundation/index.ts | 1 - packages/core/lib/rest/foundation/kernel.ts | 4 +- .../foundation/middlewares/configurator.ts | 2 +- .../middlewares/middleware-composer.ts | 10 +- .../rest/foundation/middlewares/middleware.ts | 3 +- .../core/lib/rest/foundation/request-mixin.ts | 163 - packages/core/lib/rest/foundation/server.ts | 28 +- .../contexts/http-execution-context.ts | 2 +- .../core/lib/rest/http-server/decorators.ts | 2 +- .../core/lib/rest/http-server/http-handler.ts | 6 +- .../lib/rest/http-server/request/extension.ts | 2 +- .../http-server/request/multipart-handler.ts | 2 +- .../lib/rest/http-server/route-explorer.ts | 11 +- packages/core/lib/rest/http-server/server.ts | 13 +- packages/core/lib/rest/middlewares/cors.ts | 17 +- packages/core/lib/rest/middlewares/helmet.ts | 2 +- packages/core/lib/serviceProvider.ts | 2 +- packages/core/lib/storage/service.ts | 2 +- packages/core/lib/utils/array.ts | 2 +- packages/core/lib/utils/context.ts | 14 - packages/core/lib/utils/index.ts | 1 - packages/core/lib/utils/object.ts | 2 +- packages/core/package.json | 4 +- 53 files changed, 9586 insertions(+), 533 deletions(-) create mode 100644 packages/core/dependency-graph.svg rename packages/core/lib/console/{interfaces/index.ts => interfaces.ts} (93%) create mode 100644 packages/core/lib/foundation/decorators.ts delete mode 100644 packages/core/lib/interfaces/exceptions.ts create mode 100644 packages/core/lib/interfaces/utils.ts delete mode 100644 packages/core/lib/rest/foundation/request-mixin.ts delete mode 100644 packages/core/lib/utils/context.ts diff --git a/integrations/sample-app/app/errors/filter.ts b/integrations/sample-app/app/errors/filter.ts index 527a3a1..2fb89f1 100644 --- a/integrations/sample-app/app/errors/filter.ts +++ b/integrations/sample-app/app/errors/filter.ts @@ -1,4 +1,5 @@ import { + ConfigService, ExecutionContext, HttpException, IntentExceptionFilter, @@ -6,6 +7,10 @@ import { } from '@intentjs/core'; export class ApplicationExceptionFilter extends IntentExceptionFilter { + constructor(private config: ConfigService) { + super(); + } + doNotReport(): Array> { return []; } diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index 7fc8a9f..4690524 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -32,7 +32,7 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/middlewares */ public middlewares(): Type[] { - return [GlobalMiddleware]; + return [GlobalMiddleware, CorsMiddleware, GlobalMiddleware]; } /** diff --git a/integrations/sample-app/app/http/middlewares/global.ts b/integrations/sample-app/app/http/middlewares/global.ts index 426b3cf..6e7c2f2 100644 --- a/integrations/sample-app/app/http/middlewares/global.ts +++ b/integrations/sample-app/app/http/middlewares/global.ts @@ -1,11 +1,12 @@ -import { - IntentMiddleware, - MiddlewareNext, - Request, - Response, -} from '@intentjs/core'; +import { ConfigService, Injectable, IntentMiddleware } from '@intentjs/core'; +import { MiddlewareNext, Request, Response } from 'hyper-express'; +@Injectable() export class GlobalMiddleware extends IntentMiddleware { + constructor(private readonly config: ConfigService) { + super(); + } + use(req: Request, res: Response, next: MiddlewareNext): void | Promise { console.log('inside global middleware'); next(); diff --git a/integrations/sample-app/app/http/middlewares/sample.ts b/integrations/sample-app/app/http/middlewares/sample.ts index faedab8..6b49275 100644 --- a/integrations/sample-app/app/http/middlewares/sample.ts +++ b/integrations/sample-app/app/http/middlewares/sample.ts @@ -1,9 +1,5 @@ -import { - IntentMiddleware, - MiddlewareNext, - Request, - Response, -} from '@intentjs/core'; +import { IntentMiddleware, MiddlewareNext } from '@intentjs/core'; +import { Request, Response } from 'hyper-express'; export class SampleMiddleware extends IntentMiddleware { use(req: Request, res: Response, next: MiddlewareNext): void | Promise { diff --git a/package-lock.json b/package-lock.json index 85b60b9..9d87d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,8 +82,6 @@ }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -96,8 +94,6 @@ }, "node_modules/@aws-crypto/crc32c": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", - "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -107,8 +103,6 @@ }, "node_modules/@aws-crypto/sha1-browser": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", - "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", @@ -121,8 +115,6 @@ }, "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -133,8 +125,6 @@ }, "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -146,8 +136,6 @@ }, "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -159,8 +147,6 @@ }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", @@ -174,8 +160,6 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -186,8 +170,6 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -199,8 +181,6 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -212,8 +192,6 @@ }, "node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -226,8 +204,6 @@ }, "node_modules/@aws-crypto/supports-web-crypto": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -235,8 +211,6 @@ }, "node_modules/@aws-crypto/util": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", @@ -246,8 +220,6 @@ }, "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -258,8 +230,6 @@ }, "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -271,8 +241,6 @@ }, "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -284,8 +252,6 @@ }, "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.687.0.tgz", - "integrity": "sha512-jcQTioloSed+Jc3snjrgpWejkOm8t3Zt+jWrApw3ejN8qBtpFCH43M7q/CSDVZ9RS1IjX+KRWoBFnrDOnbuw0Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -336,8 +302,6 @@ }, "node_modules/@aws-sdk/client-s3": { "version": "3.689.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.689.0.tgz", - "integrity": "sha512-qYD1GJEPeLM6H3x8BuAAMXZltvVce5vGiwtZc9uMkBBo3HyFnmPitIPTPfaD1q8LOn/7KFdkY4MJ4e8D3YpV9g==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", @@ -405,8 +369,6 @@ }, "node_modules/@aws-sdk/client-sso": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.687.0.tgz", - "integrity": "sha512-dfj0y9fQyX4kFill/ZG0BqBTLQILKlL7+O5M4F9xlsh2WNuV2St6WtcOg14Y1j5UODPJiJs//pO+mD1lihT5Kw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -454,8 +416,6 @@ }, "node_modules/@aws-sdk/client-sso-oidc": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.687.0.tgz", - "integrity": "sha512-Rdd8kLeTeh+L5ZuG4WQnWgYgdv7NorytKdZsGjiag1D8Wv3PcJvPqqWdgnI0Og717BSXVoaTYaN34FyqFYSx6Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -507,8 +467,6 @@ }, "node_modules/@aws-sdk/client-sts": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.687.0.tgz", - "integrity": "sha512-SQjDH8O4XCTtouuCVYggB0cCCrIaTzUZIkgJUpOsIEJBLlTbNOb/BZqUShAQw2o9vxr2rCeOGjAQOYPysW/Pmg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -558,8 +516,6 @@ }, "node_modules/@aws-sdk/core": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.686.0.tgz", - "integrity": "sha512-Xt3DV4DnAT3v2WURwzTxWQK34Ew+iiLzoUoguvLaZrVMFOqMMrwVjP+sizqIaHp1j7rGmFcN5I8saXnsDLuQLA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -580,8 +536,6 @@ }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.687.0.tgz", - "integrity": "sha512-hJq9ytoj2q/Jonc7mox/b0HT+j4NeMRuU184DkXRJbvIvwwB+oMt12221kThLezMhwIYfXEteZ7GEId7Hn8Y8g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cognito-identity": "3.687.0", @@ -596,8 +550,6 @@ }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.686.0.tgz", - "integrity": "sha512-osD7lPO8OREkgxPiTWmA1i6XEmOth1uW9HWWj/+A2YGCj1G/t2sHu931w4Qj9NWHYZtbTTXQYVRg+TErALV7nQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -612,8 +564,6 @@ }, "node_modules/@aws-sdk/credential-provider-http": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.686.0.tgz", - "integrity": "sha512-xyGAD/f3vR/wssUiZrNFWQWXZvI4zRm2wpHhoHA1cC2fbRMNFYtFn365yw6dU7l00ZLcdFB1H119AYIUZS7xbw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -633,8 +583,6 @@ }, "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.687.0.tgz", - "integrity": "sha512-6d5ZJeZch+ZosJccksN0PuXv7OSnYEmanGCnbhUqmUSz9uaVX6knZZfHCZJRgNcfSqg9QC0zsFA/51W5HCUqSQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -659,8 +607,6 @@ }, "node_modules/@aws-sdk/credential-provider-node": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.687.0.tgz", - "integrity": "sha512-Pqld8Nx11NYaBUrVk3bYiGGpLCxkz8iTONlpQWoVWFhSOzlO7zloNOaYbD2XgFjjqhjlKzE91drs/f41uGeCTA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.686.0", @@ -682,8 +628,6 @@ }, "node_modules/@aws-sdk/credential-provider-process": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.686.0.tgz", - "integrity": "sha512-sXqaAgyzMOc+dm4CnzAR5Q6S9OWVHyZjLfW6IQkmGjqeQXmZl24c4E82+w64C+CTkJrFLzH1VNOYp1Hy5gE6Qw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -699,8 +643,6 @@ }, "node_modules/@aws-sdk/credential-provider-sso": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.687.0.tgz", - "integrity": "sha512-N1YCoE7DovIRF2ReyRrA4PZzF0WNi4ObPwdQQkVxhvSm7PwjbWxrfq7rpYB+6YB1Uq3QPzgVwUFONE36rdpxUQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.687.0", @@ -718,8 +660,6 @@ }, "node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.686.0.tgz", - "integrity": "sha512-40UqCpPxyHCXDP7CGd9JIOZDgDZf+u1OyLaGBpjQJlz1HYuEsIWnnbTe29Yg3Ah/Zc3g4NBWcUdlGVotlnpnDg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -737,8 +677,6 @@ }, "node_modules/@aws-sdk/credential-providers": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.687.0.tgz", - "integrity": "sha512-3aKlmKaOplpanOycmoigbTrQsqtxpzhpfquCey51aHf9GYp2yYyYF1YOgkXpE3qm3w6eiEN1asjJ2gqoECUuPA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cognito-identity": "3.687.0", @@ -765,8 +703,6 @@ }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.686.0.tgz", - "integrity": "sha512-6qCoWI73/HDzQE745MHQUYz46cAQxHCgy1You8MZQX9vHAQwqBnkcsb2hGp7S6fnQY5bNsiZkMWVQ/LVd2MNjg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -783,8 +719,6 @@ }, "node_modules/@aws-sdk/middleware-expect-continue": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.686.0.tgz", - "integrity": "sha512-5yYqIbyhLhH29vn4sHiTj7sU6GttvLMk3XwCmBXjo2k2j3zHqFUwh9RyFGF9VY6Z392Drf/E/cl+qOGypwULpg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -798,8 +732,6 @@ }, "node_modules/@aws-sdk/middleware-flexible-checksums": { "version": "3.689.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.689.0.tgz", - "integrity": "sha512-6VxMOf3mgmAgg6SMagwKj5pAe+putcx2F2odOAWviLcobFpdM/xK9vNry7p6kY+RDNmSlBvcji9wnU59fjV74Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -822,8 +754,6 @@ }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.686.0.tgz", - "integrity": "sha512-+Yc6rO02z+yhFbHmRZGvEw1vmzf/ifS9a4aBjJGeVVU+ZxaUvnk+IUZWrj4YQopUQ+bSujmMUzJLXSkbDq7yuw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -837,8 +767,6 @@ }, "node_modules/@aws-sdk/middleware-location-constraint": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.686.0.tgz", - "integrity": "sha512-pCLeZzt5zUGY3NbW4J/5x3kaHyJEji4yqtoQcUlJmkoEInhSxJ0OE8sTxAfyL3nIOF4yr6L2xdaLCqYgQT8Aog==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -851,8 +779,6 @@ }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.686.0.tgz", - "integrity": "sha512-cX43ODfA2+SPdX7VRxu6gXk4t4bdVJ9pkktbfnkE5t27OlwNfvSGGhnHrQL8xTOFeyQ+3T+oowf26gf1OI+vIg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -865,8 +791,6 @@ }, "node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.686.0.tgz", - "integrity": "sha512-jF9hQ162xLgp9zZ/3w5RUNhmwVnXDBlABEUX8jCgzaFpaa742qR/KKtjjZQ6jMbQnP+8fOCSXFAVNMU+s6v81w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -880,8 +804,6 @@ }, "node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.687.0.tgz", - "integrity": "sha512-YGHYqiyRiNNucmvLrfx3QxIkjSDWR/+cc72bn0lPvqFUQBRHZgmYQLxVYrVZSmRzzkH2FQ1HsZcXhOafLbq4vQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -905,8 +827,6 @@ }, "node_modules/@aws-sdk/middleware-ssec": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.686.0.tgz", - "integrity": "sha512-zJXml/CpVHFUdlGQqja87vNQ3rPB5SlDbfdwxlj1KBbjnRRwpBtxxmOlWRShg8lnVV6aIMGv95QmpIFy4ayqnQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -919,8 +839,6 @@ }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.687.0.tgz", - "integrity": "sha512-nUgsKiEinyA50CaDXojAkOasAU3Apdg7Qox6IjNUC4ZjgOu7QWsCDB5N28AYMUt06cNYeYQdfMX1aEzG85a1Mg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.686.0", @@ -937,8 +855,6 @@ }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.686.0.tgz", - "integrity": "sha512-6zXD3bSD8tcsMAVVwO1gO7rI1uy2fCD3czgawuPGPopeLiPpo6/3FoUWCQzk2nvEhj7p9Z4BbjwZGSlRkVrXTw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -954,8 +870,6 @@ }, "node_modules/@aws-sdk/s3-request-presigner": { "version": "3.689.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.689.0.tgz", - "integrity": "sha512-E9P59HEsPeFuO10yKyYE180J3V1DRVFTa0H0XzrBTP+s2g9g8xvfyGqoDYJw5YHUckqls39jT5nlbrf+kBSrfg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.687.0", @@ -973,8 +887,6 @@ }, "node_modules/@aws-sdk/signature-v4-multi-region": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.687.0.tgz", - "integrity": "sha512-vdOQHCRHJPX9mT8BM6xOseazHD6NodvHl9cyF5UjNtLn+gERRJEItIA9hf0hlt62odGD8Fqp+rFRuqdmbNkcNw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.687.0", @@ -990,8 +902,6 @@ }, "node_modules/@aws-sdk/token-providers": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.686.0.tgz", - "integrity": "sha512-9oL4kTCSePFmyKPskibeiOXV6qavPZ63/kXM9Wh9V6dTSvBtLeNnMxqGvENGKJcTdIgtoqyqA6ET9u0PJ5IRIg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -1009,8 +919,6 @@ }, "node_modules/@aws-sdk/types": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -1022,8 +930,6 @@ }, "node_modules/@aws-sdk/util-arn-parser": { "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.679.0.tgz", - "integrity": "sha512-CwzEbU8R8rq9bqUFryO50RFBlkfufV9UfMArHPWlo+lmsC+NlSluHQALoj6Jkq3zf5ppn1CN0c1DDLrEqdQUXg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1034,8 +940,6 @@ }, "node_modules/@aws-sdk/util-endpoints": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.686.0.tgz", - "integrity": "sha512-7msZE2oYl+6QYeeRBjlDgxQUhq/XRky3cXE0FqLFs2muLS7XSuQEXkpOXB3R782ygAP6JX0kmBxPTLurRTikZg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -1049,8 +953,6 @@ }, "node_modules/@aws-sdk/util-format-url": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.686.0.tgz", - "integrity": "sha512-9doB6O4FAlnWZrvnFDUxTtSFtuL8kUqxlP00HTiDgL1uDJZ8e0S4gqjKR+9+N5goFtxGi7IJeNsDEz2H7imvgw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -1064,8 +966,6 @@ }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.679.0.tgz", - "integrity": "sha512-zKTd48/ZWrCplkXpYDABI74rQlbR0DNHs8nH95htfSLj9/mWRSwaGptoxwcihaq/77vi/fl2X3y0a1Bo8bt7RA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1076,8 +976,6 @@ }, "node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.686.0.tgz", - "integrity": "sha512-YiQXeGYZegF1b7B2GOR61orhgv79qmI0z7+Agm3NXLO6hGfVV3kFUJbXnjtH1BgWo5hbZYW7HQ2omGb3dnb6Lg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.686.0", @@ -1088,8 +986,6 @@ }, "node_modules/@aws-sdk/util-user-agent-node": { "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.687.0.tgz", - "integrity": "sha512-idkP6ojSTZ4ek1pJ8wIN7r9U3KR5dn0IkJn3KQBXQ58LWjkRqLtft2vxzdsktWwhPKjjmIKl1S0kbvqLawf8XQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.687.0", @@ -1112,8 +1008,6 @@ }, "node_modules/@aws-sdk/xml-builder": { "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.686.0.tgz", - "integrity": "sha512-k0z5b5dkYSuOHY0AOZ4iyjcGBeVL9lWsQNF4+c+1oK3OW4fRWl/bNa1soMRMpangsHPzgyn/QkzuDbl7qR4qrw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -3040,7 +2934,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.4.7", + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.8.tgz", + "integrity": "sha512-PVor9dxihg3F2LMnVNkQu42vUmea2+qukkWXUSumtVKDsBo7X7jnZWXtF5bvNTcYK7IYL4/MM4awNfJVJcJpFg==", "license": "MIT", "dependencies": { "iterare": "1.2.1", @@ -3071,7 +2967,9 @@ "license": "0BSD" }, "node_modules/@nestjs/core": { - "version": "10.4.7", + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.8.tgz", + "integrity": "sha512-Kdi9rDZdlCkGL2AK9XuJ24bZp2YFV6dWBdogGsAHSP5u95wfnSkhduxHZy6q/i1nFFiLASUHabL8Jwr+bmD22Q==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4103,8 +4001,6 @@ }, "node_modules/@smithy/abort-controller": { "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", - "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4116,8 +4012,6 @@ }, "node_modules/@smithy/chunked-blob-reader": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", - "integrity": "sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4125,8 +4019,6 @@ }, "node_modules/@smithy/chunked-blob-reader-native": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz", - "integrity": "sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==", "license": "Apache-2.0", "dependencies": { "@smithy/util-base64": "^3.0.0", @@ -4135,8 +4027,6 @@ }, "node_modules/@smithy/config-resolver": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.10.tgz", - "integrity": "sha512-Uh0Sz9gdUuz538nvkPiyv1DZRX9+D15EKDtnQP5rYVAzM/dnYk3P8cg73jcxyOitPgT3mE3OVj7ky7sibzHWkw==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.9", @@ -4151,8 +4041,6 @@ }, "node_modules/@smithy/core": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz", - "integrity": "sha512-DujtuDA7BGEKExJ05W5OdxCoyekcKT3Rhg1ZGeiUWaz2BJIWXjZmsG/DIP4W48GHno7AQwRsaCb8NcBgH3QZpg==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^3.0.8", @@ -4170,8 +4058,6 @@ }, "node_modules/@smithy/credential-provider-imds": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz", - "integrity": "sha512-4FTQGAsuwqTzVMmiRVTn0RR9GrbRfkP0wfu/tXWVHd2LgNpTY0uglQpIScXK4NaEyXbB3JmZt8gfVqO50lP8wg==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.9", @@ -4186,8 +4072,6 @@ }, "node_modules/@smithy/eventstream-codec": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.7.tgz", - "integrity": "sha512-kVSXScIiRN7q+s1x7BrQtZ1Aa9hvvP9FeCqCdBxv37GimIHgBCOnZ5Ip80HLt0DhnAKpiobFdGqTFgbaJNrazA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -4198,8 +4082,6 @@ }, "node_modules/@smithy/eventstream-serde-browser": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.11.tgz", - "integrity": "sha512-Pd1Wnq3CQ/v2SxRifDUihvpXzirJYbbtXfEnnLV/z0OGCTx/btVX74P86IgrZkjOydOASBGXdPpupYQI+iO/6A==", "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-serde-universal": "^3.0.10", @@ -4212,8 +4094,6 @@ }, "node_modules/@smithy/eventstream-serde-config-resolver": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.8.tgz", - "integrity": "sha512-zkFIG2i1BLbfoGQnf1qEeMqX0h5qAznzaZmMVNnvPZz9J5AWBPkOMckZWPedGUPcVITacwIdQXoPcdIQq5FRcg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4225,8 +4105,6 @@ }, "node_modules/@smithy/eventstream-serde-node": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.10.tgz", - "integrity": "sha512-hjpU1tIsJ9qpcoZq9zGHBJPBOeBGYt+n8vfhDwnITPhEre6APrvqq/y3XMDEGUT2cWQ4ramNqBPRbx3qn55rhw==", "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-serde-universal": "^3.0.10", @@ -4239,8 +4117,6 @@ }, "node_modules/@smithy/eventstream-serde-universal": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.10.tgz", - "integrity": "sha512-ewG1GHbbqsFZ4asaq40KmxCmXO+AFSM1b+DcO2C03dyJj/ZH71CiTg853FSE/3SHK9q3jiYQIFjlGSwfxQ9kww==", "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-codec": "^3.1.7", @@ -4253,8 +4129,6 @@ }, "node_modules/@smithy/fetch-http-handler": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", - "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.5", @@ -4266,8 +4140,6 @@ }, "node_modules/@smithy/hash-blob-browser": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.7.tgz", - "integrity": "sha512-4yNlxVNJifPM5ThaA5HKnHkn7JhctFUHvcaz6YXxHlYOSIrzI6VKQPTN8Gs1iN5nqq9iFcwIR9THqchUCouIfg==", "license": "Apache-2.0", "dependencies": { "@smithy/chunked-blob-reader": "^4.0.0", @@ -4278,8 +4150,6 @@ }, "node_modules/@smithy/hash-node": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz", - "integrity": "sha512-tlNQYbfpWXHimHqrvgo14DrMAgUBua/cNoz9fMYcDmYej7MAmUcjav/QKQbFc3NrcPxeJ7QClER4tWZmfwoPng==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4293,8 +4163,6 @@ }, "node_modules/@smithy/hash-stream-node": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.7.tgz", - "integrity": "sha512-xMAsvJ3hLG63lsBVi1Hl6BBSfhd8/Qnp8fC06kjOpJvyyCEXdwHITa5Kvdsk6gaAXLhbZMhQMIGvgUbfnJDP6Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4307,8 +4175,6 @@ }, "node_modules/@smithy/invalid-dependency": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz", - "integrity": "sha512-7Qynk6NWtTQhnGTTZwks++nJhQ1O54Mzi7fz4PqZOiYXb4Z1Flpb2yRvdALoggTS8xjtohWUM+RygOtB30YL3Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4317,8 +4183,6 @@ }, "node_modules/@smithy/is-array-buffer": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4329,8 +4193,6 @@ }, "node_modules/@smithy/md5-js": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.8.tgz", - "integrity": "sha512-LwApfTK0OJ/tCyNUXqnWCKoE2b4rDSr4BJlDAVCkiWYeHESr+y+d5zlAanuLW6fnitVJRD/7d9/kN/ZM9Su4mA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4340,8 +4202,6 @@ }, "node_modules/@smithy/middleware-content-length": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz", - "integrity": "sha512-T4dIdCs1d/+/qMpwhJ1DzOhxCZjZHbHazEPJWdB4GDi2HjIZllVzeBEcdJUN0fomV8DURsgOyrbEUzg3vzTaOg==", "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.5", @@ -4354,8 +4214,6 @@ }, "node_modules/@smithy/middleware-endpoint": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", - "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^2.5.1", @@ -4373,8 +4231,6 @@ }, "node_modules/@smithy/middleware-retry": { "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", - "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.9", @@ -4393,8 +4249,6 @@ }, "node_modules/@smithy/middleware-retry/node_modules/uuid": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -4406,8 +4260,6 @@ }, "node_modules/@smithy/middleware-serde": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", - "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4419,8 +4271,6 @@ }, "node_modules/@smithy/middleware-stack": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", - "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4432,8 +4282,6 @@ }, "node_modules/@smithy/node-config-provider": { "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", - "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.8", @@ -4447,8 +4295,6 @@ }, "node_modules/@smithy/node-http-handler": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", - "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.6", @@ -4463,8 +4309,6 @@ }, "node_modules/@smithy/property-provider": { "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", - "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4476,8 +4320,6 @@ }, "node_modules/@smithy/protocol-http": { "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", - "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4489,8 +4331,6 @@ }, "node_modules/@smithy/querystring-builder": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", - "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4503,8 +4343,6 @@ }, "node_modules/@smithy/querystring-parser": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", - "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4516,8 +4354,6 @@ }, "node_modules/@smithy/service-error-classification": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", - "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0" @@ -4528,8 +4364,6 @@ }, "node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", - "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4541,8 +4375,6 @@ }, "node_modules/@smithy/signature-v4": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", - "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", @@ -4560,8 +4392,6 @@ }, "node_modules/@smithy/smithy-client": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", - "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^2.5.1", @@ -4578,8 +4408,6 @@ }, "node_modules/@smithy/types": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", - "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4590,8 +4418,6 @@ }, "node_modules/@smithy/url-parser": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", - "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^3.0.8", @@ -4601,8 +4427,6 @@ }, "node_modules/@smithy/util-base64": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", - "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", @@ -4615,8 +4439,6 @@ }, "node_modules/@smithy/util-body-length-browser": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", - "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4624,8 +4446,6 @@ }, "node_modules/@smithy/util-body-length-node": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", - "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4636,8 +4456,6 @@ }, "node_modules/@smithy/util-buffer-from": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", @@ -4649,8 +4467,6 @@ }, "node_modules/@smithy/util-config-provider": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", - "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4661,8 +4477,6 @@ }, "node_modules/@smithy/util-defaults-mode-browser": { "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.25.tgz", - "integrity": "sha512-fRw7zymjIDt6XxIsLwfJfYUfbGoO9CmCJk6rjJ/X5cd20+d2Is7xjU5Kt/AiDt6hX8DAf5dztmfP5O82gR9emA==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.8", @@ -4677,8 +4491,6 @@ }, "node_modules/@smithy/util-defaults-mode-node": { "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz", - "integrity": "sha512-H3BSZdBDiVZGzt8TG51Pd2FvFO0PAx/A0mJ0EH8a13KJ6iUCdYnw/Dk/MdC1kTd0eUuUGisDFaxXVXo4HHFL1g==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^3.0.10", @@ -4695,8 +4507,6 @@ }, "node_modules/@smithy/util-endpoints": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz", - "integrity": "sha512-kPt8j4emm7rdMWQyL0F89o92q10gvCUa6sBkBtDJ7nV2+P7wpXczzOfoDJ49CKXe5CCqb8dc1W+ZdLlrKzSAnQ==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.9", @@ -4709,8 +4519,6 @@ }, "node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", - "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4721,8 +4529,6 @@ }, "node_modules/@smithy/util-middleware": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", - "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.6.0", @@ -4734,8 +4540,6 @@ }, "node_modules/@smithy/util-retry": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", - "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.8", @@ -4748,8 +4552,6 @@ }, "node_modules/@smithy/util-stream": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", - "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^4.0.0", @@ -4767,8 +4569,6 @@ }, "node_modules/@smithy/util-uri-escape": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4779,8 +4579,6 @@ }, "node_modules/@smithy/util-utf8": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", @@ -4792,8 +4590,6 @@ }, "node_modules/@smithy/util-waiter": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.7.tgz", - "integrity": "sha512-d5yGlQtmN/z5eoTtIYgkvOw27US2Ous4VycnXatyoImIF9tzlcpnKqQ/V7qhvJmb2p6xZne1NopCLakdTnkBBQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.6", @@ -5158,8 +4954,6 @@ }, "node_modules/@types/busboy": { "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", - "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", "dev": true, "license": "MIT", "dependencies": { @@ -6881,8 +6675,6 @@ }, "node_modules/bowser": { "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", "license": "MIT" }, "node_modules/brace-expansion": { @@ -6985,8 +6777,6 @@ }, "node_modules/busboy": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { "streamsearch": "^1.1.0" }, @@ -9528,8 +9318,6 @@ }, "node_modules/fast-xml-parser": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -10721,8 +10509,6 @@ }, "node_modules/hyper-express": { "version": "6.17.2", - "resolved": "https://registry.npmjs.org/hyper-express/-/hyper-express-6.17.2.tgz", - "integrity": "sha512-zibRNaNA3ExaYIiypHPEbFCZ3fOT2zSCnn3v78lUOrvQGqeb7VVzKuQKmJUsbkzpp7tKZoDxM4l5avpQHm35dA==", "license": "MIT", "dependencies": { "accepts": "^1.3.7", @@ -10738,8 +10524,6 @@ }, "node_modules/hyper-express/node_modules/cookie": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -10747,8 +10531,6 @@ }, "node_modules/hyper-express/node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -15471,8 +15253,6 @@ }, "node_modules/resend": { "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/resend/-/resend-4.0.1-alpha.0.tgz", - "integrity": "sha512-qtyGk72ZJ3b3ifmz34l/z/X9EpKuqgjTc76/wihMR8I71IdhDIpIPsx/CgKlkA9oLesc8mryW+zulGr8RtEkJQ==", "license": "MIT", "dependencies": { "@react-email/render": "1.0.1" @@ -16437,8 +16217,6 @@ }, "node_modules/strnum": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "license": "MIT" }, "node_modules/strong-log-transformer": { @@ -17228,8 +17006,6 @@ }, "node_modules/typed-emitter": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", - "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", "license": "MIT", "optionalDependencies": { "rxjs": "*" @@ -18011,8 +17787,8 @@ "version": "0.1.35", "license": "MIT", "dependencies": { - "@nestjs/common": "^10.4.4", - "@nestjs/core": "^10.4.4", + "@nestjs/common": "^10.4.8", + "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", "archy": "^1.0.0", "axios": "^1.7.7", diff --git a/packages/core/dependency-graph.svg b/packages/core/dependency-graph.svg new file mode 100644 index 0000000..75b9663 --- /dev/null +++ b/packages/core/dependency-graph.svg @@ -0,0 +1,9456 @@ + + + + + + +dependency-cruiser output + + +cluster_lib + +lib + + +cluster_lib/cache + +cache + + +cluster_lib/cache/drivers + +drivers + + +cluster_lib/cache/interfaces + +interfaces + + +cluster_lib/cache/utils + +utils + + +cluster_lib/codegen + +codegen + + +cluster_lib/config + +config + + +cluster_lib/console + +console + + +cluster_lib/console/commands + +commands + + +cluster_lib/console/interfaces + +interfaces + + +cluster_lib/database + +database + + +cluster_lib/database/commands + +commands + + +cluster_lib/database/exceptions + +exceptions + + +cluster_lib/database/queryBuilders + +queryBuilders + + +cluster_lib/database/repositories + +repositories + + +cluster_lib/dev-server + +dev-server + + +cluster_lib/events + +events + + +cluster_lib/exceptions + +exceptions + + +cluster_lib/foundation + +foundation + + +cluster_lib/interfaces + +interfaces + + +cluster_lib/localization + +localization + + +cluster_lib/localization/helpers + +helpers + + +cluster_lib/localization/interfaces + +interfaces + + +cluster_lib/localization/utils + +utils + + +cluster_lib/logger + +logger + + +cluster_lib/mailer + +mailer + + +cluster_lib/mailer/exceptions + +exceptions + + +cluster_lib/mailer/interfaces + +interfaces + + +cluster_lib/mailer/providers + +providers + + +cluster_lib/queue + +queue + + +cluster_lib/queue/console + +console + + +cluster_lib/queue/core + +core + + +cluster_lib/queue/drivers + +drivers + + +cluster_lib/queue/events + +events + + +cluster_lib/queue/exceptions + +exceptions + + +cluster_lib/queue/interfaces + +interfaces + + +cluster_lib/queue/jobRunners + +jobRunners + + +cluster_lib/queue/schema + +schema + + +cluster_lib/queue/strategy + +strategy + + +cluster_lib/queue/workers + +workers + + +cluster_lib/reflections + +reflections + + +cluster_lib/rest + +rest + + +cluster_lib/rest/foundation + +foundation + + +cluster_lib/rest/foundation/guards + +guards + + +cluster_lib/rest/foundation/middlewares + +middlewares + + +cluster_lib/rest/http-server + +http-server + + +cluster_lib/rest/http-server/contexts + +contexts + + +cluster_lib/rest/http-server/request + +request + + +cluster_lib/rest/interceptors + +interceptors + + +cluster_lib/rest/middlewares + +middlewares + + +cluster_lib/serializers + +serializers + + +cluster_lib/storage + +storage + + +cluster_lib/storage/data + +data + + +cluster_lib/storage/drivers + +drivers + + +cluster_lib/storage/exceptions + +exceptions + + +cluster_lib/storage/file-handlers + +file-handlers + + +cluster_lib/storage/helpers + +helpers + + +cluster_lib/storage/interfaces + +interfaces + + +cluster_lib/transformers + +transformers + + +cluster_lib/type-helpers + +type-helpers + + +cluster_lib/utils + +utils + + +cluster_lib/validator + +validator + + +cluster_lib/validator/decorators + +decorators + + +cluster_node_modules + +node_modules + + +cluster_node_modules/@nestjs + +@nestjs + + +cluster_node_modules/@react-email + +@react-email + + +cluster_resources + +resources + + +cluster_resources/mail + +mail + + +cluster_resources/mail/components + +components + + +cluster_resources/mail/emails + +emails + + + +crypto + + +crypto + + + + + +fs + + +fs + + + + + +lib/cache/cache.ts + + +cache.ts + + + + + +lib/config/index.ts + + +index.ts + + + + + +lib/cache/cache.ts->lib/config/index.ts + + + + + +lib/cache/interfaces/index.ts + + +index.ts + + + + + +lib/cache/cache.ts->lib/cache/interfaces/index.ts + + + + + +lib/cache/service.ts + + +service.ts + + + + + +lib/cache/cache.ts->lib/cache/service.ts + + + + + +lib/cache/utils/genKey.ts + + +genKey.ts + + + + + +lib/cache/cache.ts->lib/cache/utils/genKey.ts + + + + + +lib/config/builder.ts + + +builder.ts + + + + + +lib/config/index.ts->lib/config/builder.ts + + + + + +lib/config/options.ts + + +options.ts + + + + + +lib/config/index.ts->lib/config/options.ts + + + + + +lib/config/command.ts + + +command.ts + + + + + +lib/config/index.ts->lib/config/command.ts + + + + + + + +no-circular + + + +lib/config/constant.ts + + +constant.ts + + + + + +lib/config/index.ts->lib/config/constant.ts + + + + + +lib/config/register-namespace.ts + + +register-namespace.ts + + + + + +lib/config/index.ts->lib/config/register-namespace.ts + + + + + +lib/config/service.ts + + +service.ts + + + + + +lib/config/index.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/cache/interfaces/driver.ts + + +driver.ts + + + + + +lib/cache/interfaces/index.ts->lib/cache/interfaces/driver.ts + + + + + +lib/cache/interfaces/options.ts + + +options.ts + + + + + +lib/cache/interfaces/index.ts->lib/cache/interfaces/options.ts + + + + + +lib/cache/service.ts->lib/cache/interfaces/index.ts + + + + + +lib/cache/drivers/inMemory.ts + + +inMemory.ts + + + + + +lib/cache/service.ts->lib/cache/drivers/inMemory.ts + + + + + +lib/cache/drivers/redis.ts + + +redis.ts + + + + + +lib/cache/service.ts->lib/cache/drivers/redis.ts + + + + + +node_modules/@nestjs/common + + + + + +common + + + + + +lib/cache/service.ts->node_modules/@nestjs/common + + + + + +lib/cache/utils/genKey.ts->crypto + + + + + +lib/utils/array.ts + + +array.ts + + + + + +lib/cache/utils/genKey.ts->lib/utils/array.ts + + + + + +lib/cache/constants.ts + + +constants.ts + + + + + +lib/cache/drivers/inMemory.ts->lib/cache/interfaces/index.ts + + + + + +lib/interfaces/index.ts + + +index.ts + + + + + +lib/cache/drivers/inMemory.ts->lib/interfaces/index.ts + + + + + +lib/utils/packageLoader.ts + + +packageLoader.ts + + + + + +lib/cache/drivers/inMemory.ts->lib/utils/packageLoader.ts + + + + + +lib/interfaces/config.ts + + +config.ts + + + + + +lib/interfaces/index.ts->lib/interfaces/config.ts + + + + + +lib/interfaces/utils.ts + + +utils.ts + + + + + +lib/interfaces/index.ts->lib/interfaces/utils.ts + + + + + +lib/interfaces/transformer.ts + + +transformer.ts + + + + + +lib/interfaces/index.ts->lib/interfaces/transformer.ts + + + + + +node_modules/picocolors + + + + + +picocolors + + + + + +lib/utils/packageLoader.ts->node_modules/picocolors + + + + + +lib/utils/logger.ts + + +logger.ts + + + + + +lib/utils/packageLoader.ts->lib/utils/logger.ts + + + + + + + +no-circular + + + +lib/cache/drivers/redis.ts->lib/cache/interfaces/index.ts + + + + + +lib/cache/drivers/redis.ts->lib/interfaces/index.ts + + + + + +lib/utils/index.ts + + +index.ts + + + + + +lib/cache/drivers/redis.ts->lib/utils/index.ts + + + + + +lib/utils/index.ts->lib/utils/packageLoader.ts + + + + + + + +no-circular + + + +lib/utils/index.ts->lib/utils/array.ts + + + + + + + +no-circular + + + +lib/utils/string.ts + + +string.ts + + + + + +lib/utils/index.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/utils/helpers.ts + + +helpers.ts + + + + + +lib/utils/index.ts->lib/utils/helpers.ts + + + + + + + +no-circular + + + +lib/utils/number.ts + + +number.ts + + + + + +lib/utils/index.ts->lib/utils/number.ts + + + + + + + +no-circular + + + +lib/utils/expParser.ts + + +expParser.ts + + + + + +lib/utils/index.ts->lib/utils/expParser.ts + + + + + +lib/utils/object.ts + + +object.ts + + + + + +lib/utils/index.ts->lib/utils/object.ts + + + + + + + +no-circular + + + +lib/utils/context.ts + + +context.ts + + + + + +lib/utils/index.ts->lib/utils/context.ts + + + + + + + +no-circular + + + +lib/utils/path.ts + + +path.ts + + + + + +lib/utils/index.ts->lib/utils/path.ts + + + + + +lib/cache/index.ts + + +index.ts + + + + + +lib/cache/index.ts->lib/cache/cache.ts + + + + + +lib/cache/index.ts->lib/cache/interfaces/index.ts + + + + + +lib/cache/index.ts->lib/cache/service.ts + + + + + +lib/cache/interfaces/driver.ts->lib/interfaces/index.ts + + + + + +lib/cache/interfaces/options.ts->node_modules/@nestjs/common + + + + + +lib/exceptions/index.ts + + +index.ts + + + + + +lib/utils/array.ts->lib/exceptions/index.ts + + + + + + + +no-circular + + + +lib/utils/array.ts->lib/utils/object.ts + + + + + + + +no-circular + + + +lib/codegen/command.ts + + +command.ts + + + + + +lib/codegen/command.ts->node_modules/@nestjs/common + + + + + +lib/console/index.ts + + +index.ts + + + + + +lib/codegen/command.ts->lib/console/index.ts + + + + + +lib/codegen/command.ts->lib/utils/string.ts + + + + + +lib/codegen/service.ts + + +service.ts + + + + + +lib/codegen/command.ts->lib/codegen/service.ts + + + + + +lib/codegen/utils.ts + + +utils.ts + + + + + +lib/codegen/command.ts->lib/codegen/utils.ts + + + + + +path + + +path + + + + + +lib/codegen/command.ts->path + + + + + +lib/console/interfaces/index.ts + + +index.ts + + + + + +lib/console/index.ts->lib/console/interfaces/index.ts + + + + + + + +no-circular + + + +lib/console/commands/index.ts + + +index.ts + + + + + +lib/console/index.ts->lib/console/commands/index.ts + + + + + + + +no-circular + + + +lib/console/decorators.ts + + +decorators.ts + + + + + +lib/console/index.ts->lib/console/decorators.ts + + + + + + + +no-circular + + + +lib/console/metadata.ts + + +metadata.ts + + + + + +lib/console/index.ts->lib/console/metadata.ts + + + + + + + +no-circular + + + +lib/console/consoleIO.ts + + +consoleIO.ts + + + + + +lib/console/index.ts->lib/console/consoleIO.ts + + + + + + + +no-circular + + + +lib/console/inquirer.ts + + +inquirer.ts + + + + + +lib/console/index.ts->lib/console/inquirer.ts + + + + + +lib/console/logger.ts + + +logger.ts + + + + + +lib/console/index.ts->lib/console/logger.ts + + + + + + + +no-circular + + + +lib/console/runner.ts + + +runner.ts + + + + + +lib/console/index.ts->lib/console/runner.ts + + + + + + + +no-circular + + + +lib/utils/string.ts->node_modules/@nestjs/common + + + + + +lib/utils/string.ts->lib/utils/number.ts + + + + + + + +no-circular + + + +lib/utils/pluralise.ts + + +pluralise.ts + + + + + +lib/utils/string.ts->lib/utils/pluralise.ts + + + + + + + +no-circular + + + +lib/codegen/service.ts->fs + + + + + +lib/codegen/service.ts->lib/utils/index.ts + + + + + +lib/codegen/service.ts->node_modules/@nestjs/common + + + + + +lib/codegen/service.ts->path + + + + + +node_modules/eta + + + + + +eta + + + + + +lib/codegen/service.ts->node_modules/eta + + + + + +node_modules/ts-morph + + + + + +ts-morph + + + + + +lib/codegen/service.ts->node_modules/ts-morph + + + + + +lib/codegen/utils.ts->lib/utils/index.ts + + + + + +lib/codegen/utils.ts->path + + + + + +lib/codegen/utils.ts->node_modules/ts-morph + + + + + +lib/config/builder.ts->lib/config/options.ts + + + + + +lib/config/command.ts->lib/console/index.ts + + + + + + + +no-circular + + + +lib/config/command.ts->lib/config/options.ts + + + + + +lib/foundation/index.ts + + +index.ts + + + + + +lib/config/command.ts->lib/foundation/index.ts + + + + + + + +no-circular + + + +lib/utils/console-helpers.ts + + +console-helpers.ts + + + + + +lib/config/command.ts->lib/utils/console-helpers.ts + + + + + +lib/config/command.ts->lib/config/constant.ts + + + + + +node_modules/archy + + + + + +archy + + + + + +lib/config/command.ts->node_modules/archy + + + + + +lib/config/command.ts->node_modules/picocolors + + + + + +lib/foundation/index.ts->node_modules/@nestjs/common + + + + + +lib/foundation/app-container.ts + + +app-container.ts + + + + + +lib/foundation/index.ts->lib/foundation/app-container.ts + + + + + +lib/foundation/service-provider.ts + + +service-provider.ts + + + + + +lib/foundation/index.ts->lib/foundation/service-provider.ts + + + + + +lib/foundation/container-factory.ts + + +container-factory.ts + + + + + +lib/foundation/index.ts->lib/foundation/container-factory.ts + + + + + + + +no-circular + + + +lib/foundation/module-builder.ts + + +module-builder.ts + + + + + +lib/foundation/index.ts->lib/foundation/module-builder.ts + + + + + + + +no-circular + + + +lib/utils/console-helpers.ts->node_modules/picocolors + + + + + +lib/config/register-namespace.ts->lib/config/options.ts + + + + + +lib/type-helpers/index.ts + + +index.ts + + + + + +lib/config/register-namespace.ts->lib/type-helpers/index.ts + + + + + +node_modules/dotenv + + + + + +dotenv + + + + + +lib/config/register-namespace.ts->node_modules/dotenv + + + + + +lib/config/service.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/config/service.ts->lib/config/options.ts + + + + + +lib/config/service.ts->lib/foundation/index.ts + + + + + + + +no-circular + + + +lib/config/service.ts->lib/config/constant.ts + + + + + +lib/config/service.ts->lib/type-helpers/index.ts + + + + + +lib/console/argumentParser.ts + + +argumentParser.ts + + + + + +lib/console/argumentParser.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/console/argumentParser.ts->lib/utils/logger.ts + + + + + + + +no-circular + + + +lib/console/argumentParser.ts->lib/console/interfaces/index.ts + + + + + + + +no-circular + + + +lib/utils/logger.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/utils/logger.ts->node_modules/picocolors + + + + + +lib/console/interfaces/index.ts->lib/console/consoleIO.ts + + + + + + + +no-circular + + + +lib/console/commands/listCommands.ts + + +listCommands.ts + + + + + +lib/console/commands/index.ts->lib/console/commands/listCommands.ts + + + + + + + +no-circular + + + +lib/console/commands/listCommands.ts->fs + + + + + +lib/console/commands/listCommands.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/console/commands/listCommands.ts->node_modules/@nestjs/common + + + + + +lib/console/commands/listCommands.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/console/commands/listCommands.ts->path + + + + + +lib/console/commands/listCommands.ts->node_modules/picocolors + + + + + +lib/utils/columnify.ts + + +columnify.ts + + + + + +lib/console/commands/listCommands.ts->lib/utils/columnify.ts + + + + + + + +no-circular + + + +lib/console/commands/listCommands.ts->lib/console/decorators.ts + + + + + + + +no-circular + + + +lib/console/commands/listCommands.ts->lib/console/metadata.ts + + + + + + + +no-circular + + + +lib/utils/columnify.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/console/decorators.ts->lib/console/interfaces/index.ts + + + + + + + +no-circular + + + +lib/console/constants.ts + + +constants.ts + + + + + +lib/console/decorators.ts->lib/console/constants.ts + + + + + +node_modules/reflect-metadata + + + + + +reflect-metadata + + + + + +lib/console/decorators.ts->node_modules/reflect-metadata + + + + + +not-to-dev-dep + + + +lib/console/metadata.ts->lib/interfaces/index.ts + + + + + +lib/console/metadata.ts->lib/console/argumentParser.ts + + + + + + + +no-circular + + + +lib/console/metadata.ts->lib/console/interfaces/index.ts + + + + + + + +no-circular + + + +lib/console/commands/route-list.ts + + +route-list.ts + + + + + +lib/console/commands/route-list.ts->lib/utils/columnify.ts + + + + + +lib/console/commands/route-list.ts->lib/console/decorators.ts + + + + + +lib/rest/index.ts + + +index.ts + + + + + +lib/console/commands/route-list.ts->lib/rest/index.ts + + + + + +node_modules/@nestjs/core + + + + + +core + + + + + +lib/console/commands/route-list.ts->node_modules/@nestjs/core + + + + + +lib/rest/http-server/index.ts + + +index.ts + + + + + +lib/rest/index.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/index.ts + + +index.ts + + + + + +lib/rest/index.ts->lib/rest/foundation/index.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/cors.ts + + +cors.ts + + + + + +lib/rest/index.ts->lib/rest/middlewares/cors.ts + + + + + + + +no-circular + + + +lib/rest/interceptors/timeout.ts + + +timeout.ts + + + + + +lib/rest/index.ts->lib/rest/interceptors/timeout.ts + + + + + +lib/rest/middlewares/helmet.ts + + +helmet.ts + + + + + +lib/rest/index.ts->lib/rest/middlewares/helmet.ts + + + + + + + +no-circular + + + +lib/console/consoleIO.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/console/consoleIO.ts->lib/console/argumentParser.ts + + + + + + + +no-circular + + + +lib/console/consoleIO.ts->lib/console/interfaces/index.ts + + + + + + + +no-circular + + + +lib/console/consoleIO.ts->lib/console/inquirer.ts + + + + + +lib/console/consoleIO.ts->lib/console/logger.ts + + + + + + + +no-circular + + + +node_modules/enquirer + + + + + +enquirer + + + + + +lib/console/inquirer.ts->node_modules/enquirer + + + + + +lib/console/logger.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/console/logger.ts->node_modules/picocolors + + + + + +node_modules/cli-table3 + + + + + +cli-table3 + + + + + +lib/console/logger.ts->node_modules/cli-table3 + + + + + +lib/console/runner.ts->node_modules/picocolors + + + + + +lib/console/runner.ts->lib/console/interfaces/index.ts + + + + + + + +no-circular + + + +lib/console/runner.ts->lib/utils/columnify.ts + + + + + + + +no-circular + + + +lib/console/runner.ts->lib/console/metadata.ts + + + + + + + +no-circular + + + +lib/console/runner.ts->lib/console/consoleIO.ts + + + + + + + +no-circular + + + +lib/console/runner.ts->lib/console/logger.ts + + + + + + + +no-circular + + + +lib/console/runner.ts->lib/utils/helpers.ts + + + + + + + +no-circular + + + +node_modules/yargs-parser + + + + + +yargs-parser + + + + + +lib/console/runner.ts->node_modules/yargs-parser + + + + + +lib/utils/helpers.ts->lib/interfaces/index.ts + + + + + +lib/utils/helpers.ts->node_modules/@nestjs/common + + + + + +lib/utils/helpers.ts->lib/utils/array.ts + + + + + + + +no-circular + + + +lib/utils/helpers.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/utils/helpers.ts->node_modules/picocolors + + + + + +lib/utils/helpers.ts->lib/utils/logger.ts + + + + + + + +no-circular + + + +node_modules/class-validator + + + + + +class-validator + + + + + +lib/utils/helpers.ts->node_modules/class-validator + + + + + +lib/utils/helpers.ts->lib/utils/object.ts + + + + + + + +no-circular + + + +node_modules/class-transformer + + + + + +class-transformer + + + + + +lib/utils/helpers.ts->node_modules/class-transformer + + + + + +lib/constants.ts + + +constants.ts + + + + + +lib/database/baseModel.ts + + +baseModel.ts + + + + + +lib/database/interfaces.ts + + +interfaces.ts + + + + + +lib/database/baseModel.ts->lib/database/interfaces.ts + + + + + +lib/database/queryBuilders/custom.ts + + +custom.ts + + + + + +lib/database/baseModel.ts->lib/database/queryBuilders/custom.ts + + + + + +lib/database/queryBuilders/softDelete.ts + + +softDelete.ts + + + + + +lib/database/baseModel.ts->lib/database/queryBuilders/softDelete.ts + + + + + +node_modules/objection + + + + + +objection + + + + + +lib/database/baseModel.ts->node_modules/objection + + + + + +no-duplicate-dep-types + + + +lib/database/interfaces.ts->node_modules/objection + + + + + +no-duplicate-dep-types + + + +lib/database/queryBuilders/custom.ts->lib/interfaces/index.ts + + + + + +lib/database/queryBuilders/custom.ts->lib/database/interfaces.ts + + + + + +lib/database/queryBuilders/custom.ts->node_modules/objection + + + + + +no-duplicate-dep-types + + + +lib/database/queryBuilders/softDelete.ts->lib/database/queryBuilders/custom.ts + + + + + +lib/database/queryBuilders/softDelete.ts->node_modules/objection + + + + + +no-duplicate-dep-types + + + +lib/database/commands/migrations.ts + + +migrations.ts + + + + + +lib/database/commands/migrations.ts->node_modules/@nestjs/common + + + + + +lib/database/commands/migrations.ts->lib/console/index.ts + + + + + +lib/database/commands/migrations.ts->lib/utils/string.ts + + + + + +lib/database/commands/migrations.ts->node_modules/picocolors + + + + + +lib/database/service.ts + + +service.ts + + + + + +lib/database/commands/migrations.ts->lib/database/service.ts + + + + + +lib/database/service.ts->lib/config/index.ts + + + + + +lib/database/service.ts->node_modules/@nestjs/common + + + + + +lib/database/service.ts->lib/database/baseModel.ts + + + + + +lib/database/exceptions/index.ts + + +index.ts + + + + + +lib/database/service.ts->lib/database/exceptions/index.ts + + + + + +lib/database/options.ts + + +options.ts + + + + + +lib/database/service.ts->lib/database/options.ts + + + + + +node_modules/knex + + + + + +knex + + + + + +lib/database/service.ts->node_modules/knex + + + + + +no-duplicate-dep-types + + + +lib/database/decorators.ts + + +decorators.ts + + + + + +lib/database/decorators.ts->lib/database/baseModel.ts + + + + + +lib/index.ts + + +index.ts + + + + + +lib/database/decorators.ts->lib/index.ts + + + + + + + +no-circular + + + +lib/index.ts->lib/config/index.ts + + + + + +lib/index.ts->lib/interfaces/index.ts + + + + + +lib/index.ts->lib/utils/index.ts + + + + + +lib/index.ts->lib/cache/index.ts + + + + + +lib/index.ts->lib/console/index.ts + + + + + +lib/index.ts->lib/foundation/index.ts + + + + + +lib/index.ts->lib/rest/index.ts + + + + + +lib/index.ts->lib/constants.ts + + + + + +lib/database/index.ts + + +index.ts + + + + + +lib/index.ts->lib/database/index.ts + + + + + + + +no-circular + + + +lib/events/index.ts + + +index.ts + + + + + +lib/index.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/logger/index.ts + + +index.ts + + + + + +lib/index.ts->lib/logger/index.ts + + + + + +lib/index.ts->lib/exceptions/index.ts + + + + + +lib/localization/index.ts + + +index.ts + + + + + +lib/index.ts->lib/localization/index.ts + + + + + +lib/mailer/index.ts + + +index.ts + + + + + +lib/index.ts->lib/mailer/index.ts + + + + + +lib/queue/index.ts + + +index.ts + + + + + +lib/index.ts->lib/queue/index.ts + + + + + + + +no-circular + + + +lib/reflections/index.ts + + +index.ts + + + + + +lib/index.ts->lib/reflections/index.ts + + + + + +lib/serializers/validationErrorSerializer.ts + + +validationErrorSerializer.ts + + + + + +lib/index.ts->lib/serializers/validationErrorSerializer.ts + + + + + +lib/serviceProvider.ts + + +serviceProvider.ts + + + + + +lib/index.ts->lib/serviceProvider.ts + + + + + + + +no-circular + + + +lib/storage/index.ts + + +index.ts + + + + + +lib/index.ts->lib/storage/index.ts + + + + + +lib/transformers/index.ts + + +index.ts + + + + + +lib/index.ts->lib/transformers/index.ts + + + + + +lib/validator/index.ts + + +index.ts + + + + + +lib/index.ts->lib/validator/index.ts + + + + + +lib/database/exceptions/connectionNotFound.ts + + +connectionNotFound.ts + + + + + +lib/database/exceptions/index.ts->lib/database/exceptions/connectionNotFound.ts + + + + + +lib/database/exceptions/modelNotFound.ts + + +modelNotFound.ts + + + + + +lib/database/exceptions/index.ts->lib/database/exceptions/modelNotFound.ts + + + + + +lib/database/exceptions/modelNotFound.ts->node_modules/@nestjs/common + + + + + +lib/database/exceptions/repoError.ts + + +repoError.ts + + + + + +lib/database/helpers.ts + + +helpers.ts + + + + + +lib/database/index.ts->lib/database/baseModel.ts + + + + + +lib/database/index.ts->lib/database/interfaces.ts + + + + + +lib/database/index.ts->lib/database/queryBuilders/custom.ts + + + + + +lib/database/index.ts->lib/database/queryBuilders/softDelete.ts + + + + + +lib/database/index.ts->lib/database/service.ts + + + + + +lib/database/index.ts->lib/database/decorators.ts + + + + + + + +no-circular + + + +lib/database/index.ts->lib/database/exceptions/index.ts + + + + + +lib/database/index.ts->lib/database/options.ts + + + + + +lib/database/repositories/contract.ts + + +contract.ts + + + + + +lib/database/index.ts->lib/database/repositories/contract.ts + + + + + +lib/database/repositories/database.ts + + +database.ts + + + + + +lib/database/index.ts->lib/database/repositories/database.ts + + + + + +lib/database/options.ts->node_modules/@nestjs/common + + + + + +lib/database/options.ts->node_modules/knex + + + + + +no-duplicate-dep-types + + + +lib/database/repositories/contract.ts->lib/database/baseModel.ts + + + + + +lib/database/repositories/contract.ts->lib/database/interfaces.ts + + + + + +lib/database/repositories/contract.ts->node_modules/knex + + + + + +no-duplicate-dep-types + + + +lib/database/repositories/database.ts->lib/database/baseModel.ts + + + + + +lib/database/repositories/database.ts->lib/database/interfaces.ts + + + + + +lib/database/repositories/database.ts->lib/database/queryBuilders/custom.ts + + + + + +lib/database/repositories/database.ts->node_modules/objection + + + + + +no-duplicate-dep-types + + + +lib/database/repositories/database.ts->lib/database/service.ts + + + + + +lib/database/repositories/database.ts->lib/database/exceptions/index.ts + + + + + +lib/database/repositories/database.ts->lib/database/exceptions/repoError.ts + + + + + +lib/database/repositories/database.ts->lib/database/repositories/contract.ts + + + + + +lib/database/repositories/database.ts->node_modules/knex + + + + + +no-duplicate-dep-types + + + +lib/dev-server/build.ts + + +build.ts + + + + + +lib/dev-server/build.ts->lib/utils/index.ts + + + + + +lib/dev-server/build.ts->lib/console/index.ts + + + + + +lib/dev-server/build.ts->node_modules/picocolors + + + + + +lib/dev-server/serve.ts + + +serve.ts + + + + + +lib/dev-server/serve.ts->lib/utils/index.ts + + + + + +lib/dev-server/serve.ts->lib/console/index.ts + + + + + +lib/events/constants.ts + + +constants.ts + + + + + +lib/events/decorator.ts + + +decorator.ts + + + + + +lib/events/decorator.ts->lib/interfaces/index.ts + + + + + +lib/events/decorator.ts->node_modules/reflect-metadata + + + + + +not-to-dev-dep + + + +lib/events/decorator.ts->lib/events/constants.ts + + + + + +lib/events/event.ts + + +event.ts + + + + + +lib/events/event.ts->node_modules/reflect-metadata + + + + + +not-to-dev-dep + + + +lib/events/event.ts->lib/utils/helpers.ts + + + + + +lib/events/event.ts->lib/events/constants.ts + + + + + +lib/queue/queue.ts + + +queue.ts + + + + + +lib/events/event.ts->lib/queue/queue.ts + + + + + + + +no-circular + + + +lib/queue/strategy/index.ts + + +index.ts + + + + + +lib/events/event.ts->lib/queue/strategy/index.ts + + + + + +lib/events/helpers.ts + + +helpers.ts + + + + + +lib/events/event.ts->lib/events/helpers.ts + + + + + + + +no-circular + + + +lib/events/runner.ts + + +runner.ts + + + + + +lib/events/event.ts->lib/events/runner.ts + + + + + +lib/queue/queue.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/metadata.ts + + +metadata.ts + + + + + +lib/queue/queue.ts->lib/queue/metadata.ts + + + + + +lib/queue/core/index.ts + + +index.ts + + + + + +lib/queue/queue.ts->lib/queue/core/index.ts + + + + + +lib/queue/strategy/pollQueueDriver.ts + + +pollQueueDriver.ts + + + + + +lib/queue/queue.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/service.ts + + +service.ts + + + + + +lib/queue/queue.ts->lib/queue/service.ts + + + + + + + +no-circular + + + +lib/queue/strategy/subscribeQueueDriver.ts + + +subscribeQueueDriver.ts + + + + + +lib/queue/queue.ts->lib/queue/strategy/subscribeQueueDriver.ts + + + + + +lib/queue/strategy/index.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + + + +no-circular + + + +lib/queue/strategy/index.ts->lib/queue/strategy/subscribeQueueDriver.ts + + + + + +lib/queue/strategy/driverJob.ts + + +driverJob.ts + + + + + +lib/queue/strategy/index.ts->lib/queue/strategy/driverJob.ts + + + + + +lib/queue/strategy/message.ts + + +message.ts + + + + + +lib/queue/strategy/index.ts->lib/queue/strategy/message.ts + + + + + +lib/events/helpers.ts->lib/events/event.ts + + + + + + + +no-circular + + + +lib/events/runner.ts->lib/utils/helpers.ts + + + + + +lib/events/metadata.ts + + +metadata.ts + + + + + +lib/events/runner.ts->lib/events/metadata.ts + + + + + +lib/events/index.ts->lib/events/decorator.ts + + + + + +lib/events/index.ts->lib/events/event.ts + + + + + + + +no-circular + + + +lib/events/index.ts->lib/events/helpers.ts + + + + + + + +no-circular + + + +lib/events/index.ts->lib/events/runner.ts + + + + + +lib/events/interfaces.ts + + +interfaces.ts + + + + + +lib/events/index.ts->lib/events/interfaces.ts + + + + + +lib/events/jobListener.ts + + +jobListener.ts + + + + + +lib/events/index.ts->lib/events/jobListener.ts + + + + + + + +no-circular + + + +lib/events/index.ts->lib/events/metadata.ts + + + + + +lib/events/interfaces.ts->lib/queue/strategy/index.ts + + + + + +lib/events/jobListener.ts->node_modules/@nestjs/common + + + + + +lib/events/jobListener.ts->lib/events/constants.ts + + + + + +lib/events/jobListener.ts->lib/events/runner.ts + + + + + +lib/queue/decorators.ts + + +decorators.ts + + + + + +lib/events/jobListener.ts->lib/queue/decorators.ts + + + + + + + +no-circular + + + +lib/events/metadata.ts->lib/interfaces/index.ts + + + + + +lib/queue/decorators.ts->node_modules/reflect-metadata + + + + + +not-to-dev-dep + + + +lib/queue/decorators.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/decorators.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/queue/constants.ts + + +constants.ts + + + + + +lib/queue/decorators.ts->lib/queue/constants.ts + + + + + +lib/exceptions/base-exception-handler.ts + + +base-exception-handler.ts + + + + + +lib/exceptions/base-exception-handler.ts->lib/interfaces/index.ts + + + + + +lib/exceptions/base-exception-handler.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/exceptions/base-exception-handler.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/exceptions/base-exception-handler.ts->lib/logger/index.ts + + + + + + + +no-circular + + + +lib/exceptions/base-exception-handler.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/status-codes.ts + + +status-codes.ts + + + + + +lib/exceptions/base-exception-handler.ts->lib/rest/http-server/status-codes.ts + + + + + +lib/exceptions/http-exception.ts + + +http-exception.ts + + + + + +lib/exceptions/base-exception-handler.ts->lib/exceptions/http-exception.ts + + + + + +lib/exceptions/validation-failed.ts + + +validation-failed.ts + + + + + +lib/exceptions/base-exception-handler.ts->lib/exceptions/validation-failed.ts + + + + + + + +no-circular + + + +lib/logger/logger.ts + + +logger.ts + + + + + +lib/logger/index.ts->lib/logger/logger.ts + + + + + + + +no-circular + + + +lib/logger/options.ts + + +options.ts + + + + + +lib/logger/index.ts->lib/logger/options.ts + + + + + +lib/logger/service.ts + + +service.ts + + + + + +lib/logger/index.ts->lib/logger/service.ts + + + + + + + +no-circular + + + +lib/rest/http-server/index.ts->lib/rest/http-server/status-codes.ts + + + + + +lib/rest/http-server/request/index.ts + + +index.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/request/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/contexts/execution-context.ts + + +execution-context.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/contexts/execution-context.ts + + + + + + + +no-circular + + + +lib/rest/http-server/contexts/http-execution-context.ts + + +http-execution-context.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/contexts/http-execution-context.ts + + + + + + + +no-circular + + + +lib/rest/http-server/param-decorators.ts + + +param-decorators.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/param-decorators.ts + + + + + + + +no-circular + + + +lib/rest/http-server/response.ts + + +response.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/response.ts + + + + + + + +no-circular + + + +lib/rest/http-server/decorators.ts + + +decorators.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/decorators.ts + + + + + + + +no-circular + + + +lib/rest/http-server/interfaces.ts + + +interfaces.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/interfaces.ts + + + + + +lib/rest/http-server/http-handler.ts + + +http-handler.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/http-handler.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts + + +route-explorer.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/route-explorer.ts + + + + + + + +no-circular + + + +lib/rest/http-server/server.ts + + +server.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/server.ts + + + + + + + +no-circular + + + +lib/rest/http-server/streamable-file.ts + + +streamable-file.ts + + + + + +lib/rest/http-server/index.ts->lib/rest/http-server/streamable-file.ts + + + + + +lib/exceptions/http-exception.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/exceptions/http-exception.ts->lib/rest/index.ts + + + + + + + +no-circular + + + +lib/exceptions/validation-failed.ts->lib/rest/index.ts + + + + + +lib/exceptions/validation-failed.ts->lib/exceptions/http-exception.ts + + + + + + + +no-circular + + + +lib/exceptions/forbidden-exception.ts + + +forbidden-exception.ts + + + + + +lib/exceptions/forbidden-exception.ts->lib/rest/index.ts + + + + + + + +no-circular + + + +lib/exceptions/forbidden-exception.ts->lib/exceptions/http-exception.ts + + + + + + + +no-circular + + + +lib/exceptions/genericException.ts + + +genericException.ts + + + + + +lib/exceptions/genericException.ts->lib/exceptions/http-exception.ts + + + + + +lib/exceptions/index.ts->lib/exceptions/base-exception-handler.ts + + + + + + + +no-circular + + + +lib/exceptions/index.ts->lib/exceptions/http-exception.ts + + + + + + + +no-circular + + + +lib/exceptions/index.ts->lib/exceptions/validation-failed.ts + + + + + + + +no-circular + + + +lib/exceptions/index.ts->lib/exceptions/forbidden-exception.ts + + + + + +lib/exceptions/index.ts->lib/exceptions/genericException.ts + + + + + + + +no-circular + + + +lib/exceptions/invalid-credentials.ts + + +invalid-credentials.ts + + + + + +lib/exceptions/index.ts->lib/exceptions/invalid-credentials.ts + + + + + + + +no-circular + + + +lib/exceptions/invalid-value.ts + + +invalid-value.ts + + + + + +lib/exceptions/index.ts->lib/exceptions/invalid-value.ts + + + + + +lib/exceptions/invalid-value-type.ts + + +invalid-value-type.ts + + + + + +lib/exceptions/index.ts->lib/exceptions/invalid-value-type.ts + + + + + +lib/exceptions/unauthorized.ts + + +unauthorized.ts + + + + + +lib/exceptions/index.ts->lib/exceptions/unauthorized.ts + + + + + + + +no-circular + + + +lib/exceptions/invalid-credentials.ts->lib/rest/index.ts + + + + + +lib/exceptions/invalid-credentials.ts->lib/exceptions/http-exception.ts + + + + + +lib/exceptions/unauthorized.ts->lib/rest/index.ts + + + + + +lib/exceptions/unauthorized.ts->lib/exceptions/http-exception.ts + + + + + +lib/explorer.ts + + +explorer.ts + + + + + +lib/explorer.ts->lib/interfaces/index.ts + + + + + +lib/explorer.ts->lib/console/index.ts + + + + + +lib/explorer.ts->lib/foundation/index.ts + + + + + +lib/explorer.ts->node_modules/@nestjs/core + + + + + +lib/explorer.ts->lib/console/constants.ts + + + + + +lib/explorer.ts->lib/events/constants.ts + + + + + +lib/explorer.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/explorer.ts->lib/queue/constants.ts + + + + + +lib/explorer.ts->lib/queue/metadata.ts + + + + + +lib/queue/metadata.ts->lib/interfaces/index.ts + + + + + +lib/queue/metadata.ts->node_modules/@nestjs/common + + + + + +lib/queue/metadata.ts->lib/config/service.ts + + + + + +lib/queue/metadata.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/interfaces/index.ts + + +index.ts + + + + + +lib/queue/metadata.ts->lib/queue/interfaces/index.ts + + + + + +lib/foundation/app-container.ts->lib/interfaces/index.ts + + + + + +lib/foundation/app-container.ts->node_modules/@nestjs/common + + + + + +lib/foundation/app-container.ts->lib/foundation/service-provider.ts + + + + + +lib/foundation/service-provider.ts->lib/interfaces/index.ts + + + + + +lib/foundation/service-provider.ts->node_modules/@nestjs/common + + + + + +lib/foundation/container-factory.ts->lib/interfaces/index.ts + + + + + +lib/foundation/container-factory.ts->node_modules/@nestjs/core + + + + + +lib/foundation/container-factory.ts->lib/foundation/app-container.ts + + + + + +lib/foundation/container-factory.ts->lib/foundation/module-builder.ts + + + + + + + +no-circular + + + +lib/foundation/module-builder.ts->lib/interfaces/index.ts + + + + + +lib/foundation/module-builder.ts->node_modules/@nestjs/common + + + + + +lib/foundation/module-builder.ts->lib/rest/index.ts + + + + + +lib/foundation/module-builder.ts->lib/foundation/app-container.ts + + + + + +lib/rest/foundation/middlewares/configurator.ts + + +configurator.ts + + + + + +lib/foundation/module-builder.ts->lib/rest/foundation/middlewares/configurator.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/configurator.ts->lib/interfaces/index.ts + + + + + +lib/rest/foundation/middlewares/configurator.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/middleware.ts + + +middleware.ts + + + + + +lib/rest/foundation/middlewares/configurator.ts->lib/rest/foundation/middlewares/middleware.ts + + + + + + + +no-circular + + + +lib/localization/helpers/index.ts + + +index.ts + + + + + +lib/localization/index.ts->lib/localization/helpers/index.ts + + + + + +lib/localization/service.ts + + +service.ts + + + + + +lib/localization/index.ts->lib/localization/service.ts + + + + + +lib/localization/interfaces/index.ts + + +index.ts + + + + + +lib/localization/index.ts->lib/localization/interfaces/index.ts + + + + + +lib/mailer/interfaces/index.ts + + +index.ts + + + + + +lib/mailer/index.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/mail.ts + + +mail.ts + + + + + +lib/mailer/index.ts->lib/mailer/mail.ts + + + + + +lib/mailer/message.ts + + +message.ts + + + + + +lib/mailer/index.ts->lib/mailer/message.ts + + + + + +lib/mailer/service.ts + + +service.ts + + + + + +lib/mailer/index.ts->lib/mailer/service.ts + + + + + +lib/queue/index.ts->lib/queue/queue.ts + + + + + + + +no-circular + + + +lib/queue/index.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/index.ts->lib/queue/decorators.ts + + + + + + + +no-circular + + + +lib/queue/worker.ts + + +worker.ts + + + + + +lib/queue/index.ts->lib/queue/worker.ts + + + + + + + +no-circular + + + +lib/queue/index.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/drivers/index.ts + + +index.ts + + + + + +lib/queue/index.ts->lib/queue/drivers/index.ts + + + + + + + +no-circular + + + +lib/queue/index.ts->lib/queue/service.ts + + + + + + + +no-circular + + + +lib/reflections/reflector.ts + + +reflector.ts + + + + + +lib/reflections/index.ts->lib/reflections/reflector.ts + + + + + + + +no-circular + + + +lib/reflections/set-metadata.ts + + +set-metadata.ts + + + + + +lib/reflections/index.ts->lib/reflections/set-metadata.ts + + + + + +lib/serializers/validationErrorSerializer.ts->lib/utils/string.ts + + + + + +lib/serializers/validationErrorSerializer.ts->lib/utils/helpers.ts + + + + + +lib/serializers/validationErrorSerializer.ts->node_modules/class-validator + + + + + +lib/serviceProvider.ts->lib/config/index.ts + + + + + +lib/serviceProvider.ts->lib/interfaces/index.ts + + + + + +lib/serviceProvider.ts->lib/cache/index.ts + + + + + +lib/serviceProvider.ts->lib/console/index.ts + + + + + +lib/serviceProvider.ts->lib/config/command.ts + + + + + +lib/serviceProvider.ts->lib/foundation/index.ts + + + + + +lib/serviceProvider.ts->node_modules/@nestjs/core + + + + + +lib/serviceProvider.ts->lib/database/commands/migrations.ts + + + + + +lib/serviceProvider.ts->lib/database/index.ts + + + + + + + +no-circular + + + +lib/serviceProvider.ts->lib/dev-server/build.ts + + + + + +lib/serviceProvider.ts->lib/dev-server/serve.ts + + + + + +lib/serviceProvider.ts->lib/events/jobListener.ts + + + + + + + +no-circular + + + +lib/serviceProvider.ts->lib/logger/index.ts + + + + + +lib/serviceProvider.ts->lib/explorer.ts + + + + + + + +no-circular + + + +lib/serviceProvider.ts->lib/queue/metadata.ts + + + + + +lib/serviceProvider.ts->lib/localization/index.ts + + + + + +lib/serviceProvider.ts->lib/mailer/index.ts + + + + + +lib/serviceProvider.ts->lib/queue/index.ts + + + + + + + +no-circular + + + +lib/queue/console/index.ts + + +index.ts + + + + + +lib/serviceProvider.ts->lib/queue/console/index.ts + + + + + + + +no-circular + + + +lib/storage/service.ts + + +service.ts + + + + + +lib/serviceProvider.ts->lib/storage/service.ts + + + + + +lib/storage/interfaces/index.ts + + +index.ts + + + + + +lib/storage/index.ts->lib/storage/interfaces/index.ts + + + + + +lib/storage/drivers/index.ts + + +index.ts + + + + + +lib/storage/index.ts->lib/storage/drivers/index.ts + + + + + +lib/storage/storage.ts + + +storage.ts + + + + + +lib/storage/index.ts->lib/storage/storage.ts + + + + + +lib/transformers/interfaces.ts + + +interfaces.ts + + + + + +lib/transformers/index.ts->lib/transformers/interfaces.ts + + + + + +lib/transformers/transformable.ts + + +transformable.ts + + + + + +lib/transformers/index.ts->lib/transformers/transformable.ts + + + + + +lib/transformers/transformer.ts + + +transformer.ts + + + + + +lib/transformers/index.ts->lib/transformers/transformer.ts + + + + + +lib/validator/index.ts->node_modules/@nestjs/common + + + + + +lib/validator/index.ts->lib/rest/index.ts + + + + + +lib/validator/decorators/index.ts + + +index.ts + + + + + +lib/validator/index.ts->lib/validator/decorators/index.ts + + + + + + + +no-circular + + + +lib/validator/validationGuard.ts + + +validationGuard.ts + + + + + +lib/validator/index.ts->lib/validator/validationGuard.ts + + + + + + + +no-circular + + + +lib/validator/validator.ts + + +validator.ts + + + + + +lib/validator/index.ts->lib/validator/validator.ts + + + + + + + +no-circular + + + +lib/interfaces/config.ts->node_modules/@nestjs/common + + + + + +lib/interfaces/config.ts->lib/interfaces/utils.ts + + + + + +node_modules/hyper-express + + + + + +hyper-express + + + + + +lib/interfaces/config.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/interfaces/utils.ts->node_modules/@nestjs/common + + + + + +lib/localization/helpers/index.ts->lib/localization/service.ts + + + + + +lib/localization/service.ts->lib/utils/index.ts + + + + + +lib/localization/service.ts->node_modules/@nestjs/common + + + + + +lib/localization/service.ts->lib/utils/string.ts + + + + + +lib/localization/service.ts->path + + + + + +lib/localization/service.ts->lib/config/service.ts + + + + + +lib/localization/service.ts->lib/localization/interfaces/index.ts + + + + + +lib/localization/service.ts->lib/utils/number.ts + + + + + +node_modules/fs-extra + + + + + +fs-extra + + + + + +lib/localization/service.ts->node_modules/fs-extra + + + + + +lib/utils/number.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/localization/utils/get.ts + + +get.ts + + + + + +lib/localization/utils/stringToPath.ts + + +stringToPath.ts + + + + + +lib/localization/utils/get.ts->lib/localization/utils/stringToPath.ts + + + + + +lib/localization/utils/memoizeCapped.ts + + +memoizeCapped.ts + + + + + +lib/localization/utils/stringToPath.ts->lib/localization/utils/memoizeCapped.ts + + + + + +lib/localization/utils/memoize.ts + + +memoize.ts + + + + + +lib/localization/utils/memoize.ts->lib/interfaces/index.ts + + + + + +lib/localization/utils/memoizeCapped.ts->lib/localization/utils/memoize.ts + + + + + +lib/logger/logger.ts->lib/logger/service.ts + + + + + + + +no-circular + + + +node_modules/winston + + + + + +winston + + + + + +lib/logger/options.ts->node_modules/winston + + + + + +lib/logger/service.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/logger/service.ts->node_modules/@nestjs/common + + + + + +lib/logger/service.ts->path + + + + + +lib/logger/service.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/logger/service.ts->lib/utils/number.ts + + + + + + + +no-circular + + + +lib/logger/service.ts->lib/logger/options.ts + + + + + +lib/logger/service.ts->node_modules/winston + + + + + +lib/mailer/constants.ts + + +constants.ts + + + + + +lib/mailer/exceptions/errorSendingMail.ts + + +errorSendingMail.ts + + + + + +lib/mailer/exceptions/errorSendingMail.ts->lib/utils/string.ts + + + + + +lib/mailer/exceptions/invalid-mail-provider.ts + + +invalid-mail-provider.ts + + + + + +lib/mailer/interfaces/options.ts + + +options.ts + + + + + +lib/mailer/interfaces/index.ts->lib/mailer/interfaces/options.ts + + + + + +lib/mailer/mail.ts->lib/mailer/message.ts + + + + + +lib/mailer/mail.ts->lib/mailer/service.ts + + + + + +lib/mailer/message.ts->lib/utils/index.ts + + + + + +lib/mailer/message.ts->lib/config/service.ts + + + + + +lib/mailer/message.ts->lib/mailer/constants.ts + + + + + +lib/mailer/message.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/interfaces/provider.ts + + +provider.ts + + + + + +lib/mailer/message.ts->lib/mailer/interfaces/provider.ts + + + + + + + +no-circular + + + +resources/mail/emails/index.tsx + + +index.tsx + + + + + +lib/mailer/message.ts->resources/mail/emails/index.tsx + + + + + +lib/mailer/service.ts->lib/utils/index.ts + + + + + +lib/mailer/service.ts->node_modules/@nestjs/common + + + + + +lib/mailer/service.ts->lib/config/service.ts + + + + + +lib/mailer/service.ts->lib/mailer/exceptions/invalid-mail-provider.ts + + + + + +lib/mailer/service.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/service.ts->lib/mailer/interfaces/provider.ts + + + + + +lib/mailer/providers/index.ts + + +index.ts + + + + + +lib/mailer/service.ts->lib/mailer/providers/index.ts + + + + + +lib/mailer/interfaces/provider.ts->lib/mailer/message.ts + + + + + + + +no-circular + + + +node_modules/@react-email/components + + + + + +components + + + + + +resources/mail/emails/index.tsx->node_modules/@react-email/components + + + + + +node_modules/@react-email/tailwind + + + + + +tailwind + + + + + +resources/mail/emails/index.tsx->node_modules/@react-email/tailwind + + + + + +no-non-package-json + + + +resources/mail/components/Footer.tsx + + +Footer.tsx + + + + + +resources/mail/emails/index.tsx->resources/mail/components/Footer.tsx + + + + + +resources/mail/components/Header.tsx + + +Header.tsx + + + + + +resources/mail/emails/index.tsx->resources/mail/components/Header.tsx + + + + + +resources/mail/emails/components.tsx + + +components.tsx + + + + + +resources/mail/emails/index.tsx->resources/mail/emails/components.tsx + + + + + +lib/mailer/providers/logger.ts + + +logger.ts + + + + + +lib/mailer/providers/index.ts->lib/mailer/providers/logger.ts + + + + + +lib/mailer/providers/mailgun.ts + + +mailgun.ts + + + + + +lib/mailer/providers/index.ts->lib/mailer/providers/mailgun.ts + + + + + +lib/mailer/providers/nodemailer.ts + + +nodemailer.ts + + + + + +lib/mailer/providers/index.ts->lib/mailer/providers/nodemailer.ts + + + + + +lib/mailer/providers/resend.ts + + +resend.ts + + + + + +lib/mailer/providers/index.ts->lib/mailer/providers/resend.ts + + + + + +lib/mailer/providers/logger.ts->lib/logger/index.ts + + + + + +lib/mailer/providers/logger.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/providers/logger.ts->lib/mailer/interfaces/provider.ts + + + + + +lib/mailer/providers/mailgun.ts->lib/utils/index.ts + + + + + +lib/mailer/providers/mailgun.ts->lib/utils/string.ts + + + + + +lib/mailer/providers/mailgun.ts->lib/utils/helpers.ts + + + + + +lib/mailer/providers/mailgun.ts->lib/storage/index.ts + + + + + +lib/mailer/providers/mailgun.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/providers/mailgun.ts->lib/mailer/interfaces/provider.ts + + + + + +lib/mailer/providers/nodemailer.ts->lib/utils/index.ts + + + + + +lib/mailer/providers/nodemailer.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/providers/nodemailer.ts->lib/mailer/interfaces/provider.ts + + + + + +lib/mailer/providers/resend.ts->lib/utils/index.ts + + + + + +lib/mailer/providers/resend.ts->lib/mailer/exceptions/errorSendingMail.ts + + + + + +lib/mailer/providers/resend.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/providers/resend.ts->lib/mailer/interfaces/provider.ts + + + + + +lib/mailer/providers/sendgrid.ts + + +sendgrid.ts + + + + + +lib/mailer/providers/sendgrid.ts->lib/utils/index.ts + + + + + +lib/mailer/providers/sendgrid.ts->lib/mailer/interfaces/index.ts + + + + + +lib/mailer/providers/sendgrid.ts->lib/mailer/interfaces/provider.ts + + + + + +lib/queue/console/commands.ts + + +commands.ts + + + + + +lib/queue/console/commands.ts->node_modules/@nestjs/common + + + + + +lib/queue/console/commands.ts->lib/console/index.ts + + + + + +lib/queue/console/commands.ts->lib/queue/worker.ts + + + + + + + +no-circular + + + +lib/queue/worker.ts->lib/queue/metadata.ts + + + + + +lib/queue/worker.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/worker.ts->lib/queue/service.ts + + + + + + + +no-circular + + + +lib/queue/workers/pollQueue.ts + + +pollQueue.ts + + + + + +lib/queue/worker.ts->lib/queue/workers/pollQueue.ts + + + + + + + +no-circular + + + +lib/queue/workers/subscribeQueue.ts + + +subscribeQueue.ts + + + + + +lib/queue/worker.ts->lib/queue/workers/subscribeQueue.ts + + + + + + + +no-circular + + + +lib/queue/console/index.ts->lib/queue/console/commands.ts + + + + + + + +no-circular + + + +lib/queue/core/payloadBuilder.ts + + +payloadBuilder.ts + + + + + +lib/queue/core/index.ts->lib/queue/core/payloadBuilder.ts + + + + + +lib/queue/core/payloadBuilder.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/core/payloadBuilder.ts->lib/queue/metadata.ts + + + + + +node_modules/ms + + + + + +ms + + + + + +lib/queue/core/payloadBuilder.ts->node_modules/ms + + + + + +node_modules/ulid + + + + + +ulid + + + + + +lib/queue/core/payloadBuilder.ts->node_modules/ulid + + + + + +lib/queue/drivers/database.ts + + +database.ts + + + + + +lib/queue/drivers/database.ts->lib/database/index.ts + + + + + + + +no-circular + + + +lib/queue/drivers/database.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/drivers/database.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/interfaces/job.ts + + +job.ts + + + + + +lib/queue/drivers/database.ts->lib/queue/interfaces/job.ts + + + + + +lib/queue/drivers/database.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/interfaces/options.ts + + +options.ts + + + + + +lib/queue/interfaces/index.ts->lib/queue/interfaces/options.ts + + + + + +lib/queue/interfaces/job.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/strategy/pollQueueDriver.ts->lib/queue/strategy/index.ts + + + + + + + +no-circular + + + +lib/queue/strategy/pollQueueDriver.ts->lib/queue/strategy/subscribeQueueDriver.ts + + + + + +lib/queue/drivers/index.ts->lib/queue/drivers/database.ts + + + + + + + +no-circular + + + +lib/queue/drivers/redis.ts + + +redis.ts + + + + + +lib/queue/drivers/index.ts->lib/queue/drivers/redis.ts + + + + + +lib/queue/drivers/sqs.ts + + +sqs.ts + + + + + +lib/queue/drivers/index.ts->lib/queue/drivers/sqs.ts + + + + + +lib/queue/drivers/sync.ts + + +sync.ts + + + + + +lib/queue/drivers/index.ts->lib/queue/drivers/sync.ts + + + + + +lib/queue/drivers/redis.ts->lib/utils/index.ts + + + + + +lib/queue/drivers/redis.ts->lib/utils/helpers.ts + + + + + +lib/queue/drivers/redis.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/drivers/redis.ts->node_modules/ulid + + + + + +lib/queue/drivers/redis.ts->lib/queue/interfaces/job.ts + + + + + +lib/queue/drivers/redis.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/schema/index.ts + + +index.ts + + + + + +lib/queue/drivers/redis.ts->lib/queue/schema/index.ts + + + + + +lib/queue/drivers/sqs.ts->lib/utils/index.ts + + + + + +lib/queue/drivers/sqs.ts->lib/utils/helpers.ts + + + + + +lib/queue/drivers/sqs.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/drivers/sqs.ts->lib/queue/interfaces/job.ts + + + + + +lib/queue/drivers/sqs.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/drivers/sqs.ts->lib/queue/schema/index.ts + + + + + +lib/queue/drivers/sync.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/drivers/sync.ts->lib/queue/metadata.ts + + + + + +lib/queue/drivers/sync.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/schema/index.ts->node_modules/class-validator + + + + + +lib/queue/events/JobFailed.ts + + +JobFailed.ts + + + + + +lib/queue/events/JobFailed.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/queue/events/JobFailed.ts->lib/queue/constants.ts + + + + + +lib/queue/events/JobProcessed.ts + + +JobProcessed.ts + + + + + +lib/queue/events/JobProcessed.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/queue/events/JobProcessed.ts->lib/queue/constants.ts + + + + + +lib/queue/events/JobProcessing.ts + + +JobProcessing.ts + + + + + +lib/queue/events/JobProcessing.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/queue/events/JobProcessing.ts->lib/queue/constants.ts + + + + + +lib/queue/events/index.ts + + +index.ts + + + + + +lib/queue/events/index.ts->lib/queue/events/JobFailed.ts + + + + + + + +no-circular + + + +lib/queue/events/index.ts->lib/queue/events/JobProcessed.ts + + + + + + + +no-circular + + + +lib/queue/events/index.ts->lib/queue/events/JobProcessing.ts + + + + + + + +no-circular + + + +lib/queue/events/jobMaxRetries.ts + + +jobMaxRetries.ts + + + + + +lib/queue/events/index.ts->lib/queue/events/jobMaxRetries.ts + + + + + + + +no-circular + + + +lib/queue/events/jobMaxRetries.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/queue/events/jobMaxRetries.ts->lib/queue/constants.ts + + + + + +lib/queue/exceptions/maxRetriesExceeded.ts + + +maxRetriesExceeded.ts + + + + + +lib/queue/service.ts->lib/config/index.ts + + + + + +lib/queue/service.ts->lib/utils/index.ts + + + + + +lib/queue/service.ts->node_modules/@nestjs/common + + + + + +lib/queue/service.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/service.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/service.ts->lib/queue/drivers/index.ts + + + + + + + +no-circular + + + +lib/queue/interfaces/options.ts->node_modules/@nestjs/common + + + + + +lib/queue/interfaces/options.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/jobRunners/base.ts + + +base.ts + + + + + +lib/queue/jobRunners/base.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/jobRunners/base.ts->lib/queue/constants.ts + + + + + +lib/queue/jobRunners/base.ts->lib/queue/metadata.ts + + + + + +lib/queue/jobRunners/base.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/jobRunners/base.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/strategy/subscribeQueueDriver.ts->lib/interfaces/index.ts + + + + + +lib/queue/strategy/subscribeQueueDriver.ts->lib/queue/strategy/driverJob.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/utils/index.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/utils/helpers.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/queue/queue.ts + + + + + + + +no-circular + + + +lib/queue/workers/pollQueue.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/events/index.ts + + + + + + + +no-circular + + + +lib/queue/workers/pollQueue.ts->lib/queue/constants.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/queue/metadata.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/queue/strategy/pollQueueDriver.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/queue/events/index.ts + + + + + + + +no-circular + + + +lib/queue/workers/pollQueue.ts->lib/queue/events/jobMaxRetries.ts + + + + + + + +no-circular + + + +lib/queue/workers/pollQueue.ts->lib/queue/service.ts + + + + + + + +no-circular + + + +lib/queue/workers/pollQueue.ts->lib/queue/jobRunners/base.ts + + + + + +lib/queue/workers/baseWorker.ts + + +baseWorker.ts + + + + + +lib/queue/workers/pollQueue.ts->lib/queue/workers/baseWorker.ts + + + + + +lib/queue/workers/subscribeQueue.ts->lib/utils/index.ts + + + + + +lib/queue/workers/subscribeQueue.ts->lib/queue/metadata.ts + + + + + +lib/queue/workers/subscribeQueue.ts->lib/queue/interfaces/index.ts + + + + + +lib/queue/workers/subscribeQueue.ts->lib/queue/service.ts + + + + + + + +no-circular + + + +lib/queue/workers/subscribeQueue.ts->lib/queue/strategy/subscribeQueueDriver.ts + + + + + +lib/queue/workers/baseWorker.ts->node_modules/picocolors + + + + + +lib/queue/workers/baseWorker.ts->lib/utils/helpers.ts + + + + + +lib/queue/workers/baseWorker.ts->lib/queue/strategy/index.ts + + + + + +lib/queue/workers/baseWorker.ts->lib/queue/interfaces/index.ts + + + + + +lib/reflections/apply-decorators.ts + + +apply-decorators.ts + + + + + +lib/reflections/reflector.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/reflections/reflector.ts->node_modules/reflect-metadata + + + + + +not-to-dev-dep + + + +lib/reflections/reflector.ts->node_modules/ulid + + + + + +lib/rest/decorators.ts + + +decorators.ts + + + + + +lib/rest/foundation/controller-scanner.ts + + +controller-scanner.ts + + + + + +lib/rest/foundation/controller-scanner.ts->lib/interfaces/index.ts + + + + + +lib/rest/foundation/controller-scanner.ts->path + + + + + +lib/rest/foundation/controller-scanner.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/constants.ts + + +constants.ts + + + + + +lib/rest/foundation/controller-scanner.ts->lib/rest/http-server/constants.ts + + + + + +lib/rest/foundation/guards/base-guard.ts + + +base-guard.ts + + + + + +lib/rest/foundation/guards/base-guard.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/guards/base-guard.ts->lib/exceptions/forbidden-exception.ts + + + + + + + +no-circular + + + +lib/rest/foundation/guards/base-guard.ts->lib/reflections/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/guards/base-guard.ts->lib/rest/http-server/request/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/extension.ts + + +extension.ts + + + + + +lib/rest/http-server/request/index.ts->lib/rest/http-server/request/extension.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/interfaces.ts + + +interfaces.ts + + + + + +lib/rest/http-server/request/index.ts->lib/rest/http-server/request/interfaces.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/middleware.ts + + +middleware.ts + + + + + +lib/rest/http-server/request/index.ts->lib/rest/http-server/request/middleware.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/multipart-handler.ts + + +multipart-handler.ts + + + + + +lib/rest/http-server/request/index.ts->lib/rest/http-server/request/multipart-handler.ts + + + + + + + +no-circular + + + +lib/rest/foundation/index.ts->lib/rest/foundation/middlewares/configurator.ts + + + + + + + +no-circular + + + +lib/rest/foundation/index.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/foundation/index.ts->lib/rest/foundation/guards/base-guard.ts + + + + + + + +no-circular + + + +lib/rest/foundation/kernel.ts + + +kernel.ts + + + + + +lib/rest/foundation/index.ts->lib/rest/foundation/kernel.ts + + + + + + + +no-circular + + + +lib/rest/foundation/index.ts->lib/rest/foundation/middlewares/middleware.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts + + +server.ts + + + + + +lib/rest/foundation/index.ts->lib/rest/foundation/server.ts + + + + + + + +no-circular + + + +lib/rest/foundation/kernel.ts->lib/interfaces/index.ts + + + + + +lib/rest/foundation/kernel.ts->lib/rest/foundation/middlewares/configurator.ts + + + + + + + +no-circular + + + +lib/rest/foundation/kernel.ts->lib/rest/foundation/guards/base-guard.ts + + + + + + + +no-circular + + + +lib/rest/foundation/kernel.ts->lib/rest/foundation/middlewares/middleware.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/middleware.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/middleware.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/foundation/server.ts->lib/interfaces/index.ts + + + + + +lib/rest/foundation/server.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->lib/foundation/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->lib/utils/console-helpers.ts + + + + + +lib/rest/foundation/server.ts->node_modules/picocolors + + + + + +lib/rest/foundation/server.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->node_modules/@nestjs/core + + + + + +lib/rest/foundation/server.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->lib/exceptions/index.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->lib/rest/foundation/middlewares/configurator.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/foundation/server.ts->node_modules/class-validator + + + + + +lib/rest/foundation/server.ts->lib/rest/foundation/kernel.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/middleware-composer.ts + + +middleware-composer.ts + + + + + +lib/rest/foundation/server.ts->lib/rest/foundation/middlewares/middleware-composer.ts + + + + + + + +no-circular + + + +lib/rest/foundation/server.ts->lib/rest/middlewares/cors.ts + + + + + + + +no-circular + + + +node_modules/console.mute + + + + + +console.mute + + + + + +lib/rest/foundation/server.ts->node_modules/console.mute + + + + + +lib/rest/foundation/middlewares/middleware-composer.ts->lib/interfaces/index.ts + + + + + +lib/rest/foundation/middlewares/middleware-composer.ts->node_modules/@nestjs/core + + + + + +lib/rest/foundation/middlewares/middleware-composer.ts->lib/rest/foundation/middlewares/configurator.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/middleware-composer.ts->lib/rest/foundation/controller-scanner.ts + + + + + + + +no-circular + + + +lib/rest/foundation/middlewares/middleware-composer.ts->lib/rest/foundation/middlewares/middleware.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/cors.ts->lib/config/index.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/cors.ts->node_modules/@nestjs/common + + + + + +lib/rest/middlewares/cors.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/cors.ts->lib/rest/foundation/index.ts + + + + + + + +no-circular + + + +node_modules/cors + + + + + +cors + + + + + +lib/rest/middlewares/cors.ts->node_modules/cors + + + + + +no-non-package-json + + + +lib/rest/http-server/contexts/execution-context.ts->lib/interfaces/index.ts + + + + + +lib/rest/http-server/contexts/execution-context.ts->lib/rest/http-server/contexts/http-execution-context.ts + + + + + + + +no-circular + + + +lib/rest/http-server/contexts/http-execution-context.ts->lib/rest/http-server/request/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/contexts/http-execution-context.ts->lib/rest/http-server/param-decorators.ts + + + + + + + +no-circular + + + +lib/rest/http-server/contexts/http-execution-context.ts->lib/rest/http-server/response.ts + + + + + + + +no-circular + + + +lib/rest/http-server/param-decorators.ts->lib/rest/http-server/constants.ts + + + + + +lib/rest/http-server/param-decorators.ts->lib/rest/http-server/contexts/execution-context.ts + + + + + + + +no-circular + + + +lib/rest/http-server/response.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/response.ts->lib/rest/http-server/status-codes.ts + + + + + +lib/rest/http-server/response.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/http-server/response.ts->lib/rest/http-server/streamable-file.ts + + + + + +lib/utils/extension-to-mime.ts + + +extension-to-mime.ts + + + + + +lib/rest/http-server/response.ts->lib/utils/extension-to-mime.ts + + + + + +lib/rest/http-server/decorators.ts->lib/interfaces/index.ts + + + + + +lib/rest/http-server/decorators.ts->node_modules/@nestjs/common + + + + + +lib/rest/http-server/decorators.ts->lib/reflections/apply-decorators.ts + + + + + +lib/rest/http-server/decorators.ts->lib/rest/http-server/constants.ts + + + + + +lib/rest/http-server/decorators.ts->lib/rest/foundation/index.ts + + + + + +lib/rest/http-server/decorators.ts->lib/rest/http-server/interfaces.ts + + + + + +lib/rest/http-server/http-handler.ts->lib/exceptions/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/http-handler.ts->lib/rest/foundation/index.ts + + + + + +lib/rest/http-server/http-handler.ts->lib/rest/http-server/contexts/execution-context.ts + + + + + + + +no-circular + + + +lib/rest/http-server/http-handler.ts->lib/rest/http-server/param-decorators.ts + + + + + + + +no-circular + + + +lib/rest/http-server/http-handler.ts->lib/rest/http-server/response.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts->lib/interfaces/index.ts + + + + + +lib/rest/http-server/route-explorer.ts->path + + + + + +lib/rest/http-server/route-explorer.ts->node_modules/@nestjs/core + + + + + +lib/rest/http-server/route-explorer.ts->lib/exceptions/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/constants.ts + + + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/request/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts->lib/rest/foundation/index.ts + + + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/contexts/execution-context.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/contexts/http-execution-context.ts + + + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/param-decorators.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/response.ts + + + + + + + +no-circular + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/interfaces.ts + + + + + +lib/rest/http-server/route-explorer.ts->lib/rest/http-server/http-handler.ts + + + + + + + +no-circular + + + +lib/rest/http-server/server.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/http-server/server.ts->lib/rest/http-server/request/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/server.ts->lib/rest/foundation/index.ts + + + + + +lib/rest/http-server/server.ts->lib/rest/http-server/interfaces.ts + + + + + +lib/rest/http-server/server.ts->lib/rest/http-server/request/middleware.ts + + + + + + + +no-circular + + + +stream + + +stream + + + + + +lib/rest/http-server/streamable-file.ts->stream + + + + + +util + + +util + + + + + +lib/rest/http-server/streamable-file.ts->util + + + + + +lib/rest/http-server/request/extension.ts->lib/interfaces/index.ts + + + + + +lib/rest/http-server/request/extension.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/extension.ts->lib/validator/index.ts + + + + + + + +no-circular + + + +lib/storage/file-handlers/uploaded-file.ts + + +uploaded-file.ts + + + + + +lib/rest/http-server/request/extension.ts->lib/storage/file-handlers/uploaded-file.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/extension.ts->lib/utils/extension-to-mime.ts + + + + + +lib/rest/http-server/request/extension.ts->lib/rest/http-server/request/interfaces.ts + + + + + + + +no-circular + + + +lib/storage/file-handlers/uploaded-file.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/storage/file-handlers/uploaded-file.ts->node_modules/fs-extra + + + + + +lib/rest/http-server/request/interfaces.ts->lib/interfaces/index.ts + + + + + +lib/rest/http-server/request/interfaces.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/http-server/request/interfaces.ts->lib/storage/file-handlers/uploaded-file.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/middleware.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/middleware.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/http-server/request/middleware.ts->lib/rest/http-server/request/extension.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/middleware.ts->lib/rest/http-server/request/multipart-handler.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/multipart-handler.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/rest/http-server/request/multipart-handler.ts->path + + + + + +lib/rest/http-server/request/multipart-handler.ts->node_modules/hyper-express + + + + + +no-non-package-json + + + +lib/rest/http-server/request/multipart-handler.ts->node_modules/ulid + + + + + +lib/rest/http-server/request/multipart-handler.ts->lib/storage/file-handlers/uploaded-file.ts + + + + + + + +no-circular + + + +os + + +os + + + + + +lib/rest/http-server/request/multipart-handler.ts->os + + + + + +lib/rest/interceptors/timeout.ts->node_modules/@nestjs/common + + + + + +node_modules/rxjs + + + + + +rxjs + + + + + +lib/rest/interceptors/timeout.ts->node_modules/rxjs + + + + + +lib/rest/middlewares/helmet.ts->lib/config/index.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/helmet.ts->lib/foundation/index.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/helmet.ts->lib/rest/http-server/index.ts + + + + + + + +no-circular + + + +lib/rest/middlewares/helmet.ts->lib/rest/foundation/index.ts + + + + + + + +no-circular + + + +node_modules/helmet + + + + + +helmet + + + + + +lib/rest/middlewares/helmet.ts->node_modules/helmet + + + + + +lib/storage/service.ts->lib/config/index.ts + + + + + +lib/storage/service.ts->node_modules/@nestjs/common + + + + + +lib/storage/driver-mapper.ts + + +driver-mapper.ts + + + + + +lib/storage/service.ts->lib/storage/driver-mapper.ts + + + + + + + +no-circular + + + +lib/storage/service.ts->lib/storage/interfaces/index.ts + + + + + +lib/storage/exceptions/diskNotFound.ts + + +diskNotFound.ts + + + + + +lib/storage/service.ts->lib/storage/exceptions/diskNotFound.ts + + + + + +lib/storage/data/mime-db.ts + + +mime-db.ts + + + + + +lib/storage/driver-mapper.ts->lib/interfaces/index.ts + + + + + +lib/storage/drivers/local.ts + + +local.ts + + + + + +lib/storage/driver-mapper.ts->lib/storage/drivers/local.ts + + + + + + + +no-circular + + + +lib/storage/drivers/s3Storage.ts + + +s3Storage.ts + + + + + +lib/storage/driver-mapper.ts->lib/storage/drivers/s3Storage.ts + + + + + + + +no-circular + + + +lib/storage/driver-mapper.ts->lib/storage/interfaces/index.ts + + + + + +lib/storage/drivers/local.ts->fs + + + + + +lib/storage/drivers/local.ts->path + + + + + +lib/storage/drivers/local.ts->node_modules/fs-extra + + + + + +lib/storage/drivers/local.ts->lib/storage/service.ts + + + + + + + +no-circular + + + +lib/storage/drivers/local.ts->lib/storage/interfaces/index.ts + + + + + +lib/storage/exceptions/cannotParseAsJson.ts + + +cannotParseAsJson.ts + + + + + +lib/storage/drivers/local.ts->lib/storage/exceptions/cannotParseAsJson.ts + + + + + +lib/storage/exceptions/cannotPerformFileOp.ts + + +cannotPerformFileOp.ts + + + + + +lib/storage/drivers/local.ts->lib/storage/exceptions/cannotPerformFileOp.ts + + + + + +lib/storage/helpers/index.ts + + +index.ts + + + + + +lib/storage/drivers/local.ts->lib/storage/helpers/index.ts + + + + + +lib/storage/drivers/s3Storage.ts->fs + + + + + +lib/storage/drivers/s3Storage.ts->lib/interfaces/index.ts + + + + + +lib/storage/drivers/s3Storage.ts->lib/utils/index.ts + + + + + +lib/storage/drivers/s3Storage.ts->lib/utils/string.ts + + + + + +lib/storage/drivers/s3Storage.ts->lib/storage/service.ts + + + + + + + +no-circular + + + +lib/storage/drivers/s3Storage.ts->lib/storage/interfaces/index.ts + + + + + +lib/storage/drivers/s3Storage.ts->lib/storage/exceptions/cannotParseAsJson.ts + + + + + +lib/storage/drivers/s3Storage.ts->lib/storage/exceptions/cannotPerformFileOp.ts + + + + + +lib/storage/drivers/s3Storage.ts->lib/storage/helpers/index.ts + + + + + +lib/storage/interfaces/fileOptions.ts + + +fileOptions.ts + + + + + +lib/storage/interfaces/index.ts->lib/storage/interfaces/fileOptions.ts + + + + + +lib/storage/interfaces/storageDriver.ts + + +storageDriver.ts + + + + + +lib/storage/interfaces/index.ts->lib/storage/interfaces/storageDriver.ts + + + + + + + +no-circular + + + +lib/storage/interfaces/storageOptions.ts + + +storageOptions.ts + + + + + +lib/storage/interfaces/index.ts->lib/storage/interfaces/storageOptions.ts + + + + + +lib/storage/drivers/azureBlob.ts + + +azureBlob.ts + + + + + +lib/storage/drivers/index.ts->lib/storage/drivers/local.ts + + + + + +lib/storage/drivers/index.ts->lib/storage/drivers/s3Storage.ts + + + + + +lib/storage/helpers/extensions.ts + + +extensions.ts + + + + + +lib/storage/helpers/index.ts->lib/storage/helpers/extensions.ts + + + + + +lib/storage/helpers/extensions.ts->lib/storage/data/mime-db.ts + + + + + +lib/storage/storage.ts->node_modules/@nestjs/common + + + + + +lib/storage/storage.ts->lib/storage/service.ts + + + + + +lib/storage/storage.ts->lib/storage/interfaces/index.ts + + + + + +node_modules/axios + + + + + +axios + + + + + +lib/storage/storage.ts->node_modules/axios + + + + + +lib/storage/interfaces/storageDriver.ts->lib/storage/interfaces/index.ts + + + + + + + +no-circular + + + +lib/transformers/interfaces.ts->lib/rest/http-server/request/index.ts + + + + + +lib/transformers/transformable.ts->lib/utils/index.ts + + + + + +lib/transformers/transformable.ts->lib/transformers/interfaces.ts + + + + + +lib/transformers/transformable.ts->lib/transformers/transformer.ts + + + + + +lib/transformers/transformer.ts->lib/interfaces/index.ts + + + + + +lib/transformers/transformer.ts->lib/transformers/interfaces.ts + + + + + +lib/transformers/transformer.ts->lib/utils/expParser.ts + + + + + +lib/utils/object.ts->lib/utils/array.ts + + + + + + + +no-circular + + + +lib/utils/object.ts->lib/exceptions/index.ts + + + + + + + +no-circular + + + +lib/utils/context.ts->lib/rest/index.ts + + + + + +lib/utils/path.ts->path + + + + + +lib/utils/path.ts->node_modules/fs-extra + + + + + +lib/utils/pluralise.ts->lib/utils/string.ts + + + + + + + +no-circular + + + +lib/validator/decorators/exists.ts + + +exists.ts + + + + + +lib/validator/decorators/exists.ts->node_modules/@nestjs/common + + + + + +lib/validator/decorators/exists.ts->lib/utils/helpers.ts + + + + + +lib/validator/decorators/exists.ts->lib/database/index.ts + + + + + +lib/validator/decorators/exists.ts->node_modules/class-validator + + + + + +lib/validator/decorators/isEqualToProp.ts + + +isEqualToProp.ts + + + + + +lib/validator/decorators/index.ts->lib/validator/decorators/isEqualToProp.ts + + + + + +lib/validator/decorators/isValueFromConfig.ts + + +isValueFromConfig.ts + + + + + +lib/validator/decorators/index.ts->lib/validator/decorators/isValueFromConfig.ts + + + + + + + +no-circular + + + +lib/validator/decorators/valueIn.ts + + +valueIn.ts + + + + + +lib/validator/decorators/index.ts->lib/validator/decorators/valueIn.ts + + + + + +lib/validator/decorators/isEqualToProp.ts->node_modules/class-validator + + + + + +lib/validator/decorators/isValueFromConfig.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/validator/decorators/isValueFromConfig.ts->node_modules/@nestjs/common + + + + + +lib/validator/decorators/isValueFromConfig.ts->lib/utils/array.ts + + + + + + + +no-circular + + + +lib/validator/decorators/isValueFromConfig.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/validator/decorators/isValueFromConfig.ts->lib/utils/helpers.ts + + + + + + + +no-circular + + + +lib/validator/decorators/isValueFromConfig.ts->node_modules/class-validator + + + + + +lib/validator/decorators/valueIn.ts->node_modules/class-validator + + + + + +lib/validator/decorators/isUnique.ts + + +isUnique.ts + + + + + +lib/validator/decorators/isUnique.ts->node_modules/@nestjs/common + + + + + +lib/validator/decorators/isUnique.ts->lib/utils/helpers.ts + + + + + +lib/validator/decorators/isUnique.ts->lib/database/index.ts + + + + + +lib/validator/decorators/isUnique.ts->node_modules/class-validator + + + + + +lib/validator/validationGuard.ts->node_modules/@nestjs/common + + + + + +lib/validator/validationGuard.ts->lib/rest/index.ts + + + + + +lib/validator/validationGuard.ts->node_modules/@nestjs/core + + + + + +lib/validator/validator.ts->lib/utils/index.ts + + + + + + + +no-circular + + + +lib/validator/validator.ts->node_modules/@nestjs/common + + + + + +lib/validator/validator.ts->lib/config/service.ts + + + + + + + +no-circular + + + +lib/validator/validator.ts->lib/exceptions/validation-failed.ts + + + + + + + +no-circular + + + +lib/validator/validator.ts->node_modules/class-validator + + + + + +lib/validator/validator.ts->node_modules/class-transformer + + + + + +node_modules/@react-email/code-block + + + + + +code-block + + + + + +node_modules/react + + + + + +react + + + + + +resources/mail/components/CLink.tsx + + +CLink.tsx + + + + + +resources/mail/components/CLink.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/CLink.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/interface.tsx + + +interface.tsx + + + + + +resources/mail/components/CLink.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/CText.tsx + + +CText.tsx + + + + + +resources/mail/components/CText.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/CText.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/CText.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/Footer.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/Footer.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Footer.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/Greetings.tsx + + +Greetings.tsx + + + + + +resources/mail/components/Greetings.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/Greetings.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Greetings.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/Header.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/Header.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Header.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/HrLine.tsx + + +HrLine.tsx + + + + + +resources/mail/components/HrLine.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/HrLine.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Image.tsx + + +Image.tsx + + + + + +resources/mail/components/Image.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/Image.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/InjectMarkdown.tsx + + +InjectMarkdown.tsx + + + + + +resources/mail/components/InjectMarkdown.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/InjectMarkdown.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/InjectMarkdown.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/InjectReactComponent.tsx + + +InjectReactComponent.tsx + + + + + +resources/mail/components/InjectReactComponent.tsx->node_modules/@react-email/code-block + + + + + +no-non-package-json + + + +resources/mail/components/InjectReactComponent.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/InjectReactComponent.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Regards.tsx + + +Regards.tsx + + + + + +resources/mail/components/Regards.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/Regards.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Table.tsx + + +Table.tsx + + + + + +resources/mail/components/Table.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/Table.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/button.tsx + + +button.tsx + + + + + +resources/mail/components/button.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/button.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/button.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/codeBlock.tsx + + +codeBlock.tsx + + + + + +resources/mail/components/codeBlock.tsx->node_modules/@react-email/components + + + + + +resources/mail/components/codeBlock.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/codeBlock.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/components/codeInline.tsx + + +codeInline.tsx + + + + + +resources/mail/components/codeInline.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/components/codeInline.tsx->resources/mail/components/interface.tsx + + + + + +resources/mail/emails/components.tsx->node_modules/react + + + + + +no-non-package-json + + + +resources/mail/emails/components.tsx->resources/mail/components/CLink.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/CText.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/Greetings.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/HrLine.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/Image.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/InjectMarkdown.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/InjectReactComponent.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/Regards.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/Table.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/button.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/codeBlock.tsx + + + + + +resources/mail/emails/components.tsx->resources/mail/components/codeInline.tsx + + + + + diff --git a/packages/core/lib/config/command.ts b/packages/core/lib/config/command.ts index aa21c19..38bd74f 100644 --- a/packages/core/lib/config/command.ts +++ b/packages/core/lib/config/command.ts @@ -1,10 +1,11 @@ -import { Command, ConsoleIO } from '../console'; import { ConfigMap } from './options'; import { CONFIG_FACTORY } from './constant'; import pc from 'picocolors'; import archy from 'archy'; -import { Inject } from '../foundation'; import { jsonToArchy } from '../utils/console-helpers'; +import { Command } from '../console/decorators'; +import { ConsoleIO } from '../console/consoleIO'; +import { Inject } from '../foundation/decorators'; @Command('config:view {--ns : Namespace of a particular config}', { desc: 'Command to view config for a given namespace', diff --git a/packages/core/lib/config/service.ts b/packages/core/lib/config/service.ts index a2ffb79..d17028e 100644 --- a/packages/core/lib/config/service.ts +++ b/packages/core/lib/config/service.ts @@ -1,6 +1,6 @@ -import { Inject, Injectable } from '../foundation'; +import { Inject, Injectable } from '../foundation/decorators'; import { DotNotation, GetNestedPropertyType } from '../type-helpers'; -import { Obj } from '../utils'; +import { Obj } from '../utils/object'; import { CONFIG_FACTORY } from './constant'; import { ConfigMap, NamespacedConfigMapValues } from './options'; diff --git a/packages/core/lib/console/argumentParser.ts b/packages/core/lib/console/argumentParser.ts index 20b2869..0262306 100644 --- a/packages/core/lib/console/argumentParser.ts +++ b/packages/core/lib/console/argumentParser.ts @@ -1,5 +1,5 @@ -import { Str } from '../utils'; import { InternalLogger } from '../utils/logger'; +import { Str } from '../utils/string'; import { ArgumentOptionObject, ArgumentParserOutput } from './interfaces'; export class ArgumentParser { diff --git a/packages/core/lib/console/interfaces/index.ts b/packages/core/lib/console/interfaces.ts similarity index 93% rename from packages/core/lib/console/interfaces/index.ts rename to packages/core/lib/console/interfaces.ts index cb76c43..2a575b4 100644 --- a/packages/core/lib/console/interfaces/index.ts +++ b/packages/core/lib/console/interfaces.ts @@ -1,4 +1,4 @@ -import { ConsoleIO } from '../consoleIO'; +import { ConsoleIO } from './consoleIO'; export interface CommandMetaOptions { desc?: string; diff --git a/packages/core/lib/exceptions/base-exception-handler.ts b/packages/core/lib/exceptions/base-exception-handler.ts index 8c9b2cc..822699f 100644 --- a/packages/core/lib/exceptions/base-exception-handler.ts +++ b/packages/core/lib/exceptions/base-exception-handler.ts @@ -4,8 +4,8 @@ import { Package } from '../utils'; import { Type } from '../interfaces'; import { HttpException } from './http-exception'; import { ValidationFailed } from './validation-failed'; -import { ExecutionContext } from '../rest/http-server'; import { HttpStatus } from '../rest/http-server/status-codes'; +import { ExecutionContext } from '../rest/http-server/contexts/execution-context'; export abstract class IntentExceptionFilter { doNotReport(): Array> { diff --git a/packages/core/lib/exceptions/forbidden-exception.ts b/packages/core/lib/exceptions/forbidden-exception.ts index fca3733..cc21133 100644 --- a/packages/core/lib/exceptions/forbidden-exception.ts +++ b/packages/core/lib/exceptions/forbidden-exception.ts @@ -1,4 +1,4 @@ -import { HttpStatus } from '../rest'; +import { HttpStatus } from '../rest/http-server/status-codes'; import { HttpException } from './http-exception'; export class ForbiddenException extends HttpException { diff --git a/packages/core/lib/exceptions/genericException.ts b/packages/core/lib/exceptions/genericException.ts index c1ef0d9..f32d580 100644 --- a/packages/core/lib/exceptions/genericException.ts +++ b/packages/core/lib/exceptions/genericException.ts @@ -1,8 +1,9 @@ import { HttpException } from './http-exception'; +import { HttpStatus } from '../rest/http-server/status-codes'; export class GenericException extends HttpException { - constructor(message?: string) { + constructor(message?: string, status: HttpStatus = HttpStatus.FORBIDDEN) { message = message ? message : 'Something went wrong!'; - super(message, 403); + super(message, status); } } diff --git a/packages/core/lib/exceptions/http-exception.ts b/packages/core/lib/exceptions/http-exception.ts index d17fd33..9a1919a 100644 --- a/packages/core/lib/exceptions/http-exception.ts +++ b/packages/core/lib/exceptions/http-exception.ts @@ -1,5 +1,6 @@ -import { HttpStatus } from '../rest'; -import { Obj, Str } from '../utils'; +import { HttpStatus } from '../rest/http-server/status-codes'; +import { Obj } from '../utils/object'; +import { Str } from '../utils/string'; export interface HttpExceptionOptions { cause?: string; diff --git a/packages/core/lib/exceptions/invalid-credentials.ts b/packages/core/lib/exceptions/invalid-credentials.ts index 5e1529d..6e4348f 100644 --- a/packages/core/lib/exceptions/invalid-credentials.ts +++ b/packages/core/lib/exceptions/invalid-credentials.ts @@ -1,4 +1,4 @@ -import { HttpStatus } from '../rest'; +import { HttpStatus } from '../rest/http-server/status-codes'; import { HttpException } from './http-exception'; export class InvalidCredentials extends HttpException { diff --git a/packages/core/lib/exceptions/unauthorized.ts b/packages/core/lib/exceptions/unauthorized.ts index 9d9036c..4c581a3 100644 --- a/packages/core/lib/exceptions/unauthorized.ts +++ b/packages/core/lib/exceptions/unauthorized.ts @@ -1,4 +1,4 @@ -import { HttpStatus } from '../rest'; +import { HttpStatus } from '../rest/http-server/status-codes'; import { HttpException } from './http-exception'; export class Unauthorized extends HttpException { diff --git a/packages/core/lib/exceptions/validation-failed.ts b/packages/core/lib/exceptions/validation-failed.ts index 39d37b0..c5809e6 100644 --- a/packages/core/lib/exceptions/validation-failed.ts +++ b/packages/core/lib/exceptions/validation-failed.ts @@ -1,4 +1,4 @@ -import { HttpStatus } from '../rest'; +import { HttpStatus } from '../rest/http-server/status-codes'; import { HttpException } from './http-exception'; export class ValidationFailed extends HttpException { diff --git a/packages/core/lib/explorer.ts b/packages/core/lib/explorer.ts index a77438d..416a219 100644 --- a/packages/core/lib/explorer.ts +++ b/packages/core/lib/explorer.ts @@ -3,10 +3,10 @@ import { CommandMeta, CommandMetaOptions } from './console'; import { ConsoleConstants } from './console/constants'; import { EventMetadata } from './events'; import { IntentEventConstants } from './events/constants'; -import { Injectable } from './foundation'; import { GenericFunction } from './interfaces'; import { JOB_NAME, JOB_OPTIONS } from './queue/constants'; import { QueueMetadata } from './queue/metadata'; +import { Injectable } from './foundation'; @Injectable() export class IntentExplorer { diff --git a/packages/core/lib/foundation/app-container.ts b/packages/core/lib/foundation/app-container.ts index a8f78c3..02774ef 100644 --- a/packages/core/lib/foundation/app-container.ts +++ b/packages/core/lib/foundation/app-container.ts @@ -1,9 +1,5 @@ import { Provider } from '@nestjs/common'; -import { - IntentApplication, - IntentApplicationContext, - Type, -} from '../interfaces'; +import { IntentApplicationContext, Type } from '../interfaces'; import { ImportType, ServiceProvider } from './service-provider'; export abstract class IntentAppContainer { @@ -35,7 +31,7 @@ export abstract class IntentAppContainer { return providers; } - async boot(app: IntentApplication | IntentApplicationContext): Promise { + async boot(app: IntentApplicationContext): Promise { for (const serviceProvider of IntentAppContainer.serviceProviders) { serviceProvider.boot(app); } diff --git a/packages/core/lib/foundation/decorators.ts b/packages/core/lib/foundation/decorators.ts new file mode 100644 index 0000000..1d14632 --- /dev/null +++ b/packages/core/lib/foundation/decorators.ts @@ -0,0 +1 @@ +export { Injectable, Inject, Optional } from '@nestjs/common'; diff --git a/packages/core/lib/foundation/index.ts b/packages/core/lib/foundation/index.ts index c24c610..3a7d772 100644 --- a/packages/core/lib/foundation/index.ts +++ b/packages/core/lib/foundation/index.ts @@ -1,5 +1,5 @@ -export { Injectable, Inject, Optional } from '@nestjs/common'; export * from './module-builder'; export * from './service-provider'; export * from './app-container'; export * from './container-factory'; +export * from './decorators'; diff --git a/packages/core/lib/foundation/module-builder.ts b/packages/core/lib/foundation/module-builder.ts index 3cfda85..0597aca 100644 --- a/packages/core/lib/foundation/module-builder.ts +++ b/packages/core/lib/foundation/module-builder.ts @@ -1,14 +1,15 @@ import { Module } from '@nestjs/common'; -import { Kernel } from '../rest'; +import { Kernel } from '../rest/foundation/kernel'; import { IntentAppContainer } from './app-container'; export class ModuleBuilder { static build(container: IntentAppContainer, kernel?: Kernel) { const providers = container.scanProviders(); + const controllers = kernel?.controllers() || []; @Module({ imports: container.scanImports(), - providers: [...providers], + providers: [...providers, ...controllers], }) class AppModule {} diff --git a/packages/core/lib/foundation/service-provider.ts b/packages/core/lib/foundation/service-provider.ts index 72256d5..a471291 100644 --- a/packages/core/lib/foundation/service-provider.ts +++ b/packages/core/lib/foundation/service-provider.ts @@ -5,11 +5,7 @@ import { OptionalFactoryDependency, Provider, } from '@nestjs/common'; -import { - IntentApplication, - IntentApplicationContext, - Type, -} from '../interfaces'; +import { Type } from '../interfaces'; export type ImportType = | Type @@ -73,5 +69,5 @@ export abstract class ServiceProvider { /** * Use this method to run */ - abstract boot(app: IntentApplication | IntentApplicationContext); + abstract boot(app: any); } diff --git a/packages/core/lib/interfaces/config.ts b/packages/core/lib/interfaces/config.ts index cd50100..4a33ced 100644 --- a/packages/core/lib/interfaces/config.ts +++ b/packages/core/lib/interfaces/config.ts @@ -2,9 +2,8 @@ import { CorsOptions, CorsOptionsDelegate, } from '@nestjs/common/interfaces/external/cors-options.interface'; -import { GenericClass } from '.'; -import { HyperServer } from '../rest'; import { ServerConstructorOptions } from 'hyper-express'; +import { GenericClass } from './utils'; export interface SentryConfig { dsn: string; diff --git a/packages/core/lib/interfaces/exceptions.ts b/packages/core/lib/interfaces/exceptions.ts deleted file mode 100644 index d64206a..0000000 --- a/packages/core/lib/interfaces/exceptions.ts +++ /dev/null @@ -1 +0,0 @@ -export interface HttpExceptionOptions {} diff --git a/packages/core/lib/interfaces/index.ts b/packages/core/lib/interfaces/index.ts index 0e0891b..747e0e8 100644 --- a/packages/core/lib/interfaces/index.ts +++ b/packages/core/lib/interfaces/index.ts @@ -1,16 +1,3 @@ -import { NestExpressApplication } from '@nestjs/platform-express'; -import { INestApplicationContext } from '@nestjs/common'; - -export type GenericFunction = (...args: any[]) => any; -export type GenericClass = Record; - -export * from './transformer'; export * from './config'; - -// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -export interface Type extends Function { - new (...args: any[]): T; -} - -export type IntentApplication = NestExpressApplication; -export type IntentApplicationContext = INestApplicationContext; +export * from './utils'; +export * from './transformer'; diff --git a/packages/core/lib/interfaces/utils.ts b/packages/core/lib/interfaces/utils.ts new file mode 100644 index 0000000..ddc6c7b --- /dev/null +++ b/packages/core/lib/interfaces/utils.ts @@ -0,0 +1,11 @@ +import { INestApplicationContext } from '@nestjs/common'; + +export type GenericFunction = (...args: any[]) => any; +export type GenericClass = Record; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export interface Type extends Function { + new (...args: any[]): T; +} + +export type IntentApplicationContext = INestApplicationContext; diff --git a/packages/core/lib/reflections/reflector.ts b/packages/core/lib/reflections/reflector.ts index 1073cbd..20dc022 100644 --- a/packages/core/lib/reflections/reflector.ts +++ b/packages/core/lib/reflections/reflector.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import 'reflect-metadata'; import { ulid } from 'ulid'; -import { Obj } from '../utils'; +import { Obj } from '../utils/object'; /** * Reflector is a class to easily fetch metadata from a class and request handler method diff --git a/packages/core/lib/rest/foundation/controller-scanner.ts b/packages/core/lib/rest/foundation/controller-scanner.ts index a25fd76..6dc0457 100644 --- a/packages/core/lib/rest/foundation/controller-scanner.ts +++ b/packages/core/lib/rest/foundation/controller-scanner.ts @@ -1,11 +1,11 @@ import { join } from 'path'; import { Type } from '../../interfaces'; -import { HttpRoute } from '../http-server'; import { CONTROLLER_KEY, METHOD_KEY, METHOD_PATH, } from '../http-server/constants'; +import { HttpRoute } from '../http-server/interfaces'; export class ControllerScanner { handle(cls: Type): HttpRoute[] { diff --git a/packages/core/lib/rest/foundation/guards/base-guard.ts b/packages/core/lib/rest/foundation/guards/base-guard.ts index bcb75f4..c7fdb80 100644 --- a/packages/core/lib/rest/foundation/guards/base-guard.ts +++ b/packages/core/lib/rest/foundation/guards/base-guard.ts @@ -1,7 +1,8 @@ import { Reflector } from '../../../reflections'; import { ForbiddenException } from '../../../exceptions/forbidden-exception'; -import { ExecutionContext, Response } from '../../http-server'; -import { Request } from '../../http-server/request'; +import { Request } from '../../http-server/request/interfaces'; +import { ExecutionContext } from '../../http-server/contexts/execution-context'; +import { Response } from '../../http-server/response'; export abstract class IntentGuard { async handle(context: ExecutionContext): Promise { diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index 7154a8e..3d40b81 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -3,5 +3,4 @@ export * from './kernel'; export * from './middlewares/middleware'; export * from './middlewares/configurator'; export * from './server'; -export * from './request-mixin'; export { MiddlewareNext } from 'hyper-express'; diff --git a/packages/core/lib/rest/foundation/kernel.ts b/packages/core/lib/rest/foundation/kernel.ts index c672732..15e7c05 100644 --- a/packages/core/lib/rest/foundation/kernel.ts +++ b/packages/core/lib/rest/foundation/kernel.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { IntentApplication, Type } from '../../interfaces'; +import { Type } from '../../interfaces'; import { IntentGuard } from './guards/base-guard'; import { MiddlewareConfigurator } from './middlewares/configurator'; import { IntentMiddleware } from './middlewares/middleware'; @@ -19,5 +19,5 @@ export abstract class Kernel { return []; } - public abstract boot(app: IntentApplication): Promise; + public abstract boot(app: any): Promise; } diff --git a/packages/core/lib/rest/foundation/middlewares/configurator.ts b/packages/core/lib/rest/foundation/middlewares/configurator.ts index 79c1363..bba6eb8 100644 --- a/packages/core/lib/rest/foundation/middlewares/configurator.ts +++ b/packages/core/lib/rest/foundation/middlewares/configurator.ts @@ -1,5 +1,5 @@ import { Type } from '../../../interfaces'; -import { HttpMethods } from '../../http-server'; +import { HttpMethods } from '../../http-server/interfaces'; import { IntentMiddleware } from './middleware'; type MiddlewareRuleApplicationInfo = diff --git a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts index d492ef7..c074463 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts @@ -1,16 +1,13 @@ import { ModuleRef } from '@nestjs/core'; -import { Type } from '../../../interfaces'; import { MiddlewareConfigurator } from './configurator'; import { IntentMiddleware } from './middleware'; -import { CONTROLLER_KEY } from '../../http-server/constants'; import { ControllerScanner } from '../controller-scanner'; +import { Type } from '../../../interfaces/utils'; export class MiddlewareComposer { private middlewareRoute = new Map(); private excludedMiddlewareRoutes = new Map(); - private middlewareMap: Record = {}; - constructor( private moduleRef: ModuleRef, private middlewareConfigurator: MiddlewareConfigurator, @@ -20,8 +17,9 @@ export class MiddlewareComposer { async globalMiddlewares(): Promise { const globalMiddlewares = []; for (const middleware of this.middlewares) { - const middlewareInstance = await this.moduleRef.create(middleware); - globalMiddlewares.push(middlewareInstance); + console.log(middleware); + globalMiddlewares.push(await this.moduleRef.create(middleware)); + console.log(globalMiddlewares); } return globalMiddlewares; } diff --git a/packages/core/lib/rest/foundation/middlewares/middleware.ts b/packages/core/lib/rest/foundation/middlewares/middleware.ts index bb91dcc..2a846f6 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware.ts @@ -1,5 +1,4 @@ -import { Request, Response } from '../../http-server'; -import { MiddlewareNext } from 'hyper-express'; +import { MiddlewareNext, Request, Response } from 'hyper-express'; export abstract class IntentMiddleware { async handle( diff --git a/packages/core/lib/rest/foundation/request-mixin.ts b/packages/core/lib/rest/foundation/request-mixin.ts deleted file mode 100644 index 06d0319..0000000 --- a/packages/core/lib/rest/foundation/request-mixin.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Request as ERequest } from 'express'; -import { Validator } from '../../validator'; -import { Type } from '../../interfaces'; -import { isEmpty } from '../../utils'; - -export const RequestMixin = (request: ERequest) => ({ - $dto: null, - $user: null, - logger() {}, - - setDto(dto: any): void { - this.$dto = dto; - }, - - dto(): any { - return this.$dto; - }, - - all(): Record { - return { - ...(request.query || {}), - ...(request.params || {}), - ...(request.body || {}), - }; - }, - - input(name: string, defaultValue?: T): T { - const payload = this.all(); - return name in payload ? payload[name] : defaultValue; - }, - - string(name: string): string { - const value = this.input(name); - return value && value.toString(); - }, - - number(name: string): number { - const value = this.input(name); - return +value; - }, - - boolean(name: string): boolean { - const payload = this.all(); - const val = payload[name] as string; - return [true, 'yes', 'on', '1', 1, 'true'].includes(val.toLowerCase()); - }, - - hasHeader(name: string): boolean { - return name in request.headers; - }, - - bearerToken(): string { - const authHeader = request.headers['authorization']; - const asArray = authHeader?.split(' '); - if (!isEmpty(asArray)) return asArray[1]; - return undefined; - }, - - httpHost(): string { - return request.protocol; - }, - - isHttp(): boolean { - return this.httpHost() === 'http'; - }, - - isHttps(): boolean { - return this.httpHost() === 'https'; - }, - - fullUrl(): string { - return request.url; - }, - - isMethod(method: string): boolean { - return request.method.toLowerCase() === method.toLowerCase(); - }, - - getAcceptableContentTypes(): string { - return request.headers['accept']; - }, - - expectsJson(): boolean { - return request.accepts('json') === 'json'; - }, - - async validate(schema: Type): Promise { - const payload = this.all(); - const validator = Validator.compareWith(schema); - const dto = await validator - .addMeta({ ...payload, _headers: { ...request.headers } }) - .validate({ ...payload }); - this.setDto(dto); - return true; - }, - - setUser(user: any): void { - this.$user = user; - }, - - user(): T { - return this.$user as T; - }, - - only(...keys: string[]): Record { - console.log(keys); - return {}; - }, - - except(...keys: string[]): Record { - console.log(keys); - return {}; - }, - - isPath(pathPattern: string): boolean { - console.log(request, pathPattern); - return false; - }, - - has(...keys: string[]): boolean { - const payload = this.all(); - for (const key of keys) { - if (!(key in payload)) return false; - } - - return true; - }, - - hasAny(...keys: string[]): boolean { - const payload = this.all(); - for (const key of keys) { - if (key in payload) return true; - } - - return false; - }, - - missing(...keys: string[]): boolean { - const payload = this.all(); - for (const key of keys) { - if (key in payload) return false; - } - - return true; - }, - - hasHeaders(...keys: string[]): boolean { - for (const key of keys) { - if (!(key in request.headers)) return false; - } - - return true; - }, - - hasIncludes(): boolean { - const includes = this.includes(); - return includes === ''; - }, - - includes(): string { - return this.string('include'); - }, -}); diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 6d3e847..be10403 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -15,16 +15,13 @@ import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; import { Response as HyperResponse, Server } from 'hyper-express'; -import { - ExecutionContext, - HttpExecutionContext, - HyperServer, - Request, - Response, - RouteExplorer, -} from '../http-server'; import { MiddlewareConfigurator } from './middlewares/configurator'; import { MiddlewareComposer } from './middlewares/middleware-composer'; +import { HyperServer } from '../http-server/server'; +import { HttpExecutionContext } from '../http-server/contexts/http-execution-context'; +import { ExecutionContext } from '../http-server/contexts/execution-context'; +import { Response } from '../http-server/response'; +import { RouteExplorer } from '../http-server/route-explorer'; const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; @@ -55,17 +52,22 @@ export class IntentHttpServer { async start() { const module = ModuleBuilder.build(this.container, this.kernel); - const app = await NestFactory.createApplicationContext(module); + const app = await NestFactory.createApplicationContext(module, { + logger: ['error', 'warn'], + }); const globalGuards = this.kernel.guards(); - const ds = app.get(DiscoveryService, { strict: false }); + const appModule = app.select(module); + const ds = app.get(DiscoveryService); const ms = app.get(MetadataScanner, { strict: false }); - const mr = app.get(ModuleRef, { strict: false }); + const mr = appModule.get(ModuleRef, { strict: true }); + const errorHandler = await mr.create(this.errorHandler); - const config = app.get(ConfigService, { strict: false }); - useContainer(app.select(module), { fallbackOnErrors: true }); + const config = appModule.get(ConfigService); + + useContainer(appModule, { fallbackOnErrors: true }); const middlewareConfigurator = new MiddlewareConfigurator(); this.kernel.routeMiddlewares(middlewareConfigurator); diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index f6b8c36..7d47245 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -1,6 +1,6 @@ import { Response } from '../response'; import { RouteArgType, RouteParamtypes } from '../param-decorators'; -import { Request } from '../request'; +import { Request } from '../request/interfaces'; export class HttpExecutionContext { constructor( diff --git a/packages/core/lib/rest/http-server/decorators.ts b/packages/core/lib/rest/http-server/decorators.ts index c5106db..4a79e7d 100644 --- a/packages/core/lib/rest/http-server/decorators.ts +++ b/packages/core/lib/rest/http-server/decorators.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { applyDecorators } from '../../reflections/apply-decorators'; import { Type } from '../../interfaces'; -import { IntentGuard } from '../foundation'; import { CONTROLLER_KEY, CONTROLLER_OPTIONS, @@ -10,6 +9,7 @@ import { METHOD_PATH, } from './constants'; import { HttpMethods } from './interfaces'; +import { IntentGuard } from '../foundation/guards/base-guard'; export type ControllerOptions = { host?: string; diff --git a/packages/core/lib/rest/http-server/http-handler.ts b/packages/core/lib/rest/http-server/http-handler.ts index 98b48d6..0d1ab42 100644 --- a/packages/core/lib/rest/http-server/http-handler.ts +++ b/packages/core/lib/rest/http-server/http-handler.ts @@ -1,12 +1,10 @@ -import { IntentExceptionFilter } from '../../exceptions'; -import { IntentGuard, IntentMiddleware } from '../foundation'; +import { IntentExceptionFilter } from '../../exceptions/base-exception-handler'; +import { IntentGuard } from '../foundation/guards/base-guard'; import { ExecutionContext } from './contexts/execution-context'; -import { RouteArgType, RouteParamtypes } from './param-decorators'; import { Response } from './response'; export class HttpRouteHandler { constructor( - protected readonly middlewares: IntentMiddleware[], protected readonly guards: IntentGuard[], protected readonly handler: Function, protected readonly exceptionFilter: IntentExceptionFilter, diff --git a/packages/core/lib/rest/http-server/request/extension.ts b/packages/core/lib/rest/http-server/request/extension.ts index d1df1fc..e978499 100644 --- a/packages/core/lib/rest/http-server/request/extension.ts +++ b/packages/core/lib/rest/http-server/request/extension.ts @@ -1,9 +1,9 @@ import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; import { isEmpty } from '../../../utils'; import { Type } from '../../../interfaces'; -import { Validator } from '../../../validator'; import { EXTENSTION_TO_MIME } from '../../../utils/extension-to-mime'; import { Request } from './interfaces'; +import { Validator } from '../../../validator/validator'; export const INTENT_REQUEST_EXTENSIONS = { $dto: null, diff --git a/packages/core/lib/rest/http-server/request/multipart-handler.ts b/packages/core/lib/rest/http-server/request/multipart-handler.ts index 58242fd..5b986c5 100644 --- a/packages/core/lib/rest/http-server/request/multipart-handler.ts +++ b/packages/core/lib/rest/http-server/request/multipart-handler.ts @@ -2,8 +2,8 @@ import { Request as HyperRequest } from 'hyper-express'; import { tmpdir } from 'os'; import { join } from 'path'; import { ulid } from 'ulid'; -import { Str } from '../../../utils'; import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; +import { Str } from '../../../utils/string'; export const processMultipartData = async ( req: HyperRequest, diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index a9f2cff..f4cde4f 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -6,8 +6,6 @@ import { HttpExecutionContext } from './contexts/http-execution-context'; import { HttpRouteHandler } from './http-handler'; import { Response } from './response'; import { ExecutionContext } from './contexts/execution-context'; -import { IntentExceptionFilter } from '../../exceptions'; -import { IntentGuard, IntentMiddleware } from '../foundation'; import { Type } from '../../interfaces'; import { CONTROLLER_KEY, @@ -17,7 +15,10 @@ import { ROUTE_ARGS, } from './constants'; import { RouteArgType } from './param-decorators'; -import { Request } from './request'; +import { Request } from './request/interfaces'; +import { IntentGuard } from '../foundation/guards/base-guard'; +import { IntentMiddleware } from '../foundation/middlewares/middleware'; +import { IntentExceptionFilter } from '../../exceptions/base-exception-handler'; export class RouteExplorer { guards: Type[] = []; @@ -114,6 +115,9 @@ export class RouteExplorer { if (!controllerKey) return; const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); + + if (!pathMethod) return; + const methodRef = instance[key].bind(instance); const controllerGuards = Reflect.getMetadata( GUARD_KEY, @@ -143,7 +147,6 @@ export class RouteExplorer { ([] as RouteArgType[]); const handler = new HttpRouteHandler( - middlewares, composedGuards, methodRef, errorHandler, diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index 88e8812..a86d323 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -1,8 +1,7 @@ import HyperExpress from 'hyper-express'; import { HttpMethods, HttpRoute } from './interfaces'; -import { IntentMiddleware } from '../foundation'; import { requestMiddleware } from './request/middleware'; -import { Request } from './request'; +import { IntentMiddleware } from '../foundation/middlewares/middleware'; export class HyperServer { protected hyper: HyperExpress.Server; @@ -19,11 +18,11 @@ export class HyperServer { this.hyper = new HyperExpress.Server(config || {}); this.hyper.use(requestMiddleware); - // this.hyper.use(async (hReq, res, next) => { - // for (const middleware of this.globalMiddlewares) { - // await middleware.handle(req, res, next); - // } - // }); + this.hyper.use(async (hReq, res, next) => { + for (const middleware of this.globalMiddlewares) { + await middleware.handle(hReq, res, next); + } + }); for (const route of routes) { const { path, httpHandler } = route; diff --git a/packages/core/lib/rest/middlewares/cors.ts b/packages/core/lib/rest/middlewares/cors.ts index 4a9efb9..a1733eb 100644 --- a/packages/core/lib/rest/middlewares/cors.ts +++ b/packages/core/lib/rest/middlewares/cors.ts @@ -1,8 +1,8 @@ import cors, { CorsOptions } from 'cors'; -import { ConfigService } from '../../config/service'; -import { Injectable } from '../../foundation'; import { IntentMiddleware, MiddlewareNext } from '../foundation'; -import { Request, Response } from '../http-server'; +import { ConfigService } from '../../config'; +import { Injectable } from '@nestjs/common'; +import { Request, Response } from 'hyper-express'; @Injectable() export class CorsMiddleware extends IntentMiddleware { @@ -10,9 +10,14 @@ export class CorsMiddleware extends IntentMiddleware { super(); } - use(req: Request, res: Response, next: MiddlewareNext): void | Promise { - console.log(this.config.get('app.cors')); - cors(this.config.get('app.cors') as CorsOptions); + async use(req: Request, res: Response, next: MiddlewareNext): Promise { + const corsMiddleware = cors(this.config.get('app.cors') as CorsOptions); + await new Promise(resolve => { + corsMiddleware(req, res, () => { + resolve(1); + }); + }); + next(); } } diff --git a/packages/core/lib/rest/middlewares/helmet.ts b/packages/core/lib/rest/middlewares/helmet.ts index dece344..6e15a32 100644 --- a/packages/core/lib/rest/middlewares/helmet.ts +++ b/packages/core/lib/rest/middlewares/helmet.ts @@ -2,7 +2,7 @@ import helmet from 'helmet'; import { Injectable } from '../../foundation'; import { IntentMiddleware, MiddlewareNext } from '../foundation'; import { ConfigService } from '../../config'; -import { Request, Response } from '../http-server'; +import { Request, Response } from 'hyper-express'; @Injectable() export class HelmetMiddleware extends IntentMiddleware { diff --git a/packages/core/lib/serviceProvider.ts b/packages/core/lib/serviceProvider.ts index 3229af6..7f79bce 100644 --- a/packages/core/lib/serviceProvider.ts +++ b/packages/core/lib/serviceProvider.ts @@ -20,7 +20,7 @@ import { StorageService } from './storage/service'; import { BuildProjectCommand } from './dev-server/build'; import { DevServerCommand } from './dev-server/serve'; import { CONFIG_FACTORY, ConfigBuilder, ConfigService } from './config'; -import { ListRouteCommand } from './console/commands/route-list'; +// import { ListRouteCommand } from './console/commands/route-list'; export const IntentProvidersFactory = ( config: any[], diff --git a/packages/core/lib/storage/service.ts b/packages/core/lib/storage/service.ts index 93b36db..b4d0ba3 100644 --- a/packages/core/lib/storage/service.ts +++ b/packages/core/lib/storage/service.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; import { LocalDiskOptions, S3DiskOptions } from './interfaces'; import { StorageDriver } from './interfaces'; import { DiskNotFoundException } from './exceptions/diskNotFound'; import { ConfigService } from '../config'; import { DriverMap } from './driver-mapper'; +import { Injectable } from '../foundation/decorators'; @Injectable() export class StorageService { diff --git a/packages/core/lib/utils/array.ts b/packages/core/lib/utils/array.ts index 605ada9..77c4fac 100644 --- a/packages/core/lib/utils/array.ts +++ b/packages/core/lib/utils/array.ts @@ -1,4 +1,4 @@ -import { InvalidValue } from '../exceptions'; +import { InvalidValue } from '../exceptions/invalid-value'; import { Obj } from './object'; export class Arr { diff --git a/packages/core/lib/utils/context.ts b/packages/core/lib/utils/context.ts deleted file mode 100644 index 906e554..0000000 --- a/packages/core/lib/utils/context.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Request } from '../rest'; - -export class Context { - req: Request; - - setRequest(req: Request): this { - this.req = req; - return this; - } - - getRequest(): Request { - return this.req; - } -} diff --git a/packages/core/lib/utils/index.ts b/packages/core/lib/utils/index.ts index 8539c59..68fd8d1 100644 --- a/packages/core/lib/utils/index.ts +++ b/packages/core/lib/utils/index.ts @@ -1,4 +1,3 @@ -export * from './context'; export * from './expParser'; export * from './packageLoader'; export * from './object'; diff --git a/packages/core/lib/utils/object.ts b/packages/core/lib/utils/object.ts index 0fc25f0..1254ad5 100644 --- a/packages/core/lib/utils/object.ts +++ b/packages/core/lib/utils/object.ts @@ -1,4 +1,4 @@ -import { InvalidValue } from '../exceptions'; +import { InvalidValue } from '../exceptions/invalid-value'; import { Arr } from './array'; export class Obj { diff --git a/packages/core/package.json b/packages/core/package.json index 2c4c611..bc9dfc9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -65,8 +65,8 @@ "typescript": "^5.5.2" }, "dependencies": { - "@nestjs/common": "^10.4.4", - "@nestjs/core": "^10.4.4", + "@nestjs/common": "^10.4.8", + "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", "archy": "^1.0.0", "axios": "^1.7.7", From 8fb2ee2f915b75a4a77e77a17a72a36d2cfd010f Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Wed, 27 Nov 2024 11:08:35 +0530 Subject: [PATCH 12/27] fix(core): middleware bugs --- integrations/sample-app/app/boot/sp/app.ts | 6 +----- integrations/sample-app/app/http/kernel.ts | 4 +--- .../sample-app/app/http/middlewares/global.ts | 3 +-- .../foundation/middlewares/middleware-composer.ts | 3 --- .../lib/rest/foundation/middlewares/middleware.ts | 8 -------- packages/core/lib/rest/foundation/server.ts | 4 +++- .../core/lib/rest/http-server/request/middleware.ts | 2 -- packages/core/lib/rest/http-server/server.ts | 8 +++----- packages/core/lib/rest/middlewares/cors.ts | 11 +++++++---- 9 files changed, 16 insertions(+), 33 deletions(-) diff --git a/integrations/sample-app/app/boot/sp/app.ts b/integrations/sample-app/app/boot/sp/app.ts index ea6d26f..823ae4b 100644 --- a/integrations/sample-app/app/boot/sp/app.ts +++ b/integrations/sample-app/app/boot/sp/app.ts @@ -1,9 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { - IntentApplication, - IntentApplicationContext, - ServiceProvider, -} from '@intentjs/core'; +import { IntentApplicationContext, ServiceProvider } from '@intentjs/core'; import { IntentController } from 'app/http/controllers/icon'; import { QueueJobs } from 'app/jobs/job'; import { UserDbRepository } from 'app/repositories/userDbRepository'; diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index 4690524..f609eed 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -1,8 +1,6 @@ import { CorsMiddleware, - HelmetMiddleware, HttpMethods, - IntentApplication, IntentGuard, IntentMiddleware, Kernel, @@ -32,7 +30,7 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/middlewares */ public middlewares(): Type[] { - return [GlobalMiddleware, CorsMiddleware, GlobalMiddleware]; + return [GlobalMiddleware, CorsMiddleware]; } /** diff --git a/integrations/sample-app/app/http/middlewares/global.ts b/integrations/sample-app/app/http/middlewares/global.ts index 6e7c2f2..89a7dde 100644 --- a/integrations/sample-app/app/http/middlewares/global.ts +++ b/integrations/sample-app/app/http/middlewares/global.ts @@ -7,8 +7,7 @@ export class GlobalMiddleware extends IntentMiddleware { super(); } - use(req: Request, res: Response, next: MiddlewareNext): void | Promise { - console.log('inside global middleware'); + use(req: Request, res: Response, next: MiddlewareNext): void { next(); } } diff --git a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts index c074463..ab9ef75 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware-composer.ts @@ -17,9 +17,7 @@ export class MiddlewareComposer { async globalMiddlewares(): Promise { const globalMiddlewares = []; for (const middleware of this.middlewares) { - console.log(middleware); globalMiddlewares.push(await this.moduleRef.create(middleware)); - console.log(globalMiddlewares); } return globalMiddlewares; } @@ -33,7 +31,6 @@ export class MiddlewareComposer { */ for (const rule of this.middlewareConfigurator.getAllRules()) { for (const excludedPath of rule.excludedFor) { - console.log('excluded ==> ', excludedPath); if ( typeof excludedPath === 'object' && excludedPath.path && diff --git a/packages/core/lib/rest/foundation/middlewares/middleware.ts b/packages/core/lib/rest/foundation/middlewares/middleware.ts index 2a846f6..31adbdd 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware.ts @@ -1,14 +1,6 @@ import { MiddlewareNext, Request, Response } from 'hyper-express'; export abstract class IntentMiddleware { - async handle( - req: Request, - res: Response, - next: MiddlewareNext, - ): Promise { - await this.use(req, res, next); - } - abstract use( req: Request, res: Response, diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index be10403..322b4c2 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -94,7 +94,9 @@ export class IntentHttpServer { const serverOptions = config.get('http.server'); const customServer = new HyperServer(); - const server = await customServer.build(routes, serverOptions); + const server = await customServer + .useGlobalMiddlewares(globalMiddlewares) + .build(routes, serverOptions); server.set_error_handler((hReq: any, hRes: HyperResponse, error: Error) => { const res = new Response(); diff --git a/packages/core/lib/rest/http-server/request/middleware.ts b/packages/core/lib/rest/http-server/request/middleware.ts index 24323be..fd53a9b 100644 --- a/packages/core/lib/rest/http-server/request/middleware.ts +++ b/packages/core/lib/rest/http-server/request/middleware.ts @@ -42,6 +42,4 @@ export const requestMiddleware = async ( } catch (error) { console.error('Request parsing error:', error); } - - next(); }; diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index a86d323..50c73b1 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -18,11 +18,9 @@ export class HyperServer { this.hyper = new HyperExpress.Server(config || {}); this.hyper.use(requestMiddleware); - this.hyper.use(async (hReq, res, next) => { - for (const middleware of this.globalMiddlewares) { - await middleware.handle(hReq, res, next); - } - }); + for (const middleware of this.globalMiddlewares) { + this.hyper.use(middleware.use.bind(middleware)); + } for (const route of routes) { const { path, httpHandler } = route; diff --git a/packages/core/lib/rest/middlewares/cors.ts b/packages/core/lib/rest/middlewares/cors.ts index a1733eb..8905176 100644 --- a/packages/core/lib/rest/middlewares/cors.ts +++ b/packages/core/lib/rest/middlewares/cors.ts @@ -10,14 +10,17 @@ export class CorsMiddleware extends IntentMiddleware { super(); } - async use(req: Request, res: Response, next: MiddlewareNext): Promise { - const corsMiddleware = cors(this.config.get('app.cors') as CorsOptions); + async use(req: Request, res: Response): Promise { + const corsMiddleware = cors({ + origin: '*', // or specify allowed origins + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, + }); await new Promise(resolve => { corsMiddleware(req, res, () => { resolve(1); }); }); - - next(); } } From 975df2a716a7bca843d26da16be5f77c2607e1b3 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Wed, 27 Nov 2024 11:44:35 +0530 Subject: [PATCH 13/27] fix(core): remove file --- packages/core/dependency-graph.svg | 9456 ---------------------------- 1 file changed, 9456 deletions(-) delete mode 100644 packages/core/dependency-graph.svg diff --git a/packages/core/dependency-graph.svg b/packages/core/dependency-graph.svg deleted file mode 100644 index 75b9663..0000000 --- a/packages/core/dependency-graph.svg +++ /dev/null @@ -1,9456 +0,0 @@ - - - - - - -dependency-cruiser output - - -cluster_lib - -lib - - -cluster_lib/cache - -cache - - -cluster_lib/cache/drivers - -drivers - - -cluster_lib/cache/interfaces - -interfaces - - -cluster_lib/cache/utils - -utils - - -cluster_lib/codegen - -codegen - - -cluster_lib/config - -config - - -cluster_lib/console - -console - - -cluster_lib/console/commands - -commands - - -cluster_lib/console/interfaces - -interfaces - - -cluster_lib/database - -database - - -cluster_lib/database/commands - -commands - - -cluster_lib/database/exceptions - -exceptions - - -cluster_lib/database/queryBuilders - -queryBuilders - - -cluster_lib/database/repositories - -repositories - - -cluster_lib/dev-server - -dev-server - - -cluster_lib/events - -events - - -cluster_lib/exceptions - -exceptions - - -cluster_lib/foundation - -foundation - - -cluster_lib/interfaces - -interfaces - - -cluster_lib/localization - -localization - - -cluster_lib/localization/helpers - -helpers - - -cluster_lib/localization/interfaces - -interfaces - - -cluster_lib/localization/utils - -utils - - -cluster_lib/logger - -logger - - -cluster_lib/mailer - -mailer - - -cluster_lib/mailer/exceptions - -exceptions - - -cluster_lib/mailer/interfaces - -interfaces - - -cluster_lib/mailer/providers - -providers - - -cluster_lib/queue - -queue - - -cluster_lib/queue/console - -console - - -cluster_lib/queue/core - -core - - -cluster_lib/queue/drivers - -drivers - - -cluster_lib/queue/events - -events - - -cluster_lib/queue/exceptions - -exceptions - - -cluster_lib/queue/interfaces - -interfaces - - -cluster_lib/queue/jobRunners - -jobRunners - - -cluster_lib/queue/schema - -schema - - -cluster_lib/queue/strategy - -strategy - - -cluster_lib/queue/workers - -workers - - -cluster_lib/reflections - -reflections - - -cluster_lib/rest - -rest - - -cluster_lib/rest/foundation - -foundation - - -cluster_lib/rest/foundation/guards - -guards - - -cluster_lib/rest/foundation/middlewares - -middlewares - - -cluster_lib/rest/http-server - -http-server - - -cluster_lib/rest/http-server/contexts - -contexts - - -cluster_lib/rest/http-server/request - -request - - -cluster_lib/rest/interceptors - -interceptors - - -cluster_lib/rest/middlewares - -middlewares - - -cluster_lib/serializers - -serializers - - -cluster_lib/storage - -storage - - -cluster_lib/storage/data - -data - - -cluster_lib/storage/drivers - -drivers - - -cluster_lib/storage/exceptions - -exceptions - - -cluster_lib/storage/file-handlers - -file-handlers - - -cluster_lib/storage/helpers - -helpers - - -cluster_lib/storage/interfaces - -interfaces - - -cluster_lib/transformers - -transformers - - -cluster_lib/type-helpers - -type-helpers - - -cluster_lib/utils - -utils - - -cluster_lib/validator - -validator - - -cluster_lib/validator/decorators - -decorators - - -cluster_node_modules - -node_modules - - -cluster_node_modules/@nestjs - -@nestjs - - -cluster_node_modules/@react-email - -@react-email - - -cluster_resources - -resources - - -cluster_resources/mail - -mail - - -cluster_resources/mail/components - -components - - -cluster_resources/mail/emails - -emails - - - -crypto - - -crypto - - - - - -fs - - -fs - - - - - -lib/cache/cache.ts - - -cache.ts - - - - - -lib/config/index.ts - - -index.ts - - - - - -lib/cache/cache.ts->lib/config/index.ts - - - - - -lib/cache/interfaces/index.ts - - -index.ts - - - - - -lib/cache/cache.ts->lib/cache/interfaces/index.ts - - - - - -lib/cache/service.ts - - -service.ts - - - - - -lib/cache/cache.ts->lib/cache/service.ts - - - - - -lib/cache/utils/genKey.ts - - -genKey.ts - - - - - -lib/cache/cache.ts->lib/cache/utils/genKey.ts - - - - - -lib/config/builder.ts - - -builder.ts - - - - - -lib/config/index.ts->lib/config/builder.ts - - - - - -lib/config/options.ts - - -options.ts - - - - - -lib/config/index.ts->lib/config/options.ts - - - - - -lib/config/command.ts - - -command.ts - - - - - -lib/config/index.ts->lib/config/command.ts - - - - - - - -no-circular - - - -lib/config/constant.ts - - -constant.ts - - - - - -lib/config/index.ts->lib/config/constant.ts - - - - - -lib/config/register-namespace.ts - - -register-namespace.ts - - - - - -lib/config/index.ts->lib/config/register-namespace.ts - - - - - -lib/config/service.ts - - -service.ts - - - - - -lib/config/index.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/cache/interfaces/driver.ts - - -driver.ts - - - - - -lib/cache/interfaces/index.ts->lib/cache/interfaces/driver.ts - - - - - -lib/cache/interfaces/options.ts - - -options.ts - - - - - -lib/cache/interfaces/index.ts->lib/cache/interfaces/options.ts - - - - - -lib/cache/service.ts->lib/cache/interfaces/index.ts - - - - - -lib/cache/drivers/inMemory.ts - - -inMemory.ts - - - - - -lib/cache/service.ts->lib/cache/drivers/inMemory.ts - - - - - -lib/cache/drivers/redis.ts - - -redis.ts - - - - - -lib/cache/service.ts->lib/cache/drivers/redis.ts - - - - - -node_modules/@nestjs/common - - - - - -common - - - - - -lib/cache/service.ts->node_modules/@nestjs/common - - - - - -lib/cache/utils/genKey.ts->crypto - - - - - -lib/utils/array.ts - - -array.ts - - - - - -lib/cache/utils/genKey.ts->lib/utils/array.ts - - - - - -lib/cache/constants.ts - - -constants.ts - - - - - -lib/cache/drivers/inMemory.ts->lib/cache/interfaces/index.ts - - - - - -lib/interfaces/index.ts - - -index.ts - - - - - -lib/cache/drivers/inMemory.ts->lib/interfaces/index.ts - - - - - -lib/utils/packageLoader.ts - - -packageLoader.ts - - - - - -lib/cache/drivers/inMemory.ts->lib/utils/packageLoader.ts - - - - - -lib/interfaces/config.ts - - -config.ts - - - - - -lib/interfaces/index.ts->lib/interfaces/config.ts - - - - - -lib/interfaces/utils.ts - - -utils.ts - - - - - -lib/interfaces/index.ts->lib/interfaces/utils.ts - - - - - -lib/interfaces/transformer.ts - - -transformer.ts - - - - - -lib/interfaces/index.ts->lib/interfaces/transformer.ts - - - - - -node_modules/picocolors - - - - - -picocolors - - - - - -lib/utils/packageLoader.ts->node_modules/picocolors - - - - - -lib/utils/logger.ts - - -logger.ts - - - - - -lib/utils/packageLoader.ts->lib/utils/logger.ts - - - - - - - -no-circular - - - -lib/cache/drivers/redis.ts->lib/cache/interfaces/index.ts - - - - - -lib/cache/drivers/redis.ts->lib/interfaces/index.ts - - - - - -lib/utils/index.ts - - -index.ts - - - - - -lib/cache/drivers/redis.ts->lib/utils/index.ts - - - - - -lib/utils/index.ts->lib/utils/packageLoader.ts - - - - - - - -no-circular - - - -lib/utils/index.ts->lib/utils/array.ts - - - - - - - -no-circular - - - -lib/utils/string.ts - - -string.ts - - - - - -lib/utils/index.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/utils/helpers.ts - - -helpers.ts - - - - - -lib/utils/index.ts->lib/utils/helpers.ts - - - - - - - -no-circular - - - -lib/utils/number.ts - - -number.ts - - - - - -lib/utils/index.ts->lib/utils/number.ts - - - - - - - -no-circular - - - -lib/utils/expParser.ts - - -expParser.ts - - - - - -lib/utils/index.ts->lib/utils/expParser.ts - - - - - -lib/utils/object.ts - - -object.ts - - - - - -lib/utils/index.ts->lib/utils/object.ts - - - - - - - -no-circular - - - -lib/utils/context.ts - - -context.ts - - - - - -lib/utils/index.ts->lib/utils/context.ts - - - - - - - -no-circular - - - -lib/utils/path.ts - - -path.ts - - - - - -lib/utils/index.ts->lib/utils/path.ts - - - - - -lib/cache/index.ts - - -index.ts - - - - - -lib/cache/index.ts->lib/cache/cache.ts - - - - - -lib/cache/index.ts->lib/cache/interfaces/index.ts - - - - - -lib/cache/index.ts->lib/cache/service.ts - - - - - -lib/cache/interfaces/driver.ts->lib/interfaces/index.ts - - - - - -lib/cache/interfaces/options.ts->node_modules/@nestjs/common - - - - - -lib/exceptions/index.ts - - -index.ts - - - - - -lib/utils/array.ts->lib/exceptions/index.ts - - - - - - - -no-circular - - - -lib/utils/array.ts->lib/utils/object.ts - - - - - - - -no-circular - - - -lib/codegen/command.ts - - -command.ts - - - - - -lib/codegen/command.ts->node_modules/@nestjs/common - - - - - -lib/console/index.ts - - -index.ts - - - - - -lib/codegen/command.ts->lib/console/index.ts - - - - - -lib/codegen/command.ts->lib/utils/string.ts - - - - - -lib/codegen/service.ts - - -service.ts - - - - - -lib/codegen/command.ts->lib/codegen/service.ts - - - - - -lib/codegen/utils.ts - - -utils.ts - - - - - -lib/codegen/command.ts->lib/codegen/utils.ts - - - - - -path - - -path - - - - - -lib/codegen/command.ts->path - - - - - -lib/console/interfaces/index.ts - - -index.ts - - - - - -lib/console/index.ts->lib/console/interfaces/index.ts - - - - - - - -no-circular - - - -lib/console/commands/index.ts - - -index.ts - - - - - -lib/console/index.ts->lib/console/commands/index.ts - - - - - - - -no-circular - - - -lib/console/decorators.ts - - -decorators.ts - - - - - -lib/console/index.ts->lib/console/decorators.ts - - - - - - - -no-circular - - - -lib/console/metadata.ts - - -metadata.ts - - - - - -lib/console/index.ts->lib/console/metadata.ts - - - - - - - -no-circular - - - -lib/console/consoleIO.ts - - -consoleIO.ts - - - - - -lib/console/index.ts->lib/console/consoleIO.ts - - - - - - - -no-circular - - - -lib/console/inquirer.ts - - -inquirer.ts - - - - - -lib/console/index.ts->lib/console/inquirer.ts - - - - - -lib/console/logger.ts - - -logger.ts - - - - - -lib/console/index.ts->lib/console/logger.ts - - - - - - - -no-circular - - - -lib/console/runner.ts - - -runner.ts - - - - - -lib/console/index.ts->lib/console/runner.ts - - - - - - - -no-circular - - - -lib/utils/string.ts->node_modules/@nestjs/common - - - - - -lib/utils/string.ts->lib/utils/number.ts - - - - - - - -no-circular - - - -lib/utils/pluralise.ts - - -pluralise.ts - - - - - -lib/utils/string.ts->lib/utils/pluralise.ts - - - - - - - -no-circular - - - -lib/codegen/service.ts->fs - - - - - -lib/codegen/service.ts->lib/utils/index.ts - - - - - -lib/codegen/service.ts->node_modules/@nestjs/common - - - - - -lib/codegen/service.ts->path - - - - - -node_modules/eta - - - - - -eta - - - - - -lib/codegen/service.ts->node_modules/eta - - - - - -node_modules/ts-morph - - - - - -ts-morph - - - - - -lib/codegen/service.ts->node_modules/ts-morph - - - - - -lib/codegen/utils.ts->lib/utils/index.ts - - - - - -lib/codegen/utils.ts->path - - - - - -lib/codegen/utils.ts->node_modules/ts-morph - - - - - -lib/config/builder.ts->lib/config/options.ts - - - - - -lib/config/command.ts->lib/console/index.ts - - - - - - - -no-circular - - - -lib/config/command.ts->lib/config/options.ts - - - - - -lib/foundation/index.ts - - -index.ts - - - - - -lib/config/command.ts->lib/foundation/index.ts - - - - - - - -no-circular - - - -lib/utils/console-helpers.ts - - -console-helpers.ts - - - - - -lib/config/command.ts->lib/utils/console-helpers.ts - - - - - -lib/config/command.ts->lib/config/constant.ts - - - - - -node_modules/archy - - - - - -archy - - - - - -lib/config/command.ts->node_modules/archy - - - - - -lib/config/command.ts->node_modules/picocolors - - - - - -lib/foundation/index.ts->node_modules/@nestjs/common - - - - - -lib/foundation/app-container.ts - - -app-container.ts - - - - - -lib/foundation/index.ts->lib/foundation/app-container.ts - - - - - -lib/foundation/service-provider.ts - - -service-provider.ts - - - - - -lib/foundation/index.ts->lib/foundation/service-provider.ts - - - - - -lib/foundation/container-factory.ts - - -container-factory.ts - - - - - -lib/foundation/index.ts->lib/foundation/container-factory.ts - - - - - - - -no-circular - - - -lib/foundation/module-builder.ts - - -module-builder.ts - - - - - -lib/foundation/index.ts->lib/foundation/module-builder.ts - - - - - - - -no-circular - - - -lib/utils/console-helpers.ts->node_modules/picocolors - - - - - -lib/config/register-namespace.ts->lib/config/options.ts - - - - - -lib/type-helpers/index.ts - - -index.ts - - - - - -lib/config/register-namespace.ts->lib/type-helpers/index.ts - - - - - -node_modules/dotenv - - - - - -dotenv - - - - - -lib/config/register-namespace.ts->node_modules/dotenv - - - - - -lib/config/service.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/config/service.ts->lib/config/options.ts - - - - - -lib/config/service.ts->lib/foundation/index.ts - - - - - - - -no-circular - - - -lib/config/service.ts->lib/config/constant.ts - - - - - -lib/config/service.ts->lib/type-helpers/index.ts - - - - - -lib/console/argumentParser.ts - - -argumentParser.ts - - - - - -lib/console/argumentParser.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/console/argumentParser.ts->lib/utils/logger.ts - - - - - - - -no-circular - - - -lib/console/argumentParser.ts->lib/console/interfaces/index.ts - - - - - - - -no-circular - - - -lib/utils/logger.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/utils/logger.ts->node_modules/picocolors - - - - - -lib/console/interfaces/index.ts->lib/console/consoleIO.ts - - - - - - - -no-circular - - - -lib/console/commands/listCommands.ts - - -listCommands.ts - - - - - -lib/console/commands/index.ts->lib/console/commands/listCommands.ts - - - - - - - -no-circular - - - -lib/console/commands/listCommands.ts->fs - - - - - -lib/console/commands/listCommands.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/console/commands/listCommands.ts->node_modules/@nestjs/common - - - - - -lib/console/commands/listCommands.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/console/commands/listCommands.ts->path - - - - - -lib/console/commands/listCommands.ts->node_modules/picocolors - - - - - -lib/utils/columnify.ts - - -columnify.ts - - - - - -lib/console/commands/listCommands.ts->lib/utils/columnify.ts - - - - - - - -no-circular - - - -lib/console/commands/listCommands.ts->lib/console/decorators.ts - - - - - - - -no-circular - - - -lib/console/commands/listCommands.ts->lib/console/metadata.ts - - - - - - - -no-circular - - - -lib/utils/columnify.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/console/decorators.ts->lib/console/interfaces/index.ts - - - - - - - -no-circular - - - -lib/console/constants.ts - - -constants.ts - - - - - -lib/console/decorators.ts->lib/console/constants.ts - - - - - -node_modules/reflect-metadata - - - - - -reflect-metadata - - - - - -lib/console/decorators.ts->node_modules/reflect-metadata - - - - - -not-to-dev-dep - - - -lib/console/metadata.ts->lib/interfaces/index.ts - - - - - -lib/console/metadata.ts->lib/console/argumentParser.ts - - - - - - - -no-circular - - - -lib/console/metadata.ts->lib/console/interfaces/index.ts - - - - - - - -no-circular - - - -lib/console/commands/route-list.ts - - -route-list.ts - - - - - -lib/console/commands/route-list.ts->lib/utils/columnify.ts - - - - - -lib/console/commands/route-list.ts->lib/console/decorators.ts - - - - - -lib/rest/index.ts - - -index.ts - - - - - -lib/console/commands/route-list.ts->lib/rest/index.ts - - - - - -node_modules/@nestjs/core - - - - - -core - - - - - -lib/console/commands/route-list.ts->node_modules/@nestjs/core - - - - - -lib/rest/http-server/index.ts - - -index.ts - - - - - -lib/rest/index.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/index.ts - - -index.ts - - - - - -lib/rest/index.ts->lib/rest/foundation/index.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/cors.ts - - -cors.ts - - - - - -lib/rest/index.ts->lib/rest/middlewares/cors.ts - - - - - - - -no-circular - - - -lib/rest/interceptors/timeout.ts - - -timeout.ts - - - - - -lib/rest/index.ts->lib/rest/interceptors/timeout.ts - - - - - -lib/rest/middlewares/helmet.ts - - -helmet.ts - - - - - -lib/rest/index.ts->lib/rest/middlewares/helmet.ts - - - - - - - -no-circular - - - -lib/console/consoleIO.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/console/consoleIO.ts->lib/console/argumentParser.ts - - - - - - - -no-circular - - - -lib/console/consoleIO.ts->lib/console/interfaces/index.ts - - - - - - - -no-circular - - - -lib/console/consoleIO.ts->lib/console/inquirer.ts - - - - - -lib/console/consoleIO.ts->lib/console/logger.ts - - - - - - - -no-circular - - - -node_modules/enquirer - - - - - -enquirer - - - - - -lib/console/inquirer.ts->node_modules/enquirer - - - - - -lib/console/logger.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/console/logger.ts->node_modules/picocolors - - - - - -node_modules/cli-table3 - - - - - -cli-table3 - - - - - -lib/console/logger.ts->node_modules/cli-table3 - - - - - -lib/console/runner.ts->node_modules/picocolors - - - - - -lib/console/runner.ts->lib/console/interfaces/index.ts - - - - - - - -no-circular - - - -lib/console/runner.ts->lib/utils/columnify.ts - - - - - - - -no-circular - - - -lib/console/runner.ts->lib/console/metadata.ts - - - - - - - -no-circular - - - -lib/console/runner.ts->lib/console/consoleIO.ts - - - - - - - -no-circular - - - -lib/console/runner.ts->lib/console/logger.ts - - - - - - - -no-circular - - - -lib/console/runner.ts->lib/utils/helpers.ts - - - - - - - -no-circular - - - -node_modules/yargs-parser - - - - - -yargs-parser - - - - - -lib/console/runner.ts->node_modules/yargs-parser - - - - - -lib/utils/helpers.ts->lib/interfaces/index.ts - - - - - -lib/utils/helpers.ts->node_modules/@nestjs/common - - - - - -lib/utils/helpers.ts->lib/utils/array.ts - - - - - - - -no-circular - - - -lib/utils/helpers.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/utils/helpers.ts->node_modules/picocolors - - - - - -lib/utils/helpers.ts->lib/utils/logger.ts - - - - - - - -no-circular - - - -node_modules/class-validator - - - - - -class-validator - - - - - -lib/utils/helpers.ts->node_modules/class-validator - - - - - -lib/utils/helpers.ts->lib/utils/object.ts - - - - - - - -no-circular - - - -node_modules/class-transformer - - - - - -class-transformer - - - - - -lib/utils/helpers.ts->node_modules/class-transformer - - - - - -lib/constants.ts - - -constants.ts - - - - - -lib/database/baseModel.ts - - -baseModel.ts - - - - - -lib/database/interfaces.ts - - -interfaces.ts - - - - - -lib/database/baseModel.ts->lib/database/interfaces.ts - - - - - -lib/database/queryBuilders/custom.ts - - -custom.ts - - - - - -lib/database/baseModel.ts->lib/database/queryBuilders/custom.ts - - - - - -lib/database/queryBuilders/softDelete.ts - - -softDelete.ts - - - - - -lib/database/baseModel.ts->lib/database/queryBuilders/softDelete.ts - - - - - -node_modules/objection - - - - - -objection - - - - - -lib/database/baseModel.ts->node_modules/objection - - - - - -no-duplicate-dep-types - - - -lib/database/interfaces.ts->node_modules/objection - - - - - -no-duplicate-dep-types - - - -lib/database/queryBuilders/custom.ts->lib/interfaces/index.ts - - - - - -lib/database/queryBuilders/custom.ts->lib/database/interfaces.ts - - - - - -lib/database/queryBuilders/custom.ts->node_modules/objection - - - - - -no-duplicate-dep-types - - - -lib/database/queryBuilders/softDelete.ts->lib/database/queryBuilders/custom.ts - - - - - -lib/database/queryBuilders/softDelete.ts->node_modules/objection - - - - - -no-duplicate-dep-types - - - -lib/database/commands/migrations.ts - - -migrations.ts - - - - - -lib/database/commands/migrations.ts->node_modules/@nestjs/common - - - - - -lib/database/commands/migrations.ts->lib/console/index.ts - - - - - -lib/database/commands/migrations.ts->lib/utils/string.ts - - - - - -lib/database/commands/migrations.ts->node_modules/picocolors - - - - - -lib/database/service.ts - - -service.ts - - - - - -lib/database/commands/migrations.ts->lib/database/service.ts - - - - - -lib/database/service.ts->lib/config/index.ts - - - - - -lib/database/service.ts->node_modules/@nestjs/common - - - - - -lib/database/service.ts->lib/database/baseModel.ts - - - - - -lib/database/exceptions/index.ts - - -index.ts - - - - - -lib/database/service.ts->lib/database/exceptions/index.ts - - - - - -lib/database/options.ts - - -options.ts - - - - - -lib/database/service.ts->lib/database/options.ts - - - - - -node_modules/knex - - - - - -knex - - - - - -lib/database/service.ts->node_modules/knex - - - - - -no-duplicate-dep-types - - - -lib/database/decorators.ts - - -decorators.ts - - - - - -lib/database/decorators.ts->lib/database/baseModel.ts - - - - - -lib/index.ts - - -index.ts - - - - - -lib/database/decorators.ts->lib/index.ts - - - - - - - -no-circular - - - -lib/index.ts->lib/config/index.ts - - - - - -lib/index.ts->lib/interfaces/index.ts - - - - - -lib/index.ts->lib/utils/index.ts - - - - - -lib/index.ts->lib/cache/index.ts - - - - - -lib/index.ts->lib/console/index.ts - - - - - -lib/index.ts->lib/foundation/index.ts - - - - - -lib/index.ts->lib/rest/index.ts - - - - - -lib/index.ts->lib/constants.ts - - - - - -lib/database/index.ts - - -index.ts - - - - - -lib/index.ts->lib/database/index.ts - - - - - - - -no-circular - - - -lib/events/index.ts - - -index.ts - - - - - -lib/index.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/logger/index.ts - - -index.ts - - - - - -lib/index.ts->lib/logger/index.ts - - - - - -lib/index.ts->lib/exceptions/index.ts - - - - - -lib/localization/index.ts - - -index.ts - - - - - -lib/index.ts->lib/localization/index.ts - - - - - -lib/mailer/index.ts - - -index.ts - - - - - -lib/index.ts->lib/mailer/index.ts - - - - - -lib/queue/index.ts - - -index.ts - - - - - -lib/index.ts->lib/queue/index.ts - - - - - - - -no-circular - - - -lib/reflections/index.ts - - -index.ts - - - - - -lib/index.ts->lib/reflections/index.ts - - - - - -lib/serializers/validationErrorSerializer.ts - - -validationErrorSerializer.ts - - - - - -lib/index.ts->lib/serializers/validationErrorSerializer.ts - - - - - -lib/serviceProvider.ts - - -serviceProvider.ts - - - - - -lib/index.ts->lib/serviceProvider.ts - - - - - - - -no-circular - - - -lib/storage/index.ts - - -index.ts - - - - - -lib/index.ts->lib/storage/index.ts - - - - - -lib/transformers/index.ts - - -index.ts - - - - - -lib/index.ts->lib/transformers/index.ts - - - - - -lib/validator/index.ts - - -index.ts - - - - - -lib/index.ts->lib/validator/index.ts - - - - - -lib/database/exceptions/connectionNotFound.ts - - -connectionNotFound.ts - - - - - -lib/database/exceptions/index.ts->lib/database/exceptions/connectionNotFound.ts - - - - - -lib/database/exceptions/modelNotFound.ts - - -modelNotFound.ts - - - - - -lib/database/exceptions/index.ts->lib/database/exceptions/modelNotFound.ts - - - - - -lib/database/exceptions/modelNotFound.ts->node_modules/@nestjs/common - - - - - -lib/database/exceptions/repoError.ts - - -repoError.ts - - - - - -lib/database/helpers.ts - - -helpers.ts - - - - - -lib/database/index.ts->lib/database/baseModel.ts - - - - - -lib/database/index.ts->lib/database/interfaces.ts - - - - - -lib/database/index.ts->lib/database/queryBuilders/custom.ts - - - - - -lib/database/index.ts->lib/database/queryBuilders/softDelete.ts - - - - - -lib/database/index.ts->lib/database/service.ts - - - - - -lib/database/index.ts->lib/database/decorators.ts - - - - - - - -no-circular - - - -lib/database/index.ts->lib/database/exceptions/index.ts - - - - - -lib/database/index.ts->lib/database/options.ts - - - - - -lib/database/repositories/contract.ts - - -contract.ts - - - - - -lib/database/index.ts->lib/database/repositories/contract.ts - - - - - -lib/database/repositories/database.ts - - -database.ts - - - - - -lib/database/index.ts->lib/database/repositories/database.ts - - - - - -lib/database/options.ts->node_modules/@nestjs/common - - - - - -lib/database/options.ts->node_modules/knex - - - - - -no-duplicate-dep-types - - - -lib/database/repositories/contract.ts->lib/database/baseModel.ts - - - - - -lib/database/repositories/contract.ts->lib/database/interfaces.ts - - - - - -lib/database/repositories/contract.ts->node_modules/knex - - - - - -no-duplicate-dep-types - - - -lib/database/repositories/database.ts->lib/database/baseModel.ts - - - - - -lib/database/repositories/database.ts->lib/database/interfaces.ts - - - - - -lib/database/repositories/database.ts->lib/database/queryBuilders/custom.ts - - - - - -lib/database/repositories/database.ts->node_modules/objection - - - - - -no-duplicate-dep-types - - - -lib/database/repositories/database.ts->lib/database/service.ts - - - - - -lib/database/repositories/database.ts->lib/database/exceptions/index.ts - - - - - -lib/database/repositories/database.ts->lib/database/exceptions/repoError.ts - - - - - -lib/database/repositories/database.ts->lib/database/repositories/contract.ts - - - - - -lib/database/repositories/database.ts->node_modules/knex - - - - - -no-duplicate-dep-types - - - -lib/dev-server/build.ts - - -build.ts - - - - - -lib/dev-server/build.ts->lib/utils/index.ts - - - - - -lib/dev-server/build.ts->lib/console/index.ts - - - - - -lib/dev-server/build.ts->node_modules/picocolors - - - - - -lib/dev-server/serve.ts - - -serve.ts - - - - - -lib/dev-server/serve.ts->lib/utils/index.ts - - - - - -lib/dev-server/serve.ts->lib/console/index.ts - - - - - -lib/events/constants.ts - - -constants.ts - - - - - -lib/events/decorator.ts - - -decorator.ts - - - - - -lib/events/decorator.ts->lib/interfaces/index.ts - - - - - -lib/events/decorator.ts->node_modules/reflect-metadata - - - - - -not-to-dev-dep - - - -lib/events/decorator.ts->lib/events/constants.ts - - - - - -lib/events/event.ts - - -event.ts - - - - - -lib/events/event.ts->node_modules/reflect-metadata - - - - - -not-to-dev-dep - - - -lib/events/event.ts->lib/utils/helpers.ts - - - - - -lib/events/event.ts->lib/events/constants.ts - - - - - -lib/queue/queue.ts - - -queue.ts - - - - - -lib/events/event.ts->lib/queue/queue.ts - - - - - - - -no-circular - - - -lib/queue/strategy/index.ts - - -index.ts - - - - - -lib/events/event.ts->lib/queue/strategy/index.ts - - - - - -lib/events/helpers.ts - - -helpers.ts - - - - - -lib/events/event.ts->lib/events/helpers.ts - - - - - - - -no-circular - - - -lib/events/runner.ts - - -runner.ts - - - - - -lib/events/event.ts->lib/events/runner.ts - - - - - -lib/queue/queue.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/metadata.ts - - -metadata.ts - - - - - -lib/queue/queue.ts->lib/queue/metadata.ts - - - - - -lib/queue/core/index.ts - - -index.ts - - - - - -lib/queue/queue.ts->lib/queue/core/index.ts - - - - - -lib/queue/strategy/pollQueueDriver.ts - - -pollQueueDriver.ts - - - - - -lib/queue/queue.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/service.ts - - -service.ts - - - - - -lib/queue/queue.ts->lib/queue/service.ts - - - - - - - -no-circular - - - -lib/queue/strategy/subscribeQueueDriver.ts - - -subscribeQueueDriver.ts - - - - - -lib/queue/queue.ts->lib/queue/strategy/subscribeQueueDriver.ts - - - - - -lib/queue/strategy/index.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - - - -no-circular - - - -lib/queue/strategy/index.ts->lib/queue/strategy/subscribeQueueDriver.ts - - - - - -lib/queue/strategy/driverJob.ts - - -driverJob.ts - - - - - -lib/queue/strategy/index.ts->lib/queue/strategy/driverJob.ts - - - - - -lib/queue/strategy/message.ts - - -message.ts - - - - - -lib/queue/strategy/index.ts->lib/queue/strategy/message.ts - - - - - -lib/events/helpers.ts->lib/events/event.ts - - - - - - - -no-circular - - - -lib/events/runner.ts->lib/utils/helpers.ts - - - - - -lib/events/metadata.ts - - -metadata.ts - - - - - -lib/events/runner.ts->lib/events/metadata.ts - - - - - -lib/events/index.ts->lib/events/decorator.ts - - - - - -lib/events/index.ts->lib/events/event.ts - - - - - - - -no-circular - - - -lib/events/index.ts->lib/events/helpers.ts - - - - - - - -no-circular - - - -lib/events/index.ts->lib/events/runner.ts - - - - - -lib/events/interfaces.ts - - -interfaces.ts - - - - - -lib/events/index.ts->lib/events/interfaces.ts - - - - - -lib/events/jobListener.ts - - -jobListener.ts - - - - - -lib/events/index.ts->lib/events/jobListener.ts - - - - - - - -no-circular - - - -lib/events/index.ts->lib/events/metadata.ts - - - - - -lib/events/interfaces.ts->lib/queue/strategy/index.ts - - - - - -lib/events/jobListener.ts->node_modules/@nestjs/common - - - - - -lib/events/jobListener.ts->lib/events/constants.ts - - - - - -lib/events/jobListener.ts->lib/events/runner.ts - - - - - -lib/queue/decorators.ts - - -decorators.ts - - - - - -lib/events/jobListener.ts->lib/queue/decorators.ts - - - - - - - -no-circular - - - -lib/events/metadata.ts->lib/interfaces/index.ts - - - - - -lib/queue/decorators.ts->node_modules/reflect-metadata - - - - - -not-to-dev-dep - - - -lib/queue/decorators.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/decorators.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/queue/constants.ts - - -constants.ts - - - - - -lib/queue/decorators.ts->lib/queue/constants.ts - - - - - -lib/exceptions/base-exception-handler.ts - - -base-exception-handler.ts - - - - - -lib/exceptions/base-exception-handler.ts->lib/interfaces/index.ts - - - - - -lib/exceptions/base-exception-handler.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/exceptions/base-exception-handler.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/exceptions/base-exception-handler.ts->lib/logger/index.ts - - - - - - - -no-circular - - - -lib/exceptions/base-exception-handler.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/status-codes.ts - - -status-codes.ts - - - - - -lib/exceptions/base-exception-handler.ts->lib/rest/http-server/status-codes.ts - - - - - -lib/exceptions/http-exception.ts - - -http-exception.ts - - - - - -lib/exceptions/base-exception-handler.ts->lib/exceptions/http-exception.ts - - - - - -lib/exceptions/validation-failed.ts - - -validation-failed.ts - - - - - -lib/exceptions/base-exception-handler.ts->lib/exceptions/validation-failed.ts - - - - - - - -no-circular - - - -lib/logger/logger.ts - - -logger.ts - - - - - -lib/logger/index.ts->lib/logger/logger.ts - - - - - - - -no-circular - - - -lib/logger/options.ts - - -options.ts - - - - - -lib/logger/index.ts->lib/logger/options.ts - - - - - -lib/logger/service.ts - - -service.ts - - - - - -lib/logger/index.ts->lib/logger/service.ts - - - - - - - -no-circular - - - -lib/rest/http-server/index.ts->lib/rest/http-server/status-codes.ts - - - - - -lib/rest/http-server/request/index.ts - - -index.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/request/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/contexts/execution-context.ts - - -execution-context.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/contexts/execution-context.ts - - - - - - - -no-circular - - - -lib/rest/http-server/contexts/http-execution-context.ts - - -http-execution-context.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/contexts/http-execution-context.ts - - - - - - - -no-circular - - - -lib/rest/http-server/param-decorators.ts - - -param-decorators.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/param-decorators.ts - - - - - - - -no-circular - - - -lib/rest/http-server/response.ts - - -response.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/response.ts - - - - - - - -no-circular - - - -lib/rest/http-server/decorators.ts - - -decorators.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/decorators.ts - - - - - - - -no-circular - - - -lib/rest/http-server/interfaces.ts - - -interfaces.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/interfaces.ts - - - - - -lib/rest/http-server/http-handler.ts - - -http-handler.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/http-handler.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts - - -route-explorer.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/route-explorer.ts - - - - - - - -no-circular - - - -lib/rest/http-server/server.ts - - -server.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/server.ts - - - - - - - -no-circular - - - -lib/rest/http-server/streamable-file.ts - - -streamable-file.ts - - - - - -lib/rest/http-server/index.ts->lib/rest/http-server/streamable-file.ts - - - - - -lib/exceptions/http-exception.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/exceptions/http-exception.ts->lib/rest/index.ts - - - - - - - -no-circular - - - -lib/exceptions/validation-failed.ts->lib/rest/index.ts - - - - - -lib/exceptions/validation-failed.ts->lib/exceptions/http-exception.ts - - - - - - - -no-circular - - - -lib/exceptions/forbidden-exception.ts - - -forbidden-exception.ts - - - - - -lib/exceptions/forbidden-exception.ts->lib/rest/index.ts - - - - - - - -no-circular - - - -lib/exceptions/forbidden-exception.ts->lib/exceptions/http-exception.ts - - - - - - - -no-circular - - - -lib/exceptions/genericException.ts - - -genericException.ts - - - - - -lib/exceptions/genericException.ts->lib/exceptions/http-exception.ts - - - - - -lib/exceptions/index.ts->lib/exceptions/base-exception-handler.ts - - - - - - - -no-circular - - - -lib/exceptions/index.ts->lib/exceptions/http-exception.ts - - - - - - - -no-circular - - - -lib/exceptions/index.ts->lib/exceptions/validation-failed.ts - - - - - - - -no-circular - - - -lib/exceptions/index.ts->lib/exceptions/forbidden-exception.ts - - - - - -lib/exceptions/index.ts->lib/exceptions/genericException.ts - - - - - - - -no-circular - - - -lib/exceptions/invalid-credentials.ts - - -invalid-credentials.ts - - - - - -lib/exceptions/index.ts->lib/exceptions/invalid-credentials.ts - - - - - - - -no-circular - - - -lib/exceptions/invalid-value.ts - - -invalid-value.ts - - - - - -lib/exceptions/index.ts->lib/exceptions/invalid-value.ts - - - - - -lib/exceptions/invalid-value-type.ts - - -invalid-value-type.ts - - - - - -lib/exceptions/index.ts->lib/exceptions/invalid-value-type.ts - - - - - -lib/exceptions/unauthorized.ts - - -unauthorized.ts - - - - - -lib/exceptions/index.ts->lib/exceptions/unauthorized.ts - - - - - - - -no-circular - - - -lib/exceptions/invalid-credentials.ts->lib/rest/index.ts - - - - - -lib/exceptions/invalid-credentials.ts->lib/exceptions/http-exception.ts - - - - - -lib/exceptions/unauthorized.ts->lib/rest/index.ts - - - - - -lib/exceptions/unauthorized.ts->lib/exceptions/http-exception.ts - - - - - -lib/explorer.ts - - -explorer.ts - - - - - -lib/explorer.ts->lib/interfaces/index.ts - - - - - -lib/explorer.ts->lib/console/index.ts - - - - - -lib/explorer.ts->lib/foundation/index.ts - - - - - -lib/explorer.ts->node_modules/@nestjs/core - - - - - -lib/explorer.ts->lib/console/constants.ts - - - - - -lib/explorer.ts->lib/events/constants.ts - - - - - -lib/explorer.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/explorer.ts->lib/queue/constants.ts - - - - - -lib/explorer.ts->lib/queue/metadata.ts - - - - - -lib/queue/metadata.ts->lib/interfaces/index.ts - - - - - -lib/queue/metadata.ts->node_modules/@nestjs/common - - - - - -lib/queue/metadata.ts->lib/config/service.ts - - - - - -lib/queue/metadata.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/interfaces/index.ts - - -index.ts - - - - - -lib/queue/metadata.ts->lib/queue/interfaces/index.ts - - - - - -lib/foundation/app-container.ts->lib/interfaces/index.ts - - - - - -lib/foundation/app-container.ts->node_modules/@nestjs/common - - - - - -lib/foundation/app-container.ts->lib/foundation/service-provider.ts - - - - - -lib/foundation/service-provider.ts->lib/interfaces/index.ts - - - - - -lib/foundation/service-provider.ts->node_modules/@nestjs/common - - - - - -lib/foundation/container-factory.ts->lib/interfaces/index.ts - - - - - -lib/foundation/container-factory.ts->node_modules/@nestjs/core - - - - - -lib/foundation/container-factory.ts->lib/foundation/app-container.ts - - - - - -lib/foundation/container-factory.ts->lib/foundation/module-builder.ts - - - - - - - -no-circular - - - -lib/foundation/module-builder.ts->lib/interfaces/index.ts - - - - - -lib/foundation/module-builder.ts->node_modules/@nestjs/common - - - - - -lib/foundation/module-builder.ts->lib/rest/index.ts - - - - - -lib/foundation/module-builder.ts->lib/foundation/app-container.ts - - - - - -lib/rest/foundation/middlewares/configurator.ts - - -configurator.ts - - - - - -lib/foundation/module-builder.ts->lib/rest/foundation/middlewares/configurator.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/configurator.ts->lib/interfaces/index.ts - - - - - -lib/rest/foundation/middlewares/configurator.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/middleware.ts - - -middleware.ts - - - - - -lib/rest/foundation/middlewares/configurator.ts->lib/rest/foundation/middlewares/middleware.ts - - - - - - - -no-circular - - - -lib/localization/helpers/index.ts - - -index.ts - - - - - -lib/localization/index.ts->lib/localization/helpers/index.ts - - - - - -lib/localization/service.ts - - -service.ts - - - - - -lib/localization/index.ts->lib/localization/service.ts - - - - - -lib/localization/interfaces/index.ts - - -index.ts - - - - - -lib/localization/index.ts->lib/localization/interfaces/index.ts - - - - - -lib/mailer/interfaces/index.ts - - -index.ts - - - - - -lib/mailer/index.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/mail.ts - - -mail.ts - - - - - -lib/mailer/index.ts->lib/mailer/mail.ts - - - - - -lib/mailer/message.ts - - -message.ts - - - - - -lib/mailer/index.ts->lib/mailer/message.ts - - - - - -lib/mailer/service.ts - - -service.ts - - - - - -lib/mailer/index.ts->lib/mailer/service.ts - - - - - -lib/queue/index.ts->lib/queue/queue.ts - - - - - - - -no-circular - - - -lib/queue/index.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/index.ts->lib/queue/decorators.ts - - - - - - - -no-circular - - - -lib/queue/worker.ts - - -worker.ts - - - - - -lib/queue/index.ts->lib/queue/worker.ts - - - - - - - -no-circular - - - -lib/queue/index.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/drivers/index.ts - - -index.ts - - - - - -lib/queue/index.ts->lib/queue/drivers/index.ts - - - - - - - -no-circular - - - -lib/queue/index.ts->lib/queue/service.ts - - - - - - - -no-circular - - - -lib/reflections/reflector.ts - - -reflector.ts - - - - - -lib/reflections/index.ts->lib/reflections/reflector.ts - - - - - - - -no-circular - - - -lib/reflections/set-metadata.ts - - -set-metadata.ts - - - - - -lib/reflections/index.ts->lib/reflections/set-metadata.ts - - - - - -lib/serializers/validationErrorSerializer.ts->lib/utils/string.ts - - - - - -lib/serializers/validationErrorSerializer.ts->lib/utils/helpers.ts - - - - - -lib/serializers/validationErrorSerializer.ts->node_modules/class-validator - - - - - -lib/serviceProvider.ts->lib/config/index.ts - - - - - -lib/serviceProvider.ts->lib/interfaces/index.ts - - - - - -lib/serviceProvider.ts->lib/cache/index.ts - - - - - -lib/serviceProvider.ts->lib/console/index.ts - - - - - -lib/serviceProvider.ts->lib/config/command.ts - - - - - -lib/serviceProvider.ts->lib/foundation/index.ts - - - - - -lib/serviceProvider.ts->node_modules/@nestjs/core - - - - - -lib/serviceProvider.ts->lib/database/commands/migrations.ts - - - - - -lib/serviceProvider.ts->lib/database/index.ts - - - - - - - -no-circular - - - -lib/serviceProvider.ts->lib/dev-server/build.ts - - - - - -lib/serviceProvider.ts->lib/dev-server/serve.ts - - - - - -lib/serviceProvider.ts->lib/events/jobListener.ts - - - - - - - -no-circular - - - -lib/serviceProvider.ts->lib/logger/index.ts - - - - - -lib/serviceProvider.ts->lib/explorer.ts - - - - - - - -no-circular - - - -lib/serviceProvider.ts->lib/queue/metadata.ts - - - - - -lib/serviceProvider.ts->lib/localization/index.ts - - - - - -lib/serviceProvider.ts->lib/mailer/index.ts - - - - - -lib/serviceProvider.ts->lib/queue/index.ts - - - - - - - -no-circular - - - -lib/queue/console/index.ts - - -index.ts - - - - - -lib/serviceProvider.ts->lib/queue/console/index.ts - - - - - - - -no-circular - - - -lib/storage/service.ts - - -service.ts - - - - - -lib/serviceProvider.ts->lib/storage/service.ts - - - - - -lib/storage/interfaces/index.ts - - -index.ts - - - - - -lib/storage/index.ts->lib/storage/interfaces/index.ts - - - - - -lib/storage/drivers/index.ts - - -index.ts - - - - - -lib/storage/index.ts->lib/storage/drivers/index.ts - - - - - -lib/storage/storage.ts - - -storage.ts - - - - - -lib/storage/index.ts->lib/storage/storage.ts - - - - - -lib/transformers/interfaces.ts - - -interfaces.ts - - - - - -lib/transformers/index.ts->lib/transformers/interfaces.ts - - - - - -lib/transformers/transformable.ts - - -transformable.ts - - - - - -lib/transformers/index.ts->lib/transformers/transformable.ts - - - - - -lib/transformers/transformer.ts - - -transformer.ts - - - - - -lib/transformers/index.ts->lib/transformers/transformer.ts - - - - - -lib/validator/index.ts->node_modules/@nestjs/common - - - - - -lib/validator/index.ts->lib/rest/index.ts - - - - - -lib/validator/decorators/index.ts - - -index.ts - - - - - -lib/validator/index.ts->lib/validator/decorators/index.ts - - - - - - - -no-circular - - - -lib/validator/validationGuard.ts - - -validationGuard.ts - - - - - -lib/validator/index.ts->lib/validator/validationGuard.ts - - - - - - - -no-circular - - - -lib/validator/validator.ts - - -validator.ts - - - - - -lib/validator/index.ts->lib/validator/validator.ts - - - - - - - -no-circular - - - -lib/interfaces/config.ts->node_modules/@nestjs/common - - - - - -lib/interfaces/config.ts->lib/interfaces/utils.ts - - - - - -node_modules/hyper-express - - - - - -hyper-express - - - - - -lib/interfaces/config.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/interfaces/utils.ts->node_modules/@nestjs/common - - - - - -lib/localization/helpers/index.ts->lib/localization/service.ts - - - - - -lib/localization/service.ts->lib/utils/index.ts - - - - - -lib/localization/service.ts->node_modules/@nestjs/common - - - - - -lib/localization/service.ts->lib/utils/string.ts - - - - - -lib/localization/service.ts->path - - - - - -lib/localization/service.ts->lib/config/service.ts - - - - - -lib/localization/service.ts->lib/localization/interfaces/index.ts - - - - - -lib/localization/service.ts->lib/utils/number.ts - - - - - -node_modules/fs-extra - - - - - -fs-extra - - - - - -lib/localization/service.ts->node_modules/fs-extra - - - - - -lib/utils/number.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/localization/utils/get.ts - - -get.ts - - - - - -lib/localization/utils/stringToPath.ts - - -stringToPath.ts - - - - - -lib/localization/utils/get.ts->lib/localization/utils/stringToPath.ts - - - - - -lib/localization/utils/memoizeCapped.ts - - -memoizeCapped.ts - - - - - -lib/localization/utils/stringToPath.ts->lib/localization/utils/memoizeCapped.ts - - - - - -lib/localization/utils/memoize.ts - - -memoize.ts - - - - - -lib/localization/utils/memoize.ts->lib/interfaces/index.ts - - - - - -lib/localization/utils/memoizeCapped.ts->lib/localization/utils/memoize.ts - - - - - -lib/logger/logger.ts->lib/logger/service.ts - - - - - - - -no-circular - - - -node_modules/winston - - - - - -winston - - - - - -lib/logger/options.ts->node_modules/winston - - - - - -lib/logger/service.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/logger/service.ts->node_modules/@nestjs/common - - - - - -lib/logger/service.ts->path - - - - - -lib/logger/service.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/logger/service.ts->lib/utils/number.ts - - - - - - - -no-circular - - - -lib/logger/service.ts->lib/logger/options.ts - - - - - -lib/logger/service.ts->node_modules/winston - - - - - -lib/mailer/constants.ts - - -constants.ts - - - - - -lib/mailer/exceptions/errorSendingMail.ts - - -errorSendingMail.ts - - - - - -lib/mailer/exceptions/errorSendingMail.ts->lib/utils/string.ts - - - - - -lib/mailer/exceptions/invalid-mail-provider.ts - - -invalid-mail-provider.ts - - - - - -lib/mailer/interfaces/options.ts - - -options.ts - - - - - -lib/mailer/interfaces/index.ts->lib/mailer/interfaces/options.ts - - - - - -lib/mailer/mail.ts->lib/mailer/message.ts - - - - - -lib/mailer/mail.ts->lib/mailer/service.ts - - - - - -lib/mailer/message.ts->lib/utils/index.ts - - - - - -lib/mailer/message.ts->lib/config/service.ts - - - - - -lib/mailer/message.ts->lib/mailer/constants.ts - - - - - -lib/mailer/message.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/interfaces/provider.ts - - -provider.ts - - - - - -lib/mailer/message.ts->lib/mailer/interfaces/provider.ts - - - - - - - -no-circular - - - -resources/mail/emails/index.tsx - - -index.tsx - - - - - -lib/mailer/message.ts->resources/mail/emails/index.tsx - - - - - -lib/mailer/service.ts->lib/utils/index.ts - - - - - -lib/mailer/service.ts->node_modules/@nestjs/common - - - - - -lib/mailer/service.ts->lib/config/service.ts - - - - - -lib/mailer/service.ts->lib/mailer/exceptions/invalid-mail-provider.ts - - - - - -lib/mailer/service.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/service.ts->lib/mailer/interfaces/provider.ts - - - - - -lib/mailer/providers/index.ts - - -index.ts - - - - - -lib/mailer/service.ts->lib/mailer/providers/index.ts - - - - - -lib/mailer/interfaces/provider.ts->lib/mailer/message.ts - - - - - - - -no-circular - - - -node_modules/@react-email/components - - - - - -components - - - - - -resources/mail/emails/index.tsx->node_modules/@react-email/components - - - - - -node_modules/@react-email/tailwind - - - - - -tailwind - - - - - -resources/mail/emails/index.tsx->node_modules/@react-email/tailwind - - - - - -no-non-package-json - - - -resources/mail/components/Footer.tsx - - -Footer.tsx - - - - - -resources/mail/emails/index.tsx->resources/mail/components/Footer.tsx - - - - - -resources/mail/components/Header.tsx - - -Header.tsx - - - - - -resources/mail/emails/index.tsx->resources/mail/components/Header.tsx - - - - - -resources/mail/emails/components.tsx - - -components.tsx - - - - - -resources/mail/emails/index.tsx->resources/mail/emails/components.tsx - - - - - -lib/mailer/providers/logger.ts - - -logger.ts - - - - - -lib/mailer/providers/index.ts->lib/mailer/providers/logger.ts - - - - - -lib/mailer/providers/mailgun.ts - - -mailgun.ts - - - - - -lib/mailer/providers/index.ts->lib/mailer/providers/mailgun.ts - - - - - -lib/mailer/providers/nodemailer.ts - - -nodemailer.ts - - - - - -lib/mailer/providers/index.ts->lib/mailer/providers/nodemailer.ts - - - - - -lib/mailer/providers/resend.ts - - -resend.ts - - - - - -lib/mailer/providers/index.ts->lib/mailer/providers/resend.ts - - - - - -lib/mailer/providers/logger.ts->lib/logger/index.ts - - - - - -lib/mailer/providers/logger.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/providers/logger.ts->lib/mailer/interfaces/provider.ts - - - - - -lib/mailer/providers/mailgun.ts->lib/utils/index.ts - - - - - -lib/mailer/providers/mailgun.ts->lib/utils/string.ts - - - - - -lib/mailer/providers/mailgun.ts->lib/utils/helpers.ts - - - - - -lib/mailer/providers/mailgun.ts->lib/storage/index.ts - - - - - -lib/mailer/providers/mailgun.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/providers/mailgun.ts->lib/mailer/interfaces/provider.ts - - - - - -lib/mailer/providers/nodemailer.ts->lib/utils/index.ts - - - - - -lib/mailer/providers/nodemailer.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/providers/nodemailer.ts->lib/mailer/interfaces/provider.ts - - - - - -lib/mailer/providers/resend.ts->lib/utils/index.ts - - - - - -lib/mailer/providers/resend.ts->lib/mailer/exceptions/errorSendingMail.ts - - - - - -lib/mailer/providers/resend.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/providers/resend.ts->lib/mailer/interfaces/provider.ts - - - - - -lib/mailer/providers/sendgrid.ts - - -sendgrid.ts - - - - - -lib/mailer/providers/sendgrid.ts->lib/utils/index.ts - - - - - -lib/mailer/providers/sendgrid.ts->lib/mailer/interfaces/index.ts - - - - - -lib/mailer/providers/sendgrid.ts->lib/mailer/interfaces/provider.ts - - - - - -lib/queue/console/commands.ts - - -commands.ts - - - - - -lib/queue/console/commands.ts->node_modules/@nestjs/common - - - - - -lib/queue/console/commands.ts->lib/console/index.ts - - - - - -lib/queue/console/commands.ts->lib/queue/worker.ts - - - - - - - -no-circular - - - -lib/queue/worker.ts->lib/queue/metadata.ts - - - - - -lib/queue/worker.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/worker.ts->lib/queue/service.ts - - - - - - - -no-circular - - - -lib/queue/workers/pollQueue.ts - - -pollQueue.ts - - - - - -lib/queue/worker.ts->lib/queue/workers/pollQueue.ts - - - - - - - -no-circular - - - -lib/queue/workers/subscribeQueue.ts - - -subscribeQueue.ts - - - - - -lib/queue/worker.ts->lib/queue/workers/subscribeQueue.ts - - - - - - - -no-circular - - - -lib/queue/console/index.ts->lib/queue/console/commands.ts - - - - - - - -no-circular - - - -lib/queue/core/payloadBuilder.ts - - -payloadBuilder.ts - - - - - -lib/queue/core/index.ts->lib/queue/core/payloadBuilder.ts - - - - - -lib/queue/core/payloadBuilder.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/core/payloadBuilder.ts->lib/queue/metadata.ts - - - - - -node_modules/ms - - - - - -ms - - - - - -lib/queue/core/payloadBuilder.ts->node_modules/ms - - - - - -node_modules/ulid - - - - - -ulid - - - - - -lib/queue/core/payloadBuilder.ts->node_modules/ulid - - - - - -lib/queue/drivers/database.ts - - -database.ts - - - - - -lib/queue/drivers/database.ts->lib/database/index.ts - - - - - - - -no-circular - - - -lib/queue/drivers/database.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/drivers/database.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/interfaces/job.ts - - -job.ts - - - - - -lib/queue/drivers/database.ts->lib/queue/interfaces/job.ts - - - - - -lib/queue/drivers/database.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/interfaces/options.ts - - -options.ts - - - - - -lib/queue/interfaces/index.ts->lib/queue/interfaces/options.ts - - - - - -lib/queue/interfaces/job.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/strategy/pollQueueDriver.ts->lib/queue/strategy/index.ts - - - - - - - -no-circular - - - -lib/queue/strategy/pollQueueDriver.ts->lib/queue/strategy/subscribeQueueDriver.ts - - - - - -lib/queue/drivers/index.ts->lib/queue/drivers/database.ts - - - - - - - -no-circular - - - -lib/queue/drivers/redis.ts - - -redis.ts - - - - - -lib/queue/drivers/index.ts->lib/queue/drivers/redis.ts - - - - - -lib/queue/drivers/sqs.ts - - -sqs.ts - - - - - -lib/queue/drivers/index.ts->lib/queue/drivers/sqs.ts - - - - - -lib/queue/drivers/sync.ts - - -sync.ts - - - - - -lib/queue/drivers/index.ts->lib/queue/drivers/sync.ts - - - - - -lib/queue/drivers/redis.ts->lib/utils/index.ts - - - - - -lib/queue/drivers/redis.ts->lib/utils/helpers.ts - - - - - -lib/queue/drivers/redis.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/drivers/redis.ts->node_modules/ulid - - - - - -lib/queue/drivers/redis.ts->lib/queue/interfaces/job.ts - - - - - -lib/queue/drivers/redis.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/schema/index.ts - - -index.ts - - - - - -lib/queue/drivers/redis.ts->lib/queue/schema/index.ts - - - - - -lib/queue/drivers/sqs.ts->lib/utils/index.ts - - - - - -lib/queue/drivers/sqs.ts->lib/utils/helpers.ts - - - - - -lib/queue/drivers/sqs.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/drivers/sqs.ts->lib/queue/interfaces/job.ts - - - - - -lib/queue/drivers/sqs.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/drivers/sqs.ts->lib/queue/schema/index.ts - - - - - -lib/queue/drivers/sync.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/drivers/sync.ts->lib/queue/metadata.ts - - - - - -lib/queue/drivers/sync.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/schema/index.ts->node_modules/class-validator - - - - - -lib/queue/events/JobFailed.ts - - -JobFailed.ts - - - - - -lib/queue/events/JobFailed.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/queue/events/JobFailed.ts->lib/queue/constants.ts - - - - - -lib/queue/events/JobProcessed.ts - - -JobProcessed.ts - - - - - -lib/queue/events/JobProcessed.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/queue/events/JobProcessed.ts->lib/queue/constants.ts - - - - - -lib/queue/events/JobProcessing.ts - - -JobProcessing.ts - - - - - -lib/queue/events/JobProcessing.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/queue/events/JobProcessing.ts->lib/queue/constants.ts - - - - - -lib/queue/events/index.ts - - -index.ts - - - - - -lib/queue/events/index.ts->lib/queue/events/JobFailed.ts - - - - - - - -no-circular - - - -lib/queue/events/index.ts->lib/queue/events/JobProcessed.ts - - - - - - - -no-circular - - - -lib/queue/events/index.ts->lib/queue/events/JobProcessing.ts - - - - - - - -no-circular - - - -lib/queue/events/jobMaxRetries.ts - - -jobMaxRetries.ts - - - - - -lib/queue/events/index.ts->lib/queue/events/jobMaxRetries.ts - - - - - - - -no-circular - - - -lib/queue/events/jobMaxRetries.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/queue/events/jobMaxRetries.ts->lib/queue/constants.ts - - - - - -lib/queue/exceptions/maxRetriesExceeded.ts - - -maxRetriesExceeded.ts - - - - - -lib/queue/service.ts->lib/config/index.ts - - - - - -lib/queue/service.ts->lib/utils/index.ts - - - - - -lib/queue/service.ts->node_modules/@nestjs/common - - - - - -lib/queue/service.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/service.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/service.ts->lib/queue/drivers/index.ts - - - - - - - -no-circular - - - -lib/queue/interfaces/options.ts->node_modules/@nestjs/common - - - - - -lib/queue/interfaces/options.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/jobRunners/base.ts - - -base.ts - - - - - -lib/queue/jobRunners/base.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/jobRunners/base.ts->lib/queue/constants.ts - - - - - -lib/queue/jobRunners/base.ts->lib/queue/metadata.ts - - - - - -lib/queue/jobRunners/base.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/jobRunners/base.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/strategy/subscribeQueueDriver.ts->lib/interfaces/index.ts - - - - - -lib/queue/strategy/subscribeQueueDriver.ts->lib/queue/strategy/driverJob.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/utils/index.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/utils/helpers.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/queue/queue.ts - - - - - - - -no-circular - - - -lib/queue/workers/pollQueue.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/events/index.ts - - - - - - - -no-circular - - - -lib/queue/workers/pollQueue.ts->lib/queue/constants.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/queue/metadata.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/queue/strategy/pollQueueDriver.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/queue/events/index.ts - - - - - - - -no-circular - - - -lib/queue/workers/pollQueue.ts->lib/queue/events/jobMaxRetries.ts - - - - - - - -no-circular - - - -lib/queue/workers/pollQueue.ts->lib/queue/service.ts - - - - - - - -no-circular - - - -lib/queue/workers/pollQueue.ts->lib/queue/jobRunners/base.ts - - - - - -lib/queue/workers/baseWorker.ts - - -baseWorker.ts - - - - - -lib/queue/workers/pollQueue.ts->lib/queue/workers/baseWorker.ts - - - - - -lib/queue/workers/subscribeQueue.ts->lib/utils/index.ts - - - - - -lib/queue/workers/subscribeQueue.ts->lib/queue/metadata.ts - - - - - -lib/queue/workers/subscribeQueue.ts->lib/queue/interfaces/index.ts - - - - - -lib/queue/workers/subscribeQueue.ts->lib/queue/service.ts - - - - - - - -no-circular - - - -lib/queue/workers/subscribeQueue.ts->lib/queue/strategy/subscribeQueueDriver.ts - - - - - -lib/queue/workers/baseWorker.ts->node_modules/picocolors - - - - - -lib/queue/workers/baseWorker.ts->lib/utils/helpers.ts - - - - - -lib/queue/workers/baseWorker.ts->lib/queue/strategy/index.ts - - - - - -lib/queue/workers/baseWorker.ts->lib/queue/interfaces/index.ts - - - - - -lib/reflections/apply-decorators.ts - - -apply-decorators.ts - - - - - -lib/reflections/reflector.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/reflections/reflector.ts->node_modules/reflect-metadata - - - - - -not-to-dev-dep - - - -lib/reflections/reflector.ts->node_modules/ulid - - - - - -lib/rest/decorators.ts - - -decorators.ts - - - - - -lib/rest/foundation/controller-scanner.ts - - -controller-scanner.ts - - - - - -lib/rest/foundation/controller-scanner.ts->lib/interfaces/index.ts - - - - - -lib/rest/foundation/controller-scanner.ts->path - - - - - -lib/rest/foundation/controller-scanner.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/constants.ts - - -constants.ts - - - - - -lib/rest/foundation/controller-scanner.ts->lib/rest/http-server/constants.ts - - - - - -lib/rest/foundation/guards/base-guard.ts - - -base-guard.ts - - - - - -lib/rest/foundation/guards/base-guard.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/guards/base-guard.ts->lib/exceptions/forbidden-exception.ts - - - - - - - -no-circular - - - -lib/rest/foundation/guards/base-guard.ts->lib/reflections/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/guards/base-guard.ts->lib/rest/http-server/request/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/extension.ts - - -extension.ts - - - - - -lib/rest/http-server/request/index.ts->lib/rest/http-server/request/extension.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/interfaces.ts - - -interfaces.ts - - - - - -lib/rest/http-server/request/index.ts->lib/rest/http-server/request/interfaces.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/middleware.ts - - -middleware.ts - - - - - -lib/rest/http-server/request/index.ts->lib/rest/http-server/request/middleware.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/multipart-handler.ts - - -multipart-handler.ts - - - - - -lib/rest/http-server/request/index.ts->lib/rest/http-server/request/multipart-handler.ts - - - - - - - -no-circular - - - -lib/rest/foundation/index.ts->lib/rest/foundation/middlewares/configurator.ts - - - - - - - -no-circular - - - -lib/rest/foundation/index.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/foundation/index.ts->lib/rest/foundation/guards/base-guard.ts - - - - - - - -no-circular - - - -lib/rest/foundation/kernel.ts - - -kernel.ts - - - - - -lib/rest/foundation/index.ts->lib/rest/foundation/kernel.ts - - - - - - - -no-circular - - - -lib/rest/foundation/index.ts->lib/rest/foundation/middlewares/middleware.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts - - -server.ts - - - - - -lib/rest/foundation/index.ts->lib/rest/foundation/server.ts - - - - - - - -no-circular - - - -lib/rest/foundation/kernel.ts->lib/interfaces/index.ts - - - - - -lib/rest/foundation/kernel.ts->lib/rest/foundation/middlewares/configurator.ts - - - - - - - -no-circular - - - -lib/rest/foundation/kernel.ts->lib/rest/foundation/guards/base-guard.ts - - - - - - - -no-circular - - - -lib/rest/foundation/kernel.ts->lib/rest/foundation/middlewares/middleware.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/middleware.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/middleware.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/foundation/server.ts->lib/interfaces/index.ts - - - - - -lib/rest/foundation/server.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->lib/foundation/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->lib/utils/console-helpers.ts - - - - - -lib/rest/foundation/server.ts->node_modules/picocolors - - - - - -lib/rest/foundation/server.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->node_modules/@nestjs/core - - - - - -lib/rest/foundation/server.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->lib/exceptions/index.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->lib/rest/foundation/middlewares/configurator.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/foundation/server.ts->node_modules/class-validator - - - - - -lib/rest/foundation/server.ts->lib/rest/foundation/kernel.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/middleware-composer.ts - - -middleware-composer.ts - - - - - -lib/rest/foundation/server.ts->lib/rest/foundation/middlewares/middleware-composer.ts - - - - - - - -no-circular - - - -lib/rest/foundation/server.ts->lib/rest/middlewares/cors.ts - - - - - - - -no-circular - - - -node_modules/console.mute - - - - - -console.mute - - - - - -lib/rest/foundation/server.ts->node_modules/console.mute - - - - - -lib/rest/foundation/middlewares/middleware-composer.ts->lib/interfaces/index.ts - - - - - -lib/rest/foundation/middlewares/middleware-composer.ts->node_modules/@nestjs/core - - - - - -lib/rest/foundation/middlewares/middleware-composer.ts->lib/rest/foundation/middlewares/configurator.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/middleware-composer.ts->lib/rest/foundation/controller-scanner.ts - - - - - - - -no-circular - - - -lib/rest/foundation/middlewares/middleware-composer.ts->lib/rest/foundation/middlewares/middleware.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/cors.ts->lib/config/index.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/cors.ts->node_modules/@nestjs/common - - - - - -lib/rest/middlewares/cors.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/cors.ts->lib/rest/foundation/index.ts - - - - - - - -no-circular - - - -node_modules/cors - - - - - -cors - - - - - -lib/rest/middlewares/cors.ts->node_modules/cors - - - - - -no-non-package-json - - - -lib/rest/http-server/contexts/execution-context.ts->lib/interfaces/index.ts - - - - - -lib/rest/http-server/contexts/execution-context.ts->lib/rest/http-server/contexts/http-execution-context.ts - - - - - - - -no-circular - - - -lib/rest/http-server/contexts/http-execution-context.ts->lib/rest/http-server/request/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/contexts/http-execution-context.ts->lib/rest/http-server/param-decorators.ts - - - - - - - -no-circular - - - -lib/rest/http-server/contexts/http-execution-context.ts->lib/rest/http-server/response.ts - - - - - - - -no-circular - - - -lib/rest/http-server/param-decorators.ts->lib/rest/http-server/constants.ts - - - - - -lib/rest/http-server/param-decorators.ts->lib/rest/http-server/contexts/execution-context.ts - - - - - - - -no-circular - - - -lib/rest/http-server/response.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/response.ts->lib/rest/http-server/status-codes.ts - - - - - -lib/rest/http-server/response.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/http-server/response.ts->lib/rest/http-server/streamable-file.ts - - - - - -lib/utils/extension-to-mime.ts - - -extension-to-mime.ts - - - - - -lib/rest/http-server/response.ts->lib/utils/extension-to-mime.ts - - - - - -lib/rest/http-server/decorators.ts->lib/interfaces/index.ts - - - - - -lib/rest/http-server/decorators.ts->node_modules/@nestjs/common - - - - - -lib/rest/http-server/decorators.ts->lib/reflections/apply-decorators.ts - - - - - -lib/rest/http-server/decorators.ts->lib/rest/http-server/constants.ts - - - - - -lib/rest/http-server/decorators.ts->lib/rest/foundation/index.ts - - - - - -lib/rest/http-server/decorators.ts->lib/rest/http-server/interfaces.ts - - - - - -lib/rest/http-server/http-handler.ts->lib/exceptions/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/http-handler.ts->lib/rest/foundation/index.ts - - - - - -lib/rest/http-server/http-handler.ts->lib/rest/http-server/contexts/execution-context.ts - - - - - - - -no-circular - - - -lib/rest/http-server/http-handler.ts->lib/rest/http-server/param-decorators.ts - - - - - - - -no-circular - - - -lib/rest/http-server/http-handler.ts->lib/rest/http-server/response.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts->lib/interfaces/index.ts - - - - - -lib/rest/http-server/route-explorer.ts->path - - - - - -lib/rest/http-server/route-explorer.ts->node_modules/@nestjs/core - - - - - -lib/rest/http-server/route-explorer.ts->lib/exceptions/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/constants.ts - - - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/request/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts->lib/rest/foundation/index.ts - - - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/contexts/execution-context.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/contexts/http-execution-context.ts - - - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/param-decorators.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/response.ts - - - - - - - -no-circular - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/interfaces.ts - - - - - -lib/rest/http-server/route-explorer.ts->lib/rest/http-server/http-handler.ts - - - - - - - -no-circular - - - -lib/rest/http-server/server.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/http-server/server.ts->lib/rest/http-server/request/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/server.ts->lib/rest/foundation/index.ts - - - - - -lib/rest/http-server/server.ts->lib/rest/http-server/interfaces.ts - - - - - -lib/rest/http-server/server.ts->lib/rest/http-server/request/middleware.ts - - - - - - - -no-circular - - - -stream - - -stream - - - - - -lib/rest/http-server/streamable-file.ts->stream - - - - - -util - - -util - - - - - -lib/rest/http-server/streamable-file.ts->util - - - - - -lib/rest/http-server/request/extension.ts->lib/interfaces/index.ts - - - - - -lib/rest/http-server/request/extension.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/extension.ts->lib/validator/index.ts - - - - - - - -no-circular - - - -lib/storage/file-handlers/uploaded-file.ts - - -uploaded-file.ts - - - - - -lib/rest/http-server/request/extension.ts->lib/storage/file-handlers/uploaded-file.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/extension.ts->lib/utils/extension-to-mime.ts - - - - - -lib/rest/http-server/request/extension.ts->lib/rest/http-server/request/interfaces.ts - - - - - - - -no-circular - - - -lib/storage/file-handlers/uploaded-file.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/storage/file-handlers/uploaded-file.ts->node_modules/fs-extra - - - - - -lib/rest/http-server/request/interfaces.ts->lib/interfaces/index.ts - - - - - -lib/rest/http-server/request/interfaces.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/http-server/request/interfaces.ts->lib/storage/file-handlers/uploaded-file.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/middleware.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/middleware.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/http-server/request/middleware.ts->lib/rest/http-server/request/extension.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/middleware.ts->lib/rest/http-server/request/multipart-handler.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/multipart-handler.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/rest/http-server/request/multipart-handler.ts->path - - - - - -lib/rest/http-server/request/multipart-handler.ts->node_modules/hyper-express - - - - - -no-non-package-json - - - -lib/rest/http-server/request/multipart-handler.ts->node_modules/ulid - - - - - -lib/rest/http-server/request/multipart-handler.ts->lib/storage/file-handlers/uploaded-file.ts - - - - - - - -no-circular - - - -os - - -os - - - - - -lib/rest/http-server/request/multipart-handler.ts->os - - - - - -lib/rest/interceptors/timeout.ts->node_modules/@nestjs/common - - - - - -node_modules/rxjs - - - - - -rxjs - - - - - -lib/rest/interceptors/timeout.ts->node_modules/rxjs - - - - - -lib/rest/middlewares/helmet.ts->lib/config/index.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/helmet.ts->lib/foundation/index.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/helmet.ts->lib/rest/http-server/index.ts - - - - - - - -no-circular - - - -lib/rest/middlewares/helmet.ts->lib/rest/foundation/index.ts - - - - - - - -no-circular - - - -node_modules/helmet - - - - - -helmet - - - - - -lib/rest/middlewares/helmet.ts->node_modules/helmet - - - - - -lib/storage/service.ts->lib/config/index.ts - - - - - -lib/storage/service.ts->node_modules/@nestjs/common - - - - - -lib/storage/driver-mapper.ts - - -driver-mapper.ts - - - - - -lib/storage/service.ts->lib/storage/driver-mapper.ts - - - - - - - -no-circular - - - -lib/storage/service.ts->lib/storage/interfaces/index.ts - - - - - -lib/storage/exceptions/diskNotFound.ts - - -diskNotFound.ts - - - - - -lib/storage/service.ts->lib/storage/exceptions/diskNotFound.ts - - - - - -lib/storage/data/mime-db.ts - - -mime-db.ts - - - - - -lib/storage/driver-mapper.ts->lib/interfaces/index.ts - - - - - -lib/storage/drivers/local.ts - - -local.ts - - - - - -lib/storage/driver-mapper.ts->lib/storage/drivers/local.ts - - - - - - - -no-circular - - - -lib/storage/drivers/s3Storage.ts - - -s3Storage.ts - - - - - -lib/storage/driver-mapper.ts->lib/storage/drivers/s3Storage.ts - - - - - - - -no-circular - - - -lib/storage/driver-mapper.ts->lib/storage/interfaces/index.ts - - - - - -lib/storage/drivers/local.ts->fs - - - - - -lib/storage/drivers/local.ts->path - - - - - -lib/storage/drivers/local.ts->node_modules/fs-extra - - - - - -lib/storage/drivers/local.ts->lib/storage/service.ts - - - - - - - -no-circular - - - -lib/storage/drivers/local.ts->lib/storage/interfaces/index.ts - - - - - -lib/storage/exceptions/cannotParseAsJson.ts - - -cannotParseAsJson.ts - - - - - -lib/storage/drivers/local.ts->lib/storage/exceptions/cannotParseAsJson.ts - - - - - -lib/storage/exceptions/cannotPerformFileOp.ts - - -cannotPerformFileOp.ts - - - - - -lib/storage/drivers/local.ts->lib/storage/exceptions/cannotPerformFileOp.ts - - - - - -lib/storage/helpers/index.ts - - -index.ts - - - - - -lib/storage/drivers/local.ts->lib/storage/helpers/index.ts - - - - - -lib/storage/drivers/s3Storage.ts->fs - - - - - -lib/storage/drivers/s3Storage.ts->lib/interfaces/index.ts - - - - - -lib/storage/drivers/s3Storage.ts->lib/utils/index.ts - - - - - -lib/storage/drivers/s3Storage.ts->lib/utils/string.ts - - - - - -lib/storage/drivers/s3Storage.ts->lib/storage/service.ts - - - - - - - -no-circular - - - -lib/storage/drivers/s3Storage.ts->lib/storage/interfaces/index.ts - - - - - -lib/storage/drivers/s3Storage.ts->lib/storage/exceptions/cannotParseAsJson.ts - - - - - -lib/storage/drivers/s3Storage.ts->lib/storage/exceptions/cannotPerformFileOp.ts - - - - - -lib/storage/drivers/s3Storage.ts->lib/storage/helpers/index.ts - - - - - -lib/storage/interfaces/fileOptions.ts - - -fileOptions.ts - - - - - -lib/storage/interfaces/index.ts->lib/storage/interfaces/fileOptions.ts - - - - - -lib/storage/interfaces/storageDriver.ts - - -storageDriver.ts - - - - - -lib/storage/interfaces/index.ts->lib/storage/interfaces/storageDriver.ts - - - - - - - -no-circular - - - -lib/storage/interfaces/storageOptions.ts - - -storageOptions.ts - - - - - -lib/storage/interfaces/index.ts->lib/storage/interfaces/storageOptions.ts - - - - - -lib/storage/drivers/azureBlob.ts - - -azureBlob.ts - - - - - -lib/storage/drivers/index.ts->lib/storage/drivers/local.ts - - - - - -lib/storage/drivers/index.ts->lib/storage/drivers/s3Storage.ts - - - - - -lib/storage/helpers/extensions.ts - - -extensions.ts - - - - - -lib/storage/helpers/index.ts->lib/storage/helpers/extensions.ts - - - - - -lib/storage/helpers/extensions.ts->lib/storage/data/mime-db.ts - - - - - -lib/storage/storage.ts->node_modules/@nestjs/common - - - - - -lib/storage/storage.ts->lib/storage/service.ts - - - - - -lib/storage/storage.ts->lib/storage/interfaces/index.ts - - - - - -node_modules/axios - - - - - -axios - - - - - -lib/storage/storage.ts->node_modules/axios - - - - - -lib/storage/interfaces/storageDriver.ts->lib/storage/interfaces/index.ts - - - - - - - -no-circular - - - -lib/transformers/interfaces.ts->lib/rest/http-server/request/index.ts - - - - - -lib/transformers/transformable.ts->lib/utils/index.ts - - - - - -lib/transformers/transformable.ts->lib/transformers/interfaces.ts - - - - - -lib/transformers/transformable.ts->lib/transformers/transformer.ts - - - - - -lib/transformers/transformer.ts->lib/interfaces/index.ts - - - - - -lib/transformers/transformer.ts->lib/transformers/interfaces.ts - - - - - -lib/transformers/transformer.ts->lib/utils/expParser.ts - - - - - -lib/utils/object.ts->lib/utils/array.ts - - - - - - - -no-circular - - - -lib/utils/object.ts->lib/exceptions/index.ts - - - - - - - -no-circular - - - -lib/utils/context.ts->lib/rest/index.ts - - - - - -lib/utils/path.ts->path - - - - - -lib/utils/path.ts->node_modules/fs-extra - - - - - -lib/utils/pluralise.ts->lib/utils/string.ts - - - - - - - -no-circular - - - -lib/validator/decorators/exists.ts - - -exists.ts - - - - - -lib/validator/decorators/exists.ts->node_modules/@nestjs/common - - - - - -lib/validator/decorators/exists.ts->lib/utils/helpers.ts - - - - - -lib/validator/decorators/exists.ts->lib/database/index.ts - - - - - -lib/validator/decorators/exists.ts->node_modules/class-validator - - - - - -lib/validator/decorators/isEqualToProp.ts - - -isEqualToProp.ts - - - - - -lib/validator/decorators/index.ts->lib/validator/decorators/isEqualToProp.ts - - - - - -lib/validator/decorators/isValueFromConfig.ts - - -isValueFromConfig.ts - - - - - -lib/validator/decorators/index.ts->lib/validator/decorators/isValueFromConfig.ts - - - - - - - -no-circular - - - -lib/validator/decorators/valueIn.ts - - -valueIn.ts - - - - - -lib/validator/decorators/index.ts->lib/validator/decorators/valueIn.ts - - - - - -lib/validator/decorators/isEqualToProp.ts->node_modules/class-validator - - - - - -lib/validator/decorators/isValueFromConfig.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/validator/decorators/isValueFromConfig.ts->node_modules/@nestjs/common - - - - - -lib/validator/decorators/isValueFromConfig.ts->lib/utils/array.ts - - - - - - - -no-circular - - - -lib/validator/decorators/isValueFromConfig.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/validator/decorators/isValueFromConfig.ts->lib/utils/helpers.ts - - - - - - - -no-circular - - - -lib/validator/decorators/isValueFromConfig.ts->node_modules/class-validator - - - - - -lib/validator/decorators/valueIn.ts->node_modules/class-validator - - - - - -lib/validator/decorators/isUnique.ts - - -isUnique.ts - - - - - -lib/validator/decorators/isUnique.ts->node_modules/@nestjs/common - - - - - -lib/validator/decorators/isUnique.ts->lib/utils/helpers.ts - - - - - -lib/validator/decorators/isUnique.ts->lib/database/index.ts - - - - - -lib/validator/decorators/isUnique.ts->node_modules/class-validator - - - - - -lib/validator/validationGuard.ts->node_modules/@nestjs/common - - - - - -lib/validator/validationGuard.ts->lib/rest/index.ts - - - - - -lib/validator/validationGuard.ts->node_modules/@nestjs/core - - - - - -lib/validator/validator.ts->lib/utils/index.ts - - - - - - - -no-circular - - - -lib/validator/validator.ts->node_modules/@nestjs/common - - - - - -lib/validator/validator.ts->lib/config/service.ts - - - - - - - -no-circular - - - -lib/validator/validator.ts->lib/exceptions/validation-failed.ts - - - - - - - -no-circular - - - -lib/validator/validator.ts->node_modules/class-validator - - - - - -lib/validator/validator.ts->node_modules/class-transformer - - - - - -node_modules/@react-email/code-block - - - - - -code-block - - - - - -node_modules/react - - - - - -react - - - - - -resources/mail/components/CLink.tsx - - -CLink.tsx - - - - - -resources/mail/components/CLink.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/CLink.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/interface.tsx - - -interface.tsx - - - - - -resources/mail/components/CLink.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/CText.tsx - - -CText.tsx - - - - - -resources/mail/components/CText.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/CText.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/CText.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/Footer.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/Footer.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Footer.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/Greetings.tsx - - -Greetings.tsx - - - - - -resources/mail/components/Greetings.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/Greetings.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Greetings.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/Header.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/Header.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Header.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/HrLine.tsx - - -HrLine.tsx - - - - - -resources/mail/components/HrLine.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/HrLine.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Image.tsx - - -Image.tsx - - - - - -resources/mail/components/Image.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/Image.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/InjectMarkdown.tsx - - -InjectMarkdown.tsx - - - - - -resources/mail/components/InjectMarkdown.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/InjectMarkdown.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/InjectMarkdown.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/InjectReactComponent.tsx - - -InjectReactComponent.tsx - - - - - -resources/mail/components/InjectReactComponent.tsx->node_modules/@react-email/code-block - - - - - -no-non-package-json - - - -resources/mail/components/InjectReactComponent.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/InjectReactComponent.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Regards.tsx - - -Regards.tsx - - - - - -resources/mail/components/Regards.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/Regards.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Table.tsx - - -Table.tsx - - - - - -resources/mail/components/Table.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/Table.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/button.tsx - - -button.tsx - - - - - -resources/mail/components/button.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/button.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/button.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/codeBlock.tsx - - -codeBlock.tsx - - - - - -resources/mail/components/codeBlock.tsx->node_modules/@react-email/components - - - - - -resources/mail/components/codeBlock.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/codeBlock.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/components/codeInline.tsx - - -codeInline.tsx - - - - - -resources/mail/components/codeInline.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/components/codeInline.tsx->resources/mail/components/interface.tsx - - - - - -resources/mail/emails/components.tsx->node_modules/react - - - - - -no-non-package-json - - - -resources/mail/emails/components.tsx->resources/mail/components/CLink.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/CText.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/Greetings.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/HrLine.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/Image.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/InjectMarkdown.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/InjectReactComponent.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/Regards.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/Table.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/button.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/codeBlock.tsx - - - - - -resources/mail/emails/components.tsx->resources/mail/components/codeInline.tsx - - - - - From 21817c82c933f5d2d73833ec02cc3c86e5786a3b Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Wed, 27 Nov 2024 11:47:15 +0530 Subject: [PATCH 14/27] fix(core): remove unused dependency --- package-lock.json | 9 --------- packages/core/package.json | 1 - 2 files changed, 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d87d28..2f88c01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4952,14 +4952,6 @@ "@types/node": "*" } }, - "node_modules/@types/busboy": { - "version": "1.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "dev": true, @@ -17819,7 +17811,6 @@ "@nestjs/testing": "^10.3.9", "@stylistic/eslint-plugin-ts": "^2.6.1", "@types/archy": "^0.0.36", - "@types/busboy": "^1.5.4", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^9.0.7", "@types/jest": "^29.5.13", diff --git a/packages/core/package.json b/packages/core/package.json index bc9dfc9..9913128 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,7 +43,6 @@ "@nestjs/testing": "^10.3.9", "@stylistic/eslint-plugin-ts": "^2.6.1", "@types/archy": "^0.0.36", - "@types/busboy": "^1.5.4", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^9.0.7", "@types/jest": "^29.5.13", From 3b41206e423319044b511781f3f0ac147cdd8efe Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 14:52:08 +0530 Subject: [PATCH 15/27] feat(hyper-express): add custom flavour of hyper-express server fix(core): fix core in http servers --- .gitignore | 3 + integrations/sample-app/app/boot/sp/app.ts | 2 +- .../sample-app/app/boot/sp/console.ts | 8 +- .../sample-app/app/http/controllers/icon.ts | 22 +- .../sample-app/app/http/guards/custom.ts | 14 +- .../sample-app/app/http/guards/global.ts | 16 +- integrations/sample-app/app/http/kernel.ts | 13 +- .../sample-app/app/http/middlewares/global.ts | 2 +- .../sample-app/app/http/middlewares/sample.ts | 4 +- integrations/sample-app/config/http.ts | 7 + package-lock.json | 101 +- .../core/lib/foundation/service-provider.ts | 4 +- packages/core/lib/interfaces/config.ts | 3 +- .../lib/rest/foundation/guards/base-guard.ts | 26 +- packages/core/lib/rest/foundation/index.ts | 2 +- packages/core/lib/rest/foundation/kernel.ts | 4 +- .../rest/foundation/middlewares/middleware.ts | 2 +- packages/core/lib/rest/foundation/server.ts | 10 +- .../http-server/contexts/execution-context.ts | 11 +- .../contexts/http-execution-context.ts | 8 +- packages/core/lib/rest/http-server/index.ts | 1 - .../lib/rest/http-server/request/extension.ts | 187 - .../lib/rest/http-server/request/index.ts | 4 - .../rest/http-server/request/interfaces.ts | 41 - .../rest/http-server/request/middleware.ts | 45 - .../http-server/request/multipart-handler.ts | 65 - .../core/lib/rest/http-server/response.ts | 9 +- .../lib/rest/http-server/route-explorer.ts | 38 +- packages/core/lib/rest/http-server/server.ts | 46 +- packages/core/lib/rest/middlewares/cors.ts | 10 +- packages/core/lib/rest/middlewares/helmet.ts | 2 +- packages/core/lib/serviceProvider.ts | 2 - packages/core/lib/transformers/interfaces.ts | 2 +- packages/core/lib/validator/index.ts | 26 +- .../core/lib/validator/validation-guard.ts | 20 + .../core/lib/validator/validationGuard.ts | 15 - packages/core/package.json | 1 + packages/hyper-express/.gitignore | 4 + packages/hyper-express/.gitmodules | 9 + packages/hyper-express/.npmignore | 5 + packages/hyper-express/.prettierrc | 7 + packages/hyper-express/LICENSE | 22 + packages/hyper-express/README.md | 60 + .../hyper-express/benchmarks/benchmark.sh | 26 + .../benchmarks/configuration.json | 5 + packages/hyper-express/benchmarks/index.js | 102 + .../benchmarks/package-lock.json | 1644 +++++++ .../hyper-express/benchmarks/package.json | 20 + .../benchmarks/scenarios/simple_html_page.js | 23 + .../hyper-express/benchmarks/setup/express.js | 20 + .../hyper-express/benchmarks/setup/fastify.js | 20 + .../benchmarks/setup/hyperexpress.js | 21 + .../benchmarks/setup/nanoexpress.js | 20 + .../benchmarks/setup/uwebsockets.js | 20 + packages/hyper-express/benchmarks/utils.js | 24 + packages/hyper-express/index.js | 33 + packages/hyper-express/package-lock.json | 283 ++ packages/hyper-express/package.json | 60 + .../hyper-express/src/components/Server.js | 608 +++ .../compatibility/ExpressRequest.js | 208 + .../compatibility/ExpressResponse.js | 112 + .../components/compatibility/NodeRequest.js | 5 + .../components/compatibility/NodeResponse.js | 27 + .../src/components/http/Request.js | 1155 +++++ .../src/components/http/Response.js | 1012 +++++ .../src/components/plugins/HostManager.js | 74 + .../src/components/plugins/LiveFile.js | 172 + .../src/components/plugins/MultipartField.js | 132 + .../src/components/plugins/SSEventStream.js | 116 + .../src/components/router/Route.js | 210 + .../src/components/router/Router.js | 483 ++ .../src/components/ws/Websocket.js | 423 ++ .../src/components/ws/WebsocketRoute.js | 198 + .../hyper-express/src/shared/operators.js | 197 + .../src/shared/process-multipart.js | 60 + .../hyper-express/src/shared/uploaded-file.js | 39 + .../hyper-express/tests/components/Server.js | 110 + .../tests/components/features/HostManager.js | 58 + .../tests/components/features/LiveFile.js | 49 + .../tests/components/http/Request.js | 363 ++ .../tests/components/http/Response.js | 198 + .../scenarios/middleware_double_iteration.js | 55 + .../scenarios/middleware_dynamic_iteration.js | 108 + .../scenarios/middleware_execution_order.js | 103 + .../scenarios/middleware_iteration_error.js | 43 + .../scenarios/middleware_layered_iteration.js | 73 + .../middleware_uncaught_async_error.js | 43 + .../http/scenarios/request_body_echo_test.js | 59 + .../http/scenarios/request_chunked_json.js | 43 + .../http/scenarios/request_chunked_stream.js | 72 + .../http/scenarios/request_multipart.js | 214 + .../scenarios/request_router_paths_test.js | 44 + .../http/scenarios/request_stream.js | 71 + .../scenarios/request_uncaught_rejections.js | 85 + .../http/scenarios/response_chunked_write.js | 77 + .../response_custom_content_length.js | 35 + .../http/scenarios/response_custom_status.js | 55 + .../scenarios/response_headers_behavior.js | 122 + .../http/scenarios/response_hooks.js | 68 + .../http/scenarios/response_piped.js | 61 + .../http/scenarios/response_send_no_body.js | 49 + .../http/scenarios/response_send_status.js | 49 + .../http/scenarios/response_set_header.js | 35 + .../components/http/scenarios/response_sse.js | 136 + .../http/scenarios/response_stream.js | 101 + .../scenarios/response_stream_sync_writes.js | 42 + .../tests/components/router/Router.js | 115 + .../router/scenarios/chainable_routes.js | 59 + .../tests/components/ws/Websocket.js | 134 + .../tests/components/ws/WebsocketRoute.js | 122 + .../tests/components/ws/scenarios/stream.js | 64 + .../tests/components/ws/scenarios/writable.js | 69 + packages/hyper-express/tests/configuration.js | 26 + .../hyper-express/tests/content/example.txt | 1 + .../tests/content/large-image.jpg | Bin 0 -> 879067 bytes .../tests/content/test-body.json | 6 + .../hyper-express/tests/content/test.html | 8 + .../tests/content/written/.required | 0 packages/hyper-express/tests/index.js | 93 + packages/hyper-express/tests/local.js | 78 + .../configuration.json | 3 + .../hyper-express-body-parser/index.js | 22 + .../scenarios/parser_compression.js | 184 + .../scenarios/parser_limit.js | 71 + .../scenarios/parser_types.js | 94 + .../scenarios/parser_validation.js | 75 + .../hyper-express-session/configuration.json | 4 + .../hyper-express-session/index.js | 23 + .../hyper-express-session/scenarios/brute.js | 49 + .../scenarios/duration.js | 75 + .../scenarios/properties.js | 71 + .../hyper-express-session/scenarios/roll.js | 74 + .../hyper-express-session/scenarios/visits.js | 106 + .../hyper-express-session/test_engine.js | 64 + .../hyper-express/tests/package-lock.json | 3943 +++++++++++++++++ packages/hyper-express/tests/package.json | 21 + packages/hyper-express/tests/performance.js | 74 + .../tests/scripts/MemoryStore.js | 80 + .../hyper-express/tests/scripts/operators.js | 82 + .../hyper-express/tests/ssl/dummy-cert.pem | 34 + .../hyper-express/tests/ssl/dummy-key.pem | 52 + packages/hyper-express/tests/types/Router.ts | 127 + packages/hyper-express/tsconfig.json | 17 + .../types/components/Server.d.ts | 145 + .../types/components/http/Request.d.ts | 252 ++ .../types/components/http/Response.d.ts | 275 ++ .../middleware/MiddlewareHandler.d.ts | 10 + .../components/middleware/MiddlewareNext.d.ts | 1 + .../types/components/plugins/HostManager.d.ts | 36 + .../types/components/plugins/LiveFile.d.ts | 48 + .../components/plugins/MultipartField.d.ts | 73 + .../components/plugins/SSEventStream.d.ts | 50 + .../types/components/router/Route.d.ts | 1 + .../types/components/router/Router.d.ts | 188 + .../types/components/ws/Websocket.d.ts | 157 + .../types/components/ws/WebsocketRoute.d.ts | 1 + packages/hyper-express/types/index.d.ts | 16 + .../hyper-express/types/shared/operators.d.ts | 10 + .../types/shared/uploaded-file.d.ts | 19 + 159 files changed, 17921 insertions(+), 537 deletions(-) delete mode 100644 packages/core/lib/rest/http-server/request/extension.ts delete mode 100644 packages/core/lib/rest/http-server/request/index.ts delete mode 100644 packages/core/lib/rest/http-server/request/interfaces.ts delete mode 100644 packages/core/lib/rest/http-server/request/middleware.ts delete mode 100644 packages/core/lib/rest/http-server/request/multipart-handler.ts create mode 100644 packages/core/lib/validator/validation-guard.ts delete mode 100644 packages/core/lib/validator/validationGuard.ts create mode 100644 packages/hyper-express/.gitignore create mode 100644 packages/hyper-express/.gitmodules create mode 100644 packages/hyper-express/.npmignore create mode 100644 packages/hyper-express/.prettierrc create mode 100644 packages/hyper-express/LICENSE create mode 100644 packages/hyper-express/README.md create mode 100644 packages/hyper-express/benchmarks/benchmark.sh create mode 100644 packages/hyper-express/benchmarks/configuration.json create mode 100644 packages/hyper-express/benchmarks/index.js create mode 100644 packages/hyper-express/benchmarks/package-lock.json create mode 100644 packages/hyper-express/benchmarks/package.json create mode 100644 packages/hyper-express/benchmarks/scenarios/simple_html_page.js create mode 100644 packages/hyper-express/benchmarks/setup/express.js create mode 100644 packages/hyper-express/benchmarks/setup/fastify.js create mode 100644 packages/hyper-express/benchmarks/setup/hyperexpress.js create mode 100644 packages/hyper-express/benchmarks/setup/nanoexpress.js create mode 100644 packages/hyper-express/benchmarks/setup/uwebsockets.js create mode 100644 packages/hyper-express/benchmarks/utils.js create mode 100644 packages/hyper-express/index.js create mode 100644 packages/hyper-express/package-lock.json create mode 100644 packages/hyper-express/package.json create mode 100644 packages/hyper-express/src/components/Server.js create mode 100644 packages/hyper-express/src/components/compatibility/ExpressRequest.js create mode 100644 packages/hyper-express/src/components/compatibility/ExpressResponse.js create mode 100644 packages/hyper-express/src/components/compatibility/NodeRequest.js create mode 100644 packages/hyper-express/src/components/compatibility/NodeResponse.js create mode 100644 packages/hyper-express/src/components/http/Request.js create mode 100644 packages/hyper-express/src/components/http/Response.js create mode 100644 packages/hyper-express/src/components/plugins/HostManager.js create mode 100644 packages/hyper-express/src/components/plugins/LiveFile.js create mode 100644 packages/hyper-express/src/components/plugins/MultipartField.js create mode 100644 packages/hyper-express/src/components/plugins/SSEventStream.js create mode 100644 packages/hyper-express/src/components/router/Route.js create mode 100644 packages/hyper-express/src/components/router/Router.js create mode 100644 packages/hyper-express/src/components/ws/Websocket.js create mode 100644 packages/hyper-express/src/components/ws/WebsocketRoute.js create mode 100644 packages/hyper-express/src/shared/operators.js create mode 100644 packages/hyper-express/src/shared/process-multipart.js create mode 100644 packages/hyper-express/src/shared/uploaded-file.js create mode 100644 packages/hyper-express/tests/components/Server.js create mode 100644 packages/hyper-express/tests/components/features/HostManager.js create mode 100644 packages/hyper-express/tests/components/features/LiveFile.js create mode 100644 packages/hyper-express/tests/components/http/Request.js create mode 100644 packages/hyper-express/tests/components/http/Response.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/middleware_double_iteration.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/middleware_dynamic_iteration.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/middleware_execution_order.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/middleware_iteration_error.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/middleware_layered_iteration.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/middleware_uncaught_async_error.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_body_echo_test.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_chunked_json.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_chunked_stream.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_multipart.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_router_paths_test.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_stream.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/request_uncaught_rejections.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_chunked_write.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_custom_content_length.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_custom_status.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_headers_behavior.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_hooks.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_piped.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_send_no_body.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_send_status.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_set_header.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_sse.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_stream.js create mode 100644 packages/hyper-express/tests/components/http/scenarios/response_stream_sync_writes.js create mode 100644 packages/hyper-express/tests/components/router/Router.js create mode 100644 packages/hyper-express/tests/components/router/scenarios/chainable_routes.js create mode 100644 packages/hyper-express/tests/components/ws/Websocket.js create mode 100644 packages/hyper-express/tests/components/ws/WebsocketRoute.js create mode 100644 packages/hyper-express/tests/components/ws/scenarios/stream.js create mode 100644 packages/hyper-express/tests/components/ws/scenarios/writable.js create mode 100644 packages/hyper-express/tests/configuration.js create mode 100644 packages/hyper-express/tests/content/example.txt create mode 100644 packages/hyper-express/tests/content/large-image.jpg create mode 100644 packages/hyper-express/tests/content/test-body.json create mode 100644 packages/hyper-express/tests/content/test.html create mode 100644 packages/hyper-express/tests/content/written/.required create mode 100644 packages/hyper-express/tests/index.js create mode 100644 packages/hyper-express/tests/local.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-body-parser/configuration.json create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-body-parser/index.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_compression.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_limit.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_types.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_validation.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/configuration.json create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/index.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/brute.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/duration.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/properties.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/roll.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/visits.js create mode 100644 packages/hyper-express/tests/middlewares/hyper-express-session/test_engine.js create mode 100644 packages/hyper-express/tests/package-lock.json create mode 100644 packages/hyper-express/tests/package.json create mode 100644 packages/hyper-express/tests/performance.js create mode 100644 packages/hyper-express/tests/scripts/MemoryStore.js create mode 100644 packages/hyper-express/tests/scripts/operators.js create mode 100644 packages/hyper-express/tests/ssl/dummy-cert.pem create mode 100644 packages/hyper-express/tests/ssl/dummy-key.pem create mode 100644 packages/hyper-express/tests/types/Router.ts create mode 100644 packages/hyper-express/tsconfig.json create mode 100644 packages/hyper-express/types/components/Server.d.ts create mode 100644 packages/hyper-express/types/components/http/Request.d.ts create mode 100644 packages/hyper-express/types/components/http/Response.d.ts create mode 100644 packages/hyper-express/types/components/middleware/MiddlewareHandler.d.ts create mode 100644 packages/hyper-express/types/components/middleware/MiddlewareNext.d.ts create mode 100644 packages/hyper-express/types/components/plugins/HostManager.d.ts create mode 100644 packages/hyper-express/types/components/plugins/LiveFile.d.ts create mode 100644 packages/hyper-express/types/components/plugins/MultipartField.d.ts create mode 100644 packages/hyper-express/types/components/plugins/SSEventStream.d.ts create mode 100644 packages/hyper-express/types/components/router/Route.d.ts create mode 100644 packages/hyper-express/types/components/router/Router.d.ts create mode 100644 packages/hyper-express/types/components/ws/Websocket.d.ts create mode 100644 packages/hyper-express/types/components/ws/WebsocketRoute.d.ts create mode 100644 packages/hyper-express/types/index.d.ts create mode 100644 packages/hyper-express/types/shared/operators.d.ts create mode 100644 packages/hyper-express/types/shared/uploaded-file.d.ts diff --git a/.gitignore b/.gitignore index ebbf247..816692d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ storage/**/*.log packages/**/*.d.ts packages/**/*.js +!packages/hyper-express/**/*.js +!packages/hyper-express/**/*.d.ts + #yarn .pnp.* .yarn/* diff --git a/integrations/sample-app/app/boot/sp/app.ts b/integrations/sample-app/app/boot/sp/app.ts index 823ae4b..1bf6686 100644 --- a/integrations/sample-app/app/boot/sp/app.ts +++ b/integrations/sample-app/app/boot/sp/app.ts @@ -33,5 +33,5 @@ export class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application service here. */ - boot(app: IntentApplication | IntentApplicationContext) {} + boot(app: IntentApplicationContext) {} } diff --git a/integrations/sample-app/app/boot/sp/console.ts b/integrations/sample-app/app/boot/sp/console.ts index 8c9c4ac..d29b5f6 100644 --- a/integrations/sample-app/app/boot/sp/console.ts +++ b/integrations/sample-app/app/boot/sp/console.ts @@ -1,8 +1,4 @@ -import { - IntentApplication, - IntentApplicationContext, - ServiceProvider, -} from '@intentjs/core'; +import { IntentApplicationContext, ServiceProvider } from '@intentjs/core'; import { TestCacheConsoleCommand } from 'app/console/cache'; import { GreetingCommand } from 'app/console/greeting'; import { TestLogConsoleCommand } from 'app/console/log'; @@ -27,5 +23,5 @@ export class ConsoleServiceProvider extends ServiceProvider { * */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - boot(app: IntentApplication | IntentApplicationContext) {} + boot(app: IntentApplicationContext) {} } diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 5829451..702a81d 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -12,7 +12,7 @@ import { UseGuard, } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; -import { Request } from '@intentjs/core/dist/lib/rest/http-server/request'; +import { Request } from '@intentjs/hyper-express'; @Controller('/icon') @UseGuard(CustomGuard) @@ -74,22 +74,20 @@ export class IntentController { @Param() pathParams: string, @Host() hostname: string, @IP() ips: string, - @Accepts() accepts: string, + // @Accepts() accepts: string, @Body() body: any, ) { console.log('inside post method'); console.log( - await req.file('file1'), - await req.file('file2'), - // query, - // bQuery, - // name, - // pathParams, - // hostname, + query, + bQuery, + name, + pathParams, + hostname, // accepts, - // ips, - // 'inside post method', - // body, + ips, + 'inside post method', + body, ); return { hello: 'world from POST /json' }; } diff --git a/integrations/sample-app/app/http/guards/custom.ts b/integrations/sample-app/app/http/guards/custom.ts index c896646..eeca0ad 100644 --- a/integrations/sample-app/app/http/guards/custom.ts +++ b/integrations/sample-app/app/http/guards/custom.ts @@ -1,18 +1,8 @@ -import { - Injectable, - IntentGuard, - Reflector, - Request, - Response, -} from '@intentjs/core'; +import { ExecutionContext, Injectable, IntentGuard } from '@intentjs/core'; @Injectable() export class CustomGuard extends IntentGuard { - async guard( - req: Request, - res: Response, - reflector: Reflector, - ): Promise { + async guard(ctx: ExecutionContext): Promise { return true; } } diff --git a/integrations/sample-app/app/http/guards/global.ts b/integrations/sample-app/app/http/guards/global.ts index a7248a1..e7142a3 100644 --- a/integrations/sample-app/app/http/guards/global.ts +++ b/integrations/sample-app/app/http/guards/global.ts @@ -1,20 +1,8 @@ -import { - Injectable, - IntentGuard, - Reflector, - Request, - Response, -} from '@intentjs/core'; +import { ExecutionContext, Injectable, IntentGuard } from '@intentjs/core'; @Injectable() export class GlobalGuard extends IntentGuard { - async guard( - req: Request, - res: Response, - reflector: Reflector, - ): Promise { - console.log('inside global guard'); - + async guard(ctx: ExecutionContext): Promise { return true; } } diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index f609eed..6c5a89a 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -12,6 +12,8 @@ import { AuthController } from './controllers/auth'; import { SampleMiddleware } from './middlewares/sample'; import { IntentController } from './controllers/icon'; import { GlobalMiddleware } from './middlewares/global'; +import { Server } from '@intentjs/hyper-express'; +import { GlobalGuard } from './guards/global'; export class HttpKernel extends Kernel { /** @@ -44,8 +46,8 @@ export class HttpKernel extends Kernel { configurator .use(SampleMiddleware) .for({ path: '/icon/sample', method: HttpMethods.POST }) - .for(IntentController) - .exclude('/icon/:name'); + .for(IntentController); + // .exclude('/icon/:name'); configurator.use(GlobalMiddleware).exclude('/icon/:name'); @@ -60,13 +62,12 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/guards */ public guards(): Type[] { - return []; + return [GlobalGuard]; } /** * @param app */ - public async boot(app: IntentApplication): Promise { - app.disable('x-powered-by'); - } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async boot(app: Server): Promise {} } diff --git a/integrations/sample-app/app/http/middlewares/global.ts b/integrations/sample-app/app/http/middlewares/global.ts index 89a7dde..f050e2e 100644 --- a/integrations/sample-app/app/http/middlewares/global.ts +++ b/integrations/sample-app/app/http/middlewares/global.ts @@ -1,5 +1,5 @@ import { ConfigService, Injectable, IntentMiddleware } from '@intentjs/core'; -import { MiddlewareNext, Request, Response } from 'hyper-express'; +import { MiddlewareNext, Request, Response } from '@intentjs/hyper-express'; @Injectable() export class GlobalMiddleware extends IntentMiddleware { diff --git a/integrations/sample-app/app/http/middlewares/sample.ts b/integrations/sample-app/app/http/middlewares/sample.ts index 6b49275..db13f44 100644 --- a/integrations/sample-app/app/http/middlewares/sample.ts +++ b/integrations/sample-app/app/http/middlewares/sample.ts @@ -1,9 +1,9 @@ import { IntentMiddleware, MiddlewareNext } from '@intentjs/core'; -import { Request, Response } from 'hyper-express'; +import { Request, Response } from '@intentjs/hyper-express'; export class SampleMiddleware extends IntentMiddleware { use(req: Request, res: Response, next: MiddlewareNext): void | Promise { - console.log('inside middleware'); + // console.log(req.isHttp(), req.httpHost(), req.all(), req.bearerToken()); next(); } } diff --git a/integrations/sample-app/config/http.ts b/integrations/sample-app/config/http.ts index 8f37395..711951d 100644 --- a/integrations/sample-app/config/http.ts +++ b/integrations/sample-app/config/http.ts @@ -14,6 +14,13 @@ export default registerNamespace( */ parsers: ['json', 'formdata', 'plain', 'urlencoded'], + cors: { + origin: true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, + }, + server: { max_body_buffer: 100000000, max_body_length: 100000000, diff --git a/package-lock.json b/package-lock.json index 2f88c01..28b6f01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2135,6 +2135,10 @@ "resolved": "packages/core", "link": true }, + "node_modules/@intentjs/hyper-express": { + "resolved": "packages/hyper-express", + "link": true + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "license": "MIT" @@ -4952,6 +4956,16 @@ "@types/node": "*" } }, + "node_modules/@types/busboy": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "dev": true, @@ -13265,7 +13279,6 @@ }, "node_modules/negotiator": { "version": "0.6.4", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -17043,6 +17056,8 @@ }, "node_modules/ulid": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", + "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", "license": "MIT", "bin": { "ulid": "bin/cli.js" @@ -17779,6 +17794,7 @@ "version": "0.1.35", "license": "MIT", "dependencies": { + "@intentjs/hyper-express": "^6.17.2-beta-2", "@nestjs/common": "^10.4.8", "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", @@ -18045,6 +18061,89 @@ "version": "0.1.14", "dev": true, "license": "Apache-2.0" + }, + "packages/hyper-express": { + "name": "@intentjs/hyper-express", + "version": "6.17.2-beta-2", + "license": "MIT", + "dependencies": { + "busboy": "^1.6.0", + "cookie": "^1.0.1", + "cookie-signature": "^1.2.1", + "mime-types": "^2.1.35", + "negotiator": "^0.6.3", + "range-parser": "^1.2.1", + "type-is": "^1.6.18", + "typed-emitter": "^2.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0" + }, + "devDependencies": { + "@types/busboy": "^1.5.4", + "@types/express": "^5.0.0", + "@types/node": "^22.7.5", + "typescript": "^5.6.3" + } + }, + "packages/hyper-express/node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "packages/hyper-express/node_modules/@types/express-serve-static-core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", + "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "packages/hyper-express/node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "packages/hyper-express/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "packages/hyper-express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "packages/hyper-express/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" } } } diff --git a/packages/core/lib/foundation/service-provider.ts b/packages/core/lib/foundation/service-provider.ts index a471291..b4e96d5 100644 --- a/packages/core/lib/foundation/service-provider.ts +++ b/packages/core/lib/foundation/service-provider.ts @@ -5,7 +5,7 @@ import { OptionalFactoryDependency, Provider, } from '@nestjs/common'; -import { Type } from '../interfaces'; +import { IntentApplicationContext, Type } from '../interfaces'; export type ImportType = | Type @@ -69,5 +69,5 @@ export abstract class ServiceProvider { /** * Use this method to run */ - abstract boot(app: any); + abstract boot(app: IntentApplicationContext); } diff --git a/packages/core/lib/interfaces/config.ts b/packages/core/lib/interfaces/config.ts index 4a33ced..e340543 100644 --- a/packages/core/lib/interfaces/config.ts +++ b/packages/core/lib/interfaces/config.ts @@ -2,7 +2,7 @@ import { CorsOptions, CorsOptionsDelegate, } from '@nestjs/common/interfaces/external/cors-options.interface'; -import { ServerConstructorOptions } from 'hyper-express'; +import { ServerConstructorOptions } from '@intentjs/hyper-express'; import { GenericClass } from './utils'; export interface SentryConfig { @@ -35,5 +35,6 @@ export type RequestParsers = export interface HttpConfig { parsers: RequestParsers[]; + cors?: CorsOptions | CorsOptionsDelegate; server?: ServerConstructorOptions; } diff --git a/packages/core/lib/rest/foundation/guards/base-guard.ts b/packages/core/lib/rest/foundation/guards/base-guard.ts index c7fdb80..ed365da 100644 --- a/packages/core/lib/rest/foundation/guards/base-guard.ts +++ b/packages/core/lib/rest/foundation/guards/base-guard.ts @@ -1,35 +1,13 @@ -import { Reflector } from '../../../reflections'; import { ForbiddenException } from '../../../exceptions/forbidden-exception'; -import { Request } from '../../http-server/request/interfaces'; import { ExecutionContext } from '../../http-server/contexts/execution-context'; -import { Response } from '../../http-server/response'; export abstract class IntentGuard { async handle(context: ExecutionContext): Promise { - /** - * Get Express Request Object - */ - const request = context.switchToHttp().getRequest(); - - /** - * Get Express Response Object - */ - const response = context.switchToHttp().getResponse(); - - /** - * Initialise a new Reflector class. - */ - const reflector = new Reflector(context.getClass(), context.getHandler()); - - const validationFromGuard = await this.guard(request, response, reflector); + const validationFromGuard = await this.guard(context); if (!validationFromGuard) { throw new ForbiddenException('Forbidden Resource'); } } - abstract guard( - req: Request, - res: Response, - reflector: Reflector, - ): boolean | Promise; + abstract guard(ctx: ExecutionContext): boolean | Promise; } diff --git a/packages/core/lib/rest/foundation/index.ts b/packages/core/lib/rest/foundation/index.ts index 3d40b81..4c61bc1 100644 --- a/packages/core/lib/rest/foundation/index.ts +++ b/packages/core/lib/rest/foundation/index.ts @@ -3,4 +3,4 @@ export * from './kernel'; export * from './middlewares/middleware'; export * from './middlewares/configurator'; export * from './server'; -export { MiddlewareNext } from 'hyper-express'; +export { MiddlewareNext } from '@intentjs/hyper-express'; diff --git a/packages/core/lib/rest/foundation/kernel.ts b/packages/core/lib/rest/foundation/kernel.ts index 15e7c05..63127ab 100644 --- a/packages/core/lib/rest/foundation/kernel.ts +++ b/packages/core/lib/rest/foundation/kernel.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Server } from '@intentjs/hyper-express'; import { Type } from '../../interfaces'; import { IntentGuard } from './guards/base-guard'; import { MiddlewareConfigurator } from './middlewares/configurator'; @@ -19,5 +19,5 @@ export abstract class Kernel { return []; } - public abstract boot(app: any): Promise; + public abstract boot(app: Server): Promise; } diff --git a/packages/core/lib/rest/foundation/middlewares/middleware.ts b/packages/core/lib/rest/foundation/middlewares/middleware.ts index 31adbdd..ae6e7e5 100644 --- a/packages/core/lib/rest/foundation/middlewares/middleware.ts +++ b/packages/core/lib/rest/foundation/middlewares/middleware.ts @@ -1,4 +1,4 @@ -import { MiddlewareNext, Request, Response } from 'hyper-express'; +import { MiddlewareNext, Request, Response } from '@intentjs/hyper-express'; export abstract class IntentMiddleware { abstract use( diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 322b4c2..00f0e0d 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -14,7 +14,7 @@ import { Kernel } from '../foundation/kernel'; import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; import 'console.mute'; -import { Response as HyperResponse, Server } from 'hyper-express'; +import { Response as HyperResponse, Server } from '@intentjs/hyper-express'; import { MiddlewareConfigurator } from './middlewares/configurator'; import { MiddlewareComposer } from './middlewares/middleware-composer'; import { HyperServer } from '../http-server/server'; @@ -86,9 +86,6 @@ export class IntentHttpServer { const routeExplorer = new RouteExplorer(ds, ms, mr); const routes = await routeExplorer .useGlobalGuards(globalGuards) - .useGlobalMiddlewares(globalMiddlewares) - .useRouteMiddlewares(routeMiddlewares) - .useExcludeMiddlewareRoutes(excludedMiddlewares) .exploreFullRoutes(errorHandler); const serverOptions = config.get('http.server'); @@ -96,8 +93,11 @@ export class IntentHttpServer { const customServer = new HyperServer(); const server = await customServer .useGlobalMiddlewares(globalMiddlewares) + .useRouteMiddlewares(routeMiddlewares) .build(routes, serverOptions); + await this.container.boot(app); + await this.kernel.boot(server); server.set_error_handler((hReq: any, hRes: HyperResponse, error: Error) => { const res = new Response(); const httpContext = new HttpExecutionContext(hReq, new Response()); @@ -111,8 +111,6 @@ export class IntentHttpServer { const port = config.get('app.port'); const hostname = config.get('app.hostname'); - await this.container.boot(app); - await server.listen(port, hostname || '0.0.0.0'); for (const signal of signals) { diff --git a/packages/core/lib/rest/http-server/contexts/execution-context.ts b/packages/core/lib/rest/http-server/contexts/execution-context.ts index 5fd367d..59a6ea4 100644 --- a/packages/core/lib/rest/http-server/contexts/execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/execution-context.ts @@ -1,12 +1,17 @@ import { GenericClass } from '../../../interfaces'; +import { Reflector } from '../../../reflections'; import { HttpExecutionContext } from './http-execution-context'; export class ExecutionContext { + private reflectorClass: Reflector; + constructor( private protocolContext: HttpExecutionContext, private readonly handlerClass: GenericClass, private readonly handlerMethod: Function, - ) {} + ) { + this.reflectorClass = new Reflector(this.handlerClass, this.handlerMethod); + } getClass(): GenericClass { return this.handlerClass; @@ -19,4 +24,8 @@ export class ExecutionContext { switchToHttp(): HttpExecutionContext { return this.protocolContext; } + + getReflector(): Reflector { + return this.reflectorClass; + } } diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index 7d47245..7b1a1f8 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -1,6 +1,6 @@ import { Response } from '../response'; import { RouteArgType, RouteParamtypes } from '../param-decorators'; -import { Request } from '../request/interfaces'; +import { Request } from '@intentjs/hyper-express'; export class HttpExecutionContext { constructor( @@ -49,12 +49,6 @@ export class HttpExecutionContext { return { ...this.request.params }; - case RouteParamtypes.SESSION: - - case RouteParamtypes.FILE: - - case RouteParamtypes.FILES: - case RouteParamtypes.IP: return this.request.ip; diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index fa594e7..b5603a1 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -9,4 +9,3 @@ export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; export * from './interfaces'; -export * from './request'; diff --git a/packages/core/lib/rest/http-server/request/extension.ts b/packages/core/lib/rest/http-server/request/extension.ts deleted file mode 100644 index e978499..0000000 --- a/packages/core/lib/rest/http-server/request/extension.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; -import { isEmpty } from '../../../utils'; -import { Type } from '../../../interfaces'; -import { EXTENSTION_TO_MIME } from '../../../utils/extension-to-mime'; -import { Request } from './interfaces'; -import { Validator } from '../../../validator/validator'; - -export const INTENT_REQUEST_EXTENSIONS = { - $dto: null, - $user: null, - uploadedFiles: new Map(), - - setDto(dto: any): void { - this.$dto = dto; - }, - - dto(): any { - return this.$dto; - }, - - all(): Record { - return { - ...(this.query_parameters || {}), - ...(this.path_parameters || {}), - ...(this.body || {}), - }; - }, - - input(name: string, defaultValue?: T): T { - const payload = this.all(); - return name in payload ? payload[name] : defaultValue; - }, - - string(name: string): string { - const value = this.input(name); - return value && value.toString(); - }, - - number(name: string): number { - const value = this.input(name); - return +value; - }, - - boolean(name: string): boolean { - const payload = this.all(); - const val = payload[name] as string; - return [true, 'yes', 'on', '1', 1, 'true'].includes(val?.toLowerCase()); - }, - - hasHeader(name: string): boolean { - return name in this.headers; - }, - - bearerToken(): string { - const authHeader = this.headers['authorization']; - const asArray = authHeader?.split(' '); - if (!isEmpty(asArray)) return asArray[1]; - return undefined; - }, - - httpHost(): string { - return this.protocol; - }, - - isHttp(): boolean { - return this.httpHost() === 'http'; - }, - - isHttps(): boolean { - return this.httpHost() === 'https'; - }, - - fullUrl(): string { - return this.url; - }, - - isMethod(method: string): boolean { - return this.method.toLowerCase() === method.toLowerCase(); - }, - - contentType(): string { - return this.headers['content-type']; - }, - - getAcceptableContentTypes(): string { - return this.headers['accept']; - }, - - accepts(): string[] { - return (this.headers['accept'] || '').split(','); - }, - - expectsJson(): boolean { - return this.accepts().includes(EXTENSTION_TO_MIME['json']); - }, - - async validate(schema: Type): Promise { - const payload = this.all(); - const validator = Validator.compareWith(schema); - const dto = await validator - .addMeta({ ...payload, _headers: { ...this.headers } }) - .validate({ ...payload }); - this.setDto(dto); - return true; - }, - - setUser(user: any): void { - this.$user = user; - }, - - user(): T { - return this.$user as T; - }, - - only(...keys: string[]): Record { - const payload = this.all(); - return Object.fromEntries( - keys.filter(key => key in payload).map(key => [key, payload[key]]), - ); - }, - - except(...keys: string[]): Record { - const payload = this.all(); - return Object.fromEntries( - Object.entries(payload).filter(([key]) => !keys.includes(key)), - ); - }, - - isPath(pathPattern: string): boolean { - return this.path === pathPattern; - }, - - has(...keys: string[]): boolean { - const payload = this.all(); - return keys.every(key => key in payload); - }, - - hasAny(...keys: string[]): boolean { - const payload = this.all(); - return keys.some(key => key in payload); - }, - - missing(...keys: string[]): boolean { - const payload = this.all(); - return keys.every(key => !(key in payload)); - }, - - hasHeaders(...keys: string[]): boolean { - return keys.every(key => key in this.headers); - }, - - hasIncludes(): boolean { - return Boolean(this.includes()); - }, - - includes(): string { - return this.string('include'); - }, - - files(keys: string): Record { - if (!this.body) return {}; - // return Object.fromEntries( - // Object.entries(this.body).filter( - // ([key]) => - // key === keys && - // (this.body[key] instanceof UploadedFile || - // Array.isArray(this.body[key])), - // ), - // ); - }, - - async file( - key: string, - ): Promise { - const fileAtKey = this.body?.[key]; - if (!fileAtKey) return undefined as T; - - if (Array.isArray(fileAtKey)) { - const values = fileAtKey.filter(file => file instanceof UploadedFile); - return values as T; - } - - if (!(fileAtKey instanceof UploadedFile)) return undefined as T; - - return fileAtKey as T; - }, -} as Request; diff --git a/packages/core/lib/rest/http-server/request/index.ts b/packages/core/lib/rest/http-server/request/index.ts deleted file mode 100644 index c44f94c..0000000 --- a/packages/core/lib/rest/http-server/request/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './extension'; -export * from './interfaces'; -export * from './middleware'; -export * from './multipart-handler'; diff --git a/packages/core/lib/rest/http-server/request/interfaces.ts b/packages/core/lib/rest/http-server/request/interfaces.ts deleted file mode 100644 index 623e42c..0000000 --- a/packages/core/lib/rest/http-server/request/interfaces.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Request as HyperRequest } from 'hyper-express'; -import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; -import { Type } from '../../../interfaces'; - -export interface Request extends HyperRequest { - $dto: any; - $user: any; - uploadedFiles: Map; - setDto(dto: any): void; - dto(): any; - all(): Record; - input(name: string, defaultValue?: T): T; - string(name: string): string; - number(name: string): number; - boolean(name: string): boolean; - hasHeader(name: string): boolean; - bearerToken(): string; - httpHost(): string; - isHttp(): boolean; - isHttps(): boolean; - fullUrl(): string; - isMethod(method: string): boolean; - contentType(): string; - getAcceptableContentTypes(): string; - // accepts(): string[]; - expectsJson(): boolean; - validate(schema: Type): Promise; - setUser(user: any): void; - user(): T; - only(...keys: string[]): Record; - except(...keys: string[]): Record; - isPath(pathPattern: string): boolean; - has(...keys: string[]): boolean; - hasAny(...keys: string[]): boolean; - missing(...keys: string[]): boolean; - hasHeaders(...keys: string[]): boolean; - hasIncludes(): boolean; - includes(): string; - files(keys: string): Record; - file(key: string): Promise; -} diff --git a/packages/core/lib/rest/http-server/request/middleware.ts b/packages/core/lib/rest/http-server/request/middleware.ts deleted file mode 100644 index fd53a9b..0000000 --- a/packages/core/lib/rest/http-server/request/middleware.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Request as HyperRequest } from 'hyper-express'; -import { processMultipartData } from './multipart-handler'; -import { INTENT_REQUEST_EXTENSIONS } from './extension'; -import { ConfigService } from '../../../config/service'; - -export const requestMiddleware = async ( - req: HyperRequest, - res: any, - next: () => void, -) => { - Object.assign(req, INTENT_REQUEST_EXTENSIONS); - - const enabledParsers = (ConfigService.get('http.parsers') as string[]) || []; - const contentType = req.headers['content-type'] || ''; - - try { - if (enabledParsers.includes('json') && contentType === 'application/json') { - req.body = await req.json(); - } else if ( - enabledParsers.includes('urlencoded') && - contentType === 'application/x-www-form-urlencoded' - ) { - req.body = await req.urlencoded(); - } else if ( - enabledParsers.includes('formdata') && - contentType.includes('multipart/form-data') - ) { - req.body = await processMultipartData(req); - } else if ( - enabledParsers.includes('plain') && - contentType === 'text/plain' - ) { - req.body = await req.text(); - } else if ( - (enabledParsers.includes('html') && contentType === 'text/html') || - (enabledParsers.includes('xml') && contentType === 'application/xml') - ) { - req.body = (await req.buffer()).toString(); - } else { - req.body = await req.buffer(); - } - } catch (error) { - console.error('Request parsing error:', error); - } -}; diff --git a/packages/core/lib/rest/http-server/request/multipart-handler.ts b/packages/core/lib/rest/http-server/request/multipart-handler.ts deleted file mode 100644 index 5b986c5..0000000 --- a/packages/core/lib/rest/http-server/request/multipart-handler.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Request as HyperRequest } from 'hyper-express'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import { ulid } from 'ulid'; -import { UploadedFile } from '../../../storage/file-handlers/uploaded-file'; -import { Str } from '../../../utils/string'; - -export const processMultipartData = async ( - req: HyperRequest, -): Promise> => { - const fields = new Map(); - const tempDirectory = tmpdir(); - const generateTempFilename = (filename: string) => `${ulid()}-${filename}`; - - try { - await req.multipart(async field => { - const isArray = Str.is(field.name, '*[*]'); - const strippedFieldName = Str.before(field.name, '['); - const existingFieldValue = fields.get(strippedFieldName); - - if (isArray && !existingFieldValue) { - fields.set(strippedFieldName, []); - } - - if (field.file) { - const tempFileName = generateTempFilename(field.file.name); - const tempFilePath = join(tempDirectory, tempFileName); - let fileSize = 0; - - field.file.stream.on('data', chunk => { - fileSize += chunk.length; - }); - - await field.write(tempFilePath); - - const uploadedFile = new UploadedFile( - field.file.name, - fileSize, - field.mime_type, - tempFileName, - tempFilePath, - ); - - if (Array.isArray(existingFieldValue)) { - fields.set( - strippedFieldName, - existingFieldValue.concat(uploadedFile), - ); - } else { - fields.set(strippedFieldName, uploadedFile); - } - } else { - if (Array.isArray(existingFieldValue)) { - fields.set(strippedFieldName, existingFieldValue.concat(field.value)); - } else { - fields.set(strippedFieldName, field.value); - } - } - }); - } catch (e) { - console.error(e); - } - - return fields; -}; diff --git a/packages/core/lib/rest/http-server/response.ts b/packages/core/lib/rest/http-server/response.ts index d88e9b7..077f2cf 100644 --- a/packages/core/lib/rest/http-server/response.ts +++ b/packages/core/lib/rest/http-server/response.ts @@ -1,4 +1,7 @@ -import { Response as HResponse, Request as HRequest } from 'hyper-express'; +import { + Response as HResponse, + Request as HRequest, +} from '@intentjs/hyper-express'; import { StreamableFile } from './streamable-file'; import { HttpStatus } from './status-codes'; import { @@ -71,9 +74,9 @@ export class Response { return this; } - redirect(): Response { + redirect(url: string): Response { this.statusCode = HttpStatus.FOUND; - this.responseHeaders['']; + this.responseHeaders.set('location', url); return this; } diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index f4cde4f..feb2d18 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -1,7 +1,11 @@ import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core'; import { join } from 'path'; import { HttpRoute } from './interfaces'; -import { Response as HResponse, MiddlewareNext } from 'hyper-express'; +import { + Request, + Response as HResponse, + MiddlewareNext, +} from '@intentjs/hyper-express'; import { HttpExecutionContext } from './contexts/http-execution-context'; import { HttpRouteHandler } from './http-handler'; import { Response } from './response'; @@ -15,17 +19,12 @@ import { ROUTE_ARGS, } from './constants'; import { RouteArgType } from './param-decorators'; -import { Request } from './request/interfaces'; import { IntentGuard } from '../foundation/guards/base-guard'; import { IntentMiddleware } from '../foundation/middlewares/middleware'; import { IntentExceptionFilter } from '../../exceptions/base-exception-handler'; export class RouteExplorer { - guards: Type[] = []; - - globalMiddlewares: IntentMiddleware[] = []; - routeMiddlewares: Map; - excludedRouteMiddlewares: Map; + globalGuards: Type[] = []; constructor( private discoveryService: DiscoveryService, @@ -132,7 +131,7 @@ export class RouteExplorer { ] as Type[]; const composedGuards = []; - for (const globalGuard of this.guards) { + for (const globalGuard of this.globalGuards) { composedGuards.push(await this.moduleRef.create(globalGuard)); } @@ -140,8 +139,6 @@ export class RouteExplorer { composedGuards.push(await this.moduleRef.create(guardType)); } - const middlewares = []; - const routeArgs = Reflect.getMetadata(ROUTE_ARGS, instance.constructor, key) || ([] as RouteArgType[]); @@ -177,26 +174,7 @@ export class RouteExplorer { } useGlobalGuards(guards: Type[]): RouteExplorer { - this.guards.push(...guards); - return this; - } - - useGlobalMiddlewares(globalMiddlewares: IntentMiddleware[]): RouteExplorer { - this.globalMiddlewares = globalMiddlewares; - return this; - } - - useExcludeMiddlewareRoutes( - routeMiddlewares: Map, - ): RouteExplorer { - this.excludedRouteMiddlewares = routeMiddlewares; - return this; - } - - useRouteMiddlewares( - routeMiddlewares: Map, - ): RouteExplorer { - this.routeMiddlewares = routeMiddlewares; + this.globalGuards.push(...guards); return this; } } diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index 50c73b1..c18a041 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -1,6 +1,5 @@ -import HyperExpress from 'hyper-express'; +import HyperExpress, { MiddlewareHandler } from '@intentjs/hyper-express'; import { HttpMethods, HttpRoute } from './interfaces'; -import { requestMiddleware } from './request/middleware'; import { IntentMiddleware } from '../foundation/middlewares/middleware'; export class HyperServer { @@ -16,7 +15,10 @@ export class HyperServer { config: HyperExpress.ServerConstructorOptions, ): Promise { this.hyper = new HyperExpress.Server(config || {}); - this.hyper.use(requestMiddleware); + + this.hyper.use(async (req, res) => { + await req.processBody(); + }); for (const middleware of this.globalMiddlewares) { this.hyper.use(middleware.use.bind(middleware)); @@ -24,47 +26,63 @@ export class HyperServer { for (const route of routes) { const { path, httpHandler } = route; + + const middlewares = this.composeMiddlewares(path, route.method); switch (route.method) { case HttpMethods.GET: - this.hyper.get(path, httpHandler); + this.hyper.get(path, ...middlewares, httpHandler); break; case HttpMethods.POST: - this.hyper.post(path, httpHandler); + this.hyper.post(path, ...middlewares, httpHandler); break; case HttpMethods.DELETE: - this.hyper.delete(path, httpHandler); + this.hyper.delete(path, ...middlewares, httpHandler); break; case HttpMethods.HEAD: - this.hyper.head(path, httpHandler); + this.hyper.head(path, ...middlewares, httpHandler); break; case HttpMethods.PUT: - this.hyper.put(path, httpHandler); + this.hyper.put(path, ...middlewares, httpHandler); break; case HttpMethods.PATCH: - this.hyper.patch(path, httpHandler); + this.hyper.patch(path, ...middlewares, httpHandler); break; case HttpMethods.OPTIONS: - this.hyper.options(path, httpHandler); + this.hyper.options(path, ...middlewares, httpHandler); break; case HttpMethods.ANY: - this.hyper.any(path, httpHandler); + this.hyper.any(path, ...middlewares, httpHandler); break; } } - // this.hyper.set_not_found_handler((req: HyperExpress.Request, res: HyperExpress.Response) => { - // return res.status(HttpStatus.NOT_FOUND).type('text').send() - // }) return this.hyper; } + composeMiddlewares(path: string, method: string): MiddlewareHandler[] { + const methodBasedRouteKey = `${method}:${path}`; + const routeKey = `*:${path}`; + + const middlewareInstances = [ + ...(this.routeMiddlewares.get(methodBasedRouteKey) || []), + ...(this.routeMiddlewares.get(routeKey) || []), + ]; + + const middlewares = []; + for (const middlewareInstance of middlewareInstances) { + middlewares.push(middlewareInstance.use.bind(middlewareInstance)); + } + + return middlewares; + } + useGlobalMiddlewares(globalMiddlewares: IntentMiddleware[]): HyperServer { this.globalMiddlewares = globalMiddlewares; return this; diff --git a/packages/core/lib/rest/middlewares/cors.ts b/packages/core/lib/rest/middlewares/cors.ts index 8905176..ffe516b 100644 --- a/packages/core/lib/rest/middlewares/cors.ts +++ b/packages/core/lib/rest/middlewares/cors.ts @@ -2,7 +2,7 @@ import cors, { CorsOptions } from 'cors'; import { IntentMiddleware, MiddlewareNext } from '../foundation'; import { ConfigService } from '../../config'; import { Injectable } from '@nestjs/common'; -import { Request, Response } from 'hyper-express'; +import { Request, Response } from '@intentjs/hyper-express'; @Injectable() export class CorsMiddleware extends IntentMiddleware { @@ -11,12 +11,8 @@ export class CorsMiddleware extends IntentMiddleware { } async use(req: Request, res: Response): Promise { - const corsMiddleware = cors({ - origin: '*', // or specify allowed origins - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], - credentials: true, - }); + const corsOptions = this.config.get('http.cors') || ({} as CorsOptions); + const corsMiddleware = cors(corsOptions); await new Promise(resolve => { corsMiddleware(req, res, () => { resolve(1); diff --git a/packages/core/lib/rest/middlewares/helmet.ts b/packages/core/lib/rest/middlewares/helmet.ts index 6e15a32..3218437 100644 --- a/packages/core/lib/rest/middlewares/helmet.ts +++ b/packages/core/lib/rest/middlewares/helmet.ts @@ -2,7 +2,7 @@ import helmet from 'helmet'; import { Injectable } from '../../foundation'; import { IntentMiddleware, MiddlewareNext } from '../foundation'; import { ConfigService } from '../../config'; -import { Request, Response } from 'hyper-express'; +import { Request, Response } from '@intentjs/hyper-express'; @Injectable() export class HelmetMiddleware extends IntentMiddleware { diff --git a/packages/core/lib/serviceProvider.ts b/packages/core/lib/serviceProvider.ts index 7f79bce..50c72a6 100644 --- a/packages/core/lib/serviceProvider.ts +++ b/packages/core/lib/serviceProvider.ts @@ -1,7 +1,5 @@ import { DiscoveryModule } from '@nestjs/core'; import { CacheService } from './cache'; -// import { CodegenCommand } from './codegen/command'; -// import { CodegenService } from './codegen/service'; import { ViewConfigCommand } from './config/command'; import { ListCommands } from './console'; import { ObjectionService } from './database'; diff --git a/packages/core/lib/transformers/interfaces.ts b/packages/core/lib/transformers/interfaces.ts index 7aa1948..f67697f 100644 --- a/packages/core/lib/transformers/interfaces.ts +++ b/packages/core/lib/transformers/interfaces.ts @@ -1,4 +1,4 @@ -import { Request } from '../rest/http-server/request'; +import { Request } from '@intentjs/hyper-express'; export interface TransformableContextOptions { req?: Request; diff --git a/packages/core/lib/validator/index.ts b/packages/core/lib/validator/index.ts index 3b6d5cd..1c8e928 100644 --- a/packages/core/lib/validator/index.ts +++ b/packages/core/lib/validator/index.ts @@ -1,12 +1,7 @@ -import { - ExecutionContext, - SetMetadata, - UseGuards, - applyDecorators, - createParamDecorator, -} from '@nestjs/common'; -import { IntentValidationGuard } from './validationGuard'; -import { Request } from '../rest'; +import { applyDecorators } from '../reflections/apply-decorators'; +import { SetMetadata } from '../reflections/set-metadata'; +import { UseGuard } from '../rest'; +import { IntentValidationGuard } from './validation-guard'; export * from './validator'; export * from './decorators'; @@ -14,17 +9,6 @@ export * from './decorators'; export function Validate(DTO: any) { return applyDecorators( SetMetadata('dtoSchema', DTO), - UseGuards(IntentValidationGuard), + UseGuard(IntentValidationGuard), ); } - -export const Dto = createParamDecorator( - (data: string, ctx: ExecutionContext) => { - const contextType = ctx['contextType']; - if (contextType === 'ws') { - return ctx.switchToWs().getClient()._dto; - } - const request = ctx.switchToHttp().getRequest() as Request; - return request.dto(); - }, -); diff --git a/packages/core/lib/validator/validation-guard.ts b/packages/core/lib/validator/validation-guard.ts new file mode 100644 index 0000000..c9862fc --- /dev/null +++ b/packages/core/lib/validator/validation-guard.ts @@ -0,0 +1,20 @@ +import { Request } from '@intentjs/hyper-express'; +import { ExecutionContext, IntentGuard } from '../rest'; +import { Reflector } from '../reflections'; +import { Injectable } from '../foundation/decorators'; + +@Injectable() +export class IntentValidationGuard extends IntentGuard { + constructor() { + super(); + } + + async guard(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest() as Request; + const reflector = context.getReflector(); + const schema = reflector.getFromMethod('dtoSchema'); + console.log(schema); + await request.validate(schema); + return true; + } +} diff --git a/packages/core/lib/validator/validationGuard.ts b/packages/core/lib/validator/validationGuard.ts deleted file mode 100644 index bc663b1..0000000 --- a/packages/core/lib/validator/validationGuard.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { Request } from '../rest'; - -@Injectable() -export class IntentValidationGuard implements CanActivate { - constructor(private reflector: Reflector) {} - - async canActivate(context: ExecutionContext): Promise { - const request = context.switchToHttp().getRequest() as Request; - const schema = this.reflector.get('dtoSchema', context.getHandler()); - await request.validate(schema); - return true; - } -} diff --git a/packages/core/package.json b/packages/core/package.json index 9913128..f86b348 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -64,6 +64,7 @@ "typescript": "^5.5.2" }, "dependencies": { + "@intentjs/hyper-express": "^6.17.2-beta-2", "@nestjs/common": "^10.4.8", "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", diff --git a/packages/hyper-express/.gitignore b/packages/hyper-express/.gitignore new file mode 100644 index 0000000..5ef0e8d --- /dev/null +++ b/packages/hyper-express/.gitignore @@ -0,0 +1,4 @@ +.npmrc +.vscode/ +node_modules/ +tests/content/large-files/ \ No newline at end of file diff --git a/packages/hyper-express/.gitmodules b/packages/hyper-express/.gitmodules new file mode 100644 index 0000000..a10cfa0 --- /dev/null +++ b/packages/hyper-express/.gitmodules @@ -0,0 +1,9 @@ +[submodule "middlewares/hyper-express-session"] + path = middlewares/hyper-express-session + url = git@github.com:kartikk221/hyper-express-session.git +[submodule "middlewares/hyper-express-body-parser"] + path = middlewares/hyper-express-body-parser + url = git@github.com:kartikk221/hyper-express-body-parser.git +[submodule "middlewares/hyper-express-serve-static"] + path = middlewares/hyper-express-serve-static + url = git@github.com:kartikk221/hyper-express-serve-static.git diff --git a/packages/hyper-express/.npmignore b/packages/hyper-express/.npmignore new file mode 100644 index 0000000..722b67d --- /dev/null +++ b/packages/hyper-express/.npmignore @@ -0,0 +1,5 @@ +tests/ +benchmarks/ +middlewares/ +docs/ +.npmrc \ No newline at end of file diff --git a/packages/hyper-express/.prettierrc b/packages/hyper-express/.prettierrc new file mode 100644 index 0000000..b4618d4 --- /dev/null +++ b/packages/hyper-express/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "tabWidth": 4, + "printWidth": 120, + "singleQuote": true, + "bracketSpacing": true +} diff --git a/packages/hyper-express/LICENSE b/packages/hyper-express/LICENSE new file mode 100644 index 0000000..421a191 --- /dev/null +++ b/packages/hyper-express/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2021-2021 Kartik Kumar <@kartikk221> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/hyper-express/README.md b/packages/hyper-express/README.md new file mode 100644 index 0000000..3263803 --- /dev/null +++ b/packages/hyper-express/README.md @@ -0,0 +1,60 @@ +# HyperExpress: High Performance Node.js Webserver +#### Powered by [`uWebSockets.js`](https://github.com/uNetworking/uWebSockets.js/) + +
+ +[![NPM version](https://img.shields.io/npm/v/hyper-express.svg?style=flat)](https://www.npmjs.com/package/hyper-express) +[![NPM downloads](https://img.shields.io/npm/dm/hyper-express.svg?style=flat)](https://www.npmjs.com/package/hyper-express) +[![GitHub issues](https://img.shields.io/github/issues/kartikk221/hyper-express)](https://github.com/kartikk221/hyper-express/issues) +[![GitHub stars](https://img.shields.io/github/stars/kartikk221/hyper-express)](https://github.com/kartikk221/hyper-express/stargazers) +[![GitHub license](https://img.shields.io/github/license/kartikk221/hyper-express)](https://github.com/kartikk221/hyper-express/blob/master/LICENSE) + +
+ +## Motivation +HyperExpress aims to be a simple yet performant HTTP & Websocket Server. Combined with the power of uWebsockets.js, a Node.js binding of uSockets written in C++, HyperExpress allows developers to unlock higher throughput for their web applications with their existing hardware. This can allow many web applications to become much more performant on optimized data serving endpoints without having to scale hardware. + +Some of the prominent highlights are: +- Simplified HTTP & Websocket API +- Server-Sent Events Support +- Multipart File Uploading Support +- Modular Routers & Middlewares Support +- Multiple Host/Domain Support Over SSL +- Limited Express.js API Compatibility Through Shared Methods/Properties + +See [`> [Benchmarks]`](https://web-frameworks-benchmark.netlify.app/result?l=javascript) for **performance metrics** against other webservers in real world deployments. + +## Documentation +HyperExpress **supports** the latest three LTS (Long-term Support) Node.js versions only and can be installed using Node Package Manager (`npm`). +``` +npm i hyper-express +``` + +- See [`> [Examples & Snippets]`](./docs/Examples.md) for small and **easy-to-use snippets** with HyperExpress. +- See [`> [Server]`](./docs/Server.md) for creating a webserver and working with the **Server** component. +- See [`> [Router]`](./docs/Router.md) for working with the modular **Router** component. +- See [`> [Request]`](./docs/Request.md) for working with the **Request** component made available through handlers. +- See [`> [Response]`](./docs/Response.md) for working with the **Response** component made available through handlers. +- See [`> [Websocket]`](./docs/Websocket.md) for working with **Websockets** in HyperExpress. +- See [`> [Middlewares]`](./docs/Middlewares.md) for working with global and route-specific **Middlewares** in HyperExpress. +- See [`> [SSEventStream]`](./docs/SSEventStream.md) for working with **Server-Sent Events** based streaming in HyperExpress. +- See [`> [MultipartField]`](./docs/MultipartField.md) for working with multipart requests and **File Uploading** in HyperExpress. +- See [`> [SessionEngine]`](https://github.com/kartikk221/hyper-express-session) for working with cookie based web **Sessions** in HyperExpress. +- See [`> [LiveDirectory]`](./docs/LiveDirectory.md) for implementing **static file/asset** serving functionality into HyperExpress. +- See [`> [HostManager]`](./docs/HostManager.md) for supporting requests over **muliple hostnames** in HyperExpress. + +## Encountering Problems? +- HyperExpress is mostly compatible with `Express` but not **100%** therefore you may encounter some middlewares not working out of the box. In this scenario, you must either write your own polyfill or omit the middleware to continue. +- The uWebsockets.js version header is disabled by default. You may opt-out of this behavior by setting an environment variable called `KEEP_UWS_HEADER` to a truthy value such as `1` or `true`. +- Still having problems? Open an [`> [Issue]`](https://github.com/kartikk221/hyper-express/issues) with details about what led up to the problem including error traces, route information etc etc. + +## Testing Changes +To run HyperExpress functionality tests locally on your machine, you must follow the steps below. +1. Clone the HyperExpress repository to your machine. +2. Initialize and pull any submodule(s) which are used throughout the tests. +3. Run `npm install` in the root directory. +4. Run `npm install` in the `/tests` directory. +5. Run `npm test` to run all tests with your local changes. + +## License +[MIT](./LICENSE) diff --git a/packages/hyper-express/benchmarks/benchmark.sh b/packages/hyper-express/benchmarks/benchmark.sh new file mode 100644 index 0000000..c91545e --- /dev/null +++ b/packages/hyper-express/benchmarks/benchmark.sh @@ -0,0 +1,26 @@ +# Define Execution Variables +HOST="localhost"; +PORT_START=3000; +PORT_END=3004; +NUM_OF_CONNECTIONS=2500; +DURATION_SECONDS=30; +PIPELINE_FACTOR=4; + +# Ensure "autocannon" is not installed, install it with NPM +if ! [ -x "$(command -v autocannon)" ]; then + echo 'Error: autocannon is not installed. Attempting to install with NPM.'; + npm install autocannon -g; +fi + +# Iterate a for loop from PORT_START to PORT_END +for ((PORT=$PORT_START; PORT<=$PORT_END; PORT++)) +do + # Execute the benchmark + echo "Benchmarking Webserver @ Port: $HOST:$PORT"; + + # Use the autocannon utility to benchmark + autocannon -c $NUM_OF_CONNECTIONS -d $DURATION_SECONDS -p $PIPELINE_FACTOR http://localhost:$PORT/; + + # Append a visual line to separate results + echo "----------------------------------------------------"; +done \ No newline at end of file diff --git a/packages/hyper-express/benchmarks/configuration.json b/packages/hyper-express/benchmarks/configuration.json new file mode 100644 index 0000000..0b17892 --- /dev/null +++ b/packages/hyper-express/benchmarks/configuration.json @@ -0,0 +1,5 @@ +{ + "hostname": "localhost", + "port_start": 3000, + "multi_core": false +} diff --git a/packages/hyper-express/benchmarks/index.js b/packages/hyper-express/benchmarks/index.js new file mode 100644 index 0000000..e884639 --- /dev/null +++ b/packages/hyper-express/benchmarks/index.js @@ -0,0 +1,102 @@ +import os from 'os'; +import fs from 'fs'; +import cluster from 'cluster'; +import fetch from 'node-fetch'; +import uWebsocketsJS from 'uWebSockets.js'; +import { log } from './utils.js'; + +// Load the server instances to be benchmarked +import uWebsockets from './setup/uwebsockets.js'; +import NanoExpress from './setup/nanoexpress.js'; +import HyperExpress from './setup/hyperexpress.js'; +import Fastify from './setup/fastify.js'; +import Express from './setup/express.js'; + +// Load the configuration from disk +const configuration = JSON.parse(fs.readFileSync('./configuration.json', 'utf8')); + +// Handle spawning of worker processes from the master process +const numCPUs = configuration.multi_core ? os.cpus().length : 1; +if (numCPUs > 1 && (cluster.isMaster || cluster.isPrimary)) { + for (let i = 0; i < numCPUs; i++) { + cluster.fork(); + } + log(`Forked ${numCPUs} workers for benchmarking on ${os.platform()}`); +} + +// Handle spawning of webservers for each worker process +let uws_socket; +if (numCPUs <= 1 || cluster.isWorker) { + // Perform startup tasks + log('Initializing Webservers...'); + (async () => { + try { + // Remember the initial port for HTTP request checks after all servers are started + const initial_port = configuration.port_start; + + // Initialize the uWebsockets server instance + uws_socket = await new Promise((resolve) => + uWebsockets.listen(configuration.hostname, configuration.port_start, resolve) + ); + log(`uWebsockets.js server listening on port ${configuration.port_start}`); + + // Initialize the NanoExpress server instance + configuration.port_start++; + await HyperExpress.listen(configuration.port_start, configuration.hostname); + log(`HyperExpress server listening on port ${configuration.port_start}`); + + // Initialize the NanoExpress server instance + configuration.port_start++; + await NanoExpress.listen(configuration.port_start); + log(`NanoExpress server listening on port ${configuration.port_start}`); + + // Initialize the Fastify server instance + configuration.port_start++; + Fastify.listen({ port: configuration.port_start, host: configuration.hostname }); + log(`Fastify server listening on port ${configuration.port_start}`); + + // Initialize the Express server instance + configuration.port_start++; + await new Promise((resolve) => Express.listen(configuration.port_start, configuration.hostname, resolve)); + log(`Express.js server listening on port ${configuration.port_start}`); + + // Make HTTP GET requests to all used ports to test the servers + log('Testing each webserver with a HTTP GET request...'); + for (let i = initial_port; i <= configuration.port_start; i++) { + const response = await fetch(`http://localhost:${i}/`); + if (response.status !== 200) + throw new Error(`HTTP request to port ${i} failed with status ${response.status}`); + log(`GET HTTP -> Port ${i} -> Status ${response.status} -> ${response.headers.get('content-type')}`); + } + + log( + 'All webservers are ready to receive request between ports ' + + initial_port + + ' - ' + + configuration.port_start + + '!', + false + ); + } catch (error) { + console.log(error); + process.exit(); + } + })(); +} + +['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'SIGTERM'].forEach((type) => + process.once(type, () => { + // Close all the webserver instances + try { + if (uws_socket) uWebsocketsJS.us_listen_socket_close(uws_socket); + NanoExpress.close(); + HyperExpress.close(); + Fastify.close(); + } catch (error) { + console.log(error); + } + + // Exit the process + process.exit(); + }) +); diff --git a/packages/hyper-express/benchmarks/package-lock.json b/packages/hyper-express/benchmarks/package-lock.json new file mode 100644 index 0000000..d0391a8 --- /dev/null +++ b/packages/hyper-express/benchmarks/package-lock.json @@ -0,0 +1,1644 @@ +{ + "name": "benchmarks", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "benchmarks", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "express": "^5.0.1", + "fastify": "^5.0.0", + "nanoexpress": "^6.4.4", + "node-fetch": "^3.3.2", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0" + } + }, + "node_modules/@dalisoft/args": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@dalisoft/args/-/args-0.1.1.tgz", + "integrity": "sha512-PwmLEhnTyK6+AUwD0oqS57nLs1vpB17vEDlMc1i27zH8mrtFJmBmYfQv4FszoUHDHv3PtFk8M0X9yWxry2TSwA==", + "license": "MIT" + }, + "node_modules/@dalisoft/events": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@dalisoft/events/-/events-0.2.0.tgz", + "integrity": "sha512-2NS/0vS9eL8ZxhgCVZQgPS4uoqbcSk9FN3G8Eqakcej7wOpH72z1kmDEDGI5T+MOp9IueKT2qI0XUdXhKeK9GA==", + "license": "MIT", + "dependencies": { + "@dalisoft/args": "^0.1.1" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.1.tgz", + "integrity": "sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==", + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.0.0.tgz", + "integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.1.tgz", + "integrity": "sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.0.0.tgz", + "integrity": "sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.1.tgz", + "integrity": "sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.0.tgz", + "integrity": "sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.1", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.3.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fast-query-parse": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-query-parse/-/fast-query-parse-3.1.0.tgz", + "integrity": "sha512-ThdC8A4Wi8naYwS1vUERRC3aNhAP5s9U0suMS7afVfQAYFjV/xVToQdzn0bCk4uQgn0no42X46sBI7DAnnCswQ==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.0.0.tgz", + "integrity": "sha512-Qe4dU+zGOzg7vXjw4EvcuyIbNnMwTmcuOhlOrOJsgwzvjEZmsM/IeHulgJk+r46STjdJS/ZJbxO8N70ODXDMEQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.0.0", + "process-warning": "^4.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.1", + "secure-json-parse": "^2.7.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify/node_modules/process-warning": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/finalhandler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-my-way": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.1.0.tgz", + "integrity": "sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.1.0.tgz", + "integrity": "sha512-+NFuhlOGoEwxeQfJ/pobkVFxcnKyDtiX847hLjuB/IzBxIl3q4VJeFI8uRCgb3AlTWL1lgOr+u5+8QdUcr33ng==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/nanoexpress": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/nanoexpress/-/nanoexpress-6.4.4.tgz", + "integrity": "sha512-yyGHOl9koJf7C/a6Ojs6boKaZyrTneCG76vL+/SqDC1uRn9f/h6LL84PbVT22rX1SVQ5NB8v7mfoRrjbUPWpyQ==", + "license": "Apache-2.0", + "dependencies": { + "@dalisoft/events": "^0.2.0", + "ajv": "^8.17.1", + "cookie": "^0.6.0", + "fast-query-parse": "^3.0.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.47.0" + }, + "engines": { + "node": ">=18.20.4" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/dalisoft" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/pino": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.2.0.tgz", + "integrity": "sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">= 12.13.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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "license": "MIT", + "dependencies": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-4.0.0.tgz", + "integrity": "sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==", + "license": "MIT", + "dependencies": { + "ret": "~0.5.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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uWebSockets.js": { + "version": "20.44.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#8fa05571bf6ea95be8966ad313d9d39453e381ae", + "license": "Apache-2.0" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/packages/hyper-express/benchmarks/package.json b/packages/hyper-express/benchmarks/package.json new file mode 100644 index 0000000..3e8f249 --- /dev/null +++ b/packages/hyper-express/benchmarks/package.json @@ -0,0 +1,20 @@ +{ + "name": "benchmarks", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "express": "^5.0.1", + "fastify": "^5.0.0", + "nanoexpress": "^6.4.4", + "node-fetch": "^3.3.2", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0" + } +} \ No newline at end of file diff --git a/packages/hyper-express/benchmarks/scenarios/simple_html_page.js b/packages/hyper-express/benchmarks/scenarios/simple_html_page.js new file mode 100644 index 0000000..5e20923 --- /dev/null +++ b/packages/hyper-express/benchmarks/scenarios/simple_html_page.js @@ -0,0 +1,23 @@ +export function get_simple_html_page({ server_name }) { + const date = new Date(); + return { + status: 200, + headers: { + 'unix-ms-ts': date.getTime().toString(), + 'cache-control': 'no-cache', + 'content-type': 'text/html; charset=utf-8', + 'server-name': server_name, + }, + body: ` + + + Welcome @ ${date.toLocaleDateString()} + + +

This is a simple HTML page.

+

This page was rendered at ${date.toLocaleString()} and delivered using '${server_name}' webserver.

+ + + `, + }; +} diff --git a/packages/hyper-express/benchmarks/setup/express.js b/packages/hyper-express/benchmarks/setup/express.js new file mode 100644 index 0000000..181f730 --- /dev/null +++ b/packages/hyper-express/benchmarks/setup/express.js @@ -0,0 +1,20 @@ +import Express from 'express'; +import { get_simple_html_page } from '../scenarios/simple_html_page.js'; + +// Initialize the Express app instance +const app = Express(); + +// Bind the 'simple_html_page' scenario route +app.get('/', (request, response) => { + // Generate the scenario payload + const { status, headers, body } = get_simple_html_page({ server_name: 'Express.js' }); + + // Write the status and headers + response.status(status); + Object.keys(headers).forEach((header) => response.header(header, headers[header])); + + // Write the body and end the response + return response.send(body); +}); + +export default app; diff --git a/packages/hyper-express/benchmarks/setup/fastify.js b/packages/hyper-express/benchmarks/setup/fastify.js new file mode 100644 index 0000000..78972bb --- /dev/null +++ b/packages/hyper-express/benchmarks/setup/fastify.js @@ -0,0 +1,20 @@ +import Fastify from 'fastify'; +import { get_simple_html_page } from '../scenarios/simple_html_page.js'; + +// Initialize the Express app instance +const app = Fastify(); + +// Bind the 'simple_html_page' scenario route +app.get('/', (request, response) => { + // Generate the scenario payload + const { status, headers, body } = get_simple_html_page({ server_name: 'Fastify' }); + + // Write the status and headers + response.status(status); + Object.keys(headers).forEach((header) => response.header(header, headers[header])); + + // Write the body and end the response + return response.send(body); +}); + +export default app; diff --git a/packages/hyper-express/benchmarks/setup/hyperexpress.js b/packages/hyper-express/benchmarks/setup/hyperexpress.js new file mode 100644 index 0000000..087936d --- /dev/null +++ b/packages/hyper-express/benchmarks/setup/hyperexpress.js @@ -0,0 +1,21 @@ +import HyperExpress from '../../index.js'; +import { get_simple_html_page } from '../scenarios/simple_html_page.js'; + +// Initialize the Express app instance +const app = new HyperExpress.Server(); + +// Generate the scenario payload +const { status, headers, body } = get_simple_html_page({ server_name: 'HyperExpress' }); + +// Bind the 'simple_html_page' scenario route +app.get('/', (request, response) => { + // Write the status and headers + response.status(status); + + for (const key in headers) response.header(key, headers[key]); + + // Write the body and end the response + response.send(body); +}); + +export default app; diff --git a/packages/hyper-express/benchmarks/setup/nanoexpress.js b/packages/hyper-express/benchmarks/setup/nanoexpress.js new file mode 100644 index 0000000..0884e73 --- /dev/null +++ b/packages/hyper-express/benchmarks/setup/nanoexpress.js @@ -0,0 +1,20 @@ +import NanoExpress from 'nanoexpress'; +import { get_simple_html_page } from '../scenarios/simple_html_page.js'; + +// Initialize the Express app instance +const app = NanoExpress(); + +// Bind the 'simple_html_page' scenario route +app.get('/', (request, response) => { + // Generate the scenario payload + const { status, headers, body } = get_simple_html_page({ server_name: 'NanoExpress' }); + + // Write the status and headers + response.status(status); + Object.keys(headers).forEach((header) => response.header(header, headers[header])); + + // Write the body and end the response + return response.send(body); +}); + +export default app; diff --git a/packages/hyper-express/benchmarks/setup/uwebsockets.js b/packages/hyper-express/benchmarks/setup/uwebsockets.js new file mode 100644 index 0000000..9bf77a2 --- /dev/null +++ b/packages/hyper-express/benchmarks/setup/uwebsockets.js @@ -0,0 +1,20 @@ +import uWebsockets from 'uWebSockets.js'; +import { get_simple_html_page } from '../scenarios/simple_html_page.js'; + +// Initialize an app instance which will be used to create the server +const app = uWebsockets.App(); + +// Bind the 'simple_html_page' scenario route +app.get('/', (response, request) => { + // Generate the scenario payload + const { status, headers, body } = get_simple_html_page({ server_name: 'uWebSockets.js' }); + + // Write the status and headers + response.writeStatus(`${status} OK`); + Object.keys(headers).forEach((header) => response.writeHeader(header, headers[header])); + + // Write the body and end the response + return response.end(body); +}); + +export default app; diff --git a/packages/hyper-express/benchmarks/utils.js b/packages/hyper-express/benchmarks/utils.js new file mode 100644 index 0000000..8695268 --- /dev/null +++ b/packages/hyper-express/benchmarks/utils.js @@ -0,0 +1,24 @@ +import cluster from 'cluster'; + +/** + * Logs a message to the console. + * Will only log if the current process is a worker and primary_only is set to false. + * + * @param {String} message + * @param {Boolean} [primary_only=true] + * @returns + */ +export function log(message, primary_only = true) { + if (primary_only && cluster.isWorker) return; + console.log(`[${process.pid}] ${message}`); +} + +/** + * Returns a Promise which is resolved after the given number of milliseconds. + * + * @param {Number} ms + * @returns {Promise} + */ +export function async_wait(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/hyper-express/index.js b/packages/hyper-express/index.js new file mode 100644 index 0000000..b34020a --- /dev/null +++ b/packages/hyper-express/index.js @@ -0,0 +1,33 @@ +'use strict'; + +// Load uWebSockets.js and fundamental Server/Router classes +const uWebsockets = require('uWebSockets.js'); +const Server = require('./src/components/Server.js'); +const Router = require('./src/components/router/Router.js'); +const Request = require('./src/components/http/Request.js'); +const Response = require('./src/components/http/Response.js'); +const LiveFile = require('./src/components/plugins/LiveFile.js'); +const MultipartField = require('./src/components/plugins/MultipartField.js'); +const SSEventStream = require('./src/components/plugins/SSEventStream.js'); +const Websocket = require('./src/components/ws/Websocket.js'); + +// Disable the uWebsockets.js version header if not specified to be kept +if (!process.env['KEEP_UWS_HEADER']) { + try { + uWebsockets._cfg('999999990007'); + } catch (error) {} +} + +// Expose Server and Router classes along with uWebSockets.js constants +module.exports = { + Server, + Router, + Request, + Response, + LiveFile, + MultipartField, + SSEventStream, + Websocket, + compressors: uWebsockets, + express(...args) { return new Server(...args); }, +}; diff --git a/packages/hyper-express/package-lock.json b/packages/hyper-express/package-lock.json new file mode 100644 index 0000000..695b993 --- /dev/null +++ b/packages/hyper-express/package-lock.json @@ -0,0 +1,283 @@ +{ + "name": "@intentjs/hyper-express", + "version": "6.17.2-beta", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@intentjs/hyper-express", + "version": "6.17.2-beta", + "license": "MIT", + "dependencies": { + "busboy": "^1.6.0", + "cookie": "^1.0.1", + "cookie-signature": "^1.2.1", + "mime-types": "^2.1.35", + "negotiator": "^0.6.3", + "range-parser": "^1.2.1", + "type-is": "^1.6.18", + "typed-emitter": "^2.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0" + }, + "devDependencies": { + "@types/busboy": "^1.5.4", + "@types/express": "^5.0.0", + "@types/node": "^22.7.5", + "typescript": "^5.6.3" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/busboy": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/cookie": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", + "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "optional": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "optional": true + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "optionalDependencies": { + "rxjs": "*" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uWebSockets.js": { + "version": "20.49.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#442087c0a01bf146acb7386910739ec81df06700", + "license": "Apache-2.0" + } + } +} diff --git a/packages/hyper-express/package.json b/packages/hyper-express/package.json new file mode 100644 index 0000000..884f7bf --- /dev/null +++ b/packages/hyper-express/package.json @@ -0,0 +1,60 @@ +{ + "name": "@intentjs/hyper-express", + "version": "6.17.2-beta-2", + "description": "A fork of hyper-express to suit IntentJS requirements. High performance Node.js webserver with a simple-to-use API powered by uWebsockets.js under the hood.", + "main": "index.js", + "types": "./types/index.d.ts", + "scripts": { + "test": "node tests/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/intentjs/hyper-express.git" + }, + "keywords": [ + "uws", + "websockets", + "uwebsocketsjs", + "express", + "expressjs", + "fast", + "http-server", + "https-server", + "http", + "https", + "sse", + "events", + "streaming", + "stream", + "upload", + "file", + "multipart", + "ws", + "websocket", + "performance", + "router" + ], + "author": "Vinayak Sarawagi", + "license": "MIT", + "bugs": { + "url": "https://github.com/intentjs/hyper-express/issues" + }, + "homepage": "https://github.com/intentjs/hyper-express#readme", + "dependencies": { + "busboy": "^1.6.0", + "cookie": "^1.0.1", + "cookie-signature": "^1.2.1", + "mime-types": "^2.1.35", + "negotiator": "^0.6.3", + "range-parser": "^1.2.1", + "type-is": "^1.6.18", + "typed-emitter": "^2.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0" + }, + "devDependencies": { + "@types/busboy": "^1.5.4", + "@types/express": "^5.0.0", + "@types/node": "^22.7.5", + "typescript": "^5.6.3" + } +} diff --git a/packages/hyper-express/src/components/Server.js b/packages/hyper-express/src/components/Server.js new file mode 100644 index 0000000..da9b919 --- /dev/null +++ b/packages/hyper-express/src/components/Server.js @@ -0,0 +1,608 @@ +'use strict'; +const path = require('path'); +const fs = require('fs/promises'); +const uWebSockets = require('uWebSockets.js'); + +const Route = require('./router/Route.js'); +const Router = require('./router/Router.js'); +const Request = require('./http/Request.js'); +const Response = require('./http/Response.js'); +const HostManager = require('./plugins/HostManager.js'); +const WebsocketRoute = require('./ws/WebsocketRoute.js'); + +const { wrap_object, to_forward_slashes } = require('../shared/operators.js'); + +class Server extends Router { + #port; + #hosts; + #uws_instance; + #listen_socket; + #options = { + is_ssl: false, + auto_close: true, + fast_abort: false, + trust_proxy: false, + fast_buffers: false, + max_body_buffer: 16 * 1024, + max_body_length: 250 * 1024, + streaming: {}, + }; + + /** + * Server instance options. + * @returns {Object} + */ + _options = null; + + /** + * @param {Object} options Server Options + * @param {String=} options.cert_file_name Path to SSL certificate file to be used for SSL/TLS. + * @param {String=} options.key_file_name Path to SSL private key file to be used for SSL/TLS. + * @param {String=} options.passphrase Strong passphrase for SSL cryptographic purposes. + * @param {String=} options.dh_params_file_name Path to SSL Diffie-Hellman parameters file. + * @param {Boolean=} options.ssl_prefer_low_memory_usage Specifies uWebsockets to prefer lower memory usage while serving SSL. + * @param {Boolean=} options.fast_buffers Buffer.allocUnsafe is used when set to true for faster performance. + * @param {Boolean=} options.fast_abort Determines whether HyperExpress will abrubptly close bad requests. This can be much faster but the client does not receive an HTTP status code as it is a premature connection closure. + * @param {Boolean=} options.trust_proxy Specifies whether to trust incoming request data from intermediate proxy(s) + * @param {Number=} options.max_body_buffer Maximum body content to buffer in memory before a request data is handled. Behaves similar to `highWaterMark` in Node.js streams. + * @param {Number=} options.max_body_length Maximum body content length allowed in bytes. For Reference: 1kb = 1024 bytes and 1mb = 1024kb. + * @param {Boolean=} options.auto_close Whether to automatically close the server instance when the process exits. Default: true + * @param {Object} options.streaming Global content streaming options. + * @param {import('stream').ReadableOptions=} options.streaming.readable Global content streaming options for Readable streams. + * @param {import('stream').WritableOptions=} options.streaming.writable Global content streaming options for Writable streams. + */ + constructor(options = {}) { + // Only accept object as a parameter type for options + if (options == null || typeof options !== 'object') + throw new Error( + 'HyperExpress: HyperExpress.Server constructor only accepts an object type for the options parameter.' + ); + + // Initialize extended Router instance + super(); + super._is_app(true); + + // Store options locally for access throughout processing + wrap_object(this.#options, options); + + // Expose the options object for future use + this._options = this.#options; + try { + // Create underlying uWebsockets App or SSLApp to power HyperExpress + const { cert_file_name, key_file_name } = options; + this.#options.is_ssl = cert_file_name && key_file_name; // cert and key are required for SSL + if (this.#options.is_ssl) { + // Convert the certificate and key file names to absolute system paths + this.#options.cert_file_name = to_forward_slashes(path.resolve(cert_file_name)); + this.#options.key_file_name = to_forward_slashes(path.resolve(key_file_name)); + + // Create an SSL app with the provided SSL options + this.#uws_instance = uWebSockets.SSLApp(this.#options); + } else { + // Create a non-SSL app since no SSL options were provided + this.#uws_instance = uWebSockets.App(this.#options); + } + } catch (error) { + // Convert all the options to string values for logging purposes + const _options = Object.keys(options) + .map((key) => `options.${key}: "${options[key]}"`) + .join('\n'); + + // Throw error if uWebsockets.js fails to initialize + throw new Error( + `new HyperExpress.Server(): Failed to create new Server instance due to an invalid configuration in options.\n${_options}` + ); + } + + // Initialize the HostManager for this Server instance + this.#hosts = new HostManager(this); + } + + /** + * This object can be used to store properties/references local to this Server instance. + */ + locals = {}; + + /** + * @private + * This method binds a cleanup handler which automatically closes this Server instance. + */ + _bind_auto_close() { + const reference = this; + ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'SIGTERM'].forEach((type) => + process.once(type, () => reference.close()) + ); + } + + /** + * Starts HyperExpress webserver on specified port and host, or unix domain socket. + * + * @param {Number|String} first Required. Port or unix domain socket path to listen on. Example: 80 or "/run/listener.sock" + * @param {(String|function(import('uWebSockets.js').listen_socket):void)=} second Optional. Host or callback to be called when the server is listening. Default: "0.0.0.0" + * @param {(function(import('uWebSockets.js').us_listen_socket):void)=} third Optional. Callback to be called when the server is listening. + * @returns {Promise} Promise which resolves to the listen socket when the server is listening. + */ + async listen(first, second, third) { + let port; + let path; + + // Determine if first argument is a number or string castable to a port number + if (typeof first == 'number' || (+first > 0 && +first < 65536)) { + // Parse the port number + port = typeof first == 'string' ? +first : first; + } else if (typeof first == 'string') { + // Parse the path to a UNIX domain socket + path = first; + } + + let host = '0.0.0.0'; // Host by default is 0.0.0.0 + let callback; // Callback may be optionally provided as second or third argument + if (second) { + // If second argument is a function then it is the callback or else it is the host + if (typeof second === 'function') { + callback = second; + } else { + // Ensure the second argument is a string + if (typeof second == 'string') { + host = second; + } else { + throw new Error( + `HyperExpress.Server.listen(): The second argument must either be a callback function or a string as a hostname.` + ); + } + + // If we have a third argument and it is a function then it is the callback + if (third && typeof third === 'function') callback = third; + } + } + + // If the server is using SSL then verify that the provided SSL certificate and key files exist and are readable + if (this.#options.is_ssl) { + const { cert_file_name, key_file_name } = this.#options; + try { + // Verify that both the key and cert files exist and are readable + await Promise.all([fs.access(key_file_name), fs.access(cert_file_name)]); + } catch (error) { + throw new Error( + `HyperExpress.Server.listen(): The provided SSL certificate file at "${cert_file_name}" or private key file at "${key_file_name}" does not exist or is not readable.\n${error}` + ); + } + } + + // Bind the server to the specified port or unix domain socket with uWS.listen() or uWS.listen_unix() + const reference = this; + return await new Promise((resolve, reject) => { + // Define a callback to handle the listen socket from a listen event + const on_listen_socket = (listen_socket) => { + // Compile the Server instance to cache the routes and middlewares + reference._compile(); + + // Determine if we received a listen socket + if (listen_socket) { + // Store the listen socket for future closure + reference.#listen_socket = listen_socket; + + // Bind the auto close handler if enabled from constructor options + if (reference.#options.auto_close) reference._bind_auto_close(); + + // Serve the list socket over callback if provided + if (callback) callback(listen_socket); + + // Resolve the listen socket + resolve(listen_socket); + } else { + reject( + 'HyperExpress.Server.listen(): No Socket Received From uWebsockets.js likely due to an invalid host or busy port.' + ); + } + }; + + // Determine whether to bind on a port or unix domain socket with priority to port + if (port !== undefined) { + reference.#uws_instance.listen(host, port, on_listen_socket); + } else { + reference.#uws_instance.listen_unix(on_listen_socket, path); + } + }); + } + + #shutdown_promise; + /** + * Performs a graceful shutdown of the server and closes the listen socket once all pending requests have been completed. + * @param {uWebSockets.us_listen_socket=} listen_socket Optional + * @returns {Promise} + */ + shutdown(listen_socket) { + // If we already have a shutdown promise in flight, return it + if (this.#shutdown_promise) return this.#shutdown_promise; + + // If we have no pending requests, we can shutdown immediately + if (!this.#pending_requests_count) return Promise.resolve(this.close(listen_socket)); + + // Create a promise which resolves once all pending requests have been completed + const scope = this; + this.#shutdown_promise = new Promise((resolve) => { + // Bind a zero pending request handler to close the server + scope.#pending_requests_zero_handler = () => { + // Close the server and resolve the returned boolean + resolve(scope.close(listen_socket)); + }; + }); + + return this.#shutdown_promise; + } + + /** + * Stops/Closes HyperExpress webserver instance. + * + * @param {uWebSockets.us_listen_socket=} listen_socket Optional + * @returns {Boolean} + */ + close(listen_socket) { + // Fall back to self listen socket if none provided by user + const socket = listen_socket || this.#listen_socket; + if (socket) { + // Close the determined socket + uWebSockets.us_listen_socket_close(socket); + + // Nullify the local socket reference if it was used + if (!listen_socket) this.#listen_socket = null; + + return true; + } + return false; + } + + #routes_locked = false; + #handlers = { + on_not_found: (request, response) => response.status(404).send(), + on_error: (request, response, error) => { + // Log the error to the console + console.error(error); + + // Throw on default if user has not bound an error handler + return response.status(500).send('HyperExpress: Uncaught Exception Occured'); + }, + }; + + /** + * @typedef RouteErrorHandler + * @type {function(Request, Response, Error):void} + */ + + /** + * Sets a global error handler which will catch most uncaught errors across all routes/middlewares. + * + * @param {RouteErrorHandler} handler + */ + set_error_handler(handler) { + if (typeof handler !== 'function') throw new Error('HyperExpress: handler must be a function'); + this.#handlers.on_error = handler; + } + + /** + * @typedef RouteHandler + * @type {function(Request, Response):void} + */ + + /** + * Sets a global not found handler which will handle all requests that are unhandled by any registered route. + * + * @param {RouteHandler} handler + */ + set_not_found_handler(handler) { + if (typeof handler !== 'function') throw new Error('HyperExpress: handler must be a function'); + this.#handlers.on_not_found = handler; + } + + /** + * Publish a message to a topic in MQTT syntax to all WebSocket connections on this Server instance. + * You cannot publish using wildcards, only fully specified topics. + * + * @param {String} topic + * @param {String|Buffer|ArrayBuffer} message + * @param {Boolean=} is_binary + * @param {Boolean=} compress + * @returns {Boolean} + */ + publish(topic, message, is_binary, compress) { + return this.#uws_instance.publish(topic, message, is_binary, compress); + } + + /** + * Returns the number of subscribers to a topic across all WebSocket connections on this Server instance. + * + * @param {String} topic + * @returns {Number} + */ + num_of_subscribers(topic) { + return this.#uws_instance.numSubscribers(topic); + } + + /* Server Routes & Middlewares Logic */ + + #middlewares = { + '/': [], // This will contain global middlewares + }; + + #routes = { + any: {}, + get: {}, + post: {}, + del: {}, + head: {}, + options: {}, + patch: {}, + put: {}, + trace: {}, + upgrade: {}, + ws: {}, + }; + + #incremented_id = 0; + + /** + * Returns an incremented ID unique to this Server instance. + * + * @private + * @returns {Number} + */ + _get_incremented_id() { + return this.#incremented_id++; + } + + /** + * Binds route to uWS server instance and begins handling incoming requests. + * + * @private + * @param {Object} record { method, pattern, options, handler } + */ + _create_route(record) { + // Destructure record into route options + const { method, pattern, options, handler } = record; + + // Do not allow route creation once it is locked after a not found handler has been bound + if (this.#routes_locked === true) + throw new Error( + `HyperExpress: Routes/Routers must not be created or used after the Server.listen() has been called. [${method.toUpperCase()} ${pattern}]` + ); + + // Do not allow duplicate routes for performance/stability reasons + // We make an exception for 'upgrade' routes as they must replace the default route added by WebsocketRoute + if (method !== 'upgrade' && this.#routes[method][pattern]) + throw new Error( + `HyperExpress: Failed to create route as duplicate routes are not allowed. Ensure that you do not have any routers or routes that try to handle requests with the same pattern. [${method.toUpperCase()} ${pattern}]` + ); + + // Create a Route object to contain route information through handling process + const route = new Route({ + app: this, + method, + pattern, + options, + handler, + }); + + // Mark route as temporary if specified from options + if (options._temporary === true) route._temporary = true; + + // Handle websocket/upgrade routes separately as they follow a different lifecycle + switch (method) { + case 'ws': + // Create a WebsocketRoute which initializes uWS.ws() route + this.#routes[method][pattern] = new WebsocketRoute({ + app: this, + pattern, + handler, + options, + }); + break; + case 'upgrade': + // Throw an error if an upgrade route already exists that was not created by WebsocketRoute + const current = this.#routes[method][pattern]; + if (current && current._temporary !== true) + throw new Error( + `HyperExpress: Failed to create upgrade route as an upgrade route with the same pattern already exists and duplicate routes are not allowed. [${method.toUpperCase()} ${pattern}]` + ); + + // Overwrite the upgrade route that exists from WebsocketRoute with this custom route + this.#routes[method][pattern] = route; + + // Assign route to companion WebsocketRoute + const companion = this.#routes['ws'][pattern]; + if (companion) companion._set_upgrade_route(route); + break; + default: + // Store route in routes object for structural tracking + this.#routes[method][pattern] = route; + + // Bind the uWS route handler which pipes all incoming uWS requests to the HyperExpress request lifecycle + return this.#uws_instance[method](pattern, (response, request) => { + this._handle_uws_request(route, request, response, null); + }); + } + } + + /** + * Binds middleware to server instance and distributes over all created routes. + * + * @private + * @param {Object} record + */ + _create_middleware(record) { + // Destructure record from Router + const { pattern, middleware } = record; + + // Do not allow route creation once it is locked after a not found handler has been bound + if (this.#routes_locked === true) + throw new Error( + `HyperExpress: Routes/Routers must not be created or used after the Server.listen() has been called. [${method.toUpperCase()} ${pattern}]` + ); + + // Initialize middlewares array for specified pattern + if (this.#middlewares[pattern] == undefined) this.#middlewares[pattern] = []; + + // Create a middleware object with an appropriate priority + const object = { + id: this._get_incremented_id(), + pattern, + handler: middleware, + }; + + // Store middleware object in its pattern branch + this.#middlewares[pattern].push(object); + } + + /** + * Compiles the route and middleware structures for this instance for use in the uWS server. + * Note! This method will lock any future creation of routes or middlewares. + * @private + */ + _compile() { + // Bind the not found handler as a catchall route if the user did not already bind a global ANY catchall route + if (this.#handlers.on_not_found) { + const exists = this.#routes.any['/*'] !== undefined; + if (!exists) this.any('/*', (request, response) => this.#handlers.on_not_found(request, response)); + } + + // Iterate through all routes + Object.keys(this.#routes).forEach((method) => + Object.keys(this.#routes[method]).forEach((pattern) => this.#routes[method][pattern].compile()) + ); + + // Lock routes from further creation + this.#routes_locked = true; + } + + /* uWS -> Server Request/Response Handling Logic */ + + #pending_requests_count = 0; + #pending_requests_zero_handler = null; + + /** + * Resolves a single pending request and ticks sthe pending request handler if one exists. + */ + _resolve_pending_request() { + // Ensure we have at least one pending request + if (this.#pending_requests_count > 0) { + // Decrement the pending request count + this.#pending_requests_count--; + + // If we have no more pending requests and a zero pending request handler was set, execute it + if (this.#pending_requests_count === 0 && this.#pending_requests_zero_handler) + this.#pending_requests_zero_handler(); + } + } + + /** + * This method is used to handle incoming requests from uWS and pass them to the appropriate route through the HyperExpress request lifecycle. + * + * @private + * @param {Route} route + * @param {uWebSockets.HttpRequest} uws_request + * @param {uWebSockets.HttpResponse} uws_response + * @param {uWebSockets.us_socket_context_t=} socket + */ + _handle_uws_request(route, uws_request, uws_response, socket) { + // Construct the wrapper Request around uWS.HttpRequest + const request = new Request(route, uws_request); + request._raw_response = uws_response; + + // Construct the wrapper Response around uWS.Response + const response = new Response(uws_response); + response.route = route; + response._wrapped_request = request; + response._upgrade_socket = socket || null; + + // If we are in the process of gracefully shutting down, we must immediately close the request + if (this.#pending_requests_zero_handler) return response.close(); + + // Increment the pending request count + this.#pending_requests_count++; + + // Attempt to start the body parser for this request + // This method will return false If the request body is larger than the max_body_length + if (request._body_parser_run(response, route.max_body_length)) { + // Handle this request with the associated route + route.handle(request, response); + + // If by this point the response has not been sent then this is request is being asynchronously handled hence we must cork when the response is sent + if (!response.completed) response._cork = true; + } + } + + /* Safe Server Getters */ + + /** + * Returns the local server listening port of the server instance. + * @returns {Number} + */ + get port() { + // Initialize port if it does not exist yet + // Ensure there is a listening socket before returning port + if (this.#port === undefined) { + // Throw error if listening socket does not exist + if (!this.#listen_socket) + throw new Error( + 'HyperExpress: Server.port is not available as the server is not listening. Please ensure you called already Server.listen() OR have not yet called Server.close() when accessing this property.' + ); + + // Cache the resolved port + this.#port = uWebSockets.us_socket_local_port(this.#listen_socket); + } + + // Return port + return this.#port; + } + + /** + * Returns the server's internal uWS listening socket. + * @returns {uWebSockets.us_listen_socket=} + */ + get socket() { + return this.#listen_socket; + } + + /** + * Underlying uWS instance. + * @returns {uWebSockets.TemplatedApp} + */ + get uws_instance() { + return this.#uws_instance; + } + + /** + * Returns the Server Hostnames manager for this instance. + * Use this to support multiple hostnames on the same server with different SSL configurations. + * @returns {HostManager} + */ + get hosts() { + return this.#hosts; + } + + /** + * Server instance global handlers. + * @returns {Object} + */ + get handlers() { + return this.#handlers; + } + + /** + * Server instance routes. + * @returns {Object} + */ + get routes() { + return this.#routes; + } + + /** + * Server instance middlewares. + * @returns {Object} + */ + get middlewares() { + return this.#middlewares; + } +} + +module.exports = Server; diff --git a/packages/hyper-express/src/components/compatibility/ExpressRequest.js b/packages/hyper-express/src/components/compatibility/ExpressRequest.js new file mode 100644 index 0000000..c1fe39f --- /dev/null +++ b/packages/hyper-express/src/components/compatibility/ExpressRequest.js @@ -0,0 +1,208 @@ +'use strict'; +const Negotiator = require('negotiator'); +const parse_range = require('range-parser'); +const type_is = require('type-is'); +const is_ip = require('net').isIP; + +class ExpressRequest { + #negotiator; + + ExpressRequest() { + this.#negotiator = new Negotiator(this); + } + + /* Methods */ + get(name) { + let lowercase = name.toLowerCase(); + switch (lowercase) { + case 'referer': + // Continue execution to below case for catching of both spelling variations + case 'referrer': + return this.headers['referer'] || this.headers['referrer']; + default: + return this.headers[lowercase]; + } + } + + header(name) { + return this.get(name); + } + + accepts() { + let instance = accepts(this); + return instance.types.apply(instance, arguments); + } + + acceptsCharsets() { + charsets = flattened(charsets, arguments); + + // no charsets, return all requested charsets + if (!charsets || charsets.length === 0) { + return this.#negotiator.charsets(); + } + + return this.#negotiator.charsets(charsets)[0] || false; + } + + acceptsEncodings() { + encodings = flattened(encodings, arguments); + + // no encodings, return all requested encodings + if (!encodings || encodings.length === 0) { + return this.#negotiator.encodings(); + } + + return this.#negotiator.encodings(encodings)[0] || false; + } + + acceptsLanguages() { + languages = flattened(languages, arguments); + + // no languages, return all requested languages + if (!languages || languages.length === 0) { + return this.#negotiator.languages(); + } + + return this.#negotiator.languages(languages)[0] || false; + } + + range(size, options) { + let range = this.get('Range'); + if (!range) return; + return parse_range(size, range, options); + } + + param(name, default_value) { + // Parse three dataset candidates + let body = this.body; + let path_parameters = this.path_parameters; + let query_parameters = this.query_parameters; + + // First check path parameters, body, and finally query_parameters + if (null != path_parameters[name] && path_parameters.hasOwnProperty(name)) return path_parameters[name]; + if (null != body[name]) return body[name]; + if (null != query_parameters[name]) return query_parameters[name]; + + return default_value; + } + + is(types) { + // support flattened arguments + let arr = types; + if (!Array.isArray(types)) { + arr = new Array(arguments.length); + for (let i = 0; i < arr.length; i++) arr[i] = arguments[i]; + } + return type_is(this, arr); + } + + /* Properties */ + get baseUrl() { + return this.path; + } + + get originalUrl() { + return this.url; + } + + get fresh() { + this._throw_unsupported('fresh'); + } + + get params() { + return this.path_parameters; + } + + get hostname() { + // Retrieve the host header and determine if we can trust intermediary proxy servers + let host = this.get('X-Forwarded-Host'); + const trust_proxy = this.route.app._options.trust_proxy; + if (!host || !trust_proxy) { + // Use the 'Host' header as fallback + host = this.get('Host'); + } else { + // Note: X-Forwarded-Host is normally only ever a single value, but this is to be safe. + host = host.split(',')[0]; + } + + // If we don't have a host, return undefined + if (!host) return; + + // IPv6 literal support + let offset = host[0] === '[' ? host.indexOf(']') + 1 : 0; + let index = host.indexOf(':', offset); + return index !== -1 ? host.substring(0, index) : host; + } + + get ips() { + // Retrieve the client and proxy IP addresses + const client_ip = this.ip; + const proxy_ip = this.proxy_ip; + + // Determine if we can trust intermediary proxy servers and have a x-forwarded-for header + const trust_proxy = this.route.app._options.trust_proxy; + const x_forwarded_for = this.get('X-Forwarded-For'); + if (trust_proxy && x_forwarded_for) { + // Will split and return all possible IP addresses in the x-forwarded-for header (e.g. "client, proxy1, proxy2") + return x_forwarded_for.split(','); + } else { + // Returns all valid IP addresses available from uWS + return [client_ip, proxy_ip].filter((ip) => ip); + } + } + + get protocol() { + // Resolves x-forwarded-proto header if trust proxy is enabled + const trust_proxy = this.route.app._options.trust_proxy; + const x_forwarded_proto = this.get('X-Forwarded-Proto'); + if (trust_proxy && x_forwarded_proto) { + // Return the first protocol in the x-forwarded-proto header + // If the header contains a single value, the split will contain that value in the first index element anyways + return x_forwarded_proto.split(',')[0]; + } else { + // Use HyperExpress/uWS initially defined protocol as fallback + return this.route.app.is_ssl ? 'https' : 'http'; + } + } + + get query() { + return this.query_parameters; + } + + get secure() { + return this.protocol === 'https'; + } + + get signedCookies() { + this._throw_unsupported('signedCookies'); + } + + get stale() { + this._throw_unsupported('stale'); + } + + get subdomains() { + let hostname = this.hostname; + if (!hostname) return []; + + let offset = 2; + let subdomains = !is_ip(hostname) ? hostname.split('.').reverse() : [hostname]; + return subdomains.slice(offset); + } + + get xhr() { + return (this.get('X-Requested-With') || '').toLowerCase() === 'xmlhttprequest'; + } +} + +const flattened = function (arr, args) { + if (arr && !Array.isArray(arr)) { + arr = new Array(args.length); + for (var i = 0; i < arr.length; i++) { + arr[i] = args[i]; + } + } + return arr; +}; + +module.exports = ExpressRequest; diff --git a/packages/hyper-express/src/components/compatibility/ExpressResponse.js b/packages/hyper-express/src/components/compatibility/ExpressResponse.js new file mode 100644 index 0000000..693efd7 --- /dev/null +++ b/packages/hyper-express/src/components/compatibility/ExpressResponse.js @@ -0,0 +1,112 @@ +'use strict'; + +class ExpressResponse { + /* Methods */ + append(name, values) { + return this.header(name, values); + } + + setHeader(name, values) { + return this.append(name, values); + } + + writeHeaders(headers) { + Object.keys(headers).forEach((name) => this.header(name, headers[name])); + } + + setHeaders(headers) { + this.writeHeaders(headers); + } + + writeHeaderValues(name, values) { + values.forEach((value) => this.header(name, value)); + } + + getHeader(name) { + return this._headers[name]; + } + + removeHeader(name) { + delete this._headers[name]; + } + + setCookie(name, value, options) { + return this.cookie(name, value, null, options); + } + + hasCookie(name) { + return this._cookies && this._cookies[name] !== undefined; + } + + removeCookie(name) { + return this.cookie(name, null); + } + + clearCookie(name) { + return this.cookie(name, null); + } + + end(data) { + return this.send(data); + } + + format() { + this._throw_unsupported('format()'); + } + + get(name) { + let values = this._headers[name]; + if (values) return values.length == 0 ? values[0] : values; + } + + links(links) { + // Build chunks of links and combine into header spec + let chunks = []; + Object.keys(links).forEach((rel) => { + let url = links[rel]; + chunks.push(`<${url}>; rel="${rel}"`); + }); + + // Write the link header + this.header('link', chunks.join(', ')); + } + + location(path) { + return this.header('location', path); + } + + render() { + this._throw_unsupported('render()'); + } + + sendFile(path) { + return this.file(path); + } + + sendStatus(status_code) { + return this.status(status_code).send(); + } + + set(field, value) { + if (typeof field == 'object') { + const reference = this; + Object.keys(field).forEach((name) => { + let value = field[name]; + reference.header(name, value); + }); + } else { + this.header(field, value); + } + } + + vary(name) { + return this.header('vary', name); + } + + /* Properties */ + get headersSent() { + return this.initiated; + } +} + +module.exports = ExpressResponse; diff --git a/packages/hyper-express/src/components/compatibility/NodeRequest.js b/packages/hyper-express/src/components/compatibility/NodeRequest.js new file mode 100644 index 0000000..3d22592 --- /dev/null +++ b/packages/hyper-express/src/components/compatibility/NodeRequest.js @@ -0,0 +1,5 @@ +'use strict'; + +class NodeRequest {} + +module.exports = NodeRequest; diff --git a/packages/hyper-express/src/components/compatibility/NodeResponse.js b/packages/hyper-express/src/components/compatibility/NodeResponse.js new file mode 100644 index 0000000..c2c4ce4 --- /dev/null +++ b/packages/hyper-express/src/components/compatibility/NodeResponse.js @@ -0,0 +1,27 @@ +'use strict'; + +/** + * @typedef {Object} NodeResponseTypes + * @property {number} statusCode + * @property {string} statusMessage + */ +class NodeResponse { + /* Properties */ + get statusCode() { + return this._status_code; + } + + set statusCode(value) { + this._status_code = value; + } + + get statusMessage() { + return this._status_message; + } + + set statusMessage(value) { + this._status_message = value; + } +} + +module.exports = NodeResponse; diff --git a/packages/hyper-express/src/components/http/Request.js b/packages/hyper-express/src/components/http/Request.js new file mode 100644 index 0000000..4b621f5 --- /dev/null +++ b/packages/hyper-express/src/components/http/Request.js @@ -0,0 +1,1155 @@ +'use strict'; +const util = require('util'); +const cookie = require('cookie'); +const stream = require('stream'); +const busboy = require('busboy'); +const querystring = require('querystring'); +const signature = require('cookie-signature'); + +const MultipartField = require('../plugins/MultipartField.js'); +const NodeRequest = require('../compatibility/NodeRequest.js'); +const ExpressRequest = require('../compatibility/ExpressRequest.js'); +const { + inherit_prototype, + array_buffer_to_string, + copy_array_buffer_to_uint8array, +} = require('../../shared/operators.js'); +const { process_multipart_data } = require('../../shared/process-multipart.js'); +const UploadedFile = require('../../shared/uploaded-file.js'); + +class Request { + _locals; + _paused = false; + _request_ended = false; + _raw_request = null; + _raw_response = null; + _method = ''; + _url = ''; + _path = ''; + _query = ''; + _remote_ip = ''; + _remote_proxy_ip = ''; + _cookies; + _path_parameters; + _query_parameters; + _dto = undefined; + _user = undefined; + _all_input = undefined; + _body = undefined; + + /** + * The route that this request is being handled by. + */ + route = null; + + /** + * Underlying lazy initialized readable body stream. + * @private + */ + _readable = null; + + /** + * Returns whether all expected incoming request body chunks have been received. + * @returns {Boolean} + */ + received = true; // Assume there is no body data to stream + + /** + * Returns request headers from incoming request. + * @returns {Object.} + */ + headers = {}; + + /** + * Creates a new HyperExpress request instance that wraps a uWS.HttpRequest instance. + * + * @param {import('../router/Route.js')} route + * @param {import('uWebSockets.js').HttpRequest} raw_request + */ + constructor(route, raw_request) { + // Store reference to the route of this request and the raw uWS.HttpResponse instance for certain operations + this.route = route; + this._raw_request = raw_request; + + // Cache request properties from uWS.HttpRequest as it is stack allocated and will be deallocated after this function returns + this._query = raw_request.getQuery(); + this._path = route.path || raw_request.getUrl(); + this._method = route.method !== 'ANY' ? route.method : raw_request.getMethod(); + + // Cache request headers from uWS.HttpRequest as it is stack allocated and will be deallocated after this function returns + raw_request.forEach((key, value) => (this.headers[key] = value)); + + // Cache the path parameters from the route pattern if any as uWS.HttpRequest will be deallocated after this function returns + const num_path_parameters = route.path_parameters_key.length; + if (num_path_parameters) { + this._path_parameters = {}; + for (let i = 0; i < num_path_parameters; i++) { + const parts = route.path_parameters_key[i]; + this._path_parameters[parts[0]] = raw_request.getParameter(parts[1]); + } + } + } + + /* HyperExpress Methods */ + + /** + * Returns the raw uWS.HttpRequest instance. + * Note! This property is unsafe and should not be used unless you have no asynchronous code or you are accessing from the first top level synchronous middleware before any asynchronous code. + * @returns {import('uWebSockets.js').HttpRequest} + */ + get raw() { + return this._raw_request; + } + + /** + * Pauses the current request and flow of incoming body data. + * @returns {Request} + */ + pause() { + // Ensure there is content being streamed before pausing + // Ensure that the stream is currently not paused before pausing + if (!this._paused) { + this._paused = true; + this._raw_response.pause(); + if (this._readable) return this._super_pause(); + } + return this; + } + + /** + * Resumes the current request and flow of incoming body data. + * @returns {Request} + */ + resume() { + // Ensure there is content being streamed before resuming + // Ensure that the stream is currently paused before resuming + if (this._paused) { + this._paused = false; + this._raw_response.resume(); + if (this._readable) return this._super_resume(); + } + return this; + } + + /** + * Pipes the request body stream data to the provided destination stream with the provided set of options. + * + * @param {stream.Writable} destination + * @param {stream.WritableOptions} options + * @returns {Request} + */ + pipe(destination, options) { + // Pipe the arguments to the request body stream + this._super_pipe(destination, options); + + // Resume the request body stream as it will be in a paused state by default + return this._super_resume(); + } + + /** + * Securely signs a value with provided secret and returns the signed value. + * + * @param {String} string + * @param {String} secret + * @returns {String} String OR undefined + */ + sign(string, secret) { + return signature.sign(string, secret); + } + + /** + * Securely unsigns a value with provided secret and returns its original value upon successful verification. + * + * @param {String} signed_value + * @param {String} secret + * @returns {String=} String OR undefined + */ + unsign(signed_value, secret) { + let unsigned_value = signature.unsign(signed_value, secret); + if (unsigned_value !== false) return unsigned_value; + } + + /* Body Parsing */ + _body_parser_mode = 0; // 0 = none (awaiting mode), 1 = buffering (internal use), 2 = streaming (external use) + _body_limit_bytes = 0; + _body_received_bytes = 0; + _body_expected_bytes = -1; // We initialize this to -1 as we will use this to ensure the uWS.HttpResponse.onData() is only called once + _body_parser_flushing = false; + _body_chunked_transfer = false; + _body_parser_buffered; // This will hold the buffered chunks until the user decides to internally or externally consume the body data + _body_parser_passthrough; // This will be a passthrough chunk acceptor callback used by internal body parsers + + /** + * Begins parsing the incoming request body data within the provided limit in bytes. + * NOTE: This method will be a no-op if there is no expected body based on the content-length header. + * NOTE: This method can be called multiple times to update the bytes limit during the parsing process process. + * + * @private + * @param {import('./Response.js')} response + * @param {Number} bytes + * @returns {Boolean} Returns whether this request is within the bytes limit and should be handled further. + */ + _body_parser_run(response, limit_bytes) { + // Parse the content length into a number to ensure we have some body data to parse + // Even though it can be NaN, the > 0 check will handle this case and ignore NaN + // OR if the transfer-encoding header is chunked which means we will have to do a more inefficient chunked transfer + const content_length = Number(this.headers['content-length']); + const is_chunked_transfer = this.headers['transfer-encoding'] === 'chunked'; + if (content_length > 0 || is_chunked_transfer) { + // Determine if this is a first run meaning we have not began parsing the body yet + const is_first_run = this._body_expected_bytes === -1; + + // Update the limit and expected body bytes as these will be used to check if we are within the limit + this._body_limit_bytes = limit_bytes; + this._body_expected_bytes = is_chunked_transfer ? 0 : content_length; // We use 0 to indicate we do not know the content length with chunked transfers + + // We want to track if we are expecting a chunked transfer so depending logic does not treat the 0 expected bytes as an empty body + this._body_chunked_transfer = is_chunked_transfer; + + // Determine if this is a first time body parser run + if (is_first_run) { + // Set the request body to not received as we have some body data to parse + this.received = false; + + // Ensure future runs do not trigger the handling process + this._body_received_bytes = 0; + + // Initialize the array which will buffer the incoming chunks until a different parser mode is requested aka. user does something with the data + this._body_parser_buffered = []; + + // Bind the uWS.HttpResponse.onData() event handler to begin accepting incoming body data + this._raw_response.onData((chunk, is_last) => this._body_parser_on_chunk(response, chunk, is_last)); + } + + // Enforce the limit as we may have a different limit than the previous run + this._body_parser_enforce_limit(response); + } + + // Return whether the body parser is actively parsing the incoming body data + return !this._body_parser_flushing; + } + + /** + * Stops the body parser from accepting any more incoming body data. + * @private + */ + _body_parser_stop() { + // Return if we have no expected body length or already flushing the body + if (this._body_expected_bytes === -1 || this._body_parser_flushing) return; + + // Mark the body parser as flushing to prevent any more incoming body data from being accepted + this._body_parser_flushing = true; + + // Determine if we have a readable stream + if (this._readable) { + // Push an empty chunk to indicate the end of the stream + this.push(null); + + // Resume the readable stream to ensure in case it was paused to flush the buffered chunks + this.resume(); + } + } + + /** + * Checks if the body parser so far is within the bytes limit and triggers the limit handling if reached. + * + * @private + * @param {import('./Response.js')} response + * @returns {Boolean} Returns `true` when the body limit has been reached. + */ + _body_parser_enforce_limit(response) { + // Determine if we may have either received or are expecting more incoming bytes than the limit allows for + const incoming_bytes = Math.max(this._body_received_bytes, this._body_expected_bytes); + if (incoming_bytes > this._body_limit_bytes) { + // Stop the body parser from accepting any more incoming body data + this._body_parser_stop(); + + // Determine if we have not began sending a response yet and hence must send a response as soon as we can + if (!response.initiated) { + // If the server is instructed to do fast aborts, we will close the request immediately + if (this.route.app._options.fast_abort) { + response.close(); + } else if (this.received) { + // Otherwise, we will send a HTTP 413 Payload Too Large response once the request body has been fully flushed aka. received + response.status(413).send(); + } + } + + return true; + } + + return false; + } + + /** + * Processes incoming raw body data chunks from the uWS HttpResponse. + * + * @private + * @param {import('./Response.js')} response + * @param {ArrayBuffer} chunk + * @param {Boolean} is_last + */ + _body_parser_on_chunk(response, chunk, is_last) { + // If this chunk has no length and is not the last chunk, we will ignore it + if (!chunk.byteLength && !is_last) return; + + // Increment the received bytes counter by the byteLength of the incoming chunk + this._body_received_bytes += chunk.byteLength; + + // Determine if the body parser is active / not flushing + if (!this._body_parser_flushing) { + // Enforce the body parser limit as the number of incoming bytes may have exceeded the limit + const limited = this._body_parser_enforce_limit(response); + if (!limited) { + // Process this chunk depending on the current body parser mode + switch (this._body_parser_mode) { + // Awaiting mode - Awaiting the user to do something with the incoming body data + case 0: + // Buffer a COPIED Uint8Array chunk from the uWS volatile ArrayBuffer chunk + this._body_parser_buffered.push(copy_array_buffer_to_uint8array(chunk)); + + // If we have exceeded the Server.options.max_body_buffer number of buffered bytes, then pause the request to prevent more buffering + if (this._body_received_bytes > this.app._options.max_body_buffer) this.pause(); + break; + // Buffering mode - Internal use only + case 1: + // Pass through the uWS volatile ArrayBuffer chunk to the passthrough callback as a volatile Uint8Array chunk + this._body_parser_passthrough( + // If this is a chunked transfer, we need to COPY the chunk as any passthrough consumer will have no immediate way of processing + // hence this chunk needs to stick around across multiple cycles without being deallocated by uWS + this._body_chunked_transfer + ? copy_array_buffer_to_uint8array(chunk) + : new Uint8Array(chunk), + is_last, + ); + break; + // Streaming mode - External use only + case 2: + // Attempt to push a COPIED Uint8Array chunk from the uWS volatile ArrayBuffer chunk to the readable stream + // Pause the request if we have reached the highWaterMark to prevent backpressure + if (!this.push(copy_array_buffer_to_uint8array(chunk))) this.pause(); + + // If this is the last chunk, push a null chunk to indicate the end of the stream + if (is_last) this.push(null); + break; + } + } + } + + // Determine if this is the last chunk of the incoming body data to perform final closing operations + if (is_last) { + // Mark the request as fully received as we have flushed all incoming body data + this.received = true; + + // Emit the 'received' event that indicates how many bytes were received in total from the incoming body + if (this._readable) this.emit('received', this._body_received_bytes); + + // Enforce the body parser limit one last time in case the request is waiting for the body to be flushed before sending a response + if (this._body_parser_flushing) this._body_parser_enforce_limit(response); + } + } + + /** + * Flushes the buffered chunks to the appropriate body parser mode. + * @private + */ + _body_parser_flush_buffered() { + // Determine if we have any buffered chunks + if (this._body_parser_buffered) { + // Determine the body parser mode to flush the buffered chunks to + switch (this._body_parser_mode) { + // Buffering mode - Internal use only + case 1: + // Iterate over the buffered chunks and pass them to the passthrough callback + for (let i = 0; i < this._body_parser_buffered.length; i++) { + this._body_parser_passthrough( + this._body_parser_buffered[i], + i === this._body_parser_buffered.length - 1 ? this.received : false, + ); + } + break; + // Streaming mode - External use only + case 2: + // Iterate over the buffered chunks and push them to the readable stream + for (const chunk of this._body_parser_buffered) { + // Convert Uint8Array into a Buffer chunk + const buffer = Buffer.from(chunk); + + // Push the buffer to the readable stream + // We will ignore the return value as we are not handling backpressure here + this.push(buffer); + } + + // If the request has been received at this point already, we must also push a null chunk to indicate the end of the stream + if (this.received) this.push(null); + break; + } + } + + // Deallocate the buffered chunks array as they are no longer needed + this._body_parser_buffered = null; + + // Resume the request in case we had paused the request due to having reached the max_body_buffer for this request + this.resume(); + } + + /** + * This method is called when the underlying Readable stream is initialized and begins expecting incoming data. + * @private + */ + _body_parser_stream_init() { + // Set the body parser mode to stream mode + this._body_parser_mode = 2; + + // Overwrite the underlying readable _read handler to resume the request when more chunks are requested + // This will properly handle backpressure and prevent the request from being paused forever + this._readable._read = () => this.resume(); + + // Flush the buffered chunks to the readable stream if we have any + this._body_parser_flush_buffered(); + } + + _received_data_promise; + /** + * Returns a single Uint8Array buffer which contains all incoming body data. + * @private + * @returns {Promise} + */ + _body_parser_get_received_data() { + // Return the current promise if it exists + if (this._received_data_promise) return this._received_data_promise; + + // If this is not a chunked transfer and we have no expected body length, we will return an empty buffer as we have no body data to parse + if (!this._body_chunked_transfer && this._body_expected_bytes <= 0) return Promise.resolve(new Uint8Array(0)); + + // Create a new promise which will be resolved once all incoming body data has been received + this._received_data_promise = new Promise((resolve) => { + // Determine if this is a chunked transfer + if (this._body_chunked_transfer) { + // Since we don't know how many or how much each chunk will be, we have to store all the chunks + // After all the chunks have been received, we will concatenate them into a single Uint8Array + const chunks = []; + + // Define a passthrough callback which will be called for each incoming chunk + this._body_parser_passthrough = (chunk, is_last) => { + // Push the chunk to the chunks array + chunks.push(chunk); + + // If this is the last chunk, call the callback with the body buffer + if (is_last) { + // Initialize a new Uint8Array of size received bytes + let offset = 0; + const buffer = new Uint8Array(this._body_received_bytes); + for (const chunk of chunks) { + // Write the chunk into the body buffer at the current offset + buffer.set(chunk, offset); + offset += chunk.byteLength; + } + + // Resolve the promise with the body buffer + resolve(buffer); + } + }; + } else { + // Initialize the full size body Uint8Array buffer based on the expected body length + // We will copy all volatile chunk data onto this stable buffer for memory efficiency + const buffer = new Uint8Array(this._body_expected_bytes); + + // Define a passthrough callback which will be called for each incoming chunk + let offset = 0; + this._body_parser_passthrough = (chunk, is_last) => { + // Write the chunk into the body buffer at the current offset + buffer.set(chunk, offset); + + // Increment the offset by the byteLength of the incoming chunk + offset += chunk.byteLength; + + // If this is the last chunk, call the callback with the body buffer + if (is_last) resolve(buffer); + }; + } + + // Set the body parser mode to buffering mode as we want to receive all incoming chunks through the passthrough callback + this._body_parser_mode = 1; + + // Flush the buffered chunks so the passthrough callback receives all buffered data through its callback + this._body_parser_flush_buffered(); + }); + + // Return the data promise + return this._received_data_promise; + } + + _body_buffer; + _buffer_promise; + /** + * Returns the incoming request body as a Buffer. + * @returns {Promise} + */ + buffer() { + // Check cache and return if body has already been parsed + if (this._body_buffer) return Promise.resolve(this._body_buffer); + + // Initialize the buffer promise if it does not exist + this._buffer_promise = new Promise((resolve) => + this._body_parser_get_received_data().then((raw) => { + // Convert the Uint8Array buffer into a Buffer + this._body_buffer = Buffer.from(raw); + + // Resolve the buffer promise with the body buffer + resolve(this._body_buffer); + }), + ); + + // Return the buffer promise + return this._buffer_promise; + } + + /** + * Decodes the incoming request body as a String. + * @private + * @param {Uint8Array} uint8 + * @param {string} encoding + * @returns {string} + */ + _uint8_to_string(uint8, encoding = 'utf-8') { + const decoder = new util.TextDecoder(encoding); + return decoder.decode(uint8); + } + + _body_text; + _text_promise; + /** + * Downloads and parses the request body as a String. + * @returns {Promise} + */ + text() { + // Resolve from cache if available + if (this._body_text) return Promise.resolve(this._body_text); + + // Initialize the text promise if it does not exist + this._text_promise = new Promise((resolve) => + this._body_parser_get_received_data().then((raw) => { + // Decode the Uint8Array buffer into a String + this._body_text = this._uint8_to_string(raw); + + // Resolve the text promise with the body text + resolve(this._body_text); + }), + ); + + // Return the text promise + return this._text_promise; + } + + _body_binary; + _binary_promise; + /** + * Downloads and parses the request body as a String. + * @returns {Promise} + */ + binary() { + // Resolve from cache if available + if (this._body_binary) return Promise.resolve(this._body_binary); + + // Initialize the text promise if it does not exist + this._binary_promise = new Promise((resolve) => + this._body_parser_get_received_data().then((raw) => { + // Convert the Uint8Array buffer into a Buffer + this._body_binary = Buffer.from(raw); + + // Resolve the text promise with the body text + resolve(this._body_binary); + }), + ); + + // Return the text promise + return this._text_promise; + } + + _body_json; + _json_promise; + /** + * Downloads and parses the request body as a JSON object. + * Passing default_value as null will lead to the function throwing an exception if invalid JSON is received. + * + * @param {Any=} default_value Default: {} + * @returns {Promise} + */ + json(default_value = {}) { + // Return from cache if available + if (this._body_json) return Promise.resolve(this._body_json); + + // Initialize the json promise if it does not exist + this._json_promise = new Promise((resolve, reject) => + this._body_parser_get_received_data().then((raw) => { + // Decode the Uint8Array buffer into a String + const text = this._uint8_to_string(raw); + try { + // Parse the text as JSON + this._body_json = JSON.parse(text); + } catch (error) { + if (default_value) { + // Use the default value if provided + this._body_json = default_value; + } else { + reject(error); + } + } + + // Resolve the json promise with the body json + resolve(this._body_json); + }), + ); + + // Return the json promise + return this._json_promise; + } + + _body_urlencoded; + _urlencoded_promise; + /** + * Parses and resolves an Object of urlencoded values from body. + * @returns {Promise} + */ + urlencoded() { + // Return from cache if available + if (this._body_urlencoded) return Promise.resolve(this._body_urlencoded); + + // Initialize the urlencoded promise if it does not exist + this._urlencoded_promise = new Promise((resolve) => + this._body_parser_get_received_data().then((raw) => { + // Decode the Uint8Array buffer into a String + const text = this._uint8_to_string(raw); + + // Parse the text as urlencoded + this._body_urlencoded = querystring.parse(text); + + // Resolve the urlencoded promise with the body urlencoded + resolve(this._body_urlencoded); + }), + ); + + // Return the urlencoded promise + return this._urlencoded_promise; + } + + _multipart_promise; + /** + * Handles incoming multipart fields from uploader and calls user specified handler with MultipartField. + * + * @private + * @param {Function} handler + * @param {String} name + * @param {String|stream.Readable} value + * @param {Object} info + */ + async _on_multipart_field(handler, name, value, info) { + // Create a MultipartField instance with the incoming information + const field = new MultipartField(name, value, info); + + // Check if a field is being handled by the user across a different exeuction + if (this._multipart_promise instanceof Promise) { + // Pause the request to prevent more fields from being received + this.pause(); + + // Wait for this field to be handled + if (this._multipart_promise) await this._multipart_promise; + + // Resume the request to accept more fields + this.resume(); + } + + // Determine if the handler is a synchronous function and returns a promise + const output = handler(field); + if (output instanceof Promise) { + // Store the promise, so concurrent multipart fields can wait for it + this._multipart_promise = output; + + // Hold the current exectution context until the promise resolves + if (this._multipart_promise) await this._multipart_promise; + + // Clear the promise reference + this._multipart_promise = null; + } + + // Flush this field's file stream if it has not been consumed by the user in the handler execution + // This is neccessary as defined in the Busboy documentation to prevent holding up the processing + if (field.file && !field.file.stream.readableEnded) field.file.stream.resume(); + } + + /** + * @typedef {function(MultipartField):void} SyncMultipartHandler + */ + + /** + * @typedef {function(MultipartField):Promise} AsyncMultipartHandler + */ + + /** + * @typedef {('PARTS_LIMIT_REACHED'|'FILES_LIMIT_REACHED'|'FIELDS_LIMIT_REACHED')} MultipartLimitReject + */ + + /** + * Downloads and parses incoming body as a multipart form. + * This allows for easy consumption of fields, values and files. + * + * @param {busboy.BusboyConfig|SyncMultipartHandler|AsyncMultipartHandler} options + * @param {(SyncMultipartHandler|AsyncMultipartHandler)=} handler + * @returns {Promise} A promise which is resolved once all multipart fields have been processed + */ + multipart(options, handler) { + // Migrate options to handler if no options object is provided by user + if (typeof options == 'function') { + handler = options; + options = {}; + } + + // Make a shallow copy of the options object + options = Object.assign({}, options); + + // Inject the request headers into the busboy options if not provided + if (!options.headers) options.headers = this.headers; + + // Ensure the provided handler is a function type + if (typeof handler !== 'function') + throw new Error('HyperExpress: Request.multipart(handler) -> handler must be a Function.'); + + // Resolve instantly if we have no readable body stream + if (this.readableEnded) return Promise.resolve(); + + // Resolve instantly if we do not have a valid multipart content type header + const content_type = this.headers['content-type']; + if (!/^(multipart\/.+);(.*)$/i.test(content_type)) return Promise.resolve(); + + // Return a promise which will be resolved after all incoming multipart data has been processed + const reference = this; + return new Promise((resolve, reject) => { + // Create a Busboy instance which will perform + const uploader = busboy(options); + + // Create a function to finish the uploading process + let finished = false; + const finish = async (error) => { + // Ensure we are not already finished + if (finished) return; + finished = true; + + // Determine if the caught error should be silenced + let silent_error = false; + if (error instanceof Error) { + // Silence the BusBoy "Unexpected end of form" error + // This usually happens when the client abruptly closes the connection + if (error.message == 'Unexpected end of form') silent_error = true; + } + + // Resolve/Reject the promise depending on whether an error occurred + if (error && !silent_error) { + // Reject the promise if an error occurred + reject(error); + } else { + // Wait for any pending multipart handler exeuction to complete + if (reference._multipart_promise) await reference._multipart_promise; + + // Resolve the promise if no error occurred + resolve(); + } + + // Stop the body parser from accepting any more incoming body data + reference._body_parser_stop(); + + // Destroy the uploader instance + uploader.destroy(); + }; + + // Bind an 'error' event handler to emit errors + uploader.once('error', finish); + + // Bind limit event handlers to reject as error code constants + uploader.once('partsLimit', () => finish('PARTS_LIMIT_REACHED')); + uploader.once('filesLimit', () => finish('FILES_LIMIT_REACHED')); + uploader.once('fieldsLimit', () => finish('FIELDS_LIMIT_REACHED')); + + // Define a function to handle incoming multipart data + const on_field = (name, value, info) => { + // Catch and pipe any errors from the value readable stream to the finish function + if (value instanceof stream.Readable) value.once('error', finish); + + // Call the user defined handler with the incoming multipart field + // Catch and pipe any errors to the finish function + reference._on_multipart_field(handler, name, value, info).catch(finish); + }; + + // Bind a 'field' event handler to process each incoming field + uploader.on('field', on_field); + + // Bind a 'file' event handler to process each incoming file + uploader.on('file', on_field); + + // Bind a 'finish' event handler to resolve the upload promise + uploader.once('close', () => { + // Wait for any pending multipart handler exeuction to complete + if (reference._multipart_promise) { + // Wait for the pending promise to resolve + // Use an anonymous callback for the .then() to prevent finish() from receving a resolved value which would lead to an error finish + reference._multipart_promise.then(() => finish()).catch(finish); + } else { + finish(); + } + }); + + // Pipe the readable request stream into the busboy uploader + reference.pipe(uploader); + }); + } + + /* HyperExpress Properties */ + + /** + * Returns the request locals for this request. + * @returns {Object.} + */ + get locals() { + // Initialize locals object if it does not exist + if (!this._locals) this._locals = {}; + return this._locals; + } + + /** + * Returns the HyperExpress.Server instance this Request object originated from. + * @returns {import('../Server.js')} + */ + get app() { + return this.route.app; + } + + /** + * Returns whether this request is in a paused state and thus not consuming any body chunks. + * @returns {Boolean} + */ + get paused() { + return this._paused; + } + + /** + * Returns HTTP request method for incoming request in uppercase. + * @returns {String} + */ + get method() { + // Enforce uppercase for the returned method value + const uppercase = this._method.toUpperCase(); + + // For some reason, uWebsockets.js populates DELETE requests as DEL hence this translation + return uppercase === 'DEL' ? 'DELETE' : uppercase; + } + + /** + * Returns full request url for incoming request (path + query). + * @returns {String} + */ + get url() { + // Return from cache if available + if (this._url) return this._url; + + // Parse the incoming request url + this._url = this._path + (this._query ? '?' + this._query : ''); + + // Return the url + return this._url; + } + + /** + * Returns path for incoming request. + * @returns {String} + */ + get path() { + return this._path; + } + + /** + * Returns query for incoming request without the '?'. + * @returns {String} + */ + get path_query() { + return this._query; + } + + /** + * Returns request cookies from incoming request. + * @returns {Object.} + */ + get cookies() { + // Return from cache if already parsed once + if (this._cookies) return this._cookies; + + // Parse cookies from Cookie header and cache results + const header = this.headers['cookie']; + this._cookies = header ? cookie.parse(header) : {}; + + // Return the cookies + return this._cookies; + } + + /** + * Returns path parameters from incoming request. + * @returns {Object.} + */ + get path_parameters() { + return this._path_parameters || {}; + } + + /** + * Returns query parameters from incoming request. + * @returns {Object.} + */ + get query_parameters() { + // Return from cache if already parsed once + if (this._query_parameters) return this._query_parameters; + + // Parse query using querystring and cache results + this._query_parameters = querystring.parse(this._query); + return this._query_parameters; + } + + /** + * Returns remote IP address in string format from incoming request. + * Note! You cannot call this method after the response has been sent or ended. + * @returns {String} + */ + get ip() { + // Resolve IP from cache if already resolved + if (this._remote_ip) return this._remote_ip; + + // Ensure request has not ended yet + if (this._request_ended) + throw new Error('HyperExpress.Request.ip cannot be consumed after the Request/Response has ended.'); + + // Determine if we can trust intermediary proxy servers and have a x-forwarded-for header + const x_forwarded_for = this.get('X-Forwarded-For'); + const trust_proxy = this.route.app._options.trust_proxy; + if (trust_proxy && x_forwarded_for) { + // The first IP in the x-forwarded-for header is the client IP if we trust proxies + this._remote_ip = x_forwarded_for.split(',')[0]; + } else { + // Use the uWS detected connection IP address as a fallback + this._remote_ip = array_buffer_to_string(this._raw_response.getRemoteAddressAsText()); + } + + // Return Remote IP + return this._remote_ip; + } + + /** + * Returns remote proxy IP address in string format from incoming request. + * Note! You cannot call this method after the response has been sent or ended. + * @returns {String} + */ + get proxy_ip() { + // Resolve IP from cache if already resolved + if (this._remote_proxy_ip) return this._remote_proxy_ip; + + // Ensure request has not ended yet + if (this._request_ended) + throw new Error('HyperExpress.Request.proxy_ip cannot be consumed after the Request/Response has ended.'); + + // Parse and cache remote proxy IP from uWS + this._remote_proxy_ip = array_buffer_to_string(this._raw_response.getProxiedRemoteAddressAsText()); + + // Return Remote Proxy IP + return this._remote_proxy_ip; + } + + /** + * Throws an ERR_INCOMPATIBLE_CALL error with the provided property/method name. + * @private + */ + _throw_unsupported(name) { + throw new Error( + `ERR_INCOMPATIBLE_CALL: One of your middlewares or route logic tried to call Request.${name} which is unsupported with HyperExpress.`, + ); + } + + setDto(dto) { + this._dto = dto; + } + + dto() { + return this._dto; + } + + body() { + return this._body; + } + + _body; + _body_promise; + async processBody() { + if (this._body) return Promise.resolve(this._body); + const contentType = this.headers['content-type'] || ''; + this._body_promise = new Promise(async (resolve, reject) => { + let bodyData = undefined; + + if (contentType === 'application/json') { + bodyData = await this.json(); + } else if (contentType === 'application/x-www-form-urlencoded') { + bodyData = await this.urlencoded(); + } else if (contentType.includes('multipart/form-data')) { + bodyData = await process_multipart_data(this); + } else if (contentType === 'text/plain') { + bodyData = { $body: await this.text() }; + } else if (contentType === 'text/html' || contentType === 'application/xml') { + bodyData = { $body: (await this.buffer()).toString() }; + } else { + bodyData = { $body: await this.buffer() }; + } + + this._body = bodyData; + + resolve(this._body); + }); + + return this._body_promise; + } + + _all_input; + async all() { + if (this._all_input) return this._all_input; + + this._all_input = { + ...(this.query_parameters || {}), + ...(this.path_parameters || {}), + ...(this._body || {}), + }; + + return this._all_input; + } + + hasHeader(name) { + return name in this.headers; + } + + bearerToken() { + const authHeader = this.headers['authorization']; + const asArray = authHeader?.split(' '); + if (asArray?.length) return asArray[1]; + return undefined; + } + + httpHost() { + return this.protocol; + } + + isHttp() { + return this.httpHost() === 'http'; + } + + isHttps() { + return this.httpHost() === 'https'; + } + + fullUrl() { + return this.url; + } + + isMethod(method) { + return this.method.toLowerCase() === method.toLowerCase(); + } + + contentType() { + return this.headers['content-type']; + } + + getAcceptableContentTypes() { + return this.headers['accept']; + } + + accepts() { + return (this.headers['accept'] || '').split(','); + } + + expectsJson() { + return this.accepts().includes(EXTENSTION_TO_MIME['json']); + } + + setUser(user) { + this._user = user; + } + + user() { + return this._user; + } + + isPath(pathPattern) { + return this.path === pathPattern; + } + + hasHeaders(...keys) { + return keys.every((key) => key in this.headers); + } + + _validatorClass; + setValidator(cls) { + this._validatorClass = cls; + } + + async validate(schema) { + const payload = await this.all(); + const validator = this._validatorClass.compareWith(schema); + const dto = await validator.addMeta({ ...payload }).validate({ ...payload }); + this.setDto(dto); + return true; + } + + file(key) { + const dataAtKey = this._body[key]; + if (Array.isArray(dataAtKey)) { + for (const dataAtIndex of dataAtKey) { + if (dataAtIndex instanceof UploadedFile) return dataAtKey; + } + } else if (dataAtKey instanceof UploadedFile) { + return dataAtKey; + } + + return undefined; + } +} + +// Inherit the compatibility classes +inherit_prototype({ + from: [NodeRequest.prototype, ExpressRequest.prototype], + to: Request.prototype, + method: (type, name, original) => { + // Return an anonymous function which calls the original function with Request scope + return function () { + // Call the original function with the Request scope + return original.apply(this, arguments); + }; + }, +}); + +// Inherit the stream.Readable prototype and lazy initialize the stream on first call to inherited methods +inherit_prototype({ + from: stream.Readable.prototype, + to: Request.prototype, + override: (name) => '_super_' + name, // Prefix all overrides with _super_ + method: (type, name, original) => { + // Initialize a pass through method + const passthrough = function () { + // Determine if the underlying readable stream has not been initialized yet + if (this._readable === null) { + // Initialize the readable stream with the route's streaming configuration + this._readable = new stream.Readable(this.route.streaming.readable); + + // Trigger the readable stream initialization event + this._body_parser_stream_init(); + } + + // Return the original function with the readable stream as the context + return original.apply(this._readable, arguments); + }; + + return passthrough; + }, +}); + +module.exports = Request; diff --git a/packages/hyper-express/src/components/http/Response.js b/packages/hyper-express/src/components/http/Response.js new file mode 100644 index 0000000..447caee --- /dev/null +++ b/packages/hyper-express/src/components/http/Response.js @@ -0,0 +1,1012 @@ +'use strict'; +const crypto = require('crypto'); +const cookie = require('cookie'); +const signature = require('cookie-signature'); +const status_codes = require('http').STATUS_CODES; +const mime_types = require('mime-types'); +const stream = require('stream'); + +const NodeResponse = require('../compatibility/NodeResponse.js'); +const ExpressResponse = require('../compatibility/ExpressResponse.js'); +const { inherit_prototype } = require('../../shared/operators.js'); + +const FilePool = {}; +const LiveFile = require('../plugins/LiveFile.js'); +const SSEventStream = require('../plugins/SSEventStream.js'); + +class Response { + _sse; + _locals; + route = null; + _corked = false; + _streaming = false; + _middleware_cursor; + _wrapped_request = null; + _upgrade_socket = null; + _raw_response = null; + + /** + * Returns the custom HTTP underlying status code of the response. + * @private + * @type {Number=} + */ + _status_code; + + /** + * Returns the custom HTTP underlying status code message of the response. + * @private + * @type {String=} + */ + _status_message; + + /** + * Contains underlying headers for the response. + * @private + * @type {Record} + */ + _cookies; + + /** + * Underlying lazy initialized writable body stream. + * @private + */ + _writable = null; + + /** + * Whether this response needs to cork before sending. + * @private + */ + _cork = false; + + /** + * Alias of aborted property as they both represent the same request state in terms of inaccessibility. + * @returns {Boolean} + */ + completed = false; + + /** + * Returns whether response has been initiated by writing the HTTP status code and headers. + * Note! No changes can be made to the HTTP status code or headers after a response has been initiated. + * @returns {Boolean} + */ + initiated = false; + + /** + * Creates a new HyperExpress response instance that wraps a uWS.HttpResponse instance. + * + * @param {import('uWebSockets.js').HttpResponse} raw_response + */ + constructor(raw_response) { + this._raw_response = raw_response; + + // Bind the abort handler as required by uWebsockets.js for each uWS.HttpResponse to allow for async processing + raw_response.onAborted(() => { + // If this request has already been completed then this request cannot be aborted again + if (this.completed) return; + this.completed = true; + + // Decrement the pending request count + this.route.app._resolve_pending_request(); + + // Stop the body parser from accepting any more data + this._wrapped_request._body_parser_stop(); + + // Ensure we have a writable/emitter instance to emit over + if (this._writable) { + // Emit an 'abort' event to signify that the client aborted the request + this.emit('abort', this._wrapped_request, this); + + // Emit an 'close' event to signify that the client has disconnected + this.emit('close', this._wrapped_request, this); + } + }); + } + + /* HyperExpress Methods */ + + /** + * Tracks middleware cursor position over a request's lifetime. + * This is so we can detect any double middleware iterations and throw an error. + * @private + * @param {Number} position - Cursor position + */ + _track_middleware_cursor(position) { + // Track and ensure each middleware cursor value is greater than previously tracked value for sequential progression + if (this._middleware_cursor === undefined || position > this._middleware_cursor) + return (this._middleware_cursor = position); + + // If position is not greater than last cursor then we likely have a double middleware execution + this.throw( + new Error( + 'ERR_DOUBLE_MIDDLEWARE_EXEUCTION_DETECTED: Please ensure you are not calling the next() iterator inside of an ASYNC middleware. You must only call next() ONCE per middleware inside of SYNCHRONOUS middlewares only!' + ) + ); + } + + /* Response Methods/Operators */ + + /** + * Alias of `uWS.HttpResponse.cork()` which allows for manual corking of the response. + * This is required by `uWebsockets.js` to maximize network performance with batched writes. + * + * @param {Function} handler + * @returns {Response} Response (Chainable) + */ + atomic(handler) { + // Cork the provided handler if the response is not finished yet + if (!this.completed) this._raw_response.cork(handler); + + // Make this chainable + return this; + } + + /** + * This method is used to set a custom response code. + * + * @param {Number} code Example: response.status(403) + * @param {String=} message Example: response.status(403, 'Forbidden') + * @returns {Response} Response (Chainable) + */ + status(code, message) { + // Set the numeric status code. Status text is appended before writing status to uws + this._status_code = code; + this._status_message = message; + return this; + } + + /** + * This method is used to set the response content type header based on the provided mime type. Example: type('json') + * + * @param {String} mime_type Mime type + * @returns {Response} Response (Chainable) + */ + type(mime_type) { + // Remove leading dot from mime type if present + if (mime_type[0] === '.') mime_type = mime_type.substring(1); + + // Determine proper mime type and send response + this.header('content-type', mime_types.contentType(mime_type) || 'text/plain'); + return this; + } + + /** + * This method can be used to write a response header and supports chaining. + * + * @param {String} name Header Name + * @param {String|String[]} value Header Value + * @param {Boolean=} overwrite If true, overwrites existing header value with same name + * @returns {Response} Response (Chainable) + */ + header(name, value, overwrite) { + // Enforce lowercase for header name + name = name.toLowerCase(); + + // Determine if this operation is an overwrite onto any existing header values + if (overwrite) { + // Overwrite the header value + this._headers[name] = value; + + // Check if some value(s) already exist for this header name + } else if (this._headers[name]) { + // Check if there are multiple current values for this header name + if (Array.isArray(this._headers[name])) { + // Check if the provided value is an array + if (Array.isArray(value)) { + // Concatenate the current and provided header values + this._headers[name] = this._headers[name].concat(value); + } else { + // Push the provided header value to the current header values array + this._headers[name].push(value); + } + } else { + // Convert the current header value to an array + this._headers[name] = [this._headers[name], value]; + } + } else { + // Write the header value + this._headers[name] = value; + } + + // Make chainable + return this; + } + + /** + * @typedef {Object} CookieOptions + * @property {String} domain + * @property {String} path + * @property {Number} maxAge + * @property {Boolean} secure + * @property {Boolean} httpOnly + * @property {Boolean|'none'|'lax'|'strict'} sameSite + * @property {String} secret + */ + + /** + * This method is used to write a cookie to incoming request. + * To delete a cookie, set the value to null. + * + * @param {String} name Cookie Name + * @param {String|null} value Cookie Value + * @param {Number=} expiry In milliseconds + * @param {CookieOptions=} options Cookie Options + * @param {Boolean=} sign_cookie Enables/Disables Cookie Signing + * @returns {Response} Response (Chainable) + */ + cookie(name, value, expiry, options, sign_cookie = true) { + // Determine if this is a delete operation and recursively call self with appropriate options + if (name && value === null) + return this.cookie(name, '', null, { + maxAge: 0, + }); + + // If an options object was not provided, shallow copy it to prevent mutation to the original object + // If an options object was not provided, create a new object with default options + options = options + ? { ...options } + : { + secure: true, + sameSite: 'none', + path: '/', + }; + + // Determine if a expiry duration was provided in milliseconds + if (typeof expiry == 'number') { + // Set the expires value of the cookie if one was not already defined + options.expires = options.expires || new Date(Date.now() + expiry); + + // Define a max age if one was not already defined + options.maxAge = options.maxAge || Math.round(expiry / 1000); + } + + // Sign cookie value if signing is enabled and a valid secret is provided + if (sign_cookie && typeof options.secret == 'string') { + options.encode = false; // Turn off encoding to prevent loss of signature structure + value = signature.sign(value, options.secret); + } + + // Initialize the cookies holder object if it does not exist + if (this._cookies == undefined) this._cookies = {}; + + // Store the seralized cookie value to be written during response + this._cookies[name] = cookie.serialize(name, value, options); + return this; + } + + /** + * This method is used to upgrade an incoming upgrade HTTP request to a Websocket connection. + * @param {Object=} context Store information about the websocket connection + */ + upgrade(context) { + // Do not allow upgrades if request is already completed + if (this.completed) return; + + // Ensure a upgrade_socket exists before upgrading ensuring only upgrade handler requests are handled + if (this._upgrade_socket == null) + this.throw( + new Error( + 'HyperExpress: You cannot upgrade a request that does not come from an upgrade handler. No upgrade socket was found.' + ) + ); + + // Resume the request in case it was paused + this._wrapped_request.resume(); + + // Cork the response if it has not been corked yet for when this was handled asynchonously + if (this._cork && !this._corked) { + this._corked = true; + return this.atomic(() => this.upgrade(context)); + } + + // Call uWS.Response.upgrade() method with user data, protocol headers and uWS upgrade socket + const headers = this._wrapped_request.headers; + this._raw_response.upgrade( + { + context, + }, + headers['sec-websocket-key'], + headers['sec-websocket-protocol'], + headers['sec-websocket-extensions'], + this._upgrade_socket + ); + + // Mark request as complete so no more operations can be performed + this.completed = true; + + // Decrement the pending request count + this.route.app._resolve_pending_request(); + } + + /** + * Initiates response process by writing HTTP status code and then writing the appropriate headers. + * @private + * @returns {Boolean} + */ + _initiate_response() { + // Halt execution if response has already been initiated or completed + if (this.initiated) return false; + + // Emit the 'prepare' event to allow for any last minute response modifications + if (this._writable) this.emit('prepare', this._wrapped_request, this); + + // Mark the instance as initiated signifying that no more status/header based operations can be performed + this.initiated = true; + + // Resume the request in case it was paused + this._wrapped_request.resume(); + + // Write the appropriate status code to the response along with mapped status code message + if (this._status_code || this._status_message) + this._raw_response.writeStatus( + this._status_code + ' ' + (this._status_message || status_codes[this._status_code]) + ); + + // Iterate through all headers and write them to uWS + for (const name in this._headers) { + // If this is a custom content-length header, we need to skip it as we will write it later during the response send + if (name == 'content-length') continue; + + // Write the header value to uWS + const values = this._headers[name]; + if (Array.isArray(values)) { + // Write each individual header value to uWS as there are multiple headers + for (const value of values) { + this._raw_response.writeHeader(name, value); + } + } else { + // Write the single header value to uWS + this._raw_response.writeHeader(name, values); + } + } + + // Iterate through all cookies and write them to uWS + if (this._cookies) { + for (const name in this._cookies) { + this._raw_response.writeHeader('set-cookie', this._cookies[name]); + } + } + + // Signify that the response was successfully initiated + return true; + } + + _drain_handler = null; + /** + * Binds a drain handler which gets called with a byte offset that can be used to try a failed chunk write. + * You MUST perform a write call inside the handler for uWS chunking to work properly. + * You MUST return a boolean value indicating if the write was successful or not. + * Note! This method can only provie drain events to a single handler at any given time which means If you call this method again with a different handler, it will stop providing drain events to the previous handler. + * + * @param {function(number):boolean} handler Synchronous callback only + */ + drain(handler) { + // Determine if this is the first time the drain handler is being set + const is_first_time = this._drain_handler === null; + + // Store the handler which will be used to provide drain events to uWS + this._drain_handler = handler; + + // Bind a writable handler with a fallback return value to true as uWS expects a Boolean + if (is_first_time) + this._raw_response.onWritable((offset) => { + // Retrieve the write result from the handler + const output = this._drain_handler(offset); + + // Throw an exception if the handler did not return a boolean value as that is an improper implementation + if (typeof output !== 'boolean') + this.throw( + new Error( + 'HyperExpress: Response.drain(handler) -> handler must return a boolean value stating if the write was successful or not.' + ) + ); + + // Return the boolean value to uWS as required by uWS documentation + return output; + }); + } + + /** + * Writes the provided chunk to the client over uWS with backpressure handling if a callback is provided. + * + * @private + * @param {String|Buffer|ArrayBuffer} chunk + * @param {String} encoding + * @param {Function} callback + */ + _write(chunk, encoding, callback) { + // Spread the arguments to allow for a single object argument + if (chunk.chunk && chunk.encoding) { + // Pull out the chunk and encoding from the object argument + const temp = chunk; + chunk = temp.chunk; + encoding = temp.encoding; + + // Only use the callback from this specific chunk if one is not provided + // This is because we want to respect the iteratore callback from the _writev method + if (!callback) callback = temp.callback; + } + + // Ensure this request has not been completed yet + if (!this.completed) { + // If this response has not be marked as an active stream, mark it as one and bind a 'finish' event handler to send response once a piped stream has completed + if (!this._streaming) { + this._streaming = true; + this.once('finish', () => this.send()); + } + + // Attempt to write the chunk to the client with backpressure handling + this._stream_chunk(chunk).then(callback).catch(callback); + } else { + // Trigger callback to flush the chunk as the response has already been completed + callback(); + } + } + + /** + * Writes multiples chunks for the response to the client over uWS with backpressure handling if a callback is provided. + * + * @private + * @param {Array} chunks + * @param {Function} callback + * @param {number} index + */ + _writev(chunks, callback, index = 0) { + // Serve the chunk at the current index + this._write(chunks[index], null, (error) => { + // Pass the error to the callback if one was provided + if (error) return callback(error); + + // Trigger the specific callback for the chunk we just served if it was in object format + if (typeof chunks[index].callback == 'function') chunks[index].callback(); + + // Determine if we have more chunks after the chunk we just served + if (index < chunks.length - 1) { + // Recursively serve the remaining chunks + this._writev(chunks, callback, index + 1); + } else { + // Trigger the callback as all chunks have been served + callback(); + } + }); + } + + /** + * This method is used to end the current request and send response with specified body and headers. + * + * @param {String|Buffer|ArrayBuffer=} body Optional + * @param {Boolean=} close_connection + * @returns {Response} + */ + send(body, close_connection) { + // Ensure response connection is still active + if (!this.completed) { + // If this request has a writable stream with some data in it, we must schedule this send() as the last chunk after which the stream will be flushed + if (this._writable && this._writable.writableLength) { + // If we have some body data, queue it as the last chunk of the body to be written + if (body) this._writable.write(body); + + // Mark the writable stream as ended + this._writable.end(); + + // Return this to make the Router chainable + return this; + } + + // If the response has not been corked yet, cork it and wait for the next tick to send the response + if (this._cork && !this._corked) { + this._corked = true; + return this.atomic(() => this.send(body, close_connection)); + } + + // Initiate the response to begin writing the status code and headers + this._initiate_response(); + + // Determine if the request still has not fully received the whole request body yet + if (!this._wrapped_request.received) { + // Instruct the request to stop accepting any more data as a response is being sent + this._wrapped_request._body_parser_stop(); + + // Wait for the request to fully receive the whole request body before sending the response + return this._wrapped_request.once('received', () => + // Because 'received' will be emitted asynchronously, we need to cork the response to ensure the response is sent in the correct order + this.atomic(() => this.send(body, close_connection)) + ); + } + + // If we have no body and are not streaming and have a custom content-length header, we need to send a response without a body with the custom content-length header + const custom_length = this._headers['content-length']; + if (!(body !== undefined || this._streaming || !custom_length)) { + // We can only use one of the content-lengths, so we will use the last one if there are multiple + const content_length = + typeof custom_length == 'string' ? custom_length : custom_length[custom_length.length - 1]; + + // Send the response with the uWS.HttpResponse.endWithoutBody() method as we have no body data + // NOTE: This method is completely undocumented by uWS but exists in the source code to solve the problem of no body being sent with a custom content-length + this._raw_response.endWithoutBody(content_length, close_connection); + } else { + // Send the response with the uWS.HttpResponse.end(body, close_connection) method as we have some body data + this._raw_response.end(body, close_connection); + } + + // Emit the 'finish' event to signify that the response has been sent without streaming + if (this._writable && !this._streaming) this.emit('finish', this._wrapped_request, this); + + // Mark request as completed as it has been sent + this.completed = true; + + // Decrement the pending request count + this.route.app._resolve_pending_request(); + + // Emit the 'close' event to signify that the response has been completed + if (this._writable) this.emit('close', this._wrapped_request, this); + } + + // Make chainable + return this; + } + + /** + * Writes a given chunk to the client over uWS with the appropriate writing method. + * Note! This method uses `uWS.tryEnd()` when a `total_size` is provided. + * Note! This method uses `uWS.write()` when a `total_size` is not provided. + * + * @private + * @param {Buffer} chunk + * @param {Number=} total_size + * @returns {Array} [sent, finished] + */ + _uws_write_chunk(chunk, total_size) { + // The specific uWS method to stream the chunk to the client differs depending on if we have a total_size or not + let sent, finished; + if (total_size) { + // Attempt to stream the current chunk using uWS.tryEnd with a total size + const [ok, done] = this._raw_response.tryEnd(chunk, total_size); + sent = ok; + finished = done; + } else { + // Attempt to stream the current chunk uWS.write() + sent = this._raw_response.write(chunk); + + // Since we are streaming without a total size, we are not finished + finished = false; + } + + // Return the sent and finished booleans + return [sent, finished]; + } + + /** + * Stream an individual chunk to the client with backpressure handling. + * Delivers with chunked transfer and without content-length header when no total_size is specified. + * Delivers with chunk writes and content-length header when a total_size is specified. + * Calls the `callback` once the chunk has been fully sent to the client. + * + * @private + * @param {Buffer} chunk + * @param {Number=} total_size + * @returns {Promise} + */ + _stream_chunk(chunk, total_size) { + // If the request has already been completed, we can resolve the promise immediately as we cannot write to the client anymore + if (this.completed) return Promise.resolve(); + + // Return a Promise which resolves once the chunk has been fully sent to the client + return new Promise((resolve) => + this.atomic(() => { + // Ensure the client is still connected after the cork + if (this.completed) return resolve(); + + // Initiate the response to ensure status code & headers get written first if they have not been written yet + this._initiate_response(); + + // Remember the initial write offset for future backpressure sliced chunks + // Write the chunk to the client using the appropriate uWS chunk writing method + const write_offset = this.write_offset; + const [sent] = this._uws_write_chunk(chunk, total_size); + if (sent) { + // The chunk was fully sent, we can resolve the promise + resolve(); + } else { + // Bind a drain handler to relieve backpressure + // Note! This callback may be called as many times as neccessary to send a full chunk when using the tryEnd method + this.drain((offset) => { + // Check if the response has been completed / connection has been closed since we can no longer write to the client + // When total_size is not provided, the chunk has been fully sent already via uWS.write() + // Only when total_size is provided we need to retry to send the ramining chunk since we have used uWS.tryEnd() and + // that does not guarantee that the whole chunk has been sent when stream is drained + if (this.completed || !total_size) { + resolve(); + return true; + } + + // Attempt to write the remaining chunk to the client + const remaining = chunk.slice(offset - write_offset); + const [flushed] = this._uws_write_chunk(remaining, total_size); + if (flushed) resolve(); + + // Return the flushed boolean as not flushed means we are still waiting for more drain events from uWS + return flushed; + }); + } + }) + ); + } + + /** + * This method is used to serve a readable stream as response body and send response. + * By default, this method will use chunked encoding transfer to stream data. + * If your use-case requires a content-length header, you must specify the total payload size. + * + * @param {stream.Readable} readable A Readable stream which will be consumed as response body + * @param {Number=} total_size Total size of the Readable stream source in bytes (Optional) + * @returns {Promise} a Promise which resolves once the stream has been fully consumed and response has been sent + */ + async stream(readable, total_size) { + // Ensure readable is an instance of a stream.Readable + if (!(readable instanceof stream.Readable)) + this.throw( + new Error('HyperExpress: Response.stream(readable, total_size) -> readable must be a Readable stream.') + ); + + // Do not allow streaming if response has already been aborted or completed + if (!this.completed) { + // Bind an 'close' event handler which will destroy the consumed stream if request is closed + this.once('close', () => (!readable.destroyed ? readable.destroy() : null)); + + // Define a while loop to consume chunks from the readable stream until it is fully consumed or the response has been completed + while (!this.completed && !(readable.readableEnded || readable.destroyed)) { + // Attempt to read a chunk from the readable stream + let chunk = readable.read(); + if (!chunk) { + // Wait for the readable stream to emit a 'readable' event if no chunk was available in our initial read attempt + await new Promise((resolve) => { + // Bind a 'end' handler in case the readable stream ends before emitting a 'readable' event + readable.once('end', resolve); + + // Bind a 'readable' handler to resolve the promise once a chunk is available to read + readable.once('readable', () => { + // Unbind the 'end' handler as we have a chunk available to read + readable.removeListener('end', resolve); + + // Resolve the promise to continue the while loop + resolve(); + }); + }); + + // Attempt to read a chunk from the readable stream again + chunk = readable.read(); + } + + // Stream the chunk to the client + if (chunk) await this._stream_chunk(chunk, total_size); + } + + // If we had no total size and the response is still not completed, we need to end the response + // This is because no total size means we served with chunked encoding and we need to end the response as it is a unbounded stream + if (!this.completed) { + // Determine if we have a total size or not + if (total_size) { + // We must call the decrement method as a conventional close would not be detected + this.route.app._resolve_pending_request(); + } else { + // We must manually close the response as this stream operation is unbounded + this.send(); + } + } + } + } + + /** + * Instantly aborts/closes current request without writing a status response code. + * Use this to instantly abort a request where a proper response with an HTTP status code is not neccessary. + */ + close() { + // Ensure request has already not been completed + if (!this.completed) { + // Mark request as completed + this.completed = true; + + // Decrement the pending request count + this.route.app._resolve_pending_request(); + + // Stop the body parser from accepting any more data + this._wrapped_request._body_parser_stop(); + + // Resume the request in case it was paused + this._wrapped_request.resume(); + + // Close the underlying uWS request + this._raw_response.close(); + } + } + + /** + * This method is used to redirect an incoming request to a different url. + * + * @param {String} url Redirect URL + * @returns {Boolean} Boolean + */ + redirect(url) { + if (!this.completed) return this.status(302).header('location', url).send(); + return false; + } + + /** + * This method is an alias of send() method except it accepts an object and automatically stringifies the passed payload object. + * + * @param {Object} body JSON body + * @returns {Boolean} Boolean + */ + json(body) { + return this.header('content-type', 'application/json', true).send(JSON.stringify(body)); + } + + /** + * This method is an alias of send() method except it accepts an object + * and automatically stringifies the passed payload object with a callback name. + * Note! This method uses 'callback' query parameter by default but you can specify 'name' to use something else. + * + * @param {Object} body + * @param {String=} name + * @returns {Boolean} Boolean + */ + jsonp(body, name) { + let query_parameters = this._wrapped_request.query_parameters; + let method_name = query_parameters['callback'] || name; + return this.header('content-type', 'application/javascript', true).send(`${method_name}(${JSON.stringify(body)})`); + } + + /** + * This method is an alias of send() method except it automatically sets + * html as the response content type and sends provided html response body. + * + * @param {String} body + * @returns {Boolean} Boolean + */ + html(body) { + return this.header('content-type', 'text/html', true).send(body); + } + + /** + * @private + * Sends file content with appropriate content-type header based on file extension from LiveFile. + * + * @param {LiveFile} live_file + * @param {function(Object):void} callback + */ + async _send_file(live_file, callback) { + // Wait for LiveFile to be ready before serving + if (!live_file.is_ready) await live_file.ready(); + + // Write appropriate extension type if one has not been written yet + this.type(live_file.extension); + + // Send response with file buffer as body + this.send(live_file.buffer); + + // Execute callback with cache pool, so user can expire as they wish. + if (callback) setImmediate(() => callback(FilePool)); + } + + /** + * This method is an alias of send() method except it sends the file at specified path. + * This method automatically writes the appropriate content-type header if one has not been specified yet. + * This method also maintains its own cache pool in memory allowing for fast performance. + * Avoid using this method to a send a large file as it will be kept in memory. + * + * @param {String} path + * @param {function(Object):void=} callback Executed after file has been served with the parameter being the cache pool. + */ + file(path, callback) { + // Send file from local cache pool if available + if (FilePool[path]) return this._send_file(FilePool[path], callback); + + // Create new LiveFile instance in local cache pool for new file path + FilePool[path] = new LiveFile({ + path, + }); + + // Assign error handler to live file + FilePool[path].on('error', (error) => this.throw(error)); + + // Serve file as response + this._send_file(FilePool[path], callback); + } + + /** + * Writes approriate headers to signify that file at path has been attached. + * + * @param {String} path + * @param {String=} name + * @returns {Response} + */ + attachment(path, name) { + // Attach a blank content-disposition header when no filename is defined + if (path == undefined) return this.header('Content-Disposition', 'attachment'); + + // Parses path in to file name and extension to write appropriate attachment headers + let chunks = path.split('/'); + let final_name = name || chunks[chunks.length - 1]; + let name_chunks = final_name.split('.'); + let extension = name_chunks[name_chunks.length - 1]; + return this.header('content-disposition', `attachment; filename="${final_name}"`).type(extension); + } + + /** + * Writes appropriate attachment headers and sends file content for download on user browser. + * This method combined Response.attachment() and Response.file() under the hood, so be sure to follow the same guidelines for usage. + * + * @param {String} path + * @param {String=} filename + */ + download(path, filename) { + return this.attachment(path, filename).file(path); + } + + #thrown = false; + /** + * This method allows you to throw an error which will be caught by the global error handler. + * + * @param {Error} error + * @returns {Response} + */ + throw(error) { + // If we have already thrown an error, ignore further throws + if (this.#thrown) return this; + this.#thrown = true; + + // If the error is not an instance of Error, wrap it in an Error object that + if (!(error instanceof Error)) error = new Error(`ERR_CAUGHT_NON_ERROR_TYPE: ${error}`); + + // Trigger the global error handler + this.route.app.handlers.on_error(this._wrapped_request, this, error); + + // Return this response instance + return this; + } + + /* HyperExpress Properties */ + + /** + * Returns the request locals for this request. + * @returns {Object.} + */ + get locals() { + // Initialize locals object if it does not exist + if (!this._locals) this._locals = {}; + return this._locals; + } + + /** + * Returns the underlying raw uWS.Response object. + * Note! Utilizing any of uWS.Response's methods after response has been sent will result in an invalid discarded access error. + * @returns {import('uWebSockets.js').Response} + */ + get raw() { + return this._raw_response; + } + + /** + * Returns the HyperExpress.Server instance this Response object originated from. + * + * @returns {import('../Server.js')} + */ + get app() { + return this.route.app; + } + + /** + * Returns current state of request in regards to whether the source is still connected. + * @returns {Boolean} + */ + get aborted() { + return this.completed; + } + + /** + * Upgrade socket context for upgrade requests. + * @returns {import('uWebSockets.js').ux_socket_context} + */ + get upgrade_socket() { + return this._upgrade_socket; + } + + /** + * Returns a "Server-Sent Events" connection object to allow for SSE functionality. + * This property will only be available for GET requests as per the SSE specification. + * + * @returns {SSEventStream=} + */ + get sse() { + // Return a new SSE instance if one has not been created yet + if (this._wrapped_request.method === 'GET') { + // Initialize the SSE instance if one has not been created yet + if (this._sse === undefined) { + this._sse = new SSEventStream(); + + // Provide the response object to the SSE instance + this._sse._response = this; + } + + // Return the SSE instance + return this._sse; + } + } + + /** + * Returns the current response body content write offset in bytes. + * Use in conjunction with the drain() offset handler to retry writing failed chunks. + * Note! This method will return `-1` after the Response has been completed and the connection has been closed. + * @returns {Number} + */ + get write_offset() { + return this.completed ? -1 : this._raw_response.getWriteOffset(); + } + + /** + * Throws a descriptive error when an unsupported ExpressJS property/method is invocated. + * @private + * @param {String} name + */ + _throw_unsupported(name) { + throw new Error( + `ERR_INCOMPATIBLE_CALL: One of your middlewares or route logic tried to call Response.${name} which is unsupported with HyperExpress.` + ); + } +} + +// Store the descriptors of the original HyperExpress.Response class +const descriptors = Object.getOwnPropertyDescriptors(Response.prototype); + +// Inherit the compatibility classes +inherit_prototype({ + from: [NodeResponse.prototype, ExpressResponse.prototype], + to: Response.prototype, + method: (type, name, original) => { + // Initialize a passthrough method for each descriptor + const passthrough = function () { + // Call the original function with the Request scope + return original.apply(this, arguments); + }; + + // Return the passthrough function + return passthrough; + }, +}); + +// Inherit the stream.Writable prototype and lazy initialize the stream on first call to any inherited method +inherit_prototype({ + from: stream.Writable.prototype, + to: Response.prototype, + override: (name) => '_super_' + name, // Prefix all overrides with _super_ + method: (type, name, original) => { + // Initialize a pass through method + const passthrough = function () { + // Lazy initialize the writable stream on local scope + if (this._writable === null) { + // Initialize the writable stream + this._writable = new stream.Writable(this.route.streaming.writable); + + // Bind the natively implemented _write and _writev methods + // Ensure the Response scope is passed to these methods + this._writable._write = descriptors['_write'].value.bind(this); + this._writable._writev = descriptors['_writev'].value.bind(this); + } + + // Return the original function with the writable stream as the context + return original.apply(this._writable, arguments); + }; + + // Otherwise, simply return the passthrough method + return passthrough; + }, +}); + +module.exports = Response; diff --git a/packages/hyper-express/src/components/plugins/HostManager.js b/packages/hyper-express/src/components/plugins/HostManager.js new file mode 100644 index 0000000..9069381 --- /dev/null +++ b/packages/hyper-express/src/components/plugins/HostManager.js @@ -0,0 +1,74 @@ +'use strict'; +const EventEmitter = require('events'); + +class HostManager extends EventEmitter { + #app; + #hosts = {}; + + constructor(app) { + // Initialize event emitter + super(); + + // Store app reference + this.#app = app; + + // Bind a listener which emits 'missing' events from uWS when a host is not found + this.#app.uws_instance.missingServerName((hostname) => this.emit('missing', hostname)); + } + + /** + * @typedef {Object} HostOptions + * @property {String=} passphrase Strong passphrase for SSL cryptographic purposes. + * @property {String=} cert_file_name Path to SSL certificate file to be used for SSL/TLS. + * @property {String=} key_file_name Path to SSL private key file to be used for SSL/TLS. + * @property {String=} dh_params_file_name Path to file containing Diffie-Hellman parameters. + * @property {Boolean=} ssl_prefer_low_memory_usage Whether to prefer low memory usage over high performance. + */ + + /** + * Registers the unique host options to use for the specified hostname for incoming requests. + * + * @param {String} hostname + * @param {HostOptions} options + * @returns {HostManager} + */ + add(hostname, options) { + // Store host options + this.#hosts[hostname] = options; + + // Register the host server with uWS + this.#app.uws_instance.addServerName(hostname, options); + + // Return this instance + return this; + } + + /** + * Un-Registers the unique host options to use for the specified hostname for incoming requests. + * + * @param {String} hostname + * @returns {HostManager} + */ + remove(hostname) { + // Remove host options + delete this.#hosts[hostname]; + + // Un-Register the host server with uWS + this.#app.uws_instance.removeServerName(hostname); + + // Return this instance + return this; + } + + /* HostManager Getters & Properties */ + + /** + * Returns all of the registered hostname options. + * @returns {Object.} + */ + get registered() { + return this.#hosts; + } +} + +module.exports = HostManager; diff --git a/packages/hyper-express/src/components/plugins/LiveFile.js b/packages/hyper-express/src/components/plugins/LiveFile.js new file mode 100644 index 0000000..56a4c0a --- /dev/null +++ b/packages/hyper-express/src/components/plugins/LiveFile.js @@ -0,0 +1,172 @@ +'use strict'; +const FileSystem = require('fs'); +const EventEmitter = require('events'); +const { wrap_object, async_wait } = require('../../shared/operators.js'); + +class LiveFile extends EventEmitter { + #name; + #watcher; + #extension; + #buffer; + #content; + #last_update; + #options = { + path: '', + retry: { + every: 300, + max: 3, + }, + }; + + constructor(options) { + // Initialize EventEmitter instance + super(); + + // Wrap options object with provided object + wrap_object(this.#options, options); + + // Determine the name of the file + const chunks = options.path.split('/'); + this.#name = chunks[chunks.length - 1]; + + // Determine the extension of the file + this.#extension = this.#options.path.split('.'); + this.#extension = this.#extension[this.#extension.length - 1]; + + // Initialize file watcher to keep file updated in memory + this.reload(); + this._initiate_watcher(); + } + + /** + * @private + * Initializes File Watcher to reload file on changes + */ + _initiate_watcher() { + // Create FileWatcher that trigger reload method + this.#watcher = FileSystem.watch(this.#options.path, () => this.reload()); + } + + #reload_promise; + #reload_resolve; + #reload_reject; + + /** + * Reloads buffer/content for file asynchronously with retry policy. + * + * @private + * @param {Boolean} fresh + * @param {Number} count + * @returns {Promise} + */ + reload(fresh = true, count = 0) { + const reference = this; + if (fresh) { + // Reuse promise if there if one pending + if (this.#reload_promise instanceof Promise) return this.#reload_promise; + + // Create a new promise for fresh lookups + this.#reload_promise = new Promise((resolve, reject) => { + reference.#reload_resolve = resolve; + reference.#reload_reject = reject; + }); + } + + // Perform filesystem lookup query + FileSystem.readFile(this.#options.path, async (error, buffer) => { + // Pipe filesystem error through promise + if (error) { + reference._flush_ready(); + return reference.#reload_reject(error); + } + + // Perform retries in accordance with retry policy + // This is to prevent empty reads on atomicity based modifications from third-party programs + const { every, max } = reference.#options.retry; + if (buffer.length == 0 && count < max) { + await async_wait(every); + return reference.reload(false, count + 1); + } + + // Update instance buffer/content/last_update variables + reference.#buffer = buffer; + reference.#content = buffer.toString(); + reference.#last_update = Date.now(); + + // Cleanup reload promises and methods + reference.#reload_resolve(); + reference._flush_ready(); + reference.#reload_resolve = null; + reference.#reload_reject = null; + reference.#reload_promise = null; + }); + + return this.#reload_promise; + } + + #ready_promise; + #ready_resolve; + + /** + * Flushes pending ready promise. + * @private + */ + _flush_ready() { + if (typeof this.#ready_resolve == 'function') { + this.#ready_resolve(); + this.#ready_resolve = null; + } + this.#ready_promise = true; + } + + /** + * Returns a promise which resolves once first reload is complete. + * + * @returns {Promise} + */ + ready() { + // Return true if no ready promise exists + if (this.#ready_promise === true) return Promise.resolve(); + + // Create a Promise if one does not exist for ready event + if (this.#ready_promise === undefined) + this.#ready_promise = new Promise((resolve) => (this.#ready_resolve = resolve)); + + return this.#ready_promise; + } + + /* LiveFile Getters */ + get is_ready() { + return this.#ready_promise === true; + } + + get name() { + return this.#name; + } + + get path() { + return this.#options.path; + } + + get extension() { + return this.#extension; + } + + get content() { + return this.#content; + } + + get buffer() { + return this.#buffer; + } + + get last_update() { + return this.#last_update; + } + + get watcher() { + return this.#watcher; + } +} + +module.exports = LiveFile; diff --git a/packages/hyper-express/src/components/plugins/MultipartField.js b/packages/hyper-express/src/components/plugins/MultipartField.js new file mode 100644 index 0000000..13c05f1 --- /dev/null +++ b/packages/hyper-express/src/components/plugins/MultipartField.js @@ -0,0 +1,132 @@ +'use strict'; +const stream = require('stream'); +const FileSystem = require('fs'); + +class MultipartField { + #name; + #encoding; + #mime_type; + #file; + #value; + #truncated; + + constructor(name, value, info) { + // Store general information about this field + this.#name = name; + this.#encoding = info.encoding; + this.#mime_type = info.mimeType; + + // Determine if this field is a file or a normal field + if (value instanceof stream.Readable) { + // Store this file's supplied name and data stream + this.#file = { + name: info.filename, + stream: value, + }; + } else { + // Store field value and truncation information + this.#value = value; + this.#truncated = { + name: info.nameTruncated, + value: info.valueTruncated, + }; + } + } + + /* MultipartField Methods */ + + /** + * Saves this multipart file content to the specified path. + * Note! You must specify the file name and extension in the path itself. + * + * @param {String} path Path with file name to which you would like to save this file. + * @param {stream.WritableOptions} options Writable stream options + * @returns {Promise} + */ + write(path, options) { + // Throw an error if this method is called on a non file field + if (this.file === undefined) + throw new Error( + 'HyperExpress.Request.MultipartField.write(path, options) -> This method can only be called on a field that is a file type.' + ); + + // Return a promise which resolves once write stream has finished + const reference = this; + return new Promise((resolve, reject) => { + const writable = FileSystem.createWriteStream(path, options); + writable.on('close', resolve); + writable.on('error', reject); + reference.file.stream.pipe(writable); + }); + } + + /* MultipartField Properties */ + + /** + * Field name as specified in the multipart form. + * @returns {String} + */ + get name() { + return this.#name; + } + + /** + * Field encoding as specified in the multipart form. + * @returns {String} + */ + get encoding() { + return this.#encoding; + } + + /** + * Field mime type as specified in the multipart form. + * @returns {String} + */ + get mime_type() { + return this.#mime_type; + } + + /** + * @typedef {Object} MultipartFile + * @property {String=} name If supplied, this file's name as supplied by sender. + * @property {stream.Readable} stream Readable stream to consume this file's data. + */ + + /** + * Returns file information about this field if it is a file type. + * Note! This property will ONLY be defined if this field is a file type. + * + * @returns {MultipartFile} + */ + get file() { + return this.#file; + } + + /** + * Returns field value if this field is a non-file type. + * Note! This property will ONLY be defined if this field is a non-file type. + * + * @returns {String} + */ + get value() { + return this.#value; + } + + /** + * @typedef {Object} Truncations + * @property {Boolean} name Whether this field's name was truncated. + * @property {Boolean} value Whether this field's value was truncated. + */ + + /** + * Returns information about truncations in this field. + * Note! This property will ONLY be defined if this field is a non-file type. + * + * @returns {Truncations} + */ + get truncated() { + return this.#truncated; + } +} + +module.exports = MultipartField; diff --git a/packages/hyper-express/src/components/plugins/SSEventStream.js b/packages/hyper-express/src/components/plugins/SSEventStream.js new file mode 100644 index 0000000..d2b1bc4 --- /dev/null +++ b/packages/hyper-express/src/components/plugins/SSEventStream.js @@ -0,0 +1,116 @@ +'use strict'; +class SSEventStream { + _response; + + #wrote_headers = false; + /** + * @private + * Ensures the proper SSE headers are written to the client to initiate the SSE stream. + * @returns {Boolean} Whether the headers were written + */ + _initiate_sse_stream() { + // If the response has already been initiated, we cannot write headers anymore + if (this._response.initiated) return false; + + // If we have already written headers, we cannot write again + if (this.#wrote_headers) return false; + this.#wrote_headers = true; + + // Write the headers for the SSE stream to the client + this._response + .header('content-type', 'text/event-stream') + .header('cache-control', 'no-cache') + .header('connection', 'keep-alive') + .header('x-accel-buffering', 'no'); + + // Return true to signify that we have written headers + return true; + } + + /** + * @private + * Internal method to write data to the response stream. + * @returns {Boolean} Whether the data was written + */ + _write(data) { + // Initialize the SSE stream + this._initiate_sse_stream(); + + // Write the data to the response stream + return this._response.write(data); + } + + /** + * Opens the "Server-Sent Events" connection to the client. + * + * @returns {Boolean} + */ + open() { + // We simply send a comment-type message to the client to indicate that the connection has been established + // The "data" can be anything as it will not be handled by the client EventSource object + return this.comment('open'); + } + + /** + * Closes the "Server-Sent Events" connection to the client. + * + * @returns {Boolean} + */ + close() { + // Ends the connection by sending the final empty message + return this._response.send(); + } + + /** + * Sends a comment-type message to the client that will not be emitted by EventSource. + * This can be useful as a keep-alive mechanism if messages might not be sent regularly. + * + * @param {String} data + * @returns {Boolean} + */ + comment(data) { + // Prefix the message with a colon character to signify a comment + return this._write(`: ${data}\n`); + } + + /** + * Sends a message to the client based on the specified event and data. + * Note! You must retry failed messages if you receive a false output from this method. + * + * @param {String} id + * @param {String=} event + * @param {String=} data + * @returns {Boolean} + */ + send(id, event, data) { + // Parse arguments into overloaded parameter translations + const _id = id && event && data ? id : undefined; + const _event = id && event ? (_id ? event : id) : undefined; + const _data = data || event || id; + + // Build message parts to prepare a payload + const parts = []; + if (_id) parts.push(`id: ${_id}`); + if (_event) parts.push(`event: ${_event}`); + if (_data) parts.push(`data: ${_data}`); + + // Push an empty line to indicate the end of the message + parts.push('', ''); + + // Write the string based payload to the client + return this._write(parts.join('\n')); + } + + /* SSEConnection Properties */ + + /** + * Whether this Server-Sent Events stream is still active. + * + * @returns {Boolean} + */ + get active() { + return !this._response.completed; + } +} + +module.exports = SSEventStream; diff --git a/packages/hyper-express/src/components/router/Route.js b/packages/hyper-express/src/components/router/Route.js new file mode 100644 index 0000000..1c77cb2 --- /dev/null +++ b/packages/hyper-express/src/components/router/Route.js @@ -0,0 +1,210 @@ +'use strict'; +const { parse_path_parameters } = require('../../shared/operators.js'); + +class Route { + id = null; + app = null; + path = ''; + method = ''; + pattern = ''; + handler = null; + options = null; + streaming = null; + max_body_length = null; + path_parameters_key = null; + + /** + * Constructs a new Route object. + * @param {Object} options + * @param {import('../Server.js')} options.app - The server instance. + * @param {String} options.method - The HTTP method. + * @param {String} options.pattern - The route pattern. + * @param {Function} options.handler - The route handler. + */ + constructor({ app, method, pattern, options, handler }) { + this.id = app._get_incremented_id(); + this.app = app; + this.pattern = pattern; + this.handler = handler; + this.options = options; + this.method = method.toUpperCase(); + this.streaming = options.streaming || app._options.streaming || {}; + this.max_body_length = options.max_body_length || app._options.max_body_length; + this.path_parameters_key = parse_path_parameters(pattern); + + // Translate to HTTP DELETE + if (this.method === 'DEL') this.method = 'DELETE'; + + // Cache the expected request path for this route if it is not a wildcard route + // This will be used to optimize performance for determining incoming request paths + const wildcard = pattern.includes('*') || this.path_parameters_key.length > 0; + if (!wildcard) this.path = pattern; + } + + /** + * @typedef {Object} Middleware + * @property {Number} id - Unique identifier for this middleware based on it's registeration order. + * @property {String} pattern - The middleware pattern. + * @property {function} handler - The middleware handler function. + * @property {Boolean=} match - Whether to match the middleware pattern against the request path. + */ + + /** + * Binds middleware to this route and sorts middlewares to ensure execution order. + * + * @param {Middleware} middleware + */ + use(middleware) { + // Store and sort middlewares to ensure proper execution order + this.options.middlewares.push(middleware); + } + + /** + * Handles an incoming request through this route. + * + * @param {import('../http/Request.js')} request The HyperExpress request object. + * @param {import('../http/Response.js')} response The HyperExpress response object. + * @param {Number=} cursor The middleware cursor. + */ + handle(request, response, cursor = 0) { + // Do not handle the request if the response has been sent aka. the request is no longer active + if (response.completed) return; + + // Retrieve the middleware for the current cursor, track the cursor if there is a valid middleware + let iterator; + const middleware = this.options.middlewares[cursor]; + if (middleware) { + // Determine if this middleware requires path matching + if (middleware.match) { + // Check if the middleware pattern matches that starting of the request path + if (request.path.startsWith(middleware.pattern)) { + // Ensure that the character after the middleware pattern is either a trailing slash or out of bounds of string + const trailing = request.path[middleware.pattern.length]; + if (trailing !== '/' && trailing !== undefined) { + // This handles cases where "/docs" middleware will incorrectly match "/docs-JSON" for example + return this.handle(request, response, cursor + 1); + } + } else { + // Since the middleware pattern does not match the start of the request path, skip this middleware + return this.handle(request, response, cursor + 1); + } + } + + // Track the middleware cursor to prevent double execution + response._track_middleware_cursor(cursor); + + // Initialize the iterator for this middleware + iterator = (error) => { + // If an error occured, pipe it to the error handler + if (error instanceof Error) return response.throw(error); + + // Handle this request again with an incremented cursor to execute the next middleware or route handler + this.handle(request, response, cursor + 1); + }; + } + + // Determine if this is an async handler which can explicitly throw uncaught errors + const is_async_handler = (middleware ? middleware.handler : this.handler).constructor.name === 'AsyncFunction'; + if (is_async_handler) { + // Execute the middleware or route handler within a promise to catch and pipe synchronous errors + new Promise(async (resolve) => { + try { + if (middleware) { + // Execute the middleware or route handler with the iterator + await middleware.handler(request, response, iterator); + + // Call the iterator anyways in case the middleware never calls the next() iterator + iterator(); + } else { + await this.handler(request, response); + } + } catch (error) { + // Catch and pipe any errors to the error handler + response.throw(error); + } + + // Resolve promise to ensure it is properly cleaned up from memory + resolve(); + }); + } else { + // Execute the middleware or route handler within a protected try/catch to catch and pipe synchronous errors + try { + let output; + if (middleware) { + output = middleware.handler(request, response, iterator); + } else { + output = this.handler(request, response); + } + + // Determine if a Promise instance was returned by the handler + if (typeof output?.then === 'function') { + // If this is a middleware, we must try to call iterator after returned promise resolves + if (middleware) output.then(iterator); + + // Catch and pipe any errors to the global error handler + output.catch((error) => response.throw(error)); + } + } catch (error) { + // Catch and pipe any errors to the error handler + response.throw(error); + } + } + } + + /** + * Compiles the route's internal components and caches for incoming requests. + */ + compile() { + // Initialize a fresh array of middlewares + const middlewares = []; + const pattern = this.pattern; + + // Determine wildcard properties about this route + const is_wildcard = pattern.endsWith('*'); + const wildcard_path = pattern.substring(0, pattern.length - 1); + + // Iterate through the global/local middlewares and connect them to this route if eligible + const app_middlewares = this.app.middlewares; + Object.keys(app_middlewares).forEach((pattern) => + app_middlewares[pattern].forEach((middleware) => { + // A route can be a direct child when a route's pattern has more path depth than the middleware with a matching start + // A route can be an indirect child when it is a wildcard and the middleware's pattern is a direct parent of the route child + const direct_child = pattern.startsWith(middleware.pattern); + const indirect_child = middleware.pattern.startsWith(wildcard_path); + if (direct_child || (is_wildcard && indirect_child)) { + // Create shallow copy of the middleware + const record = Object.assign({}, middleware); + + // Set the match property based on whether this is a direct child + record.match = direct_child; + + // Push the middleware + middlewares.push(record); + } + }) + ); + + // Find the largest ID from the current middlewares + const offset = middlewares.reduce((max, middleware) => (middleware.id > max ? middleware.id : max), 0); + + // Push the route-specific middlewares to the array at the end + if (Array.isArray(this.options.middlewares)) + this.options.middlewares.forEach((middleware) => + middlewares.push({ + id: this.id + offset, + pattern, + handler: middleware, + match: false, // Route-specific middlewares do not need to be matched + }) + ); + + // Sort the middlewares by their id in ascending order + // This will ensure that middlewares are executed in the order they were registered throughout the application + middlewares.sort((a, b) => a.id - b.id); + + // Write the middlewares property with the sorted array + this.options.middlewares = middlewares; + } +} + +module.exports = Route; diff --git a/packages/hyper-express/src/components/router/Router.js b/packages/hyper-express/src/components/router/Router.js new file mode 100644 index 0000000..30ee49a --- /dev/null +++ b/packages/hyper-express/src/components/router/Router.js @@ -0,0 +1,483 @@ +'use strict'; +const { merge_relative_paths } = require('../../shared/operators.js'); + +/** + * @typedef {import('../compatibility/NodeRequest.js')} NodeRequest + * @typedef {import('../compatibility/NodeResponse.js').NodeResponseTypes} NodeResponse + * @typedef {import('../compatibility/ExpressRequest.js')} ExpressRequest + * @typedef {import('../compatibility/ExpressResponse.js')} ExpressResponse + * @typedef {import('../http/Request.js')} NativeRequest + * @typedef {import('../http/Response.js')} NativeResponse + * @typedef {NativeRequest & NodeRequest & ExpressRequest & import('stream').Stream} Request + * @typedef {NativeResponse & NodeResponse & ExpressResponse & import('stream').Stream} Response + * @typedef {function(Request, Response, Function):any|Promise} MiddlewareHandler + */ + +class Router { + #is_app = false; + #context_pattern; + #subscribers = []; + #records = { + routes: [], + middlewares: [], + }; + + constructor() {} + + /** + * Used by the server to declare self as an app instance. + * + * @private + * @param {Boolean} value + */ + _is_app(value) { + this.#is_app = value; + } + + /** + * Sets context pattern for this router which will auto define the pattern of each route called on this router. + * This is called by the .route() returned Router instance which allows for omission of pattern to be passed to route() method. + * Example: Router.route('/something/else').get().post().delete() etc will all be bound to pattern '/something/else' + * @private + * @param {string} path + */ + _set_context_pattern(path) { + this.#context_pattern = path; + } + + /** + * Registers a route in the routes array for this router. + * + * @private + * @param {String} method Supported: any, get, post, delete, head, options, patch, put, trace + * @param {String} pattern Example: "/api/v1" + * @param {Object} options Route processor options (Optional) + * @param {Function} handler Example: (request, response) => {} + * @returns {this} Chainable instance + */ + _register_route() { + // The first argument will always be the method (in lowercase) + const method = arguments[0]; + + // The pattern, options and handler must be dynamically parsed depending on the arguments provided and router behavior + let pattern, options, handler; + + // Iterate through the remaining arguments to find the above values and also build an Array of middleware / handler callbacks + // The route handler will be the last one in the array + const callbacks = []; + for (let i = 1; i < arguments.length; i++) { + const argument = arguments[i]; + + // The second argument should be the pattern. If it is a string, it is the pattern. If it is anything else and we do not have a context pattern, throw an error as that means we have no pattern. + if (i === 1) { + if (typeof argument === 'string') { + if (this.#context_pattern) { + // merge the provided pattern with the context pattern + pattern = merge_relative_paths(this.#context_pattern, argument); + } else { + // The path is as is + pattern = argument; + } + + // Continue to the next argument as this is not the pattern but we have a context pattern + continue; + } else if (!this.#context_pattern) { + throw new Error( + 'HyperExpress.Router: Route pattern is required unless created from a chainable route instance using Route.route() method.' + ); + } else { + // The path is the context pattern + pattern = this.#context_pattern; + } + } + + // Look for options, middlewares and handler in the remaining arguments + if (typeof argument == 'function') { + // Scenario: Single function + callbacks.push(argument); + } else if (Array.isArray(argument)) { + // Scenario: Array of functions + callbacks.push(...argument); + } else if (argument && typeof argument == 'object') { + // Scenario: Route options object + options = argument; + } + } + + // Write the route handler and route options object with fallback to the default options + handler = callbacks.pop(); + options = { + streaming: {}, + middlewares: [], + ...(options || {}), + }; + + // Make a shallow copy of the options object to avoid mutating the original + options = Object.assign({}, options); + + // Enforce a leading slash on the pattern if it begins with a catchall star + // This is because uWebsockets.js does not treat non-leading slashes as catchall stars + if (pattern.startsWith('*')) pattern = '/' + pattern; + + // Parse the middlewares into a new array to prevent mutating the original + const middlewares = []; + + // Push all the options provided middlewares into the middlewares array + if (Array.isArray(options.middlewares)) middlewares.push(...options.middlewares); + + // Push all the callback provided middlewares into the middlewares array + if (callbacks.length > 0) middlewares.push(...callbacks); + + // Write the middlewares into the options object + options.middlewares = middlewares; + + // Initialize the record object which will hold information about this route + const record = { + method, + pattern, + options, + handler, + }; + + // Store record for future subscribers + this.#records.routes.push(record); + + // Create route if this is a Server extended Router instance (ROOT) + if (this.#is_app) return this._create_route(record); + + // Alert all subscribers of the new route that was created + this.#subscribers.forEach((subscriber) => subscriber('route', record)); + + // Return this to make the Router chainable + return this; + } + + /** + * Registers a middleware from use() method and recalibrates. + * + * @private + * @param {String} pattern + * @param {Function} middleware + */ + _register_middleware(pattern, middleware) { + const record = { + pattern: pattern.endsWith('/') ? pattern.slice(0, -1) : pattern, // Do not allow trailing slash in middlewares + middleware, + }; + + // Store record for future subscribers + this.#records.middlewares.push(record); + + // Create middleware if this is a Server extended Router instance (ROOT) + if (this.#is_app) return this._create_middleware(record); + + // Alert all subscribers of the new middleware that was created + this.#subscribers.forEach((subscriber) => subscriber('middleware', record)); + } + + /** + * Registers a router from use() method and recalibrates. + * + * @private + * @param {String} pattern + * @param {this} router + */ + _register_router(pattern, router) { + const reference = this; + router._subscribe((event, object) => { + switch (event) { + case 'records': + // Destructure records from router + const { routes, middlewares } = object; + + // Register routes from router locally with adjusted pattern + routes.forEach((record) => + reference._register_route( + record.method, + merge_relative_paths(pattern, record.pattern), + record.options, + record.handler + ) + ); + + // Register middlewares from router locally with adjusted pattern + return middlewares.forEach((record) => + reference._register_middleware(merge_relative_paths(pattern, record.pattern), record.middleware) + ); + case 'route': + // Register route from router locally with adjusted pattern + return reference._register_route( + object.method, + merge_relative_paths(pattern, object.pattern), + object.options, + object.handler + ); + case 'middleware': + // Register middleware from router locally with adjusted pattern + return reference._register_middleware( + merge_relative_paths(pattern, object.patch), + object.middleware + ); + } + }); + } + + /* Router public methods */ + + /** + * Subscribes a handler which will be invocated with changes. + * + * @private + * @param {*} handler + */ + _subscribe(handler) { + // Pipe all records on first subscription to synchronize + handler('records', this.#records); + + // Register subscriber handler for future updates + this.#subscribers.push(handler); + } + + /** + * Registers middlewares and router instances on the specified pattern if specified. + * If no pattern is specified, the middleware/router instance will be mounted on the '/' root path by default of this instance. + * + * @param {...(String|MiddlewareHandler|Router)} args (request, response, next) => {} OR (request, response) => new Promise((resolve, reject) => {}) + * @returns {this} Chainable instance + */ + use() { + // If we have a context pattern, then this is a contextual Chainable and should not allow middlewares or routers to be bound to it + if (this.#context_pattern) + throw new Error( + 'HyperExpress.Router.use() -> Cannot bind middlewares or routers to a contextual router created using Router.route() method.' + ); + + // Parse a pattern for this use call with a fallback to the local-global scope aka. '/' pattern + const pattern = arguments[0] && typeof arguments[0] == 'string' ? arguments[0] : '/'; + + // Validate that the pattern value does not contain any wildcard or path parameter prefixes which are not allowed + if (pattern.indexOf('*') > -1 || pattern.indexOf(':') > -1) + throw new Error( + 'HyperExpress: Server/Router.use() -> Wildcard "*" & ":parameter" prefixed paths are not allowed when binding middlewares or routers using this method.' + ); + + // Register each candidate individually depending on the type of candidate value + for (let i = 0; i < arguments.length; i++) { + const candidate = arguments[i]; + if (typeof candidate == 'function') { + // Scenario: Single function + this._register_middleware(pattern, candidate); + } else if (Array.isArray(candidate)) { + // Scenario: Array of functions + candidate.forEach((middleware) => this._register_middleware(pattern, middleware)); + } else if (typeof candidate == 'object' && candidate.constructor.name === 'Router') { + // Scenario: Router instance + this._register_router(pattern, candidate); + } else if (candidate && typeof candidate == 'object' && typeof candidate.middleware == 'function') { + // Scenario: Inferred middleware for third-party middlewares which support the Middleware.middleware property + this._register_middleware(pattern, candidate.middleware); + } + } + + // Return this to make the Router chainable + return this; + } + + /** + * @typedef {Object} RouteOptions + * @property {Number} max_body_length Overrides the global maximum body length specified in Server constructor options. + * @property {Array.} middlewares Route specific middlewares + * @property {Object} streaming Global content streaming options. + * @property {import('stream').ReadableOptions} streaming.readable Global content streaming options for Readable streams. + * @property {import('stream').WritableOptions} streaming.writable Global content streaming options for Writable streams. + */ + + /** + * Returns a Chainable instance which can be used to bind multiple method routes or middlewares on the same path easily. + * Example: `Router.route('/api/v1').get(getHandler).post(postHandler).delete(destroyHandler)` + * Example: `Router.route('/api/v1').use(middleware).user(middleware2)` + * @param {String} pattern + * @returns {this} A Chainable instance with a context pattern set to this router's pattern. + */ + route(pattern) { + // Ensure that the pattern is a string + if (!pattern || typeof pattern !== 'string') + throw new Error('HyperExpress.Router.route(pattern) -> pattern must be a string.'); + + // Create a new router instance with the context pattern set to the provided pattern + const router = new Router(); + router._set_context_pattern(pattern); + this.use(router); + + // Return the router instance to allow for chainable bindings + return router; + } + + /** + * Creates an HTTP route that handles any HTTP method requests. + * Note! ANY routes do not support route specific middlewares. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + any() { + return this._register_route('any', ...arguments); + } + + /** + * Alias of any() method. + * Creates an HTTP route that handles any HTTP method requests. + * Note! ANY routes do not support route specific middlewares. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + all() { + // Alias of any() method + return this.any(...arguments); + } + + /** + * Creates an HTTP route that handles GET method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + get() { + return this._register_route('get', ...arguments); + } + + /** + * Creates an HTTP route that handles POST method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + post() { + return this._register_route('post', ...arguments); + } + + /** + * Creates an HTTP route that handles PUT method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + put() { + return this._register_route('put', ...arguments); + } + + /** + * Creates an HTTP route that handles DELETE method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + delete() { + return this._register_route('del', ...arguments); + } + + /** + * Creates an HTTP route that handles HEAD method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + head() { + return this._register_route('head', ...arguments); + } + + /** + * Creates an HTTP route that handles OPTIONS method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + options() { + return this._register_route('options', ...arguments); + } + + /** + * Creates an HTTP route that handles PATCH method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + patch() { + return this._register_route('patch', ...arguments); + } + + /** + * Creates an HTTP route that handles TRACE method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + trace() { + return this._register_route('trace', ...arguments); + } + + /** + * Creates an HTTP route that handles CONNECT method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + connect() { + return this._register_route('connect', ...arguments); + } + + /** + * Intercepts and handles upgrade requests for incoming websocket connections. + * Note! You must call response.upgrade(data) at some point in this route to open a websocket connection. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + upgrade() { + return this._register_route('upgrade', ...arguments); + } + + /** + * @typedef {Object} WSRouteOptions + * @property {('String'|'Buffer'|'ArrayBuffer')} message_type Specifies data type in which to provide incoming websocket messages. Default: 'String' + * @property {Number} compression Specifies preset for permessage-deflate compression. Specify one from HyperExpress.compressors.PRESET + * @property {Number} idle_timeout Specifies interval to automatically timeout/close idle websocket connection in seconds. Default: 32 + * @property {Number} max_backpressure Specifies maximum websocket backpressure allowed in character length. Default: 1024 * 1024 + * @property {Number} max_payload_length Specifies maximum length allowed on incoming messages. Default: 32 * 1024 + */ + + /** + * @typedef WSRouteHandler + * @type {function(import('../ws/Websocket.js')):void} + */ + + /** + * @param {String} pattern + * @param {WSRouteOptions|WSRouteHandler} options + * @param {WSRouteHandler} handler + */ + ws(pattern, options, handler) { + return this._register_route('ws', pattern, options, handler); + } + + /* Route getters */ + + /** + * Returns All routes in this router in the order they were registered. + * @returns {Array} + */ + get routes() { + return this.#records.routes; + } + + /** + * Returns all middlewares in this router in the order they were registered. + * @returns {Array} + */ + get middlewares() { + return this.#records.middlewares; + } +} + +module.exports = Router; diff --git a/packages/hyper-express/src/components/ws/Websocket.js b/packages/hyper-express/src/components/ws/Websocket.js new file mode 100644 index 0000000..e8d1ffc --- /dev/null +++ b/packages/hyper-express/src/components/ws/Websocket.js @@ -0,0 +1,423 @@ +'use strict'; +const EventEmitter = require('events'); +const { Readable, Writable } = require('stream'); +const { array_buffer_to_string } = require('../../shared/operators.js'); + +const FRAGMENTS = { + FIRST: 'FIRST', + MIDDLE: 'MIDDLE', + LAST: 'LAST', +}; + +class Websocket extends EventEmitter { + #ws; + #ip; + #context; + #stream; + #closed = false; + + constructor(ws) { + // Initialize event emitter + super(); + + // Parse information about websocket connection + this.#ws = ws; + this.#context = ws.context || {}; + this.#ip = array_buffer_to_string(ws.getRemoteAddressAsText()); + } + + /* EventEmitter overrides */ + + /** + * Binds an event listener to this `Websocket` instance. + * See the Node.js `EventEmitter` documentation for more details on this extended method. + * @param {('message'|'close'|'drain'|'ping'|'pong')} eventName + * @param {Function} listener + * @returns {Websocket} + */ + on(eventName, listener) { + // Pass all events to EventEmitter + super.on(eventName, listener); + return this; + } + + /** + * Binds a `one-time` event listener to this `Websocket` instance. + * See the Node.js `EventEmitter` documentation for more details on this extended method. + * @param {('message'|'close'|'drain'|'ping'|'pong')} eventName + * @param {Function} listener + * @returns {Websocket} + */ + once(eventName, listener) { + // Pass all events to EventEmitter + super.once(eventName, listener); + return this; + } + + /** + * Alias of uWS.cork() method. Accepts a callback with multiple operations for network efficiency. + * + * @param {Function} callback + * @returns {Websocket} + */ + atomic(callback) { + return this.#ws ? this.#ws.cork(callback) : this; + } + + /** + * Sends a message to websocket connection. + * Returns true if message was sent successfully. + * Returns false if message was not sent due to buil up backpressure. + * + * @param {String|Buffer|ArrayBuffer} message + * @param {Boolean=} is_binary + * @param {Boolean=} compress + * @returns {Boolean} + */ + send(message, is_binary, compress) { + // Send message through uWS connection + if (this.#ws) return this.#ws.send(message, is_binary, compress); + return false; + } + + /** + * Sends a ping control message. + * Returns Boolean depending on backpressure similar to send(). + * + * @param {String|Buffer|ArrayBuffer=} message + * @returns {Boolean} + */ + ping(message) { + // Send ping OPCODE message through uWS connection + return this.#ws ? this.#ws.ping(message) : false; + } + + /** + * Destroys this polyfill Websocket component and derefernces the underlying ws object + * @private + */ + _destroy() { + this.#ws = null; + this.#closed = true; + } + + /** + * Gracefully closes websocket connection by sending specified code and short message. + * + * @param {Number=} code + * @param {(String|Buffer|ArrayBuffer)=} message + */ + close(code, message) { + // Close websocket using uWS.end() method which gracefully closes connections + if (this.#ws) this.#ws.end(code, message); + } + + /** + * Forcefully closes websocket connection. + * No websocket close code/message is sent. + * This will immediately emit the 'close' event. + */ + destroy() { + if (this.#ws) this.#ws.close(); + } + + /** + * Returns whether this `Websocket` is subscribed to the specified topic. + * + * @param {String} topic + * @returns {Boolean} + */ + is_subscribed(topic) { + return this.#ws ? this.#ws.isSubscribed(topic) : false; + } + + /** + * Subscribe to a topic in MQTT syntax. + * MQTT syntax includes things like "root/child/+/grandchild" where "+" is a wildcard and "root/#" where "#" is a terminating wildcard. + * + * @param {String} topic + * @returns {Boolean} + */ + subscribe(topic) { + return this.#ws ? this.#ws.subscribe(topic) : false; + } + + /** + * Unsubscribe from a topic. + * Returns true on success, if the WebSocket was subscribed. + * + * @param {String} topic + * @returns {Boolean} + */ + unsubscribe(topic) { + return this.#ws ? this.#ws.unsubscribe(topic) : false; + } + + /** + * Publish a message to a topic in MQTT syntax. + * You cannot publish using wildcards, only fully specified topics. + * + * @param {String} topic + * @param {String|Buffer|ArrayBuffer} message + * @param {Boolean=} is_binary + * @param {Boolean=} compress + */ + publish(topic, message, is_binary, compress) { + return this.#ws ? this.#ws.publish(topic, message, is_binary, compress) : false; + } + + #buffered_fragment; + /** + * Buffers the provided fragment and returns the last buffered fragment. + * + * @param {String|Buffer|ArrayBuffer} fragment + * @returns {String|Buffer|ArrayBuffer|undefined} + */ + _buffer_fragment(fragment) { + const current = this.#buffered_fragment; + this.#buffered_fragment = fragment; + return current; + } + + /** + * Initiates fragment based message writing with uWS and writes appropriate chunk based on provided type parameter. + * + * @param {String} type + * @param {String|Buffer|ArrayBuffer} chunk + * @param {Boolean=} is_binary + * @param {Boolean=} compress + * @param {Function=} callback + * @returns {Boolean} + */ + _write(type, chunk, is_binary, compress, callback) { + // Ensure websocket still exists before attempting to write + if (this.#ws) { + // Attempt to send this fragment using the appropriate fragment method from uWS + let sent; + switch (type) { + case FRAGMENTS.FIRST: + sent = this.#ws.sendFirstFragment(chunk, is_binary, compress); + break; + case FRAGMENTS.MIDDLE: + sent = this.#ws.sendFragment(chunk, is_binary, compress); + break; + case FRAGMENTS.LAST: + sent = this.#ws.sendLastFragment(chunk, is_binary, compress); + break; + default: + throw new Error('Websocket._write() -> Invalid Fragment type constant provided.'); + } + + if (sent) { + // Invoke the callback if chunk was sent successfully + if (callback) callback(); + } else { + // Wait for this connection to drain before retrying this chunk + this.once('drain', () => this._write(type, chunk, is_binary, compress, callback)); + } + + // Return the sent status for consumer + return sent; + } + + // Throw an error with NOT_CONNECTED message to be caught by executor + throw new Error('Websocket is no longer connected.'); + } + + /** + * Streams the provided chunk while pausing the stream being consumed during backpressure. + * + * @param {Readable} stream + * @param {String} type + * @param {Buffer|ArrayBuffer} chunk + * @param {Boolean} is_binary + */ + _stream_chunk(stream, type, chunk, is_binary) { + // Break execution if connection is no longer connected + if (this.#ws === null) return; + + // Attempt to write this chunk + const sent = this._write(type, chunk, is_binary); + if (!sent) { + // Pause the readable stream as we failed to write this chunk + stream.pause(); + + // Wait for this connection to be drained before trying again + this.once('drain', () => this._stream_chunk(stream, type, chunk, is_binary)); + } else if (stream.isPaused()) { + // Resume the stream if it has been paused and we sent a chunk successfully + stream.resume(); + } + } + + /** + * This method is used to stream a message to the receiver. + * Note! The data is by default streamed as Binary due to how partial fragments are sent. + * This is done to prevent processing errors depending on client's receiver's incoming fragment processing strategy. + * + * @param {Readable} readable A Readable stream which will be consumed as message + * @param {Boolean=} is_binary Whether data being streamed is in binary. Default: true + * @returns {Promise} + */ + stream(readable, is_binary = true) { + // Ensure readable is an instance of a stream.Readable + if (!(readable instanceof Readable)) + throw new Error('Websocket.stream(readable) -> readable must be a Readable stream.'); + + // Prevent multiple streams from taking place + if (this.#stream) + throw new Error( + 'Websocket.stream(readable) -> You may not stream data while another stream operation is active on this websocket. Make sure you are not already streaming or piping a stream to this websocket.' + ); + + // Return a promise which resolves once stream has finished + const scope = this; + return new Promise((resolve) => { + // Store the readable as the pending stream for this connection + scope.#stream = readable; + + // Bind a listener for the 'data' event to consume chunks + let is_first = true; // By default, we will send the first chunk as a fragment + readable.on('data', (chunk) => { + // Check to see if we have a fragment to send post buffering + const fragment = scope._buffer_fragment(chunk); + if (fragment) { + // Stream the retrieved current fragment + scope._stream_chunk(readable, is_first ? FRAGMENTS.FIRST : FRAGMENTS.MIDDLE, fragment, is_binary); + + // If this was the first chunk, invert the is_first boolean + if (is_first) is_first = false; + } + }); + + // Create a callback for ending the readable consumption + const end_stream = () => { + // Retrieve the last buffered fragment to send as last or only chunk + const fragment = scope._buffer_fragment(); + + // If we streamed no individual fragments aka. the is_first flag was set to true, then we did no streaming and can simply send the last fragment as a message + if (is_first) { + scope.#ws.send(fragment, is_binary); + } else { + // Stream the final chunk as last fragment + scope._stream_chunk(scope.#stream, FRAGMENTS.LAST, fragment, is_binary); + } + + // Clean up the readable + scope.#stream = undefined; + resolve(); + }; + + // Bind listeners to end the framented write procedure + readable.once('end', end_stream); + }); + } + + /* Websocket Getters */ + + /** + * Underlying uWS.Websocket object + */ + get raw() { + return this.#ws; + } + + /** + * Returns IP address of this websocket connection. + * @returns {String} + */ + get ip() { + return this.#ip; + } + + /** + * Returns context values from the response.update(context) connection upgrade call. + * @returns {Object} + */ + get context() { + return this.#context; + } + + /** + * Returns whether is websocket connection is closed. + * @returns {Boolean} + */ + get closed() { + return this.#closed; + } + + /** + * Returns the bytes buffered in backpressure. + * This is similar to the bufferedAmount property in the browser counterpart. + * @returns {Number} + */ + get buffered() { + return this.#ws ? this.#ws.getBufferedAmount() : 0; + } + + /** + * Returns a list of topics this websocket is subscribed to. + * @returns {Array} + */ + get topics() { + return this.#ws ? this.#ws.getTopics() : []; + } + + /** + * Returns a Writable stream associated with this response to be used for piping streams. + * Note! You can only retrieve/use only one writable at any given time. + * + * @returns {Writable} + */ + get writable() { + // Prevent multiple streaming operations from taking place + const scope = this; + if (this.#stream) + throw new Error( + 'Websocket.writable -> You may only access and utilize one writable stream at any given time. Make sure you are not already streaming or piping a stream to this websocket.' + ); + + // Create a new writable stream object which will write with the _write method + let is_first = true; + this.#stream = new Writable({ + write: (chunk, encoding, callback) => { + // Buffer the incoming chunk as a fragment + const fragment = scope._buffer_fragment(chunk); + + // Check to see if we have a fragment to send post buffering + if (fragment) { + // Write the current retrieved fragment + scope._write(is_first ? FRAGMENTS.FIRST : FRAGMENTS.MIDDLE, fragment, true, false, callback); + + // Invert the is_first boolean after first fragment + if (is_first) is_first = false; + } else { + // Trigger the callback even if don't have a fragment to continue consuming + callback(); + } + }, + }); + + // Create a callback for ending the writable usage + const end_stream = () => { + // Retrieve the last buffered fragment to write as last or only chunk + const fragment = scope._buffer_fragment(); + + if (is_first) { + scope.#ws.send(fragment, true, false); + scope.#ws.stream = undefined; + } else { + // Write the final empty chunk as last fragment and cleanup the writable + scope._write(FRAGMENTS.LAST, fragment, true, false, () => (scope.#stream = undefined)); + } + }; + + // Bind listeners to end the fragmented write procedure + this.#stream.on('finish', end_stream); + + // Return the writable stream + return this.#stream; + } +} + +module.exports = Websocket; diff --git a/packages/hyper-express/src/components/ws/WebsocketRoute.js b/packages/hyper-express/src/components/ws/WebsocketRoute.js new file mode 100644 index 0000000..f0cd944 --- /dev/null +++ b/packages/hyper-express/src/components/ws/WebsocketRoute.js @@ -0,0 +1,198 @@ +'use strict'; +const uWebsockets = require('uWebSockets.js'); + +const Route = require('../router/Route.js'); +const Websocket = require('./Websocket.js'); +const { wrap_object, array_buffer_to_string } = require('../../shared/operators.js'); + +class WebsocketRoute extends Route { + #upgrade_with; + #message_parser; + options = { + idle_timeout: 32, + message_type: 'String', + compression: uWebsockets.DISABLED, + max_backpressure: 1024 * 1024, + max_payload_length: 32 * 1024, + }; + + constructor({ app, pattern, handler, options }) { + // Initialize the super Router class + super({ app, method: 'ws', pattern, options, handler }); + + // Wrap local default options with user specified options + wrap_object(this.options, options); + this.#message_parser = this._get_message_parser(this.options.message_type); + + // Load companion upgrade route and initialize uWS.ws() route + this._load_companion_route(); + this._create_uws_route(); + } + + /** + * Returns a parser that automatically converts uWS ArrayBuffer to specified data type. + * @private + * @returns {Function} + */ + _get_message_parser(type) { + switch (type) { + case 'String': + // Converts ArrayBuffer -> String + return (array_buffer) => array_buffer_to_string(array_buffer); + case 'Buffer': + // Converts & Copies ArrayBuffer -> Buffer + // We concat (copy) because ArrayBuffer from uWS is deallocated after initial synchronous execution + return (array_buffer) => Buffer.concat([Buffer.from(array_buffer)]); + case 'ArrayBuffer': + // Simply return the ArrayBuffer from uWS handler + return (array_buffer) => array_buffer; + default: + // Throw error on invalid type + throw new Error( + "Server.ws(options) -> options.message_type must be one of ['String', 'Buffer', 'ArrayBuffer']" + ); + } + } + + /** + * Loads a companion upgrade route from app routes object. + * @private + */ + _load_companion_route() { + const companion = this.app.routes['upgrade'][this.pattern]; + if (companion) { + // Use existing companion route as it is a user assigned route + this.#upgrade_with = companion; + } else { + // Create and use a temporary default route to allow for healthy upgrade request cycle + // Default route will upgrade all incoming requests automatically + this.app._create_route({ + method: 'upgrade', + pattern: this.pattern, + handler: (request, response) => response.upgrade(), // By default, upgrade all incoming requests + options: { + _temporary: true, // Flag this route as temporary so it will get overwritten by user specified upgrade route + }, + }); + + // Store created route locally as Server will not call _set_upgrade_route + // This is because this WebsocketRoute has not been created synchronously yet + this.#upgrade_with = this.app.routes['upgrade'][this.pattern]; + } + } + + /** + * Sets the upgrade route for incoming upgrade request to traverse through HyperExpress request lifecycle. + * @private + * @param {Route} route + */ + _set_upgrade_route(route) { + this.#upgrade_with = route; + } + + /** + * Creates a uWS.ws() route that will power this WebsocketRoute instance. + * @private + */ + _create_uws_route() { + // Destructure and convert HyperExpress options to uWS.ws() route options + const { compression, idle_timeout, max_backpressure, max_payload_length } = this.options; + const uws_options = { + compression, + idleTimeout: idle_timeout, + maxBackpressure: max_backpressure, + maxPayloadLength: max_payload_length, + }; + + // Create middleman upgrade route that pipes upgrade requests to HyperExpress request handler + uws_options.upgrade = (uws_response, uws_request, socket_context) => + this.app._handle_uws_request(this.#upgrade_with, uws_request, uws_response, socket_context); + + // Bind middleman routes to pipe uws events into poly handlers + uws_options.open = (ws) => this._on_open(ws); + uws_options.drain = (ws) => this._on_drain(ws); + uws_options.ping = (ws, message) => this._on_ping(ws, message); + uws_options.pong = (ws, message) => this._on_pong(ws, message); + uws_options.close = (ws, code, message) => this._on_close(ws, code, message); + uws_options.message = (ws, message, isBinary) => this._on_message(ws, message, isBinary); + + // Create uWebsockets instance route + this.app.uws_instance.ws(this.pattern, uws_options); + } + + /** + * Handles 'open' event from uWebsockets.js + * @private + * @param {uWS.Websocket} ws + */ + _on_open(ws) { + // Create and attach HyperExpress.Websocket polyfill component to uWS websocket + ws.poly = new Websocket(ws); + + // Trigger WebsocketRoute handler on new connection open so user can listen for events + this.handler(ws.poly); + } + + /** + * Handles 'ping' event from uWebsockets.js + * @private + * @param {uWS.Websocket} ws + * @param {ArrayBuffer=} message + */ + _on_ping(ws, message = '') { + // Emit 'ping' event on websocket poly component + ws.poly.emit('ping', this.#message_parser(message)); + } + + /** + * Handles 'pong' event from uWebsockets.js + * @private + * @param {uWS.Websocket} ws + * @param {ArrayBuffer=} message + */ + _on_pong(ws, message = '') { + // Emit 'pong' event on websocket poly component + ws.poly.emit('pong', this.#message_parser(message)); + } + + /** + * Handles 'drain' event from uWebsockets.js + * @private + * @param {uWS.Websocket} ws + */ + _on_drain(ws) { + // Emit 'drain' event on websocket poly component + ws.poly.emit('drain'); + } + + /** + * Handles 'message' event from uWebsockets.js + * @private + * @param {uWS.Websocket} ws + * @param {ArrayBuffer} message + * @param {Boolean} is_binary + */ + _on_message(ws, message = '', is_binary) { + // Emit 'message' event with parsed message from uWS + ws.poly.emit('message', this.#message_parser(message), is_binary); + } + + /** + * Handles 'close' event from uWebsockets.js + * @param {uWS.Websocket} ws + * @param {Number} code + * @param {ArrayBuffer} message + */ + _on_close(ws, code, message = '') { + // Mark websocket poly component as closed + ws.poly._destroy(); + + // Emit 'close' event with parsed message + ws.poly.emit('close', code, this.#message_parser(message)); + + // De-reference the attached polyfill Websocket component so it can ne garbage collected + delete ws.poly; + } +} + +module.exports = WebsocketRoute; diff --git a/packages/hyper-express/src/shared/operators.js b/packages/hyper-express/src/shared/operators.js new file mode 100644 index 0000000..0e00a7b --- /dev/null +++ b/packages/hyper-express/src/shared/operators.js @@ -0,0 +1,197 @@ +'use strict'; +/** + * Writes values from focus object onto base object. + * + * @param {Object} obj1 Base Object + * @param {Object} obj2 Focus Object + */ +function wrap_object(original, target) { + Object.keys(target).forEach((key) => { + if (typeof target[key] == 'object') { + if (Array.isArray(target[key])) return (original[key] = target[key]); // lgtm [js/prototype-pollution-utility] + if (original[key] === null || typeof original[key] !== 'object') original[key] = {}; + wrap_object(original[key], target[key]); + } else { + original[key] = target[key]; + } + }); +} + +/** + * This method parses route pattern into an array of expected path parameters. + * + * @param {String} pattern + * @returns {Array} [[key {String}, index {Number}], ...] + */ + +function parse_path_parameters(pattern) { + let results = []; + let counter = 0; + if (pattern.indexOf('/:') > -1) { + let chunks = pattern.split('/').filter((chunk) => chunk.length > 0); + for (let index = 0; index < chunks.length; index++) { + let current = chunks[index]; + if (current.startsWith(':') && current.length > 2) { + results.push([current.substring(1), counter]); + counter++; + } + } + } + return results; +} + +/** + * This method converts ArrayBuffers to a string. + * + * @param {ArrayBuffer} array_buffer + * @param {String} encoding + * @returns {String} String + */ + +function array_buffer_to_string(array_buffer, encoding = 'utf8') { + return Buffer.from(array_buffer).toString(encoding); +} + +/** + * Copies an ArrayBuffer to a Uint8Array. + * Note! This method is supposed to be extremely performant as it is used by the Body parser. + * @param {ArrayBuffer} array_buffer + */ +function copy_array_buffer_to_uint8array(array_buffer) { + const source = new Uint8Array(array_buffer); + return new Uint8Array(source.subarray(0, source.length)); +} + +/** + * Returns a promise which is resolved after provided delay in milliseconds. + * + * @param {Number} delay + * @returns {Promise} + */ +function async_wait(delay) { + return new Promise((resolve, reject) => setTimeout((res) => res(), delay, resolve)); +} + +/** + * Merges provided relative paths into a singular relative path. + * + * @param {String} base_path + * @param {String} new_path + * @returns {String} path + */ +function merge_relative_paths(base_path, new_path) { + // handle both roots merger case + if (base_path == '/' && new_path == '/') return '/'; + + // Inject leading slash to new_path + if (!new_path.startsWith('/')) new_path = '/' + new_path; + + // handle base root merger case + if (base_path == '/') return new_path; + + // handle new path root merger case + if (new_path == '/') return base_path; + + // strip away leading slash from base path + if (base_path.endsWith('/')) base_path = base_path.substr(0, base_path.length - 1); + + // Merge path and add a slash in between if new_path does not have a starting slash + return `${base_path}${new_path}`; +} + +/** + * Returns all property descriptors of an Object including extended prototypes. + * + * @param {Object} prototype + */ +function get_all_property_descriptors(prototype) { + // Retrieve initial property descriptors + const descriptors = Object.getOwnPropertyDescriptors(prototype); + + // Determine if we have a parent prototype with a custom name + const parent = Object.getPrototypeOf(prototype); + if (parent && parent.constructor.name !== 'Object') { + // Merge and return property descriptors along with parent prototype + return Object.assign(descriptors, get_all_property_descriptors(parent)); + } + + // Return property descriptors + return descriptors; +} + +/** + * Inherits properties, getters, and setters from one prototype to another with the ability to optionally define middleman methods. + * + * @param {Object} options + * @param {Object|Array} options.from - The prototype to inherit from + * @param {Object} options.to - The prototype to inherit to + * @param {function(('FUNCTION'|'GETTER'|'SETTER'), string, function):function=} options.method - The method to inherit. Parameters are: type, name, method. + * @param {function(string):string=} options.override - The method name to override the original with. Parameters are: name. + * @param {Array} options.ignore - The property names to ignore + */ +function inherit_prototype({ from, to, method, override, ignore = ['constructor'] }) { + // Recursively call self if the from prototype is an Array of prototypes + if (Array.isArray(from)) return from.forEach((f) => inherit_prototype({ from: f, to, override, method, ignore })); + + // Inherit the descriptors from the "from" prototype to the "to" prototype + const to_descriptors = get_all_property_descriptors(to); + const from_descriptors = get_all_property_descriptors(from); + Object.keys(from_descriptors).forEach((name) => { + // Ignore the properties specified in the ignore array + if (ignore.includes(name)) return; + + // Destructure the descriptor function properties + const { value, get, set } = from_descriptors[name]; + + // Determine if this descriptor name would be an override + // Override the original name with the provided name resolver for overrides + if (typeof override == 'function' && to_descriptors[name]?.value) name = override(name) || name; + + // Determine if the descriptor is a method aka. a function + if (typeof value === 'function') { + // Inject a middleman method into the "to" prototype + const middleman = method('FUNCTION', name, value); + if (middleman) { + // Define the middleman method on the "to" prototype + Object.defineProperty(to, name, { + configurable: true, + enumerable: true, + writable: true, + value: middleman, + }); + } + } else { + // Initialize a definition object + const definition = {}; + + // Initialize a middleman getter method + if (typeof get === 'function') definition.get = method('GETTER', name, get); + + // Initialize a middleman setter method + if (typeof set === 'function') definition.set = method('SETTER', name, set); + + // Inject the definition into the "to" prototype + if (definition.get || definition.set) Object.defineProperty(to, name, definition); + } + }); +} + +/** + * Converts Windows path backslashes to forward slashes. + * @param {string} string + * @returns {string} + */ +function to_forward_slashes(string) { + return string.split('\\').join('/'); +} + +module.exports = { + parse_path_parameters, + array_buffer_to_string, + wrap_object, + async_wait, + inherit_prototype, + merge_relative_paths, + to_forward_slashes, + copy_array_buffer_to_uint8array, +}; diff --git a/packages/hyper-express/src/shared/process-multipart.js b/packages/hyper-express/src/shared/process-multipart.js new file mode 100644 index 0000000..6e7c465 --- /dev/null +++ b/packages/hyper-express/src/shared/process-multipart.js @@ -0,0 +1,60 @@ +const os = require('os'); +const path = require('path'); +const UploadedFile = require('./uploaded-file'); + +async function process_multipart_data(req) { + const fields = {}; + const tempDirectory = os.tmpdir(); + const generateTempFilename = (filename) => `${Date.now()}-${filename}`; + const advancedPattern = /(\w+)(\[\d*\])+/; + + await req.multipart(async (field) => { + const isArray = field.name.match(advancedPattern); + const strippedFieldName = isArray?.length ? isArray[1] : field.name; + const existingFieldValue = fields[strippedFieldName]; + + if (Array.isArray(isArray) && !existingFieldValue) { + fields[strippedFieldName] = []; + } + + if (field.file) { + const tempFileName = generateTempFilename(field.file.name); + const tempFilePath = path.join(tempDirectory, tempFileName); + let fileSize = 0; + + field.file.stream.on('data', (chunk) => { + fileSize += chunk.length; + }); + + await field.write(tempFilePath); + + const uploadedFile = new UploadedFile( + field.file.name, + fileSize, + field.mime_type, + tempFileName, + tempFilePath, + ); + + const existingFieldValue = fields[strippedFieldName]; + if (Array.isArray(existingFieldValue)) { + fields[strippedFieldName] = existingFieldValue.concat(uploadedFile); + } else { + fields[strippedFieldName] = uploadedFile; + } + } else { + const existingFieldValue = fields[strippedFieldName]; + if (Array.isArray(existingFieldValue)) { + fields[strippedFieldName] = existingFieldValue.concat(field.value); + } else { + fields[strippedFieldName] = field.value; + } + } + }); + + return fields; +} + +module.exports = { + process_multipart_data, +}; diff --git a/packages/hyper-express/src/shared/uploaded-file.js b/packages/hyper-express/src/shared/uploaded-file.js new file mode 100644 index 0000000..2cbb4f0 --- /dev/null +++ b/packages/hyper-express/src/shared/uploaded-file.js @@ -0,0 +1,39 @@ +const fs = require('fs'); + +class UploadedFile { + _filename; + _sizeInBytes; + _mimeType; + _tempName; + _tempPath; + + constructor(filename, size, mimeType, tempName, tempPath) { + this._filename = filename; + this._sizeInBytes = size; + this._mimeType = mimeType; + this._tempName = tempName; + this._tempPath = tempPath; + } + + get filename() { + return this._filename; + } + + get sizeInBytes() { + return this._sizeInBytes; + } + + get mimeType() { + return this._mimeType; + } + + get extension() { + return this.filename; + } + + async toBuffer() { + return fs.readFileSync(this.tempPath); + } +} + +module.exports = UploadedFile; diff --git a/packages/hyper-express/tests/components/Server.js b/packages/hyper-express/tests/components/Server.js new file mode 100644 index 0000000..a57320a --- /dev/null +++ b/packages/hyper-express/tests/components/Server.js @@ -0,0 +1,110 @@ +const { server, HyperExpress } = require('../configuration.js'); +const { log, assert_log } = require('../scripts/operators.js'); + +// Create a test HyperExpress instance +const TEST_SERVER = new HyperExpress.Server({ + fast_buffers: true, + max_body_length: 1000 * 1000 * 7, +}); + +// Set some value into the locals object to be checked in the future +// through the Request/Response app property +TEST_SERVER.locals.some_reference = { + some_data: true, +}; + +// Bind error handler for catch-all logging +TEST_SERVER.set_error_handler((request, response, error) => { + // Handle expected errors with their appropriate callbacks + if (typeof request.expected_error == 'function') { + request.expected_error(error); + } else { + // Treat as global error and log to console + log( + 'UNCAUGHT_ERROR_REQUEST', + `${request.method} | ${request.url}\n ${JSON.stringify(request.headers, null, 2)}` + ); + console.log(error); + response.send('Uncaught Error Occured'); + } +}); + +function not_found_handler(request, response) { + // Handle dynamic middleware executions to the requester + if (Array.isArray(request.middleware_executions)) { + request.middleware_executions.push('not-found'); + return response.json(request.middleware_executions); + } + + // Return a 404 response + return response.status(404).send('Not Found'); +} + +// Bind not found handler for unexpected incoming requests +TEST_SERVER.set_not_found_handler((request, response) => { + console.warn( + 'This handler should not actually be called as one of the tests binds a Server.all("*") route which should prevent this handler from ever being ran.' + ); + not_found_handler(request, response); +}); + +// Bind a test route which returns a response with a delay +// This will be used to simulate long running requests +TEST_SERVER.get('/echo/:delay', async (request, response) => { + // Wait for the specified delay and return a response + const delay = Number(request.path_parameters.delay) || 0; + await new Promise((resolve) => setTimeout(resolve, delay)); + return response.send(delay.toString()); +}); + +async function test_server_shutdown() { + let group = 'SERVER'; + + // Make a fetch request to the echo endpoint with a delay of 100ms + const delay = 100; + const started_at = Date.now(); + + // Send the request and time the response + const response = await fetch(`${server.base}/echo/${delay}`); + + // Begin the server shutdown process and time the shutdown + let shutdown_time_ms = 0; + const shutdown_promise = TEST_SERVER.shutdown(); + shutdown_promise.then(() => (shutdown_time_ms = Date.now() - started_at)); + + // Send a second fetch which should be immediately closed + let response2_error; + try { + const response2 = await fetch(`${server.base}/echo/${delay}`); + } catch (error) { + response2_error = error; + } + + // Begin processing the response body + const body = await response.text(); + const request_time_ms = Date.now() - started_at; + + // Wait for the server shutdown to complete + await shutdown_promise; + + // Verify middleware functionalitiy and property binding + assert_log( + group, + 'Graceful Shutdown Test In ' + (Date.now() - started_at) + 'ms', + // Ensure that the response body matches the delay + // Ensure that the request time is greater than the delay (The handler artificially waited for the delay) + // Ensure that the shutdown time is greater than the delay (The server shutdown took longer than the delay) + // Ensure that response2 failed over network as the server shutdown was in process which would immediately close the request + () => + body === delay.toString() && + request_time_ms >= delay && + shutdown_time_ms >= delay && + response2_error !== undefined + ); +} + +module.exports = { + TEST_SERVER, + not_found_handler, + test_server_shutdown, +}; diff --git a/packages/hyper-express/tests/components/features/HostManager.js b/packages/hyper-express/tests/components/features/HostManager.js new file mode 100644 index 0000000..0f17205 --- /dev/null +++ b/packages/hyper-express/tests/components/features/HostManager.js @@ -0,0 +1,58 @@ +const { assert_log } = require('../../scripts/operators.js'); +const { TEST_SERVER } = require('../Server.js'); + +function test_hostmanager_object() { + let group = 'Server'; + let candidate = 'HyperExpress.HostManager'; + + // Retrieve the host manager + const manager = TEST_SERVER.hosts; + + // Define random host configurations + const hostnames = [ + [ + 'example.com', + { + passphrase: 'passphrase', + }, + ], + [ + 'google.com', + { + passphrase: 'passphrase', + }, + ], + ]; + + // Add the host names to the host manager + for (const [hostname, options] of hostnames) { + manager.add(hostname, options); + } + + // Assert that the host manager contains the host names + for (const [hostname, options] of hostnames) { + assert_log( + group, + candidate + ` - Host Registeration Test For ${hostname}`, + () => JSON.stringify(manager.registered[hostname]) === JSON.stringify(options) + ); + } + + // Remove the host names from the host manager + for (const [hostname, options] of hostnames) { + manager.remove(hostname); + } + + // Assert that the host manager does not contain the host names + for (const [hostname, options] of hostnames) { + assert_log( + group, + candidate + ` - Host Un-Registeration Test For ${hostname}`, + () => !(hostname in manager.registered) + ); + } +} + +module.exports = { + test_hostmanager_object, +}; diff --git a/packages/hyper-express/tests/components/features/LiveFile.js b/packages/hyper-express/tests/components/features/LiveFile.js new file mode 100644 index 0000000..b62e4f1 --- /dev/null +++ b/packages/hyper-express/tests/components/features/LiveFile.js @@ -0,0 +1,49 @@ +const { assert_log } = require('../../scripts/operators.js'); +const { fetch, server } = require('../../configuration.js'); +const { TEST_SERVER } = require('../Server.js'); +const fs = require('fs'); +const path = require('path'); +const endpoint = '/tests/response/send-file'; +const endpoint_url = server.base + endpoint; +const test_file_path = path.resolve(__dirname, '../../../tests/content/test.html'); + +// Create Backend HTTP Route +TEST_SERVER.get(endpoint, async (request, response) => { + // We purposely delay 100ms so cached vs. uncached does not rely too much on system disk + return response.download(test_file_path, 'something.html'); +}); + +async function test_livefile_object() { + let group = 'RESPONSE'; + let candidate = 'HyperExpress.Response'; + + // Read the test file into memory + const test_file_string = fs.readFileSync(test_file_path).toString(); + + // Perform fetch request + const response = await fetch(endpoint_url); + const body = await response.text(); + + // Test initial content type and length test for file + const headers = response.headers.raw(); + const content_type = headers['content-type']; + const content_length = headers['content-length']; + assert_log(group, candidate + '.file()', () => { + return ( + content_type == 'text/html; charset=utf-8' && + content_length == test_file_string.length.toString() && + body.length == test_file_string.length + ); + }); + + // Test Content-Disposition header to validate .attachment() + assert_log( + group, + `${candidate}.attachment() & ${candidate}.download()`, + () => headers['content-disposition'][0] == 'attachment; filename="something.html"' + ); +} + +module.exports = { + test_livefile_object: test_livefile_object, +}; diff --git a/packages/hyper-express/tests/components/http/Request.js b/packages/hyper-express/tests/components/http/Request.js new file mode 100644 index 0000000..56dccf4 --- /dev/null +++ b/packages/hyper-express/tests/components/http/Request.js @@ -0,0 +1,363 @@ +const { log, assert_log, random_string } = require('../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../configuration.js'); +const { test_request_multipart } = require('./scenarios/request_multipart.js'); +const { test_request_stream_pipe } = require('./scenarios/request_stream.js'); +const { test_request_chunked_stream } = require('./scenarios/request_chunked_stream.js'); +const { test_request_body_echo_test } = require('./scenarios/request_body_echo_test.js'); +const { test_request_uncaught_rejections } = require('./scenarios/request_uncaught_rejections.js'); +const { test_request_router_paths_test } = require('./scenarios/request_router_paths_test.js'); +const { test_request_chunked_json } = require('./scenarios/request_chunked_json.js'); +const fs = require('fs'); +const _path = require('path'); +const crypto = require('crypto'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request/:param1/:param2'; +const route_specific_endpoint = '/tests/request-route/'; +const middleware_delay = 100 + Math.floor(Math.random() * 150); +const signature_value = random_string(10); +const signature_secret = random_string(10); +const middleware_property = random_string(10); +const base = server.base; + +// Bind a middlewares for simulating artificial delay on request endpoint +const global_middleware_1 = (request, response, next) => { + // We only want this middleware to run for this request endpoint + if (request.headers['x-middleware-test'] === 'true') { + request.mproperty = middleware_property; + return setTimeout((n) => n(), middleware_delay, next); + } + + return next(); +}; +router.use(global_middleware_1); + +// Test Promise returning middlewares support +const global_middleware_2 = (request, response) => { + return new Promise((resolve, reject) => { + // We only want this middleware to run for this request endpoint + if (request.headers['x-middleware-test-2'] === 'true') { + request.mproperty2 = middleware_property; + } + + resolve(); + }); +}; +router.use(global_middleware_2); + +let last_endpoint_mproperty; +let last_endpoint_mproperty2; +let last_endpoint_mproperty3; + +const route_specific_middleware = (request, response, next) => { + // We only want this middleware to run for this request endpoint + if (request.headers['x-middleware-test-3'] === 'true') { + request.mproperty3 = middleware_property; + return setTimeout((n) => n(), middleware_delay, next); + } + + return next(); +}; + +// Load scenarios and bind router to test server +const { test_middleware_double_iteration } = require('./scenarios/middleware_double_iteration.js'); +const { test_middleware_iteration_error } = require('./scenarios/middleware_iteration_error.js'); +const { test_middleware_uncaught_async_error } = require('./scenarios/middleware_uncaught_async_error.js'); +const { test_middleware_layered_iterations } = require('./scenarios/middleware_layered_iteration.js'); +const { test_middleware_dynamic_iteration } = require('./scenarios/middleware_dynamic_iteration.js'); +const { test_middleware_execution_order } = require('./scenarios/middleware_execution_order.js'); +const { TEST_SERVER } = require('../Server.js'); +TEST_SERVER.use(router); + +// Create a temporary specific middleware route +router.get( + route_specific_endpoint, + { + middlewares: [route_specific_middleware], + }, + (request, response) => { + // Store mproperty if exists on request object + if (request.mproperty3) last_endpoint_mproperty3 = request.mproperty3; + + let body_error; + try { + request.body; + } catch (error) { + body_error = error; + } + + return response.json({ + success: true, + body_error: body_error !== undefined, + }); + } +); + +// Create Backend HTTP Route with expected body of urlencoded to test request.body property +router.any(endpoint, async (request, response) => { + // Parse the incoming request body as text, json, and urlencoded to test all formats + let text = await request.text(); + let json = await request.json(); + let urlencoded = await request.urlencoded(); + + // Store mproperty if exists on request object to check for middleware + if (request.mproperty) last_endpoint_mproperty = request.mproperty; + if (request.mproperty2) last_endpoint_mproperty2 = request.mproperty; + + // Return all possible information about incoming request + return response.json({ + locals: request.app.locals, + method: request.method, + url: request.url, + path: request.path, + path_query: request.path_query, + headers: request.headers, + path_parameters: request.path_parameters, + query_parameters: request.query_parameters, + ip: request.ip, + proxy_ip: request.proxy_ip, + cookies: request.cookies, + signature_check: + request.unsign(request.sign(signature_value, signature_secret), signature_secret) === signature_value, + body: { + text, + json, + urlencoded, + }, + }); +}); + +function crypto_random(length) { + return new Promise((resolve, reject) => + crypto.randomBytes(Math.round(length / 2), (error, buffer) => { + if (error) return reject(error); + resolve(buffer.toString('hex')); + }) + ); +} + +async function test_request_object() { + // Prepare Test Candidates + log('REQUEST', 'Testing HyperExpress.Request Object...'); + + const body_size = 10 * 1024 * 1024; + log( + 'REQUEST', + `Generating A Large ${body_size.toLocaleString()} Characters Size Body To Simulate Too-Large Large Payload...` + ); + + let group = 'REQUEST'; + let candidate = 'HyperExpress.Request'; + let start_time = Date.now(); + let test_method = 'POST'; + let param1 = random_string(10); + let param2 = random_string(10); + let query1 = random_string(10); + let query2 = random_string(10); + let query = `?query1=${query1}&query2=${query2}`; + let too_large_body_value = await crypto_random(body_size); + let body_test_value = too_large_body_value.substr(0, too_large_body_value.length / 2); + let fetch_body = JSON.stringify({ + test_value: body_test_value, + }); + let header_test_value = random_string(10); + let header_test_cookie = { + name: random_string(10), + value: random_string(10), + }; + + // Prepare HTTP Request Information + let path = `/tests/request/${param1}/${param2}`; + let url = path + query; + let options = { + method: test_method, + headers: { + 'x-test-value': header_test_value, + 'content-type': 'application/json', + cookie: `${header_test_cookie.name}=${header_test_cookie.value}`, + 'x-middleware-test': 'true', + 'x-middleware-test-2': 'true', + }, + body: fetch_body, + }; + + // Perform Too Large Body Rejection Test + const too_large_response = await fetch(base + url, { + method: test_method, + body: too_large_body_value, + }); + + // Assert no uwebsockets version header to be found + assert_log(group, 'No uWebsockets Version Header', () => !too_large_response.headers.get('uwebsockets')); + + // Assert rejection status code as 413 Too Large Payload + assert_log(group, 'Too Large Body 413 HTTP Code Reject', () => too_large_response.status === 413); + + // Perform a too large body test with transfer-encoding: chunked + const temp_file_path = _path.resolve(_path.join(__dirname, '../../../tests/content/too-large-file.temp')); + fs.writeFileSync(temp_file_path, too_large_body_value); + try { + const too_large_chunked_response = await fetch(base + url, { + method: test_method, + body: fs.createReadStream(temp_file_path), + headers: { + 'transfer-encoding': 'chunked', + }, + }); + + // Cleanup the temp file + fs.unlinkSync(temp_file_path); + + // Assert rejection status code as 413 Too Large Payload + assert_log( + group, + 'Too Large Body 413 HTTP Code Reject (Chunked)', + () => too_large_chunked_response.status === 413 + ); + } catch (error) { + // Cleanup the temp file + fs.unlinkSync(temp_file_path); + } + + // Perform a request with a urlencoded body to test .urlencoded() method + const urlencoded_string = `url1=${param1}&url2=${param2}`; + const urlencoded_response = await fetch(base + url, { + method: test_method, + body: urlencoded_string, + }); + const urlencoded_body = await urlencoded_response.json(); + + // Perform HTTP Request To Endpoint + let req_start_time = Date.now(); + let response = await fetch(base + url, options); + let body = await response.json(); + + // Verify middleware functionalitiy and property binding + assert_log(group, 'Middleware Execution & Timing Test', () => Date.now() - req_start_time > middleware_delay); + + assert_log( + group, + 'Middleware Property Binding Test', + () => last_endpoint_mproperty === middleware_property && last_endpoint_mproperty2 === middleware_property + ); + + assert_log(group, 'Route Specific Middleware Avoidance Test', () => last_endpoint_mproperty3 == undefined); + + await fetch(base + route_specific_endpoint, { + headers: { + 'x-middleware-test-3': 'true', + }, + }); + + assert_log( + group, + 'Route Specific Middleware Binding & Property Test', + () => last_endpoint_mproperty3 === middleware_property + ); + + // Test request uncaught rejections + await test_request_uncaught_rejections(); + + // Test double iteration violation for middlewares + await test_middleware_double_iteration(); + + // Test layered middleware iterations + await test_middleware_layered_iterations(); + + // Test simulated middleware iteration error + await test_middleware_iteration_error(); + + // Test uncaught async middleware error + await test_middleware_uncaught_async_error(); + + // Test dynamic middleware iteration + await test_middleware_dynamic_iteration(); + + // Test middleware execution order + await test_middleware_execution_order(); + + // Verify .app.locals + assert_log(group, candidate + '.app.locals', () => body.locals.some_reference.some_data === true); + + // Verify .method + assert_log(group, candidate + '.method', () => test_method === body.method); + + // Verify .url + assert_log(group, candidate + '.url', () => body.url === url); + + // Verify .path + assert_log(group, candidate + '.path', () => path === body.path); + + test_request_router_paths_test(); + + // Verify .query + assert_log(group, candidate + '.query', () => query.substring(1) === body.path_query); + + // Verify .ip + assert_log(group, candidate + '.ip', () => body.ip === '127.0.0.1'); + + // Verify .proxy_ip + assert_log(group, candidate + '.proxy_ip', () => body.proxy_ip === ''); + + // Verify .headers + assert_log(group, candidate + '.headers["x-test-value", "cookie", "content-length"]', () => { + let headers = body.headers; + let value_test = headers['x-test-value'] === header_test_value; + let cookie_test = headers.cookie === options.headers.cookie; + let content_length_test = +headers['content-length'] === fetch_body.length; + return value_test && cookie_test && content_length_test; + }); + + // Verify .query_parameters + assert_log(group, candidate + '.query_parameters', () => { + let query1_test = body.query_parameters.query1 === query1; + let query2_test = body.query_parameters.query2 === query2; + return query1_test && query2_test; + }); + + // Verify .path_parameters + assert_log(group, candidate + '.path_parameters', () => { + let param1_test = body.path_parameters.param1 === param1; + let param2_test = body.path_parameters.param2 === param2; + return param1_test && param2_test; + }); + + // Verify .cookies + assert_log(group, candidate + '.cookies', () => body.cookies[header_test_cookie.name] === header_test_cookie.value); + + // Verify chunked transfer request stream + await test_request_chunked_stream(); + + // Verify .stream readable request stream piping + await test_request_stream_pipe(); + + // Verify .sign() and .unsign() + assert_log(group, `${candidate}.sign() and ${candidate}.unsign()`, () => body.signature_check === true); + + // Verify .text() + assert_log(group, candidate + '.text()', () => body.body.text === options.body); + + // Verify .json() + assert_log(group, candidate + '.json()', () => JSON.stringify(body.body.json) === options.body); + + // Verify .json() with chunked transfer + await test_request_chunked_json(); + + // Verify .json() with small body payload echo test + await test_request_body_echo_test(); + + // Verify .urlencoded() + assert_log(group, candidate + '.urlencoded()', () => { + const { url1, url2 } = urlencoded_body.body.urlencoded; + return url1 === param1 && url2 === param2; + }); + + // Test .multipart() uploader with both a sync/async handler + await test_request_multipart(false); + await test_request_multipart(true); + + log(group, `Finished Testing ${candidate} In ${Date.now() - start_time}ms\n`); +} + +module.exports = { + test_request_object: test_request_object, +}; diff --git a/packages/hyper-express/tests/components/http/Response.js b/packages/hyper-express/tests/components/http/Response.js new file mode 100644 index 0000000..6889b36 --- /dev/null +++ b/packages/hyper-express/tests/components/http/Response.js @@ -0,0 +1,198 @@ +const { log, assert_log, random_string } = require('../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../configuration.js'); +const { test_livefile_object } = require('../../components/features/LiveFile.js'); +const { test_response_custom_status } = require('./scenarios/response_custom_status.js'); +const { test_response_send_no_body } = require('./scenarios/response_send_no_body.js'); +const { test_response_headers_behavior } = require('./scenarios/response_headers_behavior.js'); +const { test_response_stream_method } = require('./scenarios/response_stream.js'); +const { test_response_chunked_write } = require('./scenarios/response_chunked_write.js'); +const { test_response_piped_write } = require('./scenarios/response_piped.js'); +const { test_response_events } = require('./scenarios/response_hooks.js'); +const { test_response_sync_writes } = require('./scenarios/response_stream_sync_writes.js'); +const { test_response_custom_content_length } = require('./scenarios/response_custom_content_length.js'); +const { test_response_sse } = require('./scenarios/response_sse.js'); +const { test_response_set_header } = require('./scenarios/response_set_header.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response/operators'; +const endpoint_url = server.base + endpoint; + +function write_prepare_event(request, response) { + if (typeof request.url == 'string' && !response.completed) { + response.header('hook-called', 'prepare'); + events_emitted.push('prepare'); + } +} + +// Create Backend HTTP Route +const events_emitted = []; +router.post(endpoint, async (request, response) => { + let body = await request.json(); + + // Validate response.app.locals + if (response.app.locals.some_reference.some_data !== true) throw new Error('Invalid Response App Locals Detected!'); + + // Test hooks + response.on('abort', () => events_emitted.push('abort')); + response.on('prepare', write_prepare_event); + response.on('finish', () => events_emitted.push('finish')); + response.on('close', () => events_emitted.push('close')); + + // Perform Requested Operations For Testing + if (Array.isArray(body)) + body.forEach((operation) => { + let method = operation[0]; + let parameters = operation[1]; + + // Utilize the Response.statusCode compatibility setter for status code modifications + if (method == 'status') { + response.statusCode = parameters; + } else if (Array.isArray(parameters)) { + // Support up to 4 multi parameters + response[method](parameters[0], parameters[1], parameters[2], parameters[3]); + } else { + response[method](parameters); + } + }); + + if (!response.aborted) return response.send(); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../Server.js'); +const { test_response_send_status } = require('./scenarios/response_send_status.js'); +TEST_SERVER.use(router); + +async function test_response_object() { + let start_time = Date.now(); + let group = 'RESPONSE'; + let candidate = 'HyperExpress.Response'; + log(group, 'Testing HyperExpress.Response Object...'); + + // Test HyperExpress.Response Operators + let test_status_code = 404; + let test_mime_type = 'html'; + let header_test_name = random_string(10); + let header_test_value = random_string(10); + let cookie_test_name = random_string(10); + let cookie_test_value = random_string(10); + let test_html_placeholder = random_string(20); + let test_cookie = { + name: random_string(10) + '_sess', + value: random_string(10), + }; + + let response1 = await fetch(endpoint_url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + cookie: `${test_cookie.name}=${test_cookie.value}`, + }, + body: JSON.stringify([ + ['status', test_status_code], + ['type', test_mime_type], + ['header', [header_test_name, header_test_value]], + ['cookie', [cookie_test_name, cookie_test_value]], + ['cookie', [test_cookie.name, null]], + ['send', test_html_placeholder], + ]), + }); + let body1 = await response1.text(); + + // Verify .status() + assert_log(group, candidate + '.status()', () => test_status_code === response1.status); + + // Verify .type() + assert_log( + group, + candidate + '.type()', + () => response1.headers.get('content-type') === 'text/html; charset=utf-8' + ); + + // Verify .header() + assert_log(group, candidate + '.header()', () => response1.headers.get(header_test_name) === header_test_value); + + // Verify .cookie() + assert_log(group, candidate + '.cookie() AND .cookie(name, null) to delete', () => { + let cookies = {}; + response1.headers + .get('set-cookie') + .split(', ') + .forEach((chunk) => { + if (chunk.indexOf('=') > -1) { + chunk = chunk.split('='); + let name = chunk[0]; + let value = chunk[1].split(';')[0]; + let properties = chunk.join('=').split('; ')[1]; + cookies[name] = { + value: value, + properties: properties, + }; + } + }); + + let test_cookie_test = cookies[cookie_test_name]?.value === cookie_test_value; + let delete_cookie_value_test = cookies[test_cookie.name]?.value === ''; + let delete_cookie_props_test = cookies[test_cookie.name]?.properties === 'Max-Age=0'; + return test_cookie_test && delete_cookie_value_test && delete_cookie_props_test; + }); + + // Verify the custom HTTP status code and message support + await test_response_custom_status(); + + // Verify the custom HTTP status code and message support + await test_response_send_status(); + + // Verify the behavior of the .header() and .cookie() methods + await test_response_headers_behavior(); + + // Verify .on() aka. Response events + await test_response_events(); + + // Verify .send() + assert_log(group, candidate + '.send()', () => body1 === test_html_placeholder); + + // Verify .send() with custom content-length header specified body + await test_response_custom_content_length(); + + // Verify .send() with no body and custom content-length + await test_response_send_no_body(); + + // Test Response.sse (Server-Sent Events) support + await test_response_sse(); + + // Test Response.stream() + await test_response_stream_method(); + + // Test Response.write() for sync writes + await test_response_sync_writes(); + + // Test Response.write() for chunked writing + await test_response_chunked_write(); + + // Test Response.write() for piped writes + await test_response_piped_write(); + + // Test Response.LiveFile object + await test_livefile_object(); + + // Test Response.set() header + await test_response_set_header(); + + // Verify .on() aka. Response events + assert_log( + group, + candidate + '.on()', + () => + events_emitted.length == 3 && + events_emitted[0] === 'prepare' && + events_emitted[1] === 'finish' && + events_emitted[2] === 'close' && + response1.headers.get('hook-called') === 'prepare' + ); + + log(group, `Finished Testing ${candidate} In ${Date.now() - start_time}ms\n`); +} + +module.exports = { + test_response_object: test_response_object, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/middleware_double_iteration.js b/packages/hyper-express/tests/components/http/scenarios/middleware_double_iteration.js new file mode 100644 index 0000000..2867224 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/middleware_double_iteration.js @@ -0,0 +1,55 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/middleware-double-iteration'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// This middleware should only run on this endpoint +const double_iteration_middleware = async (request, response, next) => { + // Bind an artificial error handler so we don't treat this as uncaught error + request.expected_error = () => response.status(501).send('DOUBLE_ITERATION_VIOLATION'); + + // Since this is an async callback, calling next and the async callback resolving will trigger a double iteration violation + next(); +}; + +const delay_middleware = (request, response, next) => setTimeout(next, 10); + +// Create Backend HTTP Route +router.get( + scenario_endpoint, + double_iteration_middleware, + [delay_middleware], // This weird parameter pattern is to test Express.js compatibility pattern for providing multiple middlewares through parameters/arrays + { + max_body_length: 1024 * 1024 * 10, + middlewares: [delay_middleware], + }, + async (request, response) => { + return response.send('Good'); + } +); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_middleware_double_iteration() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + + // Perform fetch request + const response = await fetch(endpoint_url); + const body = await response.text(); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Middleware Double Iteration Violation`, + () => response.status === 501 && body === 'DOUBLE_ITERATION_VIOLATION' + ); +} + +module.exports = { + test_middleware_double_iteration, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/middleware_dynamic_iteration.js b/packages/hyper-express/tests/components/http/scenarios/middleware_dynamic_iteration.js new file mode 100644 index 0000000..35ab38a --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/middleware_dynamic_iteration.js @@ -0,0 +1,108 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/middleware-dynamic-iteration'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const { TEST_SERVER } = require('../../Server.js'); + +// Bind a global middleware which is a wildcard for a path that has no existing routes +// This middleware will apply to the not found handler +const global_wildcard_middleware = (request, response, next) => { + // Check if dynamic middleware is enabled + if (request.headers['x-dynamic-middleware'] === 'true') { + return response.send('GLOBAL_WILDCARD'); + } + + // Call next middleware + next(); +}; + +TEST_SERVER.use('/global-wildcard/', global_wildcard_middleware); + +// Bind a global middleware which is a wildcard for a path that is on an existing route path +const route_specific_dynamic_middleware = (request, response, next) => { + if (request.headers['x-dynamic-middleware'] === 'true') { + response.send('ROUTE_SPECIFIC_WILDCARD'); + } + + // Call next middleware + next(); +}; +router.use('/middleware-dynamic-iteration/middleware', route_specific_dynamic_middleware); + +// Bind a middleware which will try target an incomplete part of the path and should not be executed +const incomplete_path_middleware = (request, response, next) => { + // This should never be executed + console.log('INCOMPLETE_PATH_MIDDLEWARE'); + return response.send('INCOMPLETE_PATH_MIDDLEWARE'); +}; +router.use('/middleware-dy', incomplete_path_middleware); // Notice how "/middleware-dy" should not match "/middleware-dynamic-iteration/..." + +// Create Backend HTTP Route +router.get(scenario_endpoint + '/*', async (request, response) => { + response.send('ROUTE_HANDLER'); +}); + +// Bind router to webserver +TEST_SERVER.use(endpoint, router); + +async function test_middleware_dynamic_iteration() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + + // Make a fetch request to a random path that will not be found + const not_found_response = await fetch(server.base + '/not-found/' + Math.random(), { + headers: { + 'x-dynamic-middleware': 'true', + }, + }); + + // Assert that we received a 404 response + assert_log(group, `${candidate} Unhandled Middleware Iteration`, () => not_found_response.status === 404); + + // Make a fetch request to a global not found path on the global wildcard pattern + const global_response = await fetch(server.base + '/global-wildcard/' + Math.random(), { + headers: { + 'x-dynamic-middleware': 'true', + }, + }); + const global_text = await global_response.text(); + + // Assert that the global wildcard middleware was executed + assert_log(group, `${candidate} Global Dynamic Middleware Iteration`, () => global_text === 'GLOBAL_WILDCARD'); + + // Make a fetch request to a path that has a route with a wildcard middleware + const route_specific_response = await fetch(endpoint_url + '/middleware/' + Math.random(), { + headers: { + 'x-dynamic-middleware': 'true', + }, + }); + const route_specific_text = await route_specific_response.text(); + + // Assert that the route specific wildcard middleware was executed + assert_log( + group, + `${candidate} Route-Specific Dynamic Middleware Iteration`, + () => route_specific_text === 'ROUTE_SPECIFIC_WILDCARD' + ); + + // Make a fetch request to a path that has an exact route match + const route_handler_response = await fetch(endpoint_url + '/test/random/' + Math.random(), { + headers: { + 'x-dynamic-middleware': 'true', + }, + }); + const route_handler_text = await route_handler_response.text(); + + // Assert that the route handler was executed + assert_log( + group, + `${candidate} Route-Specific Dynamic Middleware Pattern Matching Check`, + () => route_handler_text === 'ROUTE_HANDLER' + ); +} + +module.exports = { + test_middleware_dynamic_iteration, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/middleware_execution_order.js b/packages/hyper-express/tests/components/http/scenarios/middleware_execution_order.js new file mode 100644 index 0000000..3bfc387 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/middleware_execution_order.js @@ -0,0 +1,103 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/middleware-execution-order'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const { TEST_SERVER } = require('../../Server.js'); + +// Create a middleware to bind a middleware executions array to the request object +router.use(scenario_endpoint, (request, response, next) => { + // Initialize an array to contain middleware_executions + request.middleware_executions = []; + next(); +}); + +// Create a single depth middleware +router.use(scenario_endpoint + '/one', (request, response, next) => { + request.middleware_executions.push('one'); + next(); +}); + +// Create a two depth middleware that depends on the previous middleware +router.use(scenario_endpoint + '/one/two', (request, response, next) => { + request.middleware_executions.push('one/two'); + next(); +}); + +// Create a unique single depth middleware +router.use(scenario_endpoint + '/three', (request, response, next) => { + request.middleware_executions.push('three'); + next(); +}); + +// Create a catch-all middleware to ensure execution order +router.use(scenario_endpoint, (request, response, next) => { + request.middleware_executions.push('catch-all'); + next(); +}); + +// Bind routes for each middleware to test route assignment +router.get(scenario_endpoint + '/one', (request, response) => { + request.middleware_executions.push('one/route'); + response.json(request.middleware_executions); +}); + +router.get( + scenario_endpoint + '/one/two/*', + { + max_body_length: 100 * 1e6, + }, + (request, response) => { + request.middleware_executions.push('one/two/route'); + response.json(request.middleware_executions); + } +); + +// Bind router to webserver +TEST_SERVER.use(endpoint, router); + +async function test_middleware_execution_order() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + + // Make a fetch request to just the scenario endpoint which should only trigger catch-all + const catch_all_response = await fetch(endpoint_url); + const catch_all_response_json = await catch_all_response.json(); + assert_log( + group, + `${candidate} Catch-All Middleware Execution Order`, + () => ['catch-all', 'not-found'].join(',') === catch_all_response_json.join(',') + ); + + // Make a fetch request to the single depth middleware + const single_depth_response = await fetch(endpoint_url + '/one'); + const single_depth_response_json = await single_depth_response.json(); + assert_log( + group, + `${candidate} Single Path Depth Middleware Execution Order`, + () => ['one', 'catch-all', 'one/route'].join(',') === single_depth_response_json.join(',') + ); + + // Make a fetch request to the two depth middleware that depends on the previous middleware + const two_depth_response = await fetch(endpoint_url + '/one/two/' + Math.random()); + const two_depth_response_json = await two_depth_response.json(); + assert_log( + group, + `${candidate} Double Path Depth-Dependent Middleware Execution Order`, + () => ['one', 'one/two', 'catch-all', 'one/two/route'].join(',') === two_depth_response_json.join(',') + ); + + // Make a fetch request to the unique single depth middleware + const unique_single_depth_response = await fetch(endpoint_url + '/three/' + Math.random()); + const unique_single_depth_response_json = await unique_single_depth_response.json(); + assert_log( + group, + `${candidate} Single Path Depth Unique Middleware Execution Order`, + () => ['three', 'catch-all', 'not-found'].join(',') === unique_single_depth_response_json.join(',') + ); +} + +module.exports = { + test_middleware_execution_order, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/middleware_iteration_error.js b/packages/hyper-express/tests/components/http/scenarios/middleware_iteration_error.js new file mode 100644 index 0000000..a9d860b --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/middleware_iteration_error.js @@ -0,0 +1,43 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/middleware-error'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +const middleware = (request, response, next) => { + // Bind an artificial error handler so we don't treat this as uncaught error + request.expected_error = () => response.status(501).send('MIDDLEWARE_ERROR'); + + // Assume some problem occured, so we pass an error to next + next(new Error('EXPECTED_ERROR')); +}; + +// Create Backend HTTP Route +router.get(scenario_endpoint, middleware, async (request, response) => { + return response.send('Good'); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_middleware_iteration_error() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + + // Perform fetch request + const response = await fetch(endpoint_url); + const body = await response.text(); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Middleware Thrown Iteration Error Handler`, + () => response.status === 501 && body === 'MIDDLEWARE_ERROR' + ); +} + +module.exports = { + test_middleware_iteration_error, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/middleware_layered_iteration.js b/packages/hyper-express/tests/components/http/scenarios/middleware_layered_iteration.js new file mode 100644 index 0000000..15dc3be --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/middleware_layered_iteration.js @@ -0,0 +1,73 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const crypto = require('crypto'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/middleware-layered-iteration'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route +const options = { + max_body_length: 1024 * 1024 * 25, +}; + +// Shallow copy of options before route creation +const options_copy = { + ...options, +}; + +router.post( + scenario_endpoint, + options, + async (req, res, next) => { + req.body = await req.json(); + }, + (req, res, next) => { + res.locals.data = req.body; + next(); + }, + (req, res) => { + res.status(200).json(res.locals.data); + } +); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_middleware_layered_iterations(iterations = 5) { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + for (let iteration = 0; iteration < iterations; iteration++) { + // Generate a random payload + const payload = {}; + for (let i = 0; i < 10; i++) { + payload[crypto.randomUUID()] = crypto.randomUUID(); + } + + // Perform fetch request + const response = await fetch(endpoint_url, { + method: 'POST', + body: JSON.stringify(payload), + }); + const body = await response.json(); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Middleware Layered Iterations Test #${iteration + 1}`, + () => JSON.stringify(payload) === JSON.stringify(body) + ); + } + + // Test to see that the provided options object was not modified + assert_log( + group, + `${candidate} Middleware Provided Object Immutability Test`, + () => JSON.stringify(options) === JSON.stringify(options_copy) + ); +} + +module.exports = { + test_middleware_layered_iterations, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/middleware_uncaught_async_error.js b/packages/hyper-express/tests/components/http/scenarios/middleware_uncaught_async_error.js new file mode 100644 index 0000000..abd1475 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/middleware_uncaught_async_error.js @@ -0,0 +1,43 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/middleware-uncaught-async-error'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +const middleware = async (request, response, next) => { + // Bind an artificial error handler so we don't treat this as uncaught error + request.expected_error = () => response.status(501).send('MIDDLEWARE_ERROR'); + + // Assume some problem occured, so we pass an error to next + throw new Error('EXPECTED_ERROR'); +}; + +// Create Backend HTTP Route +router.get(scenario_endpoint, middleware, async (request, response) => { + return response.send('Good'); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_middleware_uncaught_async_error() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + + // Perform fetch request + const response = await fetch(endpoint_url); + const body = await response.text(); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Middleware Thrown Iteration Error Handler`, + () => response.status === 501 && body === 'MIDDLEWARE_ERROR' + ); +} + +module.exports = { + test_middleware_uncaught_async_error, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_body_echo_test.js b/packages/hyper-express/tests/components/http/scenarios/request_body_echo_test.js new file mode 100644 index 0000000..66842f4 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_body_echo_test.js @@ -0,0 +1,59 @@ +const crypto = require('crypto'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/json-body-echo'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route +router.post( + scenario_endpoint, + async (req) => { + req.body = await req.json(); + return; + }, + (req, res, next) => { + res.locals.data = req.body; + next(); + }, + (_, res) => { + res.status(200).json(res.locals.data); + } +); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_request_body_echo_test(iterations = 5) { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request.json()'; + + for (let i = 0; i < iterations; i++) { + // Generate a small random payload + const payload = { + foo: crypto.randomBytes(5).toString('hex'), + }; + + // Make the fetch request + const response = await fetch(endpoint_url, { + method: 'POST', + body: JSON.stringify(payload), + }); + + // Retrieve the JSON response body + const body = await response.json(); + + // Assert that the payload and response body are the same + assert_log( + group, + `${candidate} JSON Small Body Echo Test #${i + 1}`, + () => JSON.stringify(payload) === JSON.stringify(body) + ); + } +} + +module.exports = { + test_request_body_echo_test, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_chunked_json.js b/packages/hyper-express/tests/components/http/scenarios/request_chunked_json.js new file mode 100644 index 0000000..fc48dbf --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_chunked_json.js @@ -0,0 +1,43 @@ +const path = require('path'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/chunked-json'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const test_file_path = path.resolve(path.join(__dirname, '../../../content/test-body.json')); + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + const body = await request.json(); + return response.json(body); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_request_chunked_json() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request.json()'; + + // Send a buffer of the file in the request body so we have a content-length on server side + const expected_json = JSON.stringify(JSON.parse(fs.readFileSync(test_file_path).toString('utf8'))); + const json_stream_response = await fetch(endpoint_url, { + method: 'POST', + headers: { + 'transfer-encoding': 'chunked', + 'x-file-name': 'request_upload_body.json', + }, + body: fs.createReadStream(test_file_path), + }); + + // Validate the hash uploaded on the server side with the expected hash from client side + const uploaded_json = await json_stream_response.text(); + assert_log(group, `${candidate} Chunked Transfer JSON Upload Test`, () => expected_json === uploaded_json); +} + +module.exports = { + test_request_chunked_json, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_chunked_stream.js b/packages/hyper-express/tests/components/http/scenarios/request_chunked_stream.js new file mode 100644 index 0000000..3424536 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_chunked_stream.js @@ -0,0 +1,72 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/chunked-stream'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const test_file_path = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); +const test_file_stats = fs.statSync(test_file_path); + +function get_file_write_path(file_name) { + return path.resolve(path.join(__dirname, '../../../content/written/' + file_name)); +} + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + // Create a writable stream to specified file name path + const file_name = request.headers['x-file-name']; + const path = get_file_write_path(file_name); + const writable = fs.createWriteStream(path); + + // Pipe the readable body stream to the writable and wait for it to finish + request.pipe(writable); + await new Promise((resolve) => writable.once('finish', resolve)); + + // Read the written file's buffer and calculate its md5 hash + const written_buffer = fs.readFileSync(path); + const written_hash = crypto.createHash('md5').update(written_buffer).digest('hex'); + + // Cleanup the written file for future testing + fs.rmSync(path); + + // Return the written hash to be validated on client side + return response.json({ + hash: written_hash, + }); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_request_chunked_stream() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request.stream'; + + // Send a buffer of the file in the request body so we have a content-length on server side + const expected_buffer = fs.readFileSync(test_file_path); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + const buffer_upload_response = await fetch(endpoint_url, { + method: 'POST', + headers: { + 'transfer-encoding': 'chunked', + 'x-file-name': 'request_upload_buffer.jpg', + }, + body: fs.createReadStream(test_file_path), + }); + + // Validate the hash uploaded on the server side with the expected hash from client side + const buffer_upload_body = await buffer_upload_response.json(); + assert_log( + group, + `${candidate} Chunked Transfer Piped Upload With Content Length - ${expected_hash} === ${buffer_upload_body.hash} - ${test_file_stats.size} bytes`, + () => expected_hash === buffer_upload_body.hash + ); +} + +module.exports = { + test_request_chunked_stream, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_multipart.js b/packages/hyper-express/tests/components/http/scenarios/request_multipart.js new file mode 100644 index 0000000..4d9f16b --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_multipart.js @@ -0,0 +1,214 @@ +const path = require('path'); +const FormData = require('form-data'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/multipart-form'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +function md5_from_stream(stream) { + return new Promise((resolve, reject) => { + const hash = crypto.createHash('md5'); + stream.on('data', (chunk) => hash.update(chunk)); + stream.on('end', () => resolve(hash.digest('hex'))); + }); +} + +function md5_from_buffer(buffer) { + return crypto.createHash('md5').update(buffer).digest('hex'); +} + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + const fields = []; + const ignore_fields = request.headers['ignore-fields'].split(','); + const use_async_handler = request.headers['x-use-async-handler'] === 'true'; + const async_handler = async (field) => { + // Throw a simulated error if the field name is in the ignore list + const simulated_error = request.headers['x-simulate-error'] === 'true'; + if (simulated_error) throw new Error('SIMULATED_ERROR'); + + // Do not process fields which should be ignored + if (ignore_fields.includes(field)) return; + + // Increment the cursor and store locally + const object = { + name: field.name, + value: field.value, + }; + + // Perform integrity verification if this field is a file + if (field.file) { + object.file_name = field.file.name; + object.hash = await md5_from_stream(field.file.stream); + } + + // Store the object into the server fields array for client side + fields.push(object); + }; + + let cursor = -1; + let in_flight = 0; + const sync_handler = (field) => { + // Increment and remember current iteration's cursor + cursor++; + const position = cursor; + const object = { + name: field.name, + value: field.value, + }; + + if (field.file) { + // Asynchronously calculate the md5 hash of the incoming file + in_flight++; + object.file_name = field.file.name; + md5_from_stream(field.file.stream).then((hash) => { + // Decrement the in flight counter and store hash into the server field object + in_flight--; + object.hash = hash; + fields[position] = object; + + // Send response if no more operations in flight + if (in_flight < 1) response.json(fields); + }); + } else { + // Store the server fields into fields object + // Send response if no operations are in flight + fields[position] = object; + } + }; + + // Handle the incoming fields as multipart with the appropriate handler type + try { + await request.multipart(use_async_handler ? async_handler : sync_handler); + } catch (error) { + // Pipe errors back to the client + return response.json({ error: error.message }); + } + + // Only respond here if we are using the async handler or we have no inflight operations + if (use_async_handler || in_flight < 0) return response.json(fields); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +function get_asset_buffer(file_name) { + return fs.readFileSync(path.resolve(path.join(__dirname, '../../../content/' + file_name))); +} + +async function test_request_multipart(use_async_handler = false) { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request.multipart()'; + + const ignore_fields = ['file3']; + const fields = [ + { + name: 'field1', + value: 'field1', + }, + { + name: 'file1', + value: get_asset_buffer('example.txt'), + file_name: 'example.txt', + }, + { + name: 'file2', + value: get_asset_buffer('large-image.jpg'), + }, + { + name: 'file3', + value: get_asset_buffer('test.html'), + file_name: 'something.html', + }, + { + name: 'field2', + value: Math.random().toString(), + }, + ].map((field) => { + if (field.value instanceof Buffer) field.hash = md5_from_buffer(field.value); + return field; + }); + + // Perform a multipart form request that uploads files and fields + const form = new FormData(); + fields.forEach(({ name, value, file_name }) => form.append(name, value, file_name)); + + // Perform multipart uploading with a synchronous handler + const response = await fetch(endpoint_url, { + method: 'POST', + body: form, + headers: { + 'ignore-fields': ignore_fields.join(','), + 'x-use-async-handler': use_async_handler.toString(), + }, + }); + const server_fields = await response.json(); + + // Assert comparison of each field in order to match with client-side from server-side + for (let i = 0; i < fields.length; i++) { + const client_field = fields[i]; + const server_field = server_fields[i]; + + // Only perform assertion if we are not ignoring this field + if (!ignore_fields.includes(client_field.name)) + assert_log( + group, + `${candidate} - Multipart Form Field/File Upload Test (${ + use_async_handler ? 'Asynchronous' : 'Synchronous' + } Handler) - ${client_field.name} - ${client_field.value.length} bytes`, + () => { + // Assert that the field names match + if (client_field.name !== server_field.name) return false; + + // Asser that the field values match if this is a non file type field + if (typeof client_field.value == 'string' && client_field.value !== server_field.value) + return false; + + // Assert that the file names match if it was supplied + if (client_field.file_name && client_field.file_name !== server_field.file_name) return false; + + // Assert the file hashes match if this is a file type field + if (client_field.value instanceof Buffer && client_field.hash !== server_field.hash) return false; + + return true; + } + ); + } + + // Perform simulated error test for only async handler + if (use_async_handler) { + // Create a new form with a random value + const test_form = new FormData(); + test_form.append('field1', 'field1'); + + // Perform multipart uploading with a simulated error + const response = await fetch(endpoint_url, { + method: 'POST', + body: test_form, + headers: { + 'ignore-fields': ignore_fields.join(','), + 'x-use-async-handler': use_async_handler.toString(), + 'x-simulate-error': 'true', + }, + }); + + // Assert that the error was thrown + const { error } = await response.json(); + assert_log( + group, + `${candidate} - Multipart Form Field/File Upload Test (${ + use_async_handler ? 'Asynchronous' : 'Synchronous' + } Handler) - Handler Simulated Error Test`, + () => error === 'SIMULATED_ERROR' + ); + } +} + +module.exports = { + test_request_multipart, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_router_paths_test.js b/packages/hyper-express/tests/components/http/scenarios/request_router_paths_test.js new file mode 100644 index 0000000..6ed7986 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_router_paths_test.js @@ -0,0 +1,44 @@ +const crypto = require('crypto'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/cached-paths'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route to echo the path of the request +router.get(scenario_endpoint, (req, res) => res.send(req.path)); +router.get(scenario_endpoint + '/:random', (req, res) => res.send(req.path)); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_request_router_paths_test() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request.path'; + + // Test the candidates to ensure that the path is being cached properly + const _candidates = []; + const candidates = [ + endpoint_url, + `${endpoint_url}/${crypto.randomUUID()}`, + `${endpoint_url}/${crypto.randomUUID()}`, + ]; + for (const candidate of candidates) { + const response = await fetch(candidate); + const _candidate = await response.text(); + _candidates.push(_candidate); + } + + // Assert that the candidates match + assert_log( + group, + `${candidate} Cached Router Paths Test`, + () => _candidates.join(',') === candidates.map((url) => url.replace(server.base, '')).join(',') + ); +} + +module.exports = { + test_request_router_paths_test, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_stream.js b/packages/hyper-express/tests/components/http/scenarios/request_stream.js new file mode 100644 index 0000000..f1609cf --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_stream.js @@ -0,0 +1,71 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/stream-pipe'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const test_file_path = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); +const test_file_stats = fs.statSync(test_file_path); + +function get_file_write_path(file_name) { + return path.resolve(path.join(__dirname, '../../../content/written/' + file_name)); +} + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + // Create a writable stream to specified file name path + const file_name = request.headers['x-file-name']; + const path = get_file_write_path(file_name); + const writable = fs.createWriteStream(path); + + // Pipe the readable body stream to the writable and wait for it to finish + request.pipe(writable); + await new Promise((resolve) => writable.once('finish', resolve)); + + // Read the written file's buffer and calculate its md5 hash + const written_buffer = fs.readFileSync(path); + const written_hash = crypto.createHash('md5').update(written_buffer).digest('hex'); + + // Cleanup the written file for future testing + fs.rmSync(path); + + // Return the written hash to be validated on client side + return response.json({ + hash: written_hash, + }); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_request_stream_pipe() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request.stream'; + + // Send a buffer of the file in the request body so we have a content-length on server side + const expected_buffer = fs.readFileSync(test_file_path); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + const buffer_upload_response = await fetch(endpoint_url, { + method: 'POST', + headers: { + 'x-file-name': 'request_upload_buffer.jpg', + }, + body: expected_buffer, + }); + + // Validate the hash uploaded on the server side with the expected hash from client side + const buffer_upload_body = await buffer_upload_response.json(); + assert_log( + group, + `${candidate} Piped Upload With Content Length - ${expected_hash} === ${buffer_upload_body.hash} - ${test_file_stats.size} bytes`, + () => expected_hash === buffer_upload_body.hash + ); +} + +module.exports = { + test_request_stream_pipe, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/request_uncaught_rejections.js b/packages/hyper-express/tests/components/http/scenarios/request_uncaught_rejections.js new file mode 100644 index 0000000..62d1213 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/request_uncaught_rejections.js @@ -0,0 +1,85 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/request'; +const scenario_endpoint = '/uncaught-rejection'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + // Retrieve the desired scenario from the request body + const { scenario } = await request.json(); + + // Bind an expected error handler + request.expected_error = (error) => + response.json({ + code: error.message, + }); + + // Trigger a specific error scenario + switch (scenario) { + case 1: + // Manually throw a shallow error + throw new Error('MANUAL_SHALLOW_ERROR'); + case 2: + // Manually throw a deep error + await new Promise((_, reject) => reject(new Error('MANUAL_DEEP_ERROR'))); + case 3: + // Manually thrown non-Error object + throw 'MANUAL_SHALLOW_NON_ERROR'; + case 4: + // Manually thrown non-Error object + await (async () => { + throw 'MANUAL_DEEP_NON_ERROR'; + })(); + default: + return response.json({ + code: 'SUCCESS', + }); + } +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_request_uncaught_rejections() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Request'; + const promises = [ + [1, 'MANUAL_SHALLOW_ERROR'], + [2, 'MANUAL_DEEP_ERROR'], + [3, 'ERR_CAUGHT_NON_ERROR_TYPE: MANUAL_SHALLOW_NON_ERROR'], + [4, 'ERR_CAUGHT_NON_ERROR_TYPE: MANUAL_DEEP_NON_ERROR'], + ].map( + ([scenario, expected_code]) => + new Promise(async (resolve) => { + // Make the fetch request + const response = await fetch(endpoint_url, { + method: 'POST', + body: JSON.stringify({ + scenario, + }), + }); + + // Retrieve the received code from the server + const { code } = await response.json(); + + // Validate the hash uploaded on the server side with the expected hash from client side + assert_log( + group, + `${candidate} Uncaught Rejections Test Scenario ${scenario} => ${code}`, + () => code === expected_code + ); + + // Release this promise + resolve(); + }) + ); + + await Promise.all(promises); +} + +module.exports = { + test_request_uncaught_rejections, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_chunked_write.js b/packages/hyper-express/tests/components/http/scenarios/response_chunked_write.js new file mode 100644 index 0000000..8c6cc87 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_chunked_write.js @@ -0,0 +1,77 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { Writable } = require('stream'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/write'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const test_file_path = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); +const test_file_stats = fs.statSync(test_file_path); + +function safe_write_chunk(response, chunk, callback) { + return response.write(chunk, 'utf8', callback); +} + +// Create Backend HTTP Route +router.get(scenario_endpoint, async (request, response) => { + // Set some headers to ensure we have proper headers being received + response.header('x-is-written', 'true'); + + // Create a readable stream for test file and stream it + const readable = fs.createReadStream(test_file_path); + + // Create a Writable which we will pipe the readable into + const writable = new Writable({ + write: (chunk, encoding, callback) => { + // Safe write a chunk until it has FULLY been served + safe_write_chunk(response, chunk, callback); + }, + }); + + // Bind event handlers for ending the request once Writable has ended or closed + writable.on('close', () => response.send()); + + // Pipe the readable into the writable we created + readable.pipe(writable); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_chunked_write() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.write()'; + + // Read test file's buffer into memory + const expected_buffer = fs.readFileSync(test_file_path); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + + // Perform chunked encoding based fetch request to download streamed buffer for test file from server + const chunked_response = await fetch(endpoint_url); + + // Ensure custom headers are received first + assert_log( + group, + `${candidate} Custom Chunked Transfer Write Headers Test`, + () => chunked_response.headers.get('x-is-written') === 'true' + ); + + // Download buffer from request to compare + let received_buffer = await chunked_response.buffer(); + let received_hash = crypto.createHash('md5').update(received_buffer).digest('hex'); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Custom Chunked Transfer Write Buffer/Hash Comparison Test - ${expected_hash} - ${test_file_stats.size} bytes`, + () => expected_buffer.equals(received_buffer) && expected_hash === received_hash + ); +} + +module.exports = { + test_response_chunked_write, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_custom_content_length.js b/packages/hyper-express/tests/components/http/scenarios/response_custom_content_length.js new file mode 100644 index 0000000..00bea22 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_custom_content_length.js @@ -0,0 +1,35 @@ +const crypto = require('crypto'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/custom-content-length'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Generate a random string payload +const payload = crypto.randomBytes(800).toString('hex'); + +// Create Backend HTTP Route +router.get(scenario_endpoint, (_, response) => { + response.header('content-length', payload.length.toString()).send(payload); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_custom_content_length() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.send()'; + + // Send a normal request to trigger the appropriate hooks + const response = await fetch(endpoint_url); + const received = await response.text(); + + // Assert that the received headers all match the expected headers + assert_log(group, `${candidate} Custom Content-Length With Body Test`, () => received === payload); +} + +module.exports = { + test_response_custom_content_length, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_custom_status.js b/packages/hyper-express/tests/components/http/scenarios/response_custom_status.js new file mode 100644 index 0000000..7010688 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_custom_status.js @@ -0,0 +1,55 @@ +const crypto = require('crypto'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/custom-status'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + const { status, message } = await request.json(); + response.statusCode = status; + response.statusMessage = message; + response.send(); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_custom_status() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.statusCode'; + + [ + { + status: 200, + message: 'Some Message', + }, + { + status: 609, + message: 'User Moved to Another Server', + }, + ].map(async ({ status, message }) => { + // Make a request to the server with a custom status code and message + const response = await fetch(endpoint_url, { + method: 'POST', + body: JSON.stringify({ + status, + message, + }), + }); + + // Validate the status code and message on the response + assert_log( + group, + `${candidate} Custom Status Code & Response Test - "HTTP ${status} ${message}"`, + () => response.status === status && response.statusText === message + ); + }); +} + +module.exports = { + test_response_custom_status, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_headers_behavior.js b/packages/hyper-express/tests/components/http/scenarios/response_headers_behavior.js new file mode 100644 index 0000000..5fdd57c --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_headers_behavior.js @@ -0,0 +1,122 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/headers-behavior'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +const RAW_HEADERS = [ + { + name: 'test', + value: 'first', // This will be overwritten by the second header + }, + { + name: 'test', + value: 'second', // This will be overwritten by the third header + }, + { + name: 'test', + value: 'third', // This will be the served header for the the "test" header + }, +]; + +const RAW_COOKIES = [ + { + name: 'test-cookie', + value: 'test-value', // This will be overwritten by the second cookie + }, + { + name: 'test-cookie', + value: 'test-value-2', // This will be served to the client + }, + { + name: 'test-cookie-3', + value: 'test-value-3', // This will be served to the client + }, +]; + +// Create Backend HTTP Route +router.get(scenario_endpoint, (request, response) => { + // Serve the headers + RAW_HEADERS.forEach((header) => response.header(header.name, header.value)); + + // Serve the cookies + RAW_COOKIES.forEach((cookie) => response.cookie(cookie.name, cookie.value, 1000 * 60 * 60 * 24 * 7)); + + // Send response + response.send(); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_headers_behavior() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.header()'; + + // Parse the last written header as the expected value + const EXPECTED_HEADERS = {}; + RAW_HEADERS.forEach((header) => { + if (EXPECTED_HEADERS[header.name]) { + if (Array.isArray(EXPECTED_HEADERS[header.name])) { + EXPECTED_HEADERS[header.name].push(header.value); + } else { + EXPECTED_HEADERS[header.name] = [EXPECTED_HEADERS[header.name], header.value]; + } + } else { + EXPECTED_HEADERS[header.name] = header.value; + } + }); + + // Join all multi headers with comma whitespaces + for (const name in EXPECTED_HEADERS) { + if (Array.isArray(EXPECTED_HEADERS[name])) { + EXPECTED_HEADERS[name] = EXPECTED_HEADERS[name].join(', '); + } + } + + // Parse the last written cookie as the expected value + const EXPECTED_COOKIES = {}; + RAW_COOKIES.forEach((cookie) => (EXPECTED_COOKIES[cookie.name] = cookie.value)); + + // Send a fetch request to retrieve headers + const response = await fetch(endpoint_url); + const received_headers = response.headers.raw(); + + // Assert that the headers were served correctly + assert_log(group, `${candidate} - Single/Multiple Header Values Behavior Test`, () => { + let valid = true; + Object.keys(EXPECTED_HEADERS).forEach((name) => { + let expected = EXPECTED_HEADERS[name]; + let received = received_headers[name]; + + // Assert that the received header is an array + valid = Array.isArray(expected) + ? JSON.stringify(expected) === JSON.stringify(received) + : expected === received[0]; + }); + return valid; + }); + + // Assert that the cookies were served correctly + assert_log(group, `${candidate} - Single/Multiple Cookie Values Behavior Test`, () => { + const received_cookies = {}; + received_headers['set-cookie'].forEach((cookie) => { + const [name, value] = cookie.split('; ')[0].split('='); + received_cookies[name] = value; + }); + + let valid = true; + Object.keys(EXPECTED_COOKIES).forEach((name) => { + const expected_value = EXPECTED_COOKIES[name]; + const received_value = received_cookies[name]; + valid = expected_value === received_value; + }); + return valid; + }); +} + +module.exports = { + test_response_headers_behavior, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_hooks.js b/packages/hyper-express/tests/components/http/scenarios/response_hooks.js new file mode 100644 index 0000000..821ef24 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_hooks.js @@ -0,0 +1,68 @@ +const { assert_log, async_wait } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server, AbortController } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/hooks'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const response_delay = 100; + +const hook_emissions = {}; +function increment_event(type) { + hook_emissions[type] = hook_emissions[type] ? hook_emissions[type] + 1 : 1; +} + +// Create Backend HTTP Route +router.get(scenario_endpoint, (request, response) => { + // Bind all of the hooks to the response + ['abort', 'prepare', 'finish', 'close'].forEach((type) => response.on(type, () => increment_event(type))); + + // Send response after some delay to allow for client to prematurely abort + setTimeout(() => (!response.completed ? response.send() : null), response_delay); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_events() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.on()'; + + // Send a normal request to trigger the appropriate hooks + await fetch(endpoint_url); + + // Assert that only the appropriate hooks were called + assert_log( + group, + `${candidate} - Normal Request Events Test`, + () => hook_emissions['prepare'] === 1 && hook_emissions['finish'] === 1 && hook_emissions['close'] === 1 + ); + + // Send and prematurely abort a request to trigger the appropriate hooks + const controller = new AbortController(); + setTimeout(() => controller.abort(), response_delay / 3); + try { + await fetch(endpoint_url, { + signal: controller.signal, + }); + } catch (error) { + // Supress the error as we expect an abort + // Wait a little bit for the hook emissions to be updated + await async_wait(response_delay / 3); + } + + // Assert that only the appropriate hooks were called + assert_log( + group, + `${candidate} - Premature Aborted Request Events Test`, + () => + hook_emissions['prepare'] === 1 && + hook_emissions['finish'] === 1 && + hook_emissions['close'] === 2 && + hook_emissions['abort'] === 1 + ); +} + +module.exports = { + test_response_events, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_piped.js b/packages/hyper-express/tests/components/http/scenarios/response_piped.js new file mode 100644 index 0000000..ab37dec --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_piped.js @@ -0,0 +1,61 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/pipe'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const test_file_path = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); +const test_file_stats = fs.statSync(test_file_path); + +// Create Backend HTTP Route +router.get(scenario_endpoint, async (request, response) => { + // Set some headers to ensure we have proper headers being received + response.header('x-is-written', 'true'); + + // Create a readable stream for test file and stream it + const readable = fs.createReadStream(test_file_path); + + // Pipe the readable stream into the response + readable.pipe(response); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_piped_write() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.write()'; + + // Read test file's buffer into memory + const expected_buffer = fs.readFileSync(test_file_path); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + + // Perform chunked encoding based fetch request to download streamed buffer for test file from server + const chunked_response = await fetch(endpoint_url); + + // Ensure custom headers are received first + assert_log( + group, + `${candidate} Piped Stream Write Headers Test`, + () => chunked_response.headers.get('x-is-written') === 'true' + ); + + // Download buffer from request to compare + let received_buffer = await chunked_response.buffer(); + let received_hash = crypto.createHash('md5').update(received_buffer).digest('hex'); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Piped Stream Write Buffer/Hash Comparison Test - ${expected_hash} - ${test_file_stats.size} bytes`, + () => expected_buffer.equals(received_buffer) && expected_hash === received_hash + ); +} + +module.exports = { + test_response_piped_write, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_send_no_body.js b/packages/hyper-express/tests/components/http/scenarios/response_send_no_body.js new file mode 100644 index 0000000..429fd31 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_send_no_body.js @@ -0,0 +1,49 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/send-no-body'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route +const response_headers = [ + ['Content-Type', 'application/json'], + ['Content-Length', Math.floor(Math.random() * 1e5).toString()], + ['Last-Modified', new Date().toUTCString()], + ['ETag', 'W/"' + Math.floor(Math.random() * 1e5).toString() + '"'], +]; + +router.head(scenario_endpoint, (_, response) => { + // Write the response headers + response_headers.forEach(([key, value]) => response.header(key, value)); + + // Should send without body under the hood with the custom content-length + return response.vary('Accept-Encoding').send(); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_send_no_body() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.send()'; + + // Send a normal request to trigger the appropriate hooks + const response = await fetch(endpoint_url, { + method: 'HEAD', + }); + + // Assert that the received headers all match the expected headers + assert_log(group, `${candidate} Custom Content-Length Without Body Test`, () => { + let verdict = true; + response_headers.forEach(([key, value]) => { + if (response.headers.get(key) !== value) verdict = false; + }); + return verdict; + }); +} + +module.exports = { + test_response_send_no_body, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_send_status.js b/packages/hyper-express/tests/components/http/scenarios/response_send_status.js new file mode 100644 index 0000000..68d2949 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_send_status.js @@ -0,0 +1,49 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/send-status'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +// Create Backend HTTP Route +router.post(scenario_endpoint, async (request, response) => { + const { status } = await request.json(); + response.sendStatus(status); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_send_status() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.statusCode'; + + [ + { + status: 200, + }, + { + status: 609, + }, + ].map(async ({ status }) => { + // Make a request to the server with a status code + const response = await fetch(endpoint_url, { + method: 'POST', + body: JSON.stringify({ + status, + }), + }); + + // Validate the status code on the response + assert_log( + group, + `${candidate} Custom Status Code & Response Test - "HTTP ${status}"`, + () => response.status === status + ); + }); +} + +module.exports = { + test_response_send_status, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_set_header.js b/packages/hyper-express/tests/components/http/scenarios/response_set_header.js new file mode 100644 index 0000000..1d7aef2 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_set_header.js @@ -0,0 +1,35 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../Server.js'); + +const endpoint = '/tests/response/set'; +const endpoint_url = server.base + endpoint; + +// Create Backend HTTP Route +TEST_SERVER.get(endpoint, async (request, response) => { + response.set({ 'test-header-1': 'test-value-1' }); + response.set('test-header-2', 'test-value-2'); + return response.end(); +}); + +async function test_response_set_header() { + let group = 'RESPONSE'; + let candidate = 'HyperExpress.Response.set()'; + + // Perform fetch request + const response = await fetch(endpoint_url); + const headers = response.headers.raw(); + + assert_log( + group, + candidate + ' Set Header Test', + () => { + return headers['test-header-1'] == 'test-value-1' + && headers['test-header-2'] == 'test-value-2'; + } + ); +} + +module.exports = { + test_response_set_header, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_sse.js b/packages/hyper-express/tests/components/http/scenarios/response_sse.js new file mode 100644 index 0000000..b783d39 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_sse.js @@ -0,0 +1,136 @@ +const { assert_log, async_wait } = require('../../../scripts/operators.js'); +const { HyperExpress, server, EventSource } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/sse'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +const test_data = [ + { + data: 'asdasdasd', + }, + { + data: 'xasdxasdxasd', + }, + { + event: 'x3123x123x', + data: 'xasdasdasdxasd', + }, + { + event: '3x123x123x', + data: '123123x123x12', + }, + { + id: '3x12x123x123x', + event: 'x3123x123', + data: 'x123x123x123x123', + }, + { + data: 'x3123x123x1231', + }, +]; + +// Create Backend HTTP Route to serve test data +let sse_closed = false; +router.get(scenario_endpoint, async (request, response) => { + // Ensure SSE is available for this request + if (response.sse) { + // Open the SSE connection to ensure the client is properly connected + response.sse.open(); + + // Serve the appropriate test data after a short delay + await async_wait(5); + test_data.forEach(({ id, event, data }) => { + // Send with the appropriate parameters based on the test data + let output; + if (id && event && data) { + output = response.sse.send(id, event, data); + } else if (event && data) { + output = response.sse.send(event, data); + } else { + output = response.sse.send(data); + } + + if (!output) console.log(`Failed to send SSE message: ${id}, ${event}, ${data}`); + }); + + // Listen for the client to close the connection + response.once('abort', () => (sse_closed = true)); + response.once('close', () => (sse_closed = true)); + } +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_sse() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.sse'; + + // Open a new SSE connection to the server + const sse = new EventSource(endpoint_url); + + // Record all of the incoming events to assert against test data + const recorded_data = []; + const recorded_ids = []; + const record_event = (event, customEvent) => { + // Determine various properties about this event + const is_custom_id = Number.isNaN(+event.lastEventId); + const is_recorded_id = recorded_ids.includes(event.lastEventId); + const data = event.data; + + // Build the event based on recorded properties + const payload = {}; + if (is_custom_id && !is_recorded_id) payload.id = event.lastEventId; + if (customEvent) payload.event = customEvent; + if (data) payload.data = data; + recorded_data.push(payload); + + // Remember the event ID for future reference as the last event ID does not reset + if (is_custom_id && !is_recorded_id) recorded_ids.push(event.lastEventId); + }; + + // Bind custom event handlers from test data array + test_data.forEach(({ event }) => (event ? sse.addEventListener(event, (ev) => record_event(ev, event)) : null)); + + // Bind a catch-all message handler + sse.onmessage = record_event; + + // Wait for the connection to initially open and disconnect + let interval; + await new Promise((resolve, reject) => { + sse.onerror = reject; + + // Wait for all test data to be received + interval = setInterval(() => { + if (recorded_data.length >= test_data.length) resolve(); + }, 100); + }); + clearInterval(interval); + + // Close the connection + sse.close(); + + // Let the server propogate the boolean value + await async_wait(5); + + // Assert that all test data was received successfully + assert_log( + group, + `${candidate} - Server-Sent Events Communiciation Test`, + () => + sse_closed && + test_data.find( + (test) => + recorded_data.find( + (recorded) => + test.id === recorded.id && test.event === recorded.event && test.data === recorded.data + ) === undefined + ) === undefined + ); +} + +module.exports = { + test_response_sse, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_stream.js b/packages/hyper-express/tests/components/http/scenarios/response_stream.js new file mode 100644 index 0000000..824cb75 --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_stream.js @@ -0,0 +1,101 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/stream'; +const endpoint_url = server.base + endpoint + scenario_endpoint; +const test_file_path = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); +const test_file_stats = fs.statSync(test_file_path); + +// Create Backend HTTP Route +router.get(scenario_endpoint, async (request, response) => { + // Set some headers to ensure we have proper headers being received + response.header('x-is-streamed', 'true'); + + // Create a readable stream for test file and stream it + const readable = fs.createReadStream(test_file_path); + + // Deliver with chunked encoding if specified by header or fall back to normal handled delivery + const use_chunked_encoding = request.headers['x-chunked-encoding'] === 'true'; + response.stream(readable, use_chunked_encoding ? undefined : test_file_stats.size); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_stream_method() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.stream()'; + + // Read test file's buffer into memory + const expected_buffer = fs.readFileSync(test_file_path); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + + // Perform chunked encoding based fetch request to download streamed buffer for test file from server + const chunked_response = await fetch(endpoint_url, { + headers: { + 'x-chunked-encoding': 'true', + }, + }); + + // Ensure custom headers are received first + assert_log( + group, + `${candidate} Chunked Transfer Streamed Headers Test`, + () => chunked_response.headers.get('x-is-streamed') === 'true' + ); + + // Download buffer from request to compare + let received_buffer = await chunked_response.buffer(); + let received_hash = crypto.createHash('md5').update(received_buffer).digest('hex'); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Chunked Transfer Streamed Buffer/Hash Comparison Test - ${expected_hash} - ${test_file_stats.size} bytes`, + () => { + const matches = expected_buffer.equals(received_buffer) && expected_hash === received_hash; + if (!matches) { + console.log({ + expected_buffer, + received_buffer, + expected_hash, + received_hash, + }); + } + + return matches; + } + ); + + // Perform handled response based fetch request to download streamed buffer for test file from server + const handled_response = await fetch(endpoint_url); + + // Ensure custom headers are received and a valid content-length is also received + assert_log( + group, + `${candidate} Handled Response Streamed Headers & Content-Length Test`, + () => + handled_response.headers.get('x-is-streamed') === 'true' && + +handled_response.headers.get('content-length') === expected_buffer.byteLength + ); + + // Download buffer from request to compare + received_buffer = await handled_response.buffer(); + received_hash = crypto.createHash('md5').update(received_buffer).digest('hex'); + + // Test to see error handler was properly called on expected middleware error + assert_log( + group, + `${candidate} Handled Response Streamed Buffer/Hash Comparison Test - ${expected_hash} - ${test_file_stats.size} bytes`, + () => expected_buffer.equals(received_buffer) && expected_hash === received_hash + ); +} + +module.exports = { + test_response_stream_method, +}; diff --git a/packages/hyper-express/tests/components/http/scenarios/response_stream_sync_writes.js b/packages/hyper-express/tests/components/http/scenarios/response_stream_sync_writes.js new file mode 100644 index 0000000..92daf4e --- /dev/null +++ b/packages/hyper-express/tests/components/http/scenarios/response_stream_sync_writes.js @@ -0,0 +1,42 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const router = new HyperExpress.Router(); +const endpoint = '/tests/response'; +const scenario_endpoint = '/sync-writes'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +const expected_parts = ['1', '2', '3', 'done']; + +// Create Backend HTTP Route +router.get(scenario_endpoint, (request, response) => { + // Write the first 3 parts with response.write() + response.write(expected_parts[0]); + response.write(expected_parts[1]); + response.write(expected_parts[2]); + + // Send the last part with response.send() + response.send(expected_parts[3]); +}); + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_response_sync_writes() { + const group = 'RESPONSE'; + const candidate = 'HyperExpress.Response.write()'; + + // Make a fetch request to the endpoint + const response = await fetch(endpoint_url); + + // Get the received body from the response + const expected_body = expected_parts.join(''); + const received_body = await response.text(); + + // Ensure that the received body is the same as the expected body + assert_log(group, `${candidate} Sync Writes Test`, () => expected_body === received_body); +} + +module.exports = { + test_response_sync_writes, +}; diff --git a/packages/hyper-express/tests/components/router/Router.js b/packages/hyper-express/tests/components/router/Router.js new file mode 100644 index 0000000..3197e1b --- /dev/null +++ b/packages/hyper-express/tests/components/router/Router.js @@ -0,0 +1,115 @@ +const { log, assert_log } = require('../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../configuration.js'); +const { test_router_chainable_route } = require('./scenarios/chainable_routes.js'); +const { TEST_SERVER } = require('../Server.js'); +const endpoint_base = '/tests/router/echo-'; + +// Inject middleweare signature values into the requests through global, local and route specific middlewares +const middleware_signature = [Math.random(), Math.random(), Math.random()]; +TEST_SERVER.use((request, response, next) => { + // Initialize with first signature value + request.middleware_signature = [middleware_signature[0]]; + next(); +}); + +// Define all HTTP test method definitions +const route_definitions = { + get: { + method: 'GET', + call: 'get', + }, + post: { + method: 'POST', + call: 'post', + }, + del: { + method: 'DELETE', + call: 'delete', + }, + options: { + method: 'OPTIONS', + call: 'options', + }, + patch: { + method: 'PATCH', + call: 'patch', + }, + put: { + method: 'PUT', + call: 'put', + }, + trace: { + method: 'TRACE', + call: 'trace', + }, +}; + +// Create dynamic routes for testing across all methods +const router = new HyperExpress.Router(); +Object.keys(route_definitions).forEach((type) => { + const { method, call } = route_definitions[type]; + router[call]( + endpoint_base + type, + async (request, response) => { + // Push the third signature value to the request + request.middleware_signature.push(middleware_signature[2]); + }, + (request, response) => { + // Echo the methods, call and signature values to the client + response.json({ + method: request.method, + signature: request.middleware_signature, + }); + } + ); +}); +TEST_SERVER.use(router); + +// Bind a second global middleware +TEST_SERVER.use((request, response, next) => { + // Push the second signature value to the request + request.middleware_signature.push(middleware_signature[1]); + next(); +}); + +async function test_router_object() { + // Prepare Test Candidates + log('ROUTER', 'Testing HyperExpress.Router Object...'); + const group = 'ROUTER'; + const candidate = 'HyperExpress.Router'; + const start_time = Date.now(); + + // Test all route definitions to ensure consistency + await Promise.all( + Object.keys(route_definitions).map(async (type) => { + // Retrieve the expected method and call values + const { method, call } = route_definitions[type]; + + // Make the fetch request + const response = await fetch(server.base + endpoint_base + type, { + method, + }); + + // Retrieve the response body + const body = await response.json(); + + // Assert the response body + assert_log(group, `${candidate}.${call}() - HTTP ${method} Test`, () => { + const call_check = typeof router[call] == 'function'; + const method_check = method === body.method; + const signature_check = JSON.stringify(body.signature) === JSON.stringify(middleware_signature); + const route_check = TEST_SERVER.routes[type][endpoint_base + type] !== undefined; + return call_check && method_check && signature_check && route_check; + }); + }) + ); + + // Test the chainable route scenario + await test_router_chainable_route(); + + log(group, `Finished Testing ${candidate} In ${Date.now() - start_time}ms\n`); +} + +module.exports = { + test_router_object, +}; diff --git a/packages/hyper-express/tests/components/router/scenarios/chainable_routes.js b/packages/hyper-express/tests/components/router/scenarios/chainable_routes.js new file mode 100644 index 0000000..76d76eb --- /dev/null +++ b/packages/hyper-express/tests/components/router/scenarios/chainable_routes.js @@ -0,0 +1,59 @@ +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, fetch, server } = require('../../../configuration.js'); +const endpoint = '/tests/router'; +const scenario_endpoint = '/chainable-routes'; +const endpoint_url = server.base + endpoint + scenario_endpoint; + +const routes = [ + { + method: 'GET', + payload: Math.random().toString(), + }, + { + method: 'POST', + payload: Math.random().toString(), + }, + { + method: 'PUT', + payload: Math.random().toString(), + }, + { + method: 'DELETE', + payload: Math.random().toString(), + }, +]; + +const router = new HyperExpress.Router(); + +let chainable = router.route(scenario_endpoint); +for (const route of routes) { + // This will test the chainability of the router + // Simulates Router.route().get().post().put().delete() + chainable = chainable[route.method.toLowerCase()]((_, response) => { + response.send(route.payload); + }); +} + +// Bind router to webserver +const { TEST_SERVER } = require('../../Server.js'); +TEST_SERVER.use(endpoint, router); + +async function test_router_chainable_route() { + const group = 'REQUEST'; + const candidate = 'HyperExpress.Router.route()'; + + // Perform fetch requests for each method + for (const route of routes) { + const response = await fetch(endpoint_url, { + method: route.method, + }); + + // Assert that the payload matches payload sent + const _payload = await response.text(); + assert_log(group, `${candidate} Chained HTTP ${route.method} Route`, () => _payload === route.payload); + } +} + +module.exports = { + test_router_chainable_route, +}; diff --git a/packages/hyper-express/tests/components/ws/Websocket.js b/packages/hyper-express/tests/components/ws/Websocket.js new file mode 100644 index 0000000..078c853 --- /dev/null +++ b/packages/hyper-express/tests/components/ws/Websocket.js @@ -0,0 +1,134 @@ +const { log, random_string, assert_log } = require('../../scripts/operators.js'); +const { HyperExpress, Websocket, server } = require('../../configuration.js'); +const { test_websocket_stream } = require('./scenarios/stream.js'); +const { test_websocket_writable } = require('./scenarios/writable.js'); + +const Router = new HyperExpress.Router(); +const TestPath = '/websocket-component'; +const TestCode = 1000; +const TestKey = random_string(30); + +// Create websocket route for handling protected upgrade +let remote_ws; +let remote_closed = false; +Router.ws('/echo', (ws) => { + // Store websocket object for checking throught tests + remote_ws = ws; + + // Bind message handler + ws.on('message', (message) => { + // Echo messages until we receive 'CLOSE' message + if (message === 'CLOSE') { + ws.close(TestCode); + } else { + ws.send(message); + } + }); + + // This will test that close event fires properly + ws.on('close', () => (remote_closed = true)); +}); + +// Create upgrade route for testing user assigned upgrade handler +Router.upgrade('/echo', (request, response) => { + // Reject upgrade request if valid key is not provided + const key = request.query_parameters['key']; + const delay = +request.query_parameters['delay'] || 0; + if (key !== TestKey) return response.status(403).send(); + + // Upgrade request with delay to simulate async upgrade handler or not + if (delay) { + setTimeout( + () => + response.upgrade({ + key, + }), + delay + ); + } else { + response.upgrade({ + key, + }); + } +}); + +// Bind router to test server instance +const { TEST_SERVER } = require('../../components/Server.js'); +TEST_SERVER.use(TestPath, Router); + +async function test_websocket_component() { + const group = 'WEBSOCKET'; + const candidate = 'HyperExpress.Websocket'; + const endpoint_base = `${server.base.replace('http', 'ws')}${TestPath}`; + log(group, 'Testing ' + candidate); + + // Test with No delay and random delay between 30-60ms for upgrade handler + await Promise.all( + [0, Math.floor(Math.random() * 30) + 30].map((delay) => { + // Test protected websocket route upgrade handling (NO KEY) + let count = 5; + const delay_message = `With ${delay}ms Delay`; + const ws_echo = new Websocket(`${endpoint_base}/echo?key=${TestKey}&delay=${delay}`); + return new Promise((resolve, reject) => { + let expecting; + ws_echo.on('open', () => { + // Assert that remote upgrade context was accessible from polyfill component + assert_log( + group, + `${candidate} ${delay_message} Upgrade Context Integrity`, + () => remote_ws.context.key === TestKey + ); + + // Start of echo chain with an expected random string + expecting = random_string(10); + ws_echo.send(expecting); + }); + + ws_echo.on('message', (message) => { + // Perform assertion to compare expected value with received value + message = message.toString(); + assert_log( + group, + `${candidate} ${delay_message} Echo Test > [${expecting} === ${message}]`, + () => expecting === message + ); + + // Perform echo tests until count is 0 + count--; + if (count > 0) { + expecting = random_string(10); + ws_echo.send(expecting); + } else { + // Tell remote to close connection + ws_echo.send('CLOSE'); + } + }); + + // Create a reject timeout to throw on hangups + let timeout = setTimeout(reject, 1000); + ws_echo.on('close', (code) => { + // Assert that close code matches the test code + assert_log(group, `${candidate} ${delay_message} Connection Close Code`, () => code === TestCode); + + clearTimeout(timeout); + resolve(); + }); + }); + }) + ); + + // Assert that remote server also closed connection/update polyfill state appropriately + assert_log(group, `${candidate} Remote Polyfill Close`, () => remote_ws.closed === true && remote_closed === true); + + // Test websocket .stream() method + await test_websocket_stream(); + + // Test websocket .writable property + await test_websocket_writable(); + + log(group, `Finished Testing ${candidate}\n`); +} + +module.exports = { + test_websocket_component, +}; diff --git a/packages/hyper-express/tests/components/ws/WebsocketRoute.js b/packages/hyper-express/tests/components/ws/WebsocketRoute.js new file mode 100644 index 0000000..3372944 --- /dev/null +++ b/packages/hyper-express/tests/components/ws/WebsocketRoute.js @@ -0,0 +1,122 @@ +const { log, random_string, assert_log } = require('../../scripts/operators.js'); +const { HyperExpress, Websocket, server } = require('../../configuration.js'); + +const Router = new HyperExpress.Router(); +const TestPath = '/websocket-route'; +const TestPayload = random_string(30); +const TestKey = random_string(30); +const TestOptions = { + idle_timeout: 500, + message_type: 'String', + compression: HyperExpress.compressors.DISABLED, + max_backpressure: 512 * 1024, + max_payload_length: 16 * 1024, +}; + +// Create websocket route for testing default upgrade handler +Router.ws('/unprotected', TestOptions, (ws) => { + // Send test payload and close if successful + if (ws.send(TestPayload)) ws.close(); +}); + +// Create upgrade route for testing user assigned upgrade handler +Router.upgrade('/protected', (request, response) => { + // Reject upgrade request if valid key is not provided + const key = request.query_parameters['key']; + if (key !== TestKey) return response.status(403).send(); + + // Upgrade request normally + response.upgrade({ + key, + }); +}); + +// Create websocket route for handling protected upgrade +Router.ws('/protected', (ws) => { + // Send test payload and close if successful + if (ws.send(TestPayload)) ws.close(); +}); + +// Bind router to test server instance +const { TEST_SERVER } = require('../../components/Server.js'); +TEST_SERVER.use(TestPath, Router); + +async function test_websocket_route() { + const group = 'WEBSOCKET'; + const candidate = 'HyperExpress.WebsocketRoute'; + const endpoint_base = `${server.base.replace('http', 'ws')}${TestPath}`; + log(group, 'Testing ' + candidate); + + // Test unprotected websocket route upgrade handling + const ws_unprotected = new Websocket(`${endpoint_base}/unprotected`); + await new Promise((resolve, reject) => { + // Store last message to test payload integrity + let last_message; + ws_unprotected.on('message', (message) => { + last_message = message.toString(); + }); + + // Create a reject timeout to throw on hangups + let timeout = setTimeout(reject, 1000); + ws_unprotected.on('close', () => { + // Perform assertion to test for valid last message + assert_log(group, `${candidate} Default/Unprotected Upgrade Handler`, () => last_message === TestPayload); + + // Cancel reject timeout and move on after assertion succeeds + clearTimeout(timeout); + resolve(); + }); + }); + + // Test protected websocket route upgrade handling (NO KEY) + const ws_protected_nokey = new Websocket(`${endpoint_base}/protected`); + await new Promise((resolve, reject) => { + // Store last error so we can compare the expected error type + let last_error; + ws_protected_nokey.on('error', (error) => { + last_error = error; + }); + + // Create a reject timeout to throw on hangups + let timeout = setTimeout(reject, 1000); + ws_protected_nokey.on('close', () => { + // Perform assertion to test for valid last message + assert_log( + group, + `${candidate} Protected Upgrade Handler Rejection With No Key`, + () => last_error && last_error.message.indexOf('403') > -1 + ); + + // Cancel reject timeout and move on after assertion succeeds + clearTimeout(timeout); + resolve(); + }); + }); + + // Test protected websocket route upgrade handling (WITH KEY) + const ws_protected_key = new Websocket(`${endpoint_base}/protected?key=${TestKey}`); + await new Promise((resolve, reject) => { + // Store last message to test payload integrity + let last_message; + ws_protected_key.on('message', (message) => { + last_message = message.toString(); + }); + + // Create a reject timeout to throw on hangups + let timeout = setTimeout(reject, 1000); + ws_protected_key.on('close', () => { + // Perform assertion to test for valid last message + assert_log(group, `${candidate} Protected Upgrade Handler With Key`, () => last_message === TestPayload); + + // Cancel reject timeout and move on after assertion succeeds + clearTimeout(timeout); + resolve(); + }); + }); + + log(group, `Finished Testing ${candidate}\n`); +} + +module.exports = { + test_websocket_route, +}; diff --git a/packages/hyper-express/tests/components/ws/scenarios/stream.js b/packages/hyper-express/tests/components/ws/scenarios/stream.js new file mode 100644 index 0000000..3dcd786 --- /dev/null +++ b/packages/hyper-express/tests/components/ws/scenarios/stream.js @@ -0,0 +1,64 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, Websocket, server } = require('../../../configuration.js'); + +const Router = new HyperExpress.Router(); +const TestPath = '/websocket-component'; +const TestFilePath = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); + +// Create an endpoint for serving a file +Router.ws('/stream', async (ws) => { + // Create a readable stream to serve to the receiver + const readable = fs.createReadStream(TestFilePath); + + // Stream the readable stream to the receiver + await ws.stream(readable); + + // Close the connection once we are done streaming + ws.close(); +}); + +// Bind router to test server instance +const { TEST_SERVER } = require('../../../components/Server.js'); +TEST_SERVER.use(TestPath, Router); + +async function test_websocket_stream() { + const group = 'WEBSOCKET'; + const candidate = 'HyperExpress.Websocket.stream()'; + const endpoint_base = `${server.base.replace('http', 'ws')}${TestPath}`; + + // Read test file's buffer into memory + const expected_buffer = fs.readFileSync(TestFilePath); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + + // Test protected websocket route upgrade handling (NO KEY) + const ws_stream = new Websocket(`${endpoint_base}/stream`); + await new Promise((resolve, reject) => { + let received_buffer; + let received_hash; + + // Assign a message handler to receive from websocket + ws_stream.on('message', (message) => { + // Store the received buffer and its hash + received_buffer = message; + received_hash = crypto.createHash('md5').update(received_buffer).digest('hex'); + }); + + // Assign a close handler to handle assertion + ws_stream.on('close', () => { + // Perform assertion to compare buffers and hashes + assert_log( + group, + `${candidate} - Streamed Binary Buffer Integrity - [${expected_hash}] == [${received_hash}]`, + () => expected_buffer.equals(received_buffer) && expected_hash === received_hash + ); + resolve(); + }); + }); +} + +module.exports = { + test_websocket_stream, +}; diff --git a/packages/hyper-express/tests/components/ws/scenarios/writable.js b/packages/hyper-express/tests/components/ws/scenarios/writable.js new file mode 100644 index 0000000..e8726cc --- /dev/null +++ b/packages/hyper-express/tests/components/ws/scenarios/writable.js @@ -0,0 +1,69 @@ +const path = require('path'); +const crypto = require('crypto'); +const fs = require('fs'); +const { assert_log } = require('../../../scripts/operators.js'); +const { HyperExpress, Websocket, server } = require('../../../configuration.js'); + +const Router = new HyperExpress.Router(); +const TestPath = '/websocket-component'; +const TestFilePath = path.resolve(path.join(__dirname, '../../../content/large-image.jpg')); + +// Create an endpoint for serving a file +Router.ws('/writable', async (ws) => { + // Create a readable stream to serve to the receiver + let readable = fs.createReadStream(TestFilePath); + + // Pipe the readable into the websocket writable + readable.pipe(ws.writable); + + // Bind a handler for once readable is finished + readable.once('close', () => { + // Repeat the same process as above to test multiple pipes to the same websocket connection + readable = fs.createReadStream(TestFilePath); + readable.pipe(ws.writable); + + // Bind the end handler again to close the connection this time + readable.once('close', () => ws.close()); + }); +}); + +// Bind router to test server instance +const { TEST_SERVER } = require('../../../components/Server.js'); +TEST_SERVER.use(TestPath, Router); + +async function test_websocket_writable() { + const group = 'WEBSOCKET'; + const candidate = 'HyperExpress.Websocket.writable'; + const endpoint_base = `${server.base.replace('http', 'ws')}${TestPath}`; + + // Read test file's buffer into memory + const expected_buffer = fs.readFileSync(TestFilePath); + const expected_hash = crypto.createHash('md5').update(expected_buffer).digest('hex'); + + // Test protected websocket route upgrade handling (NO KEY) + const ws_writable = new Websocket(`${endpoint_base}/writable`); + await new Promise((resolve, reject) => { + // Assign a message handler to receive from websocket + let counter = 1; + ws_writable.on('message', (message) => { + // Derive the retrieved buffer and its hash + const received_buffer = message; + const received_hash = crypto.createHash('md5').update(received_buffer).digest('hex'); + + // Assert the received data against the expected data + assert_log( + group, + `${candidate} - Piped Binary Buffer Integrity #${counter} - [${expected_hash}] == [${received_hash}]`, + () => expected_buffer.equals(received_buffer) && expected_hash === received_hash + ); + counter++; + }); + + // Assign a close handler to handle assertion + ws_writable.on('close', () => resolve()); + }); +} + +module.exports = { + test_websocket_writable, +}; diff --git a/packages/hyper-express/tests/configuration.js b/packages/hyper-express/tests/configuration.js new file mode 100644 index 0000000..0468580 --- /dev/null +++ b/packages/hyper-express/tests/configuration.js @@ -0,0 +1,26 @@ +const http = require('http'); +const fetch = require('node-fetch'); +const Websocket = require('ws'); +const EventSource = require('eventsource'); +const HyperExpress = require('../index.js'); +const AbortController = require('abort-controller'); + +const patchedFetch = (url, options = {}) => { + // Use a different http agent for each request to prevent connection pooling + options.agent = new http.Agent({ keepAlive: false }); + return fetch(url, options); +}; + +module.exports = { + fetch: patchedFetch, + Websocket, + EventSource, + HyperExpress, + AbortController, + server: { + host: '127.0.0.1', + port: '8080', // Ports should always be numbers but we are maintaining compatibility with strings + secure_port: 8443, + base: 'http://127.0.0.1:8080', + }, +}; diff --git a/packages/hyper-express/tests/content/example.txt b/packages/hyper-express/tests/content/example.txt new file mode 100644 index 0000000..81290d6 --- /dev/null +++ b/packages/hyper-express/tests/content/example.txt @@ -0,0 +1 @@ +This is some example text \ No newline at end of file diff --git a/packages/hyper-express/tests/content/large-image.jpg b/packages/hyper-express/tests/content/large-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..86b2b4e2463ebd1091b3aa98c578a358f0d18ad9 GIT binary patch literal 879067 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�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�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%0A@Aob+!8+S*FYX0HI@V^arwPzNwGV0L)r}{*ShB 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%0DxGH_ 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

b7UgZO(J#6VX&J! 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 DZ~BA*tGC>{l|T(It7;I+ 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 AYybcN literal 0 HcmV?d00001 diff --git a/packages/hyper-express/tests/content/test-body.json b/packages/hyper-express/tests/content/test-body.json new file mode 100644 index 0000000..012c244 --- /dev/null +++ b/packages/hyper-express/tests/content/test-body.json @@ -0,0 +1,6 @@ +{ + "field1": "value1", + "field2": "value2", + "field3": "value3", + "field4": "12381923819283192831923" +} diff --git a/packages/hyper-express/tests/content/test.html b/packages/hyper-express/tests/content/test.html new file mode 100644 index 0000000..b0cc931 --- /dev/null +++ b/packages/hyper-express/tests/content/test.html @@ -0,0 +1,8 @@ + + + Test HTML + + + Test HTML + + diff --git a/packages/hyper-express/tests/content/written/.required b/packages/hyper-express/tests/content/written/.required new file mode 100644 index 0000000..e69de29 diff --git a/packages/hyper-express/tests/index.js b/packages/hyper-express/tests/index.js new file mode 100644 index 0000000..f9a24e8 --- /dev/null +++ b/packages/hyper-express/tests/index.js @@ -0,0 +1,93 @@ +const HyperExpress = require('../index.js'); +const { log, assert_log } = require('./scripts/operators.js'); +const { test_hostmanager_object } = require('./components/features/HostManager.js'); +const { test_router_object } = require('./components/router/Router.js'); +const { test_request_object } = require('./components/http/Request.js'); +const { test_response_object } = require('./components/http/Response.js'); +const { test_websocket_route } = require('./components/ws/WebsocketRoute.js'); +const { test_session_middleware } = require('./middlewares/hyper-express-session/index.js'); +const { test_websocket_component } = require('./components/ws/Websocket.js'); +// const { test_body_parser_middleware } = require('./middlewares/hyper-express-body-parser/index.js'); + +const { server } = require('./configuration.js'); +const { TEST_SERVER, not_found_handler, test_server_shutdown } = require('./components/Server.js'); +(async () => { + try { + // While this is effectively doing the same thing as the not_found_handler, we do not want HyperExpress to also bind its own not found handler which would throw a duplicate route error + TEST_SERVER.all('*', not_found_handler); + + // Initiate Test API Webserver + const group = 'Server'; + const start_time = Date.now(); + await TEST_SERVER.listen(server.port, server.host); + log('TESTING', `Successfully Started HyperExpress HTTP Server @ ${server.host}:${server.port}`); + + // Assert that the server port matches the configuration port + assert_log(group, 'Server Listening Port Test', () => +server.port === TEST_SERVER.port); + + // Assert that a server instance with a bad SSL configuration throws an error + await assert_log(group, 'Good SSL Configuration Initialization Test', async () => { + let result = false; + try { + const TEST_GOOD_SERVER = new HyperExpress.Server({ + key_file_name: './tests/ssl/dummy-key.pem', + cert_file_name: './tests/ssl/dummy-cert.pem', + }); + + // Also tests the callback functionality of the listen method + await new Promise((resolve) => { + TEST_GOOD_SERVER.listen(server.secure_port, server.host, resolve); + }); + TEST_GOOD_SERVER.close(); + result = true; + } catch (error) { + console.error(error); + } + return result; + }); + + // Assert that a server instance with a bad SSL configuration throws an error + assert_log(group, 'Bad SSL Configuration Error Test', () => { + let result = true; + try { + const TEST_BAD_SERVER = new HyperExpress.Server({ + key_file_name: './error.key', + cert_file_name: './error.cert', + }); + result = false; + } catch (error) { + return true; + } + return result; + }); + + // Test Server.HostManager Object + test_hostmanager_object(); + + // Test Router Object + await test_router_object(); + + // Test Request Object + await test_request_object(); + + // Test Response Object + await test_response_object(); + + // Test WebsocketRoute Object + await test_websocket_route(); + + // Test Websocket Polyfill Object + await test_websocket_component(); + + // Test SessionEngine Middleware + await test_session_middleware(); + + // Test the server shutdown process + await test_server_shutdown(); + + log('TESTING', `Successfully Tested All Specified Tests For HyperExpress In ${Date.now() - start_time}ms!`); + process.exit(); + } catch (error) { + console.log(error); + } +})(); diff --git a/packages/hyper-express/tests/local.js b/packages/hyper-express/tests/local.js new file mode 100644 index 0000000..5c0838b --- /dev/null +++ b/packages/hyper-express/tests/local.js @@ -0,0 +1,78 @@ +const fs = require('fs'); +const crypto = require('crypto'); +const { log } = require('./scripts/operators.js'); +const { server, fetch } = require('./configuration.js'); +const { TEST_SERVER } = require('./components/Server.js'); + +(async () => { + try { + // Define information about the test file + const test_file_path = './content/large-files/song.mp3'; + const test_file_checksum = crypto.createHash('md5').update(fs.readFileSync(test_file_path)).digest('hex'); + const test_file_stats = fs.statSync(test_file_path); + + // Create a simple GET route to stream a large file with chunked encoding + TEST_SERVER.get('/stream', async (request, response) => { + // Write appropriate headers + response.header('md5-checksum', test_file_checksum).type('mp3'); + + // Stream the file to the client + const readable = fs.createReadStream(test_file_path); + + // Stream the file to the client with a random streaming method + const random = Math.floor(Math.random() * 3); + switch (random) { + case 0: + // Use Chunked Transfer + readable.once('close', () => response.send()); + readable.pipe(response); + break; + case 1: + // Use Chunked-Transfer With Built In Streaming + response.stream(readable); + break; + case 2: + // Use Streaming With Content-Length + response.stream(readable, test_file_stats.size); + break; + } + }); + + // Initiate Test API Webserver + await TEST_SERVER.listen(server.port, server.host); + log( + 'TESTING', + `Successfully Started HyperExpress HTTP Server For Local Testing @ ${server.host}:${server.port}` + ); + + // Perform a stress test of the endpoint + let completed = 0; + let start_ts = Date.now(); + const test_endpoint = async () => { + // Make a request to the endpoint + const response = await fetch(`${server.base}/stream`); + + // Retrieve both the expected and received checksums + const expected_checksum = response.headers.get('md5-checksum'); + const received_checksum = crypto + .createHash('md5') + .update(await response.buffer()) + .digest('hex'); + + // Assert that the checksums match + if (expected_checksum !== received_checksum) + throw new Error( + `Checksums Do Not Match! Expected: ${expected_checksum} Received: ${received_checksum}` + ); + completed++; + }; + + setInterval(test_endpoint, 0); + setInterval( + () => console.log(`Requests/Second: ${(completed / ((Date.now() - start_ts) / 1000)).toFixed(2)}`), + 1000 + ); + } catch (error) { + console.log(error); + } +})(); diff --git a/packages/hyper-express/tests/middlewares/hyper-express-body-parser/configuration.json b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/configuration.json new file mode 100644 index 0000000..9222950 --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/configuration.json @@ -0,0 +1,3 @@ +{ + "path": "/middlewares/hyper-express-body-parser" +} diff --git a/packages/hyper-express/tests/middlewares/hyper-express-body-parser/index.js b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/index.js new file mode 100644 index 0000000..89e626c --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/index.js @@ -0,0 +1,22 @@ +const { test_parser_limit } = require('./scenarios/parser_limit.js'); +const { test_parser_validation } = require('./scenarios/parser_validation.js'); +const { test_parser_compression } = require('./scenarios/parser_compression.js'); +const { test_parser_types } = require('./scenarios/parser_types.js'); + +async function test_body_parser_middleware() { + // Test the BodyParser.options.limit property for limiting the size of the body + await test_parser_limit(); + + // Test the BodyParser.options.type and BodyParser.options.verify options functionaltiy + await test_parser_validation(); + + // Test the BodyParser compression functionality + await test_parser_compression(); + + // Test the BodyParser body types functionality + await test_parser_types(); +} + +module.exports = { + test_body_parser_middleware, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_compression.js b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_compression.js new file mode 100644 index 0000000..010076a --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_compression.js @@ -0,0 +1,184 @@ +const fs = require('fs'); +const zlib = require('zlib'); +const BodyParser = require('../../../../middlewares/hyper-express-body-parser/index.js'); +const { log, assert_log, md5_from_buffer } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/parser-compression`; +const endpoint_url = server.base + endpoint; +const test_file_path = './content/large-image.jpg'; + +// Bind a raw parser to the endpoint which does not uncompress the body +TEST_SERVER.use( + endpoint, + BodyParser.raw({ + limit: '5mb', + inflate: false, + type: 'application/octet-stream-strict', + }) +); + +// Bind a raw parser to the endpoint which does uncompress the body +TEST_SERVER.use( + endpoint, + BodyParser.raw({ + limit: '5mb', + type: 'application/octet-stream', + }) +); + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, (request, response) => { + // Echo the body back to the client + return response.send(request.body); +}); + +let file_length_cache = {}; +function get_test_file_length(encoding = 'identity') { + // Return value from cache if it exists + if (file_length_cache[encoding]) { + return file_length_cache[encoding]; + } + + // Otherwise, calculate the length of the file + const buffer = fs.readFileSync(test_file_path); + switch (encoding) { + case 'identity': + file_length_cache[encoding] = buffer.length; + break; + case 'gzip': + file_length_cache[encoding] = zlib.gzipSync(buffer).length; + break; + case 'deflate': + file_length_cache[encoding] = zlib.deflateSync(buffer).length; + break; + default: + throw new Error('Unsupported encoding: ' + encoding); + } + + return file_length_cache[encoding]; +} + +function get_test_file_stream(encoding = 'identity') { + const readable = fs.createReadStream(test_file_path); + switch (encoding) { + case 'gzip': + const gzip = zlib.createGzip(); + return readable.pipe(gzip); + case 'deflate': + const deflate = zlib.createDeflate(); + return readable.pipe(deflate); + default: + return readable; + } +} + +async function test_parser_compression() { + // User Specified ID Brute Vulnerability Test + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.BodyParser'; + log(group, 'Testing ' + candidate + ' - Parser Compression Test'); + + // Determine the expected md5 hash of the test file + const expected_md5 = md5_from_buffer(await fs.promises.readFile(test_file_path)); + + // Perform fetch requests with the strict type but different compression types + const [strict_response_1, strict_response_2] = await Promise.all([ + fetch(endpoint_url, { + method: 'POST', + body: get_test_file_stream(), + headers: { + 'content-type': 'application/octet-stream-strict', + 'content-length': get_test_file_length(), + }, + }), + fetch(endpoint_url, { + method: 'POST', + body: get_test_file_stream('gzip'), + headers: { + 'content-type': 'application/octet-stream-strict', + 'content-encoding': 'gzip', + 'content-length': get_test_file_length('gzip'), + }, + }), + ]); + + // Retrieve the response bodies of the strict responses + const strict_body_1 = await strict_response_1.buffer(); + + // Assert that the strict response 1 was successful with matching bodies + assert_log( + group, + candidate + ' - Strict Parser Echo With Normal Body Test', + () => strict_response_1.status == 200 && md5_from_buffer(strict_body_1) == expected_md5 + ); + + // Assert that the strict response 2 was unsuccessful with a 415 status code + assert_log( + group, + candidate + ' - Strict Parser Reject With Compressed Body Test', + () => strict_response_2.status == 415 + ); + + // Test the integrity of buffer with different compressions with server + const [normal_response_1, normal_response_2, normal_response_3] = await Promise.all([ + fetch(endpoint_url, { + method: 'POST', + body: get_test_file_stream(), + headers: { + 'content-type': 'application/octet-stream', + 'content-length': get_test_file_length(), + }, + }), + fetch(endpoint_url, { + method: 'POST', + body: get_test_file_stream('deflate'), + headers: { + 'content-type': 'application/octet-stream', + 'content-encoding': 'deflate', + 'content-length': get_test_file_length('deflate'), + }, + }), + fetch(endpoint_url, { + method: 'POST', + body: get_test_file_stream('gzip'), + headers: { + 'content-type': 'application/octet-stream', + 'content-encoding': 'gzip', + 'content-length': get_test_file_length('gzip'), + }, + }), + ]); + + // Assert that all of the normal responses returned a 200 status code + assert_log( + group, + candidate + ' - Normal Parser Identity/Deflated/Gzipped HTTP Response Test', + () => normal_response_1.status == 200 && normal_response_2.status == 200 && normal_response_3.status == 200 + ); + + // Retrieve the response bodies of the normal responses + const [normal_body_1, normal_body_2, normal_body_3] = await Promise.all([ + normal_response_1.buffer(), + normal_response_2.buffer(), + normal_response_3.buffer(), + ]); + + // Assert that all normal bodies match the expected body + assert_log( + group, + candidate + ' - Normal Parser Identity/Deflated/Gzipped Body Integrity Test', + () => + md5_from_buffer(normal_body_1) == expected_md5 && + md5_from_buffer(normal_body_2) == expected_md5 && + md5_from_buffer(normal_body_3) == expected_md5 + ); + + // Wait for all the promises to resolve + log(group, 'Finished ' + candidate + ' - Parser Compression Test\n'); +} + +module.exports = { + test_parser_compression, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_limit.js b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_limit.js new file mode 100644 index 0000000..a151a6c --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_limit.js @@ -0,0 +1,71 @@ +const crypto = require('crypto'); +const BodyParser = require('../../../../middlewares/hyper-express-body-parser/index.js'); +const { log, assert_log } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/parser-limit`; +const endpoint_url = server.base + endpoint; + +// Bind a raw parser to the endpoint +const TEST_LIMIT_BYTES = Math.floor(Math.random() * 100) + 100; +TEST_SERVER.use( + endpoint, + BodyParser.raw({ + limit: TEST_LIMIT_BYTES, + }) +); + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, (request, response) => { + return response.send(); +}); + +async function test_parser_limit() { + // User Specified ID Brute Vulnerability Test + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.BodyParser'; + log(group, 'Testing ' + candidate + ' - Parser Body Size Limit Test'); + + // Perform fetch requests with various body sizes + const promises = [ + Math.floor(Math.random() * TEST_LIMIT_BYTES), // Smaller than max size + TEST_LIMIT_BYTES, // Max size + Math.floor(Math.random() * TEST_LIMIT_BYTES) + TEST_LIMIT_BYTES, // Larger than max size + TEST_LIMIT_BYTES * Math.floor(Math.random() * 5), // Random Factor Larger than max size + Math.floor(TEST_LIMIT_BYTES * 0.1), // Smaller than max size + ].map( + (size_bytes) => + new Promise(async (resolve) => { + // Generate a random buffer of bytes size + const buffer = crypto.randomBytes(size_bytes); + + // Make the fetch request + const response = await fetch(endpoint_url, { + method: 'POST', + body: buffer, + headers: { + 'content-type': 'application/octet-stream', + }, + }); + + // Assert that the response status code is 413 for the large body + assert_log( + group, + candidate + + ` - Body Size Limit Test With ${size_bytes} / ${TEST_LIMIT_BYTES} Bytes Limit -> HTTP ${response.status}`, + () => response.status == (size_bytes > TEST_LIMIT_BYTES ? 413 : 200) + ); + + resolve(); + }) + ); + + // Wait for all the promises to resolve + await Promise.all(promises); + log(group, 'Finished ' + candidate + ' - Parser Body Size Limit Test\n'); +} + +module.exports = { + test_parser_limit, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_types.js b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_types.js new file mode 100644 index 0000000..95ddc85 --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_types.js @@ -0,0 +1,94 @@ +const crypto = require('crypto'); +const BodyParser = require('../../../../middlewares/hyper-express-body-parser/index.js'); +const { log, assert_log } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/parser-types`; +const endpoint_url = server.base + endpoint; + +// Bind all parser types to the endpoint +TEST_SERVER.use(endpoint, BodyParser.raw(), BodyParser.text(), BodyParser.json()); + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, (request, response) => { + const content_type = request.headers['content-type']; + switch (content_type) { + case 'application/json': + return response.json(request.body); + default: + return response.send(request.body); + } +}); + +async function test_parser_types() { + // User Specified ID Brute Vulnerability Test + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.BodyParser'; + log(group, 'Testing ' + candidate + ' - Parser Body Types Test'); + + // Test the empty bodies + + // Perform fetch requests with various body types + const promises = [ + [crypto.randomBytes(1000), 'application/octet-stream'], + [crypto.randomBytes(1000).toString('hex'), 'text/plain'], + [ + JSON.stringify({ + name: 'json test', + payload: crypto.randomBytes(1000).toString('hex'), + }), + 'application/json', + ], + ].map( + ([request_body, content_type]) => + new Promise(async (resolve) => { + // Make the fetch request + const response = await fetch(endpoint_url, { + method: 'POST', + headers: { + 'content-type': content_type, + }, + body: request_body, + }); + + // Parse the incoming body as the appropriate type + let response_body; + switch (content_type) { + case 'application/octet-stream': + response_body = await response.buffer(); + break; + case 'text/plain': + response_body = await response.text(); + break; + case 'application/json': + response_body = await response.text(); + break; + } + + // Assert that the response status code is 413 for the large body + assert_log(group, candidate + ` - Body Type Test With '${content_type}'`, () => { + switch (content_type) { + case 'application/octet-stream': + return Buffer.compare(request_body, response_body) === 0; + case 'text/plain': + return request_body === response_body; + case 'application/json': + return JSON.stringify(request_body) === JSON.stringify(response_body); + default: + return false; + } + }); + + resolve(); + }) + ); + + // Wait for all the promises to resolve + await Promise.all(promises); + log(group, 'Finished ' + candidate + ' - Parser Body Types Test\n'); +} + +module.exports = { + test_parser_types, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_validation.js b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_validation.js new file mode 100644 index 0000000..6c87490 --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-body-parser/scenarios/parser_validation.js @@ -0,0 +1,75 @@ +const crypto = require('crypto'); +const BodyParser = require('../../../../middlewares/hyper-express-body-parser/index.js'); +const { log, assert_log } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/parser-validation`; +const endpoint_url = server.base + endpoint; + +const TEST_PAYLOAD_SIZE = Math.floor(Math.random() * 250) + 250; + +// Bind a raw parser that will only parse if the content type matches +TEST_SERVER.use( + endpoint, + BodyParser.raw({ + type: 'application/octet-stream', + verify: (req, res, buffer) => { + return buffer.length > TEST_PAYLOAD_SIZE * 0.5; + }, // Reject bodies that are less than half size of the payload + }) +); + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, (request, response) => { + // Send a 200 if we have some body content else send a 204 + response.status(request.body.length > 0 ? 200 : 204).send(); +}); + +async function test_parser_validation() { + // User Specified ID Brute Vulnerability Test + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.BodyParser'; + log(group, 'Testing ' + candidate + ' - Parser Body Validation Test'); + + // Perform fetch requests with various body sizes + const promises = [ + ['application/json', TEST_PAYLOAD_SIZE, 204], // ~100% of the payload size but incorrect content type + ['application/octet-stream', Math.floor(TEST_PAYLOAD_SIZE * 0.25), 403], // ~25% of the payload size + ['application/octet-stream', Math.floor(TEST_PAYLOAD_SIZE * 0.75), 200], // ~75% of the payload size + ['application/octet-stream', TEST_PAYLOAD_SIZE, 200], // ~75% of the payload size + ].map( + ([content_type, size_bytes, status_code]) => + new Promise(async (resolve) => { + // Generate a random buffer of bytes size + const buffer = crypto.randomBytes(size_bytes); + + // Make the fetch request + const response = await fetch(endpoint_url, { + method: 'POST', + body: buffer, + headers: { + 'content-type': content_type, + }, + }); + + // Assert that the response status code is 413 for the large body + assert_log( + group, + candidate + + ` - Content Type & Verify Function Test With "${content_type}" @ ${size_bytes} Bytes Payload`, + () => response.status === status_code + ); + + resolve(); + }) + ); + + // Wait for all the promises to resolve + await Promise.all(promises); + log(group, 'Finished ' + candidate + ' - Parser Body Validation Test\n'); +} + +module.exports = { + test_parser_validation, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/configuration.json b/packages/hyper-express/tests/middlewares/hyper-express-session/configuration.json new file mode 100644 index 0000000..f62148d --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/configuration.json @@ -0,0 +1,4 @@ +{ + "path": "/middlewares/hyper-express-session", + "log_store_events": false +} diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/index.js b/packages/hyper-express/tests/middlewares/hyper-express-session/index.js new file mode 100644 index 0000000..8c5eca1 --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/index.js @@ -0,0 +1,23 @@ +// Bind test session engine to configuration path on test server +const { TEST_SERVER } = require('../../components/Server.js'); +const { TEST_ENGINE } = require('./test_engine.js'); +const { path } = require('./configuration.json'); +TEST_SERVER.use(path, TEST_ENGINE); + +const { test_properties_scenario } = require('./scenarios/properties.js'); +const { test_brute_scenario } = require('./scenarios/brute.js'); +const { test_duration_scenario } = require('./scenarios/duration.js'); +const { test_roll_scenario } = require('./scenarios/roll.js'); +const { test_visits_scenario } = require('./scenarios/visits.js'); + +async function test_session_middleware() { + await test_properties_scenario(); + await test_brute_scenario(); + await test_roll_scenario(); + await test_visits_scenario(); + await test_duration_scenario(); +} + +module.exports = { + test_session_middleware, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/brute.js b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/brute.js new file mode 100644 index 0000000..e2e21d7 --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/brute.js @@ -0,0 +1,49 @@ +const { log, assert_log, random_string, async_for_each } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { TEST_STORE } = require('../test_engine.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/brute`; +const endpoint_url = server.base + endpoint; + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, async (request, response) => { + await request.session.start(); + return response.json({ + session_id: request.session.id, + store: TEST_STORE.data, + }); +}); + +async function test_brute_scenario() { + // User Specified ID Brute Vulnerability Test + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.SessionEngine.Session'; + let last_session_id = ''; + log(group, `Testing ${candidate} - Self-Specified/Brute Session ID Test`); + TEST_STORE.empty(); + await async_for_each([0, 1, 2, 3, 4], async (value, next) => { + let response = await fetch(endpoint_url, { + method: 'POST', + headers: { + cookie: 'test_sess=' + random_string(30), // Random Session IDs + }, + }); + + let body = await response.json(); + assert_log( + group, + candidate + ' Brute-Force ID Vulnerability Prevention @ ' + value, + () => Object.keys(body.store).length === 0 && last_session_id !== body.session_id + ); + + last_session_id = body.session_id; + next(); + }); + + log(group, `Finished Testing ${candidate} - Self-Specified/Brute Session ID Test\n`); +} + +module.exports = { + test_brute_scenario, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/duration.js b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/duration.js new file mode 100644 index 0000000..f364c2a --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/duration.js @@ -0,0 +1,75 @@ +const { log, assert_log, async_for_each } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { TEST_STORE, TEST_ENGINE } = require('../test_engine.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/duration`; +const endpoint_url = server.base + endpoint; + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, async (request, response) => { + await TEST_ENGINE.cleanup(); // Purposely trigger cleanup before every request to simulate ideal session cleanup + await request.session.start(); + let body = await request.text(); + let duration = parseInt(body); + + if (duration > 0) request.session.set_duration(duration); + + return response.json({ + session_id: request.session.id, + store: TEST_STORE.data, + }); +}); + +async function test_duration_scenario() { + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.SessionEngine.Session'; + let cookies = []; + + log(group, 'Testing ' + candidate + ' - Custom Duration/Cleanup Test'); + TEST_STORE.empty(); + await async_for_each([1, 2, 3], async (value, next) => { + let response = await fetch(endpoint_url, { + method: 'POST', + headers: { + cookie: cookies.join('; '), + }, + body: value < 3 ? '250' : '', // Set Custom Duration On First 2 Requests + }); + let headers = response.headers.raw(); + let body = await response.json(); + + // Send session cookie with future requests + if (Array.isArray(headers['set-cookie'])) { + cookies = []; + headers['set-cookie'].forEach((chunk) => { + chunk = chunk.split('; ')[0].split('='); + let name = chunk[0]; + let value = chunk[1]; + let header = `${name}=${value}`; + cookies.push(header); + }); + } + + assert_log(group, candidate + ' Custom Duration/Cleanup @ Iteration ' + value, () => { + if (value == 1 || value == 3) return Object.keys(body.store).length == 0; + + let store_test = Object.keys(body.store).length == 1; + let sess_obj_test = body.store?.[body.session_id]?.data !== undefined; + + return store_test && sess_obj_test; + }); + + // Wait 1.5 Seconds for session to expire with custom duration before 3rd request + let delay = value == 2 ? 300 : 0; + if (delay > 0) log(group, `Waiting ${delay}ms to simulate custom duration expiry...`); + + setTimeout((n) => n(), delay, next); + }); + + log(group, 'Finished Testing ' + candidate + ' - Custom Duration/Cleanup Test\n'); +} + +module.exports = { + test_duration_scenario, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/properties.js b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/properties.js new file mode 100644 index 0000000..0c90e9e --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/properties.js @@ -0,0 +1,71 @@ +const { log, assert_log, random_string, async_for_each } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { TEST_STORE } = require('../test_engine.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/properties`; +const endpoint_url = server.base + endpoint; + +// Create Backend HTTP Route +TEST_SERVER.get(endpoint, async (request, response) => { + // Start the session + await request.session.start(); + + // Set some value into the session object + // The await is unneccessary but it is used to simulate a long running operation + await request.session.set({ + myid: 'some_id', + visits: 0, + }); + + return response.json({ + id: request.session.id, + signed_id: request.session.signed_id, + ready: request.session.ready, + stored: request.session.stored, + }); +}); + +async function test_properties_scenario() { + // Test session persistence with visits test - VISITS ITERATOR TEST + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.SessionEngine.Session'; + + // Make first fetch request + const response1 = await fetch(endpoint_url); + const data1 = await response1.json(); + + // Make second fetch request + const response2 = await fetch(endpoint_url, { + headers: { + cookie: response1.headers.get('set-cookie').split('; ')[0], + }, + }); + const data2 = await response2.json(); + + // Assert that the Session.id is a string and exactly same in both requests + assert_log( + group, + `${candidate}.id`, + () => typeof data1.id == 'string' && data1.id.length > 0 && data1.id == data2.id + ); + + // Assert that the Session.signed_id is a string and exactly same in both requests + assert_log( + group, + `${candidate}.signed_id`, + () => typeof data1.signed_id == 'string' && data1.signed_id.length > 0 && data1.signed_id == data2.signed_id + ); + + // Assert that the session was Session.ready in both requests + assert_log(group, `${candidate}.ready`, () => data1.ready && data2.ready); + + // Assert that the session was Session.stored only in second request + assert_log(group, `${candidate}.stored`, () => !data1.stored && data2.stored); + + log(group, 'Finished Testing ' + candidate + ' - Properties Test\n'); +} + +module.exports = { + test_properties_scenario, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/roll.js b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/roll.js new file mode 100644 index 0000000..e7c7c8a --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/roll.js @@ -0,0 +1,74 @@ +const { log, assert_log, random_string, async_for_each } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { TEST_STORE } = require('../test_engine.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/roll`; +const endpoint_url = server.base + endpoint; + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, async (request, response) => { + await request.session.start(); + if (request.session.get('some_data') == undefined) { + request.session.set('some_data', random_string(10)); + } else { + // Performs a delete and migrate to a new roll id + await request.session.roll(); + } + + return response.json({ + session_id: request.session.id, + session_data: request.session.get(), + store: TEST_STORE.data, + }); +}); + +async function test_roll_scenario() { + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.SessionEngine.Session'; + let cookies = []; + let last_rolled_id = ''; + log(group, 'Testing ' + candidate + ' - Roll Test'); + + TEST_STORE.empty(); + await async_for_each([0, 0, 1, 0], async (value, next) => { + let response = await fetch(endpoint_url, { + method: 'POST', + headers: { + cookie: cookies.join('; '), + }, + }); + let headers = response.headers.raw(); + let body = await response.json(); + + // Send session cookie with future requests + let current_session_id; + if (Array.isArray(headers['set-cookie'])) { + cookies = []; // Reset cookies for new session id + headers['set-cookie'].forEach((chunk) => { + chunk = chunk.split('; ')[0].split('='); + let name = chunk[0]; + let value = chunk[1]; + let header = `${name}=${value}`; + if (name === 'test_sess') current_session_id = value; + cookies.push(header); + }); + } + + assert_log(group, candidate + ' Session Roll @ Iterative Scenario ' + value, () => { + // Store will always be empty due to lazy persistance and .roll() destroying session during request + let store_test = Object.keys(body.store).length === Math.floor(value); + let id_test = value < 1 ? current_session_id !== last_rolled_id : true; + last_rolled_id = current_session_id; + return store_test && id_test; + }); + + next(); + }); + + log(group, 'Finished Testing ' + candidate + ' - Roll Test\n'); +} + +module.exports = { + test_roll_scenario, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/visits.js b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/visits.js new file mode 100644 index 0000000..157d66a --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/scenarios/visits.js @@ -0,0 +1,106 @@ +const { log, assert_log, async_for_each } = require('../../../scripts/operators.js'); +const { fetch, server } = require('../../../configuration.js'); +const { TEST_SERVER } = require('../../../components/Server.js'); +const { TEST_STORE } = require('../test_engine.js'); +const { path } = require('../configuration.json'); +const endpoint = `${path}/scenarios/visits`; +const endpoint_url = server.base + endpoint; + +// Create Backend HTTP Route +TEST_SERVER.post(endpoint, async (request, response) => { + await request.session.start(); + let visits = request.session.get('visits'); + + if (visits == undefined) { + visits = 1; + } else if (visits < 5) { + visits++; + } else { + visits = undefined; + } + + if (visits) { + request.session.set('visits', visits); + } else { + await request.session.destroy(); + } + + return response.json({ + session_id: request.session.id, + session: request.session.get(), + store: TEST_STORE.data, + }); +}); + +async function test_visits_scenario() { + // Test session persistence with visits test - VISITS ITERATOR TEST + let group = 'MIDDLEWARE'; + let candidate = 'Middleware.SessionEngine.Session'; + let cookies = []; + let session_expiry = 0; + + TEST_STORE.empty(); + log(group, 'Testing ' + candidate + ' - Visits Test'); + await async_for_each([1, 2, 3, 4, 5, 0, 1, 2, 3, 4], async (value, next) => { + let response = await fetch(endpoint_url, { + method: 'POST', + headers: { + cookie: cookies.join('; '), + }, + }); + let headers = response.headers.raw(); + let body = await response.json(); + + // Send session cookie with future requests + if (Array.isArray(headers['set-cookie'])) { + cookies = []; + headers['set-cookie'].forEach((chunk) => { + let chunks = chunk.split('; ')[0].split('='); + let name = chunks[0]; + let value = chunks[1]; + let header = `${name}=${value}`; + + // Ensure the cookie is not a "delete" operation with a max-age of 0 + if (chunk.toLowerCase().includes('max-age=0')) return; + + // Push the cookie to the list of cookies to send with future requests + cookies.push(header); + }); + } + + // Perform Visits Check + if (value == 0) { + assert_log(group, `${candidate} VISITS_TEST @ ${value}`, () => { + let visits_test = Object.keys(body.session).length == 0; + let store_test = body.store[body.session_id] == undefined && Object.keys(body.store).length == 0; + return visits_test && store_test; + }); + } else if (value == 1) { + assert_log(group, `${candidate} VISITS_TEST @ ${value}`, () => { + let visits_test = body.session.visits === value; + let store_test = body.store[body.session_id] == undefined; + return visits_test && store_test; + }); + } else { + assert_log(group, `${candidate} OBJ_TOUCH_TEST & OBJ_VISITS_TEST @ ${value}`, () => { + let session_object = body.store?.[body.session_id]; + let visits_test = body.session.visits === value; + let store_test = session_object?.data?.visits === value; + + let touch_test = value < 3; + if (!touch_test && session_object.expiry >= session_expiry) touch_test = true; + + session_expiry = session_object.expiry; + return visits_test && store_test && touch_test; + }); + } + + next(); + }); + + log(group, 'Finished Testing ' + candidate + ' - Visits Test\n'); +} + +module.exports = { + test_visits_scenario, +}; diff --git a/packages/hyper-express/tests/middlewares/hyper-express-session/test_engine.js b/packages/hyper-express/tests/middlewares/hyper-express-session/test_engine.js new file mode 100644 index 0000000..9f660eb --- /dev/null +++ b/packages/hyper-express/tests/middlewares/hyper-express-session/test_engine.js @@ -0,0 +1,64 @@ +const SessionEngine = require('../../../middlewares/hyper-express-session/index.js'); +const MemoryStore = require('../../scripts/MemoryStore.js'); +const { random_string } = require('../../scripts/operators.js'); + +// Create Test Engine For Usage In Tests +const TEST_ENGINE = new SessionEngine({ + duration: 1000 * 60 * 45, + cookie: { + name: 'test_sess', + httpOnly: false, + secure: false, + sameSite: 'none', + secret: random_string(20), + }, +}); + +const { log } = require('../../scripts/operators.js'); +const { log_store_events } = require('./configuration.json'); +function store_log(message) { + if (log_store_events === true) log('SESSION_STORE', message); +} + +// Use a simulated SQL-like memory store +const TEST_STORE = new MemoryStore(); + +// Handle READ events +TEST_ENGINE.use('read', (session) => { + store_log('READ -> ' + session.id); + return TEST_STORE.select(session.id); +}); + +// Handle WRITE events +TEST_ENGINE.use('write', (session) => { + if (session.stored) { + store_log('UPDATE -> ' + session.id + ' -> ' + session.expires_at); + TEST_STORE.update(session.id, session.get(), session.expires_at); + } else { + store_log('INSERT -> ' + session.id + ' -> ' + session.expires_at); + TEST_STORE.insert(session.id, session.get(), session.expires_at); + } +}); + +// Handle TOUCH events +TEST_ENGINE.use('touch', (session) => { + store_log('TOUCH -> ' + session.id + ' -> ' + session.expires_at); + TEST_STORE.touch(session.id, session.expires_at); +}); + +// Handle DESTROY events +TEST_ENGINE.use('destroy', (session) => { + store_log('DESTROY -> ' + session.id); + TEST_STORE.delete(session.id); +}); + +// Handle CLEANUP events +TEST_ENGINE.use('cleanup', () => { + store_log('CLEANUP -> ALL SESSIONS'); + TEST_STORE.cleanup(); +}); + +module.exports = { + TEST_ENGINE, + TEST_STORE, +}; diff --git a/packages/hyper-express/tests/package-lock.json b/packages/hyper-express/tests/package-lock.json new file mode 100644 index 0000000..fc618ad --- /dev/null +++ b/packages/hyper-express/tests/package-lock.json @@ -0,0 +1,3943 @@ +{ + "name": "hyper-express-tests", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "hyper-express-tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "abort-controller": "^3.0.0", + "eventsource": "^2.0.0", + "form-data": "^4.0.0", + "i": "^0.3.7", + "node-fetch": "^2.6.6", + "npm": "^8.5.5", + "time-cost": "^1.0.0", + "ws": "^8.2.3", + "zlib": "^1.0.5" + } + }, + "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==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventsource": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.0.tgz", + "integrity": "sha512-U9TI2qLWwedwiDLCbSUoSAPHGK2P7nT6/f25wBzMy9tWOKgFoNY4n+GYCPCYg3sGKrIoCmpChJoO3KKymcLo8A==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/npm": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/npm/-/npm-8.5.5.tgz", + "integrity": "sha512-a1vl26nokCNlD+my/iNYmOUPx/hpYR4ZyZk8gb7/A2XXtrPZf2gTSJOnVjS77jQS+BSfIVQpipZwXWCL0+5wzg==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/ci-detect", + "@npmcli/config", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/run-script", + "abbrev", + "ansicolors", + "ansistyles", + "archy", + "cacache", + "chalk", + "chownr", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minipass", + "minipass-pipeline", + "mkdirp", + "mkdirp-infer-owner", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "opener", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "read-package-json", + "read-package-json-fast", + "readdir-scoped-modules", + "rimraf", + "semver", + "ssri", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^5.0.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/config": "^4.0.1", + "@npmcli/map-workspaces": "^2.0.2", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^3.0.1", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "archy": "~1.0.0", + "cacache": "^16.0.2", + "chalk": "^4.1.2", + "chownr": "^2.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.1", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.12", + "glob": "^7.2.0", + "graceful-fs": "^4.2.9", + "hosted-git-info": "^5.0.0", + "ini": "^2.0.0", + "init-package-json": "^3.0.1", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "libnpmaccess": "^6.0.2", + "libnpmdiff": "^4.0.2", + "libnpmexec": "^4.0.2", + "libnpmfund": "^3.0.1", + "libnpmhook": "^8.0.2", + "libnpmorg": "^4.0.2", + "libnpmpack": "^4.0.2", + "libnpmpublish": "^6.0.2", + "libnpmsearch": "^5.0.2", + "libnpmteam": "^4.0.2", + "libnpmversion": "^3.0.1", + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^9.0.0", + "nopt": "^5.0.0", + "npm-audit-report": "^2.1.5", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-pick-manifest": "^7.0.0", + "npm-profile": "^6.0.2", + "npm-registry-fetch": "^13.0.1", + "npm-user-validate": "^1.0.1", + "npmlog": "^6.0.1", + "opener": "^1.5.2", + "pacote": "^13.0.5", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "tar": "^6.1.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^1.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^2.0.2", + "write-file-atomic": "^4.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@gar/promisify": { + "version": "1.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "5.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^2.0.0", + "@npmcli/metavuln-calculator": "^3.0.1", + "@npmcli/move-file": "^1.1.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^1.0.3", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^3.0.0", + "bin-links": "^3.0.0", + "cacache": "^16.0.0", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^9.0.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.0", + "npmlog": "^6.0.1", + "pacote": "^13.0.5", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "treeverse": "^1.0.4", + "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/ci-detect": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^2.0.1", + "ini": "^2.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "proc-log": "^2.0.0", + "read-package-json-fast": "^2.0.3", + "semver": "^7.3.5", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^7.3.1", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.2.0", + "minimatch": "^5.0.1", + "read-package-json-fast": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^16.0.0", + "json-parse-even-better-errors": "^2.3.1", + "pacote": "^13.0.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "1.3.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^1.0.3", + "@npmcli/promise-spawn": "^1.3.2", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.2.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/ansicolors": { + "version": "0.3.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ansistyles": { + "version": "0.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/asap": { + "version": "2.0.6", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^4.0.1", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^2.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/cacache": { + "version": "16.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.1.2", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^7.2.0", + "infer-owner": "^1.0.4", + "lru-cache": "^7.5.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "4.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mkdirp-infer-owner": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/colors": { + "version": "1.4.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/debuglog": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/depd": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/dezalgo": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.0.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.12", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/gauge": { + "version": "4.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "7.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.9", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm/node_modules/has-flag": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.0", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.0.0", + "promzard": "^0.3.0", + "read": "^1.0.7", + "read-package-json": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "1.1.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.8.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^3.0.4", + "npm-package-arg": "^9.0.1", + "pacote": "^13.0.5", + "tar": "^6.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.0.0", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/run-script": "^3.0.0", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^9.0.1", + "npmlog": "^6.0.1", + "pacote": "^13.0.5", + "proc-log": "^2.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/run-script": "^3.0.0", + "npm-package-arg": "^9.0.1", + "pacote": "^13.0.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "normalize-package-data": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0", + "semver": "^7.1.3", + "ssri": "^8.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "5.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/run-script": "^3.0.0", + "json-parse-even-better-errors": "^2.3.1", + "proc-log": "^2.0.0", + "semver": "^7.3.5", + "stringify-package": "^1.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "7.5.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "10.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.0.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.5.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.1.1", + "ssri": "^8.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "3.1.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/mkdirp-infer-owner": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "0.0.8", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "9.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "4.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "2.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "1.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "4.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "9.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.2.0", + "ignore-walk": "^4.0.1", + "npm-bundled": "^1.1.2", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.0", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "13.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^10.0.3", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.1", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.0", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "1.0.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/npmlog": { + "version": "6.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/opener": { + "version": "1.5.2", + "inBundle": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "13.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^3.0.1", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^4.0.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1", + "just-diff": "^5.0.1", + "just-diff-apply": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "0.3.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "1" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "1.0.7", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.2.0", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "2.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "3.6.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.3.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "3.0.7", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.6.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "6.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.11", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/stringify-package": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.1.11", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "1.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^1.0.3" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/time-cost": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-cost/-/time-cost-1.0.0.tgz", + "integrity": "sha512-GOxm/HrgYxVRIUXq4nP2s46zcaESp2H/vPQMj9zeuvODiXE1Slr/Ey7BnagD3QeKmkdzSYLJ7zaSMfzBeeJKvQ==" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", + "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zlib": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", + "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=", + "hasInstallScript": true, + "engines": { + "node": ">=0.2.0" + } + } + }, + "dependencies": { + "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==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "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==" + }, + "eventsource": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.0.tgz", + "integrity": "sha512-U9TI2qLWwedwiDLCbSUoSAPHGK2P7nT6/f25wBzMy9tWOKgFoNY4n+GYCPCYg3sGKrIoCmpChJoO3KKymcLo8A==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==" + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "requires": { + "mime-db": "1.51.0" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "npm": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/npm/-/npm-8.5.5.tgz", + "integrity": "sha512-a1vl26nokCNlD+my/iNYmOUPx/hpYR4ZyZk8gb7/A2XXtrPZf2gTSJOnVjS77jQS+BSfIVQpipZwXWCL0+5wzg==", + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^5.0.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/config": "^4.0.1", + "@npmcli/map-workspaces": "^2.0.2", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^3.0.1", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "archy": "~1.0.0", + "cacache": "^16.0.2", + "chalk": "^4.1.2", + "chownr": "^2.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.1", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.12", + "glob": "^7.2.0", + "graceful-fs": "^4.2.9", + "hosted-git-info": "^5.0.0", + "ini": "^2.0.0", + "init-package-json": "^3.0.1", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "libnpmaccess": "^6.0.2", + "libnpmdiff": "^4.0.2", + "libnpmexec": "^4.0.2", + "libnpmfund": "^3.0.1", + "libnpmhook": "^8.0.2", + "libnpmorg": "^4.0.2", + "libnpmpack": "^4.0.2", + "libnpmpublish": "^6.0.2", + "libnpmsearch": "^5.0.2", + "libnpmteam": "^4.0.2", + "libnpmversion": "^3.0.1", + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^9.0.0", + "nopt": "^5.0.0", + "npm-audit-report": "^2.1.5", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-pick-manifest": "^7.0.0", + "npm-profile": "^6.0.2", + "npm-registry-fetch": "^13.0.1", + "npm-user-validate": "^1.0.1", + "npmlog": "^6.0.1", + "opener": "^1.5.2", + "pacote": "^13.0.5", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "tar": "^6.1.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^1.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^2.0.2", + "write-file-atomic": "^4.0.1" + }, + "dependencies": { + "@gar/promisify": { + "version": "1.1.3", + "bundled": true + }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "bundled": true + }, + "@npmcli/arborist": { + "version": "5.0.3", + "bundled": true, + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^2.0.0", + "@npmcli/metavuln-calculator": "^3.0.1", + "@npmcli/move-file": "^1.1.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^1.0.3", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^3.0.0", + "bin-links": "^3.0.0", + "cacache": "^16.0.0", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^9.0.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.0", + "npmlog": "^6.0.1", + "pacote": "^13.0.5", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "treeverse": "^1.0.4", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/ci-detect": { + "version": "2.0.0", + "bundled": true + }, + "@npmcli/config": { + "version": "4.0.1", + "bundled": true, + "requires": { + "@npmcli/map-workspaces": "^2.0.1", + "ini": "^2.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "proc-log": "^2.0.0", + "read-package-json-fast": "^2.0.3", + "semver": "^7.3.5", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/disparity-colors": { + "version": "1.0.1", + "bundled": true, + "requires": { + "ansi-styles": "^4.3.0" + } + }, + "@npmcli/fs": { + "version": "1.1.0", + "bundled": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "3.0.0", + "bundled": true, + "requires": { + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^7.3.1", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "bundled": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/map-workspaces": { + "version": "2.0.2", + "bundled": true, + "requires": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.2.0", + "minimatch": "^5.0.1", + "read-package-json-fast": "^2.0.3" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "bundled": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@npmcli/metavuln-calculator": { + "version": "3.0.1", + "bundled": true, + "requires": { + "cacache": "^16.0.0", + "json-parse-even-better-errors": "^2.3.1", + "pacote": "^13.0.3", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "bundled": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/name-from-folder": { + "version": "1.0.1", + "bundled": true + }, + "@npmcli/node-gyp": { + "version": "1.0.3", + "bundled": true + }, + "@npmcli/package-json": { + "version": "1.0.1", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "@npmcli/promise-spawn": { + "version": "1.3.2", + "bundled": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "3.0.1", + "bundled": true, + "requires": { + "@npmcli/node-gyp": "^1.0.3", + "@npmcli/promise-spawn": "^1.3.2", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "bundled": true + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "6.0.2", + "bundled": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "bundled": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "bundled": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "3.0.0", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "bin-links": { + "version": "3.0.0", + "bundled": true, + "requires": { + "cmd-shim": "^4.0.1", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^2.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^4.0.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "cacache": { + "version": "16.0.2", + "bundled": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.1.2", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^7.2.0", + "infer-owner": "^1.0.4", + "lru-cache": "^7.5.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + } + }, + "chalk": { + "version": "4.1.2", + "bundled": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "2.0.0", + "bundled": true + }, + "cidr-regex": { + "version": "3.1.1", + "bundled": true, + "requires": { + "ip-regex": "^4.1.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "bundled": true + }, + "cli-columns": { + "version": "4.0.0", + "bundled": true, + "requires": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "cli-table3": { + "version": "0.6.1", + "bundled": true, + "requires": { + "colors": "1.4.0", + "string-width": "^4.2.0" + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "4.1.0", + "bundled": true, + "requires": { + "mkdirp-infer-owner": "^2.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "color-support": { + "version": "1.1.3", + "bundled": true + }, + "colors": { + "version": "1.4.0", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.6.0", + "bundled": true, + "requires": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + } + }, + "common-ancestor-path": { + "version": "1.0.1", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "debug": { + "version": "4.3.3", + "bundled": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "depd": { + "version": "1.1.2", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "diff": { + "version": "5.0.0", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.13", + "bundled": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "bundled": true + }, + "err-code": { + "version": "2.0.3", + "bundled": true + }, + "fastest-levenshtein": { + "version": "1.0.12", + "bundled": true + }, + "fs-minipass": { + "version": "2.1.0", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "gauge": { + "version": "4.0.3", + "bundled": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "glob": { + "version": "7.2.0", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "bundled": true + }, + "has": { + "version": "1.0.3", + "bundled": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "5.0.0", + "bundled": true, + "requires": { + "lru-cache": "^7.5.1" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "bundled": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "bundled": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "bundled": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore-walk": { + "version": "4.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "indent-string": { + "version": "4.0.0", + "bundled": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "2.0.0", + "bundled": true + }, + "init-package-json": { + "version": "3.0.1", + "bundled": true, + "requires": { + "npm-package-arg": "^9.0.0", + "promzard": "^0.3.0", + "read": "^1.0.7", + "read-package-json": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^3.0.0" + } + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "4.3.0", + "bundled": true + }, + "is-cidr": { + "version": "4.0.2", + "bundled": true, + "requires": { + "cidr-regex": "^3.1.1" + } + }, + "is-core-module": { + "version": "2.8.1", + "bundled": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "is-lambda": { + "version": "1.0.1", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "bundled": true + }, + "json-stringify-nice": { + "version": "1.1.4", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "just-diff": { + "version": "5.0.1", + "bundled": true + }, + "just-diff-apply": { + "version": "4.0.1", + "bundled": true + }, + "libnpmaccess": { + "version": "6.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmdiff": { + "version": "4.0.2", + "bundled": true, + "requires": { + "@npmcli/disparity-colors": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^3.0.4", + "npm-package-arg": "^9.0.1", + "pacote": "^13.0.5", + "tar": "^6.1.0" + } + }, + "libnpmexec": { + "version": "4.0.2", + "bundled": true, + "requires": { + "@npmcli/arborist": "^5.0.0", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/run-script": "^3.0.0", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^9.0.1", + "npmlog": "^6.0.1", + "pacote": "^13.0.5", + "proc-log": "^2.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "walk-up-path": "^1.0.0" + } + }, + "libnpmfund": { + "version": "3.0.1", + "bundled": true, + "requires": { + "@npmcli/arborist": "^5.0.0" + } + }, + "libnpmhook": { + "version": "8.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmorg": { + "version": "4.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmpack": { + "version": "4.0.2", + "bundled": true, + "requires": { + "@npmcli/run-script": "^3.0.0", + "npm-package-arg": "^9.0.1", + "pacote": "^13.0.5" + } + }, + "libnpmpublish": { + "version": "6.0.2", + "bundled": true, + "requires": { + "normalize-package-data": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0", + "semver": "^7.1.3", + "ssri": "^8.0.1" + } + }, + "libnpmsearch": { + "version": "5.0.2", + "bundled": true, + "requires": { + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmteam": { + "version": "4.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmversion": { + "version": "3.0.1", + "bundled": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/run-script": "^3.0.0", + "json-parse-even-better-errors": "^2.3.1", + "proc-log": "^2.0.0", + "semver": "^7.3.5", + "stringify-package": "^1.0.1" + } + }, + "lru-cache": { + "version": "7.5.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "10.0.6", + "bundled": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.0.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.5.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.1.1", + "ssri": "^8.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "3.1.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "2.0.3", + "bundled": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "minipass-flush": { + "version": "1.0.5", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "bundled": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "bundled": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true + }, + "mkdirp-infer-owner": { + "version": "2.0.0", + "bundled": true, + "requires": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + } + }, + "ms": { + "version": "2.1.3", + "bundled": true + }, + "mute-stream": { + "version": "0.0.8", + "bundled": true + }, + "negotiator": { + "version": "0.6.3", + "bundled": true + }, + "node-gyp": { + "version": "9.0.0", + "bundled": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + } + }, + "nopt": { + "version": "5.0.0", + "bundled": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "4.0.0", + "bundled": true, + "requires": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "npm-audit-report": { + "version": "2.1.5", + "bundled": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "npm-bundled": { + "version": "1.1.2", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "4.0.0", + "bundled": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, + "npm-package-arg": { + "version": "9.0.1", + "bundled": true, + "requires": { + "hosted-git-info": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "4.0.0", + "bundled": true, + "requires": { + "glob": "^7.2.0", + "ignore-walk": "^4.0.1", + "npm-bundled": "^1.1.2", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "7.0.0", + "bundled": true, + "requires": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + } + }, + "npm-profile": { + "version": "6.0.2", + "bundled": true, + "requires": { + "npm-registry-fetch": "^13.0.0", + "proc-log": "^2.0.0" + } + }, + "npm-registry-fetch": { + "version": "13.0.1", + "bundled": true, + "requires": { + "make-fetch-happen": "^10.0.3", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.1", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.0", + "proc-log": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.1", + "bundled": true + }, + "npmlog": { + "version": "6.0.1", + "bundled": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.2", + "bundled": true + }, + "p-map": { + "version": "4.0.0", + "bundled": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pacote": { + "version": "13.0.5", + "bundled": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^3.0.1", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^4.0.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.11" + } + }, + "parse-conflict-json": { + "version": "2.0.1", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^2.3.1", + "just-diff": "^5.0.1", + "just-diff-apply": "^4.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "proc-log": { + "version": "2.0.0", + "bundled": true + }, + "promise-all-reject-late": { + "version": "1.0.1", + "bundled": true + }, + "promise-call-limit": { + "version": "1.0.1", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "2.0.1", + "bundled": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "2.0.0", + "bundled": true + }, + "read-package-json": { + "version": "5.0.0", + "bundled": true, + "requires": { + "glob": "^7.2.0", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "read-package-json-fast": { + "version": "2.0.3", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "3.0.2", + "bundled": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "semver": { + "version": "7.3.5", + "bundled": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.7", + "bundled": true + }, + "smart-buffer": { + "version": "4.2.0", + "bundled": true + }, + "socks": { + "version": "2.6.2", + "bundled": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "6.1.1", + "bundled": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + } + }, + "spdx-correct": { + "version": "3.1.1", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "bundled": true + }, + "ssri": { + "version": "8.0.1", + "bundled": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "bundled": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar": { + "version": "6.1.11", + "bundled": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "treeverse": { + "version": "1.0.4", + "bundled": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "walk-up-path": { + "version": "1.0.0", + "bundled": true + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "2.0.2", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "4.0.1", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "yallist": { + "version": "4.0.0", + "bundled": true + } + } + }, + "time-cost": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-cost/-/time-cost-1.0.0.tgz", + "integrity": "sha512-GOxm/HrgYxVRIUXq4nP2s46zcaESp2H/vPQMj9zeuvODiXE1Slr/Ey7BnagD3QeKmkdzSYLJ7zaSMfzBeeJKvQ==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "ws": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", + "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", + "requires": {} + }, + "zlib": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", + "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=" + } + } +} diff --git a/packages/hyper-express/tests/package.json b/packages/hyper-express/tests/package.json new file mode 100644 index 0000000..2aa2eda --- /dev/null +++ b/packages/hyper-express/tests/package.json @@ -0,0 +1,21 @@ +{ + "name": "hyper-express-tests", + "version": "1.0.0", + "description": "Unit/API Tests For HyperExpress", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "abort-controller": "^3.0.0", + "eventsource": "^2.0.0", + "form-data": "^4.0.0", + "i": "^0.3.7", + "node-fetch": "^2.6.6", + "npm": "^8.5.5", + "ws": "^8.2.3", + "zlib": "^1.0.5" + } +} diff --git a/packages/hyper-express/tests/performance.js b/packages/hyper-express/tests/performance.js new file mode 100644 index 0000000..7d1ba4d --- /dev/null +++ b/packages/hyper-express/tests/performance.js @@ -0,0 +1,74 @@ +class PerformanceMeasurement { + #data = []; + + /** + * Name for this performance measurement. + * @param {string} name + */ + constructor(name) { + // Register graceful shutdown handlers + let context = this; + let in_shutdown = false; + [`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `SIGTERM`].forEach((type) => + process.on(type, () => { + // Mark the server as shutting down + if (in_shutdown) return; + in_shutdown = true; + + // Log the performance measurements + console.log(name, JSON.stringify(context.measurements)); + + // Set a timeout to exit the process after 1 second + setTimeout(() => process.exit(0), 1000); + }) + ); + } + + /** + * Records the amount of time it took to execute a function. + * Use `process.hrtime.bigint()` to get the start time. + * @param {BigInt} start_time + */ + record(start_time) { + const delta = process.hrtime.bigint() - start_time; + if (delta > 0) this.#data.push(delta); + } + + /** + * Returns the measurements of this performance measurement. + */ + get measurements() { + // Initialize the individual statistics + let average = 0; + let sum = BigInt(0); + let min = BigInt(Number.MAX_SAFE_INTEGER); + let max = BigInt(Number.MIN_SAFE_INTEGER); + + // Iterate over all of the measurements + for (const measurement of this.#data) { + // Do not consider measurements that are less than 0ns (invalid) + if (measurement >= 0) { + // Update the sum + sum += BigInt(measurement); + + // Update the min and max + if (measurement < min) min = measurement; + if (measurement > max) max = measurement; + } + } + + // Calculate the average + average = sum / BigInt(this.#data.length); + + // Return the statistics object + return { + min: min.toString(), + max: max.toString(), + sum: sum.toString(), + count: this.#data.length.toString(), + average: average.toString(), + }; + } +} + +module.exports = PerformanceMeasurement; diff --git a/packages/hyper-express/tests/scripts/MemoryStore.js b/packages/hyper-express/tests/scripts/MemoryStore.js new file mode 100644 index 0000000..7f79e8c --- /dev/null +++ b/packages/hyper-express/tests/scripts/MemoryStore.js @@ -0,0 +1,80 @@ +// Memory store with simulated functionalities similar to SQL databases +class MemoryStore { + #container = {}; + constructor() {} + + /** + * This method can be used to lookup/select specific keys from store + * + * @param {String} key + * @returns {Any} Any OR undefined + */ + select(key) { + return this.#container?.[key]?.data; + } + + /** + * + * @param {String} key + * @param {Object} data + * @param {Number} expiry_ts In Milliseconds + */ + insert(key, data, expiry_ts) { + // Throw on overwrites + if (this.#container[key]) + throw new Error('MemoryStore: key ' + key + ' already exists. Use update() method.'); + + this.#container[key] = { + data: data, + expiry: expiry_ts, + }; + } + + update(key, data, expiry_ts) { + // Throw on non existent source + if (this.#container[key] == undefined) + throw new Error( + 'MemoryStore: key ' + key + ' does not exist in store. Use insert() method.' + ); + + this.#container[key].data = data; + if (typeof expiry_ts == 'number') this.#container[key].expiry = expiry_ts; + } + + touch(key, expiry_ts) { + // Throw on non existent source + if (this.#container[key] == undefined) + throw new Error( + 'MemoryStore: cannot touch key ' + key + ' because it does not exist in store.' + ); + + this.#container[key].expiry = expiry_ts; + } + + delete(key) { + delete this.#container[key]; + } + + empty() { + this.#container = {}; + } + + cleanup() { + let removed = 0; + Object.keys(this.#container).forEach((key) => { + let data = this.#container[key]; + let expiry = data.expiry; + if (expiry < Date.now()) { + delete this.#container[key]; + removed++; + } + }); + return removed; + } + + get data() { + return this.#container; + } +} + +module.exports = MemoryStore; diff --git a/packages/hyper-express/tests/scripts/operators.js b/packages/hyper-express/tests/scripts/operators.js new file mode 100644 index 0000000..969c75b --- /dev/null +++ b/packages/hyper-express/tests/scripts/operators.js @@ -0,0 +1,82 @@ +const crypto = require('crypto'); +const HTTP = require('http'); + +function log(logger = 'SYSTEM', message) { + let dt = new Date(); + let timeStamp = dt.toLocaleString([], { hour12: true, timeZone: 'America/New_York' }).replace(', ', ' ').split(' '); + timeStamp[1] += ':' + dt.getMilliseconds().toString().padStart(3, '0') + 'ms'; + timeStamp = timeStamp.join(' '); + console.log(`[${timeStamp}][${logger}] ${message}`); +} + +function random_string(length = 7) { + var result = []; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result.push(characters.charAt(Math.floor(Math.random() * charactersLength))); + } + return result.join(''); +} + +async function assert_log(group, target, assertion) { + try { + let result = await assertion(); + if (result) { + log(group, 'Verified ' + target); + } else { + throw new Error('Failed To Verify ' + target + ' @ ' + group + ' -> ' + assertion.toString()); + } + } catch (error) { + console.log(error); + throw new Error('Failed To Verify ' + target + ' @ ' + group + ' -> ' + assertion.toString()); + } +} + +function async_for_each(items, handler, cursor = 0, final) { + if (final == undefined) return new Promise((resolve, reject) => async_for_each(items, handler, cursor, resolve)); + if (cursor < items.length) return handler(items[cursor], () => async_for_each(items, handler, cursor + 1, final)); + return final(); // Resolve master promise +} + +function http_post_headers({ host, port, path, method = 'GET', body, headers = {}, silence_errors = false }) { + return new Promise((resolve, reject) => { + const request = HTTP.request({ + host, + port, + path, + method, + headers, + }); + + if (body) request.write(body); + + request.on('response', (response) => + resolve({ + url: response.url, + status: response.statusCode, + headers: response.headers, + }) + ); + + if (!silence_errors) request.on('error', reject); + }); +} + +function async_wait(delay) { + return new Promise((resolve, reject) => setTimeout((res) => res(), delay, resolve)); +} + +function md5_from_buffer(buffer) { + return crypto.createHash('md5').update(buffer).digest('hex'); +} + +module.exports = { + log: log, + random_string: random_string, + assert_log: assert_log, + async_for_each: async_for_each, + http_post_headers: http_post_headers, + async_wait: async_wait, + md5_from_buffer: md5_from_buffer, +}; diff --git a/packages/hyper-express/tests/ssl/dummy-cert.pem b/packages/hyper-express/tests/ssl/dummy-cert.pem new file mode 100644 index 0000000..c90b84c --- /dev/null +++ b/packages/hyper-express/tests/ssl/dummy-cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIUHQd5vSaP28tenjjSWoPLQ31Kf7IwDQYJKoZIhvcNAQEL +BQAwgYYxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcM +CENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFu +eVNlY3Rpb25OYW1lMR0wGwYDVQQDDBRDb21tb25OYW1lT3JIb3N0bmFtZTAeFw0y +MzA1MTYyMzMyNTVaFw0zMzA1MTMyMzMyNTVaMIGGMQswCQYDVQQGEwJYWDESMBAG +A1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29t +cGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTEdMBsGA1UEAwwU +Q29tbW9uTmFtZU9ySG9zdG5hbWUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQC/48UKxs++PJD+MniBEF0yc2crUS2fPg1NA0j92sg0Vlh28maZBDtQMJw6 +DZDVb74paw8jLSZHdmTQEKsE0uvAl++gvN6pZulpbTWlNlLcvSG2FEYnOCb/Vxwu +Mfqx3ijANtrH4fx3eYB2TV1gZkXcmncO7a048WuBlpaVFDqvmJiObKeKA6XXnqC4 +ih19R4Yqq+bBzIdSYeS89n8GaOrO9Y7AbMtuMT+x1YtfUPsipbz4/76qqX4WC9Ph +qpLAHY71suQTT5l2N+eY3uuvqTRwxxda7Xdq/ABp1/kmStQwVTw/iRQPrCr1DxJy +8jm1Dyk3IlvBNv8oMCdrf+rfmljPIxA65LiRn/8oE+D32NbhDAo8zGzXS9BhBqtY +6Qj8HgSTiJhLw2ZzIfLWKRbfUO6dv3XsKx8vbF+kMqPZ1E+bACv3lCP6XJsihugM +Y3uVyyAFIAyRuczbRsLAtrZwbxYPQ/gvKWxNAg63Iwp/kluGwO6om+pp913z9YJW +Qr9pIO/ISayiKzXN7tqJ/BgTgLlD0fWexJOXkccfT9NjSV3DIq9m2McwN8GHspnh +lf8aZO4X48RGIS+vkH1oxgLJaZt0zmX43aZm83Aip4ykQyYg6ixDObrrpSG76UiT +H98s7+CAFFWO5usPIiHE5yQ9NTaBjHwMXm/3uLpEQ+ESKMCkwQIDAQABo1MwUTAd +BgNVHQ4EFgQULzXMAU/+wT/ukDydvZF6qhMv2kwwHwYDVR0jBBgwFoAULzXMAU/+ +wT/ukDydvZF6qhMv2kwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEAjcDsY7fFij4iiU0KpxecTJFg3nGGz4xFsln6IedUV9K2Gw5ZoAwyGc6BO29j ++bFTAbzbs+OHmExDyX1Y0t++Gj2MN6NP66yLvVaTJCQZHIshg6PTQx4u4EthwVb8 +k5NR0moSt2kk7rBWXlcWiZLAASruEFoZGlT1ofNMjGiRd1l1iQEm1+QQN95GTF8L +M8m/s36dHz1bsRa2BdBZnN9Qv0KvS43zigjW/d6+jJLePVJ+enwSn4fLCW+Nyx3i +7XSVqAUt/lW+h4jza3TF5jkvlRPOAc5+MJOemKoS1uGS+lPg0Z8jv+nPNkEmUcd8 +t+KKJAI/0E1nvEMpW3Rr6XVgIqlKSketddeDG+3t3m4ZOU14tiSpp6MTS4DGcPO/ +vhgWIFOi2ABeYhhzBrYs+jgpc1ogT6OHXBrhVNfLBXQ15II+cfQDBkhK526TC/EW +P9oYFX5RD5TTVMWYKfSDfL9nIPH7YtA0AeNx9iNb91V0mz2Ma5+/WVi7DAijdVFn +0n0+isn52QqNcUUrXvesY8Gm/QaClZipKk/Vq6DLdPOLZuZR2YcKh5OGRlxP8ouY +IrNf9XSm5yAm0qbbHQiQlTeg+pIu8UJkPpg20/vhqiLPygYOkD5YKrW0cwVZXFZq ++EzEmM77LOI6sp1aRq+GAVv5gfB4AqrfhxGAXK9TYnVQ7w4= +-----END CERTIFICATE----- diff --git a/packages/hyper-express/tests/ssl/dummy-key.pem b/packages/hyper-express/tests/ssl/dummy-key.pem new file mode 100644 index 0000000..c1f1057 --- /dev/null +++ b/packages/hyper-express/tests/ssl/dummy-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +qK9UJyw/XeyqX7BsMcKq5pQ1ywSq6yo= +-----END PRIVATE KEY----- diff --git a/packages/hyper-express/tests/types/Router.ts b/packages/hyper-express/tests/types/Router.ts new file mode 100644 index 0000000..5d8c2a8 --- /dev/null +++ b/packages/hyper-express/tests/types/Router.ts @@ -0,0 +1,127 @@ +// THIS FILE CAN BE TYPE CHECKED TO ENSURE THE TYPES ARE CORRECT + +import { Router, Request, Response, MiddlewareNext } from '../../types/index'; + +// Create a new router instance +const router = new Router(); + +// Pattern + Handler +router.any('/', async (request, response) => { + const body = await request.json(); +}); + +// Pattern + Options + Handler +router.all( + '/', + { + max_body_length: 250, + }, + async (request, response) => { + const body = await request.json(); + } +); + +const middleware = (request: Request, response: Response, next: MiddlewareNext) => {}; + +// Pattern + 2 Middlewares + Handler +router.connect( + '/', + middleware, + async (request, repsonse, next) => { + await request.text(); + next(); + }, + async (request, response) => { + const body = await request.json(); + } +); + +// Pattern + options + 4 Middlewares + Handler +router.post( + '/', + { + max_body_length: 250, + }, + middleware, + middleware, + middleware, + async (request, repsonse, next) => { + await request.text(); + next(); + }, + async (request, response) => { + const body = await request.json(); + } +); + +// Pattern + 4 Middlewares (Array) + Handler +router.put( + '/', + [ + middleware, + middleware, + middleware, + async (request, repsonse, next) => { + await request.text(); + next(); + }, + ], + async (request, response) => { + const body = await request.json(); + } +); + +// Pattern + options + 4 Middlewares (Array) + Handler +router.delete( + '/', + { + max_body_length: 250, + }, + [ + middleware, + middleware, + middleware, + async (request, repsonse, next) => { + await request.text(); + next(); + }, + ], + async (request, response) => { + const body = await request.json(); + } +); + +// Handler +router + .route('/api/v1') + .get(async (request, response) => { + const body = await request.json(); + }) + .post( + { + max_body_length: 250, + }, + async (request, response, next) => { + const body = await request.json(); + }, + async (request, response) => { + const body = await request.json(); + } + ) + .delete( + { + max_body_length: 250, + }, + middleware, + [middleware, middleware], + async (request, response) => { + const body = await request.json(); + } + ); + +// Ensures router usage is valid in all possible forms +router.use(router); +router.use('/something', router); +router.use('/something', middleware); +router.use(middleware, middleware, middleware); +router.use('else', middleware, [middleware, middleware, middleware], middleware); diff --git a/packages/hyper-express/tsconfig.json b/packages/hyper-express/tsconfig.json new file mode 100644 index 0000000..e80a450 --- /dev/null +++ b/packages/hyper-express/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2021", + "moduleResolution": "node", + "typeRoots": [ + "./types/*.d.ts", + "./node_modules/@types" + ], + "types": [ + "node", + "express" + ] + }, + "exclude": [ + "./src" + ] +} \ No newline at end of file diff --git a/packages/hyper-express/types/components/Server.d.ts b/packages/hyper-express/types/components/Server.d.ts new file mode 100644 index 0000000..82141f0 --- /dev/null +++ b/packages/hyper-express/types/components/Server.d.ts @@ -0,0 +1,145 @@ +import { ReadableOptions, WritableOptions } from 'stream'; +import * as uWebsockets from 'uWebSockets.js'; +import { SendableData } from './http/Response'; +import { Request } from './http/Request'; +import { Response } from './http/Response'; +import { Router } from './router/Router'; +import { HostManager } from './plugins/HostManager'; + +export interface ServerConstructorOptions { + key_file_name?: string; + cert_file_name?: string; + passphrase?: string; + dh_params_file_name?: string; + ssl_prefer_low_memory_usage?: boolean; + auto_close?: boolean; + fast_buffers?: boolean; + fast_abort?: boolean; + trust_proxy?: boolean; + max_body_buffer?: number; + max_body_length?: number; + streaming?: { + readable?: ReadableOptions; + writable?: WritableOptions; + }; +} + +export type GlobalErrorHandler = (request: Request, response: Response, error: Error) => void; +export type GlobalNotFoundHandler = (request: Request, response: Response) => void; + +export class Server extends Router { + constructor(options?: ServerConstructorOptions); + + /** + * This object can be used to store properties/references local to this Server instance. + */ + locals: Object; + + /* Server Methods */ + + /** + * Starts HyperExpress webserver on specified port and host. + * + * @param {Number} port + * @param {String=} host Optional. Default: 0.0.0.0 + * @param {Function=} callback Optional. Callback to be called when the server is listening. Default: "0.0.0.0" + * @returns {Promise} Promise + */ + listen( + port: number, + callback?: (listen_socket: uWebsockets.us_listen_socket) => void + ): Promise; + listen( + port: number, + host?: string, + callback?: (listen_socket: uWebsockets.us_listen_socket) => void + ): Promise; + listen( + unix_path: string, + callback?: (listen_socket: uWebsockets.us_listen_socket) => void + ): Promise; + + /** + * Performs a graceful shutdown of the server and closes the listen socket once all pending requests have been completed. + * + * @param {uWebSockets.us_listen_socket=} [listen_socket] Optional + * @returns {Promise} + */ + shutdown(listen_socket?: uWebsockets.us_listen_socket): Promise; + + /** + * Stops/Closes HyperExpress webserver instance. + * + * @param {uWebSockets.us_listen_socket=} [listen_socket] Optional + * @returns {Boolean} + */ + close(listen_socket?: uWebsockets.us_listen_socket): boolean; + + /** + * Sets a global error handler which will catch most uncaught errors across all routes/middlewares. + * + * @param {GlobalErrorHandler} handler + */ + set_error_handler(handler: GlobalErrorHandler): void; + + /** + * Sets a global not found handler which will handle all requests that are unhandled by any registered route. + * Note! This handler must be registered after all routes and routers. + * + * @param {GlobalNotFoundHandler} handler + */ + set_not_found_handler(handler: GlobalNotFoundHandler): void; + + /** + * Publish a message to a topic in MQTT syntax to all WebSocket connections on this Server instance. + * You cannot publish using wildcards, only fully specified topics. + * + * @param {String} topic + * @param {String|Buffer|ArrayBuffer} message + * @param {Boolean=} is_binary + * @param {Boolean=} compress + * @returns {Boolean} + */ + publish(topic: string, message: SendableData, is_binary?: boolean, compress?: boolean): boolean; + + /** + * Returns the number of subscribers to a topic across all WebSocket connections on this Server instance. + * + * @param {String} topic + * @returns {Number} + */ + num_of_subscribers(topic: string): number; + + /* Server Properties */ + + /** + * Returns the local server listening port of the server instance. + * @returns {Number} + */ + get port(): number; + + /** + * Returns the server's internal uWS listening socket. + * @returns {uWebSockets.us_listen_socket=} + */ + get socket(): uWebsockets.us_listen_socket | null; + + /** + * Underlying uWS instance. + * @returns {uWebSockets.TemplatedApp} + */ + get uws_instance(): uWebsockets.TemplatedApp; + + /** + * Server instance global handlers. + * @returns {Object} + */ + get handlers(): Object; + + /** + * Returns the Server Hostnames manager for this instance. + * Use this to support multiple hostnames on the same server with different SSL configurations. + * @returns {HostManager} + */ + get hosts(): HostManager; +} diff --git a/packages/hyper-express/types/components/http/Request.d.ts b/packages/hyper-express/types/components/http/Request.d.ts new file mode 100644 index 0000000..6fbd4e4 --- /dev/null +++ b/packages/hyper-express/types/components/http/Request.d.ts @@ -0,0 +1,252 @@ +import { Readable } from 'stream'; +import { Server } from '../Server'; +import { BusboyConfig } from 'busboy'; +import { HttpRequest } from 'uWebSockets.js'; +import { Options, Ranges, Result } from 'range-parser'; +import { MultipartHandler } from '../plugins/MultipartField'; +import { UploadedFile } from '../../shared/uploaded-file'; + +type default_value = any; + +interface ParamsDictionary { + [key: string]: string; +} + +interface ParsedQs { + [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[]; +} + +type DefaultRequestLocals = { + [key: string]: any; +}; + +export class Request extends Readable { + /** + * Underlying raw lazy initialized readable body stream. + */ + _readable: null | Readable; + + /** + * Returns whether all expected incoming request body chunks have been received. + * @returns {Boolean} + */ + received: boolean; + + /* HyperExpress Methods */ + + /** + * Returns the raw uWS.HttpRequest instance. + * Note! This property is unsafe and should not be used unless you have no asynchronous code or you are accessing from the first top level synchronous middleware before any asynchronous code. + * @returns {import('uWebSockets.js').HttpRequest} + */ + get raw(): HttpRequest; + + /** + * Securely signs a value with provided secret and returns the signed value. + * + * @param {String} string + * @param {String} secret + * @returns {String} String OR undefined + */ + sign(string: string, secret: string): string | void; + + /** + * Securely unsigns a value with provided secret and returns its original value upon successful verification. + * + * @param {String} signed_value + * @param {String} secret + * @returns {String=} String OR undefined + */ + unsign(signed_value: string, secret: string): string | void; + + /** + * Downloads and returns request body as a Buffer. + * @returns {Promise} + */ + buffer(): Promise; + + /** + * Downloads and parses the request body as a String. + * @returns {Promise} + */ + text(): Promise; + + /** + * Downloads and parses the request body as a JSON object. + * Passing default_value as undefined will lead to the function throwing an exception if invalid JSON is received. + * + * @param {Any} default_value Default: {} + * @returns {Promise} + */ + json(default_value?: D): Promise; + + /** + * Parses and resolves an Object of urlencoded values from body. + * @returns {Promise} + */ + urlencoded(): Promise; + + /** + * Parses incoming multipart form and allows for easy consumption of fields/values including files. + * + * @param {MultipartHandler} handler + * @returns {Promise} A promise which is resolved once all multipart fields have been processed + */ + multipart(handler: MultipartHandler): Promise; + + /** + * Parses incoming multipart form and allows for easy consumption of fields/values including files. + * + * @param {BusboyConfig} options + * @param {MultipartHandler} handler + * @returns {Promise} A promise which is resolved once all multipart fields have been processed + */ + multipart(options: BusboyConfig, handler: MultipartHandler): Promise; + + /* HyperExpress Properties */ + + /** + * Returns the HyperExpress.Server instance this Request object originated from. + * @returns {Server} + */ + get app(): Server; + + /** + * Returns whether this request is in a paused state and thus not consuming any body chunks. + * @returns {Boolean} + */ + get paused(): boolean; + + /** + * Returns HTTP request method for incoming request in all uppercase. + * @returns {String} + */ + get method(): string; + + /** + * Returns full request url for incoming request (path + query). + * @returns {String} + */ + get url(): string; + + /** + * Returns path for incoming request. + * @returns {String} + */ + get path(): string; + + /** + * Returns query for incoming request without the '?'. + * @returns {String} + */ + get path_query(): string; + + /** + * Returns request headers from incoming request. + * @returns {Object.} + */ + get headers(): { [key: string]: string }; + + /** + * Returns request cookies from incoming request. + * @returns {Object.} + */ + get cookies(): { [key: string]: string }; + + /** + * Returns path parameters from incoming request. + * @returns {Object.} + */ + get path_parameters(): { [key: string]: string }; + + /** + * Returns query parameters from incoming request. + * @returns {Object.} + */ + get query_parameters(): { [key: string]: string }; + + /** + * Returns remote IP address in string format from incoming request. + * @returns {String} + */ + get ip(): string; + + /** + * Returns remote proxy IP address in string format from incoming request. + * @returns {String} + */ + get proxy_ip(): string; + + /* ExpressJS Methods */ + get(name: 'set-cookie'): string[]; + get(name: string): string; + header(name: 'set-cookie'): string[]; + header(name: string): string; + accepts(): string[]; + accepts(type: string): string | false; + accepts(type: string[]): string | false; + accepts(...type: string[]): string | false; + acceptsCharsets(): string[]; + acceptsCharsets(charset: string): string | false; + acceptsCharsets(charset: string[]): string | false; + acceptsCharsets(...charset: string[]): string | false; + acceptsEncodings(): string[]; + acceptsEncodings(encoding: string): string | false; + acceptsEncodings(encoding: string[]): string | false; + acceptsEncodings(...encoding: string[]): string | false; + acceptsLanguages(): string[]; + acceptsLanguages(lang: string): string | false; + acceptsLanguages(lang: string[]): string | false; + acceptsLanguages(...lang: string[]): string | false; + range(size: number, options?: Options): Ranges | Result; + param(name: string, defaultValue?: any): string; + is(type: string | string[]): string | false; + + /* ExpressJS Properties */ + locals: Locals; + protocol: string; + secure: boolean; + ips: string[]; + subdomains: string[]; + hostname: string; + fresh: boolean; + stale: boolean; + xhr: boolean; + body: any; + params: ParamsDictionary; + query: ParsedQs; + originalUrl: string; + baseUrl: string; + + $dto: any; + $user: any; + // uploadedFiles: Map; + setDto(dto: any): void; + dto(): any; + + processBody(): Promise; + all(): Promise>; + hasHeader(name: string): boolean; + bearerToken(): string; + httpHost(): string; + isHttp(): boolean; + isHttps(): boolean; + fullUrl(): string; + isMethod(method: string): boolean; + contentType(): string; + getAcceptableContentTypes(): string; + // accepts(): string[]; + expectsJson(): boolean; + setUser(user: any): void; + user(): T; + isPath(pathPattern: string): boolean; + hasHeaders(...keys: string[]): boolean; + hasIncludes(): boolean; + includes(): string; + + setValidator(cls: any): void; + validate(schema: any): Promise; + + // files(keys: string): Record; + file(key: string): Promise; +} diff --git a/packages/hyper-express/types/components/http/Response.d.ts b/packages/hyper-express/types/components/http/Response.d.ts new file mode 100644 index 0000000..94c90d4 --- /dev/null +++ b/packages/hyper-express/types/components/http/Response.d.ts @@ -0,0 +1,275 @@ +import { Readable, Writable } from 'stream'; +import * as uWebsockets from 'uWebSockets.js'; +import { LiveFile } from '../plugins/LiveFile'; +import { Server } from '../Server'; + +export type SendableData = string | Buffer | ArrayBuffer; +export type FileCachePool = { + [key: string]: LiveFile; +}; + +export interface CookieOptions { + domain?: string; + path?: string; + maxAge?: number; + secure?: boolean; + httpOnly?: boolean; + sameSite?: boolean | 'none' | 'lax' | 'strict'; + secret?: string; +} + +type DefaultResponseLocals = { + [key: string]: any; +}; + +export class Response extends Writable { + /** + * Underlying raw lazy initialized writable stream. + */ + _writable: null | Writable; + + /** + * Alias of aborted property as they both represent the same request state in terms of inaccessibility. + */ + completed: boolean; + + /* HyperExpress Methods */ + + /** + * Alias of `uWS.HttpResponse.cork()` which allows for manual corking of the response. + * This is required by `uWebsockets.js` to maximize network performance with batched writes. + * + * @param {Function} handler + * @returns {Response} Response (Chainable) + */ + atomic(handler: () => void): Response; + + /** + * This method is used to set a custom response code. + * + * @param {Number} code Example: response.status(403) + * @param {String=} message Example: response.status(403, 'Forbidden') + * @returns {Response} Response (Chainable) + */ + status(code: number, message?: string): Response; + + /** + * This method is used to set the response content type header + * based on the provided mime type. Example: type('json') + * + * @param {String} mime_type Mime type + * @returns {Response} Response (Chainable) + */ + type(mime_type: string): Response; + + /** + * This method can be used to write a response header and supports chaining. + * + * @param {String} name Header Name + * @param {String|Array} value Header Value + * @param {Boolean=} overwrite If true, overwrites existing header value with same name + * @returns {Response} Response (Chainable) + */ + header(name: string, value: string | Array, overwrite?: boolean): Response; + + /** + * This method is used to write a cookie to incoming request. + * To delete a cookie, set the value to null. + * + * @param {String} name Cookie Name + * @param {String|null} value Cookie Value + * @param {Number=} expiry In milliseconds + * @param {CookieOptions=} options Cookie Options + * @param {Boolean=} sign_cookie Enables/Disables Cookie Signing + * @returns {Response} Response (Chainable) + */ + cookie( + name: string, + value: string | null, + expiry?: number, + options?: CookieOptions, + sign_cookie?: boolean + ): Response; + + /** + * This method is used to upgrade an incoming upgrade HTTP request to a Websocket connection. + * @param {Object=} context Store information about the websocket connection + */ + upgrade(context?: Object): void; + + /** + * Binds a drain handler which gets called with a byte offset that can be used to try a failed chunk write. + * You MUST perform a write call inside the handler for uWS chunking to work properly. + * You MUST return a boolean value indicating if the write was successful or not. + * + * @param {function(number):boolean} handler Synchronous callback only + */ + drain(handler: (offset: number) => boolean): void; + + /** + * This method is used to end the current request and send response with specified body and headers. + * + * @param {String|Buffer|ArrayBuffer} body Optional + * @returns {Boolean} 'false' signifies that the result was not sent due to built up backpressure. + */ + send(body?: SendableData, close_connection?: boolean): Response; + + /** + * This method is used to pipe a readable stream as response body and send response. + * By default, this method will use chunked encoding transfer to stream data. + * If your use-case requires a content-length header, you must specify the total payload size. + * + * @param {Readable} readable A Readable stream which will be piped as response body + * @param {Number=} total_size Total size of the Readable stream source in bytes (Optional) + */ + stream(readable: Readable, total_size?: number): Promise; + + /** + * Instantly aborts/closes current request without writing a status response code. + * Use this only in extreme situations to abort a request where a proper response is not neccessary. + */ + close(): void; + + /** + * This method is used to redirect an incoming request to a different url. + * + * @param {String} url Redirect URL + * @returns {Boolean} + */ + redirect(url: string): boolean; + + /** + * This method is an alias of send() method except it accepts an object and automatically stringifies the passed payload object. + * + * @param {Object} body JSON body + * @returns {Boolean} Boolean + */ + json(body: any): boolean; + + /** + * This method is an alias of send() method except it accepts an object + * and automatically stringifies the passed payload object with a callback name. + * Note! This method uses 'callback' query parameter by default but you can specify 'name' to use something else. + * + * @param {Object} body + * @param {String=} name + * @returns {Boolean} Boolean + */ + jsonp(body: any, name?: string): boolean; + + /** + * This method is an alias of send() method except it automatically sets + * html as the response content type and sends provided html response body. + * + * @param {String} body + * @returns {Boolean} Boolean + */ + html(body: string): boolean; + + /** + * This method is an alias of send() method except it sends the file at specified path. + * This method automatically writes the appropriate content-type header if one has not been specified yet. + * This method also maintains its own cache pool in memory allowing for fast performance. + * Avoid using this method to a send a large file as it will be kept in memory. + * + * @param {String} path + * @param {function(Object):void=} callback Executed after file has been served with the parameter being the cache pool. + */ + file(path: string, callback?: (pool: FileCachePool) => void): void; + + /** + * Writes approriate headers to signify that file at path has been attached. + * + * @param {String} path + * @param {String=} name + * @returns {Response} Chainable + */ + attachment(path: string, name?: string): Response; + + /** + * Writes appropriate attachment headers and sends file content for download on user browser. + * This method combined Response.attachment() and Response.file() under the hood, so be sure to follow the same guidelines for usage. + * + * @param {String} path + * @param {String=} filename + */ + download(path: string, filename?: string): void; + + /** + * This method allows you to throw an error which will be caught by the global error handler (If one was setup with the Server instance). + * + * @param {Error} error + */ + throw(error: Error): Response; + + /* HyperExpress Properties */ + + /** + * Returns the underlying raw uWS.Response object. + * @returns {uWebsockets.Response} + */ + get raw(): uWebsockets.HttpResponse; + + /** + * Returns the HyperExpress.Server instance this Response object originated from. + * + * @returns {Server} + */ + get app(): Server; + + /** + * Returns whether response has been initiated by writing the HTTP status code and headers. + * Note! No changes can be made to the HTTP status code or headers after a response has been initiated. + * @returns {Boolean} + */ + get initiated(): boolean; + + /** + * Returns current state of request in regards to whether the source is still connected. + * @returns {Boolean} + */ + get aborted(): boolean; + + /** + * Returns the current response body content write offset in bytes. + * Use in conjunction with the drain() offset handler to retry writing failed chunks. + * @returns {Number} + */ + get write_offset(): number; + + /** + * Upgrade socket context for upgrade requests. + * @returns {uWebsockets.ux_socket_context} + */ + get upgrade_socket(): uWebsockets.us_socket_context_t; + + /* ExpressJS Methods */ + append(name: string, values: string | Array): Response; + writeHead(name: string, values: string | Array): Response; + setHeader(name: string, values: string | Array): Response; + writeHeaders(headers: Object): void; + setHeaders(headers: Object): void; + writeHeaderValues(name: string, values: Array): void; + getHeader(name: string): string | Array | void; + getHeaders(): { [key: string]: Array }; + removeHeader(name: string): void; + setCookie(name: string, value: string, options?: CookieOptions): Response; + hasCookie(name: string): Boolean; + removeCookie(name: string): Response; + clearCookie(name: string): Response; + get(name: string): string | Array; + links(links: Object): string; + location(path: string): Response; + sendFile(path: string): void; + sendStatus(status_code: number): Response; + set(field: string | object, value?: string | Array): Response | void; + vary(name: string): Response; + + /* ExpressJS Properties */ + get headersSent(): boolean; + get statusCode(): number | undefined; + set statusCode(value: number | undefined); + get statusMessage(): string | undefined; + set statusMessage(value: string | undefined); + locals: Locals; +} diff --git a/packages/hyper-express/types/components/middleware/MiddlewareHandler.d.ts b/packages/hyper-express/types/components/middleware/MiddlewareHandler.d.ts new file mode 100644 index 0000000..d9ee96b --- /dev/null +++ b/packages/hyper-express/types/components/middleware/MiddlewareHandler.d.ts @@ -0,0 +1,10 @@ +import { MiddlewareNext } from './MiddlewareNext'; +import { Request, DefaultRequestLocals } from '../http/Request'; +import { Response, DefaultResponseLocals } from '../http/Response'; + +export type MiddlewarePromise = Promise; +export type MiddlewareHandler = ( + request: Request, + response: Response, + next: MiddlewareNext +) => MiddlewarePromise | any; diff --git a/packages/hyper-express/types/components/middleware/MiddlewareNext.d.ts b/packages/hyper-express/types/components/middleware/MiddlewareNext.d.ts new file mode 100644 index 0000000..a1b13cb --- /dev/null +++ b/packages/hyper-express/types/components/middleware/MiddlewareNext.d.ts @@ -0,0 +1 @@ +export type MiddlewareNext = (error?: Error) => void; \ No newline at end of file diff --git a/packages/hyper-express/types/components/plugins/HostManager.d.ts b/packages/hyper-express/types/components/plugins/HostManager.d.ts new file mode 100644 index 0000000..111904e --- /dev/null +++ b/packages/hyper-express/types/components/plugins/HostManager.d.ts @@ -0,0 +1,36 @@ +import { EventEmitter } from 'events'; + +interface HostOptions { + passphrase?: string, + cert_file_name?: string, + key_file_name?: string, + dh_params_file_name?: string, + ssl_prefer_low_memory_usage?: boolean, +} + +export class HostManager extends EventEmitter { + /** + * Registers the unique host options to use for the specified hostname for incoming requests. + * + * @param {String} hostname + * @param {HostOptions} options + * @returns {HostManager} + */ + add(hostname: string, options: HostOptions): HostManager; + + /** + * Un-Registers the unique host options to use for the specified hostname for incoming requests. + * + * @param {String} hostname + * @returns {HostManager} + */ + remove(hostname: string): HostManager; + + /* HostManager Getters & Properties */ + + /** + * Returns all of the registered hostname options. + * @returns {Object.} + */ + get registered(): {[hostname: string]: HostOptions}; +} \ No newline at end of file diff --git a/packages/hyper-express/types/components/plugins/LiveFile.d.ts b/packages/hyper-express/types/components/plugins/LiveFile.d.ts new file mode 100644 index 0000000..bd7b70a --- /dev/null +++ b/packages/hyper-express/types/components/plugins/LiveFile.d.ts @@ -0,0 +1,48 @@ +import * as FileSystem from 'fs'; +import { EventEmitter} from 'events'; + +export interface LiveFileOptions { + path: string, + retry: { + every: number, + max: number + } +} + +export class LiveFile extends EventEmitter { + constructor(options: LiveFileOptions) + + /** + * Reloads buffer/content for file asynchronously with retry policy. + * + * @private + * @param {Boolean} fresh + * @param {Number} count + * @returns {Promise} + */ + reload(fresh: boolean, count: number): Promise; + + /** + * Returns a promise which resolves once first reload is complete. + * + * @returns {Promise} + */ + ready(): Promise + + /* LiveFile Getters */ + get is_ready(): boolean; + + get name(): string; + + get path(): string; + + get extension(): string; + + get content(): string; + + get buffer(): Buffer; + + get last_update(): number; + + get watcher(): FileSystem.FSWatcher; +} diff --git a/packages/hyper-express/types/components/plugins/MultipartField.d.ts b/packages/hyper-express/types/components/plugins/MultipartField.d.ts new file mode 100644 index 0000000..db3db28 --- /dev/null +++ b/packages/hyper-express/types/components/plugins/MultipartField.d.ts @@ -0,0 +1,73 @@ +import { Readable, WritableOptions } from 'stream'; + +export type MultipartFile = { + name?: string, + stream: Readable +} + +export type Truncations = { + name: boolean, + value: boolean +} + +export class MultipartField { + /* MultipartField Methods */ + + /** + * Saves this multipart file content to the specified path. + * Note! You must specify the file name and extension in the path itself. + * + * @param {String} path Path with file name to which you would like to save this file. + * @param {WritableOptions} options Writable stream options + * @returns {Promise} + */ + write(path: string, options?: WritableOptions): Promise; + + /* MultipartField Properties */ + + /** + * Field name as specified in the multipart form. + * @returns {String} + */ + get name(): string; + + /** + * Field encoding as specified in the multipart form. + * @returns {String} + */ + get encoding(): string; + + /** + * Field mime type as specified in the multipart form. + * @returns {String} + */ + get mime_type(): string; + + /** + * Returns file information about this field if it is a file type. + * Note! This property will ONLY be defined if this field is a file type. + * + * @returns {MultipartFile} + */ + get file(): MultipartFile | void; + + /** + * Returns field value if this field is a non-file type. + * Note! This property will ONLY be defined if this field is a non-file type. + * + * @returns {String} + */ + get value(): string | void; + + /** + * Returns information about truncations in this field. + * Note! This property will ONLY be defined if this field is a non-file type. + * + * @returns {Truncations} + */ + get truncated(): Truncations | void; +} + +export type MultipartHandler = (field: MultipartField) => void | Promise; + +export type MultipartLimitReject = "PARTS_LIMIT_REACHED" | "FILES_LIMIT_REACHED" | "FIELDS_LIMIT_REACHED"; \ No newline at end of file diff --git a/packages/hyper-express/types/components/plugins/SSEventStream.d.ts b/packages/hyper-express/types/components/plugins/SSEventStream.d.ts new file mode 100644 index 0000000..a6ff6db --- /dev/null +++ b/packages/hyper-express/types/components/plugins/SSEventStream.d.ts @@ -0,0 +1,50 @@ +import { Response } from "../http/Response"; + +export class SSEventStream { + constructor(response: Response) + + /** + * Opens the "Server-Sent Events" connection to the client. + * + * @returns {Boolean} + */ + open(): boolean; + + /** + * Closes the "Server-Sent Events" connection to the client. + * + * @returns {Boolean} + */ + close(): boolean; + + /** + * Sends a comment-type message to the client that will not be emitted by EventSource. + * This can be useful as a keep-alive mechanism if messages might not be sent regularly. + * + * @param {String} data + * @returns {Boolean} + */ + comment(data: string): boolean; + + /** + * Sends a message to the client based on the specified event and data. + * Note! You must retry failed messages if you receive a false output from this method. + * + * @param {String} id + * @param {String=} event + * @param {String=} data + * @returns {Boolean} + */ + send(data: string): boolean; + send(event: string, data: string): boolean; + send(id: string, event: string, data: string): boolean; + + /* SSEventStream properties */ + + /** + * Whether this Server-Sent Events stream is still active. + * + * @returns {Boolean} + */ + get active(): boolean; +} \ No newline at end of file diff --git a/packages/hyper-express/types/components/router/Route.d.ts b/packages/hyper-express/types/components/router/Route.d.ts new file mode 100644 index 0000000..10a64f4 --- /dev/null +++ b/packages/hyper-express/types/components/router/Route.d.ts @@ -0,0 +1 @@ +// Since this a private component no types have been defined \ No newline at end of file diff --git a/packages/hyper-express/types/components/router/Router.d.ts b/packages/hyper-express/types/components/router/Router.d.ts new file mode 100644 index 0000000..ed02b08 --- /dev/null +++ b/packages/hyper-express/types/components/router/Router.d.ts @@ -0,0 +1,188 @@ +import { ReadableOptions } from 'stream'; +import { Request } from '../http/Request'; +import { Response } from '../http/Response'; +import { Websocket } from '../ws/Websocket'; +import { CompressOptions } from 'uWebSockets.js'; +import { MiddlewareHandler } from '../middleware/MiddlewareHandler'; + +// Define types for HTTP Route Creators +export type UserRouteHandler = (request: Request, response: Response) => void; +export interface UserRouteOptions { + middlewares?: Array; + stream_options?: ReadableOptions; + max_body_length?: number; +} + +// Define types for Websocket Route Creator +export type WSRouteHandler = (websocket: Websocket) => void; +export interface WSRouteOptions { + message_type?: 'String' | 'Buffer' | 'ArrayBuffer'; + compression?: CompressOptions; + idle_timeout?: number; + max_backpressure?: number; + max_payload_length?: number; +} + +// Define types for internal route/middleware records +export interface RouteRecord { + method: string; + pattern: string; + options: UserRouteOptions | WSRouteOptions; + handler: UserRouteHandler; +} + +// Defines the type for internal middleware records +export interface MiddlewareRecord { + pattern: string; + middleware: MiddlewareHandler; +} + +type UsableSpreadableArguments = (string | Router | MiddlewareHandler | MiddlewareHandler[])[]; +type RouteSpreadableArguments = ( + | string + | UserRouteOptions + // | UserRouteHandler - Temporarily disabled because Typescript cannot do "UserRouteHandler | MiddlewareHandler" due to the next parameter confusing it + | MiddlewareHandler + | MiddlewareHandler[] +)[]; + +export class Router { + constructor(); + + /** + * Returns a chainable Router instance which can be used to bind multiple method routes or middlewares on the same path easily. + * Example: `Router.route('/api/v1').get(getHandler).post(postHandler).delete(destroyHandler)` + * Example: `Router.route('/api/v1').use(middleware).user(middleware2)` + * @param {String} pattern + * @returns {Router} A chainable Router instance with a context pattern set to this router's pattern. + */ + route(pattern: string): this; + + /** + * Registers middlewares and router instances on the specified pattern if specified. + * If no pattern is specified, the middleware/router instance will be mounted on the '/' root path by default of this instance. + * + * @param {...(String|MiddlewareHandler|Router)} args (request, response, next) => {} OR (request, response) => new Promise((resolve, reject) => {}) + */ + use(...args: UsableSpreadableArguments): this; + + /** + * Creates an HTTP route that handles any HTTP method requests. + * Note! ANY routes do not support route specific middlewares. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + any(...args: RouteSpreadableArguments): this; + + /** + * Alias of any() method. + * Creates an HTTP route that handles any HTTP method requests. + * Note! ANY routes do not support route specific middlewares. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + all(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles GET method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + get(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles POST method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + post(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles PUT method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + put(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles DELETE method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + delete(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles HEAD method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + head(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles OPTIONS method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + options(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles PATCH method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + patch(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles TRACE method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + trace(...args: RouteSpreadableArguments): this; + + /** + * Creates an HTTP route that handles CONNECT method requests. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + connect(...args: RouteSpreadableArguments): this; + + /** + * Intercepts and handles upgrade requests for incoming websocket connections. + * Note! You must call response.upgrade(data) at some point in this route to open a websocket connection. + * + * @param {String} pattern + * @param {...(RouteOptions|MiddlewareHandler)} args + */ + upgrade(...args: RouteSpreadableArguments): this; + + /** + * @param {String} pattern + * @param {WSRouteOptions|WSRouteHandler} options + * @param {WSRouteHandler} handler + */ + ws(pattern: string, handler: WSRouteHandler): this; + ws(pattern: string, options: WSRouteOptions, handler: WSRouteHandler): this; + + /** + * Returns All routes in this router in the order they were registered. + * @returns {Array} + */ + get routes(): Array; + + /** + * Returns all middlewares in this router in the order they were registered. + * @returns {Array} + */ + get middlewares(): Array; +} diff --git a/packages/hyper-express/types/components/ws/Websocket.d.ts b/packages/hyper-express/types/components/ws/Websocket.d.ts new file mode 100644 index 0000000..dfe3fea --- /dev/null +++ b/packages/hyper-express/types/components/ws/Websocket.d.ts @@ -0,0 +1,157 @@ +import * as uWebsockets from 'uWebSockets.js'; +import { EventEmitter } from "events"; +import { Readable, Writable } from 'stream'; +import TypedEmitter from 'typed-emitter'; +import { SendableData } from "../http/Response"; + +export type WebsocketContext = { + [key: string]: string +} + +type Events = { + message: (...args: any[]) => void | Promise; + close: (...args: any[]) => void | Promise; + drain: (...args: any[]) => void | Promise; + ping: (...args: any[]) => void | Promise; + pong: (...args: any[]) => void | Promise; +} + +export class Websocket extends (EventEmitter as new () => TypedEmitter) { + /** + * Alias of uWS.cork() method. Accepts a callback with multiple operations for network efficiency. + * + * @param {Function} callback + * @returns {Websocket} + */ + atomic(callback: () => void): Websocket; + + /** + * Sends a message to websocket connection. + * Returns true if message was sent successfully. + * Returns false if message was not sent due to buil up backpressure. + * + * @param {String|Buffer|ArrayBuffer} message + * @param {Boolean=} is_binary + * @param {Boolean=} compress + * @returns {Boolean} + */ + send(message: SendableData, is_binary?: boolean, compress?: boolean): boolean; + + /** + * Sends a ping control message. + * Returns Boolean depending on backpressure similar to send(). + * + * @param {String|Buffer|ArrayBuffer=} message + * @returns {Boolean} + */ + ping(message?: SendableData): void; + + /** + * Gracefully closes websocket connection by sending specified code and short message. + * + * @param {Number=} code + * @param {(String|Buffer|ArrayBuffer)=} message + */ + close(code?: number, message?: SendableData): void; + + /** + * Forcefully closes websocket connection. + * No websocket close code/message is sent. + * This will immediately emit the 'close' event. + */ + destroy(): void; + + /** + * Returns whether this `Websocket` is subscribed to the specified topic. + * + * @param {String} topic + * @returns {Boolean} + */ + is_subscribed(topic: string): boolean; + + /** + * Subscribe to a topic in MQTT syntax. + * MQTT syntax includes things like "root/child/+/grandchild" where "+" is a wildcard and "root/#" where "#" is a terminating wildcard. + * + * @param {String} topic + * @returns {Boolean} + */ + subscribe(topic: string): boolean; + + /** + * Unsubscribe from a topic. + * Returns true on success, if the WebSocket was subscribed. + * + * @param {String} topic + * @returns {Boolean} + */ + unsubscribe(topic: string): boolean; + + /** + * Publish a message to a topic in MQTT syntax. + * You cannot publish using wildcards, only fully specified topics. + * + * @param {String} topic + * @param {String|Buffer|ArrayBuffer} message + * @param {Boolean=} is_binary + * @param {Boolean=} compress + */ + publish(topic: string, message: SendableData, is_binary?: boolean, compress?: boolean): boolean; + + /** + * This method is used to stream a message to the receiver. + * Note! The data is streamed as binary by default due to how partial fragments are sent. + * This is done to prevent processing errors depending on client's receiver's incoming fragment processing strategy. + * + * @param {Readable} readable A Readable stream which will be consumed as message + * @param {Boolean=} is_binary Whether data being streamed is in binary. Default: true + * @returns {Promise} + */ + stream(readable: Readable, is_binary?: boolean): Promise; + + /* Websocket Properties */ + + /** + * Underlying uWS.Websocket object + */ + get raw(): uWebsockets.WebSocket; + + /** + * Returns IP address of this websocket connection. + * @returns {String} + */ + get ip(): string; + + /** + * Returns context values from the response.update(context) connection upgrade call. + * @returns {Object} + */ + get context(): WebsocketContext; + + /** + * Returns whether is websocket connection is closed. + * @returns {Boolean} + */ + get closed(): boolean; + + /** + * Returns the bytes buffered in backpressure. + * This is similar to the bufferedAmount property in the browser counterpart. + * @returns {Number} + */ + get buffered(): number; + + /** + * Returns a list of topics this websocket is subscribed to. + * @returns {Array.} + */ + get topics(): Array; + + /** + * Returns a Writable stream associated with this response to be used for piping streams. + * Note! You can only retrieve/use only one writable at any given time. + * + * @returns {Writable} + */ + get writable(): Writable; +} diff --git a/packages/hyper-express/types/components/ws/WebsocketRoute.d.ts b/packages/hyper-express/types/components/ws/WebsocketRoute.d.ts new file mode 100644 index 0000000..10a64f4 --- /dev/null +++ b/packages/hyper-express/types/components/ws/WebsocketRoute.d.ts @@ -0,0 +1 @@ +// Since this a private component no types have been defined \ No newline at end of file diff --git a/packages/hyper-express/types/index.d.ts b/packages/hyper-express/types/index.d.ts new file mode 100644 index 0000000..f0fb2c7 --- /dev/null +++ b/packages/hyper-express/types/index.d.ts @@ -0,0 +1,16 @@ +import { Server } from './components/Server'; + +export * as compressors from 'uWebSockets.js'; +export * from './components/Server'; +export * from './components/router/Router'; +export * from './components/http/Request'; +export * from './components/http/Response'; +export * from './components/middleware/MiddlewareHandler'; +export * from './components/middleware/MiddlewareNext'; +export * from './components/plugins/LiveFile'; +export * from './components/plugins/MultipartField'; +export * from './components/plugins/SSEventStream'; +export * from './components/ws/Websocket'; +export * from './shared/uploaded-file'; + +export const express: (...args: ConstructorParameters) => Server; diff --git a/packages/hyper-express/types/shared/operators.d.ts b/packages/hyper-express/types/shared/operators.d.ts new file mode 100644 index 0000000..b43f457 --- /dev/null +++ b/packages/hyper-express/types/shared/operators.d.ts @@ -0,0 +1,10 @@ +export function wrap_object(original: object, target: object): void; + +export type PathKeyItem = [key: string, index: number]; +export function parse_path_parameters(pattern: string): PathKeyItem[]; + +export function array_buffer_to_string(array_buffer: ArrayBuffer, encoding?: string): string; + +export function async_wait(delay: number): Promise; + +export function merge_relative_paths(base_path: string, new_path: string): string; \ No newline at end of file diff --git a/packages/hyper-express/types/shared/uploaded-file.d.ts b/packages/hyper-express/types/shared/uploaded-file.d.ts new file mode 100644 index 0000000..0be241b --- /dev/null +++ b/packages/hyper-express/types/shared/uploaded-file.d.ts @@ -0,0 +1,19 @@ +import { readFileSync } from 'fs-extra'; + +export class UploadedFile { + _filename: string; + _size: number; + _mimeType: string; + _tempName: string; + _tempPath: string; + + get filename(): string; + get size(): string; + get mimeType(): string; + get tempName(): string; + get tempPath(): string; + + get extension(): string; + + toBuffer(): Promise; +} From 7810c2e5bd18fcb363b86d02bb15ea987446433d Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 15:35:19 +0530 Subject: [PATCH 16/27] fix(core): custom param decorator bug --- integrations/sample-app/.env.example | 53 ------------------- .../sample-app/app/http/controllers/icon.ts | 4 +- .../app/http/decorators/custom-param.ts | 7 +++ .../lib/rest/http-server/param-decorators.ts | 32 +++++------ .../lib/rest/http-server/route-explorer.ts | 4 +- 5 files changed, 29 insertions(+), 71 deletions(-) delete mode 100644 integrations/sample-app/.env.example create mode 100644 integrations/sample-app/app/http/decorators/custom-param.ts diff --git a/integrations/sample-app/.env.example b/integrations/sample-app/.env.example deleted file mode 100644 index d9b6322..0000000 --- a/integrations/sample-app/.env.example +++ /dev/null @@ -1,53 +0,0 @@ -APP_NAME="Intent App" -APP_PORT=5001 -APP_ENV=local -APP_URL=http://localhost:5001/ - -LOG_LEVEL=debug - -DB_DEBUG=1 -DB_HOST=127.0.0.1 -DB_PORT=5432 -DB_USER=postgres -DB_PASSWORD= -DB_DATABASE=intent_app -DB_FILENAME=intent.db - -DEFAULT_CACHE=memory -DEFAULT_QUEUE=db -DEFAULT_STORAGE=local -DEFAULT_MAILER=logger - -SENTRY_DSN= - -REDIS_HOST=127.0.0.0 -REDIS_PORT=6379 -REDIS_USERNAME= -REDIS_PASSWORD= - -QUEUE_NAME=intent-queue - -AWS_PROFILE= -AWS_REGION= -AWS_ACCESS_KEY= -AWS_SECRET_KEY= - -S3_BUCKET= - -SQS_PREFIX= -SQS_QUEUE= - -MAIL_HOST= -MAIL_PORT= -MAIL_USER= -MAIL_PASSWORD= -FROM_ADDRESS= - -MAILGUN_DOMAIN= -MAILGUN_USERNAME= -MAILGUN_API_KEY= - -RESEND_API_KEY= - - - diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 702a81d..27f9b63 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -13,6 +13,7 @@ import { } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; import { Request } from '@intentjs/hyper-express'; +import { CustomParam } from '../decorators/custom-param'; @Controller('/icon') @UseGuard(CustomGuard) @@ -67,6 +68,7 @@ export class IntentController { @Post('/json') async postJson( + @CustomParam() customParam: string, @Req() req: Request, @Query() query: Record, @Query('b') bQuery: string, @@ -77,7 +79,7 @@ export class IntentController { // @Accepts() accepts: string, @Body() body: any, ) { - console.log('inside post method'); + console.log('inside post method ===> ', customParam); console.log( query, bQuery, diff --git a/integrations/sample-app/app/http/decorators/custom-param.ts b/integrations/sample-app/app/http/decorators/custom-param.ts new file mode 100644 index 0000000..b22a66f --- /dev/null +++ b/integrations/sample-app/app/http/decorators/custom-param.ts @@ -0,0 +1,7 @@ +import { createParamDecorator, ExecutionContext } from '@intentjs/core'; + +export const CustomParam = createParamDecorator( + (data: any, ctx: ExecutionContext) => { + return 'data from custom decorator param'; + }, +); diff --git a/packages/core/lib/rest/http-server/param-decorators.ts b/packages/core/lib/rest/http-server/param-decorators.ts index fdbc508..c8c3d6d 100644 --- a/packages/core/lib/rest/http-server/param-decorators.ts +++ b/packages/core/lib/rest/http-server/param-decorators.ts @@ -22,7 +22,7 @@ export enum RouteParamtypes { export type RouteArgType = { type: RouteParamtypes; data: object | string | number; - handler: CustomRouteParamDecoratorHandler; + factory: CustomRouteParamDecoratorFactory; }; function createRouteParamDecorator(paramType: RouteParamtypes) { @@ -39,24 +39,26 @@ function createRouteParamDecorator(paramType: RouteParamtypes) { }; } -type CustomRouteParamDecoratorHandler = ( - data: any, +type CustomRouteParamDecoratorFactory = ( + data: T, context: ExecutionContext, ) => any; -export function createParamDecorator( - handler: CustomRouteParamDecoratorHandler, -): ParameterDecorator { - return (data?: string | object | number) => (target, key, index) => { - const args = Reflect.getMetadata(ROUTE_ARGS, target.constructor, key) || []; - args[index] = { - data: data, - handler, - }; +export function createParamDecorator( + factory: CustomRouteParamDecoratorFactory, + enhancers?: ParameterDecorator[], +): (data?: T) => ParameterDecorator { + return (data?: T): ParameterDecorator => + (target, key, index) => { + const args = + Reflect.getMetadata(ROUTE_ARGS, target.constructor, key) || []; + args[index] = { data: data, factory }; + Reflect.defineMetadata(ROUTE_ARGS, args, target.constructor, key); - Reflect.defineMetadata(ROUTE_ARGS, args, target.constructor, key); - console.log(Reflect.getMetadata(ROUTE_ARGS, target.constructor, key) || []); - }; + if (Array.isArray(enhancers)) { + for (const enhancer of enhancers) enhancer(target, key, index); + } + }; } export const Req: () => ParameterDecorator = createRouteParamDecorator( diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index feb2d18..4df2f8a 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -156,8 +156,8 @@ export class RouteExplorer { const args = []; for (const routeArg of routeArgs) { args.push( - routeArg.handler - ? routeArg.handler(routeArg.data, context) + routeArg.factory + ? routeArg.factory(routeArg.data, context) : httpContext.getInjectableValueFromArgType(routeArg), ); } From 1f950c9849556523a7f0019aadeb4d176e9fe06d Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 16:38:47 +0530 Subject: [PATCH 17/27] fix(core): route param decorator bugs --- .../sample-app/app/http/controllers/icon.ts | 85 +++++++++++++------ .../contexts/http-execution-context.ts | 23 +++-- .../core/lib/rest/http-server/http-handler.ts | 14 ++- .../lib/rest/http-server/param-decorators.ts | 20 +++-- .../lib/rest/http-server/route-explorer.ts | 2 +- packages/core/lib/rest/http-server/server.ts | 3 + 6 files changed, 101 insertions(+), 46 deletions(-) diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 27f9b63..9b2b990 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -1,8 +1,11 @@ import { Accepts, Body, + BufferBody, Controller, + File, Get, + Header, Host, IP, Param, @@ -10,9 +13,10 @@ import { Query, Req, UseGuard, + UserAgent, } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; -import { Request } from '@intentjs/hyper-express'; +import { Request, UploadedFile } from '@intentjs/hyper-express'; import { CustomParam } from '../decorators/custom-param'; @Controller('/icon') @@ -28,26 +32,38 @@ export class IntentController { @UseGuard(CustomGuard) async getHello( @Req() req: Request, - // @Query() query: Record, - // @Query('b') bQuery: string, - // @Param('name') name: string, - // @Param() pathParams: string, - // @Host() hostname: string, - // @IP() ips: string, - // @Accepts() accepts: string, + @Param('name') name: string, + @Query() query: Record, + @Query('b') bQuery: string, + @Param() pathParams: string, + @Host() hostname: string, + @IP() ips: string, + @Accepts() accepts: string, + @BufferBody() bufferBody: Promise, + @UserAgent() userAgent: string, + @Header() headers: Record, ) { - // console.log( - // await req.file('file1'), - // await req.file('file2'), - // query, - // bQuery, - // name, - // pathParams, - // hostname, - // accepts, - // ips, - // 'inside post method', - // ); + console.log( + 'query ==> ', + query, + 'bQuyery ==> ', + bQuery, + 'name ===> ', + name, + bufferBody, + pathParams, + 'hostname===> ', + hostname, + 'accepts ===> ', + accepts, + 'ips ===> ', + ips, + 'inside get method', + 'user agent ===> ', + userAgent, + ); + + console.log('all headers ===> ', headers); // throw new Error('hello there'); return { hello: 'world' }; } @@ -68,29 +84,42 @@ export class IntentController { @Post('/json') async postJson( - @CustomParam() customParam: string, @Req() req: Request, + @Param('name') name: string, @Query() query: Record, @Query('b') bQuery: string, - @Param('name') name: string, @Param() pathParams: string, @Host() hostname: string, @IP() ips: string, - // @Accepts() accepts: string, - @Body() body: any, + @Accepts() accepts: string, + @BufferBody() bufferBody: Promise, + @UserAgent() userAgent: string, + @Header() headers: Record, + @File('file') file: UploadedFile, ) { - console.log('inside post method ===> ', customParam); console.log( + 'query ==> ', query, + 'bQuyery ==> ', bQuery, + 'name ===> ', name, + bufferBody, pathParams, + 'hostname===> ', hostname, - // accepts, + 'accepts ===> ', + accepts, + 'ips ===> ', ips, - 'inside post method', - body, + 'inside get method', + 'user agent ===> ', + userAgent, ); + + console.log('all headers ===> ', headers); + console.log('uploaded files ==> ', file); + return { hello: 'world from POST /json' }; } diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index 7b1a1f8..93bb820 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -1,11 +1,12 @@ import { Response } from '../response'; import { RouteArgType, RouteParamtypes } from '../param-decorators'; -import { Request } from '@intentjs/hyper-express'; +import { MiddlewareNext, Request } from '@intentjs/hyper-express'; export class HttpExecutionContext { constructor( private readonly request: Request, private readonly response: Response, + private readonly next?: MiddlewareNext, ) {} getRequest(): Request { @@ -16,6 +17,10 @@ export class HttpExecutionContext { return this.response; } + getNext(): MiddlewareNext { + return this.next; + } + getInjectableValueFromArgType(routeArg: RouteArgType): any { const { type, data } = routeArg; switch (type) { @@ -32,9 +37,10 @@ export class HttpExecutionContext { return { ...this.request.query_parameters }; case RouteParamtypes.ACCEPTS: - return this.request.accepts(); + return this.request.headers['accept']; case RouteParamtypes.NEXT: + return this.getNext(); case RouteParamtypes.BODY: if (data) { @@ -52,14 +58,19 @@ export class HttpExecutionContext { case RouteParamtypes.IP: return this.request.ip; - case RouteParamtypes.RAW_BODY: - return this.request.raw; - case RouteParamtypes.USER_AGENT: return this.request.headers['user-agent']; case RouteParamtypes.HOST: - return this.request.url; + return this.request.hostname; + + case RouteParamtypes.BUFFER: + return this.request.buffer(); + + case RouteParamtypes.FILE: + if (data) { + return this.request.file(data as string); + } case RouteParamtypes.HEADERS: if (data) { diff --git a/packages/core/lib/rest/http-server/http-handler.ts b/packages/core/lib/rest/http-server/http-handler.ts index 0d1ab42..fca7c78 100644 --- a/packages/core/lib/rest/http-server/http-handler.ts +++ b/packages/core/lib/rest/http-server/http-handler.ts @@ -2,6 +2,7 @@ import { IntentExceptionFilter } from '../../exceptions/base-exception-handler'; import { IntentGuard } from '../foundation/guards/base-guard'; import { ExecutionContext } from './contexts/execution-context'; import { Response } from './response'; +import { StreamableFile } from './streamable-file'; export class HttpRouteHandler { constructor( @@ -29,11 +30,16 @@ export class HttpRouteHandler { if (responseFromHandler instanceof Response) { return responseFromHandler; - } else { - const response = context.switchToHttp().getResponse(); - response.body(responseFromHandler); - return response; } + + const response = context.switchToHttp().getResponse(); + if (responseFromHandler instanceof StreamableFile) { + response.stream(responseFromHandler); + return; + } + + response.body(responseFromHandler); + return response; } catch (e) { const res = this.exceptionFilter.catch(context, e); return res; diff --git a/packages/core/lib/rest/http-server/param-decorators.ts b/packages/core/lib/rest/http-server/param-decorators.ts index c8c3d6d..dcf6c8b 100644 --- a/packages/core/lib/rest/http-server/param-decorators.ts +++ b/packages/core/lib/rest/http-server/param-decorators.ts @@ -11,12 +11,11 @@ export enum RouteParamtypes { HEADERS = 6, SESSION = 7, FILE = 8, - FILES = 9, - HOST = 10, - IP = 11, - RAW_BODY = 12, - USER_AGENT = 13, - ACCEPTS = 14, + HOST = 9, + IP = 10, + USER_AGENT = 11, + ACCEPTS = 12, + BUFFER = 13, } export type RouteArgType = { @@ -69,6 +68,10 @@ export const Res: () => ParameterDecorator = createRouteParamDecorator( RouteParamtypes.RESPONSE, ); +export const BufferBody: () => ParameterDecorator = createRouteParamDecorator( + RouteParamtypes.BUFFER, +); + export const Query: (key?: string) => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.QUERY); @@ -90,9 +93,12 @@ export const UserAgent: () => ParameterDecorator = createRouteParamDecorator( ); export const Host: () => ParameterDecorator = createRouteParamDecorator( - RouteParamtypes.USER_AGENT, + RouteParamtypes.HOST, ); export const Accepts: () => ParameterDecorator = createRouteParamDecorator( RouteParamtypes.ACCEPTS, ); + +export const File: (key?: string) => ParameterDecorator = + createRouteParamDecorator(RouteParamtypes.FILE); diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index 4df2f8a..a16de44 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -150,7 +150,7 @@ export class RouteExplorer { ); const cb = async (hReq: Request, hRes: HResponse, next: MiddlewareNext) => { - const httpContext = new HttpExecutionContext(hReq, new Response()); + const httpContext = new HttpExecutionContext(hReq, new Response(), next); const context = new ExecutionContext(httpContext, instance, methodRef); const args = []; diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index c18a041..0484f8e 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -16,6 +16,9 @@ export class HyperServer { ): Promise { this.hyper = new HyperExpress.Server(config || {}); + /** + * process the body by default, so that it's available in all of the middleware, guards and controllers + */ this.hyper.use(async (req, res) => { await req.processBody(); }); From 4e6732355a99833d5d95c7bba2c5b4f578aca8e4 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 18:26:28 +0530 Subject: [PATCH 18/27] fix(core): response related bugs --- .../sample-app/app/http/controllers/icon.ts | 83 ++++++++++++------- packages/core/lib/rest/foundation/server.ts | 2 +- .../contexts/http-execution-context.ts | 3 +- .../core/lib/rest/http-server/http-handler.ts | 23 +++-- packages/core/lib/rest/http-server/index.ts | 1 + packages/core/lib/rest/http-server/reply.ts | 68 +++++++++++++++ .../core/lib/rest/http-server/response.ts | 1 - .../lib/rest/http-server/route-explorer.ts | 8 +- packages/core/lib/rest/http-server/server.ts | 2 + packages/core/lib/utils/helpers.ts | 11 +++ packages/core/lib/utils/object.ts | 11 +-- .../core/lib/validator/validation-guard.ts | 4 +- .../src/components/http/Response.js | 39 ++++++--- 13 files changed, 185 insertions(+), 71 deletions(-) create mode 100644 packages/core/lib/rest/http-server/reply.ts diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 9b2b990..804c6b6 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -4,6 +4,7 @@ import { BufferBody, Controller, File, + findProjectRoot, Get, Header, Host, @@ -12,12 +13,17 @@ import { Post, Query, Req, + Res, + Response, + StreamableFile, UseGuard, UserAgent, } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; import { Request, UploadedFile } from '@intentjs/hyper-express'; import { CustomParam } from '../decorators/custom-param'; +import { createReadStream, readFileSync } from 'fs'; +import { join } from 'path'; @Controller('/icon') @UseGuard(CustomGuard) @@ -31,41 +37,46 @@ export class IntentController { @Get('/:name') @UseGuard(CustomGuard) async getHello( - @Req() req: Request, - @Param('name') name: string, - @Query() query: Record, - @Query('b') bQuery: string, - @Param() pathParams: string, - @Host() hostname: string, - @IP() ips: string, - @Accepts() accepts: string, - @BufferBody() bufferBody: Promise, - @UserAgent() userAgent: string, - @Header() headers: Record, + // @Req() req: Request, + // @Param('name') name: string, + // @Query() query: Record, + // @Query('b') bQuery: string, + // @Param() pathParams: string, + // @Host() hostname: string, + // @IP() ips: string, + // @Accepts() accepts: string, + // @BufferBody() bufferBody: Promise, + // @UserAgent() userAgent: string, + // @Header() headers: Record, + @Res() res: Response, ) { - console.log( - 'query ==> ', - query, - 'bQuyery ==> ', - bQuery, - 'name ===> ', - name, - bufferBody, - pathParams, - 'hostname===> ', - hostname, - 'accepts ===> ', - accepts, - 'ips ===> ', - ips, - 'inside get method', - 'user agent ===> ', - userAgent, - ); + // console.log( + // 'query ==> ', + // query, + // 'bQuyery ==> ', + // bQuery, + // 'name ===> ', + // name, + // bufferBody, + // pathParams, + // 'hostname===> ', + // hostname, + // 'accepts ===> ', + // accepts, + // 'ips ===> ', + // ips, + // 'inside get method', + // 'user agent ===> ', + // userAgent, + // ); - console.log('all headers ===> ', headers); + // console.log('all headers ===> ', headers); // throw new Error('hello there'); - return { hello: 'world' }; + const readStream = createReadStream( + join(findProjectRoot(), 'storage/uploads/sample-image.jpg'), + ); + + return new StreamableFile(readStream, { type: 'image/jpeg' }); } @Get('/plain-with-query-param') @@ -96,6 +107,7 @@ export class IntentController { @UserAgent() userAgent: string, @Header() headers: Record, @File('file') file: UploadedFile, + @Res() res: Response, ) { console.log( 'query ==> ', @@ -120,6 +132,13 @@ export class IntentController { console.log('all headers ===> ', headers); console.log('uploaded files ==> ', file); + const readStream = createReadStream( + join(findProjectRoot(), 'storage/uploads/sample-image.jpg'), + ); + + return new StreamableFile(readStream, { type: 'image/jpeg' }); + return res.stream(new StreamableFile(readStream, { type: 'image/jpeg' })); + return { hello: 'world from POST /json' }; } diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 00f0e0d..e21964c 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -100,7 +100,7 @@ export class IntentHttpServer { await this.kernel.boot(server); server.set_error_handler((hReq: any, hRes: HyperResponse, error: Error) => { const res = new Response(); - const httpContext = new HttpExecutionContext(hReq, new Response()); + const httpContext = new HttpExecutionContext(hReq, hRes); const context = new ExecutionContext(httpContext, null, null); errorHandler.catch(context, error); res.reply(hReq, hRes); diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index 93bb820..0aeb22a 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -1,6 +1,5 @@ -import { Response } from '../response'; import { RouteArgType, RouteParamtypes } from '../param-decorators'; -import { MiddlewareNext, Request } from '@intentjs/hyper-express'; +import { MiddlewareNext, Request, Response } from '@intentjs/hyper-express'; export class HttpExecutionContext { constructor( diff --git a/packages/core/lib/rest/http-server/http-handler.ts b/packages/core/lib/rest/http-server/http-handler.ts index fca7c78..8272f5f 100644 --- a/packages/core/lib/rest/http-server/http-handler.ts +++ b/packages/core/lib/rest/http-server/http-handler.ts @@ -1,8 +1,11 @@ +import { Response } from '@intentjs/hyper-express'; import { IntentExceptionFilter } from '../../exceptions/base-exception-handler'; import { IntentGuard } from '../foundation/guards/base-guard'; import { ExecutionContext } from './contexts/execution-context'; -import { Response } from './response'; +import { Reply } from './reply'; +import { HttpStatus } from './status-codes'; import { StreamableFile } from './streamable-file'; +import { isUndefined } from '../../utils/helpers'; export class HttpRouteHandler { constructor( @@ -11,10 +14,11 @@ export class HttpRouteHandler { protected readonly exceptionFilter: IntentExceptionFilter, ) {} - async handle(context: ExecutionContext, args: any[]): Promise { - // for (const middleware of this.middlewares) { - // await middleware.use({}, {}); - // } + async handle( + context: ExecutionContext, + args: any[], + replyHandler: Reply, + ): Promise { try { /** * Handle the Guards @@ -32,14 +36,9 @@ export class HttpRouteHandler { return responseFromHandler; } + const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); - if (responseFromHandler instanceof StreamableFile) { - response.stream(responseFromHandler); - return; - } - - response.body(responseFromHandler); - return response; + replyHandler.handle(request, response, responseFromHandler); } catch (e) { const res = this.exceptionFilter.catch(context, e); return res; diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index b5603a1..b3b8a06 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -9,3 +9,4 @@ export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; export * from './interfaces'; +export { Request } from '@intentjs/hyper-express'; diff --git a/packages/core/lib/rest/http-server/reply.ts b/packages/core/lib/rest/http-server/reply.ts new file mode 100644 index 0000000..4bfb166 --- /dev/null +++ b/packages/core/lib/rest/http-server/reply.ts @@ -0,0 +1,68 @@ +import { Request, Response } from '@intentjs/hyper-express'; +import { HttpStatus } from './status-codes'; +import { isClass, isUndefined } from '../../utils/helpers'; +import { StreamableFile } from './streamable-file'; +import { Obj } from '../../utils'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; + +export class Reply { + async handle(req: Request, res: Response, dataFromHandler: any) { + const { method } = req; + + /** + * Set the status code of the response + */ + if (!res.statusCode && method === 'POST') { + res.status(HttpStatus.CREATED); + } else if (!res.statusCode) { + res.status(HttpStatus.OK); + } + + if (dataFromHandler instanceof StreamableFile) { + const headers = dataFromHandler.getHeaders(); + if ( + isUndefined(res.getHeader('Content-Type')) && + !isUndefined(headers.type) + ) { + res.header('Content-Type', headers.type); + } + if ( + isUndefined(res.getHeader('Content-Disposition')) && + !isUndefined(headers.type) + ) { + res.header('Content-Disposition', headers.disposition); + } + + if ( + isUndefined(res.getHeader('Content-Length')) && + !isUndefined(headers.length) + ) { + res.header('Content-Length', headers.length + ''); + } + + return res.stream(dataFromHandler.getStream()); + } + + /** + * Default to JSON + */ + let plainData = Array.isArray(dataFromHandler) + ? dataFromHandler.map(r => this.transformToPlain(r)) + : this.transformToPlain(dataFromHandler); + + // console.log(plainData); + // console.time('time_to_detect_object'); + // console.log(Obj.isObj(dataFromHandler), instanceToPlain(dataFromHandler)); + // console.timeEnd('time_to_detect_object'); + if (typeof plainData != null && typeof plainData === 'object') { + return res.json(plainData); + } + + return res.send(String(plainData)); + } + + transformToPlain(plainOrClass: any) { + if (!plainOrClass) return plainOrClass; + return instanceToPlain(plainOrClass); + } +} diff --git a/packages/core/lib/rest/http-server/response.ts b/packages/core/lib/rest/http-server/response.ts index 077f2cf..f9949d1 100644 --- a/packages/core/lib/rest/http-server/response.ts +++ b/packages/core/lib/rest/http-server/response.ts @@ -123,7 +123,6 @@ export class Response { res.setHeader('Content-Length', headers.length + ''); } - // this.bodyData.getStream().once('error') return res.stream(this.bodyData.getStream()); } diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index a16de44..825629b 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -22,6 +22,7 @@ import { RouteArgType } from './param-decorators'; import { IntentGuard } from '../foundation/guards/base-guard'; import { IntentMiddleware } from '../foundation/middlewares/middleware'; import { IntentExceptionFilter } from '../../exceptions/base-exception-handler'; +import { Reply } from './reply'; export class RouteExplorer { globalGuards: Type[] = []; @@ -149,8 +150,9 @@ export class RouteExplorer { errorHandler, ); + const replyHandler = new Reply(); const cb = async (hReq: Request, hRes: HResponse, next: MiddlewareNext) => { - const httpContext = new HttpExecutionContext(hReq, new Response(), next); + const httpContext = new HttpExecutionContext(hReq, hRes, next); const context = new ExecutionContext(httpContext, instance, methodRef); const args = []; @@ -162,8 +164,8 @@ export class RouteExplorer { ); } - const res = await handler.handle(context, args); - res.reply(hReq, hRes); + await handler.handle(context, args, replyHandler); + // res.reply(hReq, hRes); }; return { diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index 0484f8e..e84ede0 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -1,6 +1,7 @@ import HyperExpress, { MiddlewareHandler } from '@intentjs/hyper-express'; import { HttpMethods, HttpRoute } from './interfaces'; import { IntentMiddleware } from '../foundation/middlewares/middleware'; +import { Validator } from '../../validator'; export class HyperServer { protected hyper: HyperExpress.Server; @@ -20,6 +21,7 @@ export class HyperServer { * process the body by default, so that it's available in all of the middleware, guards and controllers */ this.hyper.use(async (req, res) => { + req.setValidator(Validator); await req.processBody(); }); diff --git a/packages/core/lib/utils/helpers.ts b/packages/core/lib/utils/helpers.ts index 96083b3..d4dd262 100644 --- a/packages/core/lib/utils/helpers.ts +++ b/packages/core/lib/utils/helpers.ts @@ -138,3 +138,14 @@ export const getTime = () => { export const isUndefined = (val: any) => { return val === undefined; }; + +export const isClass = (obj: any) => { + // First check if it's an object and not null + if (obj === null || typeof obj !== 'object') { + return false; + } + + // Check if the constructor is not the Object constructor + // This tells us if it was created by a class or Object literal + return Object.getPrototypeOf(obj).constructor.name !== 'Object'; +}; diff --git a/packages/core/lib/utils/object.ts b/packages/core/lib/utils/object.ts index 1254ad5..ace582d 100644 --- a/packages/core/lib/utils/object.ts +++ b/packages/core/lib/utils/object.ts @@ -164,14 +164,15 @@ export class Obj { } static isObj(obj: any, throwError = false): boolean { - if (typeof obj === 'object' && !Array.isArray(obj) && obj !== null) { - return true; - } + let isObj = + obj !== null && + typeof obj === 'object' && + Object.getPrototypeOf(obj) === Object.prototype; - if (throwError) { + if (!isObj && throwError) { throw new InvalidValue('Passed value is not an object'); } - return false; + return isObj; } } diff --git a/packages/core/lib/validator/validation-guard.ts b/packages/core/lib/validator/validation-guard.ts index c9862fc..53fa01f 100644 --- a/packages/core/lib/validator/validation-guard.ts +++ b/packages/core/lib/validator/validation-guard.ts @@ -1,7 +1,7 @@ import { Request } from '@intentjs/hyper-express'; -import { ExecutionContext, IntentGuard } from '../rest'; -import { Reflector } from '../reflections'; import { Injectable } from '../foundation/decorators'; +import { IntentGuard } from '../rest/foundation/guards/base-guard'; +import { ExecutionContext } from '../rest/http-server/contexts/execution-context'; @Injectable() export class IntentValidationGuard extends IntentGuard { diff --git a/packages/hyper-express/src/components/http/Response.js b/packages/hyper-express/src/components/http/Response.js index 447caee..3255bc1 100644 --- a/packages/hyper-express/src/components/http/Response.js +++ b/packages/hyper-express/src/components/http/Response.js @@ -125,8 +125,8 @@ class Response { // If position is not greater than last cursor then we likely have a double middleware execution this.throw( new Error( - 'ERR_DOUBLE_MIDDLEWARE_EXEUCTION_DETECTED: Please ensure you are not calling the next() iterator inside of an ASYNC middleware. You must only call next() ONCE per middleware inside of SYNCHRONOUS middlewares only!' - ) + 'ERR_DOUBLE_MIDDLEWARE_EXEUCTION_DETECTED: Please ensure you are not calling the next() iterator inside of an ASYNC middleware. You must only call next() ONCE per middleware inside of SYNCHRONOUS middlewares only!', + ), ); } @@ -292,8 +292,8 @@ class Response { if (this._upgrade_socket == null) this.throw( new Error( - 'HyperExpress: You cannot upgrade a request that does not come from an upgrade handler. No upgrade socket was found.' - ) + 'HyperExpress: You cannot upgrade a request that does not come from an upgrade handler. No upgrade socket was found.', + ), ); // Resume the request in case it was paused @@ -314,7 +314,7 @@ class Response { headers['sec-websocket-key'], headers['sec-websocket-protocol'], headers['sec-websocket-extensions'], - this._upgrade_socket + this._upgrade_socket, ); // Mark request as complete so no more operations can be performed @@ -345,7 +345,7 @@ class Response { // Write the appropriate status code to the response along with mapped status code message if (this._status_code || this._status_message) this._raw_response.writeStatus( - this._status_code + ' ' + (this._status_message || status_codes[this._status_code]) + this._status_code + ' ' + (this._status_message || status_codes[this._status_code]), ); // Iterate through all headers and write them to uWS @@ -403,8 +403,8 @@ class Response { if (typeof output !== 'boolean') this.throw( new Error( - 'HyperExpress: Response.drain(handler) -> handler must return a boolean value stating if the write was successful or not.' - ) + 'HyperExpress: Response.drain(handler) -> handler must return a boolean value stating if the write was successful or not.', + ), ); // Return the boolean value to uWS as required by uWS documentation @@ -516,7 +516,7 @@ class Response { // Wait for the request to fully receive the whole request body before sending the response return this._wrapped_request.once('received', () => // Because 'received' will be emitted asynchronously, we need to cork the response to ensure the response is sent in the correct order - this.atomic(() => this.send(body, close_connection)) + this.atomic(() => this.send(body, close_connection)), ); } @@ -635,7 +635,7 @@ class Response { return flushed; }); } - }) + }), ); } @@ -652,7 +652,7 @@ class Response { // Ensure readable is an instance of a stream.Readable if (!(readable instanceof stream.Readable)) this.throw( - new Error('HyperExpress: Response.stream(readable, total_size) -> readable must be a Readable stream.') + new Error('HyperExpress: Response.stream(readable, total_size) -> readable must be a Readable stream.'), ); // Do not allow streaming if response has already been aborted or completed @@ -760,7 +760,9 @@ class Response { jsonp(body, name) { let query_parameters = this._wrapped_request.query_parameters; let method_name = query_parameters['callback'] || name; - return this.header('content-type', 'application/javascript', true).send(`${method_name}(${JSON.stringify(body)})`); + return this.header('content-type', 'application/javascript', true).send( + `${method_name}(${JSON.stringify(body)})`, + ); } /** @@ -957,9 +959,20 @@ class Response { */ _throw_unsupported(name) { throw new Error( - `ERR_INCOMPATIBLE_CALL: One of your middlewares or route logic tried to call Response.${name} which is unsupported with HyperExpress.` + `ERR_INCOMPATIBLE_CALL: One of your middlewares or route logic tried to call Response.${name} which is unsupported with HyperExpress.`, ); } + + text(data) { + this.type('text'); + this.send(data); + return this; + } + + notFound() { + this.status(404); + return this; + } } // Store the descriptors of the original HyperExpress.Response class From 0a3ad166e4f27e32c073d27d7db60a78a4b18a42 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 20:05:46 +0530 Subject: [PATCH 19/27] fix(core): validation and dto related bugs --- .../sample-app/app/http/controllers/icon.ts | 59 ++++++++++--------- .../app/http/decorators/custom-param.ts | 2 +- packages/core/lib/codegen/command.ts | 2 +- packages/core/lib/reflections/reflector.ts | 2 +- .../contexts/http-execution-context.ts | 5 +- .../lib/rest/http-server/param-decorators.ts | 31 ++++++++++ packages/core/lib/rest/http-server/reply.ts | 2 + .../lib/rest/http-server/route-explorer.ts | 32 +++++++--- .../core/lib/validator/validation-guard.ts | 5 +- packages/core/lib/validator/validator.ts | 20 ++++--- .../src/components/http/Request.js | 9 ++- 11 files changed, 118 insertions(+), 51 deletions(-) diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 804c6b6..c49ef79 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -1,8 +1,8 @@ import { Accepts, - Body, BufferBody, Controller, + Dto, File, findProjectRoot, Get, @@ -18,12 +18,14 @@ import { StreamableFile, UseGuard, UserAgent, + Validate, } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; import { Request, UploadedFile } from '@intentjs/hyper-express'; import { CustomParam } from '../decorators/custom-param'; import { createReadStream, readFileSync } from 'fs'; import { join } from 'path'; +import { LoginDto } from 'app/validators/auth'; @Controller('/icon') @UseGuard(CustomGuard) @@ -69,14 +71,13 @@ export class IntentController { // 'user agent ===> ', // userAgent, // ); - // console.log('all headers ===> ', headers); // throw new Error('hello there'); - const readStream = createReadStream( - join(findProjectRoot(), 'storage/uploads/sample-image.jpg'), - ); - - return new StreamableFile(readStream, { type: 'image/jpeg' }); + // return { hello: 'world' }; + // const readStream = createReadStream( + // join(findProjectRoot(), 'storage/uploads/sample-image.jpg'), + // ); + // return new StreamableFile(readStream, { type: 'image/jpeg' }); } @Get('/plain-with-query-param') @@ -94,8 +95,10 @@ export class IntentController { } @Post('/json') + @Validate(LoginDto) async postJson( @Req() req: Request, + @Dto() dto: LoginDto, @Param('name') name: string, @Query() query: Record, @Query('b') bQuery: string, @@ -109,28 +112,28 @@ export class IntentController { @File('file') file: UploadedFile, @Res() res: Response, ) { - console.log( - 'query ==> ', - query, - 'bQuyery ==> ', - bQuery, - 'name ===> ', - name, - bufferBody, - pathParams, - 'hostname===> ', - hostname, - 'accepts ===> ', - accepts, - 'ips ===> ', - ips, - 'inside get method', - 'user agent ===> ', - userAgent, - ); + // console.log( + // 'query ==> ', + // query, + // 'bQuyery ==> ', + // bQuery, + // 'name ===> ', + // name, + // bufferBody, + // pathParams, + // 'hostname===> ', + // hostname, + // 'accepts ===> ', + // accepts, + // 'ips ===> ', + // ips, + // 'inside get method', + // 'user agent ===> ', + // userAgent, + // ); - console.log('all headers ===> ', headers); - console.log('uploaded files ==> ', file); + // console.log('all headers ===> ', headers); + console.log('uploaded files ==> ', req.dto(), dto); const readStream = createReadStream( join(findProjectRoot(), 'storage/uploads/sample-image.jpg'), diff --git a/integrations/sample-app/app/http/decorators/custom-param.ts b/integrations/sample-app/app/http/decorators/custom-param.ts index b22a66f..51973aa 100644 --- a/integrations/sample-app/app/http/decorators/custom-param.ts +++ b/integrations/sample-app/app/http/decorators/custom-param.ts @@ -1,7 +1,7 @@ import { createParamDecorator, ExecutionContext } from '@intentjs/core'; export const CustomParam = createParamDecorator( - (data: any, ctx: ExecutionContext) => { + (data: any, ctx: ExecutionContext, argIndex: number) => { return 'data from custom decorator param'; }, ); diff --git a/packages/core/lib/codegen/command.ts b/packages/core/lib/codegen/command.ts index da6e08f..83d46d5 100644 --- a/packages/core/lib/codegen/command.ts +++ b/packages/core/lib/codegen/command.ts @@ -1,9 +1,9 @@ import { join } from 'path'; -import { Injectable } from '@nestjs/common'; import { Command, CommandRunner, ConsoleIO } from '../console'; import { Str } from '../utils/string'; import { CodegenService } from './service'; import { getClassNamesFromFilePath } from './utils'; +import { Injectable } from '../foundation/decorators'; @Injectable() export class CodegenCommand { diff --git a/packages/core/lib/reflections/reflector.ts b/packages/core/lib/reflections/reflector.ts index 20dc022..1019928 100644 --- a/packages/core/lib/reflections/reflector.ts +++ b/packages/core/lib/reflections/reflector.ts @@ -83,7 +83,7 @@ export class Reflector { */ getFromMethod(keyOrDecorator: string | Object, defaultValue?: T): T { const key = - typeof keyOrDecorator === 'function' + typeof keyOrDecorator === 'object' ? keyOrDecorator['KEY'] : keyOrDecorator; diff --git a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts index 0aeb22a..bc537a6 100644 --- a/packages/core/lib/rest/http-server/contexts/http-execution-context.ts +++ b/packages/core/lib/rest/http-server/contexts/http-execution-context.ts @@ -20,7 +20,7 @@ export class HttpExecutionContext { return this.next; } - getInjectableValueFromArgType(routeArg: RouteArgType): any { + getInjectableValueFromArgType(routeArg: RouteArgType, index: number): any { const { type, data } = routeArg; switch (type) { case RouteParamtypes.REQUEST: @@ -54,6 +54,9 @@ export class HttpExecutionContext { return { ...this.request.params }; + case RouteParamtypes.DTO: + return this.request.dto(); + case RouteParamtypes.IP: return this.request.ip; diff --git a/packages/core/lib/rest/http-server/param-decorators.ts b/packages/core/lib/rest/http-server/param-decorators.ts index dcf6c8b..002cc74 100644 --- a/packages/core/lib/rest/http-server/param-decorators.ts +++ b/packages/core/lib/rest/http-server/param-decorators.ts @@ -1,3 +1,4 @@ +import { plainToInstance } from 'class-transformer'; import { ROUTE_ARGS } from './constants'; import { ExecutionContext } from './contexts/execution-context'; @@ -16,6 +17,7 @@ export enum RouteParamtypes { USER_AGENT = 11, ACCEPTS = 12, BUFFER = 13, + DTO = 14, } export type RouteArgType = { @@ -41,6 +43,7 @@ function createRouteParamDecorator(paramType: RouteParamtypes) { type CustomRouteParamDecoratorFactory = ( data: T, context: ExecutionContext, + argIndex?: number, ) => any; export function createParamDecorator( @@ -68,6 +71,10 @@ export const Res: () => ParameterDecorator = createRouteParamDecorator( RouteParamtypes.RESPONSE, ); +// export const Dto: () => ParameterDecorator = createRouteParamDecorator( +// RouteParamtypes.DTO, +// ); + export const BufferBody: () => ParameterDecorator = createRouteParamDecorator( RouteParamtypes.BUFFER, ); @@ -102,3 +109,27 @@ export const Accepts: () => ParameterDecorator = createRouteParamDecorator( export const File: (key?: string) => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.FILE); + +export const Dto = createParamDecorator( + async (data: any, ctx: ExecutionContext, argIndex: number) => { + const req = ctx.switchToHttp().getRequest(); + if (req.dto()) return req.dto(); + + const types = Reflect.getMetadata( + 'design:paramtypes', + ctx.getClass().prototype, + ctx.getHandler().name, + ); + + const paramType = types[argIndex]; + const typeParamType = typeof paramType; + + if (typeParamType === 'function') { + const dto = await plainToInstance(paramType, req.all()); + req.setDto(dto); + return dto; + } + + return req.all(); + }, +); diff --git a/packages/core/lib/rest/http-server/reply.ts b/packages/core/lib/rest/http-server/reply.ts index 4bfb166..2f6dbf2 100644 --- a/packages/core/lib/rest/http-server/reply.ts +++ b/packages/core/lib/rest/http-server/reply.ts @@ -43,6 +43,8 @@ export class Reply { return res.stream(dataFromHandler.getStream()); } + if (!dataFromHandler) return res.send(); + /** * Default to JSON */ diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index 825629b..d78e5eb 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -118,7 +118,6 @@ export class RouteExplorer { if (!pathMethod) return; - const methodRef = instance[key].bind(instance); const controllerGuards = Reflect.getMetadata( GUARD_KEY, instance.constructor, @@ -141,31 +140,46 @@ export class RouteExplorer { } const routeArgs = - Reflect.getMetadata(ROUTE_ARGS, instance.constructor, key) || - ([] as RouteArgType[]); + (Reflect.getMetadata( + ROUTE_ARGS, + instance.constructor, + key, + ) as RouteArgType[]) || []; const handler = new HttpRouteHandler( composedGuards, - methodRef, + instance[key].bind(instance), errorHandler, ); const replyHandler = new Reply(); + const cb = async (hReq: Request, hRes: HResponse, next: MiddlewareNext) => { const httpContext = new HttpExecutionContext(hReq, hRes, next); - const context = new ExecutionContext(httpContext, instance, methodRef); + const context = new ExecutionContext( + httpContext, + instance.constructor, + instance[key], + ); const args = []; - for (const routeArg of routeArgs) { + for (const index in routeArgs) { + const routeArg = routeArgs[index]; args.push( routeArg.factory - ? routeArg.factory(routeArg.data, context) - : httpContext.getInjectableValueFromArgType(routeArg), + ? await routeArg.factory( + routeArg.data, + context, + index as unknown as number, + ) + : httpContext.getInjectableValueFromArgType( + routeArg, + index as unknown as number, + ), ); } await handler.handle(context, args, replyHandler); - // res.reply(hReq, hRes); }; return { diff --git a/packages/core/lib/validator/validation-guard.ts b/packages/core/lib/validator/validation-guard.ts index 53fa01f..bf1a805 100644 --- a/packages/core/lib/validator/validation-guard.ts +++ b/packages/core/lib/validator/validation-guard.ts @@ -12,8 +12,11 @@ export class IntentValidationGuard extends IntentGuard { async guard(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest() as Request; const reflector = context.getReflector(); + /** + * Check if a DTO already exists + */ + const dto = request.dto(); const schema = reflector.getFromMethod('dtoSchema'); - console.log(schema); await request.validate(schema); return true; } diff --git a/packages/core/lib/validator/validator.ts b/packages/core/lib/validator/validator.ts index 6c9d81e..fb494be 100644 --- a/packages/core/lib/validator/validator.ts +++ b/packages/core/lib/validator/validator.ts @@ -1,9 +1,9 @@ -import { Type } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { ValidationError, validate } from 'class-validator'; import { ConfigService } from '../config/service'; import { ValidationFailed } from '../exceptions/validation-failed'; import { Obj } from '../utils'; +import { Type } from '../interfaces/utils'; export class Validator { private meta: Record; @@ -25,16 +25,22 @@ export class Validator { return this; } - async validate(inputs: Record): Promise { + async validateRaw(inputs: Record): Promise { const schema: T = plainToInstance(this.dto, inputs); - if (Obj.isNotEmpty(this.meta)) { - this.injectMeta(schema); + const errors = await validate(schema as Record, { + stopAtFirstError: true, + }); + + if (errors.length > 0) { + await this.processErrorsFromValidation(errors); } - schema['$'] = this.meta; + return schema; + } - const errors = await validate(schema as Record, { + async validateDto(dto: T): Promise { + const errors = await validate(dto as Record, { stopAtFirstError: true, }); @@ -42,7 +48,7 @@ export class Validator { await this.processErrorsFromValidation(errors); } - return schema; + return dto; } /** diff --git a/packages/hyper-express/src/components/http/Request.js b/packages/hyper-express/src/components/http/Request.js index 4b621f5..87a22ed 100644 --- a/packages/hyper-express/src/components/http/Request.js +++ b/packages/hyper-express/src/components/http/Request.js @@ -1094,9 +1094,14 @@ class Request { async validate(schema) { const payload = await this.all(); + const dto = this._dto; const validator = this._validatorClass.compareWith(schema); - const dto = await validator.addMeta({ ...payload }).validate({ ...payload }); - this.setDto(dto); + if (dto) { + await validator.validateDto(dto); + } else { + const dto = await validator.addMeta({}).validateRaw({ ...payload }); + this.setDto(dto); + } return true; } From c8bde06a6ebd7b93a4e8db8a3760890c23a19f44 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 20:09:20 +0530 Subject: [PATCH 20/27] fix(core): response class import --- .../sample-app/app/http/controllers/icon.ts | 5 +- packages/core/lib/rest/decorators.ts | 47 ------------------- packages/core/lib/rest/http-server/index.ts | 3 +- 3 files changed, 3 insertions(+), 52 deletions(-) delete mode 100644 packages/core/lib/rest/decorators.ts diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index c49ef79..600c9bc 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -22,8 +22,7 @@ import { } from '@intentjs/core'; import { CustomGuard } from '../guards/custom'; import { Request, UploadedFile } from '@intentjs/hyper-express'; -import { CustomParam } from '../decorators/custom-param'; -import { createReadStream, readFileSync } from 'fs'; +import { createReadStream } from 'fs'; import { join } from 'path'; import { LoginDto } from 'app/validators/auth'; @@ -140,7 +139,7 @@ export class IntentController { ); return new StreamableFile(readStream, { type: 'image/jpeg' }); - return res.stream(new StreamableFile(readStream, { type: 'image/jpeg' })); + // return res.stream(new StreamableFile(readStream, { type: 'image/jpeg' })); return { hello: 'world from POST /json' }; } diff --git a/packages/core/lib/rest/decorators.ts b/packages/core/lib/rest/decorators.ts deleted file mode 100644 index 59505cc..0000000 --- a/packages/core/lib/rest/decorators.ts +++ /dev/null @@ -1,47 +0,0 @@ -// import { ExecutionContext, createParamDecorator } from '@nestjs/common'; -// import { plainToInstance } from 'class-transformer'; -// import { Request } from './foundation'; - -// export const Body = createParamDecorator( -// (data: any, ctx: ExecutionContext) => { -// const request = ctx.switchToHttp().getRequest(); -// if (request.dto()) return request.dto(); - -// const types = Reflect.getMetadata( -// 'design:paramtypes', -// ctx.getClass().prototype, -// ctx.getHandler().name, -// ); - -// const paramIndex = Reflect.getMetadata( -// 'intent::body_decorator_index', -// ctx.getHandler(), -// ); - -// const paramType = types[paramIndex]; - -// /** -// * Check the type of paramType, -// * if the value is `function`, then we will assume that it's a DTO. -// * otherwise if it is another data type, we will convert the value and then send it back. -// */ -// const typeParamType = typeof paramType; - -// if (typeParamType === 'function') { -// const dto = plainToInstance(paramType, request.all()); -// request.setDto(dto); -// request.body(); -// } - -// return request.all(); -// }, -// [ -// (target: any, propKey: string | symbol, index: number): void => { -// Reflect.defineMetadata( -// 'intent::body_decorator_index', -// index, -// target[propKey], -// ); -// }, -// ], -// ); diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index b3b8a06..bf3691b 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -2,11 +2,10 @@ export * from './contexts/execution-context'; export * from './contexts/http-execution-context'; export * from './decorators'; export * from './http-handler'; -export * from './response'; export * from './route-explorer'; export * from './server'; export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; export * from './interfaces'; -export { Request } from '@intentjs/hyper-express'; +export { Request, Response } from '@intentjs/hyper-express'; From 3c29f10980fc569a08cd1c000b0aeb659e2119fe Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sat, 30 Nov 2024 20:46:48 +0530 Subject: [PATCH 21/27] feat(core): static serve --- integrations/sample-app/config/http.ts | 15 +++++- package-lock.json | 25 ++++------ .../exceptions/file-not-found-exception.ts | 11 +++++ packages/core/lib/exceptions/index.ts | 1 + packages/core/lib/interfaces/config.ts | 12 +++++ .../lib/rest/http-server/route-explorer.ts | 2 +- packages/core/lib/rest/http-server/server.ts | 48 +++++++++++++++++++ packages/core/lib/serviceProvider.ts | 4 +- packages/core/package.json | 2 +- 9 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 packages/core/lib/exceptions/file-not-found-exception.ts diff --git a/integrations/sample-app/config/http.ts b/integrations/sample-app/config/http.ts index 711951d..5a6478a 100644 --- a/integrations/sample-app/config/http.ts +++ b/integrations/sample-app/config/http.ts @@ -1,4 +1,5 @@ -import { HttpConfig, registerNamespace } from '@intentjs/core'; +import { findProjectRoot, HttpConfig, registerNamespace } from '@intentjs/core'; +import { join } from 'path'; export default registerNamespace( 'http', @@ -25,5 +26,17 @@ export default registerNamespace( max_body_buffer: 100000000, max_body_length: 100000000, }, + + staticServe: { + httpPath: 'assets', + filePath: join(findProjectRoot(), 'storage/uploads'), + keep: { + extensions: ['css', 'js', 'json', 'png', 'jpg', 'jpeg'], + }, + cache: { + max_file_count: 1000, + max_file_size: 4 * 1024 * 1024, + }, + }, }), ); diff --git a/package-lock.json b/package-lock.json index 28b6f01..63896b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6051,7 +6051,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -6622,7 +6621,6 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6975,7 +6973,6 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -6998,7 +6995,6 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -9759,7 +9755,6 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10832,7 +10827,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -12449,6 +12443,15 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/live-directory": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/live-directory/-/live-directory-3.0.3.tgz", + "integrity": "sha512-d5jchsscPvkDqwv8lypjpxIIUz4w8fu+czfEkNEMGub4+EZ0SBj5Nclb4E2QmJNC5HJ4BwEdc5DHvoHZfIAK+w==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2" + } + }, "node_modules/load-json-file": { "version": "6.2.0", "dev": true, @@ -13244,13 +13247,6 @@ "node": ">=8" } }, - "node_modules/mute-stdout": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, "node_modules/mute-stream": { "version": "0.0.8", "dev": true, @@ -15165,7 +15161,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -17811,8 +17806,8 @@ "helmet": "^7.1.0", "ioredis": "^5.3.2", "knex": "^3.1.0", + "live-directory": "^3.0.3", "ms": "^2.1.3", - "mute-stdout": "^2.0.0", "objection": "^3.1.4", "picocolors": "^1.1.0", "react-email": "^3.0.1", diff --git a/packages/core/lib/exceptions/file-not-found-exception.ts b/packages/core/lib/exceptions/file-not-found-exception.ts new file mode 100644 index 0000000..68afc8c --- /dev/null +++ b/packages/core/lib/exceptions/file-not-found-exception.ts @@ -0,0 +1,11 @@ +import { HttpStatus } from '../rest/http-server/status-codes'; +import { HttpException } from './http-exception'; + +export class FileNotFoundException extends HttpException { + constructor( + response: string | Record, + status: number | HttpStatus = HttpStatus.NOT_FOUND, + ) { + super(response, status); + } +} diff --git a/packages/core/lib/exceptions/index.ts b/packages/core/lib/exceptions/index.ts index ffea587..b8c4fbc 100644 --- a/packages/core/lib/exceptions/index.ts +++ b/packages/core/lib/exceptions/index.ts @@ -7,3 +7,4 @@ export * from './invalid-value'; export * from './invalid-value-type'; export * from './http-exception'; export * from './forbidden-exception'; +export * from './file-not-found-exception'; diff --git a/packages/core/lib/interfaces/config.ts b/packages/core/lib/interfaces/config.ts index e340543..80adcc6 100644 --- a/packages/core/lib/interfaces/config.ts +++ b/packages/core/lib/interfaces/config.ts @@ -4,6 +4,7 @@ import { } from '@nestjs/common/interfaces/external/cors-options.interface'; import { ServerConstructorOptions } from '@intentjs/hyper-express'; import { GenericClass } from './utils'; +import { WatchOptions } from 'fs-extra'; export interface SentryConfig { dsn: string; @@ -37,4 +38,15 @@ export interface HttpConfig { parsers: RequestParsers[]; cors?: CorsOptions | CorsOptionsDelegate; server?: ServerConstructorOptions; + staticServe?: { + httpPath?: string; + filePath?: string; + keep?: { + extensions?: string[]; + }; + cache?: { + max_file_count?: number; + max_file_size?: number; + }; + }; } diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index d78e5eb..68c02c8 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -184,7 +184,7 @@ export class RouteExplorer { return { method: pathMethod, - path: join(controllerKey, methodPath), + path: join('/', controllerKey, methodPath), httpHandler: cb, }; } diff --git a/packages/core/lib/rest/http-server/server.ts b/packages/core/lib/rest/http-server/server.ts index e84ede0..463b770 100644 --- a/packages/core/lib/rest/http-server/server.ts +++ b/packages/core/lib/rest/http-server/server.ts @@ -2,6 +2,11 @@ import HyperExpress, { MiddlewareHandler } from '@intentjs/hyper-express'; import { HttpMethods, HttpRoute } from './interfaces'; import { IntentMiddleware } from '../foundation/middlewares/middleware'; import { Validator } from '../../validator'; +import { ConfigService } from '../../config'; +import LiveDirectory from 'live-directory'; +import { join } from 'path'; +import { FileNotFoundException } from '../../exceptions/file-not-found-exception'; +import { Str } from '../../utils'; export class HyperServer { protected hyper: HyperExpress.Server; @@ -68,9 +73,52 @@ export class HyperServer { } } + this.configureStaticServer(); + return this.hyper; } + configureStaticServer() { + const staticServeConfig = ConfigService.get('http.staticServe'); + if (!staticServeConfig.filePath || !staticServeConfig.httpPath) return; + + const liveAssets = new LiveDirectory(staticServeConfig.filePath, { + static: true, + ...staticServeConfig, + }); + + const httpPath = join( + '/', + staticServeConfig.httpPath.replace('*', ''), + '/*', + ); + + this.hyper.get(httpPath, (req, res) => { + const path = Str.replaceFirst( + req.path.replace(join('/', staticServeConfig.httpPath), ''), + '/', + '', + ); + + const file = liveAssets.get(path); + + // Return a 404 if no asset/file exists on the derived path + if (file === undefined) { + throw new FileNotFoundException(`File at ${path} does not exist`); + } + + const fileParts = file.path.split('.'); + const extension = fileParts[fileParts.length - 1]; + + const content = file.content; + if (content instanceof Buffer) { + return res.type(extension).send(content); + } else { + return res.type(extension).stream(content); + } + }); + } + composeMiddlewares(path: string, method: string): MiddlewareHandler[] { const methodBasedRouteKey = `${method}:${path}`; const routeKey = `*:${path}`; diff --git a/packages/core/lib/serviceProvider.ts b/packages/core/lib/serviceProvider.ts index 50c72a6..e557e2c 100644 --- a/packages/core/lib/serviceProvider.ts +++ b/packages/core/lib/serviceProvider.ts @@ -18,7 +18,7 @@ import { StorageService } from './storage/service'; import { BuildProjectCommand } from './dev-server/build'; import { DevServerCommand } from './dev-server/serve'; import { CONFIG_FACTORY, ConfigBuilder, ConfigService } from './config'; -// import { ListRouteCommand } from './console/commands/route-list'; +import { ListRouteCommand } from './console/commands/route-list'; export const IntentProvidersFactory = ( config: any[], @@ -42,7 +42,7 @@ export const IntentProvidersFactory = ( QueueService, QueueConsoleCommands, QueueMetadata, - // ListRouteCommand, + ListRouteCommand, // CodegenCommand, // CodegenService, ViewConfigCommand, diff --git a/packages/core/package.json b/packages/core/package.json index f86b348..7cb7b78 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -81,8 +81,8 @@ "helmet": "^7.1.0", "ioredis": "^5.3.2", "knex": "^3.1.0", + "live-directory": "^3.0.3", "ms": "^2.1.3", - "mute-stdout": "^2.0.0", "objection": "^3.1.4", "picocolors": "^1.1.0", "react-email": "^3.0.1", From a388d968fd416e20b76067f12c8bd0f66fabba64 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 1 Dec 2024 00:11:56 +0530 Subject: [PATCH 22/27] fix(core): bugs --- integrations/sample-app/app/boot/sp/app.ts | 1 - .../sample-app/app/http/controllers/icon.ts | 3 --- integrations/sample-app/app/http/kernel.ts | 6 ++--- .../core/lib/console/commands/route-list.ts | 1 - packages/core/lib/rest/foundation/server.ts | 8 +++++-- .../core/lib/rest/http-server/decorators.ts | 2 +- .../lib/rest/http-server/route-explorer.ts | 23 +++++++++++-------- packages/core/lib/serviceProvider.ts | 2 -- 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/integrations/sample-app/app/boot/sp/app.ts b/integrations/sample-app/app/boot/sp/app.ts index 1bf6686..5587b26 100644 --- a/integrations/sample-app/app/boot/sp/app.ts +++ b/integrations/sample-app/app/boot/sp/app.ts @@ -25,7 +25,6 @@ export class AppServiceProvider extends ServiceProvider { * Read more - https://tryintent.com/docs/providers#class-based-providers */ this.bindWithClass('USER_DB_REPO', UserDbRepository); - this.bind(IntentController); this.bind(QueueJobs); } diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 600c9bc..27bfc05 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -139,9 +139,6 @@ export class IntentController { ); return new StreamableFile(readStream, { type: 'image/jpeg' }); - // return res.stream(new StreamableFile(readStream, { type: 'image/jpeg' })); - - return { hello: 'world from POST /json' }; } @Post('/multipart') diff --git a/integrations/sample-app/app/http/kernel.ts b/integrations/sample-app/app/http/kernel.ts index 6c5a89a..b54fa38 100644 --- a/integrations/sample-app/app/http/kernel.ts +++ b/integrations/sample-app/app/http/kernel.ts @@ -21,7 +21,7 @@ export class HttpKernel extends Kernel { * Read more - https://tryintent.com/docs/controllers */ public controllers(): Type[] { - return [UserController, AuthController]; + return [UserController, AuthController, IntentController]; } /** @@ -66,8 +66,8 @@ export class HttpKernel extends Kernel { } /** - * @param app + * @param server */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async boot(app: Server): Promise {} + public async boot(server: Server): Promise {} } diff --git a/packages/core/lib/console/commands/route-list.ts b/packages/core/lib/console/commands/route-list.ts index ab0a19d..d0d767f 100644 --- a/packages/core/lib/console/commands/route-list.ts +++ b/packages/core/lib/console/commands/route-list.ts @@ -14,7 +14,6 @@ export class ListRouteCommand { const routes = routeExplorer.explorePlainRoutes(ds, ms); const formattedRows = columnify(routes, { padStart: 2 }); - console.log(formattedRows); return 1; } diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index e21964c..7edbe04 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -117,10 +117,13 @@ export class IntentHttpServer { process.on(signal, () => this.shutdown(server, signal)); } - this.printToConsole(config); + this.printToConsole(config, [['➜', 'routes scanned', routes.length + '']]); } - printToConsole(config: ConfigService) { + printToConsole( + config: ConfigService, + extraInfo: [string, string, string][] = [], + ) { const port = config.get('app.port'); const hostname = config.get('app.hostname'); const environment = config.get('app.env'); @@ -131,6 +134,7 @@ export class IntentHttpServer { ['➜', 'debug', debug], ['➜', 'hostname', hostname], ['➜', 'port', port], + ...extraInfo, ]); const url = new URL( diff --git a/packages/core/lib/rest/http-server/decorators.ts b/packages/core/lib/rest/http-server/decorators.ts index 4a79e7d..6a2a798 100644 --- a/packages/core/lib/rest/http-server/decorators.ts +++ b/packages/core/lib/rest/http-server/decorators.ts @@ -33,7 +33,7 @@ function createRouteDecorators( ): MethodDecorator { return function (target: object, key?: string | symbol, descriptor?: any) { Reflect.defineMetadata(METHOD_KEY, method, target, key); - Reflect.defineMetadata(METHOD_PATH, path, target, key); + Reflect.defineMetadata(METHOD_PATH, path || '', target, key); return descriptor; }; } diff --git a/packages/core/lib/rest/http-server/route-explorer.ts b/packages/core/lib/rest/http-server/route-explorer.ts index 68c02c8..fb9321a 100644 --- a/packages/core/lib/rest/http-server/route-explorer.ts +++ b/packages/core/lib/rest/http-server/route-explorer.ts @@ -40,13 +40,13 @@ export class RouteExplorer { const providers = this.discoveryService.getProviders(); for (const provider of providers) { const { instance } = provider; - // if ( - // !instance || - // typeof instance === 'string' || - // !Object.getPrototypeOf(instance) - // ) { - // return; - // } + // if ( + // !instance || + // typeof instance === 'string' || + // !Object.getPrototypeOf(instance) + // ) { + // return; + // } const methodNames = this.metadataScanner.getAllMethodNames(instance); for (const methodName of methodNames) { @@ -99,6 +99,11 @@ export class RouteExplorer { const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); + if (!pathMethod) return; + + console.log(instance.constructor); + console.log(controllerKey, methodPath, pathMethod, key); + const fullHttpPath = join(controllerKey, methodPath); return { method: pathMethod, path: fullHttpPath }; } @@ -112,12 +117,12 @@ export class RouteExplorer { CONTROLLER_KEY, instance.constructor, ); - if (!controllerKey) return; + if (controllerKey === undefined) return; + const pathMethod = Reflect.getMetadata(METHOD_KEY, instance, key); const methodPath = Reflect.getMetadata(METHOD_PATH, instance, key); if (!pathMethod) return; - const controllerGuards = Reflect.getMetadata( GUARD_KEY, instance.constructor, diff --git a/packages/core/lib/serviceProvider.ts b/packages/core/lib/serviceProvider.ts index e557e2c..e241c20 100644 --- a/packages/core/lib/serviceProvider.ts +++ b/packages/core/lib/serviceProvider.ts @@ -18,7 +18,6 @@ import { StorageService } from './storage/service'; import { BuildProjectCommand } from './dev-server/build'; import { DevServerCommand } from './dev-server/serve'; import { CONFIG_FACTORY, ConfigBuilder, ConfigService } from './config'; -import { ListRouteCommand } from './console/commands/route-list'; export const IntentProvidersFactory = ( config: any[], @@ -42,7 +41,6 @@ export const IntentProvidersFactory = ( QueueService, QueueConsoleCommands, QueueMetadata, - ListRouteCommand, // CodegenCommand, // CodegenService, ViewConfigCommand, From 1f2c520c6780682403d325648832eae4f6696894 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 1 Dec 2024 01:21:14 +0530 Subject: [PATCH 23/27] fix(core): bugs --- .../sample-app/app/http/controllers/app.ts | 8 ++++++-- .../sample-app/app/http/controllers/icon.ts | 2 ++ integrations/sample-app/config/app.ts | 14 ++------------ integrations/sample-app/config/auth.ts | 4 ++-- integrations/sample-app/config/cache.ts | 4 ++-- integrations/sample-app/config/database.ts | 4 ++-- integrations/sample-app/config/http.ts | 15 ++------------- integrations/sample-app/config/localization.ts | 4 ++-- integrations/sample-app/config/logger.ts | 4 ++-- integrations/sample-app/config/mailer.ts | 4 ++-- integrations/sample-app/config/queue.ts | 4 ++-- integrations/sample-app/config/storage.ts | 4 ++-- package-lock.json | 4 ++-- packages/core/lib/config/register-namespace.ts | 2 +- packages/core/lib/interfaces/config.ts | 2 -- packages/core/lib/rest/foundation/server.ts | 18 +++++++++++++++--- packages/core/lib/utils/helpers.ts | 9 +++++++++ packages/core/package.json | 4 ++-- packages/hyper-express/package.json | 6 ++++-- 19 files changed, 61 insertions(+), 55 deletions(-) diff --git a/integrations/sample-app/app/http/controllers/app.ts b/integrations/sample-app/app/http/controllers/app.ts index 32aeae2..aa7910a 100644 --- a/integrations/sample-app/app/http/controllers/app.ts +++ b/integrations/sample-app/app/http/controllers/app.ts @@ -7,7 +7,11 @@ export class UserController { @Get() async getHello(@Req() req: Request) { - console.log(req.body); - return this.service.getHello(); + return { hello: 'Intent' }; + } + + @Get('hello') + async getHello2(@Req() req: Request) { + return { hello: 'Intent' }; } } diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 27bfc05..1b4a5e1 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -139,6 +139,8 @@ export class IntentController { ); return new StreamableFile(readStream, { type: 'image/jpeg' }); + + return { hello: 'world from POST /json' }; } @Post('/multipart') diff --git a/integrations/sample-app/config/app.ts b/integrations/sample-app/config/app.ts index f83ffa3..81ee3ff 100644 --- a/integrations/sample-app/config/app.ts +++ b/integrations/sample-app/config/app.ts @@ -1,11 +1,11 @@ import { AppConfig, - registerNamespace, + configNamespace, toBoolean, ValidationErrorSerializer, } from '@intentjs/core'; -export default registerNamespace( +export default configNamespace( 'app', (): AppConfig => ({ /** @@ -69,16 +69,6 @@ export default registerNamespace( */ port: +process.env.APP_PORT || 5000, - /** - * ----------------------------------------------------- - * Cross Origin Resource Sharing - * ----------------------------------------------------- - * - * You can use this setting to define the CORS rule of - * your application. - */ - cors: { origin: true }, - error: { /** * ----------------------------------------------------- diff --git a/integrations/sample-app/config/auth.ts b/integrations/sample-app/config/auth.ts index 1f3d070..87d7172 100644 --- a/integrations/sample-app/config/auth.ts +++ b/integrations/sample-app/config/auth.ts @@ -1,6 +1,6 @@ -import { registerNamespace } from '@intentjs/core'; +import { configNamespace } from '@intentjs/core'; -export default registerNamespace('auth', () => ({ +export default configNamespace('auth', () => ({ /** * ----------------------------------------------------- * JWT SECRET diff --git a/integrations/sample-app/config/cache.ts b/integrations/sample-app/config/cache.ts index f9cf6fd..1092ec5 100644 --- a/integrations/sample-app/config/cache.ts +++ b/integrations/sample-app/config/cache.ts @@ -1,6 +1,6 @@ -import { CacheOptions, registerNamespace } from '@intentjs/core'; +import { CacheOptions, configNamespace } from '@intentjs/core'; -export default registerNamespace( +export default configNamespace( 'cache', (): CacheOptions => ({ /** diff --git a/integrations/sample-app/config/database.ts b/integrations/sample-app/config/database.ts index c108494..851acdb 100644 --- a/integrations/sample-app/config/database.ts +++ b/integrations/sample-app/config/database.ts @@ -1,7 +1,7 @@ -import { DatabaseOptions, registerNamespace } from '@intentjs/core'; +import { DatabaseOptions, configNamespace } from '@intentjs/core'; import { knexSnakeCaseMappers } from 'objection'; -export default registerNamespace( +export default configNamespace( 'db', (): DatabaseOptions => ({ isGlobal: true, diff --git a/integrations/sample-app/config/http.ts b/integrations/sample-app/config/http.ts index 5a6478a..e6c889c 100644 --- a/integrations/sample-app/config/http.ts +++ b/integrations/sample-app/config/http.ts @@ -1,20 +1,9 @@ -import { findProjectRoot, HttpConfig, registerNamespace } from '@intentjs/core'; +import { findProjectRoot, HttpConfig, configNamespace } from '@intentjs/core'; import { join } from 'path'; -export default registerNamespace( +export default configNamespace( 'http', (): HttpConfig => ({ - /** - * ----------------------------------------------------- - * Parser - * ----------------------------------------------------- - * - * This value is the name of your application. This value is - * used when the framework needs to place the application's - * name in a notification or any other location as required. - */ - parsers: ['json', 'formdata', 'plain', 'urlencoded'], - cors: { origin: true, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], diff --git a/integrations/sample-app/config/localization.ts b/integrations/sample-app/config/localization.ts index 86e9ed8..1c67111 100644 --- a/integrations/sample-app/config/localization.ts +++ b/integrations/sample-app/config/localization.ts @@ -1,11 +1,11 @@ import { findProjectRoot, LocalizationOptions, - registerNamespace, + configNamespace, } from '@intentjs/core'; import { join } from 'path'; -export default registerNamespace( +export default configNamespace( 'localization', (): LocalizationOptions => ({ /** diff --git a/integrations/sample-app/config/logger.ts b/integrations/sample-app/config/logger.ts index 573e4b2..7ad96d8 100644 --- a/integrations/sample-app/config/logger.ts +++ b/integrations/sample-app/config/logger.ts @@ -2,12 +2,12 @@ import { Formats, IntentLoggerOptions, LogLevel, - registerNamespace, + configNamespace, toBoolean, Transports, } from '@intentjs/core'; -export default registerNamespace( +export default configNamespace( 'logger', (): IntentLoggerOptions => ({ /** diff --git a/integrations/sample-app/config/mailer.ts b/integrations/sample-app/config/mailer.ts index 69f3a0f..eec5c5d 100644 --- a/integrations/sample-app/config/mailer.ts +++ b/integrations/sample-app/config/mailer.ts @@ -1,6 +1,6 @@ -import { MailerOptions, registerNamespace } from '@intentjs/core'; +import { MailerOptions, configNamespace } from '@intentjs/core'; -export default registerNamespace( +export default configNamespace( 'mailers', (): MailerOptions => ({ /** diff --git a/integrations/sample-app/config/queue.ts b/integrations/sample-app/config/queue.ts index 3f9b357..8870733 100644 --- a/integrations/sample-app/config/queue.ts +++ b/integrations/sample-app/config/queue.ts @@ -1,6 +1,6 @@ -import { QueueOptions, registerNamespace } from '@intentjs/core'; +import { QueueOptions, configNamespace } from '@intentjs/core'; -export default registerNamespace('queue', (): QueueOptions => { +export default configNamespace('queue', (): QueueOptions => { return { /** * ----------------------------------------------------- diff --git a/integrations/sample-app/config/storage.ts b/integrations/sample-app/config/storage.ts index a83952c..6ae33a7 100644 --- a/integrations/sample-app/config/storage.ts +++ b/integrations/sample-app/config/storage.ts @@ -1,12 +1,12 @@ import { fromIni } from '@aws-sdk/credential-providers'; import { findProjectRoot, - registerNamespace, + configNamespace, StorageOptions, } from '@intentjs/core'; import { join } from 'path'; -export default registerNamespace( +export default configNamespace( 'filesystem', (): StorageOptions => ({ diff --git a/package-lock.json b/package-lock.json index 63896b1..41b782d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17789,7 +17789,7 @@ "version": "0.1.35", "license": "MIT", "dependencies": { - "@intentjs/hyper-express": "^6.17.2-beta-2", + "@intentjs/hyper-express": "^0.0.1", "@nestjs/common": "^10.4.8", "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", @@ -18059,7 +18059,7 @@ }, "packages/hyper-express": { "name": "@intentjs/hyper-express", - "version": "6.17.2-beta-2", + "version": "0.0.1", "license": "MIT", "dependencies": { "busboy": "^1.6.0", diff --git a/packages/core/lib/config/register-namespace.ts b/packages/core/lib/config/register-namespace.ts index 8af11c7..9b66ec3 100644 --- a/packages/core/lib/config/register-namespace.ts +++ b/packages/core/lib/config/register-namespace.ts @@ -7,7 +7,7 @@ import { // eslint-disable-next-line @typescript-eslint/no-var-requires require('dotenv').config(); -export const registerNamespace = ( +export const configNamespace = ( namespace: LiteralString, factory: () => T | Promise, options?: RegisterNamespaceOptions, diff --git a/packages/core/lib/interfaces/config.ts b/packages/core/lib/interfaces/config.ts index 80adcc6..04fd059 100644 --- a/packages/core/lib/interfaces/config.ts +++ b/packages/core/lib/interfaces/config.ts @@ -19,7 +19,6 @@ export interface AppConfig { url: string; hostname?: string; port: number; - cors: CorsOptions | CorsOptionsDelegate; error?: { validationErrorSerializer?: GenericClass; }; @@ -35,7 +34,6 @@ export type RequestParsers = | 'binary'; export interface HttpConfig { - parsers: RequestParsers[]; cors?: CorsOptions | CorsOptionsDelegate; server?: ServerConstructorOptions; staticServe?: { diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index 7edbe04..e07124e 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -9,7 +9,7 @@ import { ConfigService } from '../../config/service'; import { IntentExceptionFilter } from '../../exceptions'; import { IntentAppContainer, ModuleBuilder } from '../../foundation'; import { Type } from '../../interfaces'; -import { Obj, Package } from '../../utils'; +import { findProjectRoot, getPackageJson, Obj, Package } from '../../utils'; import { Kernel } from '../foundation/kernel'; import pc from 'picocolors'; import { printBulletPoints } from '../../utils/console-helpers'; @@ -22,6 +22,8 @@ import { HttpExecutionContext } from '../http-server/contexts/http-execution-con import { ExecutionContext } from '../http-server/contexts/execution-context'; import { Response } from '../http-server/response'; import { RouteExplorer } from '../http-server/route-explorer'; +import { readSync } from 'fs-extra'; +import { join } from 'path'; const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; @@ -124,21 +126,31 @@ export class IntentHttpServer { config: ConfigService, extraInfo: [string, string, string][] = [], ) { + console.clear(); + console.log(); const port = config.get('app.port'); const hostname = config.get('app.hostname'); const environment = config.get('app.env'); const debug = config.get('app.debug'); + try { + const { dependencies } = getPackageJson(); + console.log( + ` ${pc.bold(pc.green('Intent'))} ${pc.green(dependencies['@intentjs/core'])}`, + ); + console.log(); + } catch {} + printBulletPoints([ ['➜', 'environment', environment], ['➜', 'debug', debug], - ['➜', 'hostname', hostname], + ['➜', 'hostname', hostname ?? '127.0.0.1'], ['➜', 'port', port], ...extraInfo, ]); const url = new URL( - ['127.0.0.1', '0.0.0.0'].includes(hostname) + ['127.0.0.1', 'localhost', undefined].includes(hostname) ? 'http://localhost' : `http://${hostname}`, ); diff --git a/packages/core/lib/utils/helpers.ts b/packages/core/lib/utils/helpers.ts index d4dd262..dc573aa 100644 --- a/packages/core/lib/utils/helpers.ts +++ b/packages/core/lib/utils/helpers.ts @@ -7,6 +7,9 @@ import { Arr } from './array'; import { InternalLogger } from './logger'; import { Obj } from './object'; import { Str } from './string'; +import { readFileSync } from 'fs-extra'; +import { join } from 'path'; +import { findProjectRoot } from './path'; export const isEmpty = (value: any) => { if (Str.isString(value)) { @@ -149,3 +152,9 @@ export const isClass = (obj: any) => { // This tells us if it was created by a class or Object literal return Object.getPrototypeOf(obj).constructor.name !== 'Object'; }; + +export const getPackageJson = () => { + return JSON.parse( + readFileSync(join(findProjectRoot(), 'package.json')).toString(), + ); +}; diff --git a/packages/core/package.json b/packages/core/package.json index 7cb7b78..fa9ce30 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@intentjs/core", - "version": "0.1.35", + "version": "0.1.35-next-5", "description": "Core module for Intent", "repository": { "type": "git", @@ -64,7 +64,7 @@ "typescript": "^5.5.2" }, "dependencies": { - "@intentjs/hyper-express": "^6.17.2-beta-2", + "@intentjs/hyper-express": "^0.0.1", "@nestjs/common": "^10.4.8", "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", diff --git a/packages/hyper-express/package.json b/packages/hyper-express/package.json index 884f7bf..878ea90 100644 --- a/packages/hyper-express/package.json +++ b/packages/hyper-express/package.json @@ -1,11 +1,13 @@ { "name": "@intentjs/hyper-express", - "version": "6.17.2-beta-2", + "version": "0.0.1", "description": "A fork of hyper-express to suit IntentJS requirements. High performance Node.js webserver with a simple-to-use API powered by uWebsockets.js under the hood.", "main": "index.js", "types": "./types/index.d.ts", "scripts": { - "test": "node tests/index.js" + "test": "node tests/index.js", + "publish:npm": "npm publish --access public", + "publish:next": "npm publish --access public --tag next" }, "repository": { "type": "git", From af1e4259e4f7075ab90ac3851d0bcd4e9b957ca2 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 1 Dec 2024 22:58:24 +0530 Subject: [PATCH 24/27] feat(hyper-express): add utility methods in request --- packages/hyper-express/package.json | 2 +- .../src/components/http/Request.js | 49 ++++++++++++++++++- .../types/components/http/Request.d.ts | 6 +++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/hyper-express/package.json b/packages/hyper-express/package.json index 878ea90..f29ba7e 100644 --- a/packages/hyper-express/package.json +++ b/packages/hyper-express/package.json @@ -1,6 +1,6 @@ { "name": "@intentjs/hyper-express", - "version": "0.0.1", + "version": "0.0.3", "description": "A fork of hyper-express to suit IntentJS requirements. High performance Node.js webserver with a simple-to-use API powered by uWebsockets.js under the hood.", "main": "index.js", "types": "./types/index.d.ts", diff --git a/packages/hyper-express/src/components/http/Request.js b/packages/hyper-express/src/components/http/Request.js index 87a22ed..8c699e7 100644 --- a/packages/hyper-express/src/components/http/Request.js +++ b/packages/hyper-express/src/components/http/Request.js @@ -1012,7 +1012,7 @@ class Request { } _all_input; - async all() { + all() { if (this._all_input) return this._all_input; this._all_input = { @@ -1024,6 +1024,53 @@ class Request { return this._all_input; } + input(key, defaultValue) { + const all = this.all(); + return all[key] ?? defaultValue; + } + + string(key, defaultValue) { + const all = this.all(); + return String(all[key] ?? defaultValue); + } + + number(key, defaultValue) { + const all = this.all(); + return Number(all[key] ?? defaultValue); + } + + boolean(key) { + const all = this.all(); + return [1, '1', true, 'true', 'yes', 'on'].includes(all[key]); + } + + has(...keys) { + const payload = this.all(); + for (const key of keys) { + if (!(key in payload)) return false; + } + + return true; + } + + hasAny(...keys) { + const payload = this.all(); + for (const key of keys) { + if (key in payload) return true; + } + + return false; + } + + missing(...keys) { + const payload = this.all(); + for (const key of keys) { + if (key in payload) return false; + } + + return true; + } + hasHeader(name) { return name in this.headers; } diff --git a/packages/hyper-express/types/components/http/Request.d.ts b/packages/hyper-express/types/components/http/Request.d.ts index 6fbd4e4..41555cf 100644 --- a/packages/hyper-express/types/components/http/Request.d.ts +++ b/packages/hyper-express/types/components/http/Request.d.ts @@ -226,6 +226,12 @@ export class Request extends Readable { processBody(): Promise; all(): Promise>; + input: (name: string, defaultValue?: T) => T; + string: (name: string) => string; + number: (name: string) => number; + boolean: (name: string) => boolean; + has: (...keys: string[]) => boolean; + hasAny: (...keys: string[]) => boolean; hasHeader(name: string): boolean; bearerToken(): string; httpHost(): string; From 1eaa3032aa551eb9b3e347f177accc9d3b80a10a Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 1 Dec 2024 23:00:30 +0530 Subject: [PATCH 25/27] feat(hyper-express): add utility methods in request --- packages/hyper-express/package.json | 2 +- packages/hyper-express/src/components/http/Request.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hyper-express/package.json b/packages/hyper-express/package.json index f29ba7e..490e6e7 100644 --- a/packages/hyper-express/package.json +++ b/packages/hyper-express/package.json @@ -1,6 +1,6 @@ { "name": "@intentjs/hyper-express", - "version": "0.0.3", + "version": "0.0.4", "description": "A fork of hyper-express to suit IntentJS requirements. High performance Node.js webserver with a simple-to-use API powered by uWebsockets.js under the hood.", "main": "index.js", "types": "./types/index.d.ts", diff --git a/packages/hyper-express/src/components/http/Request.js b/packages/hyper-express/src/components/http/Request.js index 8c699e7..4e47e7b 100644 --- a/packages/hyper-express/src/components/http/Request.js +++ b/packages/hyper-express/src/components/http/Request.js @@ -1115,7 +1115,7 @@ class Request { } expectsJson() { - return this.accepts().includes(EXTENSTION_TO_MIME['json']); + return this.accepts().includes('application/json'); } setUser(user) { From 8692ef814eafa7128bb70000d216110fc6d598a4 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 1 Dec 2024 23:18:15 +0530 Subject: [PATCH 26/27] fix(core): renamed useguard decorator to usegaurds --- .../sample-app/app/http/controllers/icon.ts | 16 ++++++++-------- packages/core/lib/rest/http-server/decorators.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/integrations/sample-app/app/http/controllers/icon.ts b/integrations/sample-app/app/http/controllers/icon.ts index 1b4a5e1..7bf5ec4 100644 --- a/integrations/sample-app/app/http/controllers/icon.ts +++ b/integrations/sample-app/app/http/controllers/icon.ts @@ -16,7 +16,7 @@ import { Res, Response, StreamableFile, - UseGuard, + UseGuards, UserAgent, Validate, } from '@intentjs/core'; @@ -27,7 +27,7 @@ import { join } from 'path'; import { LoginDto } from 'app/validators/auth'; @Controller('/icon') -@UseGuard(CustomGuard) +@UseGuards(CustomGuard) export class IntentController { public service: any; @@ -36,7 +36,7 @@ export class IntentController { } @Get('/:name') - @UseGuard(CustomGuard) + @UseGuards(CustomGuard) async getHello( // @Req() req: Request, // @Param('name') name: string, @@ -80,14 +80,14 @@ export class IntentController { } @Get('/plain-with-query-param') - @UseGuard(CustomGuard) + @UseGuards(CustomGuard) async plainWithQueryParam(@Req() req: Request) { console.log(req); return { hello: 'world' }; } @Get('/:id') - @UseGuard(CustomGuard) + @UseGuards(CustomGuard) async plainWithPathParam(@Req() req: Request) { console.log(req); return { hello: 'world' }; @@ -144,19 +144,19 @@ export class IntentController { } @Post('/multipart') - @UseGuard(CustomGuard) + @UseGuards(CustomGuard) async postHello(@Req() req: Request) { return { hello: 'world' }; } @Post('/form-data') - @UseGuard(CustomGuard) + @UseGuards(CustomGuard) async postFormData(@Req() req: Request) { return { hello: 'world' }; } @Post('/binary') - @UseGuard(CustomGuard) + @UseGuards(CustomGuard) async postBinary(@Req() req: Request) { return { hello: 'world' }; } diff --git a/packages/core/lib/rest/http-server/decorators.ts b/packages/core/lib/rest/http-server/decorators.ts index 6a2a798..f6bd5f2 100644 --- a/packages/core/lib/rest/http-server/decorators.ts +++ b/packages/core/lib/rest/http-server/decorators.ts @@ -81,7 +81,7 @@ export const ANY: RouteDecoratorType = ( options?: ControllerOptions, ) => createRouteDecorators(HttpMethods.ANY, path, options); -export const UseGuard = (...guards: Type[]) => { +export const UseGuards = (...guards: Type[]) => { return function (target: object, key?: string | symbol, descriptor?: any) { if (key) { Reflect.defineMetadata(GUARD_KEY, guards, target, key); From 66e40a016260ddc6d0da151fd5707f7ca6eee374 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Mon, 2 Dec 2024 00:27:51 +0530 Subject: [PATCH 27/27] fix(core): bugs --- package-lock.json | 6 +++--- packages/core/lib/rest/http-server/index.ts | 2 +- packages/core/lib/validator/index.ts | 4 ++-- packages/core/package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 41b782d..54240f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17786,10 +17786,10 @@ }, "packages/core": { "name": "@intentjs/core", - "version": "0.1.35", + "version": "0.1.35-next-5", "license": "MIT", "dependencies": { - "@intentjs/hyper-express": "^0.0.1", + "@intentjs/hyper-express": "^0.0.4", "@nestjs/common": "^10.4.8", "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25", @@ -18059,7 +18059,7 @@ }, "packages/hyper-express": { "name": "@intentjs/hyper-express", - "version": "0.0.1", + "version": "0.0.4", "license": "MIT", "dependencies": { "busboy": "^1.6.0", diff --git a/packages/core/lib/rest/http-server/index.ts b/packages/core/lib/rest/http-server/index.ts index bf3691b..7460dbe 100644 --- a/packages/core/lib/rest/http-server/index.ts +++ b/packages/core/lib/rest/http-server/index.ts @@ -8,4 +8,4 @@ export * from './streamable-file'; export * from './status-codes'; export * from './param-decorators'; export * from './interfaces'; -export { Request, Response } from '@intentjs/hyper-express'; +export { Request, Response, MiddlewareNext } from '@intentjs/hyper-express'; diff --git a/packages/core/lib/validator/index.ts b/packages/core/lib/validator/index.ts index 1c8e928..abf3e82 100644 --- a/packages/core/lib/validator/index.ts +++ b/packages/core/lib/validator/index.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '../reflections/apply-decorators'; import { SetMetadata } from '../reflections/set-metadata'; -import { UseGuard } from '../rest'; +import { UseGuards } from '../rest'; import { IntentValidationGuard } from './validation-guard'; export * from './validator'; @@ -9,6 +9,6 @@ export * from './decorators'; export function Validate(DTO: any) { return applyDecorators( SetMetadata('dtoSchema', DTO), - UseGuard(IntentValidationGuard), + UseGuards(IntentValidationGuard), ); } diff --git a/packages/core/package.json b/packages/core/package.json index fa9ce30..b4caf2a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -64,7 +64,7 @@ "typescript": "^5.5.2" }, "dependencies": { - "@intentjs/hyper-express": "^0.0.1", + "@intentjs/hyper-express": "^0.0.4", "@nestjs/common": "^10.4.8", "@nestjs/core": "^10.4.8", "@react-email/components": "^0.0.25",