@riktajs/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +78 -0
  2. package/dist/core/application.js +16 -3
  3. package/dist/core/container/container.js +36 -4
  4. package/dist/core/container/index.d.ts +1 -0
  5. package/dist/core/container/index.js +1 -0
  6. package/dist/core/container/request-scope.d.ts +16 -0
  7. package/dist/core/container/request-scope.js +53 -0
  8. package/dist/core/discovery.d.ts +2 -1
  9. package/dist/core/discovery.js +22 -10
  10. package/dist/core/exceptions/discovery.exception.d.ts +18 -0
  11. package/dist/core/exceptions/discovery.exception.js +43 -0
  12. package/dist/core/exceptions/index.d.ts +1 -0
  13. package/dist/core/exceptions/index.js +3 -1
  14. package/dist/core/guards/execution-context.d.ts +7 -0
  15. package/dist/core/guards/execution-context.js +8 -0
  16. package/dist/core/index.d.ts +2 -0
  17. package/dist/core/index.js +2 -0
  18. package/dist/core/interceptors/index.d.ts +2 -0
  19. package/dist/core/interceptors/index.js +6 -0
  20. package/dist/core/interceptors/interceptor.interface.d.ts +7 -0
  21. package/dist/core/interceptors/interceptor.interface.js +2 -0
  22. package/dist/core/interceptors/use-interceptors.decorator.d.ts +6 -0
  23. package/dist/core/interceptors/use-interceptors.decorator.js +23 -0
  24. package/dist/core/lifecycle/event-bus.d.ts +9 -4
  25. package/dist/core/lifecycle/event-bus.js +57 -7
  26. package/dist/core/profiler/index.d.ts +2 -0
  27. package/dist/core/profiler/index.js +8 -0
  28. package/dist/core/profiler/performance-profiler.d.ts +42 -0
  29. package/dist/core/profiler/performance-profiler.js +101 -0
  30. package/dist/core/registry.d.ts +0 -2
  31. package/dist/core/registry.js +0 -32
  32. package/dist/core/router/router.d.ts +10 -0
  33. package/dist/core/router/router.js +85 -8
  34. package/dist/core/types.d.ts +7 -3
  35. package/package.json +1 -1
package/README.md CHANGED
@@ -49,6 +49,84 @@ await app.listen();
49
49
  - 📦 Built-in Dependency Injection
50
50
  - ✅ Zod Validation Integration
51
51
 
