testing ratelimit

This commit is contained in:
Boki 2025-07-06 17:18:01 -04:00
parent 95b1381480
commit a616c92656
14 changed files with 411 additions and 3 deletions

View file

@ -101,6 +101,8 @@ export class HandlerScanner {
const schedules = HandlerClass.__schedules || [];
const isDisabled = HandlerClass.__disabled || false;
const disabledOperations = HandlerClass.__disabledOperations || [];
const handlerRateLimit = HandlerClass.__handlerRateLimit || null;
const operationRateLimits = HandlerClass.__rateLimits || {};
if (isDisabled) {
this.logger.debug('Skipping disabled handler', { handlerName });
@ -131,13 +133,14 @@ export class HandlerScanner {
return !isDisabled;
});
// Build metadata
// Build metadata with rate limit information
const metadata: HandlerMetadata = {
name: handlerName,
service: this.options.serviceName,
operations: enabledOperations.map((op: any) => ({
name: op.name,
method: op.method,
rateLimit: operationRateLimits[op.method] || null,
})),
schedules: enabledSchedules.map((schedule: any) => ({
operation: schedule.operation,
@ -148,6 +151,7 @@ export class HandlerScanner {
payload: schedule.payload,
batch: schedule.batch,
})),
rateLimit: handlerRateLimit,
};
// Build configuration with operation handlers

View file

@ -5,6 +5,15 @@
import type { JobHandler, ScheduledJob } from '@stock-bot/types';
/**
* Rate limit configuration
*/
export interface RateLimitConfig {
points: number;
duration: number;
blockDuration?: number;
}
/**
* Metadata for a single operation within a handler
*/
@ -12,6 +21,7 @@ export interface OperationMetadata {
name: string;
method: string;
description?: string;
rateLimit?: RateLimitConfig | null;
}
/**
@ -35,6 +45,7 @@ export interface HandlerMetadata {
schedules?: ScheduleMetadata[];
version?: string;
description?: string;
rateLimit?: RateLimitConfig | null;
}
/**

View file

@ -0,0 +1,56 @@
/**
* Rate limit configuration for handlers and operations
*/
export interface RateLimitConfig {
points: number; // Number of allowed requests
duration: number; // Time window in seconds
blockDuration?: number; // How long to block after limit is hit (seconds)
}
/**
* RateLimit decorator - configures rate limiting for handlers or operations
*
* Can be applied to:
* - Classes: Sets default rate limit for all operations in the handler
* - Methods: Sets specific rate limit for individual operations (overrides handler-level)
*
* @param config Rate limit configuration
*
* @example
* // Handler-level rate limit
* @Handler('myHandler')
* @RateLimit({ points: 100, duration: 60 })
* class MyHandler extends BaseHandler {
* // All operations inherit the 100/minute limit
* }
*
* @example
* // Operation-specific rate limit
* @Operation('fetch-data')
* @RateLimit({ points: 10, duration: 60, blockDuration: 30 })
* async fetchData() {
* // This operation is limited to 10/minute
* }
*/
export function RateLimit(config: RateLimitConfig) {
return function (target: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
if (propertyKey) {
// Method decorator - operation-specific rate limit
const constructor = target.constructor;
// Initialize rate limits array if not exists
if (!constructor.__rateLimits) {
constructor.__rateLimits = {};
}
// Store rate limit config for this operation
constructor.__rateLimits[propertyKey] = config;
return descriptor;
} else {
// Class decorator - handler-level rate limit
(target as any).__handlerRateLimit = config;
return target;
}
};
}

View file

@ -7,6 +7,8 @@ export {
ScheduledOperation,
Disabled,
} from './decorators/decorators';
export { RateLimit } from './decorators/rate-limit';
export type { RateLimitConfig } from './decorators/rate-limit';
export { createJobHandler } from './utils/create-job-handler';
// Re-export commonly used types from @stock-bot/types for convenience