diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b47e3a2a..8bc4a7ed 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 188b54c0..2866f790 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- **Is your feature request related to a problem? Please describe.** A clear and diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af013e6d..468c8d6f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,8 @@ name: Publish on: + push: + tags: + - "*" workflow_dispatch: jobs: @@ -19,4 +22,4 @@ jobs: deno-version: canary - name: Publish package - run: deno publish --allow-slow-types + run: deno publish \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 32159860..a2dd896b 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - version: ["v1.x", canary] + version: ['v1.x', canary] steps: - name: Setup repo uses: actions/checkout@v3 diff --git a/README.md b/README.md index a996f09e..e00a8bb2 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ [![CI](https://github.com/savory/Danet/actions/workflows/run-tests.yml/badge.svg)](https://github.com/savory/Danet/actions/workflows/run-tests.yml) [![codecov](https://codecov.io/gh/Savory/Danet/branch/main/graph/badge.svg?token=R6WXVC669Z)](https://codecov.io/gh/Savory/Danet) ![Made for Deno](https://img.shields.io/badge/made%20for-Deno-6B82F6?style=flat-square) +[![JSR](https://jsr.io/badges/@danet/core)](https://jsr.io/@danet/core) + +## Warning + +From version 2.4.0, Danet is only available via [JSR](https://jsr.io/) at +[@danet/core](https://jsr.io/@danet/core). It's a step closer to runtime +agnosticism. ## Description @@ -44,7 +51,14 @@ We always welcome contributors, feel free to submit a new feature or report a bug on our [Github Repository](https://github.com/Savory/Danet) and [join our discord](https://discord.gg/Q7ZHuDPgjA) -## How to start +## Installation + +```sh +deno install --global -A -n danet jsr:@danet/cli +danet new +``` + +## Documentation [Use our CLI](https://github.com/Savory/Danet-CLI) diff --git a/deno.json b/deno.json index 9a085d3d..29900d73 100644 --- a/deno.json +++ b/deno.json @@ -1,14 +1,31 @@ { "name": "@danet/core", - "version": "2.3.0", - "exports": "./mod.ts", + "version": "2.5.1", + "exports": { + ".": "./mod.ts", + "./metadata": "./src/metadata/mod.ts", + "./validation": "./validation.ts", + "./hook": "./src/hook/mod.ts", + "./logger": "./src/logger.ts" + }, "lint": { - "include": ["src/"], - "exclude": ["node_modules/", "./**/*.test.ts"], + "include": [ + "src/" + ], + "exclude": [ + "node_modules/", + "./**/*.test.ts" + ], "rules": { - "tags": ["recommended"], - "include": ["ban-untagged-todo"], - "exclude": ["no-unused-vars"] + "tags": [ + "recommended" + ], + "include": [ + "ban-untagged-todo" + ], + "exclude": [ + "no-unused-vars" + ] } }, "fmt": { @@ -16,9 +33,11 @@ "singleQuote": true, "useTabs": true }, - "files": { - "exclude": ["./node_modules/", "./coverage/", "./doc/"] - } + "exclude": [ + "./node_modules/", + "./coverage/", + "./doc/" + ] }, "compilerOptions": { "emitDecoratorMetadata": true, @@ -29,5 +48,17 @@ "tasks": { "test": "NO_LOG=true COOKIE_SECRET_KEY=mysecretkey deno test -A --unstable-kv --unstable-cron --coverage=coverage spec/**/*.test.ts", "start:example": "deno run --allow-net --allow-env --watch example/run.ts" + }, + "imports": { + "@std/testing": "jsr:@std/testing@0.223.0", + "validatte": "jsr:@danet/validatte@0.7.4", + "@std/path": "jsr:@std/path@0.223.0", + "@std/fmt": "jsr:@std/fmt@0.223.0", + "deno_reflect": "jsr:@dx/reflect@0.2.14", + "@hono": "jsr:@hono/hono@4.6.3", + "@danet/handlebars": "jsr:@danet/handlebars@0.0.1" + }, + "publish": { + "exclude": ["./coverage/", "./spec", ".github", ".vscode", "./example"] } } diff --git a/spec/queue.test.ts b/spec/queue.test.ts index 360a74f8..25d31ff0 100644 --- a/spec/queue.test.ts +++ b/spec/queue.test.ts @@ -7,7 +7,12 @@ import { Module, OnQueueMessage, } from '../mod.ts'; -import { assertEquals, assertSpyCall, spy } from '../src/deps_test.ts'; +import { + assertEquals, + assertSpyCall, + assertSpyCalls, + spy, +} from '../src/deps_test.ts'; const sleep = (msec: number) => new Promise((resolve) => setTimeout(resolve, msec)); @@ -64,7 +69,7 @@ Deno.test('Queue Module', async (t) => { assertEquals(await res.text(), 'OK'); await sleep(500); - assertEquals(callback.calls.length, 1); + assertSpyCalls(callback, 1); assertSpyCall(callback, 0, { args: [payload], }); diff --git a/spec/validate-body-decorator.test.ts b/spec/validate-body-decorator.test.ts index 7fc7f575..5d2c74f8 100644 --- a/spec/validate-body-decorator.test.ts +++ b/spec/validate-body-decorator.test.ts @@ -1,7 +1,4 @@ -import { - assertEquals, - assertExists, -} from 'https://deno.land/std@0.224.0/testing/asserts.ts'; +import { assertEquals, assertExists } from '@std/testing/asserts'; import { DanetApplication } from '../src/mod.ts'; import { Module } from '../src/module/mod.ts'; import { Body, Controller, Get, Post } from '../src/router/controller/mod.ts'; diff --git a/src/app.ts b/src/app.ts index 244b06fe..fd070bb3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,3 +1,45 @@ +/** + * @module + * DanetApplication class where everything begins. + * @example + * ```typescript + * import { + * Controller, + * DanetApplication, + * Get, + * Module, + * Query, + * } from '../src/mod.ts'; + * + * @Controller('') + * class FirstController { + * constructor() { + * } + * + * @Get('hello-world/:name') + * getHelloWorld( + * @Param('name') name: string, + * ) { + * return `Hello World ${name}`; + * } + * } + * + * @Module({ + * controllers: [FirstController] + * }) + * class FirstModule {} + * + * const app = new DanetApplication(); + * await app.init(FirstModule); + * + * let port = Number(Deno.env.get('PORT')); + * if (isNaN(port)) { + * port = 3000; + * } + * app.listen(port); + * ``` + */ + import { Application, MiddlewareHandler } from './deps.ts'; import { FilterExecutor } from './exception/filter/executor.ts'; import { GuardExecutor } from './guard/executor.ts'; @@ -28,6 +70,11 @@ type CORSOptions = { exposeHeaders?: string[]; }; +/** + * DanetApplication is the main application class for initializing and managing the lifecycle of the application. + * It provides methods for bootstrapping modules, registering controllers, and configuring middleware. + * It also provides methods for starting and stopping the application. + */ export class DanetApplication { private app: Application = new Application({ strict: false }); private internalHttpServer?: Deno.HttpServer; @@ -36,14 +83,14 @@ export class DanetApplication { private renderer = new HandlebarRenderer(); private guardExecutor = new GuardExecutor(this.injector); private filterExecutor = new FilterExecutor(this.injector); - public httpRouter = new DanetHTTPRouter( + public httpRouter: DanetHTTPRouter = new DanetHTTPRouter( this.injector, this.guardExecutor, this.filterExecutor, this.renderer, this.app, ); - public websocketRouter = new WebSocketRouter( + public websocketRouter: WebSocketRouter = new WebSocketRouter( this.injector, this.guardExecutor, this.filterExecutor, @@ -53,10 +100,33 @@ export class DanetApplication { private logger: Logger = new Logger('DanetApplication'); public entryModule!: ModuleConstructor; + /** + * Retrieves an instance of the specified type from the injector. + * + * @template T - The type of the instance to retrieve. + * @param {Constructor | string} Type - The constructor of the type or a string identifier. + * @returns {T} - The instance of the specified type. + */ get(Type: Constructor | string): T { return this.injector.get(Type); } + /** + * Bootstraps the application by initializing the provided module and its dependencies. + * + * @param NormalOrDynamicModule - The module to bootstrap, which can be either a normal constructor or a dynamic module. + * + * This method performs the following steps: + * 1. Determines if the provided module is a normal constructor or a dynamic module. + * 2. Initializes the module and retrieves its metadata (controllers, imports, injectables). + * 3. Recursively bootstraps all imported modules. + * 4. Bootstraps the module using the injector. + * 5. Registers controllers with either the HTTP router or WebSocket router based on their metadata. + * + * @template Constructor - A class constructor type. + * @template DynamicModule - A type representing a dynamic module with metadata. + * @template ModuleMetadata - A type representing the metadata of a module. + */ async bootstrap(NormalOrDynamicModule: Constructor | DynamicModule) { // deno-lint-ignore no-explicit-any const possibleModuleInstance = NormalOrDynamicModule as any; @@ -114,6 +184,12 @@ export class DanetApplication { } } + /** + * Initializes the application with the provided module. + * + * @param Module - The constructor of the module to initialize. + * @returns A promise that resolves when the initialization process is complete. + */ async init(Module: Constructor) { this.entryModule = Module; await this.bootstrap(Module); @@ -122,12 +198,35 @@ export class DanetApplication { ); } + /** + * Closes the application by executing the necessary hooks and shutting down the internal HTTP server. + * + * @async + * @returns {Promise} A promise that resolves when the application has been closed. + */ async close() { await this.hookExecutor.executeHookForEveryInjectable(hookName.APP_CLOSE); await this.internalHttpServer?.shutdown(); this.logger.log('Shutting down'); } + /** + * Starts the HTTP server and begins listening on the specified port. + * + * @param {number} [port=3000] - The port number on which the server will listen. + * @returns {Promise<{ port: number }>} A promise that resolves with an object containing the port number. + * + * @remarks + * This method initializes an `AbortController` to manage the server's lifecycle and uses Deno's `serve` function to start the server. + * The server will log a message indicating the port it is listening on. + * + * @example + * ```typescript + * const app = new DanetApplication(); + * await app.init(FirstModule); + * const { port } = app.listen(3000); + * ``` + */ listen(port = 3000): Promise<{ port: number }> { this.controller = new AbortController(); const { signal } = this.controller; @@ -144,14 +243,29 @@ export class DanetApplication { return listen; } + /** + * Get hono application instance. + * + * @returns {Application} The hono instance. + */ get router(): Application { return this.app; } + /** + * Sets the directory for the view engine. + * + * @param path - The path to the directory to be set as the root for the view engine. + */ setViewEngineDir(path: string) { this.renderer.setRootDir(path); } + /** + * Configures the application to serve static assets from the specified path. + * + * @param path - The file system path from which to serve static assets. + */ useStaticAssets(path: string) { this.app.use('*', (context, next: () => Promise) => { const root = path; @@ -159,14 +273,29 @@ export class DanetApplication { }); } + /** + * Adds one or more global middlewares to the global middleware container. + * + * @param {...PossibleMiddlewareType[]} middlewares - The middlewares to be added to the global container. + */ addGlobalMiddlewares(...middlewares: PossibleMiddlewareType[]) { globalMiddlewareContainer.push(...middlewares); } + /** + * Enables Cross-Origin Resource Sharing (CORS) for the application. + * + * @param {CORSOptions} [options] - Optional configuration for CORS. + */ enableCors(options?: CORSOptions) { this.app.use('*', cors(options)); } + /** + * Registers a hono middleware handler to be used for all routes. + * + * @param middleware - The middleware handler to be used. + */ use(middleware: MiddlewareHandler) { this.app.use('*', middleware); } diff --git a/src/deps.ts b/src/deps.ts index d24f6dd3..4e79e0ad 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -1,24 +1,17 @@ -export { - green, - red, - white, - yellow, -} from 'https://deno.land/std@0.224.0/fmt/colors.ts'; -export { Reflect } from 'https://deno.land/x/deno_reflect@v0.2.1/mod.ts'; +export { green, red, white, yellow } from '@std/fmt/colors'; +export { Reflect } from 'deno_reflect'; export { validateObject } from '../validation.ts'; export { type Context, Hono as Application, type MiddlewareHandler, type Next, -} from 'https://deno.land/x/hono@v4.3.11/mod.ts'; -export { type HandlerInterface } from 'https://deno.land/x/hono@v4.3.11/types.ts'; -export { HonoRequest } from 'https://deno.land/x/hono@v4.3.11/request.ts'; -export { getPath } from 'https://deno.land/x/hono@v4.3.11/utils/url.ts'; -export { - RegExpRouter, - SmartRouter, - TrieRouter, -} from 'https://deno.land/x/hono@v4.3.11/mod.ts'; -export { SSEStreamingApi, streamSSE } from 'https://deno.land/x/hono@v4.3.11/helper.ts'; -export { cors } from 'https://deno.land/x/hono@v4.3.11/middleware.ts' \ No newline at end of file +} from '@hono'; +export { type HandlerInterface } from '@hono/types'; +export { HonoRequest } from '@hono/request'; +export { getPath } from '@hono/utils/url'; +export { RegExpRouter } from '@hono/router/reg-exp-router'; +export { SmartRouter } from '@hono/router/smart-router'; +export { TrieRouter } from '@hono/router/trie-router'; +export { SSEStreamingApi, streamSSE } from '@hono/streaming'; +export { cors } from '@hono/cors'; diff --git a/src/deps_test.ts b/src/deps_test.ts index 186fceb7..6ab92927 100644 --- a/src/deps_test.ts +++ b/src/deps_test.ts @@ -3,8 +3,8 @@ export { assertSpyCallArg, assertSpyCalls, spy, -} from 'https://deno.land/std@0.224.0/testing/mock.ts'; -export { FakeTime } from 'https://deno.land/std@0.224.0/testing/time.ts'; +} from '@std/testing/mock'; +export { FakeTime } from '@std/testing/time'; export { assertEquals, assertInstanceOf, @@ -12,9 +12,5 @@ export { assertObjectMatch, assertRejects, assertThrows, -} from 'https://deno.land/std@0.224.0/testing/asserts.ts'; -export * as path from 'https://deno.land/std@0.224.0/path/mod.ts'; -export { - CookieStore, - Session as OakSession, -} from 'https://deno.land/x/oak_sessions@v4.1.11/mod.ts'; +} from '@std/testing/asserts'; +export * as path from '@std/path'; diff --git a/src/events/decorator.ts b/src/events/decorator.ts index 3cd9f9ea..ab5fb587 100644 --- a/src/events/decorator.ts +++ b/src/events/decorator.ts @@ -1,6 +1,12 @@ import { MetadataHelper } from '../metadata/mod.ts'; import { eventListenerMetadataKey } from './constants.ts'; +/** + * A decorator that registers a method as an event listener for a specified channel. + * + * @param channel - The name of the event channel to listen to. + * @returns A method decorator that registers the method as an event listener. + */ export const OnEvent = (channel: string): MethodDecorator => { return (_target, _propertyKey, descriptor) => { MetadataHelper.setMetadata( diff --git a/src/events/events.ts b/src/events/events.ts index 5a5965ae..b848284f 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -3,6 +3,25 @@ import { Logger } from '../mod.ts'; // deno-lint-ignore no-explicit-any type Listener

= (payload: P) => void; +/** + * Provides event-driven architecture for subscribing, emitting, and unsubscribing events. + * + * @example + * ```ts + * const emitter = new EventEmitter(); + * + * // Subscribe to an event + * emitter.subscribe('eventName', (payload) => { + * console.log(payload); + * }); + * + * // Emit an event + * emitter.emit('eventName', { key: 'value' }); + * + * // Unsubscribe from an event + * emitter.unsubscribe('eventName'); + * ``` + */ export class EventEmitter { private logger: Logger = new Logger('EventEmitter'); private listenersRegistered: Map; @@ -13,7 +32,16 @@ export class EventEmitter { this.eventTarget = new EventTarget(); } - emit

(channelName: string, payload: P) { + /** + * Emits an event to a specified channel with the given payload. + * + * @template P - The type of the payload. + * @param {string} channelName - The name of the channel to emit the event to. + * @param {P} payload - The payload to send with the event. + * @throws {Error} If there is no listener registered for the specified channel. + * @returns {void} + */ + emit

(channelName: string, payload: P): void { const channels = Array.from(this.listenersRegistered.keys()); if (!channels.includes(channelName)) { throw new Error(`No listener for '${channelName}' channel`); @@ -27,7 +55,15 @@ export class EventEmitter { ); } - subscribe

(channelName: string, listener: Listener

) { + /** + * Subscribes a listener to a specified event channel. + * + * @template P - The type of the payload expected by the listener. + * @param {string} channelName - The name of the event channel to subscribe to. + * @param {Listener

} listener - The listener function to be called when an event is emitted on the specified channel. + * @returns {void} + */ + subscribe

(channelName: string, listener: Listener

): void { const eventListener = (ev: Event) => { const { detail: payload } = ev as CustomEvent; return listener(payload); @@ -42,7 +78,13 @@ export class EventEmitter { ); } - unsubscribe(channelName?: string) { + /** + * Unsubscribes from event listeners for a specific channel or all channels. + * + * @param channelName - The name of the channel to unsubscribe from. If not provided, unsubscribes from all channels. + * @returns void + */ + unsubscribe(channelName?: string): void { this.logger.log( `cleaning up event listeners for '${channelName ?? 'all'}' channel`, ); diff --git a/src/events/module.ts b/src/events/module.ts index 64a684a4..b008534f 100644 --- a/src/events/module.ts +++ b/src/events/module.ts @@ -1,9 +1,10 @@ import { OnAppBootstrap, OnAppClose } from '../hook/interfaces.ts'; import { MetadataHelper } from '../metadata/helper.ts'; -import { InjectableConstructor, injector, Logger, Module } from '../mod.ts'; +import { injector, Logger, Module } from '../mod.ts'; import { eventListenerMetadataKey } from './constants.ts'; import { EventEmitter } from './events.ts'; +/* Use this module if you want to use EventEmitter https://danet.land/techniques/events.html */ @Module({ injectables: [EventEmitter], }) diff --git a/src/exception/filter/decorator.ts b/src/exception/filter/decorator.ts index 9532d1d4..c21b8bff 100644 --- a/src/exception/filter/decorator.ts +++ b/src/exception/filter/decorator.ts @@ -1,9 +1,34 @@ import { Constructor } from '../../utils/constructor.ts'; -import { SetMetadata } from '../../metadata/decorator.ts'; +import { MetadataFunction, SetMetadata } from '../../metadata/decorator.ts'; +/** + * Metadata key used to mark and identify exception filters. + * + * @constant {string} filterExceptionMetadataKey + */ export const filterExceptionMetadataKey = 'filterException'; -export const UseFilter = (filter: Constructor) => - SetMetadata(filterExceptionMetadataKey, filter); +/** + * A decorator function that applies a specified filter to the metadata of a class or method. + * + * @param filter - The constructor of the filter to be applied. + * @returns A function that sets the metadata for the filter. + */ +export function UseFilter(filter: Constructor): MetadataFunction { + return SetMetadata(filterExceptionMetadataKey, filter); +} +/** + * Used to store metadata for the type of errors caught by a filter. + * + * @constant + * @type {string} + */ export const filterCatchTypeMetadataKey = 'errorCaught'; -export const Catch = (ErrorType: Constructor) => - SetMetadata(filterCatchTypeMetadataKey, ErrorType); +/** + * A decorator function to specify the error type to catch for an exception filter. + * + * @param ErrorType - The constructor of the error type to catch. + * @returns A metadata function that sets the metadata for the error type to catch. + */ +export function Catch(ErrorType: Constructor): MetadataFunction { + return SetMetadata(filterCatchTypeMetadataKey, ErrorType); +} diff --git a/src/exception/filter/executor.ts b/src/exception/filter/executor.ts index 658c2bb9..5c978664 100644 --- a/src/exception/filter/executor.ts +++ b/src/exception/filter/executor.ts @@ -10,6 +10,14 @@ import { ExceptionFilter } from './interface.ts'; import { Injector } from '../../injector/injector.ts'; import { WebSocketPayload } from '../../router/websocket/payload.ts'; +/** + * Responsible for executing exception filters + * based on metadata and handling errors within the context of an HTTP request. + * It utilizes the injector to manage dependencies and retrieve filter instances. + * + * @constructor + * @param {Injector} injector - The dependency injector used to manage and retrieve filter instances. + */ export class FilterExecutor { constructor(private injector: Injector) { } @@ -65,6 +73,15 @@ export class FilterExecutor { return; } + /** + * Executes filters for both the controller and the controller method. + * + * @param context - The HTTP context for the current request. + * @param error - The error that occurred. + * @param Controller - The constructor of the controller. + * @param ControllerMethod - The method of the controller to be executed. + * @returns A promise that resolves to a `Response`, `undefined`, or `WebSocketPayload`. + */ async executeControllerAndMethodFilter( context: HttpContext, error: unknown, diff --git a/src/exception/filter/interface.ts b/src/exception/filter/interface.ts index 6441b9b4..078adda0 100644 --- a/src/exception/filter/interface.ts +++ b/src/exception/filter/interface.ts @@ -1,5 +1,13 @@ import { HttpContext } from '../../router/router.ts'; +/** + * Interface representing an exception filter. + * + * This interface defines a method to handle exceptions that occur within an HTTP context. + * Implementations of this interface can provide custom logic for handling different types of exceptions. + * + * @interface ExceptionFilter + */ export interface ExceptionFilter { catch( exception: unknown, diff --git a/src/exception/http/enum.ts b/src/exception/http/enum.ts index f0110fd4..83867490 100644 --- a/src/exception/http/enum.ts +++ b/src/exception/http/enum.ts @@ -1,3 +1,6 @@ +/** + * Enum for HTTP status codes + */ export enum HTTP_STATUS { CONTINUE = 100, SWITCHING_PROTOCOLS = 101, diff --git a/src/exception/http/exceptions.ts b/src/exception/http/exceptions.ts index bf76d54b..f01de05b 100644 --- a/src/exception/http/exceptions.ts +++ b/src/exception/http/exceptions.ts @@ -1,55 +1,152 @@ +/** + * HTTP exceptions + */ + import { HTTP_STATUS } from './enum.ts'; +/** + * Represents an HTTP exception with a status code and description. + * Extends the built-in `Error` class. + * + * @class + * @extends {Error} + */ export class HttpException extends Error { + /** + * Creates an instance of HttpException. + * + * @param {number} status - The HTTP status code associated with the exception. + * @param {string} description - A brief description of the exception. + */ constructor(readonly status: number, description: string) { super(`${status} - ${description}`); this.name = this.constructor.name; } } +/** + * Represents a Forbidden HTTP exception. + * Extends the `HttpException` class with a status code of 403. + * + * @class + * @extends {HttpException} + */ export class ForbiddenException extends HttpException { + /** + * Creates an instance of ForbiddenException. + */ constructor() { super(HTTP_STATUS.FORBIDDEN, 'Forbidden'); } } +/** + * Represents a Bad Request HTTP exception. + * Extends the `HttpException` class with a status code of 400. + * + * @class + * @extends {HttpException} + */ export class BadRequestException extends HttpException { + /** + * Creates an instance of BadRequestException. + */ constructor() { super(HTTP_STATUS.BAD_REQUEST, 'Bad request'); } } +/** + * Represents an Unauthorized HTTP exception. + * Extends the `HttpException` class with a status code of 401. + * + * @class + * @extends {HttpException} + */ export class UnauthorizedException extends HttpException { + /** + * Creates an instance of UnauthorizedException. + */ constructor() { super(HTTP_STATUS.UNAUTHORIZED, 'Unauthorized'); } } +/** + * Represents a Payment Required HTTP exception. + * Extends the `HttpException` class with a status code of 402. + * + * @class + * @extends {HttpException} + */ export class PaymentRequiredException extends HttpException { + /** + * Creates an instance of PaymentRequiredException. + */ constructor() { super(HTTP_STATUS.PAYMENT_REQUIRED, 'Payment required'); } } +/** + * Represents a Not Found HTTP exception. + * Extends the `HttpException` class with a status code of 404. + * + * @class + * @extends {HttpException} + */ export class NotFoundException extends HttpException { + /** + * Creates an instance of NotFoundException. + */ constructor() { super(HTTP_STATUS.NOT_FOUND, 'Not found'); } } +/** + * Represents a Method Not Allowed HTTP exception. + * Extends the `HttpException` class with a status code of 405. + * + * @class + * @extends {HttpException} + */ export class MethodNotAllowedException extends HttpException { + /** + * Creates an instance of MethodNotAllowedException. + */ constructor() { super(HTTP_STATUS.METHOD_NOT_ALLOWED, 'Method not allowed'); } } +/** + * Represents a Not Acceptable HTTP exception. + * Extends the `HttpException` class with a status code of 406. + * + * @class + * @extends {HttpException} + */ export class NotAcceptableException extends HttpException { + /** + * Creates an instance of NotAcceptableException. + */ constructor() { super(HTTP_STATUS.NOT_ACCEPTABLE, 'Not acceptable'); } } +/** + * Represents a Proxy Authentication Required HTTP exception. + * Extends the `HttpException` class with a status code of 407. + * + * @class + * @extends {HttpException} + */ export class ProxyAuthenticationRequiredException extends HttpException { + /** + * Creates an instance of ProxyAuthenticationRequiredException. + */ constructor() { super( HTTP_STATUS.PROXY_AUTHENTICATION_REQUIRED, @@ -58,55 +155,145 @@ export class ProxyAuthenticationRequiredException extends HttpException { } } +/** + * Represents a Request Timeout HTTP exception. + * Extends the `HttpException` class with a status code of 408. + * + * @class + * @extends {HttpException} + */ export class RequestTimeoutException extends HttpException { + /** + * Creates an instance of RequestTimeoutException. + */ constructor() { super(HTTP_STATUS.REQUEST_TIMEOUT, 'Request timeout'); } } +/** + * Represents a Conflict HTTP exception. + * Extends the `HttpException` class with a status code of 409. + * + * @class + * @extends {HttpException} + */ export class ConflictException extends HttpException { + /** + * Creates an instance of ConflictException. + */ constructor() { super(HTTP_STATUS.CONFLICT, 'Conflict'); } } +/** + * Represents a Gone HTTP exception. + * Extends the `HttpException` class with a status code of 410. + * + * @class + * @extends {HttpException} + */ export class GoneException extends HttpException { + /** + * Creates an instance of GoneException. + */ constructor() { super(HTTP_STATUS.GONE, 'Gone'); } } +/** + * Represents a Length Required HTTP exception. + * Extends the `HttpException` class with a status code of 411. + * + * @class + * @extends {HttpException} + */ export class LengthRequiredException extends HttpException { + /** + * Creates an instance of LengthRequiredException. + */ constructor() { super(HTTP_STATUS.LENGTH_REQUIRED, 'Length required'); } } +/** + * Represents a Precondition Failed HTTP exception. + * Extends the `HttpException` class with a status code of 412. + * + * @class + * @extends {HttpException} + */ export class PreconditionFailedException extends HttpException { + /** + * Creates an instance of PreconditionFailedException. + */ constructor() { super(HTTP_STATUS.PRECONDITION_FAILED, 'Precondition failed'); } } +/** + * Represents a Payload Too Large HTTP exception. + * Extends the `HttpException` class with a status code of 413. + * + * @class + * @extends {HttpException} + */ export class PayloadTooLargeException extends HttpException { + /** + * Creates an instance of PayloadTooLargeException. + */ constructor() { super(HTTP_STATUS.PAYLOAD_TOO_LARGE, 'Payload too large'); } } +/** + * Represents a URI Too Long HTTP exception. + * Extends the `HttpException` class with a status code of 414. + * + * @class + * @extends {HttpException} + */ export class URITooLongException extends HttpException { + /** + * Creates an instance of URITooLongException. + */ constructor() { super(HTTP_STATUS.URI_TOO_LONG, 'URI too long'); } } +/** + * Represents an Unsupported Media Type HTTP exception. + * Extends the `HttpException` class with a status code of 415. + * + * @class + * @extends {HttpException} + */ export class UnsupportedMediaTypeException extends HttpException { + /** + * Creates an instance of UnsupportedMediaTypeException. + */ constructor() { super(HTTP_STATUS.UNSUPPORTED_MEDIA_TYPE, 'Unsupported media type'); } } +/** + * Represents a Requested Range Not Satisfiable HTTP exception. + * Extends the `HttpException` class with a status code of 416. + * + * @class + * @extends {HttpException} + */ export class RequestedRangeNotSatisfiableException extends HttpException { + /** + * Creates an instance of RequestedRangeNotSatisfiableException. + */ constructor() { super( HTTP_STATUS.REQUESTED_RANGE_NOT_SATISFIABLE, @@ -115,86 +302,229 @@ export class RequestedRangeNotSatisfiableException extends HttpException { } } +/** + * Represents an Expectation Failed HTTP exception. + * Extends the `HttpException` class with a status code of 417. + * + * @class + * @extends {HttpException} + */ export class ExpectationFailedException extends HttpException { + /** + * Creates an instance of ExpectationFailedException. + */ constructor() { super(HTTP_STATUS.EXPECTATION_FAILED, 'Expectation failed'); } } +/** + * Represents an I Am A Teapot HTTP exception. + * Extends the `HttpException` class with a status code of 418. + * + * @class + * @extends {HttpException} + */ export class IAmATeapotException extends HttpException { + /** + * Creates an instance of IAmATeapotException. + */ constructor() { super(HTTP_STATUS.I_AM_A_TEAPOT, 'I am a teapot'); } } +/** + * Represents a Misdirected HTTP exception. + * Extends the `HttpException` class with a status code of 421. + * + * @class + * @extends {HttpException} + */ export class MisdirectedException extends HttpException { + /** + * Creates an instance of MisdirectedException. + */ constructor() { super(HTTP_STATUS.MISDIRECTED, 'Misdirected'); } } +/** + * Represents an Unprocessable Entity HTTP exception. + * Extends the `HttpException` class with a status code of 422. + * + * @class + * @extends {HttpException} + */ export class UnprocessableEntityException extends HttpException { + /** + * Creates an instance of UnprocessableEntityException. + */ constructor() { super(HTTP_STATUS.UNPROCESSABLE_ENTITY, 'Unprocessable entity'); } } +/** + * Represents a Failed Dependency HTTP exception. + * Extends the `HttpException` class with a status code of 424. + * + * @class + * @extends {HttpException} + */ export class FailedDependencyException extends HttpException { + /** + * Creates an instance of FailedDependencyException. + */ constructor() { super(HTTP_STATUS.FAILED_DEPENDENCY, 'Failed dependency'); } } +/** + * Represents a Precondition Required HTTP exception. + * Extends the `HttpException` class with a status code of 428. + * + * @class + * @extends {HttpException} + */ export class PreconditionRequiredException extends HttpException { + /** + * Creates an instance of PreconditionRequiredException. + */ constructor() { super(HTTP_STATUS.PRECONDITION_REQUIRED, 'Precondition required'); } } +/** + * Represents a Too Many Requests HTTP exception. + * Extends the `HttpException` class with a status code of 429. + * + * @class + * @extends {HttpException} + */ export class TooManyRequestsException extends HttpException { + /** + * Creates an instance of TooManyRequestsException. + */ constructor() { super(HTTP_STATUS.TOO_MANY_REQUESTS, 'Too many requests'); } } +/** + * Represents an Internal Server Error HTTP exception. + * Extends the `HttpException` class with a status code of 500. + * + * @class + * @extends {HttpException} + */ export class InternalServerErrorException extends HttpException { + /** + * Creates an instance of InternalServerErrorException. + */ constructor() { super(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error'); } } +/** + * Represents a Not Implemented HTTP exception. + * Extends the `HttpException` class with a status code of 501. + * + * @class + * @extends {HttpException} + */ export class NotImplementedException extends HttpException { + /** + * Creates an instance of NotImplementedException. + */ constructor() { super(HTTP_STATUS.NOT_IMPLEMENTED, 'Not implemented'); } } +/** + * Represents a Bad Gateway HTTP exception. + * Extends the `HttpException` class with a status code of 502. + * + * @class + * @extends {HttpException} + */ export class BadGatewayException extends HttpException { + /** + * Creates an instance of BadGatewayException. + */ constructor() { super(HTTP_STATUS.BAD_GATEWAY, 'Bad gateway'); } } +/** + * Represents a Service Unavailable HTTP exception. + * Extends the `HttpException` class with a status code of 503. + * + * @class + * @extends {HttpException} + */ export class ServiceUnavailableException extends HttpException { + /** + * Creates an instance of ServiceUnavailableException. + */ constructor() { super(HTTP_STATUS.SERVICE_UNAVAILABLE, 'Service unavailable'); } } +/** + * Represents a Gateway Timeout HTTP exception. + * Extends the `HttpException` class with a status code of 504. + * + * @class + * @extends {HttpException} + */ export class GatewayTimeoutException extends HttpException { + /** + * Creates an instance of GatewayTimeoutException. + */ constructor() { super(HTTP_STATUS.GATEWAY_TIMEOUT, 'Gateway timeout'); } } +/** + * Represents an HTTP Version Not Supported HTTP exception. + * Extends the `HttpException` class with a status code of 505. + * + * @class + * @extends {HttpException} + */ export class HttpVersionNotSupportedException extends HttpException { + /** + * Creates an instance of HttpVersionNotSupportedException. + */ constructor() { super(HTTP_STATUS.HTTP_VERSION_NOT_SUPPORTED, 'Http version not supported'); } } +/** + * Represents a Not Valid Body HTTP exception with additional reasons. + * Extends the `HttpException` class with a status code of 400. + * + * @class + * @extends {HttpException} + * @template T + */ export class NotValidBodyException extends HttpException { reasons: T; + /** + * Creates an instance of NotValidBodyException. + * + * @param {T} reasons - The reasons why the body is not valid. + */ constructor(reasons: T) { super(HTTP_STATUS.BAD_REQUEST, 'Body bad formatted'); this.reasons = reasons; diff --git a/src/guard/constants.ts b/src/guard/constants.ts index 30b1e974..2fa7652f 100644 --- a/src/guard/constants.ts +++ b/src/guard/constants.ts @@ -1 +1,5 @@ +/** + * The global guard identifier. + * This can be used to apply a guard globally across the application. + */ export const GLOBAL_GUARD = 'global-guard'; diff --git a/src/guard/decorator.ts b/src/guard/decorator.ts index ac6a4725..ff386569 100644 --- a/src/guard/decorator.ts +++ b/src/guard/decorator.ts @@ -1,6 +1,15 @@ import { Constructor } from '../utils/constructor.ts'; -import { SetMetadata } from '../metadata/decorator.ts'; +import { MetadataFunction, SetMetadata } from '../metadata/decorator.ts'; export const guardMetadataKey = 'authGuards'; -export const UseGuard = (guard: Constructor) => - SetMetadata(guardMetadataKey, guard); +/** + * Applies a guard to a route handler or controller. + * + * https://danet.land/overview/guards.html + * + * @param guard - The constructor of the guard to be applied. + * @returns A function that sets the metadata for the guard. + */ +export function UseGuard(guard: Constructor): MetadataFunction { + return SetMetadata(guardMetadataKey, guard); +} diff --git a/src/guard/executor.ts b/src/guard/executor.ts index 37e6b631..d483ee66 100644 --- a/src/guard/executor.ts +++ b/src/guard/executor.ts @@ -8,10 +8,29 @@ import { GLOBAL_GUARD } from './constants.ts'; import { guardMetadataKey } from './decorator.ts'; import { AuthGuard } from './interface.ts'; +/** + * Responsible for executing various guards in a given execution context. + * It handles the execution of global guards, controller-level guards, and method-level guards. + * https://danet.land/overview/guards.html + * @constructor + * @param {Injector} injector - The injector instance used to retrieve and manage dependencies. + */ export class GuardExecutor { constructor(private injector: Injector) { } + /** + * https://danet.land/overview/guards.html + * Executes all relevant guards for the given context, controller, and controller method. + * + * This method first executes the global guard, followed by the controller and method-specific + * authentication guards. + * + * @param context - The execution context which provides details about the current request. + * @param Controller - The constructor of the controller being executed. + * @param ControllerMethod - The method of the controller being executed. + * @returns A promise that resolves when all relevant guards have been executed. + */ async executeAllRelevantGuards( context: ExecutionContext, Controller: ControllerConstructor, @@ -25,14 +44,14 @@ export class GuardExecutor { ); } - async executeGlobalGuard(context: ExecutionContext) { + private async executeGlobalGuard(context: ExecutionContext) { if (this.injector.has(GLOBAL_GUARD)) { const globalGuard: AuthGuard = await this.injector.get(GLOBAL_GUARD); await this.executeGuard(globalGuard, context); } } - async executeControllerAndMethodAuthGuards( + private async executeControllerAndMethodAuthGuards( context: ExecutionContext, Controller: ControllerConstructor, ControllerMethod: Callback, @@ -41,7 +60,7 @@ export class GuardExecutor { await this.executeGuardFromMetadata(context, ControllerMethod); } - async executeGuard(guard: AuthGuard, context: ExecutionContext) { + private async executeGuard(guard: AuthGuard, context: ExecutionContext) { if (guard) { const canActivate = await guard.canActivate(context); if (!canActivate) { @@ -50,7 +69,7 @@ export class GuardExecutor { } } - async executeGuardFromMetadata( + private async executeGuardFromMetadata( context: ExecutionContext, // deno-lint-ignore ban-types constructor: Constructor | Function, diff --git a/src/guard/interface.ts b/src/guard/interface.ts index 384fd3f4..b218263c 100644 --- a/src/guard/interface.ts +++ b/src/guard/interface.ts @@ -1,5 +1,9 @@ import { ExecutionContext } from '../router/router.ts'; +/** + * An authentication guard is responsible for determining whether a request + * should be allowed to proceed based on the provided execution context. + */ export interface AuthGuard { canActivate(context: ExecutionContext): Promise | boolean; } diff --git a/src/hook/executor.ts b/src/hook/executor.ts index 186af880..6fd1c228 100644 --- a/src/hook/executor.ts +++ b/src/hook/executor.ts @@ -1,12 +1,33 @@ +/** + * @module + * Hook executor. + * Provides a class to execute hooks for every injectable. + */ + import { InjectableHelper } from '../injector/injectable/helper.ts'; import { Injector } from '../injector/injector.ts'; import { MetadataHelper } from '../metadata/helper.ts'; import { hookName } from './interfaces.ts'; +/** + * The `HookExecutor` class is responsible for executing hooks on all injectables + * retrieved from the provided `Injector`. It ensures that hooks are executed + * only on instances that are objects and are marked as global. + */ export class HookExecutor { - constructor(private injector: Injector) { - } + /** + * Creates an instance of `HookExecutor`. + * @param injector - The injector instance used to retrieve all injectables. + */ + constructor(private injector: Injector) {} + /** + * Executes a specified hook on every injectable retrieved from the injector. + * It iterates through all injectables, checks if they are objects, and then + * executes the hook on each instance. + * + * @param hookName - The name of the hook to be executed. + */ public async executeHookForEveryInjectable(hookName: hookName) { const injectables = this.injector.getAll(); for (const [_, value] of injectables) { @@ -18,6 +39,13 @@ export class HookExecutor { } } + /** + * Executes a specified hook on a given instance if the instance is marked as global. + * + * @param instance - The instance on which the hook is to be executed. + * @param hookName - The name of the hook to be executed. + */ + // deno-lint-ignore no-explicit-any private async executeInstanceHook(instance: any, hookName: hookName) { if (InjectableHelper.isGlobal(instance?.constructor)) { diff --git a/src/hook/interfaces.ts b/src/hook/interfaces.ts index 9148ea16..37d9bc38 100644 --- a/src/hook/interfaces.ts +++ b/src/hook/interfaces.ts @@ -1,17 +1,42 @@ +/** + * @module + * Hook interfaces. + * Provide lifecycle hooks interface for the application. + * https://danet.land/fundamentals/lifecycle.html + */ + import { HttpContext } from '../router/router.ts'; +/** + * Interface representing a hook that is called when the application is bootstrapped. + * https://danet.land/fundamentals/lifecycle.html + */ export interface OnAppBootstrap { onAppBootstrap(): void | Promise; } +/** + * Interface representing a handler for application shutdown events. + */ export interface OnAppClose { onAppClose(): void | Promise; } +/** + * Interface representing a hook that is called before a controller method is invoked. + * Useful for Request Scoped Services. + */ export interface BeforeControllerMethodIsCalled { beforeControllerMethodIsCalled(ctx?: HttpContext): void | Promise; } +/** + * Enum representing the names of various hooks in the application. + * + * @enum {string} + * @property {string} APP_CLOSE - Hook triggered when the application is closing. + * @property {string} APP_BOOTSTRAP - Hook triggered when the application is bootstrapping. + */ export enum hookName { APP_CLOSE = 'onAppClose', APP_BOOTSTRAP = 'onAppBootstrap', diff --git a/src/hook/mod.ts b/src/hook/mod.ts index f39a5487..f26e989d 100644 --- a/src/hook/mod.ts +++ b/src/hook/mod.ts @@ -1,2 +1,7 @@ +/** + * @module + * Export useful interfaces for lifecycle hooks. + */ + export * from './executor.ts'; export * from './interfaces.ts'; diff --git a/src/injector/decorator.ts b/src/injector/decorator.ts index 46a9e835..5de00d87 100644 --- a/src/injector/decorator.ts +++ b/src/injector/decorator.ts @@ -1,21 +1,31 @@ import { MetadataHelper } from '../metadata/helper.ts'; +import { DecoratorFunction } from '../mod.ts'; export const injectionTokenMetadataKey = 'injection-token'; -export function getInjectionTokenMetadataKey(parameterIndex: number) { +export function getInjectionTokenMetadataKey(parameterIndex: number): string { return `${injectionTokenMetadataKey}:${parameterIndex}`; } -export const Inject = (token?: string) => -( - // deno-lint-ignore no-explicit-any - target: Record | any, - propertyKey: string | symbol | undefined, - parameterIndex: number, -) => { - MetadataHelper.setMetadata( - getInjectionTokenMetadataKey(parameterIndex), - token, - target, - ); -}; +/** + * Decorator to inject using token. + * + * Get example here https://danet.land/fundamentals/dynamic-modules.html#module-configuration + * + * @param token - Optional token to identify the dependency. + * @returns A decorator function that sets the metadata for the injection token. + */ +export function Inject(token?: string): DecoratorFunction { + return ( + // deno-lint-ignore no-explicit-any + target: Record | any, + propertyKey: string | symbol | undefined, + parameterIndex: number, + ) => { + MetadataHelper.setMetadata( + getInjectionTokenMetadataKey(parameterIndex), + token, + target, + ); + }; +} diff --git a/src/injector/injectable/constructor.ts b/src/injector/injectable/constructor.ts index 6797b4a8..f9363d3c 100644 --- a/src/injector/injectable/constructor.ts +++ b/src/injector/injectable/constructor.ts @@ -1,14 +1,33 @@ import { Constructor } from '../../utils/constructor.ts'; +/** + * Type Alias for Constructor + */ export type InjectableConstructor = Constructor; + +/** @deprecated Prefer plain object of Type UseClassInjector */ export class TokenInjector { constructor(public useClass: InjectableConstructor, public token: string) { } } +/** + * Represents an injector configuration that uses a class constructor for dependency injection. + * + * @typedef {Object} UseClassInjector + * @property {InjectableConstructor} useClass - The class constructor to be used for injection. + * @property {string} token - The token that identifies the dependency. + */ export type UseClassInjector = { useClass: InjectableConstructor; token: string; }; +/** + * Represents an injector that uses a specific value for dependency injection. + * + * @typedef {Object} UseValueInjector + * @property {any} useValue - The value to be used for injection. + * @property {string} token - The token that identifies the value. + */ export type UseValueInjector = { // deno-lint-ignore no-explicit-any useValue: any; diff --git a/src/injector/injectable/decorator.ts b/src/injector/injectable/decorator.ts index 325c0c60..7b9f0d07 100644 --- a/src/injector/injectable/decorator.ts +++ b/src/injector/injectable/decorator.ts @@ -1,17 +1,41 @@ -import { SetMetadata } from '../../metadata/decorator.ts'; +import { MetadataFunction, SetMetadata } from '../../metadata/decorator.ts'; +/** + * The different scopes for dependency injection. + * + * @enum {string} + * @property {string} GLOBAL - Represents a global scope where the instance is shared across the entire application. + * @property {string} REQUEST - Represents a request scope where a new instance is created for each request. + * @property {string} TRANSIENT - Represents a transient scope where a new instance is created every time it is requested. + */ export enum SCOPE { GLOBAL = 'GLOBAL', REQUEST = 'REQUEST', TRANSIENT = 'TRANSIENT', } +/** + * Options for the Injectable decorator. + * + * @interface InjectableOption + * + * @property {SCOPE} scope - The scope in which the injectable should be instantiated. + */ export interface InjectableOption { scope: SCOPE; } export const injectionData = 'dependency-injection'; -export const Injectable = ( +/** + * Mark class as an injectable. + * + * @template T - The type of the class being decorated. + * @param {InjectableOption} [options={ scope: SCOPE.GLOBAL }] - The options for the injectable, including the scope. + * @returns {MetadataFunction} - A function that sets the metadata for the injectable. + */ +export function Injectable( options: InjectableOption = { scope: SCOPE.GLOBAL }, -) => SetMetadata(injectionData, options); +): MetadataFunction { + return SetMetadata(injectionData, options); +} diff --git a/src/injector/injectable/helper.ts b/src/injector/injectable/helper.ts index 1441402e..fa6f22b5 100644 --- a/src/injector/injectable/helper.ts +++ b/src/injector/injectable/helper.ts @@ -2,8 +2,17 @@ import { MetadataHelper } from '../../metadata/helper.ts'; import { Constructor } from '../../utils/constructor.ts'; import { InjectableOption, injectionData, SCOPE } from './decorator.ts'; +/** + * A helper class for injectable services. + */ export class InjectableHelper { - static isGlobal(constructor: Constructor) { + /** + * Determines if the given constructor is global. + * + * @param constructor - The constructor to check. + * @returns `true` if the constructor is global, otherwise `false`. + */ + static isGlobal(constructor: Constructor): boolean { const data = MetadataHelper.getMetadata( injectionData, constructor, diff --git a/src/injector/injector.ts b/src/injector/injector.ts index 7572e8f7..7f7ce97f 100644 --- a/src/injector/injector.ts +++ b/src/injector/injector.ts @@ -16,6 +16,23 @@ import { import { ExecutionContext } from '../router/router.ts'; import { ModuleMetadata } from '../mod.ts'; +/** + * The `Injector` class is responsible for managing the dependency injection + * within the application. It maintains a registry of resolved instances, + * available types, and context-specific injectables. It also handles the + * bootstrapping of modules, resolving of controllers and injectables, and + * managing the lifecycle of dependencies. + * + * @class + * @property {Map Promise | unknown>} resolved - A map of resolved instances. + * @property {Map} availableTypes - A map of available types for injection. + * @property {Logger} logger - Logger instance for logging purposes. + * @property {Map} resolvedTypes - A map of resolved types. + * @property {Map>} contextInjectables - A map of context-specific injectables. + * @property {Array} modules - An array of registered modules. + * @property {Array} controllers - An array of registered controllers. + * @property {Array} injectables - An array of registered injectables. + */ export class Injector { private resolved = new Map< Constructor | string, @@ -38,14 +55,35 @@ export class Injector { // deno-lint-ignore no-explicit-any public injectables: Array = []; - public getAll() { + /** + * Retrieves all resolved dependencies. + * + * @returns An object containing all resolved dependencies. + */ + public getAll(): typeof this.resolved { return this.resolved; } + /** + * Checks if a given type or identifier is present in the resolved dependencies. + * + * @template T - The type of the dependency to check. + * @param {Constructor | string} Type - The constructor of the type or a string identifier to check. + * @returns {boolean} - Returns `true` if the type or identifier is present, otherwise `false`. + */ public has(Type: Constructor | string): boolean { return this.resolved.has(Type); } + /** + * Retrieves an instance of the specified type from the injector. + * + * @template T - The type of the instance to retrieve. + * @param {Constructor | string} Type - The constructor function or string identifier of the type to retrieve. + * @param {ExecutionContext} [ctx] - Optional execution context to pass to the instance. + * @returns {T} The instance of the specified type. + * @throws {Error} If the type has not been injected. + */ public get(Type: Constructor | string, ctx?: ExecutionContext): T { if (this.resolved.has(Type)) { return this.resolved.get(Type)!(ctx) as T; @@ -53,6 +91,12 @@ export class Injector { throw Error(`Type ${Type} not injected`); } + /** + * Bootstraps the given module by registering its injectables and resolving its controllers. + * + * @param module - The module metadata to bootstrap, containing controllers and injectables. + * @returns A promise that resolves when the module has been fully bootstrapped. + */ public async bootstrapModule(module: ModuleMetadata) { this.logger.log(`Bootstraping ${module.constructor.name}`); const { controllers, injectables } = module; @@ -67,7 +111,7 @@ export class Injector { this.modules.push(module); } - public addAvailableInjectable( + private addAvailableInjectable( injectables: (InjectableConstructor | UseClassInjector | UseValueInjector)[], ) { @@ -79,6 +123,12 @@ export class Injector { } } + /** + * Registers an array of injectables by resolving each one. + * + * @param Injectables - An array of injectable constructors or injector instances. + * @returns A promise that resolves when all injectables have been registered. + */ public async registerInjectables( Injectables: Array< InjectableConstructor | UseClassInjector | UseValueInjector @@ -89,7 +139,7 @@ export class Injector { } } - public async resolveControllers(Controllers: ControllerConstructor[]) { + private async resolveControllers(Controllers: ControllerConstructor[]) { for (const Controller of Controllers) { await this.resolveControllerParameters(Controller); } diff --git a/src/kv-queue/decorator.ts b/src/kv-queue/decorator.ts index c7a06f2b..8b733953 100644 --- a/src/kv-queue/decorator.ts +++ b/src/kv-queue/decorator.ts @@ -1,6 +1,12 @@ import { MetadataHelper } from '../metadata/mod.ts'; import { queueListenerMetadataKey } from './constants.ts'; +/** + * Method decorator that registers a method as a listener for a specific queue channel. + * + * @param channel - The name of the queue channel to listen to. + * @returns A method decorator function. + */ export const OnQueueMessage = (channel: string): MethodDecorator => { return (_target, _propertyKey, descriptor) => { MetadataHelper.setMetadata( diff --git a/src/kv-queue/kv.ts b/src/kv-queue/kv.ts index 497cabff..1ed6f1a8 100644 --- a/src/kv-queue/kv.ts +++ b/src/kv-queue/kv.ts @@ -22,15 +22,15 @@ export class KvQueue implements OnAppClose, OnAppBootstrap { this.kv = await Deno.openKv(this.name); } - public sendMessage(type: string, data: unknown) { + public sendMessage(type: string, data: unknown): Promise { return this.kv.enqueue({ type, data }); } - public addListener(type: string, callback: Listener) { + public addListener(type: string, callback: Listener): void { this.listenersMap.set(type, callback); } - public attachListeners() { + public attachListeners(): void { this.kv.listenQueue((msg: QueueEvent) => { const type = msg.type; const callback = this.listenersMap.get(type); diff --git a/src/kv-queue/module.ts b/src/kv-queue/module.ts index 06e6af16..e173e756 100644 --- a/src/kv-queue/module.ts +++ b/src/kv-queue/module.ts @@ -1,16 +1,30 @@ import { OnAppBootstrap, OnAppClose } from '../hook/interfaces.ts'; import { MetadataHelper } from '../metadata/helper.ts'; -import { injector, Logger, Module } from '../mod.ts'; +import { + InjectableConstructor, + injector, + Logger, + Module, + ModuleConstructor, + TokenInjector, + UseValueInjector, +} from '../mod.ts'; import { KV_QUEUE_NAME, queueListenerMetadataKey } from './constants.ts'; import { KvQueue } from './kv.ts'; +/* Use this module if you want to use KV Queue https://danet.land/techniques/kvQueue.html */ @Module({}) export class KvQueueModule implements OnAppBootstrap { private logger: Logger = new Logger('QueueModule'); constructor() {} - public static forRoot(kvName?: string) { + public static forRoot(kvName?: string): { + injectables: Array< + InjectableConstructor | TokenInjector | UseValueInjector + >; + module: typeof KvQueueModule; + } { return { injectables: [{ token: KV_QUEUE_NAME, useValue: kvName }, KvQueue], module: KvQueueModule, diff --git a/src/logger.ts b/src/logger.ts index c6edefe1..0edd3ae1 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,6 +1,28 @@ +/** + * @module + * Logger module. + * Provides a class to log messages to the console. + */ + import { green, red, white, yellow } from './deps.ts'; import { Injectable } from './injector/injectable/decorator.ts'; +/** + * A Logger class to handle logging with optional namespace and color-coded output. + * + * @remarks + * This class provides methods to log messages with different severity levels (log, error, warn). + * It supports optional namespaces for better context in logs and uses color functions to + * differentiate log types. + * + * @example + * ```typescript + * const logger = new Logger('MyNamespace'); + * logger.log('This is an info message'); + * logger.error('This is an error message'); + * logger.warn('This is a warning message'); + * ``` + */ @Injectable() export class Logger { constructor(private namespace?: string) { @@ -38,14 +60,29 @@ export class Logger { loggingFunc(this.concatNamespaceAndText(text, colorFunc)); } + /** + * Logs a message to the console with green color. + * + * @param text - The message to be logged. + */ log(text: string) { this.printTo(text, green, console.log); } + /** + * Logs an error message to the console with a red color. + * + * @param text - The error message to be logged. + */ error(text: string) { this.printTo(text, red, console.error); } + /** + * Logs a warning message to the console with a yellow color. + * + * @param text - The warning message to be logged. + */ warn(text: string) { this.printTo(text, yellow, console.warn); } diff --git a/src/metadata/decorator.ts b/src/metadata/decorator.ts index 5780d40e..fe77847c 100644 --- a/src/metadata/decorator.ts +++ b/src/metadata/decorator.ts @@ -1,14 +1,42 @@ +/** + ** metadataDecorator + ** Provides decorator to set Metadata + ** @module + */ + import { ControllerConstructor } from '../router/controller/constructor.ts'; import { MetadataHelper } from './helper.ts'; -export const SetMetadata = (key: string, value: unknown) => +/** + * A function type that represents a metadata decorator. + * + * @param target - The target object or constructor to which the metadata is applied. + * @param propertyKey - An optional property key for the target. + * @param descriptor - An optional property descriptor for the target. + */ +export type MetadataFunction = ( + // deno-lint-ignore ban-types + target: ControllerConstructor | Object, + propertyKey?: string | symbol, + // deno-lint-ignore no-explicit-any + descriptor?: TypedPropertyDescriptor, +) => void; + +/** + * Sets metadata on the target object or method. + * + * @param key - The key for the metadata. + * @param value - The value for the metadata. + * @returns a MetadaFunction. + */ +export const SetMetadata = (key: string, value: unknown): MetadataFunction => ( // deno-lint-ignore ban-types target: ControllerConstructor | Object, propertyKey?: string | symbol, // deno-lint-ignore no-explicit-any descriptor?: TypedPropertyDescriptor, -) => { +): void => { if (propertyKey && descriptor) { MetadataHelper.setMetadata( key, diff --git a/src/metadata/helper.ts b/src/metadata/helper.ts index 7603bdba..eaa61272 100644 --- a/src/metadata/helper.ts +++ b/src/metadata/helper.ts @@ -1,12 +1,57 @@ +/** + * metadata + * Provides metadata helper functions + * Dependencies: Reflect + * @module + */ + import { Reflect } from '../deps.ts'; +/** + * A helper class for managing metadata on objects and their properties. + * + * This class provides static methods to determine if a value is an object, + * retrieve metadata from an object or its property, and set metadata on an + * object or its property. + * + * @example + * ```ts + * // Check if a value is an object + * const isObject = MetadataHelper.IsObject(someValue); + * + * // Retrieve metadata from an object + * const metadataValue = MetadataHelper.getMetadata('metadataKey', someObject); + * + * // Set metadata on an object + * MetadataHelper.setMetadata('metadataKey', 'metadataValue', someObject); + * ``` + */ export class MetadataHelper { + /** + * Determines if the provided value is an object. + * + * This function checks if the given value is of type 'object' and not null, + * or if it is of type 'function'. + * + * @template T - The type of the value being checked. + * @param x - The value to check. It can be of type T, undefined, null, boolean, string, symbol, or number. + * @returns A boolean indicating whether the value is an object. + */ static IsObject( x: T | undefined | null | boolean | string | symbol | number, ): x is T { return typeof x === 'object' ? x !== null : typeof x === 'function'; } + /** + * Retrieves metadata of a specified key from an object or its property. + * + * @template T - The expected type of the metadata value. + * @param {string} key - The key for the metadata to retrieve. + * @param {unknown} obj - The target object from which to retrieve the metadata. + * @param {string | symbol} [property] - Optional. The property of the object from which to retrieve the metadata. + * @returns {T} - The metadata value associated with the specified key. + */ static getMetadata( key: string, obj: unknown, @@ -18,12 +63,23 @@ export class MetadataHelper { return Reflect.getMetadata(key, obj) as T; } + /** + * Sets metadata for a target object or its property. + * + * @param key - The metadata key. + * @param value - The metadata value. + * @param target - The target object to set the metadata on. + * @param property - Optional. The property of the target object to set the metadata on. + * If not provided, the metadata is set on the target object itself. + * + * @returns void + */ static setMetadata( key: string, value: unknown, target: unknown, property?: string | symbol, - ) { + ): void { if (property) { return Reflect.defineMetadata(key, value, target, property); } diff --git a/src/metadata/mod.ts b/src/metadata/mod.ts index 1899a724..874bc823 100644 --- a/src/metadata/mod.ts +++ b/src/metadata/mod.ts @@ -1 +1,6 @@ +/** + * @module + * Provide helper function and decorators for metadata. + */ + export * from './helper.ts'; diff --git a/src/mod.ts b/src/mod.ts index a8f2dbb7..19310398 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,4 +1,8 @@ -// created from ctix +/** + * @module + * + * A probably too big module that exports almost everything. + */ export * from './app.ts'; export * from './utils/mod.ts'; diff --git a/src/module/constructor.ts b/src/module/constructor.ts index 6c3f1388..b94b271a 100644 --- a/src/module/constructor.ts +++ b/src/module/constructor.ts @@ -1,3 +1,7 @@ import { Constructor } from '../utils/constructor.ts'; +/** + * Type Alias for Constructor + */ + export type ModuleConstructor = Constructor; diff --git a/src/module/decorator.ts b/src/module/decorator.ts index ce516021..f162f5aa 100644 --- a/src/module/decorator.ts +++ b/src/module/decorator.ts @@ -5,6 +5,15 @@ import { Constructor } from '../utils/constructor.ts'; import { ModuleConstructor } from './constructor.ts'; import { UseClassInjector, UseValueInjector } from '../mod.ts'; +/** + * Metadata for a module. + * + * https://danet.land/overview/modules.html + * + * @property {Array} [imports] - Optional array of modules or dynamic modules to be imported. + * @property {ControllerConstructor[]} [controllers] - Optional array of controller constructors. + * @property {Array} [injectables] - Optional array of injectables, which can be constructors, value injectors, or class injectors. + */ export interface ModuleMetadata { imports?: Array; controllers?: ControllerConstructor[]; @@ -13,13 +22,34 @@ export interface ModuleMetadata { >; } +/** + * Represents a dynamic module in the application. + * + * https://danet.land/fundamentals/dynamic-modules.html + * + * @interface DynamicModule + * @extends {ModuleMetadata} + * + * @property {ModuleConstructor} module - The constructor of the module. + */ export interface DynamicModule extends ModuleMetadata { module: ModuleConstructor; } export const moduleMetadataKey = 'module'; -export function Module(options: ModuleMetadata) { +/** + * Module Decorator. + * + * https://danet.land/overview/modules.html + * + * @template T - The type of the class to which the metadata will be attached. + * @param {ModuleMetadata} options - The metadata options to be attached to the class. + * @returns {(Type: Constructor) => void} - A function that takes a class constructor and attaches the metadata to it. + */ +export function Module( + options: ModuleMetadata, +): (Type: Constructor) => void { return (Type: Constructor): void => { MetadataHelper.setMetadata(moduleMetadataKey, options, Type); }; diff --git a/src/renderer/handlebar.ts b/src/renderer/handlebar.ts index a08df669..cbe87bd9 100644 --- a/src/renderer/handlebar.ts +++ b/src/renderer/handlebar.ts @@ -1,5 +1,5 @@ import { Renderer } from './interface.ts'; -import { Handlebars } from 'https://deno.land/x/handlebars@v0.10.0/mod.ts'; +import { Handlebars } from '@danet/handlebars'; const defaultOption = { baseDir: 'views', diff --git a/src/router/controller/constructor.ts b/src/router/controller/constructor.ts index d105ab99..f5cb693f 100644 --- a/src/router/controller/constructor.ts +++ b/src/router/controller/constructor.ts @@ -1,3 +1,6 @@ import { Constructor } from '../../utils/constructor.ts'; +/** + * Type Alias for Constructor + */ export type ControllerConstructor = Constructor; diff --git a/src/router/controller/decorator.ts b/src/router/controller/decorator.ts index 6db11219..b32e00ec 100644 --- a/src/router/controller/decorator.ts +++ b/src/router/controller/decorator.ts @@ -1,5 +1,14 @@ import { MetadataHelper } from '../../metadata/helper.ts'; -import { SetMetadata } from '../../metadata/decorator.ts'; +import { MetadataFunction, SetMetadata } from '../../metadata/decorator.ts'; +/** + * Represents the HTTP methods that can be used in routing. + * + * @typedef {('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD')} HttpMethod + * + * @example + * // Example usage: + * const method: HttpMethod = 'GET'; + */ export type HttpMethod = | 'GET' | 'POST' @@ -9,9 +18,19 @@ export type HttpMethod = | 'OPTIONS' | 'HEAD'; -export const Controller = (endpoint = '') => - SetMetadata('endpoint', endpoint); -function createMappingDecorator(method?: HttpMethod) { +/** + * Decorate a class to make it an HTTP Controller. + * + * @template T - The type parameter for the controller. + * @param {string} [endpoint=''] - The endpoint for the controller. Defaults to an empty string. + * @returns {MetadataFunction} - A function that sets the metadata for the endpoint. + */ +export function Controller(endpoint = ''): MetadataFunction { + return SetMetadata('endpoint', endpoint); +} +type MappingDecoratorFunction = (endpoint?: string) => MethodDecorator; + +function createMappingDecorator(method?: HttpMethod): MappingDecoratorFunction { return (endpoint = ''): MethodDecorator => { return (_target, _propertyKey, descriptor) => { MetadataHelper.setMetadata('endpoint', endpoint, descriptor.value); @@ -23,15 +42,139 @@ function createMappingDecorator(method?: HttpMethod) { }; } -export const Get = createMappingDecorator('GET'); -export const Post = createMappingDecorator('POST'); -export const Put = createMappingDecorator('PUT'); -export const Patch = createMappingDecorator('PATCH'); -export const Delete = createMappingDecorator('DELETE'); -export const Options = createMappingDecorator('OPTIONS'); -export const Head = createMappingDecorator('HEAD'); -export const All = createMappingDecorator(); -export const SSE = (endpoint = ''): MethodDecorator => { +/** + * Define a method as a GET request handler. + * + * This decorator can be used to annotate controller methods to handle HTTP GET requests. + * + * @example + * ```typescript + * @Get('/path') + * public getMethod(): string { + * return 'This is a GET request'; + * } + * ``` + * + * @type {MappingDecoratorFunction} + */ +export const Get: MappingDecoratorFunction = createMappingDecorator('GET'); +/** + * Define a method as a POST request handler . + * * @example + * ```typescript + * @Post('/path') + * public myMethod(): string { + * return 'This is a POST request'; + * } + * ``` + */ +export const Post: MappingDecoratorFunction = createMappingDecorator('POST'); +/** + * Define a method as a PUT request handler . + * * @example + * ```typescript + * @Put('/path') + * public myMethod(): string { + * return 'This is a PUT request'; + * } + * ``` + */ +export const Put: MappingDecoratorFunction = createMappingDecorator('PUT'); +/** + * Define a method as a PATCH request handler . + * * @example + * ```typescript + * @Patch('/path') + * public myMethod(): string { + * return 'This is a PATCH request'; + * } + * ``` + */ +export const Patch: MappingDecoratorFunction = createMappingDecorator('PATCH'); +/** + * Define a method as a DELETE request handler . + * * @example + * ```typescript + * @Delete('/path') + * public myMethod(): string { + * return 'This is a DELETE request'; + * } + * ``` + */ +export const Delete: MappingDecoratorFunction = createMappingDecorator( + 'DELETE', +); +/** + * Define a method as an OPTIONS request handler . + * * @example + * ```typescript + * @Options('/path') + * public myMethod(): string { + * return 'This is a OPTIONS request'; + * } + * ``` + */ +export const Options: MappingDecoratorFunction = createMappingDecorator( + 'OPTIONS', +); +/** + * Define a method as an HEAD request handler . + * * @example + * ```typescript + * @Head('/path') + * public myMethod(): string { + * return 'This is a HEAD request'; + * } + * ``` + */ +export const Head: MappingDecoratorFunction = createMappingDecorator('HEAD'); +/** + * Define a method as a request handler for all HTTP METHOD . + * * @example + * ```typescript + * @All('/path') + * public myMethod(): string { + * return 'This is potentially a GET || POST || OPTIONS || HEAD || PATCH || DELETE || PUT request'; + * } + * ``` + */ +export const All: MappingDecoratorFunction = createMappingDecorator(); +/** + * Define a method as a request handler that will send event with SSE. + * * @example + * ```typescript + * @SSE('/path') + * public myMethod(): eventTarget { + * const eventTarget = new EventTarget(); + let id = 0; + const interval = setInterval(() => { + if (id >= 4) { + clearInterval(interval); + const event = new SSEEvent({ + retry: 1000, + id: `${id}`, + data: 'close', + event: 'close', + }); + eventTarget.dispatchEvent(event); + return; + } + const event = new SSEEvent({ + retry: 1000, + id: `${id}`, + data: 'world', + event: 'hello', + }); + eventTarget.dispatchEvent(event); + id++; + }, 100); + return eventTarget; + * } + * ``` + */ +export const SSE: MappingDecoratorFunction = ( + endpoint = '', +): MethodDecorator => { return (_target, _propertyKey, descriptor) => { MetadataHelper.setMetadata('endpoint', endpoint, descriptor.value); MetadataHelper.setMetadata('method', 'GET', descriptor.value); diff --git a/src/router/controller/params/constants.ts b/src/router/controller/params/constants.ts new file mode 100644 index 00000000..52c298d0 --- /dev/null +++ b/src/router/controller/params/constants.ts @@ -0,0 +1 @@ +export const argumentResolverFunctionsMetadataKey = 'argumentResolverFunctions'; \ No newline at end of file diff --git a/src/router/controller/params/decorators.ts b/src/router/controller/params/decorators.ts index 7d750cb7..0f37371f 100644 --- a/src/router/controller/params/decorators.ts +++ b/src/router/controller/params/decorators.ts @@ -3,6 +3,21 @@ import { ExecutionContext } from '../../router.ts'; import { validateObject } from '../../../deps.ts'; import { Constructor } from '../../../mod.ts'; import { NotValidBodyException } from '../../../exception/mod.ts'; +import { argumentResolverFunctionsMetadataKey } from './constants.ts'; + +/** + * A type representing a decorator function. + * + * @param target - The target constructor or any other type. + * @param propertyKey - The property key of the method or undefined. + * @param parameterIndex - The index of the parameter in the method's parameter list. + */ +export type DecoratorFunction = ( + // deno-lint-ignore no-explicit-any + target: Constructor | any, + propertyKey: string | symbol | undefined, + parameterIndex: number, +) => void; export type OptionsResolver = { // deno-lint-ignore no-explicit-any @@ -11,61 +26,95 @@ export type OptionsResolver = { parameterIndex: number; }; +/** + * A type alias for a function that resolves a value based on the provided execution context and optional resolver options. + * + * @param context - The execution context in which the resolver is invoked. + * @param opts - Information about which class/property the decorator was attached to. + * @returns The resolved value, which can be either a synchronous value or a promise that resolves to a value. + */ export type Resolver = ( context: ExecutionContext, opts?: OptionsResolver, ) => unknown | Promise; -export const argumentResolverFunctionsMetadataKey = 'argumentResolverFunctions'; -export const createParamDecorator = ( + +/** + * Creates a parameter decorator that resolves a parameter using the provided resolver function. + * Optionally, an additional decorator action can be executed. + * + * @param parameterResolver - A function that resolves the parameter value. + * @param additionalDecoratorAction - An optional additional decorator action to be executed. + * @returns A decorator function that can be used to decorate a parameter. + */ +export function createParamDecorator( parameterResolver: Resolver, additionalDecoratorAction?: ParameterDecorator, -) => -() => -( - // deno-lint-ignore no-explicit-any - target: Constructor | any, - propertyKey: string | symbol | undefined, - parameterIndex: number, -) => { - const argumentsResolverMap: Map = - MetadataHelper.getMetadata( +): () => DecoratorFunction { + return () => + ( + // deno-lint-ignore no-explicit-any + target: Constructor | any, + propertyKey: string | symbol | undefined, + parameterIndex: number, + ) => { + const argumentsResolverMap: Map = + MetadataHelper.getMetadata( + argumentResolverFunctionsMetadataKey, + target.constructor, + propertyKey, + ) || new Map(); + + argumentsResolverMap.set( + parameterIndex, + (context) => + parameterResolver(context, { target, propertyKey, parameterIndex }), + ); + + MetadataHelper.setMetadata( argumentResolverFunctionsMetadataKey, + argumentsResolverMap, target.constructor, propertyKey, - ) || new Map(); - - argumentsResolverMap.set( - parameterIndex, - (context) => - parameterResolver(context, { target, propertyKey, parameterIndex }), - ); - - MetadataHelper.setMetadata( - argumentResolverFunctionsMetadataKey, - argumentsResolverMap, - target.constructor, - propertyKey, - ); - - if (additionalDecoratorAction) { - additionalDecoratorAction(target, propertyKey, parameterIndex); - } -}; + ); -export const Req = createParamDecorator((context: ExecutionContext) => { - return context.req; -}); + if (additionalDecoratorAction) { + additionalDecoratorAction(target, propertyKey, parameterIndex); + } + }; +} -export const Res = createParamDecorator((context: ExecutionContext) => { - return context.res; -}); +/** + * Get current request + */ +export const Req: DecoratorFunction = createParamDecorator( + (context: ExecutionContext) => { + return context.req; + }, +); -export const WebSocket = createParamDecorator((context: ExecutionContext) => { - return context.websocket; -}); +/** + * Get current response + */ +export const Res: DecoratorFunction = createParamDecorator( + (context: ExecutionContext) => { + return context.res; + }, +); -export const Header = (prop?: string) => +/** + * Get current request websocket instance + */ +export const WebSocket: DecoratorFunction = createParamDecorator( + (context: ExecutionContext) => { + return context.websocket; + }, +); + +/** + * Get all headers or a specific header + */ +export const Header: (prop?: string) => DecoratorFunction = (prop?: string) => createParamDecorator((context: ExecutionContext) => { if (!context.req.raw.headers) { return null; @@ -73,9 +122,17 @@ export const Header = (prop?: string) => return prop ? context.req.header(prop) : context.req.raw.headers; })(); +/** + * Used to identify the type of the body in request parameters. + * + * @constant {string} BODY_TYPE_KEY + */ export const BODY_TYPE_KEY = 'body-type'; -export const Body = (prop?: string) => +/** + * Get request's body or a given property + */ +export const Body: (prop?: string) => DecoratorFunction = (prop?: string) => createParamDecorator( async (context: ExecutionContext, opts?: OptionsResolver) => { if (!opts) { @@ -151,11 +208,19 @@ function formatQueryValue( } } +/** + * Identify the type of query in the router controller parameters. + * + * @constant {string} QUERY_TYPE_KEY + */ export const QUERY_TYPE_KEY = 'query-type'; export interface QueryOption { value?: 'first' | 'last' | 'array'; } +/** + * Get all query params or a given query param + */ export function Query( options?: QueryOption, ): ReturnType>; @@ -209,8 +274,11 @@ export function Query( }))(); } -export const Param = (paramName: string) => - createParamDecorator((context: ExecutionContext) => { +/** + * Get an url param for example /user/:userId + */ +export function Param(paramName: string): DecoratorFunction { + return createParamDecorator((context: ExecutionContext) => { const params = context.req.param(); if (paramName) { return params?.[paramName]; @@ -218,12 +286,17 @@ export const Param = (paramName: string) => return params; } })(); +} -export const Session = (prop?: string) => - createParamDecorator((context: ExecutionContext) => { +/** + * Get Session or a given property of session + */ +export function Session(prop?: string): DecoratorFunction { + return createParamDecorator((context: ExecutionContext) => { if (prop) { return context.get('session').get(prop); } else { return context.get('session'); } })(); +} diff --git a/src/router/controller/params/resolver.ts b/src/router/controller/params/resolver.ts index fc7eef18..c27136a2 100644 --- a/src/router/controller/params/resolver.ts +++ b/src/router/controller/params/resolver.ts @@ -1,11 +1,20 @@ import { MetadataHelper } from '../../../metadata/helper.ts'; import { ControllerConstructor } from '../constructor.ts'; import { - argumentResolverFunctionsMetadataKey, Resolver, } from './decorators.ts'; import { ExecutionContext } from '../../mod.ts'; +import { argumentResolverFunctionsMetadataKey } from './constants.ts'; +/** + * Resolves the parameters for a given controller method by using metadata to map + * parameter indices to resolver functions. + * + * @param Controller - The constructor of the controller class. + * @param ControllerMethod - The method of the controller for which parameters need to be resolved. + * @param context - The execution context which may be used by resolver functions to resolve parameters. + * @returns A promise that resolves to an array of parameters for the controller method. + */ export async function resolveMethodParam( Controller: ControllerConstructor, // deno-lint-ignore no-explicit-any diff --git a/src/router/middleware/decorator.ts b/src/router/middleware/decorator.ts index e5e734dd..f0dd88fc 100644 --- a/src/router/middleware/decorator.ts +++ b/src/router/middleware/decorator.ts @@ -1,12 +1,27 @@ import type { MiddlewareHandler as HonoMiddleware } from '../../deps.ts'; import { ExecutionContext } from '../router.ts'; import { InjectableConstructor } from '../../injector/injectable/constructor.ts'; -import { SetMetadata } from '../../metadata/decorator.ts'; +import { MetadataFunction, SetMetadata } from '../../metadata/decorator.ts'; +/** + * Interface representing a middleware. + * + * @interface DanetMiddleware + * + * @method action + * @param {ExecutionContext} ctx - The execution context for the middleware. + * @param {NextFunction} next - The next function to call in the middleware chain. + * @returns {Promise | unknown} - A promise or a value indicating the result of the middleware action. + */ export interface DanetMiddleware { action(ctx: ExecutionContext, next: NextFunction): Promise | unknown; } +/** + * Represents a function that, when called, proceeds to the next middleware in the chain. + * + * @returns A promise that resolves to either void or a Response object. + */ export type NextFunction = () => Promise; export type MiddlewareFunction = ( @@ -20,5 +35,14 @@ export type PossibleMiddlewareType = | MiddlewareFunction; export const isMiddlewareClass = (s: PossibleMiddlewareType) => !!s.prototype; export const middlewareMetadataKey = 'middlewares'; -export const Middleware = (...middlewares: PossibleMiddlewareType[]) => - SetMetadata(middlewareMetadataKey, middlewares); +/** + * A decorator function that attaches middleware to a route handler or controller. + * + * @param {...PossibleMiddlewareType[]} middlewares - A list of middleware functions to be applied. + * @returns {MetadataFunction} - A function that sets the metadata for the middleware. + */ +export function Middleware( + ...middlewares: PossibleMiddlewareType[] +): MetadataFunction { + return SetMetadata(middlewareMetadataKey, middlewares); +} diff --git a/src/router/middleware/executor.ts b/src/router/middleware/executor.ts index 57893c90..de3d25aa 100644 --- a/src/router/middleware/executor.ts +++ b/src/router/middleware/executor.ts @@ -9,15 +9,32 @@ import { MiddlewareFunction, middlewareMetadataKey, NextFunction, - PossibleMiddlewareType, } from './decorator.ts'; import { Constructor } from '../../utils/constructor.ts'; import { globalMiddlewareContainer } from './global-container.ts'; +/** + * The `MiddlewareExecutor` class is responsible for executing a series of middleware functions + * in a specified order. It utilizes an injector to manage dependencies and ensures that all + * relevant middlewares are executed for a given context, controller, and controller method. + */ export class MiddlewareExecutor { - constructor(private injector: Injector) { - } + /** + * Constructs a new `MiddlewareExecutor` instance. + * + * @param injector - The injector used to manage dependencies. + */ + constructor(private injector: Injector) {} + /** + * Executes all relevant middlewares for the given context, controller, and controller method. + * + * @param context - The execution context. + * @param Controller - The controller constructor. + * @param ControllerMethod - The controller method callback. + * @param next - The next function to be called after all middlewares have been executed. + * @returns A promise that resolves to the result of the next function or the middleware chain. + */ async executeAllRelevantMiddlewares( context: ExecutionContext, Controller: ControllerConstructor, diff --git a/src/router/middleware/global-container.ts b/src/router/middleware/global-container.ts index 187a9e06..28f5db0e 100644 --- a/src/router/middleware/global-container.ts +++ b/src/router/middleware/global-container.ts @@ -1,3 +1,12 @@ import { PossibleMiddlewareType } from './decorator.ts'; +/** + * A global container for middleware functions. + * + * This array holds instances of middleware that can be applied globally + * across the application. Each element in the array should conform to the + * `PossibleMiddlewareType` type. + * + * @type {PossibleMiddlewareType[]} + */ export const globalMiddlewareContainer: PossibleMiddlewareType[] = []; diff --git a/src/router/router.ts b/src/router/router.ts index 4e67badd..e45d7e27 100644 --- a/src/router/router.ts +++ b/src/router/router.ts @@ -22,14 +22,30 @@ import { trimSlash } from './utils.ts'; import { MiddlewareExecutor } from './middleware/executor.ts'; import { NextFunction } from './middleware/decorator.ts'; import { resolveMethodParam } from './controller/params/resolver.ts'; -import { SSEMessage } from '../sse/message.ts'; import { SSEEvent } from '../sse/event.ts'; +/** + * Type to define a callback function. + */ // deno-lint-ignore no-explicit-any export type Callback = (...args: any[]) => unknown; +/** + * Type Alias for Hono's Context + */ export type HttpContext = Context; +/** + * Represents Danet's execution context for an HTTP request, extending Hono's HttpContext. + * + * @typedef {Object} ExecutionContext + * @property {string} _id - Unique identifier for the execution context. + * @property {Function} getHandler - Function to retrieve the handler for the current context. + * @property {Constructor} getClass - Function to retrieve the class constructor for the current context. + * @property {WebSocket} [websocket] - Optional WebSocket instance associated with the context. + * @property {any} [websocketMessage] - Optional message received via WebSocket. + * @property {string} [websocketTopic] - Optional topic associated with the WebSocket message. + */ export type ExecutionContext = HttpContext & { _id: string; // deno-lint-ignore ban-types @@ -41,6 +57,12 @@ export type ExecutionContext = HttpContext & { websocketTopic?: string; }; +/** + * The `DanetHTTPRouter` class is responsible for managing HTTP routes and their associated handlers. + * It provides methods to register controllers, set route prefixes, and handle middleware, guards, filters, and responses. + * + * @class DanetHTTPRouter + */ export class DanetHTTPRouter { private logger: Logger = new Logger('Router'); private methodsMap: Map; @@ -68,7 +90,7 @@ export class DanetHTTPRouter { ); } - public createRoute( + private createRoute( handlerName: string | hookName, Controller: Constructor, basePath: string, @@ -134,12 +156,23 @@ export class DanetHTTPRouter { ); } + /** + * Sets the prefix for the router, ensuring that it does not end with a trailing slash. + * + * @param prefix - The prefix string to set for the router. + */ setPrefix(prefix: string) { prefix = prefix.replace(/\/$/, ''); this.router.basePath(prefix); this.prefix = prefix; } + /** + * Registers a controller and its methods as routes. + * + * @param Controller - The controller class to register. + * @param basePath - The base path for the controller's routes. + */ public registerController(Controller: Constructor, basePath: string) { const methods = Object.getOwnPropertyNames(Controller.prototype); this.logger.log( @@ -150,10 +183,10 @@ export class DanetHTTPRouter { }); } - public handleRoute( + private handleRoute( Controller: ControllerConstructor, ControllerMethod: Callback, - ) { + ): (context: HttpContext) => Promise { return async (context: HttpContext) => { (context as ExecutionContext)._id = context.get('_id'); (context as ExecutionContext).getClass = () => Controller; diff --git a/src/router/utils.ts b/src/router/utils.ts index 6111dcf1..2604aaef 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -1,4 +1,4 @@ -export function trimSlash(path: string) { +export function trimSlash(path: string): string { if (path[path.length - 1] === '/') { path = path.substring(0, path.length - 1); } diff --git a/src/router/websocket/router.ts b/src/router/websocket/router.ts index 79019c9c..b6929a08 100644 --- a/src/router/websocket/router.ts +++ b/src/router/websocket/router.ts @@ -29,7 +29,9 @@ export class WebSocketRouter { private guardExecutor: GuardExecutor = new GuardExecutor(injector), private filterExecutor: FilterExecutor = new FilterExecutor(injector), private router: Application, - private middlewareExecutor = new MiddlewareExecutor(injector), + private middlewareExecutor: MiddlewareExecutor = new MiddlewareExecutor( + injector, + ), ) {} public registerController(Controller: Constructor, endpoint: string) { @@ -108,7 +110,8 @@ export class WebSocketRouter { const methodName = methods[0][0] as string; const messageExecutionContext = {} as ExecutionContext; - messageExecutionContext.req = new HonoRequest( + // deno-lint-ignore no-explicit-any + (messageExecutionContext.req as any) = new HonoRequest( fakeRequest, getPath(fakeRequest), // deno-lint-ignore no-explicit-any diff --git a/src/schedule/decorator.ts b/src/schedule/decorator.ts index 941784f3..8f79d472 100644 --- a/src/schedule/decorator.ts +++ b/src/schedule/decorator.ts @@ -6,11 +6,29 @@ import { } from './constants.ts'; import { CronString } from './types.ts'; +/** + * Assigns a cron schedule to a method. The method will be executed according to the provided cron schedule. + * + * @param cron - A string representing the cron schedule. + * @returns A method decorator that sets the metadata key with the provided cron schedule. + */ export const Cron = (cron: CronString): MethodDecorator => SetMetadata(scheduleMetadataKey, { cron }); +/** + * Method will be executed at a specified interval. + * + * @param interval - The interval in milliseconds at which the method should be executed. + * @returns A method decorator that sets the interval metadata. + */ export const Interval = (interval: number): MethodDecorator => SetMetadata(intervalMetadataKey, { interval }); +/** + * Execute Method after X milliseconds. + * + * @param timeout - The timeout duration in milliseconds. + * @returns A method decorator that sets the timeout metadata. + */ export const Timeout = (timeout: number): MethodDecorator => SetMetadata(timeoutMetadataKey, { timeout }); diff --git a/src/schedule/enum.ts b/src/schedule/enum.ts index b7e330ff..df2e54fb 100644 --- a/src/schedule/enum.ts +++ b/src/schedule/enum.ts @@ -1,5 +1,90 @@ // Copyright (c) 2017-2024 Kamil Mysliwiec MIT +/** + * Enum representing various cron expressions for scheduling tasks. + * + * @enum {string} + * @readonly + * @property {string} EVERY_MINUTE - Runs every minute. + * @property {string} EVERY_5_MINUTES - Runs every 5 minutes. + * @property {string} EVERY_10_MINUTES - Runs every 10 minutes. + * @property {string} EVERY_30_MINUTES - Runs every 30 minutes. + * @property {string} EVERY_HOUR - Runs every hour. + * @property {string} EVERY_2_HOURS - Runs every 2 hours. + * @property {string} EVERY_3_HOURS - Runs every 3 hours. + * @property {string} EVERY_4_HOURS - Runs every 4 hours. + * @property {string} EVERY_5_HOURS - Runs every 5 hours. + * @property {string} EVERY_6_HOURS - Runs every 6 hours. + * @property {string} EVERY_7_HOURS - Runs every 7 hours. + * @property {string} EVERY_8_HOURS - Runs every 8 hours. + * @property {string} EVERY_9_HOURS - Runs every 9 hours. + * @property {string} EVERY_10_HOURS - Runs every 10 hours. + * @property {string} EVERY_11_HOURS - Runs every 11 hours. + * @property {string} EVERY_12_HOURS - Runs every 12 hours. + * @property {string} EVERY_DAY_AT_1AM - Runs every day at 1 AM. + * @property {string} EVERY_DAY_AT_2AM - Runs every day at 2 AM. + * @property {string} EVERY_DAY_AT_3AM - Runs every day at 3 AM. + * @property {string} EVERY_DAY_AT_4AM - Runs every day at 4 AM. + * @property {string} EVERY_DAY_AT_5AM - Runs every day at 5 AM. + * @property {string} EVERY_DAY_AT_6AM - Runs every day at 6 AM. + * @property {string} EVERY_DAY_AT_7AM - Runs every day at 7 AM. + * @property {string} EVERY_DAY_AT_8AM - Runs every day at 8 AM. + * @property {string} EVERY_DAY_AT_9AM - Runs every day at 9 AM. + * @property {string} EVERY_DAY_AT_10AM - Runs every day at 10 AM. + * @property {string} EVERY_DAY_AT_11AM - Runs every day at 11 AM. + * @property {string} EVERY_DAY_AT_NOON - Runs every day at noon. + * @property {string} EVERY_DAY_AT_1PM - Runs every day at 1 PM. + * @property {string} EVERY_DAY_AT_2PM - Runs every day at 2 PM. + * @property {string} EVERY_DAY_AT_3PM - Runs every day at 3 PM. + * @property {string} EVERY_DAY_AT_4PM - Runs every day at 4 PM. + * @property {string} EVERY_DAY_AT_5PM - Runs every day at 5 PM. + * @property {string} EVERY_DAY_AT_6PM - Runs every day at 6 PM. + * @property {string} EVERY_DAY_AT_7PM - Runs every day at 7 PM. + * @property {string} EVERY_DAY_AT_8PM - Runs every day at 8 PM. + * @property {string} EVERY_DAY_AT_9PM - Runs every day at 9 PM. + * @property {string} EVERY_DAY_AT_10PM - Runs every day at 10 PM. + * @property {string} EVERY_DAY_AT_11PM - Runs every day at 11 PM. + * @property {string} EVERY_DAY_AT_MIDNIGHT - Runs every day at midnight. + * @property {string} EVERY_WEEK - Runs every week. + * @property {string} EVERY_WEEKDAY - Runs every weekday. + * @property {string} EVERY_WEEKEND - Runs every weekend. + * @property {string} EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT - Runs on the 1st day of every month at midnight. + * @property {string} EVERY_1ST_DAY_OF_MONTH_AT_NOON - Runs on the 1st day of every month at noon. + * @property {string} EVERY_2ND_HOUR - Runs every 2nd hour. + * @property {string} EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM - Runs every 2nd hour from 1 AM through 11 PM. + * @property {string} EVERY_2ND_MONTH - Runs every 2nd month. + * @property {string} EVERY_QUARTER - Runs every quarter. + * @property {string} EVERY_6_MONTHS - Runs every 6 months. + * @property {string} EVERY_YEAR - Runs every year. + * @property {string} EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM - Runs every 30 minutes between 9 AM and 5 PM. + * @property {string} EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM - Runs every 30 minutes between 9 AM and 6 PM. + * @property {string} EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM - Runs every 30 minutes between 10 AM and 7 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_1AM - Runs Monday to Friday at 1 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_2AM - Runs Monday to Friday at 2 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_3AM - Runs Monday to Friday at 3 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_4AM - Runs Monday to Friday at 4 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_5AM - Runs Monday to Friday at 5 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_6AM - Runs Monday to Friday at 6 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_7AM - Runs Monday to Friday at 7 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_8AM - Runs Monday to Friday at 8 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_9AM - Runs Monday to Friday at 9 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_09_30AM - Runs Monday to Friday at 9:30 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_10AM - Runs Monday to Friday at 10 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_11AM - Runs Monday to Friday at 11 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_11_30AM - Runs Monday to Friday at 11:30 AM. + * @property {string} MONDAY_TO_FRIDAY_AT_12PM - Runs Monday to Friday at 12 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_1PM - Runs Monday to Friday at 1 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_2PM - Runs Monday to Friday at 2 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_3PM - Runs Monday to Friday at 3 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_4PM - Runs Monday to Friday at 4 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_5PM - Runs Monday to Friday at 5 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_6PM - Runs Monday to Friday at 6 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_7PM - Runs Monday to Friday at 7 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_8PM - Runs Monday to Friday at 8 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_9PM - Runs Monday to Friday at 9 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_10PM - Runs Monday to Friday at 10 PM. + * @property {string} MONDAY_TO_FRIDAY_AT_11PM - Runs Monday to Friday at 11 PM. + */ export enum CronExpression { EVERY_MINUTE = '*/1 * * * *', EVERY_5_MINUTES = '*/5 * * * *', @@ -82,6 +167,17 @@ export enum CronExpression { MONDAY_TO_FRIDAY_AT_11PM = '0 0 23 * * 1-5', } +/** + * Various time intervals in milliseconds. + * + * @enum {number} + * @readonly + * @property {number} MILISECOND - Represents one millisecond. + * @property {number} SECOND - Represents one second (1000 milliseconds). + * @property {number} MINUTE - Represents one minute (60,000 milliseconds). + * @property {number} HOUR - Represents one hour (3,600,000 milliseconds). + * @property {number} DAY - Represents one day (86,400,000 milliseconds). + */ export enum IntervalExpression { MILISECOND = 1, SECOND = 1000, diff --git a/src/schedule/module.ts b/src/schedule/module.ts index 0cf271d2..e1050526 100644 --- a/src/schedule/module.ts +++ b/src/schedule/module.ts @@ -12,6 +12,8 @@ import { TimeoutMetadataPayload, } from './types.ts'; +/* Use this module if you want to run CRON https://danet.land/techniques/task-scheduling.html */ + @Module({}) export class ScheduleModule implements OnAppBootstrap, OnAppClose { private logger: Logger = new Logger('ScheduleModule'); diff --git a/src/utils/constructor.ts b/src/utils/constructor.ts index 9c9c4bd7..0820abee 100644 --- a/src/utils/constructor.ts +++ b/src/utils/constructor.ts @@ -1,2 +1,5 @@ +/** + * Basic constructor type. + */ // deno-lint-ignore no-explicit-any export type Constructor = new (...args: any[]) => T; diff --git a/validation.ts b/validation.ts index 298b4131..40a90abd 100644 --- a/validation.ts +++ b/validation.ts @@ -1 +1,6 @@ -export * from 'https://deno.land/x/validatte@0.7.2/mod.ts'; +/** + * @module + * Validation module. + * Export everything from https://jsr.io/@danet/validatte + */ +export * from 'validatte';