fragment-ts 1.0.30 → 1.0.32

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 (52) hide show
  1. package/API.md +752 -0
  2. package/DOCS.md +555 -0
  3. package/README.md +76 -339
  4. package/USAGE.md +309 -1306
  5. package/dist/cli/commands/init.command.js +1 -1
  6. package/dist/core/container/di-container.d.ts.map +1 -1
  7. package/dist/core/container/di-container.js +62 -106
  8. package/dist/core/container/di-container.js.map +1 -1
  9. package/dist/core/decorators/exception-filter.decorator.d.ts +5 -0
  10. package/dist/core/decorators/exception-filter.decorator.d.ts.map +1 -0
  11. package/dist/core/decorators/exception-filter.decorator.js +23 -0
  12. package/dist/core/decorators/exception-filter.decorator.js.map +1 -0
  13. package/dist/core/decorators/guard.decorator.d.ts +5 -0
  14. package/dist/core/decorators/guard.decorator.d.ts.map +1 -0
  15. package/dist/core/decorators/guard.decorator.js +23 -0
  16. package/dist/core/decorators/guard.decorator.js.map +1 -0
  17. package/dist/core/decorators/injection.decorators.d.ts.map +1 -1
  18. package/dist/core/decorators/injection.decorators.js +5 -0
  19. package/dist/core/decorators/injection.decorators.js.map +1 -1
  20. package/dist/core/decorators/interceptor.decorator.d.ts +5 -0
  21. package/dist/core/decorators/interceptor.decorator.d.ts.map +1 -0
  22. package/dist/core/decorators/interceptor.decorator.js +23 -0
  23. package/dist/core/decorators/interceptor.decorator.js.map +1 -0
  24. package/dist/core/decorators/middleware.decorator.d.ts +8 -0
  25. package/dist/core/decorators/middleware.decorator.d.ts.map +1 -0
  26. package/dist/core/decorators/middleware.decorator.js +28 -0
  27. package/dist/core/decorators/middleware.decorator.js.map +1 -0
  28. package/dist/core/metadata/metadata-keys.d.ts +1 -0
  29. package/dist/core/metadata/metadata-keys.d.ts.map +1 -1
  30. package/dist/core/metadata/metadata-keys.js +1 -0
  31. package/dist/core/metadata/metadata-keys.js.map +1 -1
  32. package/dist/core/metadata/metadata-storage.d.ts +20 -4
  33. package/dist/core/metadata/metadata-storage.d.ts.map +1 -1
  34. package/dist/core/metadata/metadata-storage.js +94 -14
  35. package/dist/core/metadata/metadata-storage.js.map +1 -1
  36. package/dist/web/application.d.ts.map +1 -1
  37. package/dist/web/application.js +79 -10
  38. package/dist/web/application.js.map +1 -1
  39. package/dist/web/interfaces.d.ts +5 -1
  40. package/dist/web/interfaces.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/cli/commands/init.command.ts +1 -1
  43. package/src/core/container/di-container.ts +95 -177
  44. package/src/core/decorators/exception-filter.decorator.ts +20 -0
  45. package/src/core/decorators/guard.decorator.ts +20 -0
  46. package/src/core/decorators/injection.decorators.ts +5 -0
  47. package/src/core/decorators/interceptor.decorator.ts +20 -0
  48. package/src/core/decorators/middleware.decorator.ts +25 -0
  49. package/src/core/metadata/metadata-keys.ts +1 -0
  50. package/src/core/metadata/metadata-storage.ts +128 -24
  51. package/src/web/application.ts +207 -53
  52. package/src/web/interfaces.ts +11 -2
