Skip to content

Commit

Permalink
refactor: use Symbol.metadata for storing Handlers and Hooks configur…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
L2jLiga committed Oct 20, 2023
1 parent b3246f2 commit dc9a084
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 273 deletions.
11 changes: 5 additions & 6 deletions lib/decorators/error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@

import type { IErrorHandler } from '../interfaces/index.js';
import type { Constructable } from '../plugins/index.js';
import { ERROR_HANDLERS } from '../symbols/index.js';
import { ensureErrorHandlers } from './helpers/class-properties.js';
import { getErrorHandlerContainer } from '../plugins/index.js';

export function ErrorHandler(): PropertyDecorator;
export function ErrorHandler(code: string): PropertyDecorator;
export function ErrorHandler<T extends Error>(configuration: Constructable<T>): PropertyDecorator;
export function ErrorHandler<T extends ErrorConstructor>(configuration: T): PropertyDecorator;
export function ErrorHandler<T extends ErrorConstructor>(parameter?: T | string | null | undefined): PropertyDecorator {
return function ({ constructor }, handlerName) {
ensureErrorHandlers(constructor);
const container = getErrorHandlerContainer(constructor);

if (parameter == null) {
constructor[ERROR_HANDLERS].push(handlerFactory(() => true, handlerName));
container.push(handlerFactory(() => true, handlerName));
} else if (typeof parameter === 'string') {
constructor[ERROR_HANDLERS].push(handlerFactory((error?: ErrorWithCode) => error?.code === parameter, handlerName));
container.push(handlerFactory((error?: ErrorWithCode) => error?.code === parameter, handlerName));
} else {
constructor[ERROR_HANDLERS].push(handlerFactory((error?: Error) => error instanceof parameter, handlerName));
container.push(handlerFactory((error?: Error) => error instanceof parameter, handlerName));
}
};
}
Expand Down
50 changes: 50 additions & 0 deletions lib/decorators/helpers/class-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { IErrorHandler, IHandler, IHook } from '../../interfaces/index.js';
import { ERROR_HANDLERS, HANDLERS, HOOKS, METADATA } from '../../symbols/index.js';
import { Container } from './container.js';

// TODO: Support for ES Decorators
export function getHandlersContainer<T extends object>(target: T): Container<IHandler> {
return getContainer(target, HANDLERS);
}

export function getHooksContainer<T extends object>(target: T): Container<IHook> {
return getContainer(target, HOOKS);
}

export function getErrorHandlerContainer<T extends object>(target: T): Container<IErrorHandler> {
return getContainer(target, ERROR_HANDLERS);
}

/// Internal

function getContainer<T extends object, Type>(target: T, name: symbol): Container<Type> {
const metadata = getMetadata(target);
const base = (target as new () => unknown).prototype.__proto__?.constructor?.[METADATA]?.[name] as Container<Type>;

if (!Object.prototype.hasOwnProperty.call(metadata, name)) {
Reflect.defineProperty(metadata, name, {
value: new Container(base),
enumerable: false,
configurable: false,
writable: false,
});
}

return metadata[name as keyof typeof metadata] as Container<Type>;
}

function getMetadata<T extends object>(target: T): Record<symbol, Container<unknown>> {
ensureMetadata(target);
return target[METADATA];
}

function ensureMetadata<T extends object>(target: T): asserts target is T & { [METADATA]: Record<symbol, Container<unknown>> } {
if (!Object.prototype.hasOwnProperty.call(target, METADATA)) {
Reflect.defineProperty(target, METADATA, {
value: {},
enumerable: false,
configurable: false,
writable: false,
});
}
}
135 changes: 0 additions & 135 deletions lib/decorators/helpers/class-properties.spec.ts

This file was deleted.

57 changes: 0 additions & 57 deletions lib/decorators/helpers/class-properties.ts

This file was deleted.

38 changes: 19 additions & 19 deletions lib/decorators/helpers/request-decorators.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@

