initial
This commit is contained in:
141
ts/decorators/decorators.metadata.ts
Normal file
141
ts/decorators/decorators.metadata.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Metadata storage for decorators using Symbol.metadata (TC39 Stage 3)
|
||||
* Falls back to WeakMap for environments without Symbol.metadata
|
||||
*/
|
||||
|
||||
import type { IControllerMetadata, IRouteMetadata } from './decorators.types.js';
|
||||
import type { IInterceptOptions } from '../core/smartserve.interfaces.js';
|
||||
|
||||
// Symbol for storing metadata when Symbol.metadata is not available
|
||||
const CONTROLLER_METADATA = Symbol('smartserve:controller');
|
||||
|
||||
/**
|
||||
* Get or create controller metadata for a class
|
||||
* Uses symbol property on the class itself for metadata storage
|
||||
*/
|
||||
export function getControllerMetadata(target: any): IControllerMetadata {
|
||||
// Store metadata on the class itself using symbol
|
||||
if (!target[CONTROLLER_METADATA]) {
|
||||
target[CONTROLLER_METADATA] = createEmptyMetadata();
|
||||
}
|
||||
return target[CONTROLLER_METADATA];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controller metadata from prototype (for instance lookup)
|
||||
*/
|
||||
export function getMetadataFromInstance(instance: any): IControllerMetadata | undefined {
|
||||
const constructor = instance.constructor;
|
||||
return getControllerMetadata(constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set base path for a controller
|
||||
*/
|
||||
export function setBasePath(target: any, path: string): void {
|
||||
const metadata = getControllerMetadata(target);
|
||||
metadata.basePath = normalizePath(path);
|
||||
metadata.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a route to a controller
|
||||
*/
|
||||
export function addRoute(
|
||||
target: any,
|
||||
methodName: string | symbol,
|
||||
route: Omit<IRouteMetadata, 'methodName' | 'interceptors'>
|
||||
): void {
|
||||
const metadata = getControllerMetadata(target.constructor);
|
||||
|
||||
// Get existing route or create new one
|
||||
let existingRoute = metadata.routes.get(methodName);
|
||||
if (!existingRoute) {
|
||||
existingRoute = {
|
||||
...route,
|
||||
methodName,
|
||||
interceptors: [],
|
||||
};
|
||||
metadata.routes.set(methodName, existingRoute);
|
||||
} else {
|
||||
// Update existing route
|
||||
existingRoute.method = route.method;
|
||||
existingRoute.path = route.path;
|
||||
existingRoute.options = { ...existingRoute.options, ...route.options };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add class-level interceptor
|
||||
*/
|
||||
export function addClassInterceptor(target: any, interceptor: IInterceptOptions): void {
|
||||
const metadata = getControllerMetadata(target);
|
||||
metadata.classInterceptors.push(interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add method-level interceptor
|
||||
*/
|
||||
export function addMethodInterceptor(
|
||||
target: any,
|
||||
methodName: string | symbol,
|
||||
interceptor: IInterceptOptions
|
||||
): void {
|
||||
const metadata = getControllerMetadata(target.constructor);
|
||||
|
||||
let route = metadata.routes.get(methodName);
|
||||
if (!route) {
|
||||
// Create placeholder route (will be completed by @Get/@Post/etc.)
|
||||
route = {
|
||||
method: 'GET',
|
||||
path: '',
|
||||
methodName,
|
||||
interceptors: [],
|
||||
options: {},
|
||||
};
|
||||
metadata.routes.set(methodName, route);
|
||||
}
|
||||
|
||||
route.interceptors.push(interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create empty metadata object
|
||||
*/
|
||||
function createEmptyMetadata(): IControllerMetadata {
|
||||
return {
|
||||
basePath: '',
|
||||
classInterceptors: [],
|
||||
routes: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path to ensure consistent format
|
||||
*/
|
||||
export function normalizePath(path: string): string {
|
||||
if (!path) return '';
|
||||
|
||||
// Ensure leading slash
|
||||
let normalized = path.startsWith('/') ? path : `/${path}`;
|
||||
|
||||
// Remove trailing slash (unless it's just '/')
|
||||
if (normalized.length > 1 && normalized.endsWith('/')) {
|
||||
normalized = normalized.slice(0, -1);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine base path and route path
|
||||
*/
|
||||
export function combinePaths(basePath: string, routePath: string): string {
|
||||
const base = normalizePath(basePath);
|
||||
const route = normalizePath(routePath);
|
||||
|
||||
if (!base) return route || '/';
|
||||
if (!route) return base;
|
||||
|
||||
return `${base}${route}`;
|
||||
}
|
||||
Reference in New Issue
Block a user