@@ -23,63 +23,52 @@ export class DIContainer {
23
23
 
24
24
  register(token: any, instance?: any, factory?: () => any): void {
25
25
  this.registered.add(token);
26
- console.log(
27
- ` ✓ Registered token: ${typeof token === "function" ? token.name : token}`,
28
- );
29
26
 
30
27
  if (instance) {
31
28
  this.singletons.set(token, instance);
32
- console.log(` ➤ Singleton instance created`);
33
29
  } else if (factory) {
34
30
  this.factories.set(token, factory);
35
- console.log(` ➤ Factory registered`);
36
31
  }
37
32
  }
38
33
 
39
34
  resolve<T>(token: any): T {
40
- if (typeof token === "function" && !this.registered.has(token)) {
41
- console.log(
42
- ` ℹ️ Token ${token.name} not explicitly registered, attempting to create instance`,
43
- );
35
+ // Check if already resolved
36
+ if (this.singletons.has(token)) {
37
+ return this.singletons.get(token);
44
38
  }
45
39
 
40
+ // Handle circular dependencies
46
41
  if (this.constructing.has(token)) {
47
42
  throw new Error(
48
43
  `Circular dependency detected for ${token.name || token}`,
49
44
  );
50
45
  }
51
46
 
52
- // Check singletons first
53
- if (this.singletons.has(token)) {
54
- console.log(
55
- ` ✓ Resolved from singleton cache: ${typeof token === "function" ? token.name : token}`,
56
- );
57
- return this.singletons.get(token);
58
- }
59
-
60
- // Check factories
47
+ // Use factory if available
61
48
  if (this.factories.has(token)) {
62
- console.log(
63
- ` 🏭 Creating instance from factory: ${typeof token === "function" ? token.name : token}`,
64
- );
65
49
  const factory = this.factories.get(token)!;
66
50
  const instance = factory();
67
-
68
51
  const scope =
69
52
  Reflect.getMetadata(METADATA_KEYS.SCOPE, token) || "singleton";
53
+
70
54
  if (scope === "singleton") {
71
55
  this.singletons.set(token, instance);
72
- console.log(` ➤ Cached as singleton`);
73
56
  }
74
-
75
57
  return instance;
76
58
  }
77
59
 
78
- // Create new instance for class tokens
60
+ // Create instance for class tokens
79
61
  if (typeof token === "function") {
80
- console.log(` 🔨 Creating new instance: ${token.name}`);
81
- this.constructing.add(token);
62
+ if (
63
+ !this.registered.has(token) &&
64
+ !Reflect.getMetadata(METADATA_KEYS.INJECTABLE, token)
65
+ ) {
66
+ console.warn(
67
+ `⚠️ ${token.name} is not registered as injectable. Consider adding @Injectable(), @Service(), @Controller(), or @Repository()`,
68
+ );
69
+ }
82
70
 
71
+ this.constructing.add(token);
83
72
  try {
84
73
  const instance = this.createInstance(token);
85
74
  const scope =
@@ -87,35 +76,25 @@ export class DIContainer {
87
76
 
88
77
  if (scope === "singleton") {
89
78
  this.singletons.set(token, instance);
90
- console.log(` ➤ Registered as singleton`);
91
79
  }
92
80
 
93
81
  this.constructing.delete(token);
94
82
  return instance;
95
83
  } catch (error) {
96
84
  this.constructing.delete(token);
97
- console.error(
98
- ` ❌ Failed to create instance of ${token.name}: ${error}`,
99
- );
100
85
  throw error;
101
86
  }
102
87
  }
103
88
 
104
- throw new Error(
105
- `Cannot resolve dependency: ${typeof token === "function" ? token.name : token}`,
106
- );
89
+ throw new Error(`Cannot resolve dependency: ${token}`);
107
90
  }
108
91
 
109
92
  private createInstance(target: any): any {
110
93
  const className = target.name;
111
- console.log(` 🏗️ Constructing instance of ${className}`);
94
+ console.log(` 🔨 Creating instance of ${className}`);
112
95
 
113
- // Get constructor parameter types
96
+ // Get constructor parameters
114
97
  const paramTypes = Reflect.getMetadata("design:paramtypes", target) || [];
115
- console.log(
116
- ` 📋 Constructor parameters for ${className}: ${paramTypes.length}`,
117
- );
118
-
119
98
  const params = paramTypes.map((type: any, index: number) => {
120
99
  if (!type || type === Object) {
121
100
  console.warn(
@@ -123,20 +102,19 @@ export class DIContainer {
123
102
  );
124
103
  return undefined;
125
104
  }
126
-
127
105
  console.log(
128
- ` 🔌 Resolving constructor dependency [${index}]: ${type.name || type}`,
106
+ ` 📦 Resolving constructor dependency [${index}]: ${type.name || type}`,
129
107
  );
130
108
  return this.resolve(type);
131
109
  });
132
110
 
133
- console.log(` 🚀 Instantiating ${className}`);
111
+ // Create instance
134
112
  const instance = new target(...params);
135
113
 
136
- // Inject properties
114
+ // CRITICAL FIX: Inject properties using metadata storage
137
115
  this.injectProperties(instance);
138
116
 
139
- // Call @PostConstruct
117
+ // Call @PostConstruct method if exists
140
118
  const postConstructMethod = Reflect.getMetadata(
141
119
  METADATA_KEYS.POST_CONSTRUCT,
142
120
  target,
@@ -162,81 +140,77 @@ export class DIContainer {
162
140
  console.log(` 🔍 Scanning for property injections on ${className}`);
163
141
 
164
142
  // Get all property injections from metadata storage
165
- const propertyInjections =
166
- this.metadataStorage.getPropertyInjections(constructor);
143
+ const injections = this.metadataStorage.getPropertyInjections(constructor);
167
144
 
168
- if (propertyInjections.length === 0) {
145
+ if (injections.length === 0) {
169
146
  console.log(` ℹ️ No property injections registered for ${className}`);
170
147
  return;
171
148
  }
172
149
 
173
150
  console.log(
174
- ` 📌 Found ${propertyInjections.length} property injection(s) for ${className}`,
151
+ ` 📌 Found ${injections.length} property injection(s) for ${className}`,
175
152
  );
176
153
 
177
- for (const { propertyKey, injections } of propertyInjections) {
178
- console.log(` ➤ Property: ${propertyKey}`);
179
-
180
- for (const injection of injections) {
181
- console.log(` • Injection type: ${injection.type}`);
154
+ for (const injection of injections) {
155
+ const { propertyKey, type, metadata } = injection;
156
+ console.log(` ➤ Property: ${propertyKey} [${type}]`);
182
157
 
183
- try {
184
- switch (injection.type) {
185
- case "autowired":
186
- this.injectAutowiredProperty(
187
- instance,
188
- propertyKey,
189
- injection.metadata?.type,
190
- className,
191
- );
192
- break;
193
- case "inject":
194
- this.injectTokenProperty(
195
- instance,
196
- propertyKey,
197
- injection.metadata?.token,
198
- className,
199
- );
200
- break;
201
- case "repository":
202
- this.injectRepositoryProperty(
203
- instance,
204
- propertyKey,
205
- injection.metadata?.entity,
206
- className,
207
- );
208
- break;
209
- case "value":
210
- this.injectValueProperty(
211
- instance,
212
- propertyKey,
213
- injection.metadata?.expression,
214
- className,
215
- );
216
- break;
217
- case "lazy":
218
- this.injectLazyProperty(instance, propertyKey, className);
219
- break;
220
- }
221
- } catch (error) {
222
- const isOptional = Reflect.getMetadata(
223
- METADATA_KEYS.OPTIONAL,
224
- constructor.prototype,
225
- propertyKey,
226
- );
227
-
228
- if (!isOptional) {
229
- console.error(
230
- ` ❌ Failed to inject ${propertyKey} in ${className}: ${error}`,
158
+ try {
159
+ switch (type) {
160
+ case "autowired":
161
+ this.injectAutowiredProperty(
162
+ instance,
163
+ propertyKey,
164
+ metadata?.type,
165
+ className,
166
+ );
167
+ break;
168
+ case "inject":
169
+ this.injectTokenProperty(
170
+ instance,
171
+ propertyKey,
172
+ metadata?.token,
173
+ className,
231
174
  );
232
- throw error;
233
- }
175
+ break;
176
+ case "repository":
177
+ this.injectRepositoryProperty(
178
+ instance,
179
+ propertyKey,
180
+ metadata?.entity,
181
+ className,
182
+ );
183
+ break;
184
+ case "value":
185
+ this.injectValueProperty(
186
+ instance,
187
+ propertyKey,
188
+ metadata?.expression,
189
+ className,
190
+ );
191
+ break;
192
+ case "lazy":
193
+ this.injectLazyProperty(instance, propertyKey, className);
194
+ break;
195
+ }
196
+ } catch (error) {
197
+ const isOptional = Reflect.getMetadata(
198
+ METADATA_KEYS.OPTIONAL,
199
+ constructor.prototype,
200
+ propertyKey,
201
+ );
234
202
 
235
- console.warn(
236
- ` ⚠️ Optional dependency ${propertyKey} not available for ${className}`,
203
+ if (!isOptional) {
204
+ console.error(
205
+ ` ❌ Failed to inject ${propertyKey} in ${className}: ${error}`,
237
206
  );
238
- instance[propertyKey] = undefined;
207
+ throw error;
239
208
  }
209
+
210
+ console.warn(
211
+ ` ⚠️ Optional dependency ${propertyKey} not available for ${className}`,
212
+ );
213
+ instance[propertyKey] = undefined;
240
214
  }
241
215
  }
242
216
  }
@@ -248,21 +222,16 @@ export class DIContainer {
248
222
  className: string,
249
223
  ): void {
250
224
  if (!type) {
251
- const designType = Reflect.getMetadata(
252
- "design:type",
253
- instance,
254
- propertyKey,
255
- );
256
- if (designType && designType !== Object) {
257
- type = designType;
258
- console.warn(
259
- ` ⚠️ Using design:type metadata for ${className}.${propertyKey} as fallback`,
260
- );
261
- } else {
225
+ // Fallback to design:type metadata
226
+ type = Reflect.getMetadata("design:type", instance, propertyKey);
227
+ if (!type || type === Object) {
262
228
  throw new Error(
263
229
  `Type information not available for ${className}.${propertyKey}`,
264
230
  );
265
231
  }
232
+ console.warn(
233
+ ` ⚠️ Using design:type metadata as fallback for ${className}.${propertyKey}`,
234
+ );
266
235
  }
267
236
 
268
237
  console.log(` 💉 @Autowired: ${className}.${propertyKey} -> ${type.name}`);
@@ -276,12 +245,6 @@ export class DIContainer {
276
245
  token: any,
277
246
  className: string,
278
247
  ): void {
279
- if (!token) {
280
- throw new Error(
281
- `Token not provided for @Inject on ${className}.${propertyKey}`,
282
- );
283
- }
284
-
285
248
  const tokenName = typeof token === "string" ? token : token.name;
286
249
  console.log(` 💉 @Inject: ${className}.${propertyKey} -> ${tokenName}`);
287
250
  instance[propertyKey] = this.resolve(token);
@@ -294,12 +257,6 @@ export class DIContainer {
294
257
  entity: any,
295
258
  className: string,
296
259
  ): void {
297
- if (!entity) {
298
- throw new Error(
299
- `Entity not provided for @InjectRepository on ${className}.${propertyKey}`,
300
- );
301
- }
302
-
303
260
  console.log(
304
261
  ` 💾 @InjectRepository: ${className}.${propertyKey} -> Repository<${entity.name}>`,
305
262
  );
@@ -315,12 +272,6 @@ export class DIContainer {
315
272
  expression: string,
316
273
  className: string,
317
274
  ): void {
318
- if (!expression) {
319
- throw new Error(
320
- `Expression not provided for @Value on ${className}.${propertyKey}`,
321
- );
322
- }
323
-
324
275
  console.log(` 🔧 @Value: ${className}.${propertyKey} = ${expression}`);
325
276
  instance[propertyKey] = this.resolveValue(expression);
326
277
  console.log(` ✅ Set value for ${className}.${propertyKey}`);
@@ -331,7 +282,6 @@ export class DIContainer {
331
282
  propertyKey: string,
332
283
  className: string,
333
284
  ): void {
334
- // Get the type for later resolution
335
285
  const type = Reflect.getMetadata("design:type", instance, propertyKey);
336
286
  if (!type) {
337
287
  throw new Error(
@@ -374,33 +324,17 @@ export class DIContainer {
374
324
  key = defaultMatch[1].trim();
375
325
  defaultValue = defaultMatch[2].trim();
376
326
 
377
- // Try to parse default value as JSON or number if possible
378
- try {
379
- defaultValue = JSON.parse(defaultValue);
380
- } catch {
381
- // Try number conversion
382
- const numValue = parseFloat(defaultValue);
383
- if (!isNaN(numValue) && defaultValue.trim() === numValue.toString()) {
384
- defaultValue = numValue;
385
- }
386
- // Try boolean conversion
387
- else if (defaultValue.toLowerCase() === "true") {
388
- defaultValue = true;
389
- } else if (defaultValue.toLowerCase() === "false") {
390
- defaultValue = false;
391
- }
392
- }
327
+ // Try to parse as boolean
328
+ if (defaultValue.toLowerCase() === "true") defaultValue = true;
329
+ else if (defaultValue.toLowerCase() === "false") defaultValue = false;
330
+ // Try to parse as number
331
+ else if (!isNaN(Number(defaultValue)))
332
+ defaultValue = Number(defaultValue);
393
333
  }
394
334
 
395
- console.log(
396
- ` 🔍 Resolving config value: ${key}${defaultValue !== undefined ? ` (default: ${defaultValue})` : ""}`,
397
- );
398
-
399
- // Check environment variables first
335
+ // Check environment variables
400
336
  const envValue = process.env[key.toUpperCase()] || process.env[key];
401
337
  if (envValue !== undefined) {
402
- console.log(` 🌍 Found in environment: ${envValue}`);
403
-
404
338
  // Try to parse as JSON if it looks like an object/array
405
339
  if (envValue.startsWith("{") || envValue.startsWith("[")) {
406
340
  try {
@@ -424,19 +358,11 @@ export class DIContainer {
424
358
  }
425
359
 
426
360
  // Return default value if available
427
- if (defaultValue !== undefined) {
428
- console.log(` 📦 Using default value: ${defaultValue}`);
429
- return defaultValue;
430
- }
431
-
432
- console.warn(` ⚠️ Config value not found: ${key}`);
433
- return undefined;
361
+ return defaultValue !== undefined ? defaultValue : undefined;
434
362
  }
435
363
 
436
364
  private resolveRepository(entity: any): any {
437
365
  try {
438
- console.log(` 🔍 Resolving repository for entity: ${entity.name}`);
439
-
440
366
  const { TypeORMModule } = require("../../typeorm/typeorm-module");
441
367
  const dataSource = TypeORMModule.getDataSource();
442
368
 
@@ -444,14 +370,8 @@ export class DIContainer {
444
370
  throw new Error("TypeORM DataSource not initialized");
445
371
  }
446
372
 
447
- console.log(
448
- ` ✅ Found DataSource, creating repository for ${entity.name}`,
449
- );
450
373
  return dataSource.getRepository(entity);
451
374
  } catch (error) {
452
- console.error(
453
- ` ❌ Failed to resolve repository for ${entity.name}: ${(error as Error).message}`,
454
- );
455
375
  throw new Error(
456
376
  `Failed to resolve repository for ${entity.name}: ${(error as Error)?.message}`,
457
377
  );
@@ -471,7 +391,6 @@ export class DIContainer {
471
391
  }
472
392
 
473
393
  clear(): void {
474
- console.log("🧹 Clearing DI Container");
475
394
  this.singletons.clear();
476
395
  this.transients.clear();
477
396
  this.factories.clear();
@@ -481,6 +400,5 @@ export class DIContainer {
481
400
 
482
401
  reset(): void {
483
402
  this.clear();
484
- console.log("🔁 DI Container reset complete");
485
403
  }
486
404
  }
@@ -0,0 +1,20 @@
1
+ import { ExceptionFilter } from "../../web/interfaces";
2
+ import { METADATA_KEYS } from "../metadata/metadata-keys";
3
+ import { MetadataStorage } from "../metadata/metadata-storage";
4
+
5
+ /**
6
+ * @ExceptionFilter - Handle exceptions globally or per controller/method
7
+ */
8
+ export function ExceptionFilter(filterClass: new () => any): ClassDecorator & MethodDecorator {
9
+ return (target: any, propertyKey?: string | symbol) => {
10
+ if (propertyKey === undefined) {
11
+ Reflect.defineMetadata(METADATA_KEYS.USE_FILTERS, filterClass, target);
12
+ const storage = MetadataStorage.getInstance();
13
+ storage.addClassMetadata?.(target, 'exceptionFilter', filterClass);
14
+ } else {
15
+ Reflect.defineMetadata(METADATA_KEYS.USE_FILTERS, filterClass, target, propertyKey);
16
+ const storage = MetadataStorage.getInstance();
17
+ storage.addMethodMetadata?.(target.constructor, propertyKey.toString(), 'exceptionFilter', filterClass);
18
+ }
19
+ };
20
+ }
@@ -0,0 +1,20 @@
1
+ import { Guard } from "../../web/interfaces";
2
+ import { METADATA_KEYS } from "../metadata/metadata-keys";
3
+ import { MetadataStorage } from "../metadata/metadata-storage";
4
+
5
+ /**
6
+ * @Guard - Apply authorization/activation guard
7
+ */
8
+ export function Guard(guardClass: new () => any): ClassDecorator & MethodDecorator {
9
+ return (target: any, propertyKey?: string | symbol) => {
10
+ if (propertyKey === undefined) {
11
+ Reflect.defineMetadata(METADATA_KEYS.USE_GUARDS, guardClass, target);
12
+ const storage = MetadataStorage.getInstance();
13
+ storage.addClassMetadata?.(target, 'guard', guardClass);
14
+ } else {
15
+ Reflect.defineMetadata(METADATA_KEYS.USE_GUARDS, guardClass, target, propertyKey);
16
+ const storage = MetadataStorage.getInstance();
17
+ storage.addMethodMetadata?.(target.constructor, propertyKey.toString(), 'guard', guardClass);
18
+ }
19
+ };
20
+ }
@@ -23,6 +23,7 @@ export function Autowired(): PropertyDecorator {
23
23
  type: "autowired",
24
24
  key: propertyKey.toString(),
25
25
  metadata: { type },
26
+ propertyKey: propertyKey.toString(),
26
27
  });
27
28
 
28
29
  // Also store in Reflect metadata for backward compatibility
@@ -43,6 +44,7 @@ export function Inject(token: string | Function): PropertyDecorator {
43
44
  type: "inject",
44
45
  key: propertyKey.toString(),
45
46
  metadata: { token },
47
+ propertyKey: propertyKey.toString(),
46
48
  });
47
49
 
48
50
  // Also store in Reflect metadata for backward compatibility
@@ -62,6 +64,7 @@ export function InjectRepository(entity: Function): PropertyDecorator {
62
64
  type: "repository",
63
65
  key: propertyKey.toString(),
64
66
  metadata: { entity },
67
+ propertyKey: propertyKey.toString(),
65
68
  });
66
69
 
67
70
  // Also store in Reflect metadata for backward compatibility
@@ -97,6 +100,7 @@ export function Value(expression: string): PropertyDecorator {
97
100
  type: "value",
98
101
  key: propertyKey.toString(),
99
102
  metadata: { expression },
103
+ propertyKey: propertyKey.toString(),
100
104
  });
101
105
 
102
106
  // Also store in Reflect metadata for backward compatibility
@@ -133,6 +137,7 @@ export function Lazy(): PropertyDecorator {
133
137
  storage.addPropertyInjection?.(target.constructor, propertyKey.toString(), {
134
138
  type: "lazy",
135
139
  key: propertyKey.toString(),
140
+ propertyKey: propertyKey.toString(),
136
141
  });
137
142
 
138
143
  // Get the type for later resolution
@@ -0,0 +1,20 @@
1
+ import { Interceptor } from "../../web/interfaces";
2
+ import { METADATA_KEYS } from "../metadata/metadata-keys";
3
+ import { MetadataStorage } from "../metadata/metadata-storage";
4
+
5
+ /**
6
+ * @Interceptor - Intercept and transform request/response
7
+ */
8
+ export function Interceptor(interceptorClass: new () => any): ClassDecorator & MethodDecorator {
9
+ return (target: any, propertyKey?: string | symbol) => {
10
+ if (propertyKey === undefined) {
11
+ Reflect.defineMetadata(METADATA_KEYS.USE_INTERCEPTORS, interceptorClass, target);
12
+ const storage = MetadataStorage.getInstance();
13
+ storage.addClassMetadata?.(target, 'interceptor', interceptorClass);
14
+ } else {
15
+ Reflect.defineMetadata(METADATA_KEYS.USE_INTERCEPTORS, interceptorClass, target, propertyKey);
16
+ const storage = MetadataStorage.getInstance();
17
+ storage.addMethodMetadata?.(target.constructor, propertyKey.toString(), 'interceptor', interceptorClass);
18
+ }
19
+ };
20
+ }
@@ -0,0 +1,25 @@
1
+ import { Middleware } from "../../web/interfaces";
2
+ import { METADATA_KEYS } from "../metadata/metadata-keys";
3
+ import { MetadataStorage } from "../metadata/metadata-storage";
4
+
5
+ /**
6
+ * @Middleware - Apply Express-compatible middleware at class or method level
7
+ * Usage:
8
+ * @Middleware(AuthMiddleware) class MyController { }
9
+ * @Middleware(AuthMiddleware) @Get('/') handler() { }
10
+ */
11
+ export function Middleware(middlewareClass: new () => any): ClassDecorator & MethodDecorator {
12
+ return (target: any, propertyKey?: string | symbol) => {
13
+ if (propertyKey === undefined) {
14
+ // Class-level
15
+ Reflect.defineMetadata(METADATA_KEYS.USE_MIDDLEWARE, middlewareClass, target);
16
+ const storage = MetadataStorage.getInstance();
17
+ storage.addClassMetadata?.(target, 'middleware', middlewareClass);
18
+ } else {
19
+ // Method-level
20
+ Reflect.defineMetadata(METADATA_KEYS.USE_MIDDLEWARE, middlewareClass, target, propertyKey);
21
+ const storage = MetadataStorage.getInstance();
22
+ storage.addMethodMetadata?.(target.constructor, propertyKey.toString(), 'middleware', middlewareClass);
23
+ }
24
+ };
25
+ }
@@ -33,6 +33,7 @@ export const METADATA_KEYS = {
33
33
  CONDITIONAL_ON_BEAN: "fragment:conditional-on-bean",
34
34
 
35
35
  // Middleware & Guards
36
+ USE_MIDDLEWARE: "fragment:use-middleware",
36
37
  USE_GUARDS: "fragment:use-guards",
37
38
  USE_INTERCEPTORS: "fragment:use-interceptors",
38
39
  USE_FILTERS: "fragment:use-filters",