import type { FastifyInstance, FastifyRequest, RouteShorthandOptions } from 'fastify';
import { HttpMethods, RequestHandler, RouteConfig } from '../../interfaces/index.js';
import { Constructable, hooksRegistry, Registrable } from '../../plugins/index.js';
import { CREATOR, ERROR_HANDLERS, HANDLERS, HOOKS } from '../../symbols/index.js';
import { Constructable, getErrorHandlerContainer, getHandlersContainer, getHooksContainer, hooksRegistry, Registrable } from '../../plugins/index.js';
import { CREATOR } from '../../symbols/index.js';
import { transformAndWait } from '../../utils/transform-and-wait.js';
import { ensureHandlers, hasErrorHandlers, hasHooks } from './class-properties.js';
import { createErrorsHandler } from './create-errors-handler.js';
import { ensureRegistrable } from './ensure-registrable.js';

Expand Down Expand Up @@ -49,24 +48,25 @@ export function requestDecoratorsFactory(method: HttpMethods) {

ensureRegistrable<typeof target, RequestHandler>(target);

// TODO: call hooks
target[CREATOR].register = (instance: FastifyInstance) => {
if (hasHooks(target)) {
for (const hook of target[HOOKS]) {
const hookFn = (request: FastifyRequest, ...rest: unknown[]) => {
return getTarget(target, request, ...rest).then((t) =>
(t as unknown as Record<string, (...args: unknown[]) => unknown>)[hook.handlerName as string](request, ...rest),
);
};
for (const hook of getHooksContainer(target)) {
const hookFn = (request: FastifyRequest, ...rest: unknown[]) => {
return getTarget(target, request, ...rest).then((t) =>
(t as unknown as Record<string, (...args: unknown[]) => unknown>)[hook.handlerName as string](request, ...rest),
);
};

const option = config.options[hook.name as 'onRequest'];
if (option == null) config.options[hook.name as 'onRequest'] = hookFn;
else if (Array.isArray(option)) option.push(hookFn);
else config.options[hook.name as 'onRequest'] = [option as (...args: unknown[]) => void, hookFn];
}
const option = config.options[hook.name as 'onRequest'];
if (option == null) config.options[hook.name as 'onRequest'] = hookFn;
else if (Array.isArray(option)) option.push(hookFn);
else config.options[hook.name as 'onRequest'] = [option as (...args: unknown[]) => void, hookFn];
}
if (hasErrorHandlers(target)) {

const errorHandlers = getErrorHandlerContainer(target);
if (errorHandlers.length > 0) {
config.options.errorHandler = async (error, request, ...rest) => {
const errorsHandler = createErrorsHandler(target[ERROR_HANDLERS], (await getTarget(target, request, ...rest)) as never);
const errorsHandler = createErrorsHandler(errorHandlers, (await getTarget(target, request, ...rest)) as never);

return errorsHandler(error, request, ...rest);
};
Expand All @@ -85,9 +85,9 @@ export function controllerMethodDecoratorsFactory(
{ constructor }: Constructable,
propKey: string | symbol,
): void {
ensureHandlers(constructor);
const container = getHandlersContainer(constructor);

constructor[HANDLERS].push({
container.push({
url: config.url,
method,
options: config.options,
Expand Down
7 changes: 3 additions & 4 deletions lib/decorators/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
* found in the LICENSE file at https://github.com/L2jLiga/fastify-decorators/blob/master/LICENSE
*/

import { HOOKS } from '../symbols/index.js';
import { ensureHooks } from './helpers/class-properties.js';
import { getHooksContainer } from './helpers/class-metadata.js';

/**
* Creates handler which listen various hooks
*/
export function Hook(name: string): PropertyDecorator {
return ({ constructor }, handlerName) => {
ensureHooks(constructor);
const container = getHooksContainer(constructor);

constructor[HOOKS].push({
container.push({
name,
handlerName,
});
Expand Down
Loading

0 comments on commit dc9a084

Please sign in to comment.