@riktajs/core 0.7.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/abstract-class.utils.d.ts +10 -0
- package/dist/core/container/abstract-class.utils.js +54 -0
- package/dist/core/container/container.d.ts +3 -0
- package/dist/core/container/container.js +78 -7
- package/dist/core/container/implements.decorator.d.ts +6 -0
- package/dist/core/container/implements.decorator.js +45 -0
- package/dist/core/container/index.d.ts +3 -0
- package/dist/core/container/index.js +3 -0
- package/dist/core/container/request-scope.d.ts +16 -0
- package/dist/core/container/request-scope.js +53 -0
- package/dist/core/decorators/autowired.decorator.d.ts +2 -1
- package/dist/core/decorators/autowired.decorator.js +3 -1
- 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 +12 -0
- package/dist/core/registry.js +45 -0
- package/dist/core/router/router.d.ts +10 -0
- package/dist/core/router/router.js +85 -8
- package/dist/core/types.d.ts +9 -3
- package/package.json +2 -2
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
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Constructor } from '../types';
|
|
2
|
+
export declare const IMPLEMENTS_METADATA: unique symbol;
|
|
3
|
+
export declare const PRIMARY_METADATA: unique symbol;
|
|
4
|
+
export declare function isAbstractClass(target: unknown): target is Constructor;
|
|
5
|
+
export declare function extendsFrom(derived: Constructor, base: Constructor): boolean;
|
|
6
|
+
export declare function getImplementedAbstract(target: Constructor): Constructor | undefined;
|
|
7
|
+
export declare function isPrimaryImplementation(target: Constructor): boolean;
|
|
8
|
+
export declare function setImplementsMetadata(target: Constructor, abstractClass: Constructor): void;
|
|
9
|
+
export declare function setPrimaryMetadata(target: Constructor): void;
|
|
10
|
+
export declare function markAsAbstract(target: Constructor): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PRIMARY_METADATA = exports.IMPLEMENTS_METADATA = void 0;
|
|
4
|
+
exports.isAbstractClass = isAbstractClass;
|
|
5
|
+
exports.extendsFrom = extendsFrom;
|
|
6
|
+
exports.getImplementedAbstract = getImplementedAbstract;
|
|
7
|
+
exports.isPrimaryImplementation = isPrimaryImplementation;
|
|
8
|
+
exports.setImplementsMetadata = setImplementsMetadata;
|
|
9
|
+
exports.setPrimaryMetadata = setPrimaryMetadata;
|
|
10
|
+
exports.markAsAbstract = markAsAbstract;
|
|
11
|
+
exports.IMPLEMENTS_METADATA = Symbol('rikta:implements');
|
|
12
|
+
exports.PRIMARY_METADATA = Symbol('rikta:primary');
|
|
13
|
+
function isAbstractClass(target) {
|
|
14
|
+
if (typeof target !== 'function') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const isMarkedAbstract = Reflect.getMetadata(exports.IMPLEMENTS_METADATA, target) === undefined
|
|
18
|
+
&& Reflect.hasMetadata('abstract:class', target);
|
|
19
|
+
if (isMarkedAbstract) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return Reflect.getMetadata('abstract:class', target) === true;
|
|
23
|
+
}
|
|
24
|
+
function extendsFrom(derived, base) {
|
|
25
|
+
if (!derived || !base) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (derived === base) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
let current = Object.getPrototypeOf(derived);
|
|
32
|
+
while (current && current !== Function.prototype) {
|
|
33
|
+
if (current === base) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
current = Object.getPrototypeOf(current);
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function getImplementedAbstract(target) {
|
|
41
|
+
return Reflect.getMetadata(exports.IMPLEMENTS_METADATA, target);
|
|
42
|
+
}
|
|
43
|
+
function isPrimaryImplementation(target) {
|
|
44
|
+
return Reflect.getMetadata(exports.PRIMARY_METADATA, target) === true;
|
|
45
|
+
}
|
|
46
|
+
function setImplementsMetadata(target, abstractClass) {
|
|
47
|
+
Reflect.defineMetadata(exports.IMPLEMENTS_METADATA, abstractClass, target);
|
|
48
|
+
}
|
|
49
|
+
function setPrimaryMetadata(target) {
|
|
50
|
+
Reflect.defineMetadata(exports.PRIMARY_METADATA, true, target);
|
|
51
|
+
}
|
|
52
|
+
function markAsAbstract(target) {
|
|
53
|
+
Reflect.defineMetadata('abstract:class', true, target);
|
|
54
|
+
}
|
|
@@ -17,9 +17,12 @@ export declare class Container {
|
|
|
17
17
|
resolve<T>(token: Token<T>): T;
|
|
18
18
|
resolveOptional<T>(token: Token<T>): T | undefined;
|
|
19
19
|
private resolveToken;
|
|
20
|
+
private resolveAbstractClass;
|
|
20
21
|
private resolveProvider;
|
|
21
22
|
private resolveClass;
|
|
22
23
|
private injectProperties;
|
|
24
|
+
private resolveWithName;
|
|
25
|
+
private resolveWithNameOptional;
|
|
23
26
|
registerInstance<T>(target: Constructor<T>, instance: T): void;
|
|
24
27
|
getProviders(): Token[];
|
|
25
28
|
clearSingletons(): void;
|
|
@@ -4,6 +4,9 @@ exports.container = exports.Container = void 0;
|
|
|
4
4
|
require("reflect-metadata");
|
|
5
5
|
const constants_1 = require("../constants");
|
|
6
6
|
const injection_token_1 = require("./injection-token");
|
|
7
|
+
const registry_1 = require("../registry");
|
|
8
|
+
const abstract_class_utils_1 = require("./abstract-class.utils");
|
|
9
|
+
const request_scope_1 = require("./request-scope");
|
|
7
10
|
class Container {
|
|
8
11
|
static instance;
|
|
9
12
|
singletons = new Map();
|
|
@@ -70,12 +73,20 @@ class Container {
|
|
|
70
73
|
}
|
|
71
74
|
const config = this.providers.get(token);
|
|
72
75
|
if (!config) {
|
|
76
|
+
if (typeof token === 'function') {
|
|
77
|
+
const implementation = this.resolveAbstractClass(token);
|
|
78
|
+
if (implementation) {
|
|
79
|
+
const instance = this.resolveClass(implementation);
|
|
80
|
+
this.singletons.set(token, instance);
|
|
81
|
+
return instance;
|
|
82
|
+
}
|
|
83
|
+
if (!(0, abstract_class_utils_1.isAbstractClass)(token)) {
|
|
84
|
+
return this.resolveClass(token);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
73
87
|
if (optional) {
|
|
74
88
|
return undefined;
|
|
75
89
|
}
|
|
76
|
-
if (typeof token === 'function') {
|
|
77
|
-
return this.resolveClass(token);
|
|
78
|
-
}
|
|
79
90
|
throw new Error(`No provider found for token: ${this.getTokenName(token)}`);
|
|
80
91
|
}
|
|
81
92
|
if (config.provider && typeof config.provider !== 'function') {
|
|
@@ -86,6 +97,34 @@ class Container {
|
|
|
86
97
|
}
|
|
87
98
|
throw new Error(`Cannot resolve token: ${this.getTokenName(token)}`);
|
|
88
99
|
}
|
|
100
|
+
resolveAbstractClass(abstractClass, name) {
|
|
101
|
+
const config = this.providers.get(abstractClass);
|
|
102
|
+
if (config?.provider && 'useClass' in config.provider) {
|
|
103
|
+
return config.provider.useClass;
|
|
104
|
+
}
|
|
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.`);
|
|
127
|
+
}
|
|
89
128
|
resolveProvider(provider, scope) {
|
|
90
129
|
if (typeof provider === 'function') {
|
|
91
130
|
return this.resolveClass(provider);
|
|
@@ -128,6 +167,16 @@ class Container {
|
|
|
128
167
|
if (scope === 'singleton' && this.singletons.has(target)) {
|
|
129
168
|
return this.singletons.get(target);
|
|
130
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
|
+
}
|
|
131
180
|
this.resolutionStack.add(target);
|
|
132
181
|
try {
|
|
133
182
|
const injectMeta = Reflect.getMetadata(constants_1.INJECT_METADATA, target) ?? [];
|
|
@@ -142,6 +191,7 @@ class Container {
|
|
|
142
191
|
const injectOverride = injectMeta.find(m => m.index === index);
|
|
143
192
|
const token = injectOverride?.token ?? paramType;
|
|
144
193
|
const isOptional = injectOverride?.optional ?? false;
|
|
194
|
+
const name = injectOverride?.name;
|
|
145
195
|
if (!token) {
|
|
146
196
|
if (isOptional) {
|
|
147
197
|
dependencies.push(undefined);
|
|
@@ -159,14 +209,17 @@ class Container {
|
|
|
159
209
|
`Use @Autowired(token) decorator.`);
|
|
160
210
|
}
|
|
161
211
|
dependencies.push(isOptional
|
|
162
|
-
? this.
|
|
163
|
-
: this.
|
|
212
|
+
? this.resolveWithNameOptional(token, name)
|
|
213
|
+
: this.resolveWithName(token, name));
|
|
164
214
|
}
|
|
165
215
|
const instance = new target(...dependencies);
|
|
166
216
|
this.injectProperties(target, instance);
|
|
167
217
|
if (scope === 'singleton') {
|
|
168
218
|
this.singletons.set(target, instance);
|
|
169
219
|
}
|
|
220
|
+
else if (scope === 'request') {
|
|
221
|
+
request_scope_1.requestScopeStorage.set(target, instance);
|
|
222
|
+
}
|
|
170
223
|
return instance;
|
|
171
224
|
}
|
|
172
225
|
finally {
|
|
@@ -181,8 +234,8 @@ class Container {
|
|
|
181
234
|
const isOptional = meta.optional ?? false;
|
|
182
235
|
try {
|
|
183
236
|
const value = isOptional
|
|
184
|
-
? this.
|
|
185
|
-
: this.
|
|
237
|
+
? this.resolveWithNameOptional(meta.token, meta.name)
|
|
238
|
+
: this.resolveWithName(meta.token, meta.name);
|
|
186
239
|
instance[meta.propertyKey] = value;
|
|
187
240
|
}
|
|
188
241
|
catch (error) {
|
|
@@ -192,6 +245,24 @@ class Container {
|
|
|
192
245
|
}
|
|
193
246
|
}
|
|
194
247
|
}
|
|
248
|
+
resolveWithName(token, name) {
|
|
249
|
+
if (name && typeof token === 'function') {
|
|
250
|
+
const implementation = this.resolveAbstractClass(token, name);
|
|
251
|
+
if (implementation) {
|
|
252
|
+
return this.resolveClass(implementation);
|
|
253
|
+
}
|
|
254
|
+
throw new Error(`No implementation named '${name}' found for ${this.getTokenName(token)}`);
|
|
255
|
+
}
|
|
256
|
+
return this.resolve(token);
|
|
257
|
+
}
|
|
258
|
+
resolveWithNameOptional(token, name) {
|
|
259
|
+
try {
|
|
260
|
+
return this.resolveWithName(token, name);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
195
266
|
registerInstance(target, instance) {
|
|
196
267
|
this.providers.set(target, { scope: 'singleton' });
|
|
197
268
|
this.singletons.set(target, instance);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { AnyConstructor } from '../types';
|
|
3
|
+
export declare function Implements<T extends AnyConstructor>(abstractClass: T): ClassDecorator;
|
|
4
|
+
export declare function Primary(): ClassDecorator;
|
|
5
|
+
export declare function Named(name: string): ClassDecorator;
|
|
6
|
+
export declare function AbstractClass(): ClassDecorator;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Implements = Implements;
|
|
4
|
+
exports.Primary = Primary;
|
|
5
|
+
exports.Named = Named;
|
|
6
|
+
exports.AbstractClass = AbstractClass;
|
|
7
|
+
require("reflect-metadata");
|
|
8
|
+
const abstract_class_utils_1 = require("./abstract-class.utils");
|
|
9
|
+
const registry_1 = require("../registry");
|
|
10
|
+
function Implements(abstractClass) {
|
|
11
|
+
return (target) => {
|
|
12
|
+
(0, abstract_class_utils_1.setImplementsMetadata)(target, abstractClass);
|
|
13
|
+
(0, abstract_class_utils_1.markAsAbstract)(abstractClass);
|
|
14
|
+
const name = Reflect.getMetadata('rikta:named', target);
|
|
15
|
+
registry_1.registry.registerAbstractImplementation(abstractClass, target, name);
|
|
16
|
+
const isPrimary = Reflect.getMetadata('rikta:is-primary', target) === true;
|
|
17
|
+
if (isPrimary) {
|
|
18
|
+
registry_1.registry.setPrimaryImplementation(abstractClass, target);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function Primary() {
|
|
23
|
+
return (target) => {
|
|
24
|
+
(0, abstract_class_utils_1.setPrimaryMetadata)(target);
|
|
25
|
+
const abstractClass = Reflect.getMetadata(abstract_class_utils_1.IMPLEMENTS_METADATA, target);
|
|
26
|
+
if (abstractClass) {
|
|
27
|
+
registry_1.registry.setPrimaryImplementation(abstractClass, target);
|
|
28
|
+
}
|
|
29
|
+
Reflect.defineMetadata('rikta:is-primary', true, target);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function Named(name) {
|
|
33
|
+
return (target) => {
|
|
34
|
+
Reflect.defineMetadata('rikta:named', name, target);
|
|
35
|
+
const abstractClass = Reflect.getMetadata(abstract_class_utils_1.IMPLEMENTS_METADATA, target);
|
|
36
|
+
if (abstractClass) {
|
|
37
|
+
registry_1.registry.setImplementationName(abstractClass, target, name);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function AbstractClass() {
|
|
42
|
+
return (target) => {
|
|
43
|
+
(0, abstract_class_utils_1.markAsAbstract)(target);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -16,3 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./container"), exports);
|
|
18
18
|
__exportStar(require("./injection-token"), exports);
|
|
19
|
+
__exportStar(require("./abstract-class.utils"), exports);
|
|
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();
|
|
@@ -5,6 +5,7 @@ export interface AutowiredMetadata {
|
|
|
5
5
|
index?: number;
|
|
6
6
|
propertyKey?: string;
|
|
7
7
|
optional?: boolean;
|
|
8
|
+
name?: string;
|
|
8
9
|
}
|
|
9
|
-
export declare function Autowired(token?: Token): ParameterDecorator & PropertyDecorator;
|
|
10
|
+
export declare function Autowired(token?: Token, name?: string): ParameterDecorator & PropertyDecorator;
|
|
10
11
|
export declare function Optional(): ParameterDecorator & PropertyDecorator;
|
|
@@ -4,7 +4,7 @@ exports.Autowired = Autowired;
|
|
|
4
4
|
exports.Optional = Optional;
|
|
5
5
|
require("reflect-metadata");
|
|
6
6
|
const constants_1 = require("../constants");
|
|
7
|
-
function Autowired(token) {
|
|
7
|
+
function Autowired(token, name) {
|
|
8
8
|
return (target, propertyKey, parameterIndex) => {
|
|
9
9
|
if (typeof parameterIndex === 'number') {
|
|
10
10
|
const paramTypes = Reflect.getMetadata('design:paramtypes', target) ?? [];
|
|
@@ -18,6 +18,7 @@ function Autowired(token) {
|
|
|
18
18
|
existingInjects.push({
|
|
19
19
|
token: resolvedToken,
|
|
20
20
|
index: parameterIndex,
|
|
21
|
+
name,
|
|
21
22
|
});
|
|
22
23
|
Reflect.defineMetadata(constants_1.INJECT_METADATA, existingInjects, target);
|
|
23
24
|
}
|
|
@@ -32,6 +33,7 @@ function Autowired(token) {
|
|
|
32
33
|
existingAutowires.push({
|
|
33
34
|
token: resolvedToken,
|
|
34
35
|
propertyKey: String(propertyKey),
|
|
36
|
+
name,
|
|
35
37
|
});
|
|
36
38
|
Reflect.defineMetadata(constants_1.AUTOWIRED_METADATA, existingAutowires, target.constructor);
|
|
37
39
|
}
|
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
|
+
}
|