52
+ ## Provider Scopes
53
+
54
+ Rikta supports three provider scopes:
55
+
56
+ ```typescript
57
+ // Singleton (default) - shared instance
58
+ @Injectable()
59
+ class ConfigService {}
60
+
61
+ // Transient - new instance each time
62
+ @Injectable({ scope: 'transient' })
63
+ class RequestLogger {}
64
+
65
+ // Request - one instance per HTTP request
66
+ @Injectable({ scope: 'request' })
67
+ class RequestContext {}
68
+ ```
69
+
70
+ ## Strict Discovery Mode
71
+
72
+ Enable strict discovery to catch import errors early:
73
+
74
+ ```typescript
75
+ const app = await Rikta.create({
76
+ strictDiscovery: process.env.NODE_ENV !== 'production',
77
+ onDiscoveryError: (file, error) => {
78
+ console.warn(`Failed to import: ${file}`);
79
+ },
80
+ });
81
+ ```
82
+
83
+ ## EventBus
84
+
85
+ The EventBus provides pub/sub for lifecycle events:
86
+
87
+ ```typescript
88
+ import { Injectable, Autowired, EventBus } from '@riktajs/core';
89
+
90
+ @Injectable()
91
+ class MonitorService {
92
+ @Autowired()
93
+ private events!: EventBus;
94
+
95
+ onProviderInit() {
96
+ this.events.on('app:listen', ({ address }) => {
97
+ console.log(`Server at ${address}`);
98
+ });
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Interceptors
104
+
105
+ Interceptors allow you to bind extra logic before/after method execution:
106
+
107
+ ```typescript
108
+ import { Injectable, Interceptor, ExecutionContext, CallHandler, UseInterceptors } from '@riktajs/core';
109
+
110
+ @Injectable()
111
+ class LoggingInterceptor implements Interceptor {
112
+ async intercept(context: ExecutionContext, next: CallHandler): Promise<unknown> {
113
+ const start = Date.now();
114
+ const result = await next.handle();
115
+ console.log(`Request took ${Date.now() - start}ms`);
116
+ return result;
117
+ }
118
+ }
119
+
120
+ @Controller('/api')
121
+ @UseInterceptors(LoggingInterceptor)
122
+ class ApiController {
123
+ @Get('/data')
124
+ getData() {
125
+ return { data: 'example' };
126
+ }
127
+ }
128
+ ```
129
+
52
130
  ## License
53
131
 
54
132
  MIT
@@ -27,7 +27,12 @@ class RiktaFactory {
27
27
  const patterns = Array.isArray(config.autowired) && config.autowired.length > 0
28
28
  ? config.autowired
29
29
  : ['./**'];
30
- discoveredFiles = await (0, discovery_1.discoverModules)(patterns, callerDir);
30
+ discoveredFiles = await (0, discovery_1.discoverModules)({
31
+ patterns,
32
+ cwd: callerDir,
33
+ strict: config.strictDiscovery ?? false,
34
+ onImportError: config.onDiscoveryError,
35
+ });
31
36
  }
32
37
  const app = new RiktaApplicationImpl(config);
33
38
  await app.init(discoveredFiles);
@@ -169,7 +174,7 @@ class RiktaApplicationImpl {
169
174
  for (const { event, methodName } of metadata) {
170
175
  const method = instance[methodName];
171
176
  if (typeof method === 'function') {
172
- this.events.on(event, method.bind(instance));
177
+ this.events.on(event, method.bind(instance), target.name);
173
178
  }
174
179
  }
175
180
  }
@@ -214,19 +219,21 @@ class RiktaApplicationImpl {
214
219
  async close(signal) {
215
220
  await this.events.emit('app:shutdown', { signal });
216
221
  const reversed = [...this.initializedProviders].reverse();
217
- for (const { instance } of reversed) {
222
+ for (const { instance, name } of reversed) {
218
223
  if (this.hasHook(instance, 'onApplicationShutdown')) {
219
224
  await instance.onApplicationShutdown(signal);
220
225
  }
221
226
  if (this.hasHook(instance, 'onProviderDestroy')) {
222
227
  await instance.onProviderDestroy();
223
228
  }
229
+ this.events.removeByOwner(name);
224
230
  }
225
231
  await this.server.close();
226
232
  this.isListening = false;
227
233
  await this.events.emit('app:destroy', {
228
234
  uptime: Date.now() - this.startTime
229
235
  });
236
+ this.events.clear();
230
237
  if (!this.config.silent)
231
238
  console.log('\n👋 Application closed\n');
232
239
  }
@@ -239,4 +246,10 @@ class RiktaApplicationImpl {
239
246
  getContainer() {
240
247
  return this.container;
241
248
  }
249
+ getRouter() {
250
+ return this.router;
251
+ }
252
+ clearRouterCaches() {
253
+ this.router.clearAllCaches();
254
+ }
242
255
  }
@@ -6,6 +6,7 @@ const constants_1 = require("../constants");
6
6
  const injection_token_1 = require("./injection-token");
7
7
  const registry_1 = require("../registry");
8
8
  const abstract_class_utils_1 = require("./abstract-class.utils");
9
+ const request_scope_1 = require("./request-scope");
9
10
  class Container {
10
11
  static instance;
11
12
  singletons = new Map();
@@ -97,14 +98,32 @@ class Container {
97
98
  throw new Error(`Cannot resolve token: ${this.getTokenName(token)}`);
98
99
  }
99
100
  resolveAbstractClass(abstractClass, name) {
100
- if (name) {
101
- return registry_1.registry.resolveImplementation(abstractClass, name);
102
- }
103
101
  const config = this.providers.get(abstractClass);
104
102
  if (config?.provider && 'useClass' in config.provider) {
105
103
  return config.provider.useClass;
106
104
  }
107
- return registry_1.registry.resolveImplementation(abstractClass);
105
+ const implementations = registry_1.registry.getImplementations(abstractClass);
106
+ if (!implementations || implementations.length === 0) {
107
+ return undefined;
108
+ }
109
+ if (name) {
110
+ const named = implementations.find(i => i.name === name);
111
+ if (named) {
112
+ return named.implementation;
113
+ }
114
+ throw new Error(`No implementation named '${name}' found for abstract class ${abstractClass.name}. ` +
115
+ `Available names: ${implementations.filter(i => i.name).map(i => i.name).join(', ') || 'none'}`);
116
+ }
117
+ if (implementations.length === 1) {
118
+ return implementations[0].implementation;
119
+ }
120
+ const primary = implementations.find(i => i.isPrimary);
121
+ if (primary) {
122
+ return primary.implementation;
123
+ }
124
+ const implNames = implementations.map(i => i.implementation.name).join(', ');
125
+ throw new Error(`Multiple implementations found for abstract class ${abstractClass.name}: ${implNames}. ` +
126
+ `Use @Primary() to mark one as the default, or @Named() for qualified injection.`);
108
127
  }
109
128
  resolveProvider(provider, scope) {
110
129
  if (typeof provider === 'function') {
@@ -148,6 +167,16 @@ class Container {
148
167
  if (scope === 'singleton' && this.singletons.has(target)) {
149
168
  return this.singletons.get(target);
150
169
  }
170
+ if (scope === 'request') {
171
+ if (!request_scope_1.requestScopeStorage.isInRequestScope()) {
172
+ throw new Error(`Cannot resolve request-scoped provider '${target.name}' outside of a request context. ` +
173
+ `Request-scoped providers can only be resolved during HTTP request handling.`);
174
+ }
175
+ const existingInstance = request_scope_1.requestScopeStorage.get(target);
176
+ if (existingInstance !== undefined) {
177
+ return existingInstance;
178
+ }
179
+ }
151
180
  this.resolutionStack.add(target);
152
181
  try {
153
182
  const injectMeta = Reflect.getMetadata(constants_1.INJECT_METADATA, target) ?? [];
@@ -188,6 +217,9 @@ class Container {
188
217
  if (scope === 'singleton') {
189
218
  this.singletons.set(target, instance);
190
219
  }
220
+ else if (scope === 'request') {
221
+ request_scope_1.requestScopeStorage.set(target, instance);
222
+ }
191
223
  return instance;
192
224
  }
193
225
  finally {
@@ -2,3 +2,4 @@ export * from './container';
2
2
  export * from './injection-token';
3
3
  export * from './abstract-class.utils';
4
4
  export * from './implements.decorator';
5
+ export * from './request-scope';
@@ -18,3 +18,4 @@ __exportStar(require("./container"), exports);
18
18
  __exportStar(require("./injection-token"), exports);
19
19
  __exportStar(require("./abstract-class.utils"), exports);
20
20
  __exportStar(require("./implements.decorator"), exports);
21
+ __exportStar(require("./request-scope"), exports);
@@ -0,0 +1,16 @@
1
+ import { Token } from './injection-token';
2
+ export declare class RequestScopeStorage {
3
+ private static instance;
4
+ private readonly asyncLocalStorage;
5
+ private constructor();
6
+ static getInstance(): RequestScopeStorage;
7
+ static reset(): void;
8
+ run<T>(fn: () => T): T;
9
+ runAsync<T>(fn: () => Promise<T>): Promise<T>;
10
+ isInRequestScope(): boolean;
11
+ get<T>(token: Token<T>): T | undefined;
12
+ set<T>(token: Token<T>, instance: T): void;
13
+ has(token: Token): boolean;
14
+ getStore(): Map<Token, unknown> | undefined;
15
+ }
16
+ export declare const requestScopeStorage: RequestScopeStorage;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestScopeStorage = exports.RequestScopeStorage = void 0;
4
+ const node_async_hooks_1 = require("node:async_hooks");
5
+ class RequestScopeStorage {
6
+ static instance;
7
+ asyncLocalStorage = new node_async_hooks_1.AsyncLocalStorage();
8
+ constructor() { }
9
+ static getInstance() {
10
+ if (!RequestScopeStorage.instance) {
11
+ RequestScopeStorage.instance = new RequestScopeStorage();
12
+ }
13
+ return RequestScopeStorage.instance;
14
+ }
15
+ static reset() {
16
+ RequestScopeStorage.instance = new RequestScopeStorage();
17
+ }
18
+ run(fn) {
19
+ const store = new Map();
20
+ return this.asyncLocalStorage.run(store, fn);
21
+ }
22
+ async runAsync(fn) {
23
+ const store = new Map();
24
+ return this.asyncLocalStorage.run(store, fn);
25
+ }
26
+ isInRequestScope() {
27
+ return this.asyncLocalStorage.getStore() !== undefined;
28
+ }
29
+ get(token) {
30
+ const store = this.asyncLocalStorage.getStore();
31
+ if (!store) {
32
+ return undefined;
33
+ }
34
+ return store.get(token);
35
+ }
36
+ set(token, instance) {
37
+ const store = this.asyncLocalStorage.getStore();
38
+ if (!store) {
39
+ throw new Error('Cannot set request-scoped instance outside of a request context. ' +
40
+ 'Make sure RequestScopeStorage.run() or runAsync() is being used.');
41
+ }
42
+ store.set(token, instance);
43
+ }
44
+ has(token) {
45
+ const store = this.asyncLocalStorage.getStore();
46
+ return store?.has(token) ?? false;
47
+ }
48
+ getStore() {
49
+ return this.asyncLocalStorage.getStore();
50
+ }
51
+ }
52
+ exports.RequestScopeStorage = RequestScopeStorage;
53
+ exports.requestScopeStorage = RequestScopeStorage.getInstance();
@@ -1,2 +1,3 @@
1
- export declare function discoverModules(patterns?: string[], cwd?: string): Promise<string[]>;
1
+ import { DiscoveryOptions } from './exceptions/discovery.exception';
2
+ export declare function discoverModules(optionsOrPatterns?: DiscoveryOptions | string[], cwd?: string): Promise<string[]>;
2
3
  export declare function getCallerDirectory(): string;
@@ -41,6 +41,7 @@ exports.getCallerDirectory = getCallerDirectory;
41
41
  const fast_glob_1 = __importDefault(require("fast-glob"));
42
42
  const promises_1 = __importDefault(require("fs/promises"));
43
43
  const path_1 = __importDefault(require("path"));
44
+ const discovery_exception_1 = require("./exceptions/discovery.exception");
44
45
  const DEFAULT_IGNORE_PATTERNS = [
45
46
  '**/node_modules/**',
46
47
  '**/dist/**',
@@ -79,8 +80,14 @@ async function containsRiktaDecorators(filePath) {
79
80
  return false;
80
81
  }
81
82
  }
82
- async function discoverModules(patterns = ['./**/*.{ts,js}'], cwd) {
83
- const baseDir = cwd ?? getEntryPointDirectory();
83
+ async function discoverModules(optionsOrPatterns = ['./**/*.{ts,js}'], cwd) {
84
+ const options = Array.isArray(optionsOrPatterns)
85
+ ? { patterns: optionsOrPatterns, cwd }
86
+ : optionsOrPatterns;
87
+ const patterns = options.patterns ?? ['./**/*.{ts,js}'];
88
+ const strict = options.strict ?? false;
89
+ const onImportError = options.onImportError;
90
+ const baseDir = options.cwd ?? cwd ?? getEntryPointDirectory();
84
91
  const absoluteBaseDir = path_1.default.isAbsolute(baseDir)
85
92
  ? baseDir
86
93
  : path_1.default.resolve(process.cwd(), baseDir);
@@ -105,25 +112,30 @@ async function discoverModules(patterns = ['./**/*.{ts,js}'], cwd) {
105
112
  ignore: DEFAULT_IGNORE_PATTERNS,
106
113
  onlyFiles: true,
107
114
  });
108
- const riktaFiles = [];
109
- for (const file of files) {
110
- if (await containsRiktaDecorators(file)) {
111
- riktaFiles.push(file);
112
- }
113
- }
115
+ const decoratorChecks = await Promise.all(files.map(async (file) => ({ file, hasDecorators: await containsRiktaDecorators(file) })));
116
+ const riktaFiles = decoratorChecks
117
+ .filter(({ hasDecorators }) => hasDecorators)
118
+ .map(({ file }) => file);
114
119
  const importedFiles = [];
120
+ const failedImports = [];
115
121
  for (const file of riktaFiles) {
116
122
  try {
117
123
  const importPath = file.replace(/\.ts$/, '');
118
124
  await Promise.resolve(`${importPath}`).then(s => __importStar(require(s)));
119
125
  importedFiles.push(file);
120
126
  }
121
- catch (error) {
127
+ catch (err) {
128
+ const error = err instanceof Error ? err : new Error(String(err));
129
+ onImportError?.(file, error);
122
130
  if (process.env.DEBUG) {
123
- console.warn(`[Rikta] Failed to import ${file}:`, error);
131
+ console.warn(`[Rikta] Failed to import ${file}:`, error.message);
124
132
  }
133
+ failedImports.push({ filePath: file, error });
125
134
  }
126
135
  }
136
+ if (strict && failedImports.length > 0) {
137
+ throw new discovery_exception_1.DiscoveryException(failedImports);
138
+ }
127
139
  return importedFiles;
128
140
  }
129
141
  function getCallerDirectory() {
@@ -0,0 +1,18 @@
1
+ export declare class DiscoveryException extends Error {
2
+ readonly filePath: string;
3
+ readonly originalError: Error;
4
+ readonly failedImports: DiscoveryFailure[];
5
+ constructor(filePath: string, originalError: Error);
6
+ constructor(failures: DiscoveryFailure[]);
7
+ getReport(): string;
8
+ }
9
+ export interface DiscoveryFailure {
10
+ filePath: string;
11
+ error: Error;
12
+ }
13
+ export interface DiscoveryOptions {
14
+ patterns?: string[];
15
+ cwd?: string;
16
+ strict?: boolean;
17
+ onImportError?: (filePath: string, error: Error) => void;
18
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiscoveryException = void 0;
4
+ class DiscoveryException extends Error {
5
+ filePath;
6
+ originalError;
7
+ failedImports;
8
+ constructor(filePathOrFailures, originalError) {
9
+ if (typeof filePathOrFailures === 'string') {
10
+ const filePath = filePathOrFailures;
11
+ const error = originalError;
12
+ super(`[Rikta Discovery] Failed to import module: ${filePath}\n` +
13
+ `Reason: ${error.message}`);
14
+ this.filePath = filePath;
15
+ this.originalError = error;
16
+ this.failedImports = [{ filePath, error }];
17
+ }
18
+ else {
19
+ const failures = filePathOrFailures;
20
+ const fileList = failures.map(f => ` - ${f.filePath}: ${f.error.message}`).join('\n');
21
+ super(`[Rikta Discovery] Failed to import ${failures.length} module(s):\n${fileList}`);
22
+ this.filePath = failures[0]?.filePath ?? '';
23
+ this.originalError = failures[0]?.error ?? new Error('Unknown error');
24
+ this.failedImports = failures;
25
+ }
26
+ this.name = 'DiscoveryException';
27
+ Error.captureStackTrace(this, this.constructor);
28
+ }
29
+ getReport() {
30
+ const lines = ['Discovery Failures:', ''];
31
+ for (const { filePath, error } of this.failedImports) {
32
+ lines.push(`📁 ${filePath}`);
33
+ lines.push(` Error: ${error.message}`);
34
+ if (error.stack) {
35
+ const stackLines = error.stack.split('\n').slice(1, 4);
36
+ lines.push(` Stack: ${stackLines.join('\n ')}`);
37
+ }
38
+ lines.push('');
39
+ }
40
+ return lines.join('\n');
41
+ }
42
+ }
43
+ exports.DiscoveryException = DiscoveryException;
@@ -4,3 +4,4 @@ export { BadRequestException, UnauthorizedException, ForbiddenException, NotFoun
4
4
  export { ExceptionFilter, ExceptionContext, ErrorResponse, GlobalExceptionFilter, GlobalExceptionFilterOptions, createExceptionHandler, } from './exception-filter';
5
5
  export { Catch, CatchMetadata, CATCH_METADATA, getCatchMetadata } from './catch.decorator';
6
6
  export { ConfigProviderAlreadyRegisteredException, ConfigProviderNotFoundException, ConfigProviderInstantiationException, } from './config.exceptions';
7
+ export { DiscoveryException, DiscoveryFailure, DiscoveryOptions, } from './discovery.exception';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConfigProviderInstantiationException = exports.ConfigProviderNotFoundException = exports.ConfigProviderAlreadyRegisteredException = exports.getCatchMetadata = exports.CATCH_METADATA = exports.Catch = exports.createExceptionHandler = exports.GlobalExceptionFilter = exports.GatewayTimeoutException = exports.ServiceUnavailableException = exports.BadGatewayException = exports.NotImplementedException = exports.InternalServerErrorException = exports.TooManyRequestsException = exports.UnprocessableEntityException = exports.UnsupportedMediaTypeException = exports.PayloadTooLargeException = exports.GoneException = exports.ConflictException = exports.RequestTimeoutException = exports.NotAcceptableException = exports.MethodNotAllowedException = exports.NotFoundException = exports.ForbiddenException = exports.UnauthorizedException = exports.BadRequestException = exports.ValidationException = exports.HttpException = void 0;
3
+ exports.DiscoveryException = exports.ConfigProviderInstantiationException = exports.ConfigProviderNotFoundException = exports.ConfigProviderAlreadyRegisteredException = exports.getCatchMetadata = exports.CATCH_METADATA = exports.Catch = exports.createExceptionHandler = exports.GlobalExceptionFilter = exports.GatewayTimeoutException = exports.ServiceUnavailableException = exports.BadGatewayException = exports.NotImplementedException = exports.InternalServerErrorException = exports.TooManyRequestsException = exports.UnprocessableEntityException = exports.UnsupportedMediaTypeException = exports.PayloadTooLargeException = exports.GoneException = exports.ConflictException = exports.RequestTimeoutException = exports.NotAcceptableException = exports.MethodNotAllowedException = exports.NotFoundException = exports.ForbiddenException = exports.UnauthorizedException = exports.BadRequestException = exports.ValidationException = exports.HttpException = void 0;
4
4
  var http_exception_1 = require("./http-exception");
5
5
  Object.defineProperty(exports, "HttpException", { enumerable: true, get: function () { return http_exception_1.HttpException; } });
6
6
  var validation_exception_1 = require("./validation.exception");
@@ -35,3 +35,5 @@ var config_exceptions_1 = require("./config.exceptions");
35
35
  Object.defineProperty(exports, "ConfigProviderAlreadyRegisteredException", { enumerable: true, get: function () { return config_exceptions_1.ConfigProviderAlreadyRegisteredException; } });
36
36
  Object.defineProperty(exports, "ConfigProviderNotFoundException", { enumerable: true, get: function () { return config_exceptions_1.ConfigProviderNotFoundException; } });
37
37
  Object.defineProperty(exports, "ConfigProviderInstantiationException", { enumerable: true, get: function () { return config_exceptions_1.ConfigProviderInstantiationException; } });
38
+ var discovery_exception_1 = require("./discovery.exception");
39
+ Object.defineProperty(exports, "DiscoveryException", { enumerable: true, get: function () { return discovery_exception_1.DiscoveryException; } });
@@ -1,6 +1,11 @@
1
1
  import type { FastifyRequest, FastifyReply } from 'fastify';
2
2
  import type { Constructor } from '../types';
3
+ export interface HttpArgumentsHost {
4
+ getRequest<T = FastifyRequest>(): T;
5
+ getResponse<T = FastifyReply>(): T;
6
+ }
3
7
  export interface ExecutionContext {
8
+ switchToHttp(): HttpArgumentsHost;
4
9
  getRequest<T = FastifyRequest>(): T;
5
10
  getReply<T = FastifyReply>(): T;
6
11
  getClass(): Constructor;
@@ -12,7 +17,9 @@ export declare class ExecutionContextImpl implements ExecutionContext {
12
17
  private readonly reply;
13
18
  private readonly controllerClass;
14
19
  private readonly handlerName;
20
+ private readonly httpHost;
15
21
  constructor(request: FastifyRequest, reply: FastifyReply, controllerClass: Constructor, handlerName: string | symbol);
22
+ switchToHttp(): HttpArgumentsHost;
16
23
  getRequest<T = FastifyRequest>(): T;
17
24
  getReply<T = FastifyReply>(): T;
18
25
  getClass(): Constructor;
@@ -6,11 +6,19 @@ class ExecutionContextImpl {
6
6
  reply;
7
7
  controllerClass;
8
8
  handlerName;
9
+ httpHost;
9
10
  constructor(request, reply, controllerClass, handlerName) {
10
11
  this.request = request;
11
12
  this.reply = reply;
12
13
  this.controllerClass = controllerClass;
13
14
  this.handlerName = handlerName;
15
+ this.httpHost = {
16
+ getRequest: () => this.request,
17
+ getResponse: () => this.reply,
18
+ };
19
+ }
20
+ switchToHttp() {
21
+ return this.httpHost;
14
22
  }
15
23
  getRequest() {
16
24
  return this.request;
@@ -10,8 +10,10 @@ export * from './decorators';
10
10
  export * from './exceptions';
11
11
  export * from './guards';
12
12
  export * from './middleware';
13
+ export * from './interceptors';
13
14
  export * from './config';
14
15
  export * from './metadata';
16
+ export * from './profiler';
15
17
  export { z } from 'zod';
16
18
  export type { ZodType, ZodSchema, ZodError, ZodIssue, infer as ZodInfer } from 'zod';
17
19
  export type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
@@ -27,7 +27,9 @@ __exportStar(require("./decorators"), exports);
27
27
  __exportStar(require("./exceptions"), exports);
28
28
  __exportStar(require("./guards"), exports);
29
29
  __exportStar(require("./middleware"), exports);
30
+ __exportStar(require("./interceptors"), exports);
30
31
  __exportStar(require("./config"), exports);
31
32
  __exportStar(require("./metadata"), exports);
33
+ __exportStar(require("./profiler"), exports);
32
34
  var zod_1 = require("zod");
33
35
  Object.defineProperty(exports, "z", { enumerable: true, get: function () { return zod_1.z; } });
@@ -0,0 +1,2 @@
1
+ export { Interceptor, CallHandler } from './interceptor.interface';
2
+ export { UseInterceptors, getInterceptorsMetadata, InterceptorClass } from './use-interceptors.decorator';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getInterceptorsMetadata = exports.UseInterceptors = void 0;
4
+ var use_interceptors_decorator_1 = require("./use-interceptors.decorator");
5
+ Object.defineProperty(exports, "UseInterceptors", { enumerable: true, get: function () { return use_interceptors_decorator_1.UseInterceptors; } });
6
+ Object.defineProperty(exports, "getInterceptorsMetadata", { enumerable: true, get: function () { return use_interceptors_decorator_1.getInterceptorsMetadata; } });
@@ -0,0 +1,7 @@
1
+ import { ExecutionContext } from '../guards/execution-context';
2
+ export interface Interceptor {
3
+ intercept(context: ExecutionContext, next: CallHandler): Promise<unknown>;
4
+ }
5
+ export interface CallHandler {
6
+ handle(): Promise<unknown>;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import 'reflect-metadata';
2
+ import { Constructor } from '../types';
3
+ import type { Interceptor } from './interceptor.interface';
4
+ export type InterceptorClass = Constructor<Interceptor>;
5
+ export declare function UseInterceptors(...interceptors: InterceptorClass[]): ClassDecorator & MethodDecorator;
6
+ export declare function getInterceptorsMetadata(controllerClass: Constructor, methodName: string | symbol): InterceptorClass[];
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UseInterceptors = UseInterceptors;
4
+ exports.getInterceptorsMetadata = getInterceptorsMetadata;
5
+ require("reflect-metadata");
6
+ const constants_1 = require("../constants");
7
+ function UseInterceptors(...interceptors) {
8
+ return (target, propertyKey, descriptor) => {
9
+ if (propertyKey && descriptor) {
10
+ const existingInterceptors = Reflect.getMetadata(constants_1.INTERCEPTORS_METADATA, target.constructor, propertyKey) ?? [];
11
+ Reflect.defineMetadata(constants_1.INTERCEPTORS_METADATA, [...existingInterceptors, ...interceptors], target.constructor, propertyKey);
12
+ }
13
+ else {
14
+ const existingInterceptors = Reflect.getMetadata(constants_1.INTERCEPTORS_METADATA, target) ?? [];
15
+ Reflect.defineMetadata(constants_1.INTERCEPTORS_METADATA, [...existingInterceptors, ...interceptors], target);
16
+ }
17
+ };
18
+ }
19
+ function getInterceptorsMetadata(controllerClass, methodName) {
20
+ const controllerInterceptors = Reflect.getMetadata(constants_1.INTERCEPTORS_METADATA, controllerClass) ?? [];
21
+ const methodInterceptors = Reflect.getMetadata(constants_1.INTERCEPTORS_METADATA, controllerClass, methodName) ?? [];
22
+ return [...controllerInterceptors, ...methodInterceptors];
23
+ }
@@ -37,16 +37,21 @@ type Listener<E extends LifecycleEvent> = (payload: EventPayload[E]) => void | P
37
37
  export declare class EventBus {
38
38
  private listeners;
39
39
  private onceListeners;
40
- on<E extends LifecycleEvent>(event: E, listener: Listener<E>): () => void;
41
- on<T = unknown>(event: string, listener: (payload: T) => void | Promise<void>): () => void;
42
- once<E extends LifecycleEvent>(event: E, listener: Listener<E>): void;
43
- once<T = unknown>(event: string, listener: (payload: T) => void | Promise<void>): void;
40
+ private ownerUnsubscribers;
41
+ on<E extends LifecycleEvent>(event: E, listener: Listener<E>, owner?: string): () => void;
42
+ on<T = unknown>(event: string, listener: (payload: T) => void | Promise<void>, owner?: string): () => void;
43
+ once<E extends LifecycleEvent>(event: E, listener: Listener<E>, owner?: string): void;
44
+ once<T = unknown>(event: string, listener: (payload: T) => void | Promise<void>, owner?: string): void;
44
45
  emit<E extends LifecycleEvent>(event: E, payload: EventPayload[E]): Promise<void>;
45
46
  emit<T = unknown>(event: string, payload: T): Promise<void>;
46
47
  waitFor<E extends LifecycleEvent>(event: E): Promise<EventPayload[E]>;
47
48
  waitFor<T = unknown>(event: string): Promise<T>;
48
49
  off(event: string): void;
50
+ removeByOwner(owner: string): void;
49
51
  clear(): void;
50
52
  listenerCount(event: string): number;
53
+ totalListenerCount(): number;
54
+ listenerCountByOwner(owner: string): number;
55
+ getOwners(): string[];
51
56
  }
52
57
  export {};
@@ -11,29 +11,53 @@ const injectable_decorator_1 = require("../decorators/injectable.decorator");
11
11
  let EventBus = class EventBus {
12
12
  listeners = new Map();
13
13
  onceListeners = new Map();
14
- on(event, listener) {
14
+ ownerUnsubscribers = new Map();
15
+ on(event, listener, owner) {
15
16
  if (!this.listeners.has(event)) {
16
17
  this.listeners.set(event, new Set());
17
18
  }
18
- this.listeners.get(event).add(listener);
19
- return () => this.listeners.get(event)?.delete(listener);
19
+ const tracked = { listener, owner };
20
+ this.listeners.get(event).add(tracked);
21
+ const unsubscribe = () => {
22
+ this.listeners.get(event)?.delete(tracked);
23
+ if (owner) {
24
+ this.ownerUnsubscribers.get(owner)?.delete(unsubscribe);
25
+ }
26
+ };
27
+ if (owner) {
28
+ if (!this.ownerUnsubscribers.has(owner)) {
29
+ this.ownerUnsubscribers.set(owner, new Set());
30
+ }
31
+ this.ownerUnsubscribers.get(owner).add(unsubscribe);
32
+ }
33
+ return unsubscribe;
20
34
  }
21
- once(event, listener) {
35
+ once(event, listener, owner) {
22
36
  if (!this.onceListeners.has(event)) {
23
37
  this.onceListeners.set(event, new Set());
24
38
  }
25
- this.onceListeners.get(event).add(listener);
39
+ const tracked = { listener, owner };
40
+ this.onceListeners.get(event).add(tracked);
41
+ if (owner) {
42
+ if (!this.ownerUnsubscribers.has(owner)) {
43
+ this.ownerUnsubscribers.set(owner, new Set());
44
+ }
45
+ const unsubscribe = () => {
46
+ this.onceListeners.get(event)?.delete(tracked);
47
+ };
48
+ this.ownerUnsubscribers.get(owner).add(unsubscribe);
49
+ }
26
50
  }
27
51
  async emit(event, payload) {
28
52
  const listeners = this.listeners.get(event);
29
53
  if (listeners) {
30
- for (const listener of listeners) {
54
+ for (const { listener } of listeners) {
31
55
  await listener(payload);
32
56
  }
33
57
  }
34
58
  const onceListeners = this.onceListeners.get(event);
35
59
  if (onceListeners) {
36
- for (const listener of onceListeners) {
60
+ for (const { listener } of onceListeners) {
37
61
  await listener(payload);
38
62
  }
39
63
  this.onceListeners.delete(event);
@@ -46,14 +70,40 @@ let EventBus = class EventBus {
46
70
  this.listeners.delete(event);
47
71
  this.onceListeners.delete(event);
48
72
  }
73
+ removeByOwner(owner) {
74
+ const unsubscribers = this.ownerUnsubscribers.get(owner);
75
+ if (unsubscribers) {
76
+ for (const unsubscribe of unsubscribers) {
77
+ unsubscribe();
78
+ }
79
+ this.ownerUnsubscribers.delete(owner);
80
+ }
81
+ }
49
82
  clear() {
50
83
  this.listeners.clear();
51
84
  this.onceListeners.clear();
85
+ this.ownerUnsubscribers.clear();
52
86
  }
53
87
  listenerCount(event) {
54
88
  return (this.listeners.get(event)?.size ?? 0) +
55
89
  (this.onceListeners.get(event)?.size ?? 0);
56
90
  }
91
+ totalListenerCount() {
92
+ let count = 0;
93
+ for (const listeners of this.listeners.values()) {
94
+ count += listeners.size;
95
+ }
96
+ for (const listeners of this.onceListeners.values()) {
97
+ count += listeners.size;
98
+ }
99
+ return count;
100
+ }
101
+ listenerCountByOwner(owner) {
102
+ return this.ownerUnsubscribers.get(owner)?.size ?? 0;
103
+ }
104
+ getOwners() {
105
+ return [...this.ownerUnsubscribers.keys()];
106
+ }
57
107
  };
58
108
  exports.EventBus = EventBus;
59
109
  exports.EventBus = EventBus = __decorate([
@@ -0,0 +1,2 @@
1
+ export { PerformanceProfiler, profiler, setGlobalProfiler, getGlobalProfiler } from './performance-profiler';
2
+ export type { PerformanceMetric, RouteMetric, BootstrapMetrics } from './performance-profiler';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getGlobalProfiler = exports.setGlobalProfiler = exports.profiler = exports.PerformanceProfiler = void 0;
4
+ var performance_profiler_1 = require("./performance-profiler");
5
+ Object.defineProperty(exports, "PerformanceProfiler", { enumerable: true, get: function () { return performance_profiler_1.PerformanceProfiler; } });
6
+ Object.defineProperty(exports, "profiler", { enumerable: true, get: function () { return performance_profiler_1.profiler; } });
7
+ Object.defineProperty(exports, "setGlobalProfiler", { enumerable: true, get: function () { return performance_profiler_1.setGlobalProfiler; } });
8
+ Object.defineProperty(exports, "getGlobalProfiler", { enumerable: true, get: function () { return performance_profiler_1.getGlobalProfiler; } });
@@ -0,0 +1,42 @@
1
+ import { EventBus } from '../lifecycle/event-bus';
2
+ export interface PerformanceMetric {
3
+ name: string;
4
+ duration: number;
5
+ startTime: number;
6
+ metadata?: Record<string, unknown>;
7
+ }
8
+ export interface RouteMetric extends PerformanceMetric {
9
+ method: string;
10
+ path: string;
11
+ statusCode: number;
12
+ }
13
+ export interface BootstrapMetrics {
14
+ total: number;
15
+ discovery: number;
16
+ containerInit: number;
17
+ routeRegistration: number;
18
+ lifecycleHooks: number;
19
+ }
20
+ export declare class PerformanceProfiler {
21
+ private enabled;
22
+ private eventBus?;
23
+ private listeners;
24
+ private bootstrapMetrics;
25
+ constructor(options?: {
26
+ enabled?: boolean;
27
+ eventBus?: EventBus;
28
+ });
29
+ setEnabled(enabled: boolean): void;
30
+ isEnabled(): boolean;
31
+ startTimer(name: string): (metadata?: Record<string, unknown>) => PerformanceMetric | null;
32
+ measure<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
33
+ recordRouteMetric(metric: RouteMetric): void;
34
+ recordBootstrapPhase(phase: keyof BootstrapMetrics, duration: number): void;
35
+ getBootstrapMetrics(): Partial<BootstrapMetrics>;
36
+ onMetric(listener: (metric: PerformanceMetric) => void): () => void;
37
+ private emitMetric;
38
+ createConsoleTimer(prefix?: string): (name: string) => (metadata?: Record<string, unknown>) => void;
39
+ }
40
+ export declare let profiler: PerformanceProfiler;
41
+ export declare function setGlobalProfiler(newProfiler: PerformanceProfiler): void;
42
+ export declare function getGlobalProfiler(): PerformanceProfiler;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.profiler = exports.PerformanceProfiler = void 0;
4
+ exports.setGlobalProfiler = setGlobalProfiler;
5
+ exports.getGlobalProfiler = getGlobalProfiler;
6
+ class PerformanceProfiler {
7
+ enabled = true;
8
+ eventBus;
9
+ listeners = [];
10
+ bootstrapMetrics = {};
11
+ constructor(options) {
12
+ this.enabled = options?.enabled ?? true;
13
+ this.eventBus = options?.eventBus;
14
+ }
15
+ setEnabled(enabled) {
16
+ this.enabled = enabled;
17
+ }
18
+ isEnabled() {
19
+ return this.enabled;
20
+ }
21
+ startTimer(name) {
22
+ if (!this.enabled) {
23
+ return () => null;
24
+ }
25
+ const startTime = performance.now();
26
+ const startTimestamp = Date.now();
27
+ return (metadata) => {
28
+ const duration = performance.now() - startTime;
29
+ const metric = {
30
+ name,
31
+ duration,
32
+ startTime: startTimestamp,
33
+ metadata,
34
+ };
35
+ this.emitMetric(metric);
36
+ return metric;
37
+ };
38
+ }
39
+ async measure(name, fn) {
40
+ const end = this.startTimer(name);
41
+ try {
42
+ const result = await fn();
43
+ end();
44
+ return result;
45
+ }
46
+ catch (error) {
47
+ end({ error: true });
48
+ throw error;
49
+ }
50
+ }
51
+ recordRouteMetric(metric) {
52
+ if (!this.enabled)
53
+ return;
54
+ this.emitMetric(metric);
55
+ this.eventBus?.emit('profiler:route', metric);
56
+ }
57
+ recordBootstrapPhase(phase, duration) {
58
+ this.bootstrapMetrics[phase] = duration;
59
+ }
60
+ getBootstrapMetrics() {
61
+ return { ...this.bootstrapMetrics };
62
+ }
63
+ onMetric(listener) {
64
+ this.listeners.push(listener);
65
+ return () => {
66
+ const index = this.listeners.indexOf(listener);
67
+ if (index !== -1) {
68
+ this.listeners.splice(index, 1);
69
+ }
70
+ };
71
+ }
72
+ emitMetric(metric) {
73
+ for (const listener of this.listeners) {
74
+ try {
75
+ listener(metric);
76
+ }
77
+ catch {
78
+ }
79
+ }
80
+ this.eventBus?.emit('profiler:metric', metric);
81
+ }
82
+ createConsoleTimer(prefix = '[Rikta]') {
83
+ return (name) => {
84
+ const end = this.startTimer(name);
85
+ return (metadata) => {
86
+ const metric = end(metadata);
87
+ if (metric) {
88
+ console.log(`${prefix} ${metric.name}: ${metric.duration.toFixed(2)}ms`);
89
+ }
90
+ };
91
+ };
92
+ }
93
+ }
94
+ exports.PerformanceProfiler = PerformanceProfiler;
95
+ exports.profiler = new PerformanceProfiler();
96
+ function setGlobalProfiler(newProfiler) {
97
+ exports.profiler = newProfiler;
98
+ }
99
+ function getGlobalProfiler() {
100
+ return exports.profiler;
101
+ }
@@ -34,8 +34,6 @@ declare class Registry {
34
34
  setImplementationName(abstractClass: Constructor, implementation: Constructor, name: string): void;
35
35
  setPrimaryImplementation(abstractClass: Constructor, implementation: Constructor): void;
36
36
  getImplementations(abstractClass: Constructor): AbstractImplementation[];
37
- resolveImplementation(abstractClass: Constructor, name?: string): Constructor | undefined;
38
- getNamedImplementation(abstractClass: Constructor, name: string): Constructor | undefined;
39
37
  hasImplementation(abstractClass: Constructor): boolean;
40
38
  clearAbstractImplementations(): void;
41
39
  }
@@ -100,38 +100,6 @@ class Registry {
100
100
  getImplementations(abstractClass) {
101
101
  return this.abstractImplementations.get(abstractClass) ?? [];
102
102
  }
103
- resolveImplementation(abstractClass, name) {
104
- const implementations = this.abstractImplementations.get(abstractClass);
105
- if (!implementations || implementations.length === 0) {
106
- return undefined;
107
- }
108
- if (name) {
109
- const named = implementations.find(i => i.name === name);
110
- if (named) {
111
- return named.implementation;
112
- }
113
- throw new Error(`No implementation named '${name}' found for abstract class ${abstractClass.name}. ` +
114
- `Available names: ${implementations.filter(i => i.name).map(i => i.name).join(', ') || 'none'}`);
115
- }
116
- if (implementations.length === 1) {
117
- return implementations[0].implementation;
118
- }
119
- const primary = implementations.find(i => i.isPrimary);
120
- if (primary) {
121
- return primary.implementation;
122
- }
123
- const implNames = implementations.map(i => i.implementation.name).join(', ');
124
- throw new Error(`Multiple implementations found for abstract class ${abstractClass.name}: ${implNames}. ` +
125
- `Use @Primary() to mark one as the default, or @Named() for qualified injection.`);
126
- }
127
- getNamedImplementation(abstractClass, name) {
128
- const implementations = this.abstractImplementations.get(abstractClass);
129
- if (!implementations) {
130
- return undefined;
131
- }
132
- const named = implementations.find(i => i.name === name);
133
- return named?.implementation;
134
- }
135
103
  hasImplementation(abstractClass) {
136
104
  const implementations = this.abstractImplementations.get(abstractClass);
137
105
  return !!implementations && implementations.length > 0;
@@ -8,7 +8,15 @@ export declare class Router {
8
8
  private readonly globalPrefix;
9
9
  private readonly guardCache;
10
10
  private readonly middlewareCache;
11
+ private readonly interceptorCache;
11
12
  constructor(server: FastifyInstance, container: Container, globalPrefix?: string);
13
+ clearGuardCache(): void;
14
+ clearMiddlewareCache(): void;
15
+ clearInterceptorCache(): void;
16
+ clearAllCaches(): void;
17
+ getGuardCacheSize(): number;
18
+ getMiddlewareCacheSize(): number;
19
+ getInterceptorCacheSize(): number;
12
20
  registerController(controllerClass: Constructor, silent?: boolean): void;
13
21
  private registerRoute;
14
22
  private compileParamResolvers;
@@ -18,5 +26,7 @@ export declare class Router {
18
26
  private executeGuardsOptimized;
19
27
  private resolveMiddlewareInstances;
20
28
  private executeMiddlewareChain;
29
+ private resolveInterceptorInstances;
30
+ private executeInterceptorChain;
21
31
  private buildPath;
22
32
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Router = void 0;
4
4
  require("reflect-metadata");
5
+ const request_scope_1 = require("../container/request-scope");
5
6
  const constants_1 = require("../constants");
6
7
  const create_param_decorator_1 = require("../decorators/create-param-decorator");
7
8
  const validation_exception_1 = require("../exceptions/validation.exception");
@@ -9,17 +10,42 @@ const exceptions_1 = require("../exceptions/exceptions");
9
10
  const execution_context_1 = require("../guards/execution-context");
10
11
  const use_guards_decorator_1 = require("../guards/use-guards.decorator");
11
12
  const use_middleware_decorator_1 = require("../middleware/use-middleware.decorator");
13
+ const use_interceptors_decorator_1 = require("../interceptors/use-interceptors.decorator");
12
14
  class Router {
13
15
  server;
14
16
  container;
15
17
  globalPrefix;
16
18
  guardCache = new Map();
17
19
  middlewareCache = new Map();
20
+ interceptorCache = new Map();
18
21
  constructor(server, container, globalPrefix = '') {
19
22
  this.server = server;
20
23
  this.container = container;
21
24
  this.globalPrefix = globalPrefix;
22
25
  }
26
+ clearGuardCache() {
27
+ this.guardCache.clear();
28
+ }
29
+ clearMiddlewareCache() {
30
+ this.middlewareCache.clear();
31
+ }
32
+ clearInterceptorCache() {
33
+ this.interceptorCache.clear();
34
+ }
35
+ clearAllCaches() {
36
+ this.guardCache.clear();
37
+ this.middlewareCache.clear();
38
+ this.interceptorCache.clear();
39
+ }
40
+ getGuardCacheSize() {
41
+ return this.guardCache.size;
42
+ }
43
+ getMiddlewareCacheSize() {
44
+ return this.middlewareCache.size;
45
+ }
46
+ getInterceptorCacheSize() {
47
+ return this.interceptorCache.size;
48
+ }
23
49
  registerController(controllerClass, silent = false) {
24
50
  const controllerMeta = Reflect.getMetadata(constants_1.CONTROLLER_METADATA, controllerClass);
25
51
  if (!controllerMeta) {
@@ -43,6 +69,7 @@ class Router {
43
69
  const statusCode = Reflect.getMetadata(constants_1.HTTP_CODE_METADATA, controllerClass, route.handlerName);
44
70
  const guards = (0, use_guards_decorator_1.getGuardsMetadata)(controllerClass, route.handlerName);
45
71
  const middleware = (0, use_middleware_decorator_1.getMiddlewareMetadata)(controllerClass, route.handlerName);
72
+ const interceptors = (0, use_interceptors_decorator_1.getInterceptorsMetadata)(controllerClass, route.handlerName);
46
73
  const compiledResolvers = this.compileParamResolvers(paramsMeta);
47
74
  const hasBuiltinParams = compiledResolvers.length > 0;
48
75
  const hasCustomParams = customParamsMeta.length > 0;
@@ -56,11 +83,13 @@ class Router {
56
83
  const hasGuards = guardInstances.length > 0;
57
84
  const middlewareInstances = this.resolveMiddlewareInstances(middleware);
58
85
  const hasMiddleware = middlewareInstances.length > 0;
59
- const needsContext = hasGuards || hasCustomParams;
86
+ const interceptorInstances = this.resolveInterceptorInstances(interceptors);
87
+ const hasInterceptors = interceptorInstances.length > 0;
88
+ const needsContext = hasGuards || hasCustomParams || hasInterceptors;
60
89
  const createContext = needsContext
61
90
  ? (req, rep) => new execution_context_1.ExecutionContextImpl(req, rep, controllerClass, route.handlerName)
62
91
  : null;
63
- const routeHandler = async (request, reply) => {
92
+ const executeHandler = async (request, reply) => {
64
93
  const executionContext = createContext ? createContext(request, reply) : null;
65
94
  if (hasGuards && executionContext) {
66
95
  await this.executeGuardsOptimized(guardInstances, executionContext);
@@ -68,17 +97,30 @@ class Router {
68
97
  if (hasMiddleware) {
69
98
  await this.executeMiddlewareChain(middlewareInstances, request, reply);
70
99
  }
71
- let args;
72
- if (hasParams) {
73
- args = await this.resolveAllParams(compiledResolvers, customParamsMeta, maxParamIndex, request, reply, executionContext);
100
+ const coreHandler = async () => {
101
+ let args;
102
+ if (hasParams) {
103
+ args = await this.resolveAllParams(compiledResolvers, customParamsMeta, maxParamIndex, request, reply, executionContext);
104
+ }
105
+ const result = args
106
+ ? await handler.apply(controllerInstance, args)
107
+ : await handler.call(controllerInstance);
108
+ return result;
109
+ };
110
+ let result;
111
+ if (hasInterceptors && executionContext) {
112
+ result = await this.executeInterceptorChain(interceptorInstances, executionContext, coreHandler);
113
+ }
114
+ else {
115
+ result = await coreHandler();
74
116
  }
75
- const result = args
76
- ? await handler.apply(controllerInstance, args)
77
- : await handler.call(controllerInstance);
78
117
  if (statusCode)
79
118
  reply.status(statusCode);
80
119
  return result;
81
120
  };
121
+ const routeHandler = async (request, reply) => {
122
+ return request_scope_1.requestScopeStorage.runAsync(() => executeHandler(request, reply));
123
+ };
82
124
  const method = route.method.toLowerCase();
83
125
  this.server[method](fullPath, routeHandler);
84
126
  if (!silent)
@@ -217,6 +259,41 @@ class Router {
217
259
  };
218
260
  await next();
219
261
  }
262
+ resolveInterceptorInstances(interceptors) {
263
+ return interceptors.map(InterceptorClass => {
264
+ let instance = this.interceptorCache.get(InterceptorClass);
265
+ if (instance)
266
+ return instance;
267
+ try {
268
+ instance = this.container.resolve(InterceptorClass);
269
+ }
270
+ catch (error) {
271
+ throw new Error(`Failed to resolve interceptor ${InterceptorClass.name}. ` +
272
+ `Make sure it is decorated with @Injectable(). ` +
273
+ `Original error: ${error instanceof Error ? error.message : String(error)}`);
274
+ }
275
+ if (typeof instance.intercept !== 'function') {
276
+ throw new Error(`${InterceptorClass.name} does not implement Interceptor interface. ` +
277
+ `The interceptor must have an intercept(context, next) method.`);
278
+ }
279
+ this.interceptorCache.set(InterceptorClass, instance);
280
+ return instance;
281
+ });
282
+ }
283
+ async executeInterceptorChain(interceptorInstances, context, coreHandler) {
284
+ let handler = coreHandler;
285
+ for (let i = interceptorInstances.length - 1; i >= 0; i--) {
286
+ const interceptor = interceptorInstances[i];
287
+ const nextHandler = handler;
288
+ handler = () => {
289
+ const callHandler = {
290
+ handle: () => nextHandler()
291
+ };
292
+ return interceptor.intercept(context, callHandler);
293
+ };
294
+ }
295
+ return handler();
296
+ }
220
297
  buildPath(controllerPrefix, routePath) {
221
298
  const parts = [this.globalPrefix, controllerPrefix, routePath]
222
299
  .filter(Boolean)
@@ -37,6 +37,8 @@ export interface RiktaConfig {
37
37
  silent?: boolean;
38
38
  prefix?: string;
39
39
  autowired?: string[] | false;
40
+ strictDiscovery?: boolean;
41
+ onDiscoveryError?: (filePath: string, error: Error) => void;
40
42
  controllers?: Constructor[];
41
43
  providers?: Constructor[];
42
44
  exceptionFilter?: ExceptionFilterConfig;
@@ -46,13 +48,15 @@ export type MiddlewareFunction = (request: FastifyRequest, reply: FastifyReply,
46
48
  export interface Guard {
47
49
  canActivate(context: RouteContext): boolean | Promise<boolean>;
48
50
  }
49
- export interface Interceptor {
50
- intercept(context: RouteContext, next: () => Promise<unknown>): Promise<unknown>;
51
- }
51
+ import type { Router } from './router/router';
52
+ import type { EventBus } from './lifecycle/event-bus';
52
53
  export interface RiktaApplication {
53
54
  readonly server: FastifyInstance;
54
55
  listen(): Promise<string>;
55
56
  close(): Promise<void>;
56
57
  getUrl(): string;
57
58
  getContainer(): Container;
59
+ getRouter(): Router;
60
+ getEventBus(): EventBus;
61
+ clearRouterCaches(): void;
58
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riktajs/core",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "A fast and modern TypeScript backend framework with zero-config autowiring, powered by Fastify",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",