├── .eslintignore
├── common
├── shields
│ └── spring
│ │ └── jwt-auth.shield.ts
├── types
│ ├── dolph_port.type.ts
│ ├── media_types.type.ts
│ ├── dolph_env.type.ts
│ ├── validators.types.ts
│ ├── dolph_middleware.type.ts
│ ├── mongoose_types.type.ts
│ ├── auth.type.ts
│ ├── response.type.ts
│ ├── events.type.ts
│ ├── index.ts
│ ├── hash_param.type.ts
│ ├── spring.types.ts
│ ├── dolph.types.ts
│ └── dolph_uploader.type.ts
├── constants
│ ├── index.ts
│ ├── messages.constant.ts
│ └── error_constants.types.ts
├── api
│ ├── HttpStatus.api.ts
│ ├── index.ts
│ ├── ErrorException.api.ts
│ ├── exceptions
│ │ ├── gone.api.ts
│ │ ├── conflict.api.ts
│ │ ├── forbidden.api.ts
│ │ ├── no_content.api.ts
│ │ ├── not_found.api.ts
│ │ ├── bad_gateway.api.ts
│ │ ├── bad_request.api.ts
│ │ ├── im_a_teapot.api.ts
│ │ ├── not_modified.api.ts
│ │ ├── timeout.api.ts
│ │ ├── unauthorized.api.ts
│ │ ├── not_acceptable.api.ts
│ │ ├── misdirected.api.ts
│ │ ├── not_implemented.api.ts
│ │ ├── payment_required.api.ts
│ │ ├── method_not_allowed.api.ts
│ │ ├── service_unavailable.api.ts
│ │ ├── internal_server_error.ts
│ │ ├── unsupported_media_type.api.ts
│ │ ├── http_version_not_suported.api.ts
│ │ ├── default_exception.api.ts
│ │ └── index.ts
│ ├── pick.api.ts
│ ├── SuccessRespone.api.ts
│ └── ErrorResponse.api.ts
├── index.ts
├── interfaces
│ ├── index.ts
│ ├── mongoose.interface.ts
│ ├── jwt.interfaces.ts
│ ├── media_parser.interfaces.ts
│ ├── socket.interfaces.ts
│ └── dolph.interfaces.ts
└── middlewares
│ ├── index.ts
│ ├── jwt_auth.middleware.ts
│ ├── default_middleware.middleware.ts
│ ├── validate_request_middleware.middleware.ts
│ ├── try_catch_middleware.middleware.ts
│ └── global_validation_middleware.middlewares.ts
├── packages
├── spring
│ └── spring_module.packages.ts
├── memdb
│ ├── index.ts
│ └── memdb.packages.ts
├── sequelize
│ ├── index.ts
│ └── sequelize_connection.package.ts
├── sockets
│ ├── socket_component.packages.ts
│ ├── index.ts
│ └── socket_io.packages.ts
├── mongoose
│ ├── index.ts
│ ├── mongoose_connection.package.ts
│ └── transform_doc_plugin.package.ts
├── index.ts
├── uploader
│ ├── counter.ts
│ ├── errors
│ │ └── error_messages.ts
│ ├── remove_uploaded_files.ts
│ ├── index.ts
│ ├── file_appender.ts
│ ├── file_uploader.ts
│ └── make_middleware.ts
└── events
│ └── events_module.packages.ts
├── core
├── initializers
│ ├── service_registeries.core.ts
│ ├── index.ts
│ ├── middleware_registrar.ts
│ └── dependency_injection.core.ts
├── adapters
│ ├── index.ts
│ ├── exception_handlers.ts
│ └── mvc_registrar.ts
├── index.ts
├── config.core.ts
├── fallback_middleware.core.ts
├── morgan.core.ts
├── error.core.ts
└── transformer.ts
├── .lintstagedrc.json
├── decorators
├── events
│ ├── index.ts
│ └── event.decorator.ts
├── dolph
│ ├── index.ts
│ └── dolph_service.decorator.ts
├── sockets
│ ├── index.ts
│ └── sockets.decorators.ts
├── mysql
│ ├── index.ts
│ └── mysql_initializer.decorators.ts
├── validations
│ ├── index.ts
│ └── validations.decorators.ts
├── mongoose
│ ├── index.ts
│ └── mongoose_service_intializer.decorators.ts
├── others
│ ├── index.ts
│ ├── try_catch_async.decorator.ts
│ └── cookie_auth_verify.decorators.ts
├── spring
│ ├── meta_data_keys.decorators.ts
│ └── index.ts
└── index.ts
├── utilities
├── media
│ ├── index.ts
│ └── file_extensions.utilities.ts
├── encryptions
│ ├── index.ts
│ └── bcrypt_encryption.utilities.ts
├── auth
│ ├── index.ts
│ ├── JWT_generator.utilities.ts
│ └── cookie.utilities.ts
├── validators
│ ├── index.ts
│ ├── objectId_validator.validator.ts
│ └── password_validator.validator.ts
├── index.ts
├── normalize_path.utilities.ts
├── get_controllers_from_component.ts
├── is_component.utilities.ts
├── formatters
│ └── format_date.utilities.ts
├── spring_helpers.utilities.ts
├── generate_unique_codes.utilities.ts
└── logger.utilities.ts
├── samples
├── demo
│ ├── dolph_config.yaml
│ ├── .gitignore
│ ├── dolph_cli.yaml
│ ├── src
│ │ ├── components
│ │ │ └── user
│ │ │ │ ├── user.model.ts
│ │ │ │ ├── user2.service.ts
│ │ │ │ ├── user.component.ts
│ │ │ │ ├── user.dto.ts
│ │ │ │ ├── user.service.ts
│ │ │ │ ├── user.shield.ts
│ │ │ │ ├── user2.controller.ts
│ │ │ │ └── user.controller.ts
│ │ ├── resolvers
│ │ │ ├── user.entity.ts
│ │ │ └── user.resolver.ts
│ │ └── server.ts
│ ├── .swcrc
│ ├── package.json
│ └── tsconfig.json
├── basic
│ ├── index.routes.ts
│ ├── sqlDb.ts
│ ├── app.auth_function.ts
│ ├── views
│ │ └── layouts
│ │ │ └── main.handlebars
│ ├── app.middleware.ts
│ ├── event_emitter.service.ts
│ ├── app.component.ts
│ ├── new.dto.ts
│ ├── test_event_service_dep.service.ts
│ ├── app.schema.ts
│ ├── socket.component.ts
│ ├── app.validator.ts
│ ├── app.model.ts
│ ├── another_event.service.ts
│ ├── new.service.ts
│ ├── event.service.ts
│ ├── app.server.ts
│ ├── app.service.ts
│ ├── app.router.ts
│ ├── new.controller.ts
│ └── app.controller.ts
└── tsconfig.json
├── views
├── about.handlebars
├── home.handlebars
├── about.ejs
├── home.ejs
├── partials
│ ├── header.pug
│ └── header.ejs
├── about.pug
└── home.pug
├── .gitattributes
├── tools
├── gulp
│ ├── gulpfile.ts
│ ├── config.tools.ts
│ ├── tsconfig.json
│ ├── tasks
│ │ ├── copy.tool.ts
│ │ └── clean.tool.ts
│ └── utilities
│ │ └── helpers.task.ts
└── benchmarks
│ └── basic.ts
├── .prettierignore
├── README.md
├── .prettierrc
├── tsconfig.spec.json
├── jest.setup.js
├── classes
├── index.ts
├── service_classes.class.ts
├── router_classes.class.ts
├── event_service_classes.class.ts
├── controller_classes.class.ts
└── jwt_auth_classes.class.ts
├── .editorconfig
├── .gitignore
├── renovate.json
├── index.ts
├── compile.sh
├── .npmignore
├── dolph_config.yaml
├── gulpfile.js
├── jest.config.js
├── tests
├── core.test.ts
├── middleware.test.ts
├── uploader
│ ├── memorystorage.test.ts
│ ├── diskstorage.test.ts
│ └── fileuploader.test.ts
├── fallback_middleware.test.ts
└── try_catch.test.ts
├── tsconfig.json
├── todo.txt
├── LICENSE
├── .eslintrc.js
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bin
3 |
--------------------------------------------------------------------------------
/common/shields/spring/jwt-auth.shield.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/spring/spring_module.packages.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/initializers/service_registeries.core.ts:
--------------------------------------------------------------------------------
1 | //
2 |
--------------------------------------------------------------------------------
/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.ts": "eslint"
3 | }
4 |
--------------------------------------------------------------------------------
/core/adapters/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mvc_registrar';
2 |
--------------------------------------------------------------------------------
/packages/memdb/index.ts:
--------------------------------------------------------------------------------
1 | export * from './memdb.packages';
2 |
--------------------------------------------------------------------------------
/decorators/events/index.ts:
--------------------------------------------------------------------------------
1 | export * from './event.decorator';
2 |
--------------------------------------------------------------------------------
/utilities/media/index.ts:
--------------------------------------------------------------------------------
1 | // export * from './parser.uilities';
2 |
--------------------------------------------------------------------------------
/decorators/dolph/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dolph_service.decorator';
2 |
--------------------------------------------------------------------------------
/decorators/sockets/index.ts:
--------------------------------------------------------------------------------
1 | export * from './sockets.decorators';
2 |
--------------------------------------------------------------------------------
/common/types/dolph_port.type.ts:
--------------------------------------------------------------------------------
1 | export type dolphPort = number | string;
2 |
--------------------------------------------------------------------------------
/decorators/mysql/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mysql_initializer.decorators';
2 |
--------------------------------------------------------------------------------
/decorators/validations/index.ts:
--------------------------------------------------------------------------------
1 | export * from './validations.decorators';
2 |
--------------------------------------------------------------------------------
/samples/demo/dolph_config.yaml:
--------------------------------------------------------------------------------
1 | port: 3000
2 | globalExceptionFilter: true
--------------------------------------------------------------------------------
/common/types/media_types.type.ts:
--------------------------------------------------------------------------------
1 | export type mediaType = 'single' | 'array';
2 |
--------------------------------------------------------------------------------
/packages/sequelize/index.ts:
--------------------------------------------------------------------------------
1 | export * from './sequelize_connection.package';
2 |
--------------------------------------------------------------------------------
/packages/sockets/socket_component.packages.ts:
--------------------------------------------------------------------------------
1 | export class SocketComponent {}
2 |
--------------------------------------------------------------------------------
/utilities/encryptions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './bcrypt_encryption.utilities';
2 |
--------------------------------------------------------------------------------
/views/about.handlebars:
--------------------------------------------------------------------------------
1 |
About Page
2 | Learn more about us here.
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | yarn.lock -diff -merge
2 | yarn.lock linguist-generated=true
3 |
--------------------------------------------------------------------------------
/views/home.handlebars:
--------------------------------------------------------------------------------
1 | Home Page
2 | Welcome to the {{ title}} page!
3 |
--------------------------------------------------------------------------------
/decorators/mongoose/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mongoose_service_intializer.decorators';
2 |
--------------------------------------------------------------------------------
/samples/demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | logs
4 | yarn-error.log
5 | app
6 | uploads/
--------------------------------------------------------------------------------
/tools/gulp/gulpfile.ts:
--------------------------------------------------------------------------------
1 | // import './tasks/clean.tool';
2 | import './tasks/copy.tool';
3 |
--------------------------------------------------------------------------------
/common/types/dolph_env.type.ts:
--------------------------------------------------------------------------------
1 | export type dolphEnv = 'development' | 'test' | 'production';
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | packages/**/*.d.ts
2 | packages/**/*.js
3 | .nyc_output
4 | node_modules/
5 | coverage/
--------------------------------------------------------------------------------
/common/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from './error_constants.types';
2 | export * from './messages.constant';
3 |
--------------------------------------------------------------------------------
/utilities/auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from './JWT_generator.utilities';
2 | export * from './cookie.utilities';
3 |
--------------------------------------------------------------------------------
/packages/sockets/index.ts:
--------------------------------------------------------------------------------
1 | export * from './socket_io.packages';
2 | export * from './socket_component.packages';
3 |
--------------------------------------------------------------------------------
/views/about.ejs:
--------------------------------------------------------------------------------
1 | <%- include('partials/header') %>
2 | About Page
3 | Learn more about us here.
4 |
--------------------------------------------------------------------------------
/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dolph_factory.core';
2 | export * from './initializers';
3 | export * from './adapters';
4 |
--------------------------------------------------------------------------------
/core/initializers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dependency_injection.core';
2 | export * from './middleware_registrar';
3 |
--------------------------------------------------------------------------------
/views/home.ejs:
--------------------------------------------------------------------------------
1 | <%- include('partials/header') %>
2 | <%=title %> Page
3 | Welcome to the home page!
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DolphJS Framework
2 |
3 | DolphJs runs on both Node and Bun.
4 |
5 | See [documentation](https://dolphjs.com)
6 |
--------------------------------------------------------------------------------
/decorators/others/index.ts:
--------------------------------------------------------------------------------
1 | export * from './cookie_auth_verify.decorators';
2 | export * from './try_catch_async.decorator';
3 |
--------------------------------------------------------------------------------
/packages/mongoose/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transform_doc_plugin.package';
2 | export * from './mongoose_connection.package';
3 |
--------------------------------------------------------------------------------
/utilities/validators/index.ts:
--------------------------------------------------------------------------------
1 | export * from './password_validator.validator';
2 | export * from './objectId_validator.validator';
3 |
--------------------------------------------------------------------------------
/views/partials/header.pug:
--------------------------------------------------------------------------------
1 | header
2 | h1 My MVC App With Pug
3 | nav
4 | a(href="/") Home
5 | a(href="/about") About
6 |
--------------------------------------------------------------------------------
/common/api/HttpStatus.api.ts:
--------------------------------------------------------------------------------
1 | import httpStatus from 'http-status';
2 |
3 | const HttpStatus = httpStatus;
4 | export { HttpStatus };
5 |
--------------------------------------------------------------------------------
/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api';
2 | export * from './middlewares';
3 | export * from './types';
4 | export * from './interfaces';
5 |
--------------------------------------------------------------------------------
/common/types/validators.types.ts:
--------------------------------------------------------------------------------
1 | type passwordStrength = 'basic' | 'medium' | 'strong' | 'extraStrong';
2 | export { passwordStrength };
3 |
--------------------------------------------------------------------------------
/samples/basic/index.routes.ts:
--------------------------------------------------------------------------------
1 | import { AppRouter } from './app.router';
2 |
3 | const routes = [new AppRouter()];
4 |
5 | export { routes };
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 125,
4 | "trailingComma": "all",
5 | "tabWidth": 4,
6 | "useTabs": false
7 | }
8 |
--------------------------------------------------------------------------------
/decorators/spring/meta_data_keys.decorators.ts:
--------------------------------------------------------------------------------
1 | export const SHIELD_METADATA_KEY = 'shield';
2 | export const UN_SHIELD_METADATA_KEY = 'unshield';
3 |
--------------------------------------------------------------------------------
/common/types/dolph_middleware.type.ts:
--------------------------------------------------------------------------------
1 | import { DRequestHandler } from '../interfaces';
2 |
3 | export type DolphMiddlewareFn = (middlewares: DRequestHandler[]) => void;
4 |
--------------------------------------------------------------------------------
/samples/basic/sqlDb.ts:
--------------------------------------------------------------------------------
1 | import { initMySql } from '../../packages';
2 |
3 | const mysql = initMySql('dolph', 'root', 'password', 'localhost');
4 |
5 | export { mysql };
6 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["integration/**/*", "**/*.spec.ts"],
4 | "exclude": ["node_modules", "dist"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mongoose';
2 | export * from './sequelize';
3 | export * from './memdb';
4 | export * from './sockets';
5 | export * from './uploader';
6 |
--------------------------------------------------------------------------------
/views/partials/header.ejs:
--------------------------------------------------------------------------------
1 |
2 | My MVC App With EJS
3 |
7 |
8 |
--------------------------------------------------------------------------------
/common/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './jwt.interfaces';
2 | export * from './mongoose.interface';
3 | export * from './dolph.interfaces';
4 | export * from './socket.interfaces';
5 |
--------------------------------------------------------------------------------
/views/about.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title About
5 | body
6 | include partials/header
7 | h1 About Page
8 | p Learn more about us here.
9 |
--------------------------------------------------------------------------------
/views/home.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title Home
5 | body
6 | include partials/header
7 | h1 #{title} Page
8 | p Welcome to the home page!
9 |
--------------------------------------------------------------------------------
/samples/demo/dolph_cli.yaml:
--------------------------------------------------------------------------------
1 | # this is an auto-generated file, please do not edit manually
2 | language: ts
3 | paradigm: oop
4 | database: mongo
5 | generateFolder: true
6 | routing: spring
7 |
--------------------------------------------------------------------------------
/common/interfaces/mongoose.interface.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | interface MongooseConfig {
4 | url: string;
5 | options?: mongoose.ConnectOptions;
6 | }
7 |
8 | export { MongooseConfig };
9 |
--------------------------------------------------------------------------------
/decorators/spring/index.ts:
--------------------------------------------------------------------------------
1 | import { Service, Container, Inject, InjectMany } from 'typedi';
2 |
3 | export { Service, Container, Inject, InjectMany };
4 |
5 | export * from './spring_package_decorator.decorators';
6 |
--------------------------------------------------------------------------------
/common/types/mongoose_types.type.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | type mongooseUrl = string;
4 | type mongooseConnectionOptions = mongoose.ConnectOptions;
5 |
6 | export { mongooseUrl, mongooseConnectionOptions };
7 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | jest.setTimeout(10000);
2 |
3 | jest.mock('fs-temp', () => ({
4 | mkdir: () => Promise.resolve('/tmp/test-dir'),
5 | }));
6 |
7 | jest.mock('rimraf', () => ({
8 | rimraf: () => Promise.resolve(),
9 | }));
10 |
--------------------------------------------------------------------------------
/utilities/index.ts:
--------------------------------------------------------------------------------
1 | export * from './validators';
2 | // export * from './media';
3 | export * from './logger.utilities';
4 | export * from './encryptions';
5 | export * from './auth';
6 | export * from './generate_unique_codes.utilities';
7 |
--------------------------------------------------------------------------------
/classes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './service_classes.class';
2 | export * from './controller_classes.class';
3 | export * from './router_classes.class';
4 | export * from './jwt_auth_classes.class';
5 | export * from './event_service_classes.class';
6 |
--------------------------------------------------------------------------------
/common/types/auth.type.ts:
--------------------------------------------------------------------------------
1 | export type sub = string | object | Buffer;
2 |
3 | export type cookieContent = {
4 | name: string;
5 | value: string;
6 | expires: Date;
7 | httpOnly: boolean;
8 | secure: boolean;
9 | };
10 |
--------------------------------------------------------------------------------
/common/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './HttpStatus.api';
2 | export * from './pick.api';
3 | export * from './ErrorException.api';
4 | export * from './ErrorResponse.api';
5 | export * from './SuccessRespone.api';
6 | export * from './exceptions/index';
7 |
--------------------------------------------------------------------------------
/common/types/response.type.ts:
--------------------------------------------------------------------------------
1 | import { DResponse } from '../interfaces';
2 |
3 | type ResponseType = {
4 | res: DResponse;
5 | status?: number;
6 | msg?: string;
7 | body?: T;
8 | };
9 |
10 | export { ResponseType };
11 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user.model.ts:
--------------------------------------------------------------------------------
1 | import { Schema, Document, model } from 'mongoose';
2 |
3 | export interface IUser extends Document {}
4 |
5 | const UserSchema = new Schema({});
6 |
7 | export const UserModel = model('users', UserSchema);
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | end_of_line = lf
9 |
10 | [{*.js,*.json,*.yml,*.ts}]
11 | indent_size = 4
12 | indent_style = space
13 |
--------------------------------------------------------------------------------
/samples/basic/app.auth_function.ts:
--------------------------------------------------------------------------------
1 | import { IPayload } from '../../common';
2 |
3 | export const authFunc = (payload: IPayload) => {
4 | console.log(payload);
5 |
6 | if (payload.info) {
7 | return false;
8 | }
9 | return true;
10 | };
11 |
--------------------------------------------------------------------------------
/tools/gulp/config.tools.ts:
--------------------------------------------------------------------------------
1 | import { getDirs } from './utilities/helpers.task';
2 |
3 | export const source = ['common', 'classes', 'core', 'decorators', 'packages', 'utilities'];
4 | export const samplePath = 'sample';
5 |
6 | export const packagePath = getDirs(source);
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | yarn-error.log
3 |
4 | dolph-app
5 |
6 | dist
7 |
8 | # Logs
9 | logs
10 | *.log
11 | *.local
12 |
13 | # Misc
14 | .DS_Store
15 | .fleet
16 | .idea
17 |
18 | # Local env files
19 | .env
20 | .env.*
21 | !.env.example
22 |
23 | tests/uploads/
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base", ":dependencyDashboard"],
3 | "labels": ["dependencies"],
4 | "packageRules": [
5 | {
6 | "matchDepTypes": ["devDependencies"],
7 | "automerge": true
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/common/interfaces/jwt.interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface IPayload {
2 | sub: string | object | Buffer;
3 | iat: number;
4 | exp: number;
5 | info?: any;
6 | }
7 |
8 | export interface AuthorizationFunction {
9 | (payload: IPayload): Promise | boolean;
10 | }
11 |
--------------------------------------------------------------------------------
/utilities/normalize_path.utilities.ts:
--------------------------------------------------------------------------------
1 | export const normalizePath = (path: string) => {
2 | // Replace all backslashes with forward slashes for windows machines
3 | let normalized = path.replace(/\\/g, '/');
4 | return normalized.startsWith('/') ? normalized : `/${normalized}`;
5 | };
6 |
--------------------------------------------------------------------------------
/samples/basic/views/layouts/main.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{title}}
7 |
8 |
9 | {{{body}}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/common/types/events.type.ts:
--------------------------------------------------------------------------------
1 | export type Listener = {
2 | listener: (...args: T[]) => void | boolean;
3 | priority: number;
4 | once: boolean;
5 | };
6 |
7 | export type ListenerDecType = {
8 | listener: Function;
9 | priority: number;
10 | once: boolean;
11 | };
12 |
--------------------------------------------------------------------------------
/samples/basic/app.middleware.ts:
--------------------------------------------------------------------------------
1 | import { DNextFunc, DRequest, DResponse } from '../../common';
2 |
3 | export const testMiddleware = (req: DRequest, res: DResponse, next: DNextFunc) => {
4 | req.payload = {
5 | sub: '',
6 | exp: 0,
7 | iat: 0,
8 | };
9 | next();
10 | };
11 |
--------------------------------------------------------------------------------
/common/interfaces/media_parser.interfaces.ts:
--------------------------------------------------------------------------------
1 | import multer from 'multer';
2 | import { mediaType } from '..';
3 |
4 | export interface IMediaParserOptions {
5 | extensions?: string[];
6 | type: mediaType;
7 | storage?: multer.DiskStorageOptions;
8 | fieldname: string;
9 | limit?: number;
10 | }
11 |
--------------------------------------------------------------------------------
/common/api/ErrorException.api.ts:
--------------------------------------------------------------------------------
1 | import { DefaultException } from './exceptions/default_exception.api';
2 |
3 | /**
4 | * throws a new error exception including the stack, error and message
5 | * similar to `DefaultException`
6 | *
7 | */
8 | class ErrorException extends DefaultException {}
9 | export { ErrorException };
10 |
--------------------------------------------------------------------------------
/samples/basic/event_emitter.service.ts:
--------------------------------------------------------------------------------
1 | import { DService } from '../../decorators';
2 | import { EventEmitterService } from '../../packages/events/events_module.packages';
3 |
4 | @DService()
5 | export class TestEventEmitterService extends EventEmitterService {}
6 |
7 | export const testEventEmitterService = new TestEventEmitterService();
8 |
--------------------------------------------------------------------------------
/samples/basic/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../../decorators';
2 | import { AppService } from './app.service';
3 | import { NewController } from './new.controller';
4 | import { NewService } from './new.service';
5 |
6 | @Component({ controllers: [NewController], services: [NewService, AppService] })
7 | export class AppComponent {}
8 |
--------------------------------------------------------------------------------
/samples/basic/new.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail, IsNotEmpty, IsNumber, IsString } from 'class-validator';
2 |
3 | export class CreateUserDto {
4 | @IsString()
5 | @IsNotEmpty()
6 | name: string;
7 |
8 | @IsNumber()
9 | age: number;
10 |
11 | @IsString()
12 | @IsEmail()
13 | @IsNotEmpty()
14 | email: string;
15 | }
16 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import * as common from './common';
2 | import * as classes from './classes';
3 | import * as decorators from './decorators';
4 | import * as packages from './packages';
5 | import * as utilities from './utilities';
6 |
7 | import 'reflect-metadata';
8 |
9 | export { common, classes, decorators, packages, utilities };
10 | export * from './core';
11 |
--------------------------------------------------------------------------------
/samples/basic/test_event_service_dep.service.ts:
--------------------------------------------------------------------------------
1 | import { DolphServiceHandler } from '../../classes';
2 | import { Dolph } from '../../common';
3 |
4 | export class TestEventService extends DolphServiceHandler {
5 | constructor() {
6 | super('test');
7 | }
8 |
9 | logData(data: any) {
10 | console.log(data);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/demo/src/resolvers/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { Field, Int, ObjectType } from 'type-graphql';
2 |
3 | @ObjectType()
4 | class User {
5 | @Field((type) => Int)
6 | id: number;
7 |
8 | @Field((type) => String)
9 | name: string;
10 |
11 | @Field((type) => String, { nullable: true })
12 | email?: string;
13 | }
14 |
15 | export { User };
16 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user2.service.ts:
--------------------------------------------------------------------------------
1 | import { DolphServiceHandler } from '../../../../../classes';
2 | import { Dolph } from '../../../../../common';
3 |
4 | export class User2Service extends DolphServiceHandler {
5 | constructor() {
6 | super('userService');
7 | }
8 |
9 | get() {
10 | return 'Hello';
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/gone.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.GONE` exception
6 | */
7 | class GoneException extends DefaultException {
8 | statusCode: number = HttpStatus.GONE;
9 | name: string = 'Gone';
10 | }
11 |
12 | export { GoneException };
13 |
--------------------------------------------------------------------------------
/decorators/index.ts:
--------------------------------------------------------------------------------
1 | import { Service, Container } from 'typedi';
2 |
3 | export * from './mongoose';
4 | export * from './dolph';
5 | export * from './mysql';
6 | export * from './others';
7 | export * from './spring';
8 | export * from './validations';
9 | export * from './sockets';
10 | export * from './events';
11 |
12 | export { Service as DService, Container as DContainer };
13 |
--------------------------------------------------------------------------------
/utilities/validators/objectId_validator.validator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * verify's if a string is of type `mongoose.SchemaTypes.ObjectID`
4 | */
5 | const objectId = (value: any, helpers: any) => {
6 | if (!value.match(/^[0-9a-fA-F]{24}$/)) {
7 | return helpers.message('"{{#label}}" must be a valid mongo id');
8 | }
9 | return value;
10 | };
11 |
12 | export { objectId };
13 |
--------------------------------------------------------------------------------
/common/middlewares/index.ts:
--------------------------------------------------------------------------------
1 | export * from './try_catch_middleware.middleware';
2 | export * from './default_middleware.middleware';
3 | export * from './validate_request_middleware.middleware';
4 | export * from './jwt_auth.middleware';
5 | export {
6 | validateBodyMiddleware,
7 | validateParamMiddleware,
8 | validateQueryMiddleware,
9 | } from './global_validation_middleware.middlewares';
10 |
--------------------------------------------------------------------------------
/common/api/exceptions/conflict.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.CONFLICT` exception
6 | */
7 | class ConflictException extends DefaultException {
8 | statusCode: number = HttpStatus.CONFLICT;
9 | name: string = 'Conflict';
10 | }
11 |
12 | export { ConflictException };
13 |
--------------------------------------------------------------------------------
/compile.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Specify the top-level directories where you want to start compilation
4 | directories=("classes" "common" "core" "decorators" "packages" "utilities")
5 |
6 | # Loop through the directories and run tsc for each one
7 | for dir in "${directories[@]}"; do
8 | echo "Compiling TypeScript in $dir"
9 | find "$dir" -type f -name '*.ts' -exec tsc -b -v {} +
10 | done
11 |
12 |
--------------------------------------------------------------------------------
/common/api/exceptions/forbidden.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.FORBIDDEN` exception
6 | */
7 | class ForbiddenException extends DefaultException {
8 | statusCode: number = HttpStatus.FORBIDDEN;
9 | name: string = 'Forbidden';
10 | }
11 |
12 | export { ForbiddenException };
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/no_content.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.NO_CONTENT` exception
6 | */
7 | class NoContentException extends DefaultException {
8 | statusCode: number = HttpStatus.NO_CONTENT;
9 | name: string = 'No Content';
10 | }
11 |
12 | export { NoContentException };
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/not_found.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.NOT_FOUND` exception
6 | */
7 |
8 | class NotFoundException extends DefaultException {
9 | statusCode: number = HttpStatus.NOT_FOUND;
10 | name: string = 'Not Found';
11 | }
12 |
13 | export { NotFoundException };
14 |
--------------------------------------------------------------------------------
/common/api/exceptions/bad_gateway.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.BAD_GATEWAY` exception
6 | */
7 | class BadGatewayException extends DefaultException {
8 | statusCode: number = HttpStatus.BAD_GATEWAY;
9 | name: string = 'Bad Gateway';
10 | }
11 |
12 | export { BadGatewayException };
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/bad_request.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.BAD_REQUEST` exception
6 | */
7 | class BadRequestException extends DefaultException {
8 | statusCode: number = HttpStatus.BAD_REQUEST;
9 | name: string = 'Bad Request';
10 | }
11 |
12 | export { BadRequestException };
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/im_a_teapot.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.IM_A_TEAPOT` exception
6 | */
7 |
8 | class ImTeaPotException extends DefaultException {
9 | statusCode: number = HttpStatus.IM_A_TEAPOT;
10 | name: string = "I'm a Teapot";
11 | }
12 |
13 | export { ImTeaPotException };
14 |
--------------------------------------------------------------------------------
/common/api/exceptions/not_modified.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.NOT_MODIFIED` exception
6 | */
7 | class NotModifiedException extends DefaultException {
8 | statusCode: number = HttpStatus.NOT_MODIFIED;
9 | name: string = 'Not Modified';
10 | }
11 |
12 | export { NotModifiedException };
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/timeout.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.GATEWAY_TIMEOUT` exception
6 | */
7 | class TimeOutException extends DefaultException {
8 | statusCode: number = HttpStatus.GATEWAY_TIMEOUT;
9 | name: string = 'Gateway Time Out';
10 | }
11 |
12 | export { TimeOutException };
13 |
--------------------------------------------------------------------------------
/common/api/exceptions/unauthorized.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.UNAUTHORIZED` exception
6 | */
7 | class UnauthorizedException extends DefaultException {
8 | statusCode: number = HttpStatus.UNAUTHORIZED;
9 | name: string = 'Unauthorized';
10 | }
11 |
12 | export { UnauthorizedException };
13 |
--------------------------------------------------------------------------------
/tools/benchmarks/basic.ts:
--------------------------------------------------------------------------------
1 | const autocannon = require('autocannon');
2 |
3 | const runBasicBenchmark = async () => {
4 | const url = 'http://localhost:3030';
5 | const connections = 100;
6 | const duration = 20;
7 |
8 | const results = await autocannon({
9 | url,
10 | connections,
11 | duration,
12 | });
13 |
14 | autocannon.printResult(results);
15 | };
16 |
17 | runBasicBenchmark();
18 |
--------------------------------------------------------------------------------
/common/api/exceptions/not_acceptable.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.NOT_ACCEPTABLE` exception
6 | */
7 | class NotAcceptableException extends DefaultException {
8 | statusCode: number = HttpStatus.NOT_ACCEPTABLE;
9 | name: string = 'Not Acceptable';
10 | }
11 |
12 | export { NotAcceptableException };
13 |
--------------------------------------------------------------------------------
/utilities/get_controllers_from_component.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 |
3 | import { DolphControllerHandler } from '../classes';
4 | import { Dolph } from '../common';
5 |
6 | export function getControllersFromMetadata>(target: {
7 | new (): { controllers?: Array<{ new (): T }> };
8 | }): Array<{ new (): T }> | undefined {
9 | return Reflect.getMetadata('controllers', target.prototype);
10 | }
11 |
--------------------------------------------------------------------------------
/common/api/exceptions/misdirected.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.MISDIRECTED_REQUEST` exception
6 | */
7 |
8 | class MisDirectedException extends DefaultException {
9 | statusCode: number = HttpStatus.MISDIRECTED_REQUEST;
10 | name: string = 'Misdirected Request';
11 | }
12 |
13 | export { MisDirectedException };
14 |
--------------------------------------------------------------------------------
/common/api/exceptions/not_implemented.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.NOT_IMPLEMENTED` exception
6 | */
7 |
8 | class NotImplementedException extends DefaultException {
9 | statusCode: number = HttpStatus.NOT_IMPLEMENTED;
10 | name: string = 'Not Implemented';
11 | }
12 |
13 | export { NotImplementedException };
14 |
--------------------------------------------------------------------------------
/common/api/exceptions/payment_required.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.PAYMENT_REQUIRED` exception
6 | */
7 | class PaymentRequiredException extends DefaultException {
8 | statusCode: number = HttpStatus.PAYMENT_REQUIRED;
9 | name: string = 'Payment Required';
10 | }
11 |
12 | export { PaymentRequiredException };
13 |
--------------------------------------------------------------------------------
/core/config.core.ts:
--------------------------------------------------------------------------------
1 | import { config } from 'dotenv';
2 |
3 | config({});
4 |
5 | export const configs = {
6 | NODE_ENV: process.env.NODE_ENV,
7 | PORT: +process.env.PORT,
8 | MONGO_URL: process.env.MONGO_URL,
9 | };
10 |
11 | export const configLoader = () => {
12 | configs.NODE_ENV = process.env.NODE_ENV || 'development';
13 | configs.PORT = +process.env.PORT || 3300;
14 | configs.MONGO_URL = process.env.MONGO_URL;
15 | };
16 |
--------------------------------------------------------------------------------
/samples/basic/app.schema.ts:
--------------------------------------------------------------------------------
1 | import { INTEGER, STRING } from 'sequelize';
2 | import { mysql } from './sqlDb';
3 |
4 | const User = mysql.define('user', {
5 | id: {
6 | type: INTEGER,
7 | allowNull: false,
8 | primaryKey: true,
9 | autoIncrement: true,
10 | },
11 | username: {
12 | type: STRING,
13 | allowNull: false,
14 | },
15 | age: INTEGER,
16 | });
17 |
18 | export { User };
19 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../../../../../decorators';
2 | import { UserController } from './user.controller';
3 | import { UserService } from './user.service';
4 | import { User2Controller } from './user2.controller';
5 | import { User2Service } from './user2.service';
6 |
7 | @Component({ controllers: [UserController, User2Controller], services: [UserService, User2Service] })
8 | export class UserComponent {}
9 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user.dto.ts:
--------------------------------------------------------------------------------
1 | import { Type } from 'class-transformer';
2 | import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
3 |
4 | export class CreateUserDto {
5 | @IsString()
6 | @IsNotEmpty()
7 | name: string;
8 |
9 | @IsNumber()
10 | age: number;
11 |
12 | @Type(() => String)
13 | @IsOptional()
14 | gender: string;
15 |
16 | @IsString()
17 | email: string;
18 | }
19 |
--------------------------------------------------------------------------------
/common/api/exceptions/method_not_allowed.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.METHOD_NOT_ALLOWED` exception
6 | */
7 |
8 | class MethodNotAllowedException extends DefaultException {
9 | statusCode: number = HttpStatus.METHOD_NOT_ALLOWED;
10 | name: string = 'Method Not Allowed';
11 | }
12 |
13 | export { MethodNotAllowedException };
14 |
--------------------------------------------------------------------------------
/utilities/media/file_extensions.utilities.ts:
--------------------------------------------------------------------------------
1 | export const defaultFileExtensions = [
2 | '.jpeg',
3 | '.png',
4 | '.jpg',
5 | '.xlsx',
6 | '.mp3',
7 | '.mp4',
8 | '.doc',
9 | '.docx',
10 | '.pdf',
11 | '.txt',
12 | '.webm',
13 | '.wmv',
14 | '.mpeg',
15 | '.mkv',
16 | '.mov',
17 | '.flv',
18 | '.html',
19 | '.xml',
20 | '.xhtml',
21 | '.avi',
22 | '.wav',
23 | '.bmi',
24 | ];
25 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # source
2 |
3 | log
4 |
5 | # configuration
6 | yarn-error.log
7 | tslint.json
8 | tsconfig.json
9 | tsconfig.spec.json
10 | .prettierrc
11 |
12 | *.tsbuildinfo
13 |
14 | views
15 | samples
16 |
17 | README.md
18 | renovate.json
19 | todo.txt
20 | LICENSE
21 | jset.setup.js
22 | jest.config.js
23 | gulpfile.js
24 | CONTRIBUTING.md
25 | compile.sh
26 | .prettierrignore
27 | .gitignore
28 | .gitattributes
29 | .editorconfig
30 | .eslintrc.js
31 | .eslintignore
--------------------------------------------------------------------------------
/common/api/exceptions/service_unavailable.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.SERVICE_UNAVAILABLE` exception
6 | */
7 | class ServiceUnavaliableException extends DefaultException {
8 | statusCode: number = HttpStatus.SERVICE_UNAVAILABLE;
9 | name: string = 'Service Unavailable';
10 | }
11 |
12 | export { ServiceUnavaliableException };
13 |
--------------------------------------------------------------------------------
/dolph_config.yaml:
--------------------------------------------------------------------------------
1 | database:
2 | # mongo:
3 | # url: sensitive
4 | # options:
5 | # mysql:
6 | # host: localhost
7 | # database: dolph
8 | # user: root
9 | # pass:
10 | middlewares:
11 | cors:
12 | activate: true
13 | origin: ''
14 | methods:
15 | - GET
16 | - POST
17 | - PUT
18 | - DELETE
19 | allowedHeaders:
20 | routing:
21 | base: '/v1'
22 | port: 3030
23 | # jsonLimit: 20mb
24 | # env: production
25 |
--------------------------------------------------------------------------------
/common/api/exceptions/internal_server_error.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.INTERNAL_SERVER_ERROR` exception
6 | */
7 | class InternalServerErrorException extends DefaultException {
8 | statusCode: number = HttpStatus.INTERNAL_SERVER_ERROR;
9 | name: string = 'Internal Server Error';
10 | }
11 |
12 | export { InternalServerErrorException };
13 |
--------------------------------------------------------------------------------
/samples/basic/socket.component.ts:
--------------------------------------------------------------------------------
1 | import { Socket } from '../../decorators';
2 | import { SocketComponent } from '../../packages';
3 | import { AnotherEventService } from './another_event.service';
4 | import { EventService } from './event.service';
5 | import { TestEventService } from './test_event_service_dep.service';
6 |
7 | @Socket({ services: [TestEventService], socketServices: [EventService, AnotherEventService] })
8 | export class EventsComponent extends SocketComponent {}
9 |
--------------------------------------------------------------------------------
/common/api/exceptions/unsupported_media_type.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.UNSUPPORTED_MEDIA_TYPE` exception
6 | */
7 |
8 | class UnSupportedMediaException extends DefaultException {
9 | statusCode: number = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
10 | name: string = 'Unsupported Media Type';
11 | }
12 |
13 | export { UnSupportedMediaException };
14 |
--------------------------------------------------------------------------------
/common/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './response.type';
2 | export * from './validators.types';
3 | export * from './dolph_env.type';
4 | export * from './dolph_port.type';
5 | export * from './dolph_middleware.type';
6 | export * from './hash_param.type';
7 | export * from './mongoose_types.type';
8 | export * from './media_types.type';
9 | export * from './dolph.types';
10 | export * from './auth.type';
11 | export * from './spring.types';
12 | export * from './events.type';
13 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Load the TypeScript compiler, then load the TypeScript gulpfile which simply loads all
5 | * the tasks. The tasks are really inside tools/gulp/tasks.
6 | */
7 |
8 | const path = require('path');
9 |
10 | const projectDir = __dirname;
11 | const tsConfigPath = path.join(projectDir, 'tools/gulp/tsconfig.json');
12 |
13 | require('ts-node').register({
14 | project: tsConfigPath,
15 | });
16 |
17 | require('./tools/gulp/gulpfile');
18 |
--------------------------------------------------------------------------------
/samples/basic/app.validator.ts:
--------------------------------------------------------------------------------
1 | import Joi from 'joi';
2 |
3 | const createUser = {
4 | param: Joi.object().keys({
5 | name: Joi.string().required(),
6 | age: Joi.number().required().min(15),
7 | work: Joi.string().required(),
8 | height: Joi.number().required(),
9 | }),
10 | };
11 |
12 | const testCase = {
13 | body: Joi.object().keys({
14 | name: Joi.string().required(),
15 | }),
16 | };
17 |
18 | export { createUser, testCase };
19 |
--------------------------------------------------------------------------------
/utilities/is_component.utilities.ts:
--------------------------------------------------------------------------------
1 | import { DolphControllerHandler } from '../classes';
2 | import { DolphComponent } from '../common';
3 |
4 | export function isComponentClass(obj: any): obj is DolphComponent {
5 | return (
6 | typeof obj === 'object' &&
7 | 'controllers' in obj &&
8 | Array.isArray(obj.controllers) &&
9 | obj.controllers.every((item: any) => typeof item === 'function' && item.prototype instanceof DolphControllerHandler)
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/common/api/exceptions/http_version_not_suported.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '../HttpStatus.api';
2 | import { DefaultException } from './default_exception.api';
3 |
4 | /**
5 | * @returns `HttpStatus.HTTP_VERSION_NOT_SUPPORTED` exception
6 | */
7 | class HttpVersionUnSupportedException extends DefaultException {
8 | statusCode: number = HttpStatus.HTTP_VERSION_NOT_SUPPORTED;
9 | name: string = 'Http Version Not Supported';
10 | }
11 |
12 | export { HttpVersionUnSupportedException };
13 |
--------------------------------------------------------------------------------
/samples/basic/app.model.ts:
--------------------------------------------------------------------------------
1 | import mongoose, { Schema, Document } from 'mongoose';
2 |
3 | export interface IUser extends Document {
4 | name: string;
5 | email: string;
6 | age: string;
7 | work: string;
8 | height: string;
9 | }
10 |
11 | const userSchema = new Schema({
12 | name: String,
13 | email: String,
14 | age: Number,
15 | work: String,
16 | height: String,
17 | });
18 |
19 | const userModel = mongoose.model('User', userSchema);
20 | export { userModel };
21 |
--------------------------------------------------------------------------------
/common/constants/messages.constant.ts:
--------------------------------------------------------------------------------
1 | import clc from 'cli-color';
2 |
3 | export const dolphMessages = {
4 | coreUtilMessage: (title: string, content: string) => `${clc.bold(clc.green(`[${title}]:`))} ${clc.greenBright(content)}`,
5 | middlewareMessages: (component: string, componentName: string) => `Registered >>>> ${component} { ${componentName} }`,
6 | routeMessages: (methodName: string, req: string, method: string) =>
7 | `Registered >>>> Route { ${methodName} at ${req.toUpperCase()} ${method} }`,
8 | };
9 |
--------------------------------------------------------------------------------
/packages/sockets/socket_io.packages.ts:
--------------------------------------------------------------------------------
1 | import { Service } from 'typedi';
2 | import { Server as SocketServer } from 'socket.io';
3 | import { DSocket } from '../../common/interfaces/socket.interfaces';
4 |
5 | @Service()
6 | export class SocketService {
7 | private socketIo: SocketServer;
8 |
9 | constructor(params: DSocket) {
10 | this.socketIo = new SocketServer(params.server, params.options);
11 | }
12 |
13 | public getSocket(): SocketServer {
14 | return this.socketIo;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/common/types/hash_param.type.ts:
--------------------------------------------------------------------------------
1 | type bcryptHashParam = { salt: number; pureString: string };
2 | type bcryptCompareParam = { pureString: string; hashString: string };
3 | type argonHashParam = {
4 | pureString: string;
5 | timeCost?: number;
6 | memoryCost?: number;
7 | parallelism?: number;
8 | type?: 0 | 1 | 2;
9 | version?: number;
10 | salt?: Buffer;
11 | saltLength?: number;
12 | raw?: true;
13 | secret?: Buffer;
14 | };
15 |
16 | export { bcryptHashParam, bcryptCompareParam, argonHashParam };
17 |
--------------------------------------------------------------------------------
/utilities/formatters/format_date.utilities.ts:
--------------------------------------------------------------------------------
1 | export function formatDate(timestamp: Date) {
2 | const date = new Date(timestamp);
3 |
4 | const year = date.getFullYear();
5 | const month = String(date.getMonth() + 1).padStart(2, '0');
6 | const day = String(date.getDate()).padStart(2, '0');
7 | const hours = String(date.getHours()).padStart(2, '0');
8 | const minutes = String(date.getMinutes()).padStart(2, '0');
9 | const seconds = String(date.getSeconds()).padStart(2, '0');
10 |
11 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
12 | }
13 |
--------------------------------------------------------------------------------
/common/api/pick.api.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates an object composed of the picked object properties
3 | * - const filter = pick(req.query, ['limit', 'page']); would get the limit and page properties from the query object.
4 | * filter would become an object:
5 | * - { limit:any, page:any }
6 | */
7 | const pick = (object: Object, keys: string[]) => {
8 | return keys.reduce((obj, key) => {
9 | if (object && Object.prototype.hasOwnProperty.call(object, key)) {
10 | obj[key] = object[key];
11 | }
12 | return obj;
13 | }, {});
14 | };
15 | export { pick };
16 |
--------------------------------------------------------------------------------
/common/api/exceptions/default_exception.api.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @extends Error
3 | */
4 | class DefaultException extends Error {
5 | statusCode: number;
6 | isOperational: boolean;
7 | constructor(message: string, statusCode?: number, isOperational = true, stack = '') {
8 | super(message);
9 | this.statusCode = statusCode;
10 | this.isOperational = isOperational;
11 | if (stack) {
12 | this.stack = stack;
13 | } else {
14 | Error.captureStackTrace(this, this.constructor);
15 | }
16 | }
17 | }
18 |
19 | export { DefaultException };
20 |
--------------------------------------------------------------------------------
/common/interfaces/socket.interfaces.ts:
--------------------------------------------------------------------------------
1 | import { IncomingMessage, Server, ServerResponse } from 'http';
2 | import { ServerOptions } from 'socket.io';
3 | import { Dolph, SocketServicesParams } from '../types';
4 | import { SocketComponent, SocketService } from '../../packages';
5 |
6 | export interface DSocket {
7 | server: Server;
8 | options?: Partial;
9 | }
10 |
11 | export interface DSocketInit {
12 | options?: Partial;
13 | component: SocketComponent;
14 | socketService?: typeof SocketService;
15 | }
16 |
--------------------------------------------------------------------------------
/core/fallback_middleware.core.ts:
--------------------------------------------------------------------------------
1 | import httpStatus from 'http-status';
2 | import { DNextFunc, DRequest, DResponse } from '../common';
3 |
4 | export const fallbackResponseMiddleware = (req: DRequest, res: DResponse, next: DNextFunc) => {
5 | const originalSend = res.send;
6 |
7 | res.send = function (...args: any[]) {
8 | res.locals.responseSent = true;
9 | return originalSend.apply(res, args);
10 | };
11 |
12 | next();
13 |
14 | res.on('finish', () => {
15 | if (!res.locals.responseSent && !res.headersSent) {
16 | res.status(httpStatus.OK).send();
17 | }
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | // const { pathsToModuleNameMapper } = require('ts-jest/utils');
2 | const { compilerOptions } = require('./tsconfig.json');
3 |
4 | module.exports = {
5 | preset: 'ts-jest',
6 | testEnvironment: 'node',
7 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '/samples/', '/tools/'],
8 | roots: [''],
9 | transform: {
10 | '^.+\\.ts?$': 'ts-jest',
11 | },
12 | transformIgnorePatterns: ['node_modules/(?!(fs-temp|random-path)/)'],
13 | setupFilesAfterEnv: ['/jest.setup.js'],
14 | // moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '' }),
15 | };
16 |
--------------------------------------------------------------------------------
/tests/core.test.ts:
--------------------------------------------------------------------------------
1 | import request from 'supertest';
2 | import { DolphFactory } from '../core';
3 |
4 | describe('DolphJs Integration Test', () => {
5 | let server;
6 |
7 | beforeAll(() => {
8 | const app = new DolphFactory([]);
9 | server = app.start();
10 | });
11 |
12 | afterAll(() => {
13 | server.close();
14 | });
15 |
16 | it('should return 404 for unknown routes', async () => {
17 | const response = await request(server).get('/unknown-route');
18 | expect(response.status).toBe(404);
19 | expect(response.body.message).toBe('this endpoint does not exist');
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/samples/basic/another_event.service.ts:
--------------------------------------------------------------------------------
1 | import { DolphSocketServiceHandler } from '../../classes';
2 | import { Dolph } from '../../common';
3 |
4 | export class AnotherEventService extends DolphSocketServiceHandler {
5 | constructor() {
6 | super();
7 | this.socketService;
8 | this.handleEvents();
9 | }
10 |
11 | private handleEvents() {
12 | this.socket.on('connection', (socket) => {
13 | socket.emit('connected', 'connection successful');
14 | socket.on('new-user-two', (message) => {
15 | this.socket.emit('new-user-two', message);
16 | });
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tools/gulp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true,
4 | "noUnusedParameters": false,
5 | "noUnusedLocals": false,
6 | "module": "commonjs",
7 | "moduleResolution": "node",
8 | "outDir": "../../dist/tools/gulp",
9 | "strictNullChecks": true,
10 | "strictFunctionTypes": true,
11 | "noImplicitThis": true,
12 | "noEmitOnError": true,
13 | "noImplicitAny": false,
14 | "target": "ES2021",
15 | "types": ["node"],
16 | "typeRoots": ["./typings", "../../node_modules/@types/"],
17 | "baseUrl": "."
18 | },
19 | "files": ["gulpfile.ts"]
20 | }
21 |
--------------------------------------------------------------------------------
/decorators/others/try_catch_async.decorator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * An asynchronous class-method decorator which wraps a method in a try-catch block
3 | * @version 1.4.3
4 | */
5 | export const TryCatchAsyncDec = () => {
6 | return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
7 | const originalMethod = descriptor.value;
8 |
9 | descriptor.value = async function (...args: any[]) {
10 | const context = this;
11 | const [req, res, next] = args;
12 | try {
13 | await originalMethod.apply(context, args);
14 | } catch (err) {
15 | next(err);
16 | }
17 | };
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/packages/uploader/counter.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'stream';
2 |
3 | export class Counter extends EventEmitter {
4 | private value: number = 0;
5 |
6 | increment(): void {
7 | this.value++;
8 | }
9 |
10 | decrement(): void {
11 | this.value--;
12 | if (this.value < 0) this.value = 0;
13 | if (this.value === 0) {
14 | this.emit('zero');
15 | }
16 | }
17 |
18 | isZero(): boolean {
19 | return this.value === 0;
20 | }
21 |
22 | onceZero(fn: () => void): void {
23 | if (this.isZero()) {
24 | fn();
25 | } else {
26 | this.once('zero', fn);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/samples/demo/src/server.ts:
--------------------------------------------------------------------------------
1 | import { buildSchema } from 'type-graphql';
2 | import { DolphFactory } from '../../../core';
3 | import { UserResolver } from './resolvers/user.resolver';
4 | import { UserComponent } from './components/user/user.component';
5 | // import { UserComponent } from './components/user/user.component';
6 |
7 | const schema = async function createSchema() {
8 | return await buildSchema({
9 | resolvers: [UserResolver],
10 | });
11 | };
12 |
13 | const context = async ({ req }) => ({ token: req.headers.token });
14 |
15 | const dolph = new DolphFactory([UserComponent]);
16 | // const dolph = new DolphFactory({ graphql: true, schema: schema(), context });
17 | dolph.start();
18 |
--------------------------------------------------------------------------------
/classes/service_classes.class.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Types of dolph srvices: upperlevel and lowerlevel.
3 | * UpperLevel classes are used to interact with controllers while lowerlevel are used for interactions with one another
4 | */
5 |
6 | import { Dolph } from '../common';
7 |
8 | /**
9 | * Dolph's service handler
10 | * - takes a string generic
11 | *
12 | * - `name` accepts a unique name for the each service which is used behind the scenes by the controller handler
13 | *
14 | * @version 1.0.0
15 | */
16 | abstract class DolphServiceHandler {
17 | public name: string;
18 | constructor(name: T) {
19 | this.name = name;
20 | }
21 | }
22 |
23 | export { DolphServiceHandler };
24 |
--------------------------------------------------------------------------------
/utilities/spring_helpers.utilities.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 | import { SHIELD_METADATA_KEY, UN_SHIELD_METADATA_KEY } from '../decorators/spring/meta_data_keys.decorators';
3 | import { Middleware } from '../common';
4 |
5 | export const getShieldMiddlewares = (targetClass: any): Middleware[] | undefined => {
6 | return Reflect.getMetadata(SHIELD_METADATA_KEY, targetClass.prototype);
7 | };
8 |
9 | export const getUnShieldMiddlewares = (targetMethod: any): Middleware[] | undefined => {
10 | return Reflect.getMetadata(UN_SHIELD_METADATA_KEY, targetMethod);
11 | };
12 |
13 | export const getFunctionNames = (arr: Function[]) => {
14 | return arr.map((fn) => fn.name);
15 | };
16 |
17 | export const stringifyFunction = (func: Function) => func.toString();
18 |
--------------------------------------------------------------------------------
/classes/router_classes.class.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import { DolphControllerHandler } from '.';
3 | import { Dolph } from '../common';
4 | /**
5 | * Dolph's route handler
6 | * - The `path` method represents the api endpoint to be used as path for all routes under this handler
7 | *
8 | * - The `initRoutes` method takes in routes to be used by this handler
9 | * ```js
10 | * initRoutes() {
11 | this.router.post(`${this.path}`, this.controller.sendGreeting);
12 | }
13 | * ```
14 | *
15 | * @version 1.0.9
16 | */
17 | abstract class DolphRouteHandler {
18 | abstract path: T;
19 | abstract initRoutes(): void;
20 | public router = Router();
21 | abstract controller: DolphControllerHandler;
22 | }
23 |
24 | export { DolphRouteHandler };
25 |
--------------------------------------------------------------------------------
/samples/demo/src/resolvers/user.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Arg, Ctx, Mutation, Query, Resolver } from 'type-graphql';
2 | import { User } from './user.entity';
3 |
4 | @Resolver((of) => User)
5 | export class UserResolver {
6 | private users: User[] = [];
7 |
8 | @Query((returns) => [User])
9 | async getUsers(@Ctx() context: any) {
10 | console.log(context.token);
11 | return this.users;
12 | }
13 |
14 | @Mutation((returns) => User)
15 | async addUser(
16 | @Arg('name', (type) => String) name: string,
17 | @Arg('email', (type) => String, { nullable: true }) email?: string,
18 | ): Promise {
19 | const user = { id: this.users.length + 1, name, email };
20 | this.users.push(user);
21 | return user;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tools/gulp/tasks/copy.tool.ts:
--------------------------------------------------------------------------------
1 | import { dest, series, src, task } from 'gulp';
2 |
3 | function copyPackageJson(): NodeJS.ReadWriteStream {
4 | return src('package.json').pipe(dest('dist'));
5 | }
6 |
7 | function copyReadme(): NodeJS.ReadWriteStream {
8 | return src('README.md').pipe(dest('dist'));
9 | }
10 |
11 | function copyLicense(): NodeJS.ReadWriteStream {
12 | return src('LICENSE').pipe(dest('dist'));
13 | }
14 |
15 | function copyNpmIgnore(): NodeJS.ReadWriteStream {
16 | return src('.npmignore').pipe(dest('dist'));
17 | }
18 |
19 | task('copy:package-json', copyPackageJson);
20 | task('copy:readme', copyReadme);
21 | task('copy:license', copyLicense);
22 | task('copy:npm-ignore', copyNpmIgnore);
23 | task('copy', series('copy:package-json', 'copy:readme', 'copy:license', 'copy:npm-ignore'));
24 |
--------------------------------------------------------------------------------
/tools/gulp/tasks/clean.tool.ts:
--------------------------------------------------------------------------------
1 | import { task, src, series } from 'gulp';
2 | import { source } from '../config.tools';
3 | import * as clean from 'gulp-clean';
4 | import * as deleteEmpty from 'delete-empty';
5 |
6 | /**
7 | * Cleans the build output assets from the package folders
8 | */
9 |
10 | function cleanOutput() {
11 | // `${source}/**/*.d.ts`;
12 | return src([`${source}/**/*.js`, `${source}/**/*.js.map`, `${source}/**/*.d.ts.map`], {
13 | read: false,
14 | }).pipe(clean());
15 | }
16 |
17 | /**
18 | * Cleans empty directories
19 | */
20 |
21 | function cleanDirs(done: () => void) {
22 | deleteEmpty.sync(`${source}/`);
23 | done();
24 | }
25 |
26 | task('clean:output', cleanOutput);
27 | task('clean:dirs', cleanDirs);
28 | task('clean:bundle', series('clean:output', 'clean:dirs'));
29 |
--------------------------------------------------------------------------------
/core/initializers/middleware_registrar.ts:
--------------------------------------------------------------------------------
1 | import { RequestHandler } from 'express';
2 |
3 | class MiddlewareRegistry {
4 | private static instance: MiddlewareRegistry;
5 | private middlewares: RequestHandler[] = [];
6 |
7 | private constructor() {}
8 |
9 | public static getInstance(): MiddlewareRegistry {
10 | if (!MiddlewareRegistry.instance) {
11 | MiddlewareRegistry.instance = new MiddlewareRegistry();
12 | }
13 |
14 | return MiddlewareRegistry.instance;
15 | }
16 |
17 | public register(middleware: RequestHandler) {
18 | this.middlewares.push(middleware);
19 | }
20 |
21 | public getMiddlewares(): RequestHandler[] {
22 | return this.middlewares;
23 | }
24 | }
25 |
26 | export const middlewareRegistry = MiddlewareRegistry.getInstance();
27 |
--------------------------------------------------------------------------------
/common/api/exceptions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './bad_gateway.api';
2 | export * from './bad_request.api';
3 | export * from './conflict.api';
4 | export * from './forbidden.api';
5 | export * from './gone.api';
6 | export * from './http_version_not_suported.api';
7 | export * from './im_a_teapot.api';
8 | export * from './internal_server_error';
9 | export * from './method_not_allowed.api';
10 | export * from './misdirected.api';
11 | export * from './no_content.api';
12 | export * from './not_acceptable.api';
13 | export * from './not_found.api';
14 | export * from './not_implemented.api';
15 | export * from './not_modified.api';
16 | export * from './payment_required.api';
17 | export * from './service_unavailable.api';
18 | export * from './timeout.api';
19 | export * from './unsupported_media_type.api';
20 | export * from './unauthorized.api';
21 |
--------------------------------------------------------------------------------
/common/middlewares/jwt_auth.middleware.ts:
--------------------------------------------------------------------------------
1 | import { TryCatchAsyncFn } from '.';
2 | import { JwtBasicAuth } from '../../classes';
3 | import { DNextFunc, DRequest, DResponse } from '../interfaces';
4 |
5 | /**
6 | *
7 | * Takes the `JwtBasicAuth` class as parameter and calls the Verify method to perform Authorization
8 | *
9 | * Passes the payload to the request body as `req.payload`
10 | * - see `IPayload` interface to see the design of the payload object
11 | */
12 | function JwtAuthMiddleware(jwtBasicAuthInstance: JwtBasicAuth) {
13 | return TryCatchAsyncFn(async (req: DRequest, res: DResponse, next: DNextFunc) => {
14 | const payload = await jwtBasicAuthInstance.Verify(req, res, next);
15 | //@ts-expect-error
16 | req.payload = payload;
17 | next();
18 | });
19 | }
20 |
21 | export { JwtAuthMiddleware };
22 |
--------------------------------------------------------------------------------
/core/adapters/exception_handlers.ts:
--------------------------------------------------------------------------------
1 | // e.g., in common/exceptions/validation.exception.ts
2 | import { ValidationError } from 'class-validator';
3 | // You might use a library like 'http-status-codes' for status codes
4 | const HTTP_STATUS_BAD_REQUEST = 400;
5 |
6 | export class ValidationException extends Error {
7 | public errors: ValidationError[];
8 | public status: number;
9 |
10 | constructor(errors: ValidationError[], message: string = 'Input validation failed') {
11 | super(message); // Message for the error
12 | this.name = 'ValidationException';
13 | this.errors = errors; // Array of validation errors from class-validator
14 | this.status = HTTP_STATUS_BAD_REQUEST;
15 | Object.setPrototypeOf(this, ValidationException.prototype); // Ensures 'instanceof ValidationException' works
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/decorators/mongoose/mongoose_service_intializer.decorators.ts:
--------------------------------------------------------------------------------
1 | import { Model } from 'mongoose';
2 |
3 | /**
4 | *
5 | * this decorator is used to inject a mongoose model into the serviceHandler as a method
6 | *
7 | * @param propertyName takes the name of the mongoose model
8 | * @param model takes the actual mongoose model imported from the models dir
9 | *
10 | *
11 | * @version 2.0.0
12 | */
13 | function InjectMongo(propertyName: string, model: Model) {
14 | return function (constructor: T) {
15 | const Wrapped = class extends constructor {
16 | [propertyName]: Model = model;
17 | };
18 | Object.defineProperty(Wrapped, 'name', { value: constructor.name, configurable: true });
19 | return Wrapped;
20 | };
21 | }
22 |
23 | export { InjectMongo };
24 |
--------------------------------------------------------------------------------
/common/types/spring.types.ts:
--------------------------------------------------------------------------------
1 | import { DolphControllerHandler, DolphServiceHandler, DolphSocketServiceHandler } from '../../classes';
2 | import { DNextFunc, DRequest, DResponse } from '../../common/interfaces';
3 | import { Dolph } from './dolph.types';
4 |
5 | export type Middleware = (req: DRequest, res: DResponse, next: DNextFunc) => void;
6 |
7 | export type ComponentParams = {
8 | controllers: Array<{ new (): DolphControllerHandler }>;
9 | services?: Array<{ new (...args: any[]): {} }>;
10 | };
11 |
12 | export type DolphComponent> = {
13 | controllers: Array<{ new (): T }>;
14 | };
15 |
16 | export type SocketServicesParams = {
17 | services?: Array<{ new (): DolphServiceHandler }>;
18 | socketServices?: Array<{ new (): DolphSocketServiceHandler }>;
19 | };
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "utilities/**/*",
4 | "common/**/*",
5 | "core/**/*",
6 | "decorators/**/*",
7 | "packages/**/*",
8 | "classes/**/*",
9 | "index.ts"
10 | ],
11 | "exclude": ["node_modules"],
12 | "compilerOptions": {
13 | "allowJs": false,
14 | "declaration": true,
15 | "experimentalDecorators": true,
16 | "sourceMap": true,
17 | "useUnknownInCatchVariables": false,
18 | "esModuleInterop": true,
19 | "strictFunctionTypes": false,
20 | "emitDeclarationOnly": false,
21 | "emitDecoratorMetadata": true,
22 | "declarationMap": false,
23 | "target": "ES2022",
24 | "rootDir": ".",
25 | "outDir": "dist",
26 | "pretty": true,
27 | "module": "commonjs"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user.service.ts:
--------------------------------------------------------------------------------
1 | import { DolphServiceHandler } from '../../../../../classes';
2 | import { Dolph } from '../../../../../common';
3 | import { InjectMongo } from '../../../../../decorators';
4 | import { Model } from 'mongoose';
5 | import { UserModel, IUser } from './user.model';
6 | import { User2Service } from './user2.service';
7 |
8 | @InjectMongo('userModel', UserModel)
9 | export class UserService extends DolphServiceHandler {
10 | userModel!: Model;
11 | users: { name: string; age: number; tag: string }[] = [];
12 |
13 | constructor(private User2Service: User2Service) {
14 | super('userservice');
15 | }
16 |
17 | createUser(age: number, name: string): { name: string; age: number }[] {
18 | this.users.push({ age, name, tag: this.User2Service.get() });
19 | return this.users;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/common/constants/error_constants.types.ts:
--------------------------------------------------------------------------------
1 | const DolphErrors = {
2 | serverError: 'internal server error',
3 | deprc: (value: string) => `${value} is depreciated`,
4 | notAllowed: (value: string) => `${value} is not allowed`,
5 | serverClosed: 'dolphjs server closed',
6 | sigtermReceived: 'SIGTREM received',
7 | passwordShort: (char: number | string) => `password must be at least ${char} characters`,
8 | passwordMustContain: (letter: number | string, no: number | string, symbols?: Array) =>
9 | ` password must contain at least ${letter} letter and ${no} number ${
10 | symbols.length ? `and any of these symbols:${symbols.join(', ')}` : ''
11 | }`,
12 | noDolphConfigFile:
13 | 'dolphjs engine cannot start without dolph_config.yaml file, please ensure to add it to root directory',
14 | };
15 |
16 | export { DolphErrors };
17 |
--------------------------------------------------------------------------------
/packages/uploader/errors/error_messages.ts:
--------------------------------------------------------------------------------
1 | const errorMessages = {
2 | LIMIT_PART_COUNT: 'Too many parts',
3 | LIMIT_FILE_SIZE: 'File too large',
4 | LIMIT_FILE_COUNT: 'Too many files',
5 | LIMIT_FIELD_KEY: 'Field name too long',
6 | LIMIT_FIELD_VALUE: 'Field value too long',
7 | LIMIT_FIELD_COUNT: 'Too many fields',
8 | LIMIT_UNEXPECTED_FILE: 'Unexpected field',
9 | MISSING_FIELD_NAME: 'Field name missing',
10 | };
11 |
12 | export class DolphFIleUploaderError extends Error {
13 | private code: string;
14 | private field: string;
15 |
16 | constructor(code: string, field: string) {
17 | super(errorMessages[code]);
18 |
19 | this.name = 'UploaderError';
20 |
21 | this.code = code;
22 |
23 | if (field) {
24 | this.field = field;
25 | }
26 | Error.captureStackTrace(this, DolphFIleUploaderError);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/samples/basic/new.service.ts:
--------------------------------------------------------------------------------
1 | import { DolphServiceHandler } from '../../classes';
2 | import { Dolph } from '../../common';
3 | import { DService, OnEvent } from '../../decorators';
4 | import { EventEmitterService } from '../../packages/events/events_module.packages';
5 | import { AppService } from './app.service';
6 |
7 | @DService()
8 | export class NewService extends DolphServiceHandler {
9 | private emitterService: EventEmitterService = new EventEmitterService();
10 | private AppService: AppService;
11 |
12 | constructor() {
13 | super('newService');
14 | }
15 |
16 | logger() {
17 | this.emitterService.emitEvent('test');
18 | console.log('Okay, reached');
19 | }
20 |
21 | newA() {
22 | return this.AppService.greeting({ name: 'Aemn', age: 12 });
23 | }
24 |
25 | @OnEvent('test')
26 | test() {
27 | console.log('it works');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/samples/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "target": "ES2017",
5 | "lib": ["ES2017", "esnext.asynciterable"],
6 | "typeRoots": ["node_modules/@types"],
7 | "allowSyntheticDefaultImports": true,
8 | "experimentalDecorators": true,
9 | "emitDecoratorMetadata": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "moduleResolution": "node",
12 | "module": "commonjs",
13 | "pretty": true,
14 | "sourceMap": true,
15 | "declaration": true,
16 | "outDir": "dist",
17 | "allowJs": true,
18 | "noEmit": false,
19 | "esModuleInterop": true,
20 | "resolveJsonModule": true,
21 | "importHelpers": true,
22 | "baseUrl": "."
23 | },
24 | "ts-node": {
25 | "compilerOptions": {
26 | "module": "CommonJS"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/decorators/mysql/mysql_initializer.decorators.ts:
--------------------------------------------------------------------------------
1 | import { Model, ModelStatic } from 'sequelize';
2 | /**
3 | *
4 | * this decorator is used to inject a mongoose model into the serviceHandler as a method
5 | *
6 | * @param propertyName takes the name of the mongoose model
7 | * @param model takes the actual mognoose model imported from the models dir
8 | *
9 | *
10 | * @version 2.0.0
11 | */
12 |
13 | function InjectMySQL(propertyName: string, model: ModelStatic>) {
14 | return function (constructor: T) {
15 | const Wrapped = class extends constructor {
16 | [propertyName]: ModelStatic> = model;
17 | };
18 | // Preserving the original class name after extension
19 | Object.defineProperty(Wrapped, 'name', { value: constructor.name, configurable: true });
20 | return Wrapped;
21 | };
22 | }
23 |
24 | export { InjectMySQL };
25 |
--------------------------------------------------------------------------------
/samples/basic/event.service.ts:
--------------------------------------------------------------------------------
1 | import { DolphSocketServiceHandler } from '../../classes';
2 | import { Dolph } from '../../common';
3 | import { TestEventService } from './test_event_service_dep.service';
4 |
5 | export class EventService extends DolphSocketServiceHandler {
6 | constructor() {
7 | super();
8 | this.socketService;
9 | this.handleEvents();
10 | }
11 |
12 | private TestEventService: TestEventService;
13 |
14 | private handleEvents() {
15 | this.socket.on('connection', (socket) => {
16 | socket.emit('connected', 'connection successful');
17 | socket.on('new-user', (message) => {
18 | this.socket.emit('new-user', message);
19 | });
20 | socket.on('data', (data) => {
21 | this.TestEventService.logData(data);
22 | socket.emit('new-user', socket.id);
23 | });
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/classes/event_service_classes.class.ts:
--------------------------------------------------------------------------------
1 | import { DefaultEventsMap } from 'socket.io/dist/typed-events';
2 | import { Dolph } from '../common';
3 | import { getInjectedService } from '../core';
4 | import { SocketService } from '../packages';
5 | import { Server } from 'socket.io';
6 |
7 | abstract class DolphSocketServiceHandler {
8 | private _socketService?: SocketService;
9 | public socket?: Server;
10 |
11 | public get socketService(): SocketService {
12 | if (!this._socketService) {
13 | //@ts-expect-error
14 | this._socketService = getInjectedService(SocketService.name);
15 | this.initSocketEvents();
16 | }
17 | return this._socketService;
18 | }
19 |
20 | private initSocketEvents(): void {
21 | this.socket = this.socketService.getSocket();
22 | }
23 | }
24 |
25 | export { DolphSocketServiceHandler };
26 |
--------------------------------------------------------------------------------
/decorators/dolph/dolph_service.decorator.ts:
--------------------------------------------------------------------------------
1 | import { DolphConstructor, DolphServiceMapping } from '../../common';
2 |
3 | /**
4 | *
5 | * Injects an array of `DolphServiceHandler` into a parent class which is then shared by controllers using this services
6 | *
7 | * - top-level class
8 | * @version 1.0.0
9 | */
10 | function InjectServiceHandler(serivceMappings: DolphServiceMapping[]) {
11 | return function (BaseClass: Base): Base {
12 | return class extends BaseClass {
13 | constructor(...args: any[]) {
14 | super(...args);
15 | for (const mapping of serivceMappings) {
16 | //@ts-expect-error
17 | this[mapping.serviceName] = new mapping.serviceHandler();
18 | }
19 | }
20 | // initServiceHandlers() {
21 | // }
22 | };
23 | };
24 | }
25 |
26 | export { InjectServiceHandler };
27 |
--------------------------------------------------------------------------------
/common/api/SuccessRespone.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from './HttpStatus.api';
2 | import { ResponseType } from '../types/response.type';
3 | import { DResponse } from '../interfaces';
4 | import mime from 'mime-types';
5 |
6 | /**
7 | * returns a success response with a default `200` status code
8 | * @param {ResponseType} param takes in the `res` ,`status` , `msg` and `body` values
9 | */
10 | const SuccessResponse = (param: ResponseType): DResponse => {
11 | const contentType = mime.lookup('json') || 'application/json';
12 |
13 | param.res.set('Content-Type', contentType);
14 |
15 | const { res, status, body, msg } = param;
16 | if (body && msg) res.status(status || HttpStatus.OK).json({ ...body, message: msg });
17 | if (body) return res.status(status || HttpStatus.OK).json(body);
18 | if (msg) return res.status(status || HttpStatus.OK).send(msg);
19 | return res.status(status || HttpStatus.NO_CONTENT).send();
20 | };
21 |
22 | export { SuccessResponse };
23 |
--------------------------------------------------------------------------------
/common/middlewares/default_middleware.middleware.ts:
--------------------------------------------------------------------------------
1 | import { DNextFunc, DRequest, DResponse } from '../interfaces';
2 |
3 | const DolphAsyncMiddleware =
4 | (fn: (req: DRequest, res: DResponse, next: DNextFunc) => Promise) =>
5 | (req: DRequest, res: DResponse, next: DNextFunc) => {};
6 |
7 | /**
8 | *
9 | * Creates a function that can be used for as an express middleware function
10 | */
11 | const DolphMiddleware = (fn: any) => (req: DRequest, res: DResponse, next: DNextFunc) => {};
12 |
13 | /**
14 | *
15 | * Creates an asyncfunction wrapped in a try-catch block which can be used an express middleware function
16 | */
17 | function DolphAsyncMiddlewareDec(fn: (...args: any[]) => Promise): (...args: any[]) => void {
18 | return function (...args: any[]) {
19 | const [req, res, next] = args;
20 | Promise.resolve(fn(...args)).catch((err) => next(err));
21 | };
22 | }
23 |
24 | export { DolphAsyncMiddlewareDec, DolphMiddleware, DolphAsyncMiddleware };
25 |
--------------------------------------------------------------------------------
/utilities/generate_unique_codes.utilities.ts:
--------------------------------------------------------------------------------
1 | const strOne = Date.now().toString(36);
2 | const strTwo = Math.random().toString(36).substring(2);
3 |
4 | function generateRandomCode(digits: number, str: string) {
5 | let code = '';
6 | while (code.length < digits) {
7 | code += str[Math.floor(Math.random() * str.length)];
8 | }
9 | return code;
10 | }
11 |
12 | /**
13 | * generates a random 5 digits string
14 | *
15 | * can be used for email validation e.t.c
16 | */
17 | const uniqueFiveDigitsCode = generateRandomCode(5, strOne + strTwo);
18 |
19 | /**
20 | * generates a random 6 digits string
21 | *
22 | * can be used for email validation e.t.c
23 | */
24 | const uniqueSixDigitsCode = generateRandomCode(6, strOne + strTwo);
25 |
26 | /**
27 | * generates a random 7 digits string
28 | *
29 | * can be used for email validation e.t.c
30 | */
31 | const uniqueSevenDigitsCode = generateRandomCode(7, strOne + strTwo);
32 |
33 | export { uniqueFiveDigitsCode, uniqueSevenDigitsCode, uniqueSixDigitsCode };
34 |
--------------------------------------------------------------------------------
/todo.txt:
--------------------------------------------------------------------------------
1 | 1. Add the globalExceptionFilter documentation to the docs which means no need to wrap methods with TryCatchAsync anymore
2 | 2. Add notice that the moment library has been removed on release of v2.
3 | 3. The former hashing functions have been deprecated and replaced with new ones, should be added to the README
4 | 4. maxCount field on fileUploader is useless atm and should be worked on
5 | 5. Add documentation for the fileUploader function as MediaParser has been removed
6 |
7 |
8 | ----------
9 | Write tests for:
10 |
11 | - Routing and Controllers
12 | - Middleware
13 | - Request and Response Handling
14 | - Database Integration
15 | - Error Handling
16 | - Authentication and Authorization
17 | - Testing Edge Cases
18 | - Concurrency and Scalability
19 | - Configuration
20 | - Dependency Injection
21 | - Logging and Debugging
22 | ----------
23 |
24 |
25 | * const payload: IPayload = {
26 | iat: Math.floor(Date.now() / 1000),
27 | exp: Math.floor(exp.getTime() / 1000),
28 | sub: sub,
29 | info: info,
30 | };
--------------------------------------------------------------------------------
/common/api/ErrorResponse.api.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from './HttpStatus.api';
2 | import { ResponseType } from '../types/response.type';
3 | import { DResponse } from '../interfaces';
4 | import mime from 'mime-types';
5 |
6 | /**
7 | * returns an error as response with the provided parameters and a default `400` status code
8 | * @param {ResponseType} param takes in the `res` ,`status` , `msg` and `body` values
9 | */
10 | const ErrorResponse = (param: ResponseType): DResponse => {
11 | const contentType = mime.lookup('json') || 'application/json';
12 |
13 | param.res.set('Content-Type', contentType);
14 |
15 | const { res, status, body, msg } = param;
16 | if (body && msg) res.status(status || HttpStatus.BAD_REQUEST).json({ ...body, message: msg });
17 | if (body) return res.status(status || HttpStatus.BAD_REQUEST).json(body);
18 | if (msg) return res.status(status || HttpStatus.BAD_REQUEST).send(msg);
19 | return res.status(status || HttpStatus.INTERNAL_SERVER_ERROR).send();
20 | };
21 |
22 | export { ErrorResponse };
23 |
--------------------------------------------------------------------------------
/packages/mongoose/mongoose_connection.package.ts:
--------------------------------------------------------------------------------
1 | import { MongooseConfig } from '../../common';
2 | import mongoose from 'mongoose';
3 | import { logger } from '../../utilities';
4 | import clc from 'cli-color';
5 |
6 | /**
7 | *
8 | * Used to initialize mongodb with mogoose ORM
9 | * @returns the mogoose promise
10 | *
11 | * @version 1.0.0
12 | */
13 | const initMongo = (config: MongooseConfig): Promise => {
14 | mongoose.set('strictQuery', false);
15 | return mongoose.connect(config.url, config.options);
16 | };
17 |
18 | /**
19 | *
20 | * Used to intiialize mongodb with mongoose ORM
21 | *
22 | * Unlike the `initMongo` function, it handles the promise
23 | *
24 | * @version 1.0.0
25 | */
26 | const autoInitMongo = (config: MongooseConfig): void => {
27 | initMongo(config)
28 | .then((_res) => {
29 | logger.info(clc.blueBright('MONGODB CONNECTED'));
30 | })
31 | .catch((err) => {
32 | logger.error(clc.red(err));
33 | });
34 | };
35 |
36 | export { initMongo, autoInitMongo };
37 |
--------------------------------------------------------------------------------
/core/morgan.core.ts:
--------------------------------------------------------------------------------
1 | import { DRequest, DResponse } from '../common';
2 | import morgan from 'morgan';
3 | import { configs } from './config.core';
4 | import { logger } from '../utilities';
5 | import clc from 'cli-color';
6 |
7 | morgan.token('message', (req: DRequest, res: DResponse) => res.locals.errorMessage || '');
8 |
9 | const getIpFormat = () => (configs.NODE_ENV !== 'test' ? ':remote-addr - ' : '');
10 | const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`;
11 | const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`;
12 |
13 | export const successHandler = morgan(successResponseFormat, {
14 | skip: (req: DRequest, res: DResponse) => res.statusCode >= 400,
15 | stream: { write: (message) => logger.info(clc.blackBright(message.trim())) },
16 | });
17 |
18 | export const morganErrorHandler = morgan(errorResponseFormat, {
19 | skip: (req: DRequest, res: DResponse) => res.statusCode < 400,
20 | stream: { write: (message) => logger.error(clc.redBright(message.trim())) },
21 | });
22 |
--------------------------------------------------------------------------------
/packages/uploader/remove_uploaded_files.ts:
--------------------------------------------------------------------------------
1 | import { FileHandler, FileInfo, FileRemoveCallback } from '../../common/types/dolph_uploader.type';
2 |
3 | export function removeUploadedFiles(uploadedFiles: FileInfo[], remove: FileHandler, next: FileRemoveCallback): void {
4 | const length = uploadedFiles.length;
5 | const errors: Error[] = [];
6 |
7 | if (!length) {
8 | return next(null, errors);
9 | }
10 |
11 | function handleFile(index: number): void {
12 | const file = uploadedFiles[index];
13 |
14 | remove(file, (err: Error | null) => {
15 | if (err) {
16 | const enhancedError = err as Error & { file?: FileInfo; field?: string };
17 | enhancedError.file = file;
18 | enhancedError.field = file.fieldname;
19 | errors.push(enhancedError);
20 | }
21 |
22 | if (index < length - 1) {
23 | handleFile(index + 1);
24 | } else {
25 | next(null, errors);
26 | }
27 | });
28 | }
29 |
30 | handleFile(0);
31 | }
32 |
--------------------------------------------------------------------------------
/tools/gulp/utilities/helpers.task.ts:
--------------------------------------------------------------------------------
1 | import { readdirSync, statSync } from 'fs';
2 | import { join } from 'path';
3 |
4 | function isDirectory(path: string): boolean {
5 | return statSync(path).isDirectory();
6 | }
7 |
8 | export function getFolders(dir: string): string[] {
9 | return readdirSync(dir).filter((file) => isDirectory(join(dir, file)));
10 | }
11 |
12 | export function getDirs(base: string[]): string[] {
13 | const allDirs: string[] = [];
14 |
15 | base.forEach((baseDir) => {
16 | const folders = getFolders(baseDir);
17 | const baseDirsFullPath = folders.map((folder) => join(baseDir, folder));
18 | // return getFolders(base).map((path) => `${base}/${path}`);
19 | allDirs.push(...baseDirsFullPath);
20 | });
21 |
22 | return allDirs;
23 | }
24 |
25 | /**
26 | * Checks if the directory contains a package.json file
27 | * @param dir Path to the directory
28 | * @returns True if the directory contains a package.json
29 | */
30 | export function containsPackageJson(dir: string) {
31 | return readdirSync(dir).some((file) => file === 'package.json');
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The (MIT License)
2 |
3 | Copyright (c) 2023 dolphjs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/core/adapters/mvc_registrar.ts:
--------------------------------------------------------------------------------
1 | import { MVCEngine } from '../../common';
2 |
3 | class MVCAdapterClass {
4 | private static instance: MVCAdapterClass;
5 | private engine: MVCEngine;
6 | private pathToAssets: string;
7 | private pathToViews: string;
8 |
9 | private constructor() {}
10 |
11 | public static getInstance(): MVCAdapterClass {
12 | if (!MVCAdapterClass.instance) {
13 | MVCAdapterClass.instance = new MVCAdapterClass();
14 | }
15 |
16 | return MVCAdapterClass.instance;
17 | }
18 |
19 | public setStaticAssets(path: string) {
20 | this.pathToAssets = path;
21 | }
22 |
23 | public setViewsDir(path: string) {
24 | this.pathToViews = path;
25 | }
26 |
27 | public setViewEngine(engine: MVCEngine) {
28 | this.engine = engine;
29 | }
30 |
31 | public getViewsDir() {
32 | return this.pathToViews;
33 | }
34 |
35 | public getViewEngine() {
36 | return this.engine;
37 | }
38 |
39 | public getAssetsPath() {
40 | return this.pathToAssets;
41 | }
42 | }
43 |
44 | export const MVCAdapter = MVCAdapterClass.getInstance();
45 |
--------------------------------------------------------------------------------
/packages/mongoose/transform_doc_plugin.package.ts:
--------------------------------------------------------------------------------
1 | const deleteAtPath = (obj: any, path: any, index: any) => {
2 | if (index === path.length - 1) {
3 | delete obj[path[index]];
4 | return;
5 | }
6 | deleteAtPath(obj[path[index]], path, index + 1);
7 | };
8 |
9 | const toJSON = (schema: any) => {
10 | let transform: any;
11 | if (schema.options.toJSON && schema.options.toJSON.transform) {
12 | transform = schema.options.toJSON.transform;
13 | }
14 |
15 | schema.options.toJSON = Object.assign(schema.options.toJSON || {}, {
16 | transform(doc: any, ret: any, options: any) {
17 | Object.keys(schema.paths).forEach((path) => {
18 | if (schema.paths[path].options && schema.paths[path].options.private) {
19 | deleteAtPath(ret, path.split('.'), 0);
20 | }
21 | });
22 |
23 | ret.id = ret._id.toString();
24 | delete ret._id;
25 | delete ret.__v;
26 | if (transform) {
27 | return transform(doc, ret, options);
28 | }
29 | },
30 | });
31 | };
32 |
33 | export { toJSON as transformDoc };
34 |
--------------------------------------------------------------------------------
/packages/sequelize/sequelize_connection.package.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '../../utilities';
2 | import clc from 'cli-color';
3 | import { Sequelize } from 'sequelize';
4 |
5 | /**
6 | *
7 | * Used to initialize mysql with sequelize ORM
8 | * @returns the mogoose promise
9 | *
10 | * @version 1.0.0
11 | */
12 | const initMySql = (name: string, user: string, password: string, host: string) => {
13 | const sequelize = new Sequelize(name, user, password, {
14 | dialect: 'mysql',
15 | host: host || 'localhost',
16 | });
17 | // DolphSequelize = sequelize;
18 | return sequelize;
19 | };
20 |
21 | /**
22 | *
23 | * Used to intiialize mysql with sequelize ORM
24 | *
25 | * It accepts the return value from the `initSquelize` function and calls the `sync` function on it
26 | *
27 | * @version 1.0.0
28 | */
29 | const autoInitMySql = (sequelize: Sequelize) => {
30 | sequelize
31 | .sync()
32 | .then((_res) => {
33 | logger.info(clc.blueBright('MYSQL CONNECTED'));
34 | })
35 | .catch((err: any) => {
36 | logger.error(clc.red(err));
37 | });
38 | };
39 |
40 | export { initMySql, autoInitMySql };
41 |
--------------------------------------------------------------------------------
/core/initializers/dependency_injection.core.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 | import { Container } from 'typedi';
3 | import { logger } from '../../utilities';
4 |
5 | type injectedServices = {
6 | service: string;
7 | value: any;
8 | };
9 |
10 | export const GlobalInjection = (service: string, value: any) => {
11 | Container.set(service, value);
12 | };
13 |
14 | export const getInjectedService = (service: string) => {
15 | return Container.get(service);
16 | };
17 |
18 | const injectGlobalServices = (services: injectedServices[]) => {
19 | services.forEach((service) => {
20 | try {
21 | Container.set(service.service, service.value);
22 | } catch {
23 | logger.error(`Cannot inject service: ${service.service}`);
24 | }
25 | });
26 | };
27 |
28 | export class GlobalInjector {
29 | constructor(services?: injectedServices[]) {
30 | if (services) {
31 | injectGlobalServices(services);
32 | }
33 | }
34 |
35 | register(service: string, value: any) {
36 | injectGlobalServices([{ service, value }]);
37 | }
38 |
39 | get(service: string) {
40 | return getInjectedService(service);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/demo/.swcrc:
--------------------------------------------------------------------------------
1 | {
2 | "jsc": {
3 | "parser": {
4 | "syntax": "typescript",
5 | "tsx": false,
6 | "dynamicImport": true,
7 | "decorators": true
8 | },
9 | "transform": {
10 | "legacyDecorator": true,
11 | "decoratorMetadata": true
12 | },
13 | "target": "es2022",
14 | "externalHelpers": false,
15 | "keepClassNames": true,
16 | "loose": false,
17 | "minify": {
18 | "compress": false,
19 | "mangle": false
20 | },
21 | "baseUrl": "src",
22 | "paths": {
23 | "@/*": ["*"],
24 | "@/configs/*": ["shared/configs/*"],
25 | "@/components/*": ["components/*"],
26 | "@utils/*": ["shared/utils/*"],
27 | "@shields/*": ["shared/shields/*"],
28 | "@shared/*": ["shared/*"],
29 | "@/helpers*": ["shared/helpers/*"],
30 | "@/interfaces/*": ["shared/interfaces/*"],
31 | "@/middlewares/*": ["shared/middlewares/*"],
32 | "@/decorators/*": ["shared/decorators/*"],
33 | "@/services/*": ["shared/services/*"],
34 | "@/constants/*": ["shared/constants/*"],
35 | "@/validations/*": ["shared/validations/*"]
36 | }
37 | },
38 | "module": {
39 | "type": "commonjs"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/decorators/validations/validations.decorators.ts:
--------------------------------------------------------------------------------
1 | import { NextFunction } from 'express';
2 | import { BadRequestException, DNextFunc, DRequest, DResponse, pick } from '../../common';
3 | import Joi from 'joi';
4 |
5 | export function ValidateReq(schema: any) {
6 | return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
7 | const originalMethod = descriptor.value;
8 |
9 | descriptor.value = function (req: Request | DRequest, res: Response | DResponse, next: NextFunction | DNextFunc) {
10 | const acceptedSchema = pick(schema, ['param', 'body', 'query']);
11 | const object = pick(req, Object.keys(acceptedSchema));
12 | const { value, error } = Joi.compile(acceptedSchema)
13 | .prefs({ errors: { label: 'key' }, abortEarly: false })
14 | .validate(object);
15 |
16 | if (error) {
17 | const errorMessage = error.details.map((details) => details.message).join(', ');
18 | return next(new BadRequestException(errorMessage));
19 | }
20 |
21 | Object.assign(req, value);
22 | return originalMethod.apply(this, [req, res, next]);
23 | };
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user.shield.ts:
--------------------------------------------------------------------------------
1 | import { DRequest, DResponse, DNextFunc, UnauthorizedException, ForbiddenException } from '../../../../../common';
2 | import { verifyJWTwithHMAC } from '../../../../../utilities';
3 |
4 | export const authShield = async (req: DRequest, res: DResponse, next: DNextFunc) => {
5 | try {
6 | let authToken = req.headers['authorization'] as string;
7 |
8 | if (!authToken) {
9 | return next(new UnauthorizedException('Provide a valid authorization token header'));
10 | }
11 |
12 | const bearer = authToken.split(' ')[0];
13 |
14 | if (bearer !== 'Bearer') return next(new UnauthorizedException('Provide a valid authorization token header'));
15 |
16 | authToken = authToken.split(' ')[1];
17 |
18 | const payload = verifyJWTwithHMAC({
19 | token: authToken,
20 | secret: '12357373738388383992ndnd',
21 | });
22 |
23 | if (!payload) return next(new UnauthorizedException('Invalid or expired token'));
24 |
25 | req.payload = {
26 | ...(payload as any),
27 | };
28 |
29 | next();
30 | } catch (e) {
31 | next(new UnauthorizedException(e));
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/utilities/validators/password_validator.validator.ts:
--------------------------------------------------------------------------------
1 | import { DolphErrors } from '../../common/constants';
2 | import { passwordStrength } from '../../common';
3 |
4 | /**
5 | *
6 | * default dolphjs password validator
7 | *
8 | * checks for the level of strength of a password
9 | */
10 | const validatePassword = (level: passwordStrength, value: string, helpers: any) => {
11 | if ((level = 'basic')) {
12 | if (value.length < 6) {
13 | return helpers.message(DolphErrors.passwordShort(6));
14 | }
15 | } else if ((level = 'medium')) {
16 | if (value.length < 6) {
17 | return helpers.message(DolphErrors.passwordShort(6));
18 | }
19 | if (value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
20 | return helpers.message(DolphErrors.passwordMustContain(1, 1));
21 | }
22 | } else if ((level = 'strong')) {
23 | if (value.length < 7) {
24 | return helpers.message(DolphErrors.passwordShort(7));
25 | }
26 | if (value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
27 | return helpers.message(DolphErrors.passwordMustContain(1, 1));
28 | }
29 | }
30 | // implement for extra strong
31 | };
32 |
33 | export { validatePassword };
34 |
--------------------------------------------------------------------------------
/samples/basic/app.server.ts:
--------------------------------------------------------------------------------
1 | import helmet from 'helmet';
2 | import { DolphFactory, GlobalInjector, middlewareRegistry, MVCAdapter } from '../../core';
3 | import { SocketService } from '../../packages';
4 | import { EventEmitterService } from '../../packages/events/events_module.packages';
5 | import { AppComponent } from './app.component';
6 | // import { autoInitMySql } from '../../packages';
7 | import { routes } from './index.routes';
8 | import { EventsComponent } from './socket.component';
9 | import path from 'node:path';
10 | // import { NewController } from './new.controller';
11 | // import { mysql } from './sqlDb';
12 |
13 | new GlobalInjector([{ service: EventEmitterService.name, value: new EventEmitterService() }]);
14 |
15 | middlewareRegistry.register(helmet());
16 |
17 | MVCAdapter.setViewEngine('ejs');
18 | MVCAdapter.setStaticAssets(path.join(__dirname, 'public'));
19 |
20 | // For Handlebars
21 | // MVCAdapter.setViewsDir(path.join(__dirname, 'views', 'layouts', 'main'));
22 |
23 | MVCAdapter.setViewsDir(path.join(__dirname, 'views'));
24 |
25 | const dolph = new DolphFactory([AppComponent], {
26 | options: { cors: { origin: '*' } },
27 | socketService: SocketService,
28 | component: new EventsComponent(),
29 | });
30 |
31 | dolph.start();
32 |
--------------------------------------------------------------------------------
/samples/basic/app.service.ts:
--------------------------------------------------------------------------------
1 | import { InjectMongo, InjectMySQL } from '../../decorators';
2 | import { DolphServiceHandler } from '../../classes';
3 | import { IUser, userModel } from './app.model';
4 | import { Model } from 'mongoose';
5 | import { User } from './app.schema';
6 | import { ModelStatic, Model as SqlModel } from 'sequelize';
7 | import { Dolph } from '../../common';
8 | import { NewService } from './new.service';
9 |
10 | @InjectMongo('userModel', userModel)
11 | @InjectMySQL('userMySqlModel', User)
12 | class AppService extends DolphServiceHandler {
13 | userModel!: Model;
14 | userMySqlModel!: ModelStatic>;
15 | NewService: NewService;
16 |
17 | constructor() {
18 | super('appService');
19 | }
20 |
21 | greeting = (body: { name: string; age: number }) => {
22 | const greeting = `Hi ${body.name}, wow you are ${body.age} years old`;
23 | return greeting;
24 | };
25 |
26 | createUser = async (body: any) => {
27 | const data = await this.userModel.create(body);
28 | return data;
29 | };
30 |
31 | createSQLUser = async (body: { username: string; age: string }) => {
32 | return this.userMySqlModel.create(body);
33 | };
34 | }
35 |
36 | export { AppService };
37 |
--------------------------------------------------------------------------------
/samples/basic/app.router.ts:
--------------------------------------------------------------------------------
1 | import { DolphRouteHandler } from '../../classes';
2 | import { AppController } from './app.controller';
3 | import { reqValidatorMiddleware } from '../../common/middlewares';
4 | import { createUser } from './app.validator';
5 | import { Dolph } from '../../common';
6 | import { cookieAuthVerify } from '../../utilities';
7 |
8 | class AppRouter extends DolphRouteHandler {
9 | constructor() {
10 | super();
11 | this.initRoutes();
12 | }
13 |
14 | public readonly path: string = '/app';
15 | controller: AppController = new AppController();
16 |
17 | initRoutes() {
18 | this.router.post(`${this.path}`, this.controller.sendGreeting);
19 | this.router.post(`${this.path}/user`, reqValidatorMiddleware(createUser), this.controller.createUser);
20 | this.router.post(`${this.path}/register`, this.controller.register);
21 | this.router.post(`${this.path}/sql`, this.controller.testMysql);
22 | this.router.get(`${this.path}/cookie`, this.controller.testCookieFn);
23 | // this.router.get(`${this.path}/cookie-validation`, cookieAuthVerify('random_secret'), this.controller.testCookieVerify);
24 | this.router.get(`${this.path}/cookie-validation`, this.controller.testCookieVerify);
25 | }
26 | }
27 |
28 | export { AppRouter };
29 |
--------------------------------------------------------------------------------
/samples/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "1.0.0",
4 | "main": "app/server.js",
5 | "author": "",
6 | "license": "MIT",
7 | "engines": {
8 | "node": ">=18.0.0"
9 | },
10 | "scripts": {
11 | "dev:start": "dolph watch",
12 | "dev:docker:start": "docker-compose -f docker-compose-dev.yml up",
13 | "dev:docker:stop": "docker-compose -f docker-compose-dev.yml down",
14 | "build": "dolph build",
15 | "build:tsc": "tsc && tsc-alias",
16 | "start": "dolph watch",
17 | "clean": "rm -r app && rm -r logs"
18 | },
19 | "dependencies": {
20 | "@dolphjs/dolph": "1.3.7",
21 | "class-transformer": "^0.5.1",
22 | "class-validator": "^0.14.1",
23 | "handlebars": "^4.7.8",
24 | "helmet": "^7.1.0",
25 | "joi": "^17.12.3",
26 | "mjml": "^4.15.3",
27 | "nodemailer": "^6.9.13",
28 | "pug": "^3.0.3"
29 | },
30 | "devDependencies": {
31 | "@swc/cli": "^0.1.62",
32 | "@swc/core": "^1.3.91",
33 | "@types/express": "^4.17.21",
34 | "@types/node": "^20.8.2",
35 | "@types/nodemailer": "^6.4.14",
36 | "graphql-scalars": "^1.23.0",
37 | "ts-node": "^10.9.1",
38 | "tsc-alias": "^1.8.8",
39 | "tsconfig-paths": "^4.2.0",
40 | "typescript": "^5.2.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/common/middlewares/validate_request_middleware.middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextFunction, Request, Response } from 'express';
2 | import { BadRequestException, pick } from '../api';
3 | import Joi = require('joi');
4 | import { DNextFunc, DRequest, DResponse } from '../interfaces';
5 |
6 | /**
7 | *
8 | * dolphjs middleware used to validate a request object form the client
9 | *
10 | * it's used in conjunction with JOI in order to work
11 | *
12 | * provides validation for `body`, `params` & `query` objects
13 | *
14 | * - see [https://github.com/dolphjs/dolph#README] for usage
15 | * @version 1.0.0
16 | */
17 | const validateRequestMiddleware =
18 | (schema: any) => (req: Request | DRequest, res: Response | DResponse, next: NextFunction | DNextFunc) => {
19 | const acceptedSchema = pick(schema, ['param', 'body', 'query']);
20 | const object = pick(req, Object.keys(acceptedSchema));
21 | const { value, error } = Joi.compile(acceptedSchema)
22 | .prefs({ errors: { label: 'key' }, abortEarly: false })
23 | .validate(object);
24 |
25 | if (error) {
26 | const errorMessage = error.details.map((details) => details.message).join(', ');
27 | return next(new BadRequestException(errorMessage));
28 | }
29 |
30 | Object.assign(req, value);
31 | return next();
32 | };
33 |
34 | export { validateRequestMiddleware as reqValidatorMiddleware };
35 |
--------------------------------------------------------------------------------
/samples/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["node_modules"],
3 | "compilerOptions": {
4 | "allowJs": false,
5 | "declaration": false,
6 | "experimentalDecorators": true,
7 | "emitDecoratorMetadata": true,
8 | "sourceMap": false,
9 | "useUnknownInCatchVariables": false,
10 | "strictFunctionTypes": false,
11 | "forceConsistentCasingInFileNames": true,
12 | "resolveJsonModule": true,
13 | "esModuleInterop": true,
14 | "target": "ES2022",
15 | "baseUrl": "src",
16 | "outDir": "app",
17 | "pretty": true,
18 | "module": "commonjs",
19 | "paths": {
20 | "@/*": ["*"],
21 | "@/configs/*": ["shared/configs/*"],
22 | "@/components/*": ["components/*"],
23 | "@utils/*": ["shared/utils/*"],
24 | "@shields/*": ["shared/shields/*"],
25 | "@shared/*": ["shared/*"],
26 | "@/helpers*": ["shared/helpers/*"],
27 | "@/interfaces/*": ["shared/interfaces/*"],
28 | "@/middlewares/*": ["shared/middlewares/*"],
29 | "@/decorators/*": ["shared/decorators/*"],
30 | "@/services/*": ["shared/services/*"],
31 | "@/constants/*": ["shared/constants/*"],
32 | "@/validations/*": ["shared/validations/*"]
33 | }
34 | },
35 | "include": ["src/", ".env"],
36 | "ts-node": {
37 | "compilerOptions": {
38 | "module": "CommonJS"
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/decorators/others/cookie_auth_verify.decorators.ts:
--------------------------------------------------------------------------------
1 | import { DNextFunc, DRequest, DResponse, ErrorException, HttpStatus, IPayload } from '../../common';
2 | import { verifyJWTwithHMAC } from '../../utilities';
3 |
4 | /**
5 | *
6 | * class-method decorator used for authorization based on the dolphjs default cookie authentication and authorization pattern
7 | *
8 | * @version 1.0
9 | */
10 | export const CookieAuthVerifyDec = (tokenSecret: string) => {
11 | return (_target: any, _propertyKey: string, descriptor?: TypedPropertyDescriptor) => {
12 | const originalMethod = descriptor.value;
13 |
14 | // convert to normal func
15 |
16 | descriptor.value = (req: DRequest, res: DResponse, next: DNextFunc) => {
17 | try {
18 | const context = this;
19 |
20 | if (!req.cookies)
21 | return next(new ErrorException('user not authorized, login and try again', HttpStatus.UNAUTHORIZED));
22 |
23 | const { xAuthToken } = req.cookies;
24 |
25 | if (!xAuthToken)
26 | return next(new ErrorException('user not authorized, login and try again', HttpStatus.UNAUTHORIZED));
27 |
28 | let payload: IPayload;
29 |
30 | //@ts-expect-error
31 | payload = verifyJWTwithHMAC({ token: xAuthToken, secret: tokenSecret });
32 |
33 | req.payload = payload;
34 |
35 | return originalMethod.apply(context, [req, res, next]);
36 | } catch (e) {
37 | throw e;
38 | }
39 | };
40 | };
41 | };
42 |
--------------------------------------------------------------------------------
/common/types/dolph.types.ts:
--------------------------------------------------------------------------------
1 | import { DolphConstructor } from '..';
2 |
3 | export type DolphDbs = 'mongo' | 'postgre' | 'mysql' | 'sqllite' | 'maria' | 'cassandra';
4 | export type DolphMiddlewareOption = {
5 | activate?: boolean | undefined;
6 | origin?: string | undefined;
7 | allowedHeaders?: string[] | undefined | null;
8 | maxAge?: number | undefined;
9 | exposedHeaders?: string[] | null | undefined;
10 | credentials?: boolean | undefined;
11 | preflightContinue?: boolean | undefined;
12 | optionsSuccessStatus: number | undefined;
13 | };
14 |
15 | export type DolphMiddlewareHelmetOption = {
16 | activate: boolean;
17 | contentSecurityPolicy?: {
18 | directives?: {
19 | defaultSrc?: string[];
20 | scriptSrc?: string[];
21 | };
22 | };
23 | expectCt?: {
24 | enforce?: boolean;
25 | maxAge?: number;
26 | };
27 | featurePolicy?: {
28 | features?: {
29 | fullscreen?: string[];
30 | vibrate?: string[];
31 | };
32 | };
33 | referrerPolicy?: {
34 | policy?: string;
35 | };
36 | hsts?: {
37 | maxAge?: number;
38 | includeSubDomains?: boolean;
39 | };
40 | crossOriginEmbedderPolicy?: {
41 | policy?: string;
42 | };
43 | crossOriginOpenerPolicy?: {
44 | policy?: string;
45 | };
46 | };
47 |
48 | export type DolphServiceMapping = {
49 | serviceName: keyof T;
50 | serviceHandler: DolphConstructor;
51 | };
52 |
53 | export type Dolph = string;
54 |
55 | export type MVCEngine = 'handlebars' | 'pug' | 'ejs';
56 |
--------------------------------------------------------------------------------
/packages/memdb/memdb.packages.ts:
--------------------------------------------------------------------------------
1 | export class MemDB {
2 | data: Record;
3 |
4 | constructor() {
5 | this.data = {};
6 | }
7 |
8 | /**
9 | * Adds a new key-value pair to the database.
10 | * If the key is not a string, it will be converted to a string using JSON.stringify.
11 | */
12 | add(key: string, value: T) {
13 | if (this.get(key)) throw new Error('cannot add new value because `key` already exists. ');
14 | this.data[key] = value;
15 | }
16 |
17 | /**
18 | * Get a value from the database using the key.
19 | */
20 | get(key: string) {
21 | return this.data[key];
22 | }
23 |
24 | /**
25 | * Remove data from the database.
26 | * @returns the data if found, otherwise throws an error.
27 | */
28 | remove(key: string) {
29 | if (!(key in this.data)) return undefined;
30 |
31 | const data = this.data[key];
32 | delete this.data[key];
33 | return data;
34 | }
35 |
36 | /**
37 | * Checks if data exists in the database then returns it.
38 | */
39 | has(key: string) {
40 | return key in this.data;
41 | }
42 |
43 | /**
44 | * Returns all the keys in the database.
45 | */
46 | getAllKeys() {
47 | return Object.keys(this.data);
48 | }
49 |
50 | /**
51 | * Returns all the values in the database.
52 | */
53 | getAllValues() {
54 | return Object.values(this.data);
55 | }
56 |
57 | /**
58 | * Clears the database.
59 | */
60 | empty() {
61 | this.data = {};
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/middleware.test.ts:
--------------------------------------------------------------------------------
1 | import {} from '../decorators';
2 | import { DRequest, DResponse, DNextFunc, validateBodyMiddleware } from '../common';
3 | import { IsString, Length } from 'class-validator';
4 |
5 | class TestDto {
6 | @IsString()
7 | @Length(1, 10)
8 | name: string;
9 | }
10 |
11 | describe('Validation Middleware', () => {
12 | let req: Partial;
13 | let res: Partial;
14 | let next: DNextFunc;
15 |
16 | beforeEach(() => {
17 | req = { body: { name: 'test' } };
18 |
19 | res = {
20 | status: jest.fn().mockReturnThis(),
21 | json: jest.fn(),
22 | };
23 |
24 | next = jest.fn();
25 | });
26 |
27 | it('should call next if validation succeeds', async () => {
28 | req.body = { name: 'validName' };
29 | const middleware = validateBodyMiddleware(TestDto);
30 | await middleware(req as DRequest, res as DResponse, next);
31 | expect(next).toHaveBeenCalled();
32 | expect(res.status).not.toHaveBeenCalled();
33 | expect(res.json).not.toHaveBeenCalled();
34 | });
35 |
36 | it('should return 400 if validation fails', async () => {
37 | req.body = { name: '' };
38 | const middleware = validateBodyMiddleware(TestDto);
39 | await middleware(req as DRequest, res as DResponse, next);
40 | expect(res.status).toHaveBeenCalledWith(400);
41 | expect(res.json).toHaveBeenCalledWith(
42 | expect.objectContaining({ message: 'Validation failed', error: expect.any(String) }),
43 | );
44 | expect(next).not.toHaveBeenCalled();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/classes/controller_classes.class.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * serviceHandlers Array takes in an object which has a name for a key and the serviceHandler as value
3 | * Example: [{'userService': new UserService()}]
4 | */
5 |
6 | import { Dolph } from '../common';
7 |
8 | /**
9 | * `DolphControllerHandler` takes a string generic.
10 | *
11 | */
12 | // Note: string generic would be useful in the future
13 | abstract class DolphControllerHandler {}
14 |
15 | export { DolphControllerHandler };
16 | /**
17 | * Example of use
18 | */
19 |
20 | /**
21 | * serviceHandlers Array takes in an object which has a name for a key and the serviceHandler as value
22 | * Example: [{'userService': new UserService()}]
23 | */
24 |
25 | // import { DolphServiceHandler } from "./service_classes.class";
26 |
27 | // abstract class DolphControllerHandler{
28 | // abstract path: string;
29 | // abstract serviceHandlers: Array>
30 | // getHandler(name: string){
31 | // return this.serviceHandlers.find((handler)=> handler.name === name)
32 | // }
33 | // }
34 |
35 | // class NewO extends DolphServiceHandler<"services">{
36 | // getUser = ()=>{
37 | // console.log("It is working");
38 | // }
39 | // }
40 |
41 | // class NewH extends DolphControllerHandler{
42 | // path: string = "/home";
43 | // serviceHandlers: DolphServiceHandler[] = [new NewO("services")];
44 |
45 | // CreateUser = ()=>{
46 | // const service = this.getHandler("service")
47 | // if(service instanceof NewO){
48 | // service.getUser();
49 | // }
50 | // }
51 | // }
52 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user2.controller.ts:
--------------------------------------------------------------------------------
1 | import { DolphControllerHandler } from '../../../../../classes';
2 | import { Dolph, DRequest, DResponse, IPayload, SuccessResponse } from '../../../../../common';
3 | import { DBody, DPayload, DReq, DRes, Get, Post, Route, UseMiddleware } from '../../../../../decorators';
4 | import { generateJWTwithHMAC } from '../../../../../utilities';
5 | import { CreateUserDto } from './user.dto';
6 | import { authShield } from './user.shield';
7 | import { User2Service } from './user2.service';
8 |
9 | @Route('user2')
10 | export class User2Controller extends DolphControllerHandler {
11 | private User2Service: User2Service;
12 | constructor() {
13 | super();
14 | }
15 |
16 | @Get('')
17 | get(req: DRequest, res: DResponse) {
18 | return req.query;
19 | }
20 |
21 | @UseMiddleware(authShield)
22 | @Post('')
23 | async print(@DReq() req: DRequest, @DRes() res: DResponse, @DPayload() payload: IPayload) {
24 | console.log(payload);
25 |
26 | SuccessResponse({ res, body: payload });
27 | }
28 |
29 | @Post('login')
30 | async login(@DRes() res, @DBody(CreateUserDto) body: CreateUserDto) {
31 | const accessToken = this.signToken(body.email, new Date(Date.now() + 3000 * 1000));
32 | SuccessResponse({ res, body: accessToken, ...body });
33 | }
34 |
35 | private signToken(userId: string, expires: Date): string {
36 | const payload: IPayload = {
37 | sub: userId,
38 | iat: Math.floor(Date.now() / 1000),
39 | exp: Math.floor(expires.getTime() / 1000),
40 | };
41 |
42 | return generateJWTwithHMAC({ payload, secret: '12357373738388383992ndnd' });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/decorators/events/event.decorator.ts:
--------------------------------------------------------------------------------
1 | import { getInjectedService } from '../../core';
2 | import { EventEmitterService } from '../../packages/events/events_module.packages';
3 |
4 | /**
5 | * - currently not released
6 | * @version 2.0
7 | */
8 | function OnEvent(eventName: string, priority: number = 0, once: boolean = false) {
9 | return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
10 | const originalMethod = descriptor.value;
11 |
12 | descriptor.value = function (...args: any[]) {
13 | const eventEmitter: EventEmitterService = getInjectedService(EventEmitterService.name) as any;
14 |
15 | eventEmitter.emitEvent(eventName, originalMethod.bind(this), priority, once);
16 | return originalMethod.apply(this, args);
17 | };
18 |
19 | return descriptor;
20 | };
21 | }
22 |
23 | /**
24 | * - currently not released
25 | * @version 2.0
26 | */
27 | function OnceEvent(eventName: string, priority: number = 0) {
28 | return OnEvent(eventName, priority, true);
29 | }
30 |
31 | /**
32 | * - currently not released
33 | * @version 2.0
34 | */
35 | function OffEvent(eventName: string) {
36 | return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
37 | const originalMethod = descriptor.value;
38 |
39 | descriptor.value = function (...args: any[]) {
40 | const eventEmitter: EventEmitterService = getInjectedService(EventEmitterService.name) as any;
41 |
42 | eventEmitter.unregisterEvent(eventName, originalMethod.bind(this));
43 | return originalMethod.apply(this, args);
44 | };
45 |
46 | return descriptor;
47 | };
48 | }
49 |
50 | export { OnEvent, OnceEvent, OffEvent };
51 |
--------------------------------------------------------------------------------
/tests/uploader/memorystorage.test.ts:
--------------------------------------------------------------------------------
1 | import { Readable } from 'stream';
2 | import { memoryStorage } from '../../packages';
3 | import { DRequest } from '../../common';
4 |
5 | describe('Memory Storage Test', () => {
6 | let storage: ReturnType;
7 |
8 | beforeEach(() => {
9 | storage = memoryStorage();
10 | });
11 |
12 | test('should store file in memory correctly', (done) => {
13 | const testContent = 'test content';
14 | const mockFile = {
15 | fieldname: 'testFile',
16 | originalname: 'test-file.txt',
17 | encoding: '7bit',
18 | mimetype: 'text/plain',
19 | stream: Readable.from(Buffer.from(testContent)),
20 | };
21 |
22 | const mockRequest = {} as DRequest;
23 |
24 | storage.handleFile(mockRequest, mockFile, (error, info) => {
25 | expect(error).toBeNull();
26 | expect(info?.buffer).toBeDefined();
27 | expect(info?.size).toBe(testContent.length);
28 | expect(info?.buffer?.toString()).toBe(testContent);
29 | done();
30 | });
31 | });
32 |
33 | test('should remove file from memory correctly', (done) => {
34 | const mockFile = {
35 | fieldname: 'testFile',
36 | originalname: 'test-file.txt',
37 | encoding: '7bit',
38 | mimetype: 'text/plain',
39 | buffer: Buffer.from('test content'),
40 | };
41 |
42 | const mockRequest = {} as DRequest;
43 |
44 | storage.removeFile(mockRequest, mockFile, (error) => {
45 | expect(error).toBeNull();
46 | expect(mockFile).not.toHaveProperty('buffer');
47 | done();
48 | });
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/decorators/sockets/sockets.decorators.ts:
--------------------------------------------------------------------------------
1 | import clc from 'cli-color';
2 | import { DolphServiceHandler, DolphSocketServiceHandler } from '../../classes';
3 | import { Dolph, SocketServicesParams } from '../../common';
4 | import { logger } from '../../utilities';
5 |
6 | function isDolphServiceHandler(classOrInstance: any): classOrInstance is typeof DolphServiceHandler {
7 | return classOrInstance.prototype instanceof DolphServiceHandler || classOrInstance === DolphServiceHandler;
8 | }
9 |
10 | function isDolphSocketServiceHandler(classOrInstance: any): classOrInstance is typeof DolphSocketServiceHandler {
11 | return classOrInstance.prototype instanceof DolphSocketServiceHandler || classOrInstance === DolphSocketServiceHandler;
12 | }
13 |
14 | export const Socket = ({ services, socketServices }: SocketServicesParams): ClassDecorator => {
15 | if (Array.isArray(socketServices)) {
16 | return (target: any) => {
17 | socketServices.forEach((socketService) => {
18 | services.forEach((service) => {
19 | const serviceInstance = new service();
20 | const serviceName = service.name;
21 |
22 | Object.defineProperty(socketService.prototype, serviceName, {
23 | value: serviceInstance,
24 | writable: true,
25 | configurable: true,
26 | enumerable: true,
27 | });
28 | });
29 | });
30 |
31 | Reflect.defineMetadata('sockets', socketServices, target.prototype);
32 | };
33 | } else {
34 | logger.error(clc.red('Provide an array of socket services with type `new (): DolphSocketServiceHandler` '));
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | plugins: ['@typescript-eslint/eslint-plugin'],
7 | extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
8 | overrides: [
9 | {
10 | files: ['**/*.ts'],
11 | parser: '@typescript-eslint/parser',
12 | parserOptions: {
13 | project: 'tsconfig.json',
14 | sourceType: 'module',
15 | },
16 | rules: {
17 | '@typescript-eslint/interface-name-prefix': 'off',
18 | '@typescript-eslint/explicit-function-return-type': 'off',
19 | '@typescript-eslint/no-explicit-any': 'off',
20 | '@typescript-eslint/explicit-module-boundary-types': 'off',
21 | '@typescript-eslint/no-unused-vars': 'on',
22 | '@typescript-eslint/ban-types': 'off',
23 | },
24 | },
25 | {
26 | files: ['**/*.spec.ts', 'integration/**/*.ts'],
27 | parser: '@typescript-eslint/parser',
28 | parserOptions: {
29 | project: 'tsconfig.spec.json',
30 | sourceType: 'module',
31 | },
32 | rules: {
33 | '@typescript-eslint/interface-name-prefix': 'off',
34 | '@typescript-eslint/explicit-function-return-type': 'off',
35 | '@typescript-eslint/no-explicit-any': 'off',
36 | '@typescript-eslint/explicit-module-boundary-types': 'off',
37 | '@typescript-eslint/no-unused-vars': 'on',
38 | '@typescript-eslint/ban-types': 'off',
39 | '@typescript-eslint/no-empty-function': 'off',
40 | },
41 | },
42 | ],
43 | };
44 |
--------------------------------------------------------------------------------
/samples/basic/new.controller.ts:
--------------------------------------------------------------------------------
1 | import { DolphControllerHandler } from '../../classes';
2 | import {
3 | DRequest,
4 | DResponse,
5 | Dolph,
6 | SuccessResponse,
7 | validateBodyMiddleware,
8 | validateParamMiddleware,
9 | validateQueryMiddleware,
10 | } from '../../common';
11 | import {
12 | Body,
13 | Get,
14 | Post,
15 | Render,
16 | Route,
17 | Shield,
18 | UnShield,
19 | UseMiddleware as UseMiddleware,
20 | ValidateReq,
21 | } from '../../decorators';
22 | import { testMiddleware } from './app.middleware';
23 | import { testCase } from './app.validator';
24 | import { CreateUserDto } from './new.dto';
25 | import { NewService } from './new.service';
26 |
27 | @Shield(testMiddleware)
28 | @Route('app')
29 | export class NewController extends DolphControllerHandler {
30 | private NewService: NewService;
31 |
32 | constructor() {
33 | super();
34 | }
35 |
36 | @Get('test')
37 | async newReq(req: DRequest, res: DResponse) {
38 | this.NewService.logger();
39 | const a = this.NewService.newA();
40 | SuccessResponse({ res, body: { message: a } });
41 | }
42 |
43 | @Post('test')
44 | // @UseMiddleware(testMiddleware)
45 | // @ValidateReq(testCase)
46 | @UnShield(testMiddleware)
47 | @UseMiddleware(validateBodyMiddleware(CreateUserDto))
48 | async testNewController(req: DRequest, res: DResponse) {
49 | const dto: CreateUserDto = req.body;
50 | SuccessResponse({ res, body: { dto, ...req.payload } });
51 | }
52 |
53 | @Get('home')
54 | @Render('home')
55 | getHomePage(req: DRequest, res: DResponse) {
56 | return { title: 'Home' };
57 | }
58 |
59 | @Get('about')
60 | @Render('about')
61 | getAboutPage(req: DRequest, res: DResponse) {
62 | return { title: 'About' };
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/core/error.core.ts:
--------------------------------------------------------------------------------
1 | import { DNextFunc, DRequest, DResponse } from '../common';
2 | import mongoose from 'mongoose';
3 | import httpStatus from 'http-status';
4 | import { configs } from './config.core';
5 | import { logger } from '../utilities';
6 | import clc from 'cli-color';
7 | import { DefaultException } from '../common/api/exceptions/default_exception.api';
8 |
9 | export const errorConverter = (err: any, req: DRequest, res: DResponse, next: DNextFunc) => {
10 | let error = err;
11 |
12 | if (!(error instanceof DefaultException)) {
13 | const statusCode =
14 | error.statusCode ||
15 | (error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR);
16 | const message = error.message || httpStatus[statusCode];
17 | error = new DefaultException(message, statusCode, false, err.stack);
18 | }
19 |
20 | next(error);
21 | };
22 |
23 | export const errorHandler = (err: any, req: DRequest, res: DResponse, next: DNextFunc) => {
24 | let statusCode = err.statusCode || httpStatus.INTERNAL_SERVER_ERROR;
25 | let message = err.message || 'An unexpected server error occurred';
26 |
27 | if (configs.NODE_ENV === 'production' && !err.isOperational) {
28 | if (!statusCode) {
29 | statusCode = httpStatus.INTERNAL_SERVER_ERROR;
30 | message = 'Internal Server Error';
31 | }
32 | }
33 |
34 | res.locals.errorMessage = message;
35 |
36 | const response = {
37 | code: statusCode,
38 | message,
39 | ...(configs.NODE_ENV === 'development' && { stack: err.stack }),
40 | };
41 |
42 | if (configs.NODE_ENV === 'test') {
43 | logger.error(clc.red(err));
44 | }
45 |
46 | // TODO: remove this to allow for other content types
47 | res.set('Content-Type', 'application/json');
48 | res.status(statusCode).json(response);
49 | };
50 |
--------------------------------------------------------------------------------
/tests/fallback_middleware.test.ts:
--------------------------------------------------------------------------------
1 | import { DRequest, DRequestHandler } from '../common';
2 | import { middlewareRegistry } from '../core';
3 | import helmet from 'helmet';
4 |
5 | describe('MiddlewareRegistry', () => {
6 | beforeEach(() => {
7 | // Reset the singleton instance and clear middlewares before each test
8 | (middlewareRegistry as any).middlewares = [];
9 | });
10 |
11 | it('should return a singleton instance', () => {
12 | const instance1 = middlewareRegistry;
13 | const instance2 = middlewareRegistry;
14 | expect(instance1).toBe(instance2);
15 | });
16 |
17 | it('should register a middleware', () => {
18 | const mockMiddleware: DRequestHandler = (req, res, next) => next();
19 | middlewareRegistry.register(mockMiddleware);
20 |
21 | const middlewares = middlewareRegistry.getMiddlewares();
22 | expect(middlewares.length).toBe(1);
23 | expect(middlewares[0]).toBe(mockMiddleware);
24 | });
25 |
26 | it('should register multiple middlewares', () => {
27 | const mockMiddleware1: DRequestHandler = (req, res, next) => next();
28 | const mockMiddleware2: DRequestHandler = (req, res, next) => next();
29 | middlewareRegistry.register(mockMiddleware1);
30 | middlewareRegistry.register(mockMiddleware2);
31 |
32 | const middlewares = middlewareRegistry.getMiddlewares();
33 | expect(middlewares.length).toBe(2);
34 | expect(middlewares).toContain(mockMiddleware1);
35 | expect(middlewares).toContain(mockMiddleware2);
36 | });
37 |
38 | it('should register and retrieve helmet middleware', () => {
39 | middlewareRegistry.register(helmet());
40 |
41 | const middlewares = middlewareRegistry.getMiddlewares();
42 | expect(middlewares.length).toBe(1);
43 | expect(typeof middlewares[0]).toBe('function');
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/utilities/encryptions/bcrypt_encryption.utilities.ts:
--------------------------------------------------------------------------------
1 | import { genSalt, hashSync, compareSync } from 'bcryptjs';
2 | import { bcryptCompareParam, bcryptHashParam } from '../../common';
3 | import { boolean } from 'joi';
4 |
5 | /**
6 | *
7 | * @param param0 takes the `salt` and `pureString` params and if the salt param is not given, a 11 round salt is generated by default
8 | * @returns resulting hash
9 | *
10 | * @version 1.0.0
11 | * @deprecated use `hashString` instead
12 | */
13 | const hashWithBcrypt = async ({ pureString, salt }: bcryptHashParam): Promise => {
14 | return hashSync(pureString, salt ? salt : await genSalt(11));
15 | };
16 |
17 | /**
18 | *
19 | * @param pureString this is the string to be hashed
20 | * @param salt this is the salt. In the absence of the field, an 11 round salt is generated by default
21 | * @returns resulting hash
22 | *
23 | * @version 2.0
24 | */
25 | const hashString = async (pureString: string, salt: number = 11): Promise => {
26 | return hashSync(pureString, await genSalt(salt));
27 | };
28 |
29 | /**
30 | *
31 | * @param param0 takes the `pureString` and `hashString` params
32 | * @returns {Boolean} true/false depending on the outcome of comparison
33 | *
34 | * @version 1.0.0
35 | * @deprecated use `compareHashedString` instead
36 | *
37 | */
38 | const compareWithBcryptHash = ({ pureString, hashString }: bcryptCompareParam): boolean => {
39 | return compareSync(pureString, hashString);
40 | };
41 |
42 | /**
43 | *
44 | * @param pureString this is the string to br compared against
45 | * @param hashString this is the previously hashed string
46 | * @returns {Boolean}
47 | *
48 | * @version 2.0
49 | */
50 | const compareHashedString = (pureString: string, hashString: string): boolean => {
51 | return compareSync(pureString, hashString);
52 | };
53 |
54 | export { compareWithBcryptHash, hashWithBcrypt, compareHashedString, hashString };
55 |
--------------------------------------------------------------------------------
/utilities/auth/JWT_generator.utilities.ts:
--------------------------------------------------------------------------------
1 | import JWT from 'jsonwebtoken';
2 | import { IPayload } from '../../common';
3 | import { readFileSync } from 'fs';
4 |
5 | /**
6 | *
7 | * Generates JWT auth token using the HMAC algorithm
8 | *
9 | * @version 1.0.0
10 | */
11 | const generateJWTwithHMAC = ({ payload, secret }: { payload: IPayload; secret: string }): string => {
12 | const token = JWT.sign(payload, secret);
13 | return token;
14 | };
15 |
16 | /**
17 | *
18 | * Verify's JWT auth token created using the HMAC algorithm
19 | *
20 | * @version 1.0.0
21 | */
22 | const verifyJWTwithHMAC = ({ token, secret }: { token: string | string[]; secret: string }): string | JWT.JwtPayload => {
23 | //@ts-expect-error
24 | const payload = JWT.verify(token, secret);
25 | return payload;
26 | };
27 |
28 | /**
29 | *
30 | * Generates JWT token using the RSA algorithm and a privatekey
31 | *
32 | * @version 1.0.0
33 | */
34 | const generateJWTwithRSA = ({ pathToPrivateKey, payload }: { pathToPrivateKey: string; payload: IPayload }): string => {
35 | const privateKey = readFileSync(pathToPrivateKey, 'utf8');
36 | const token = JWT.sign(payload, privateKey, { algorithm: 'RS256' });
37 | return token;
38 | };
39 |
40 | /**
41 | *
42 | * Verify's JWT token created using the `generateJWTwithRSA` function and publicKey
43 | *
44 | * @version 1.0.0
45 | */
46 | const verifyJWTwithRSA = ({
47 | pathToPublicKey,
48 | token,
49 | }: {
50 | pathToPublicKey: string;
51 | token: string | string[];
52 | }): string | JWT.JwtPayload => {
53 | const publicKey = readFileSync(pathToPublicKey, 'utf8');
54 | try {
55 | //@ts-expect-error
56 | const payload = JWT.verify(token, publicKey, { algorithms: ['RS256'] });
57 | return payload;
58 | } catch (e) {
59 | throw e;
60 | }
61 | };
62 |
63 | export { generateJWTwithHMAC, verifyJWTwithHMAC, verifyJWTwithRSA, generateJWTwithRSA };
64 |
--------------------------------------------------------------------------------
/samples/demo/src/components/user/user.controller.ts:
--------------------------------------------------------------------------------
1 | import { DolphControllerHandler } from '../../../../../classes';
2 | import {
3 | Dolph,
4 | SuccessResponse,
5 | DRequest,
6 | DResponse,
7 | InternalServerErrorException,
8 | Middleware,
9 | } from '../../../../../common';
10 | import { Get, Post, Route, UseMiddleware } from '../../../../../decorators';
11 | import { diskStorage, fileUploader, useFileUploader } from '../../../../../packages';
12 | import { UserService } from './user.service';
13 | import { User2Service } from './user2.service';
14 |
15 | @Route('user')
16 | export class UserController extends DolphControllerHandler {
17 | private UserService: UserService;
18 | private User2Service: User2Service;
19 |
20 | constructor() {
21 | super();
22 | }
23 |
24 | @Get('greet')
25 | async greet(req: DRequest, res: DResponse) {
26 | const result1 = this.UserService.createUser(12, 'hello');
27 |
28 | const result2 = this.User2Service.get();
29 |
30 | SuccessResponse({
31 | res,
32 | body: { message: "you've reached the user endpoint.", resultOne: result1, resultTwo: result2 },
33 | });
34 | }
35 |
36 | // Make a wrapper that returns the function : req: DRequest, res: DResponse, next: DNextFunc
37 |
38 | @Post('')
39 | @UseMiddleware(
40 | useFileUploader({
41 | type: 'array',
42 | fieldname: 'upload',
43 | maxCount: 2,
44 |
45 | // storage: diskStorage({
46 | // destination: './uploads',
47 | // filename: (req, file, cb) => {
48 | // cb(null, Date.now() + '-' + file.originalname);
49 | // },
50 | // }),
51 | }),
52 | )
53 | async post(req: DRequest, res: DResponse) {
54 | console.log('req.files: ', req.file);
55 | SuccessResponse({ res, body: req.body });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/core/transformer.ts:
--------------------------------------------------------------------------------
1 | import { ClassConstructor, plainToInstance } from 'class-transformer';
2 | import { validate, ValidationError } from 'class-validator';
3 | import clc from 'cli-color';
4 | import { logger } from '../utilities';
5 | import { ValidationException } from './adapters/exception_handlers';
6 |
7 | export async function transformAndValidateDto(
8 | dtoClass: ClassConstructor | undefined,
9 | plainObject: any,
10 | // e.g., "request body", "query parameters"
11 | contextDescription: string,
12 | ): Promise {
13 | // Ensure dtoClass is a valid class constructor for DTOs
14 | if (!dtoClass) return;
15 | if (typeof dtoClass !== 'function' || [String, Number, Boolean, Object].includes(dtoClass as any)) {
16 | // Object constructor itself is not a DTO
17 | // This scenario should ideally be caught by the @DBody decorator's type checking.
18 | // However, if it somehow gets here, it's an internal error or misconfiguration.
19 | logger.error(clc.red(`Invalid DTO class provided for ${contextDescription}. Received:, dtoClass`));
20 | throw new Error(`Dolph: Misconfigured DTO type for ${contextDescription}.`);
21 | }
22 |
23 | const dtoInstance = plainToInstance(dtoClass, plainObject, {
24 | enableImplicitConversion: true,
25 | });
26 |
27 | const errors: ValidationError[] = await validate(dtoInstance, {
28 | // Remove properties that do not have any validation decorators
29 | whitelist: true,
30 | // Throw an error if non-whitelisted properties are present
31 | forbidNonWhitelisted: true,
32 | // validationError: { target: false }, // Optionally hide the target object in errors
33 | });
34 |
35 | if (errors.length > 0) {
36 | logger.error(clc.red(`Validation failed for ${contextDescription}:`), errors);
37 | throw new ValidationException(errors, `Validation failed for ${contextDescription}`);
38 | }
39 |
40 | return dtoInstance;
41 | }
42 |
--------------------------------------------------------------------------------
/common/interfaces/dolph.interfaces.ts:
--------------------------------------------------------------------------------
1 | import { DolphMiddlewareOption, dolphEnv, dolphPort } from '..';
2 | import { IPayload, MongooseConfig } from '.';
3 | import { NextFunction, Request, RequestHandler, Response, Router } from 'express';
4 | import { IncomingMessage, ServerResponse } from 'http';
5 | import { FileInfo, UploadConfig } from '../types/dolph_uploader.type';
6 |
7 | export interface DolphConfig {
8 | database?: DolphConfigDbOption;
9 | middlewares?: DolphConfigMiddlewares;
10 | port?: dolphPort;
11 | routing?: DolphConfigRouting;
12 | env?: dolphEnv;
13 | jsonLimit?: string;
14 | globalExceptionFilter?: boolean;
15 | }
16 |
17 | export interface DolphConfigDbOption {
18 | mongo: MongooseConfig;
19 | mysql: MySqlConfig;
20 | }
21 |
22 | export interface MySqlConfig {
23 | host: string;
24 | database: string;
25 | user: string;
26 | pass?: string | null;
27 | }
28 |
29 | export interface DolphConfigMiddlewares {
30 | cors?: DolphMiddlewareOption;
31 | xss?: DolphMiddlewareOption;
32 | }
33 |
34 | export interface DolphConfigRouting {
35 | base?: string;
36 | }
37 |
38 | export interface DolphConstructor {
39 | new (...args: any[]): T;
40 | }
41 |
42 | export type DRequest = Omit &
43 | IncomingMessage & {
44 | /**
45 | * stores the value of token payload
46 | */
47 | payload?: IPayload;
48 | body?: Record;
49 | file?: FileInfo;
50 | files?: FileInfo[] | Record;
51 | uploadConfig?: UploadConfig;
52 | };
53 |
54 | export type DResponse = Response &
55 | ServerResponse & {
56 | body?: T;
57 | };
58 |
59 | export interface DRequestHandler extends RequestHandler {}
60 |
61 | export type DNextFunc = NextFunction & {};
62 |
63 | export interface DRouter extends Router {}
64 |
65 | export interface OtherParams {
66 | passportConfigs?: passportConfigs;
67 | }
68 |
69 | export interface passportConfigs {
70 | passportStrategy?: any;
71 | passportSerializer?: any;
72 | }
73 |
--------------------------------------------------------------------------------
/packages/uploader/index.ts:
--------------------------------------------------------------------------------
1 | export { DolphFIleUploaderError as DFileUploaderError } from './errors/error_messages';
2 | import { extname } from 'path';
3 | import { DRequest, DResponse } from '../../common';
4 | import { FileInfo, UploadOptions } from '../../common/types/dolph_uploader.type';
5 | import { defaultFileExtensions } from '../../utilities/media/file_extensions.utilities';
6 | import { diskStorage, fileUploader, memoryStorage } from './file_uploader';
7 |
8 | /**
9 | * diskStorage({
10 | destination: './uploads',
11 | filename: (req, file, cb) => {
12 | cb(null, Date.now() + '-' + file.originalname);
13 | },
14 | */
15 |
16 | /**
17 | * Use this middleware to process media files
18 | * @version 1.4.0
19 | *
20 | * @author Utee
21 | *
22 | */
23 | export const useFileUploader =
24 | ({ storage, fileFilter, extensions, type, fieldname, fields, limit, maxCount }: UploadOptions) =>
25 | (req, res: DResponse, next) => {
26 | let _filter = fileFilter;
27 |
28 | if (!_filter) {
29 | let _extensions = extensions?.length ? extensions : defaultFileExtensions;
30 |
31 | _filter = (req: DRequest, file: FileInfo, callback) => {
32 | const extensionCheck = _extensions.includes(extname(file.originalname).toLowerCase());
33 |
34 | if (!extensionCheck && file.originalname !== 'blob') {
35 | return callback(new Error('Unsupported media file'), false);
36 | }
37 |
38 | return callback(null, true);
39 | };
40 | }
41 |
42 | // The bug is here at the fieldname upload
43 |
44 | const uploadMiddleware = fileUploader({
45 | storage: storage || memoryStorage(),
46 | limits: {
47 | fileSize: limit || 5 * 1024 * 1024, // 5MB
48 | },
49 | fieldname: fieldname || 'upload',
50 | fileFilter: _filter,
51 | type,
52 | maxCount,
53 | fields,
54 | });
55 |
56 | uploadMiddleware(req, res, next);
57 | };
58 |
59 | export { fileUploader, diskStorage, memoryStorage };
60 |
--------------------------------------------------------------------------------
/utilities/auth/cookie.utilities.ts:
--------------------------------------------------------------------------------
1 | import { configs } from '../../core/config.core';
2 | import { DNextFunc, DRequest, DResponse, ErrorException, HttpStatus, IPayload, cookieContent, sub } from '../../common';
3 | import { generateJWTwithHMAC, verifyJWTwithHMAC } from './JWT_generator.utilities';
4 |
5 | /**
6 | *
7 | * @param sub holds value for the principal data used in hashing the token
8 | * @param exp expiration time in Date
9 | * @param secret token secret used for authorization
10 | * @param info option parameter used to store data that might be useful
11 | *
12 | * @version 2
13 | */
14 | export const newAuthCookie = (sub: sub, exp: Date, secret: string, info?: string | object | []): cookieContent => {
15 | const payload: IPayload = {
16 | iat: Math.floor(Date.now() / 1000),
17 | exp: Math.floor(exp.getTime() / 1000),
18 | sub: sub,
19 | info: info,
20 | };
21 | const token = generateJWTwithHMAC({ payload, secret });
22 |
23 | const options = {
24 | expires: new Date(exp.getTime()),
25 | httpOnly: false,
26 | secure: false,
27 | };
28 |
29 | if (configs.NODE_ENV !== 'development') {
30 | options.httpOnly = true;
31 | options.secure = true;
32 | }
33 |
34 | return {
35 | name: 'xAuthToken',
36 | value: token,
37 | expires: options.expires,
38 | httpOnly: options.httpOnly,
39 | secure: options.secure,
40 | };
41 | };
42 |
43 | /**
44 | * function used for authorization based on the dolphjs default cookie authentication and authorization pattern
45 | *
46 | * @version 1.1
47 | */
48 | export const cookieAuthVerify = (tokenSecret: string) => (req: DRequest, _res: DResponse, next: DNextFunc) => {
49 | const cookies = req.cookies;
50 |
51 | if (!cookies) return next(new ErrorException('user not authorized, login and try again', HttpStatus.UNAUTHORIZED));
52 |
53 | const { xAuthToken } = cookies;
54 |
55 | if (!xAuthToken) return next(new ErrorException('user not authorized, login and try again', HttpStatus.UNAUTHORIZED));
56 |
57 | let payload: IPayload;
58 |
59 | //@ts-expect-error
60 | payload = verifyJWTwithHMAC({ token: xAuthToken, secret: tokenSecret });
61 |
62 | req.payload = payload;
63 | next();
64 | };
65 |
--------------------------------------------------------------------------------
/packages/uploader/file_appender.ts:
--------------------------------------------------------------------------------
1 | import { DRequest } from '../../common';
2 | import { FileInfo, FileStrategy } from '../../common/types/dolph_uploader.type';
3 |
4 | function arrayRemove(arr: T[], item: T): void {
5 | const index = arr.indexOf(item);
6 | if (index !== -1) arr.splice(index, 1);
7 | }
8 |
9 | export class FileAppender {
10 | constructor(
11 | private strategy: FileStrategy,
12 | private request: DRequest,
13 | ) {
14 | switch (strategy) {
15 | case 'ARRAY':
16 | this.request.files = [];
17 | break;
18 | case 'OBJECT':
19 | this.request.files = Object.create(null);
20 | break;
21 | }
22 | }
23 |
24 | insertPlaceholder(file: FileInfo): Partial {
25 | const placeholder = { fieldname: file.fieldname };
26 |
27 | switch (this.strategy) {
28 | case 'ARRAY':
29 | (this.request.files as FileInfo[]).push(placeholder as FileInfo);
30 | break;
31 | case 'OBJECT':
32 | const files = this.request.files as Record;
33 | if (files[file.fieldname]) {
34 | files[file.fieldname].push(placeholder as FileInfo);
35 | } else {
36 | files[file.fieldname] = [placeholder as FileInfo];
37 | }
38 | break;
39 | }
40 |
41 | return placeholder;
42 | }
43 |
44 | removePlaceholder(placeholder: Partial): void {
45 | switch (this.strategy) {
46 | case 'ARRAY':
47 | const files = this.request.files as FileInfo[];
48 | arrayRemove(files, placeholder);
49 | break;
50 | case 'OBJECT':
51 | const filesObj = this.request.files as Record;
52 | if (filesObj[placeholder.fieldname!].length === 1) {
53 | delete filesObj[placeholder.fieldname!];
54 | } else {
55 | arrayRemove(filesObj[placeholder.fieldname!], placeholder);
56 | }
57 | break;
58 | }
59 | }
60 |
61 | replacePlaceholder(placeholder: Partial, file: FileInfo): void {
62 | if (this.strategy === 'VALUE') {
63 | this.request.file = file;
64 | return;
65 | }
66 | delete placeholder.fieldname;
67 | Object.assign(placeholder, file);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/common/middlewares/try_catch_middleware.middleware.ts:
--------------------------------------------------------------------------------
1 | import { DNextFunc, DRequest, DResponse } from '../interfaces';
2 |
3 | /**
4 | *
5 | * Creates an asynchronous function wrapped in a try-catch block
6 | *
7 | * This is the recommended function to be used for controllers and services that need try-catch
8 | *
9 | * @version 1.0.0
10 | */
11 | const TryCatchAsyncFn =
12 | (fn: (req: DRequest, res: DResponse, next: DNextFunc) => Promise) =>
13 | (req: DRequest, res: DResponse, next: DNextFunc) => {
14 | Promise.resolve(fn(req, res, next)).catch((err) => next(err));
15 | };
16 |
17 | /**
18 | *
19 | * An asynchronous class-method decorator which wraps a method in a try-catch block
20 | *
21 | * Should be used as a top-most decorator
22 | * @version 1.0.0
23 | * @deprecated - see @dolphjs/dolph/decorators
24 | */
25 | const TryCatchAsyncDec = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
26 | const originalMethod = descriptor.value;
27 |
28 | descriptor.value = async function (...args: any[]) {
29 | const context = this;
30 | // console.log(this);
31 | const [req, res, next] = args;
32 | try {
33 | await originalMethod.apply(context, args);
34 | } catch (err) {
35 | next(err);
36 | }
37 | };
38 |
39 | // return descriptor;
40 | };
41 |
42 | /**
43 | *
44 | * A class-method decorator which wraps a method in a try-catch block
45 | *
46 | * Should be used as a top-most decorator
47 | *
48 | * @version 1.0.4
49 | */
50 | const TryCatchDec = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
51 | const originalMethod = descriptor.value;
52 |
53 | descriptor.value = function (...args: any[]) {
54 | const context = this;
55 | // console.log(this);
56 | const [req, res, next] = args;
57 | try {
58 | originalMethod.apply(context, args);
59 | } catch (err) {
60 | next(err);
61 | }
62 | };
63 |
64 | // return descriptor;
65 | };
66 |
67 | /**
68 | *
69 | * Creates a function wrapped in a try-catch block
70 | *
71 | * This is the recommended function to be used for controllers and services that don't use try-catch blocks
72 | *
73 | * @version 1.0.0
74 | */
75 | const TryCatchFn =
76 | (fn: (req: DRequest, res: DResponse, next: DNextFunc) => void) => (req: DRequest, res: DResponse, next: DNextFunc) => {
77 | Promise.resolve(fn(req, res, next)).catch((err) => next(err));
78 | };
79 |
80 | export { TryCatchAsyncFn, TryCatchFn, TryCatchAsyncDec, TryCatchDec };
81 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing to the DolphJs Docs!
4 |
5 | ## Code of Conduct
6 |
7 | DolphJs has adopted a code of conduct that we expect contributors to adhere to.
8 | Please read the instructions in the [CODE_OF_CONDUCT](CODE_OF_CONDUCT) so
9 | you can understand our ethics.
10 |
11 | **Try to follow your own instructions.**
12 |
13 | When writing step-by-step instructions (e.g. how to install something), try to forget everything you know about the topic, and actually follow the instructions you wrote, a single step at time. Often you will discover that there is implicit knowledge that you forgot to mention, or that there are missing or out-of-order steps in the instructions. Bonus points for getting _somebody else_ to follow the steps and watching what they struggle with. Often it would be something very simple that you have not anticipated.
14 |
15 | ## Guidelines for Code Examples
16 |
17 | ### Syntax
18 |
19 | #### Prefer OOP to Functional Programming
20 |
21 | DolphJS is strictly built with the OOP approach.
22 |
23 | #### Use `const` where possible, otherwise `let`. Don't use `var`
24 |
25 | #### Don't use ES5 features when equivalent ES6 features have no downsides
26 |
27 | #### Don't forget to remove console.log's when finished with them
28 |
29 | #### Ensure that your code is scalable and uses the best implementation e.g prefer code with 0(n) to 0(logn)
30 |
31 | In particular, you should prefer arrow functions `const myFunction = () => ...` to named `functions` for top-level functions.
32 |
33 | ### Package Manager
34 |
35 | - Use only yarn
36 |
37 | ### Style
38 |
39 | - Use semicolons.
40 | - No space between function names and parenthesis (`method() {}` not `method () {}`).
41 | - When in doubt, use the default style favored by [Prettier](https://prettier.io/playground/).
42 |
43 | ### Highlighting
44 |
45 | Use `typescript` as the highlighting language in Markdown code blocks:
46 |
47 | ````
48 | ```typescript
49 | // code
50 | ```
51 | ````
52 |
53 | Sometimes you'll see blocks with numbers.
54 | They tell the website to highlight specific lines.
55 |
56 | You can highlight a single line:
57 |
58 | ````
59 | ```javascipt {2}
60 | function hello() {
61 | // this line will get highlighted
62 | }
63 | ```
64 | ````
65 |
66 | A range of lines:
67 |
68 | ````
69 | ```typescript {2-4}
70 | function hello() {
71 | // these lines
72 | // will get
73 | // highlighted
74 | }
75 | ```
76 | ````
77 |
78 | Or even multiple ranges:
79 |
80 | ````
81 | ```typescript {2-4,6}
82 | function hello() {
83 | // these lines
84 | // will get
85 | // highlighted
86 | console.log('hello');
87 | // also this one
88 | console.log('there');
89 | }
90 | ```
91 | ````
92 |
93 | Be mindful that if you move some code in an example with highlighting, you also need to update the highlighting.
94 |
95 | Don't be afraid to often use highlighting! It is very valuable when you need to focus the reader's attention on a particular detail that's easy to miss.
96 |
--------------------------------------------------------------------------------
/packages/events/events_module.packages.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'node:events';
2 | import { Service } from 'typedi';
3 | import { Listener } from '../../common';
4 |
5 | @Service()
6 | export class EventEmitterService extends EventEmitter {
7 | private listenerRegistry: Map = new Map();
8 |
9 | constructor() {
10 | super();
11 | }
12 |
13 | emitEvent>(eventName: string, ...args: T): boolean {
14 | let listeners = this.listenerRegistry.get(eventName) || [];
15 | let stopPropagation = false;
16 |
17 | for (const entry of listeners) {
18 | const result = entry.listener(...args);
19 | if (entry.once) {
20 | this.offEvent(eventName, entry.listener);
21 | }
22 | if (result === false) {
23 | stopPropagation = true;
24 | break;
25 | }
26 | }
27 |
28 | // Cleanup listeners marked for one-time use
29 | listeners = listeners.filter((listener) => !listener.once);
30 | this.listenerRegistry.set(eventName, listeners);
31 |
32 | return !stopPropagation;
33 | }
34 |
35 | onEvent void | boolean>(
36 | eventName: string,
37 | listener: T,
38 | priority: number = 0,
39 | once: boolean = false,
40 | ): void {
41 | const listeners = this.listenerRegistry.get(eventName) || [];
42 | listeners.push({ listener, priority, once });
43 | listeners.sort((a, b) => b.priority - a.priority);
44 | this.listenerRegistry.set(eventName, listeners);
45 | if (once) {
46 | super.once(eventName, listener as (...args: any[]) => void);
47 | } else {
48 | super.on(eventName, listener as (...args: any[]) => void);
49 | }
50 | }
51 |
52 | onceEvent void>(eventName: string, listener: T, priority: number = 0): void {
53 | this.onEvent(eventName, listener, priority, true);
54 | }
55 |
56 | offEvent void>(eventName: string, listener: T): void {
57 | const listeners = this.listenerRegistry.get(eventName) || [];
58 | const index = listeners.findIndex((l) => l.listener === listener);
59 | if (index !== -1) {
60 | listeners.splice(index, 1);
61 | this.listenerRegistry.set(eventName, listeners);
62 | }
63 | super.off(eventName, listener);
64 | }
65 |
66 | removeAllListenersForEvent(eventName: string): void {
67 | this.listenerRegistry.delete(eventName);
68 | super.removeAllListeners(eventName);
69 | }
70 |
71 | unregisterEvent(eventName: string, listener: (...args) => void) {
72 | this.removeListener(eventName, listener);
73 | let listeners = this.listenerRegistry.get(eventName) || [];
74 | listeners = listeners.filter((l) => l.listener !== listener);
75 | this.listenerRegistry.set(eventName, listeners);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/uploader/diskstorage.test.ts:
--------------------------------------------------------------------------------
1 | import { unlinkSync } from 'fs';
2 | import { join } from 'path';
3 | import { Readable } from 'stream';
4 | import { diskStorage } from '../../packages';
5 | import { DRequest } from '../../common';
6 |
7 | describe('Disk Storage Test', () => {
8 | const testDestination = join(__dirname, 'uploads');
9 | let storage: ReturnType;
10 |
11 | beforeEach(() => {
12 | storage = diskStorage({ destination: testDestination });
13 | });
14 |
15 | afterEach(() => {
16 | try {
17 | unlinkSync(join(testDestination, 'test-file.txt'));
18 | } catch (err) {
19 | // Ignore cleanup errors
20 | }
21 | });
22 |
23 | test('should handle file upload correctly', (done) => {
24 | const mockFile = {
25 | fieldname: 'testFile',
26 | originalname: 'test-file.txt',
27 | encoding: '7bit',
28 | mimetype: 'text/plain',
29 | stream: Readable.from(Buffer.from('test content')),
30 | };
31 |
32 | const mockRequest = {} as DRequest;
33 |
34 | storage.handleFile(mockRequest, mockFile, (error, info) => {
35 | expect(error).toBeNull();
36 | expect(info).toBeDefined();
37 | expect(info?.destination).toBe(testDestination);
38 | expect(info?.filename).toBeDefined();
39 | expect(info?.path).toBeDefined();
40 | expect(info?.size).toBe(12); // length of 'test content'
41 | done();
42 | });
43 | });
44 |
45 | test('should remove file correctly', (done) => {
46 | const filePath = join(testDestination, 'test-file.txt');
47 | const mockFile = {
48 | fieldname: 'testFile',
49 | originalname: 'test-file.txt',
50 | encoding: '7bit',
51 | mimetype: 'text/plain',
52 | path: filePath,
53 | };
54 |
55 | const mockRequest = {} as DRequest;
56 |
57 | storage.removeFile(mockRequest, mockFile, (error) => {
58 | expect(error).toBeNull();
59 | expect(mockFile).not.toHaveProperty('path');
60 | expect(mockFile).not.toHaveProperty('destination');
61 | expect(mockFile).not.toHaveProperty('filename');
62 | done();
63 | });
64 | });
65 |
66 | test('should handle custom filename function', (done) => {
67 | const customStorage = diskStorage({
68 | destination: testDestination,
69 | filename: (_req, file, cb) => {
70 | cb(null, `${file.originalname}`);
71 | },
72 | });
73 |
74 | const mockFile = {
75 | fieldname: 'testFile',
76 | originalname: 'test-file.txt',
77 | encoding: '7bit',
78 | mimetype: 'text/plain',
79 | stream: Readable.from(Buffer.from('test content')),
80 | };
81 |
82 | const mockRequest = {} as DRequest;
83 |
84 | customStorage.handleFile(mockRequest, mockFile, (error, info) => {
85 | expect(error).toBeNull();
86 | expect(info?.filename).toBe('test-file.txt');
87 | done();
88 | });
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/common/types/dolph_uploader.type.ts:
--------------------------------------------------------------------------------
1 | import { DRequest } from '../interfaces';
2 |
3 | export interface FileInfo {
4 | fieldname: string;
5 | originalname: string;
6 | encoding: string;
7 | mimetype: string;
8 | destination?: string;
9 | filename?: string;
10 | path?: string;
11 | size?: number;
12 | buffer?: Buffer;
13 | stream?: NodeJS.ReadableStream;
14 | }
15 |
16 | export interface UploadConfig {
17 | limits?: Record;
18 | preservePaths?: boolean;
19 | storage: Storage;
20 | fileFilter: FileFilter;
21 | fileStrategy: FileStrategy;
22 | }
23 |
24 | export type UploadOptionAndConfig = UploadConfig & Omit & {};
25 |
26 | export type FileFilter = (
27 | req: DRequest,
28 | file: FileInfo,
29 | callback: (error: Error | null, acceptFile: boolean) => void,
30 | ) => void;
31 |
32 | export type FileStrategy = 'NONE' | 'VALUE' | 'ARRAY' | 'OBJECT';
33 |
34 | export type UploadOptions = {
35 | /**
36 | * accepts either **diskStorage** or **memoryStorage**
37 | *
38 | * defaults to *memoryStorage*
39 | *
40 | * @type {Storage}
41 | */
42 | storage?: Storage;
43 | /**
44 | * the name that file(s) sent to the dolph app will be identified by
45 | *
46 | * defaults to *upload*
47 | */
48 | fieldname?: string;
49 | /**
50 | * the limit in fileSize for media being processed
51 | *
52 | * defaults to *5MB*
53 | */
54 | limit?: number;
55 |
56 | /**
57 | * type of media process to use
58 | *
59 | * **single** accepts only one file
60 | *
61 | *
62 | * **array** accepts an array of files
63 | *
64 | * only when set to **array** does **maxCount** matter
65 | *
66 | * **fields** accepts fields/object of files
67 | *
68 | */
69 | type: 'single' | 'fields' | 'array';
70 |
71 | /**
72 | * @type {FileFilter}
73 | *
74 | * defaults to use the default *FileFilter* function which should suit most use-cases
75 | */
76 | fileFilter?: FileFilter;
77 |
78 | /**
79 | * specify what file extensions should be allowed in this format: **[".png", ".pdf"]**
80 | */
81 | extensions?: string[];
82 |
83 | /**
84 | * max count of files that can be uploaded
85 | *
86 | * only works when `type` is of *array*
87 | */
88 | maxCount?: number;
89 |
90 | /**
91 | * @type {UploadFields}
92 | */
93 | fields?: UploadFields[];
94 | };
95 |
96 | export type UploadFields = {
97 | name: string;
98 | maxCount?: number;
99 | };
100 |
101 | export interface Storage {
102 | handleFile: (req: DRequest, file: FileInfo, callback: (error: Error | null, info?: Partial) => void) => void;
103 | removeFile: (req: DRequest, file: FileInfo, callback: (error: Error | null) => void) => void;
104 | }
105 |
106 | export interface FileRemoveCallback {
107 | (error: Error | null, storageErrors?: Error[]): void;
108 | }
109 | export interface FileHandler {
110 | (file: FileInfo, callback: (error: Error | null) => void): void;
111 | }
112 |
113 | export interface DiskStorageOptions {
114 | destination?:
115 | | string
116 | | ((req: DRequest, file: FileInfo, callback: (error: Error | null, destination: string) => void) => void);
117 | filename?: (req: DRequest, file: FileInfo, callback: (error: Error | null, filename: string) => void) => void;
118 | }
119 |
--------------------------------------------------------------------------------
/samples/basic/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { DolphControllerHandler, DolphServiceHandler, JWTAuthVerifyDec, JWTAuthorizeDec } from '../../classes';
2 | import { TryCatchAsyncDec, TryCatchAsyncFn, TryCatchFn } from '../../common/middlewares';
3 | import { AppService } from './app.service';
4 | import { BadRequestException, DNextFunc, DRequest, DResponse, Dolph, SuccessResponse } from '../../common';
5 | import { generateJWTwithHMAC, newAuthCookie } from '../../utilities';
6 | import moment from 'moment';
7 | import { MediaParser } from '../../utilities';
8 | import { CookieAuthVerifyDec, InjectServiceHandler } from '../../decorators';
9 | import { authFunc } from './app.auth_function';
10 |
11 | @InjectServiceHandler([{ serviceHandler: AppService, serviceName: 'appservice' }])
12 | class ControllerService {
13 | appservice!: AppService;
14 | }
15 |
16 | const controllerServices = new ControllerService();
17 | class AppController extends DolphControllerHandler {
18 | constructor() {
19 | super();
20 | this.createUser = this.createUser.bind(this);
21 | }
22 |
23 | public readonly sendGreeting = TryCatchFn((req: DRequest, res: DResponse) => {
24 | const { body } = req;
25 | const response = controllerServices.appservice.greeting(body);
26 | SuccessResponse({ res, msg: response });
27 | });
28 |
29 | @TryCatchAsyncDec
30 | @JWTAuthVerifyDec('random_secret')
31 | // @JWTAuthorizeDec('random_secret', authFunc)
32 | @MediaParser({ fieldname: 'upload', type: 'single', extensions: ['.png'] })
33 | public async createUser(req: DRequest, res: DResponse) {
34 | const { body, file } = req;
35 | if (body.height < 1.7) throw new BadRequestException('sorry, you are too short for this program');
36 | const data = await controllerServices.appservice.createUser(body);
37 | SuccessResponse({ res, body: { data, file: file, payload: req.payload } });
38 | }
39 |
40 | public readonly register = TryCatchAsyncFn(async (req: DRequest, res: DResponse, next: DNextFunc) => {
41 | const { username } = req.body;
42 | console.log(req.file);
43 | const token = generateJWTwithHMAC({
44 | payload: {
45 | exp: moment().add(30000, 'seconds').unix(),
46 | iat: moment().unix(),
47 | sub: username,
48 | },
49 | secret: 'random_secret',
50 | });
51 | SuccessResponse({ res, body: token });
52 | });
53 |
54 | @TryCatchAsyncDec
55 | public async testMysql(req: DRequest, res: DResponse) {
56 | const { username, age } = req.body;
57 | console.log(username, age);
58 | const result = await controllerServices.appservice.createSQLUser({ username, age });
59 | SuccessResponse({ res, body: result });
60 | }
61 |
62 | @TryCatchAsyncDec
63 | public async testCookieFn(req: DRequest, res: DResponse) {
64 | const cookieValue = newAuthCookie('user_id_00', 100000, 'random_secret', { info: 'yeah' });
65 | res.cookie(cookieValue.name, cookieValue.value, {
66 | expires: cookieValue.expires,
67 | secure: cookieValue.secure,
68 | httpOnly: cookieValue.httpOnly,
69 | });
70 |
71 | res.send('done');
72 | }
73 |
74 | @TryCatchAsyncDec
75 | @CookieAuthVerifyDec('random_secret')
76 | public async testCookieVerify(req: DRequest, res: DResponse) {
77 | const payload = req.payload;
78 |
79 | res.json(payload);
80 | }
81 | }
82 | // const appController = new AppController();
83 | export { AppController };
84 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to make participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 |
77 |
--------------------------------------------------------------------------------
/common/middlewares/global_validation_middleware.middlewares.ts:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import { plainToClass } from 'class-transformer';
3 | import { DNextFunc, DRequest, DResponse } from '../interfaces';
4 | import { validate, ValidationError } from 'class-validator';
5 |
6 | // does not work for current use
7 | export const validationMiddlewareOne = async (req: DRequest, res: DResponse, next: DNextFunc) => {
8 | const routeHandler = req.route?.stack?.find((layer) => layer.method === req.method.toLowerCase())?.handle;
9 | const dto = Reflect.getMetadata('dto', routeHandler);
10 |
11 | if (!dto) {
12 | return next();
13 | }
14 |
15 | const bodyObject = plainToClass(dto, req.body);
16 | const queryObject = plainToClass(dto, req.query);
17 | const paramObject = plainToClass(dto, req.params);
18 |
19 | const errors: ValidationError[] = await Promise.all([
20 | validate(bodyObject),
21 | validate(queryObject),
22 | validate(paramObject),
23 | ]).then((results) => [].concat(...results));
24 |
25 | if (errors.length > 0) {
26 | const constraints = errors
27 | .map((error) => Object.values(error.constraints || {}))
28 | .reduce((acc, val) => acc.concat(val), []);
29 | return res.status(400).json({ message: 'Validation failed', errors: constraints });
30 | }
31 |
32 | if (req.body) {
33 | req.body = bodyObject;
34 | }
35 |
36 | if (req.params) {
37 | req.params = paramObject;
38 | }
39 |
40 | if (req.query) {
41 | req.query = queryObject;
42 | }
43 |
44 | next();
45 | };
46 |
47 | export const validateBodyMiddleware = (dto: any) => {
48 | return async (req: DRequest, res: DResponse, next: DNextFunc) => {
49 | const object = plainToClass(dto, req.body);
50 |
51 | const errors: ValidationError[] = await validate(object);
52 |
53 | if (errors.length > 0) {
54 | const constraints = errors
55 | .map((error) => Object.values(error.constraints || {}))
56 | .reduce((acc, val) => acc.concat(val), [])
57 | .join(', ');
58 | return res.status(400).json({ message: 'Validation failed', error: constraints });
59 | } else {
60 | req.body = object;
61 | next();
62 | }
63 | };
64 | };
65 |
66 | export const validateQueryMiddleware = (dto: any) => {
67 | return async (req: DRequest, res: DResponse, next: DNextFunc) => {
68 | const object = plainToClass(dto, req.query);
69 |
70 | const errors: ValidationError[] = await validate(object);
71 |
72 | if (errors.length > 0) {
73 | const constraints = errors
74 | .map((error) => Object.values(error.constraints || {}))
75 | .reduce((acc, val) => acc.concat(val), [])
76 | .join(', ');
77 | return res.status(400).json({ message: 'Validation failed', error: constraints });
78 | } else {
79 | req.body = object;
80 | next();
81 | }
82 | };
83 | };
84 |
85 | export const validateParamMiddleware = (dto: any) => {
86 | return async (req: DRequest, res: DResponse, next: DNextFunc) => {
87 | const object = plainToClass(dto, req.query);
88 |
89 | const errors: ValidationError[] = await validate(object);
90 |
91 | if (errors.length > 0) {
92 | const constraints = errors
93 | .map((error) => Object.values(error.constraints || {}))
94 | .reduce((acc, val) => acc.concat(val), [])
95 | .join(', ');
96 | return res.status(400).json({ message: 'Validation failed', error: constraints });
97 | } else {
98 | req.body = object;
99 | next();
100 | }
101 | };
102 | };
103 |
--------------------------------------------------------------------------------
/utilities/logger.utilities.ts:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 | import clc from 'cli-color';
3 | import { formatDate } from './formatters/format_date.utilities';
4 |
5 | const logger = winston.createLogger({
6 | levels: {
7 | error: 0,
8 | warn: 1,
9 | info: 2,
10 | debug: 3,
11 | },
12 | format: winston.format.combine(
13 | winston.format.timestamp(),
14 | winston.format.printf((info) => {
15 | const { timestamp, level, message, stack } = info;
16 | const formattedTimestamp = clc.white(formatDate(timestamp));
17 | let formattedLevel: string;
18 |
19 | let newMessage: string = message;
20 |
21 | if (level === 'info') {
22 | formattedLevel = clc.blue(`[${level.toUpperCase()}]`);
23 | } else if (level === 'warn') {
24 | formattedLevel = clc.yellow(`[${level.toUpperCase()}]`);
25 | newMessage = clc.yellowBright(message);
26 | } else if (level === 'debug') {
27 | formattedLevel = clc.green(`[${level.toUpperCase()}]`);
28 | newMessage = clc.greenBright(message);
29 | } else if (level === 'error') {
30 | formattedLevel = clc.red(`[${level.toUpperCase()}]`);
31 | newMessage = clc.redBright(message);
32 | }
33 |
34 | let logMessage = `${formattedLevel}: ${formattedTimestamp} ${newMessage}`;
35 |
36 | if (stack) {
37 | logMessage += `\n${stack}`;
38 | }
39 |
40 | return logMessage;
41 | }),
42 | ),
43 |
44 | transports: [new winston.transports.Console({ level: 'debug' })],
45 | });
46 |
47 | const emojiForLevel = {
48 | info: '🔹',
49 | warn: '⚠️',
50 | debug: '🐛',
51 | error: '❗️',
52 | };
53 |
54 | // const componentEmoji = {
55 | // route: '🌐',
56 | // shield: '🛡️',
57 | // middleware: '🛡️',
58 | // };
59 |
60 | const inAppLogger = winston.createLogger({
61 | levels: {
62 | error: 0,
63 | warn: 1,
64 | info: 2,
65 | debug: 3,
66 | },
67 | format: winston.format.combine(
68 | winston.format.timestamp(),
69 | winston.format.printf((info) => {
70 | const { timestamp, level, message } = info;
71 | const formattedTimestamp = formatDate(timestamp);
72 | const logEmoji = emojiForLevel[level] || '🔹';
73 | let formattedMessage = message;
74 |
75 | let levelLog = `${clc.blue([`${level.toUpperCase()}`])}`;
76 | if (level === 'warn') {
77 | levelLog = `${clc.yellow([`${level}`])}`;
78 | } else if (level === 'debug') {
79 | levelLog = `${clc.cyan([`${level}`])}`;
80 | } else if (level === 'error') {
81 | levelLog = `${clc.red([`${level}`])}`;
82 | }
83 |
84 | if (message.startsWith('Registered')) {
85 | // Split the message to extract the relevant parts
86 | const [action, componentDetails] = message.split('>>>>').map((part) => part.trim());
87 |
88 | // Determine the component type based on the presence of specific keywords
89 | // const componentType = componentDetails.includes('Middleware')
90 | // ? 'middleware'
91 | // : componentDetails.includes('Shield')
92 | // ? 'shield'
93 | // : 'route'; // Default to 'route' if neither Middleware nor Shield is found
94 |
95 | // Construct the formatted message
96 | formattedMessage = `${logEmoji} [${formattedTimestamp}] ${levelLog}: ${clc.blueBright(
97 | `${action}`,
98 | )} ${clc.blueBright(`>>>> ${componentDetails}`)}`;
99 | } else {
100 | formattedMessage = `${logEmoji} [${formattedTimestamp}] ${levelLog}: ${formattedMessage}`;
101 | }
102 |
103 | return formattedMessage;
104 | }),
105 | ),
106 | transports: [new winston.transports.Console({ level: 'debug' })],
107 | });
108 |
109 | export { logger, inAppLogger };
110 |
--------------------------------------------------------------------------------
/tests/try_catch.test.ts:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import { DRequest, DResponse, DNextFunc } from '../common';
3 | import { TryCatchAsyncDec, TryCatchAsyncFn, TryCatchDec, TryCatchFn } from '../common';
4 |
5 | describe('TryCatch Utilities', () => {
6 | let req: Partial;
7 | let res: Partial;
8 | let next: DNextFunc;
9 |
10 | beforeEach(() => {
11 | req = {};
12 | res = {
13 | status: jest.fn().mockReturnThis(),
14 | json: jest.fn(),
15 | };
16 | next = jest.fn();
17 | });
18 |
19 | describe('TryCatchAsyncFn', () => {
20 | it('should call the next function with an error if the function throws', async () => {
21 | const error = new Error('Test error');
22 | const asyncFunction = TryCatchAsyncFn(async (req, res, next) => {
23 | throw error;
24 | });
25 |
26 | await asyncFunction(req as DRequest, res as DResponse, next);
27 |
28 | expect(next).toHaveBeenCalledWith(error);
29 | });
30 |
31 | it('should call the original function if no error is thrown', async () => {
32 | const asyncFunction = TryCatchAsyncFn(async (req, res, next) => {
33 | res.status(200).json({ message: 'Success' });
34 | });
35 |
36 | await asyncFunction(req as DRequest, res as DResponse, next);
37 |
38 | expect(res.status).toHaveBeenCalledWith(200);
39 | expect(res.json).toHaveBeenCalledWith({ message: 'Success' });
40 | expect(next).not.toHaveBeenCalled();
41 | });
42 | });
43 |
44 | describe('TryCatchFn', () => {
45 | // it('should call the next function with an error if the function throws', () => {
46 | // const error = new Error('Test error');
47 | // const syncFunction = TryCatchFn((req, res, next) => {
48 | // throw error;
49 | // });
50 |
51 | // syncFunction(req as DRequest, res as DResponse, next);
52 |
53 | // expect(next).toHaveBeenCalledWith(error);
54 | // });
55 |
56 | it('should call the original function if no error is thrown', () => {
57 | const syncFunction = TryCatchFn((req, res, next) => {
58 | res.status(200).json({ message: 'Success' });
59 | });
60 |
61 | syncFunction(req as DRequest, res as DResponse, next);
62 |
63 | expect(res.status).toHaveBeenCalledWith(200);
64 | expect(res.json).toHaveBeenCalledWith({ message: 'Success' });
65 | expect(next).not.toHaveBeenCalled();
66 | });
67 | });
68 |
69 | describe('TryCatchAsyncDec', () => {
70 | it('should call the next function with an error if the method throws', async () => {
71 | const error = new Error('Test error');
72 | class TestClass {
73 | @TryCatchAsyncDec
74 | async method(req: DRequest, res: DResponse, next: DNextFunc) {
75 | throw error;
76 | }
77 | }
78 |
79 | const instance = new TestClass();
80 | await instance.method(req as DRequest, res as DResponse, next);
81 |
82 | expect(next).toHaveBeenCalledWith(error);
83 | });
84 |
85 | it('should call the original method if no error is thrown', async () => {
86 | class TestClass {
87 | @TryCatchAsyncDec
88 | async method(req: DRequest, res: DResponse, next: DNextFunc) {
89 | res.status(200).json({ message: 'Success' });
90 | }
91 | }
92 |
93 | const instance = new TestClass();
94 | await instance.method(req as DRequest, res as DResponse, next);
95 |
96 | expect(res.status).toHaveBeenCalledWith(200);
97 | expect(res.json).toHaveBeenCalledWith({ message: 'Success' });
98 | expect(next).not.toHaveBeenCalled();
99 | });
100 | });
101 |
102 | describe('TryCatchDec', () => {
103 | it('should call the next function with an error if the method throws', () => {
104 | const error = new Error('Test error');
105 | class TestClass {
106 | @TryCatchDec
107 | method(req: DRequest, res: DResponse, next: DNextFunc) {
108 | throw error;
109 | }
110 | }
111 |
112 | const instance = new TestClass();
113 | instance.method(req as DRequest, res as DResponse, next);
114 |
115 | expect(next).toHaveBeenCalledWith(error);
116 | });
117 |
118 | it('should call the original method if no error is thrown', () => {
119 | class TestClass {
120 | @TryCatchDec
121 | method(req: DRequest, res: DResponse, next: DNextFunc) {
122 | res.status(200).json({ message: 'Success' });
123 | }
124 | }
125 |
126 | const instance = new TestClass();
127 | instance.method(req as DRequest, res as DResponse, next);
128 |
129 | expect(res.status).toHaveBeenCalledWith(200);
130 | expect(res.json).toHaveBeenCalledWith({ message: 'Success' });
131 | expect(next).not.toHaveBeenCalled();
132 | });
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@dolphjs/dolph",
3 | "version": "1.5.3",
4 | "description": "New-Age, developer friendly node.js and bun framework",
5 | "repository": "https://github.com/dolphjs/dolph",
6 | "author": "Utee Akaninyene",
7 | "license": "MIT",
8 | "publishConfig": {
9 | "access": "public"
10 | },
11 | "scripts": {
12 | "build:shell": "./compile.sh",
13 | "build:classes": "tsc -b classes",
14 | "build:common": "tsc -b common",
15 | "build:core": "tsc -b core",
16 | "build:decorators": "tsc -b decorators",
17 | "build:packages": "tsc -b packages",
18 | "build:utilities": "tsc -b utilities",
19 | "build:all": "npm run build:classes && npm run build:common && npm run build:core && npm run build:decorators && npm run build:packages && npm run build:utilities",
20 | "build": "tsc -b -v .",
21 | "build:dev": "tsc -b -v . --watch",
22 | "prebuild:prod": "npm run clean",
23 | "build:prod": "tsc -b -v common",
24 | "build:sample": "tsc -b -v samples",
25 | "test": "jest --forceExit --detectOpenHandles",
26 | "clean": "npm run clean:all",
27 | "clean:all": "tsc -b --clean classes common core decorators packages utilities",
28 | "format": "prettier \"**/*.ts\" \"common/**/*.json\" --ignore-path ./.prettierignore --write && git status",
29 | "copy:package-json": "gulp copy:package-json",
30 | "copy:readme": "gulp copy:readme",
31 | "copy:license": "gulp copy:license",
32 | "copy:npm-ignore": "gulp copy:npm-ignore",
33 | "copy:all": "npm run copy:package-json && npm run copy:readme && npm run copy:license && npm run copy:npm-ignore",
34 | "pre:release": "npm run test && npm run build && npm run copy:all"
35 | },
36 | "lint-staged": {
37 | "**/*.ts": [
38 | "prettier --ignore-path ./.prettierignore --write"
39 | ],
40 | "core/**/*.json": [
41 | "prettier --ignore-path ./.prettierignore --write"
42 | ],
43 | "common/**/*.json": [
44 | "prettier --ignore-path ./.prettierignore --write"
45 | ],
46 | "classes/**/*.json": [
47 | "prettier --ignore-path ./.prettierignore --write"
48 | ],
49 | "decorators/**/*.json": [
50 | "prettier --ignore-path ./.prettierignore --write"
51 | ],
52 | "packages/**/*.json": [
53 | "prettier --ignore-path ./.prettierignore --write"
54 | ],
55 | "utilities/**/*.json": [
56 | "prettier --ignore-path ./.prettierignore --write"
57 | ]
58 | },
59 | "engines": {
60 | "node": ">= 18"
61 | },
62 | "devDependencies": {
63 | "@dolphjs/graphql": "^0.2.1",
64 | "@types/autocannon": "^7.12.5",
65 | "@types/bcryptjs": "^2.4.2",
66 | "@types/busboy": "^1.5.4",
67 | "@types/cli-color": "^2.0.2",
68 | "@types/cookie-parser": "^1.4.6",
69 | "@types/cors": "^2.8.13",
70 | "@types/ejs": "^3.1.5",
71 | "@types/express": "^4.17.17",
72 | "@types/express-handlebars": "^6.0.0",
73 | "@types/gulp": "^4.0.13",
74 | "@types/jest": "^29.5.12",
75 | "@types/js-yaml": "^4.0.5",
76 | "@types/jsonwebtoken": "^9.0.2",
77 | "@types/mime-types": "^2.1.4",
78 | "@types/multer": "^1.4.12",
79 | "@types/node": "^20.3.2",
80 | "@types/pug": "^2.0.10",
81 | "@types/sequelize": "^4.28.15",
82 | "@types/socket.io": "^3.0.2",
83 | "@typescript-eslint/eslint-plugin": "^5.60.1",
84 | "@typescript-eslint/parser": "^5.60.1",
85 | "autocannon": "^7.15.0",
86 | "delete-empty": "^3.0.0",
87 | "ejs": "^3.1.10",
88 | "eslint": "^8.43.0",
89 | "eslint-config-prettier": "^8.8.0",
90 | "eslint-plugin-import": "^2.27.5",
91 | "form-data": "^4.0.1",
92 | "fs-temp": "^2.0.1",
93 | "graphql-scalars": "^1.23.0",
94 | "gulp": "^4.0.2",
95 | "gulp-clang-format": "^1.0.27",
96 | "gulp-clean": "^0.4.0",
97 | "gulp-sourcemaps": "^3.0.0",
98 | "gulp-typescript": "^6.0.0-alpha.1",
99 | "gulp-watch": "^5.0.1",
100 | "jest": "^29.7.0",
101 | "nodemon": "^3.0.1",
102 | "pug": "^3.0.3",
103 | "rimraf": "^6.0.1",
104 | "supertest": "^7.0.0",
105 | "ts-jest": "^29.2.3",
106 | "ts-node": "^10.9.1",
107 | "type-graphql": "^2.0.0-rc.2",
108 | "typescript": "^5.1.5"
109 | },
110 | "dependencies": {
111 | "bcryptjs": "^2.4.3",
112 | "busboy": "^1.6.0",
113 | "class-transformer": "^0.5.1",
114 | "class-validator": "^0.14.1",
115 | "cli-color": "^2.0.3",
116 | "concat-stream": "^2.0.0",
117 | "cookie-parser": "^1.4.6",
118 | "cors": "^2.8.5",
119 | "dotenv": "^16.3.1",
120 | "express": "^4.18.2",
121 | "express-graphql": "^0.12.0",
122 | "express-handlebars": "^7.1.3",
123 | "helmet": "^7.1.0",
124 | "http-status": "^1.7.3",
125 | "joi": "^17.9.2",
126 | "js-yaml": "^4.1.0",
127 | "jsonwebtoken": "^9.0.1",
128 | "mime-types": "^2.1.35",
129 | "mongoose": "^8.0.1",
130 | "morgan": "^1.10.0",
131 | "reflect-metadata": "^0.2.1",
132 | "sequelize": "^6.33.0",
133 | "socket.io": "^4.7.5",
134 | "typedi": "^0.10.0",
135 | "winston": "^3.9.0",
136 | "xss": "^1.0.14",
137 | "xtend": "^4.0.2"
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/packages/uploader/file_uploader.ts:
--------------------------------------------------------------------------------
1 | import concat_stream from 'concat-stream';
2 | import { randomBytes } from 'crypto';
3 | import { createWriteStream, mkdirSync, unlink } from 'fs';
4 | import { tmpdir } from 'os';
5 | import { join } from 'path';
6 | import {
7 | DiskStorageOptions,
8 | FileInfo,
9 | Storage,
10 | UploadConfig,
11 | UploadOptionAndConfig,
12 | } from '../../common/types/dolph_uploader.type';
13 | import { DRequest } from '../../common';
14 | import { makeMiddleware } from './make_middleware';
15 |
16 | /**
17 | * @version 2.0
18 | *
19 | * @author Utee
20 | */
21 | class DiskStorage implements Storage {
22 | private getFilename: NonNullable;
23 | private getDestination: NonNullable;
24 |
25 | constructor(options: DiskStorageOptions = {}) {
26 | if (options.destination && typeof options.destination !== 'string') {
27 | throw new Error('Destination must be a string path');
28 | }
29 |
30 | this.getFilename = options.filename || defaultGetFilename;
31 |
32 | if (typeof options.destination === 'string') {
33 | createDirectory(options.destination);
34 | this.getDestination = (_req, _file, cb) => cb(null, options.destination as string);
35 | } else {
36 | this.getDestination = options.destination || defaultGetDestination;
37 | }
38 | }
39 |
40 | handleFile(req: DRequest, file: FileInfo, callback: (error: Error | null, info?: Partial) => void): void {
41 | //@ts-expect-error
42 | this.getDestination(req, file, (err, destination) => {
43 | if (err) return callback(err);
44 |
45 | this.getFilename(req, file, (err, filename) => {
46 | if (err) return callback(err);
47 |
48 | const finalPath = join(destination, filename);
49 | const outStream = createWriteStream(finalPath);
50 |
51 | file.stream!.pipe(outStream);
52 | outStream.on('error', callback);
53 | outStream.on('finish', () => {
54 | callback(null, {
55 | destination,
56 | filename,
57 | path: finalPath,
58 | size: outStream.bytesWritten,
59 | });
60 | });
61 | });
62 | });
63 | }
64 |
65 | removeFile(req: DRequest, file: FileInfo, callback: (error: Error | null) => void): void {
66 | const path = file.path;
67 | delete file.destination;
68 | delete file.filename;
69 | delete file.path;
70 | unlink(path!, callback);
71 | }
72 | }
73 |
74 | // MemoryStorage Implementation
75 | class MemoryStorage implements Storage {
76 | handleFile(req: DRequest, file: FileInfo, callback: (error: Error | null, info?: Partial) => void): void {
77 | file.stream!.pipe(
78 | concat_stream({ encoding: 'buffer' }, (data: Buffer) => {
79 | callback(null, {
80 | buffer: data,
81 | size: data.length,
82 | });
83 | }),
84 | );
85 | }
86 |
87 | removeFile(req: DRequest, file: FileInfo, callback: (error: Error | null) => void): void {
88 | delete file.buffer;
89 | callback(null);
90 | }
91 | }
92 |
93 | // Helper functions
94 | function defaultGetFilename(
95 | _req: DRequest,
96 | _file: FileInfo,
97 | callback: (error: Error | null, filename: string) => void,
98 | ): void {
99 | randomBytes(16, (err, raw) => {
100 | callback(err, err ? '' : raw.toString('hex'));
101 | });
102 | }
103 |
104 | function defaultGetDestination(
105 | _req: DRequest,
106 | _file: FileInfo,
107 | callback: (error: Error | null, destination: string) => void,
108 | ): void {
109 | callback(null, tmpdir());
110 | }
111 |
112 | function createDirectory(destination: string): void {
113 | try {
114 | mkdirSync(destination, { recursive: true });
115 | } catch (error: any) {
116 | if (error.code !== 'EEXIST') {
117 | throw error;
118 | }
119 | }
120 | }
121 |
122 | // Middleware creator function
123 | function createUploadMiddleware(options: Partial = {}) {
124 | if (!options.fieldname) throw new Error('Provide the `fieldname` option');
125 |
126 | if (options.type == 'single') {
127 | return makeMiddleware(
128 | () =>
129 | ({
130 | ...options,
131 | fileStrategy: 'VALUE',
132 | fields: [{ name: options.fieldname, maxCount: 1 }],
133 | }) as UploadConfig,
134 | );
135 | } else if (options.type == 'array') {
136 | if (!options.maxCount) throw new Error('Provide the `maxCount` option');
137 | return makeMiddleware(
138 | () =>
139 | ({
140 | ...options,
141 | fileStrategy: 'ARRAY',
142 | fields: [{ name: options.fieldname, maxCount: options.maxCount }],
143 | }) as UploadConfig,
144 | );
145 | } else if (options.type == 'fields') {
146 | if (!options.fields) throw new Error('Provide the `field` option');
147 | return makeMiddleware(
148 | () =>
149 | ({
150 | ...options,
151 | fileStrategy: 'OBJECT',
152 | fields: options.fields,
153 | }) as UploadConfig,
154 | );
155 | } else {
156 | throw new Error('Provide a valid and supported type');
157 | }
158 | }
159 |
160 | export const fileUploader = createUploadMiddleware;
161 | export const diskStorage = (options: DiskStorageOptions) => new DiskStorage(options);
162 | export const memoryStorage = () => new MemoryStorage();
163 |
--------------------------------------------------------------------------------
/classes/jwt_auth_classes.class.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AuthorizationFunction,
3 | DNextFunc,
4 | DRequest,
5 | DResponse,
6 | ErrorException,
7 | HttpStatus,
8 | IPayload,
9 | TryCatchAsyncFn,
10 | } from '../common';
11 | import { verifyJWTwithHMAC, verifyJWTwithRSA } from '../utilities';
12 | import { config } from 'dotenv';
13 | config();
14 |
15 | const authHeaderName: Array = ['x-auth-token', 'authorization'];
16 |
17 | /**
18 | * Takes a `tokenSecret` parameter
19 | *
20 | * The Verify method is used to verify JWT token/ private key
21 | */
22 | // Authorization - used when it's just secret used
23 | // x-auth-token - used when it's private and public keys being used
24 | class JwtBasicAuth {
25 | tokenSecret: string;
26 | constructor(tokenSecret: string) {
27 | this.tokenSecret = tokenSecret;
28 | }
29 |
30 | Verify = TryCatchAsyncFn(async (req: DRequest, res: DResponse, next: DNextFunc) => {
31 | let authToken: string | string[];
32 | let authHeader: string;
33 | authHeaderName.forEach((headerName) => {
34 | if (req.headers[headerName]) {
35 | authToken = req.headers[headerName];
36 | authHeader = headerName;
37 | }
38 | });
39 | if (authToken === '' || !authToken?.length) {
40 | return next(new ErrorException('provide a valid token header', HttpStatus.UNAUTHORIZED));
41 | }
42 | let payload: IPayload;
43 | if (authHeader === 'Authorization') {
44 | //@ts-expect-error
45 | payload = verifyJWTwithHMAC({ token: authToken, secret: this.tokenSecret });
46 | } else if (authHeader === 'x-auth-token') {
47 | //@ts-expect-error
48 | payload = verifyJWTwithRSA({ pathToPublicKey: this.tokenSecret, token: authToken });
49 | }
50 |
51 | return payload;
52 | });
53 | }
54 |
55 | /**
56 | *
57 | * class-method decorator used for authorization based on the dolphjs default JWT authentication and authorization design
58 | *
59 | * @version 1.0.4
60 | */
61 | const JWTAuthVerifyDec = (tokenSecret: string) => {
62 | return (_target: any, _propertyKey: string, descriptor?: TypedPropertyDescriptor) => {
63 | const originalMethod = descriptor.value;
64 |
65 | // convert to normal func
66 | descriptor.value = (req: DRequest, res: DResponse, next: DNextFunc) => {
67 | try {
68 | const context = this;
69 | let authToken: string | string[];
70 | let authHeader: string;
71 |
72 | if (req.headers[authHeaderName[0]]) {
73 | authToken = req.headers[authHeaderName[0]];
74 | authHeader = authHeaderName[0];
75 | } else if (req.headers[authHeaderName[1]]) {
76 | authToken = req.headers[authHeaderName[1]];
77 | authHeader = authHeaderName[1];
78 | }
79 |
80 | if (authToken === '' || !authToken?.length)
81 | return next(new ErrorException('provide a valid token header', HttpStatus.UNAUTHORIZED));
82 |
83 | let payload: IPayload;
84 | if (authHeader === authHeaderName[1]) {
85 | //@ts-expect-error
86 | payload = verifyJWTwithHMAC({ token: authToken, secret: tokenSecret });
87 | } else if (authHeader === authHeaderName[0]) {
88 | //@ts-expect-error
89 | payload = verifyJWTwithRSA({ pathToPublicKey: tokenSecret, token: authToken });
90 | }
91 | req.payload = payload;
92 |
93 | return originalMethod.apply(context, [req, res, next]);
94 | } catch (e) {
95 | throw e;
96 | }
97 | };
98 |
99 | // return descriptor;
100 | };
101 | };
102 |
103 | const JWTAuthorizeDec = (tokenSecret: string, authorize?: AuthorizationFunction) => {
104 | return (_target: any, _propertyKey: string, descriptor?: TypedPropertyDescriptor) => {
105 | const originalMethod = descriptor.value;
106 |
107 | descriptor.value = async (req: DRequest, res: DResponse, next: DNextFunc) => {
108 | try {
109 | const context = this;
110 | let authToken: string | string[];
111 | let authHeader: string;
112 |
113 | if (req.headers[authHeaderName[0]]) {
114 | authToken = req.headers[authHeaderName[0]];
115 | authHeader = authHeaderName[0];
116 | } else if (req.headers[authHeaderName[1]]) {
117 | authToken = req.headers[authHeaderName[1]];
118 | authHeader = authHeaderName[1];
119 | }
120 |
121 | if (authToken === '' || !authToken?.length)
122 | return next(new ErrorException('provide a valid token header', HttpStatus.UNAUTHORIZED));
123 |
124 | let payload: IPayload;
125 | if (authHeader === authHeaderName[1]) {
126 | //@ts-expect-error
127 | payload = verifyJWTwithHMAC({ token: authToken, secret: tokenSecret });
128 | } else if (authHeader === authHeaderName[0]) {
129 | //@ts-expect-error
130 | payload = verifyJWTwithRSA({ pathToPublicKey: tokenSecret, token: authToken });
131 | }
132 | req.payload = payload;
133 |
134 | // Check if authorization function is provided
135 | if (authorize) {
136 | const isAuthorized = await authorize(payload);
137 | if (!isAuthorized) {
138 | return next(new ErrorException('Access denied', HttpStatus.FORBIDDEN));
139 | }
140 | }
141 |
142 | return originalMethod.apply(context, [req, res, next]);
143 | } catch (e) {
144 | throw e;
145 | }
146 | };
147 | };
148 | };
149 |
150 | export { JwtBasicAuth, JWTAuthVerifyDec, JWTAuthorizeDec };
151 |
--------------------------------------------------------------------------------
/tests/uploader/fileuploader.test.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { diskStorage, fileUploader } from '../../packages';
3 | import { DRequest, DResponse } from '../../common';
4 | import { Readable } from 'stream';
5 |
6 | describe('File Uploader Test', () => {
7 | const mockNext = jest.fn();
8 | let upload: ReturnType;
9 |
10 | beforeEach(() => {
11 | upload = fileUploader({
12 | storage: diskStorage({ destination: join(__dirname, 'uploads') }),
13 | type: 'single', // Add this to match current fileUploader implementation
14 | fieldname: 'avatar', // Add a default fieldname
15 | });
16 | mockNext.mockClear();
17 | });
18 |
19 | test('single file upload should work correctly', (done) => {
20 | const middleware = upload;
21 |
22 | const mockFileStream = new Readable();
23 | mockFileStream.push('test file content');
24 | mockFileStream.push(null);
25 |
26 | const mockRequest = {
27 | headers: {
28 | 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
29 | },
30 | on: jest.fn((event, handler) => {
31 | if (event === 'file') {
32 | handler('avatar', {
33 | stream: mockFileStream,
34 | filename: 'test.txt',
35 | encoding: '7bit',
36 | mimetype: 'text/plain',
37 | });
38 | }
39 | if (event === 'finish') {
40 | handler();
41 | }
42 | return mockRequest;
43 | }),
44 | pipe: jest.fn(),
45 | } as unknown as DRequest;
46 |
47 | const mockResponse = {} as DResponse;
48 |
49 | middleware(mockRequest, mockResponse, (error) => {
50 | try {
51 | expect(error).toBeUndefined();
52 | expect(mockRequest.file).toBeDefined();
53 | done();
54 | } catch (err) {
55 | done(err);
56 | }
57 | });
58 |
59 | mockRequest.on('finish', () => {});
60 | }, 15000);
61 |
62 | test('array upload should handle multiple files', (done) => {
63 | const upload = fileUploader({
64 | type: 'array',
65 | fieldname: 'photos',
66 | maxCount: 3,
67 | storage: diskStorage({ destination: join(__dirname, 'uploads') }),
68 | });
69 |
70 | const middleware = upload;
71 |
72 | const mockFileStream = new Readable();
73 | mockFileStream.push('test file content');
74 | mockFileStream.push(null);
75 |
76 | const mockRequest = {
77 | headers: {
78 | 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
79 | },
80 | on: jest.fn((event, handler) => {
81 | if (event === 'file') {
82 | // Simulate multiple files
83 | handler('photos', {
84 | stream: mockFileStream,
85 | filename: 'test1.txt',
86 | encoding: '7bit',
87 | mimetype: 'text/plain',
88 | });
89 | handler('photos', {
90 | stream: mockFileStream,
91 | filename: 'test2.txt',
92 | encoding: '7bit',
93 | mimetype: 'text/plain',
94 | });
95 | }
96 | if (event === 'finish') {
97 | handler();
98 | }
99 | return mockRequest;
100 | }),
101 | pipe: jest.fn(),
102 | } as unknown as DRequest;
103 |
104 | const mockResponse = {} as DResponse;
105 |
106 | middleware(mockRequest, mockResponse, (error) => {
107 | try {
108 | expect(error).toBeUndefined();
109 | expect(Array.isArray(mockRequest.files)).toBeTruthy();
110 | done();
111 | } catch (err) {
112 | done(err);
113 | }
114 | });
115 |
116 | mockRequest.on('finish', () => {});
117 | }, 15000);
118 |
119 | test('should handle non-multipart requests', (done) => {
120 | const middleware = upload;
121 |
122 | const mockRequest = {
123 | headers: {
124 | 'content-type': 'application/json',
125 | },
126 | } as unknown as DRequest;
127 |
128 | const mockResponse = {} as DResponse;
129 |
130 | middleware(mockRequest, mockResponse, (error) => {
131 | expect(error).toBeUndefined();
132 | expect(mockRequest.file).toBeUndefined();
133 | done();
134 | });
135 | });
136 |
137 | test('fields upload should handle multiple fields', (done) => {
138 | const upload = fileUploader({
139 | type: 'fields',
140 | fields: [
141 | { name: 'avatar', maxCount: 1 },
142 | { name: 'gallery', maxCount: 3 },
143 | ],
144 | fieldname: 'upload',
145 | storage: diskStorage({ destination: join(__dirname, 'uploads') }),
146 | });
147 |
148 | const middleware = upload;
149 |
150 | const mockFileStream = new Readable();
151 | mockFileStream.push('test file content');
152 | mockFileStream.push(null);
153 |
154 | const mockRequest = {
155 | headers: {
156 | 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
157 | },
158 | on: jest.fn((event, handler) => {
159 | if (event === 'file') {
160 | // Simulate files for different fields
161 | handler('avatar', {
162 | stream: mockFileStream,
163 | filename: 'avatar.jpg',
164 | encoding: '7bit',
165 | mimetype: 'image/jpeg',
166 | });
167 | handler('gallery', {
168 | stream: mockFileStream,
169 | filename: 'gallery1.jpg',
170 | encoding: '7bit',
171 | mimetype: 'image/jpeg',
172 | });
173 | }
174 | if (event === 'finish') {
175 | handler();
176 | }
177 | return mockRequest;
178 | }),
179 | pipe: jest.fn(),
180 | } as unknown as DRequest;
181 |
182 | const mockResponse = {} as DResponse;
183 |
184 | middleware(mockRequest, mockResponse, (error) => {
185 | try {
186 | expect(error).toBeUndefined();
187 | expect(mockRequest.files).toBeDefined();
188 | done();
189 | } catch (err) {
190 | done(err);
191 | }
192 | });
193 |
194 | mockRequest.on('finish', () => {});
195 | }, 1500);
196 | });
197 |
--------------------------------------------------------------------------------
/packages/uploader/make_middleware.ts:
--------------------------------------------------------------------------------
1 | import { IncomingMessage } from 'http';
2 | import BBusboy, { Busboy } from 'busboy';
3 | import { FileInfo, UploadConfig } from '../../common/types/dolph_uploader.type';
4 | import { DRequest, DResponse } from '../../common';
5 | import { FileAppender } from './file_appender';
6 | import { Counter } from './counter';
7 | import { DolphFIleUploaderError } from './errors/error_messages';
8 | import { removeUploadedFiles } from './remove_uploaded_files';
9 |
10 | export function isMultipart(req: IncomingMessage): boolean {
11 | const contentType = req.headers['content-type'] || '';
12 | return contentType.startsWith('multipart/');
13 | }
14 |
15 | function onRequestFinished(req: IncomingMessage, callback: () => void): void {
16 | req.on('end', callback);
17 | req.on('close', callback);
18 | }
19 |
20 | function appendField(target: Record, key: string, value: any): void {
21 | if (Object.prototype.hasOwnProperty.call(target, key)) {
22 | if (!Array.isArray(target[key])) {
23 | target[key] = [target[key]];
24 | }
25 | target[key].push(value);
26 | } else {
27 | target[key] = value;
28 | }
29 | }
30 |
31 | function drainStream(stream: NodeJS.ReadableStream): void {
32 | stream.on('readable', stream.read.bind(stream));
33 | }
34 |
35 | export function makeMiddleware(getOptions: () => UploadConfig & { fields?: Array<{ name: string; maxCount?: number }> }) {
36 | return function uploadMiddleware(req: DRequest, res: DResponse, next: (error?: any) => void): void {
37 | if (!isMultipart(req)) {
38 | return next();
39 | }
40 |
41 | let options: ReturnType;
42 |
43 | try {
44 | options = getOptions();
45 | } catch (error) {
46 | return next(error);
47 | }
48 |
49 | req.body = Object.create(null);
50 |
51 | let busboy: Busboy;
52 | try {
53 | busboy = BBusboy({
54 | headers: req.headers,
55 | limits: options.limits,
56 | preservePath: options.preservePaths,
57 | });
58 | } catch (error) {
59 | return next(error);
60 | }
61 |
62 | const appender = new FileAppender(options.fileStrategy, req);
63 | const pendingWrites = new Counter();
64 | const uploadedFiles: FileInfo[] = [];
65 |
66 | let isDone = false;
67 | let readFinished = false;
68 | let errorOccurred = false;
69 |
70 | let uploadTimeOut: NodeJS.Timeout;
71 |
72 | function done(err?: any): void {
73 | if (isDone) return;
74 | isDone = true;
75 |
76 | // clear all pending timeout
77 | if (uploadTimeOut) clearTimeout(uploadTimeOut);
78 |
79 | req.unpipe(busboy);
80 | drainStream(req);
81 | busboy.removeAllListeners();
82 |
83 | // onRequestFinished(req, () => next(err));
84 |
85 | // Force resolution with a setImmediate to ensure all callbacks are processed
86 | setImmediate(() => {
87 | next(err);
88 | });
89 | }
90 |
91 | /**
92 | * Todo: allow developer to set the modify the timeout from outside.
93 | */
94 | uploadTimeOut = setTimeout(() => {
95 | console.error('Upload middleware timed out');
96 | done(new Error('Upload process timed out'));
97 | }, 3000);
98 |
99 | function indicateDone(): void {
100 | if (readFinished && pendingWrites.isZero() && !errorOccurred) {
101 | done();
102 | }
103 | }
104 |
105 | function abortWithError(uploaderError: Error): void {
106 | if (errorOccurred) return;
107 | errorOccurred = true;
108 |
109 | pendingWrites.onceZero(() => {
110 | removeUploadedFiles(
111 | uploadedFiles,
112 | (file: FileInfo, cb: (error: Error | null) => void) => options.storage.removeFile(req, file, cb),
113 | (err: Error | null, storageErrors: Error[]) => {
114 | if (err) return done(err);
115 | (uploaderError as any).storageErrors = storageErrors;
116 | done(uploaderError);
117 | },
118 | );
119 | });
120 | }
121 |
122 | function abortWithCode(code: string, field?: string): void {
123 | abortWithError(new DolphFIleUploaderError(code, field));
124 | }
125 |
126 | busboy.on('field', (fieldname: string, value: any, fieldNameTruncated: boolean, valueTruncated: boolean) => {
127 | if (!fieldname) return abortWithCode('MISSING_FIELD_NAME');
128 | if (fieldNameTruncated) return abortWithCode('LIMIT_FIELD_KEY');
129 | if (valueTruncated) return abortWithCode('LIMIT_FIELD_VALUE', fieldname);
130 |
131 | if (options.limits?.fieldNameSize && fieldname.length > options.limits.fieldNameSize) {
132 | return abortWithCode('LIMIT_FIELD_KEY');
133 | }
134 |
135 | appendField(req.body!, fieldname, value);
136 | });
137 |
138 | busboy.on(
139 | 'file',
140 | (fieldname: string, fileStream: NodeJS.ReadableStream, filename: any, encoding: string, mimetype: string) => {
141 | if (!fieldname) return fileStream.resume();
142 |
143 | if (options.limits?.fieldNameSize && fieldname.length > options.limits.fieldNameSize) {
144 | return abortWithCode('LIMIT_FIELD_KEY');
145 | }
146 |
147 | const file: FileInfo = {
148 | fieldname,
149 | originalname: filename.filename || filename,
150 | encoding: filename.encoding || encoding || '7bit',
151 | mimetype: filename.mimeType || mimetype || 'application/octet-stream',
152 | };
153 |
154 | const placeholder = appender.insertPlaceholder(file);
155 |
156 | // if (!options.fileFilter) {
157 | // // options.fileFilter = (req, file) => (err: Error | null, includeFile: boolean) => {
158 | // // if (err || !includeFile) {
159 | // // appender.removePlaceholder(placeholder);
160 | // // return fileStream.resume();
161 | // // }
162 |
163 | // // let aborting = false;
164 | // // pendingWrites.increment();
165 |
166 | // // Object.defineProperty(file, 'stream', {
167 | // // configurable: true,
168 | // // enumerable: false,
169 | // // value: fileStream,
170 | // // });
171 |
172 | // // fileStream.on('error', (err: Error) => {
173 | // // pendingWrites.decrement();
174 | // // abortWithError(err);
175 | // // });
176 |
177 | // // fileStream.on('limit', () => {
178 | // // aborting = true;
179 | // // abortWithCode('LIMIT_FILE_SIZE', fieldname);
180 | // // });
181 |
182 | // // options.storage.handleFile(req, file, (err: Error | null, info?: Partial) => {
183 | // // if (aborting) {
184 | // // appender.removePlaceholder(placeholder);
185 | // // uploadedFiles.push({ ...file, ...info });
186 | // // return pendingWrites.decrement();
187 | // // }
188 |
189 | // // if (err) {
190 | // // appender.removePlaceholder(placeholder);
191 | // // pendingWrites.decrement();
192 | // // return abortWithError(err);
193 | // // }
194 |
195 | // // const fileInfo = { ...file, ...info };
196 | // // appender.replacePlaceholder(placeholder, fileInfo);
197 | // // uploadedFiles.push(fileInfo);
198 | // // pendingWrites.decrement();
199 | // // indicateDone();
200 | // // });
201 | // // };
202 | // options.fileFilter = (_req, file, callback) => {
203 | // callback(null, true);
204 | // };
205 | // } else {
206 | // options.fileFilter(req, file, (err: Error | null, includeFile: boolean) => {
207 | // if (err || !includeFile) {
208 | // appender.removePlaceholder(placeholder);
209 | // return fileStream.resume();
210 | // }
211 |
212 | // let aborting = false;
213 | // pendingWrites.increment();
214 |
215 | // Object.defineProperty(file, 'stream', {
216 | // configurable: true,
217 | // enumerable: false,
218 | // value: fileStream,
219 | // });
220 |
221 | // fileStream.on('error', (err: Error) => {
222 | // pendingWrites.decrement();
223 | // abortWithError(err);
224 | // });
225 |
226 | // fileStream.on('limit', () => {
227 | // aborting = true;
228 | // abortWithCode('LIMIT_FILE_SIZE', fieldname);
229 | // });
230 |
231 | // options.storage.handleFile(req, file, (err: Error | null, info?: Partial) => {
232 | // if (aborting) {
233 | // appender.removePlaceholder(placeholder);
234 | // uploadedFiles.push({ ...file, ...info });
235 | // return pendingWrites.decrement();
236 | // }
237 |
238 | // if (err) {
239 | // appender.removePlaceholder(placeholder);
240 | // pendingWrites.decrement();
241 | // return abortWithError(err);
242 | // }
243 |
244 | // const fileInfo = { ...file, ...info };
245 | // appender.replacePlaceholder(placeholder, fileInfo);
246 | // uploadedFiles.push(fileInfo);
247 | // pendingWrites.decrement();
248 | // indicateDone();
249 | // });
250 | // });
251 | // }
252 |
253 | options.fileFilter(req, file, (err: Error | null, includeFile: boolean) => {
254 | if (err || !includeFile) {
255 | appender.removePlaceholder(placeholder);
256 | return fileStream.resume();
257 | }
258 |
259 | let aborting = false;
260 | pendingWrites.increment();
261 |
262 | Object.defineProperty(file, 'stream', {
263 | configurable: true,
264 | enumerable: false,
265 | value: fileStream,
266 | });
267 |
268 | fileStream.on('error', (streamErr: Error) => {
269 | pendingWrites.decrement();
270 | abortWithError(streamErr);
271 | });
272 |
273 | fileStream.on('limit', () => {
274 | aborting = true;
275 | abortWithCode('LIMIT_FILE_SIZE', fieldname);
276 | });
277 |
278 | options.storage.handleFile(req, file, (storageErr: Error | null, info?: Partial) => {
279 | if (aborting) {
280 | appender.removePlaceholder(placeholder);
281 | uploadedFiles.push({ ...file, ...info });
282 | return pendingWrites.decrement();
283 | }
284 |
285 | if (storageErr) {
286 | appender.removePlaceholder(placeholder);
287 | pendingWrites.decrement();
288 | return abortWithError(storageErr);
289 | }
290 |
291 | const fileInfo = { ...file, ...info };
292 | appender.replacePlaceholder(placeholder, fileInfo);
293 | uploadedFiles.push(fileInfo);
294 | pendingWrites.decrement();
295 | indicateDone();
296 | });
297 | });
298 | },
299 | );
300 |
301 | busboy.on('error', (err: Error) => abortWithError(err));
302 | busboy.on('partsLimit', () => abortWithCode('LIMIT_PART_COUNT'));
303 | busboy.on('filesLimit', () => abortWithCode('LIMIT_FILE_COUNT'));
304 | busboy.on('fieldsLimit', () => abortWithCode('LIMIT_FIELD_COUNT'));
305 | busboy.on('finish', () => {
306 | readFinished = true;
307 | // this ensures that indicateDone is called in next tick
308 | setImmediate(indicateDone);
309 | // indicateDone();
310 | });
311 |
312 | req.pipe(busboy);
313 | };
314 | }
315 |
--------------------------------------------------------------------------------