@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.
- package/README.md +78 -0
- package/dist/core/application.js +16 -3
- package/dist/core/container/container.js +36 -4
- package/dist/core/container/index.d.ts +1 -0
- package/dist/core/container/index.js +1 -0
- package/dist/core/container/request-scope.d.ts +16 -0
- package/dist/core/container/request-scope.js +53 -0
- package/dist/core/discovery.d.ts +2 -1
- package/dist/core/discovery.js +22 -10
- package/dist/core/exceptions/discovery.exception.d.ts +18 -0
- package/dist/core/exceptions/discovery.exception.js +43 -0
- package/dist/core/exceptions/index.d.ts +1 -0
- package/dist/core/exceptions/index.js +3 -1
- package/dist/core/guards/execution-context.d.ts +7 -0
- package/dist/core/guards/execution-context.js +8 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/interceptors/index.d.ts +2 -0
- package/dist/core/interceptors/index.js +6 -0
- package/dist/core/interceptors/interceptor.interface.d.ts +7 -0
- package/dist/core/interceptors/interceptor.interface.js +2 -0
- package/dist/core/interceptors/use-interceptors.decorator.d.ts +6 -0
- package/dist/core/interceptors/use-interceptors.decorator.js +23 -0
- package/dist/core/lifecycle/event-bus.d.ts +9 -4
- package/dist/core/lifecycle/event-bus.js +57 -7
- package/dist/core/profiler/index.d.ts +2 -0
- package/dist/core/profiler/index.js +8 -0
- package/dist/core/profiler/performance-profiler.d.ts +42 -0
- package/dist/core/profiler/performance-profiler.js +101 -0
- package/dist/core/registry.d.ts +0 -2
- package/dist/core/registry.js +0 -32
- package/dist/core/router/router.d.ts +10 -0
- package/dist/core/router/router.js +85 -8
- package/dist/core/types.d.ts +7 -3
- 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
|
package/dist/core/application.js
CHANGED
|
@@ -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)(
|
|
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
|
-
|
|
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 {
|
|
@@ -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();
|
package/dist/core/discovery.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
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;
|
package/dist/core/discovery.js
CHANGED
|
@@ -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(
|
|
83
|
-
const
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 (
|
|
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;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/core/index.js
CHANGED
|
@@ -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,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,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
|
-
|
|
41
|
-
on<
|
|
42
|
-
|
|
43
|
-
once<
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|
package/dist/core/registry.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/core/registry.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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)
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
50
|
-
|
|
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