fragment-ts 1.0.31 → 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 (44) 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/decorators/exception-filter.decorator.d.ts +5 -0
  7. package/dist/core/decorators/exception-filter.decorator.d.ts.map +1 -0
  8. package/dist/core/decorators/exception-filter.decorator.js +23 -0
  9. package/dist/core/decorators/exception-filter.decorator.js.map +1 -0
  10. package/dist/core/decorators/guard.decorator.d.ts +5 -0
  11. package/dist/core/decorators/guard.decorator.d.ts.map +1 -0
  12. package/dist/core/decorators/guard.decorator.js +23 -0
  13. package/dist/core/decorators/guard.decorator.js.map +1 -0
  14. package/dist/core/decorators/interceptor.decorator.d.ts +5 -0
  15. package/dist/core/decorators/interceptor.decorator.d.ts.map +1 -0
  16. package/dist/core/decorators/interceptor.decorator.js +23 -0
  17. package/dist/core/decorators/interceptor.decorator.js.map +1 -0
  18. package/dist/core/decorators/middleware.decorator.d.ts +8 -0
  19. package/dist/core/decorators/middleware.decorator.d.ts.map +1 -0
  20. package/dist/core/decorators/middleware.decorator.js +28 -0
  21. package/dist/core/decorators/middleware.decorator.js.map +1 -0
  22. package/dist/core/metadata/metadata-keys.d.ts +1 -0
  23. package/dist/core/metadata/metadata-keys.d.ts.map +1 -1
  24. package/dist/core/metadata/metadata-keys.js +1 -0
  25. package/dist/core/metadata/metadata-keys.js.map +1 -1
  26. package/dist/core/metadata/metadata-storage.d.ts +18 -0
  27. package/dist/core/metadata/metadata-storage.d.ts.map +1 -1
  28. package/dist/core/metadata/metadata-storage.js +82 -0
  29. package/dist/core/metadata/metadata-storage.js.map +1 -1
  30. package/dist/web/application.d.ts.map +1 -1
  31. package/dist/web/application.js +74 -5
  32. package/dist/web/application.js.map +1 -1
  33. package/dist/web/interfaces.d.ts +5 -1
  34. package/dist/web/interfaces.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/cli/commands/init.command.ts +1 -1
  37. package/src/core/decorators/exception-filter.decorator.ts +20 -0
  38. package/src/core/decorators/guard.decorator.ts +20 -0
  39. package/src/core/decorators/interceptor.decorator.ts +20 -0
  40. package/src/core/decorators/middleware.decorator.ts +25 -0
  41. package/src/core/metadata/metadata-keys.ts +1 -0
  42. package/src/core/metadata/metadata-storage.ts +114 -0
  43. package/src/web/application.ts +141 -6
  44. package/src/web/interfaces.ts +11 -2
@@ -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
+ }
@@ -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",
@@ -35,6 +35,20 @@ export interface PropertyInjectionMetadata {
35
35
  metadata?: any;
36
36
  }
37
37
 
38
+ export interface ExtendedClassMetadata extends ClassMetadata {
39
+ middlewares: any[];
40
+ guards: any[];
41
+ interceptors: any[];
42
+ exceptionFilters: any[];
43
+ }
44
+
45
+ export interface ExtendedMethodMetadata extends MethodMetadata {
46
+ middlewares?: any[];
47
+ guards?: any[];
48
+ interceptors?: any[];
49
+ exceptionFilters?: any[];
50
+ }
51
+
38
52
  export class MetadataStorage {
39
53
  private static instance: MetadataStorage;
40
54
  private classes: Map<any, ClassMetadata> = new Map();
@@ -42,6 +56,8 @@ export class MetadataStorage {
42
56
  private params: Map<string, ParamMetadata[]> = new Map();
43
57
  private propertyInjections: Map<string, PropertyInjectionMetadata[]> =
44
58
  new Map();
59
+ private extendedClasses: Map<any, ExtendedClassMetadata> = new Map();
60
+ private extendedMethods: Map<string, ExtendedMethodMetadata> = new Map();
45
61
 
46
62
  static getInstance(): MetadataStorage {
47
63
  if (!MetadataStorage.instance) {
@@ -125,4 +141,102 @@ export class MetadataStorage {
125
141
 
126
142
  return injections;
127
143
  }
144
+
145
+ addClassMetadata(target: any, type: string, value: any): void {
146
+ let meta = this.extendedClasses.get(target);
147
+
148
+ if (!meta) {
149
+ const base = this.classes.get(target);
150
+ if (!base) {
151
+ // If no base ClassMetadata exists (e.g., decorator used on non-@Controller/@Service class),
152
+ // we cannot safely create ExtendedClassMetadata — skip or warn.
153
+ console.warn(
154
+ `⚠️ @${type} applied to ${target.name}, but it's not registered as a known component (missing @Controller, @Service, etc.). Ignoring.`,
155
+ );
156
+ return;
157
+ }
158
+
159
+ // Clone base and add enhancement arrays
160
+ meta = {
161
+ ...base,
162
+ middlewares: [],
163
+ guards: [],
164
+ interceptors: [],
165
+ exceptionFilters: [],
166
+ };
167
+ }
168
+
169
+ switch (type) {
170
+ case "middleware":
171
+ meta.middlewares.push(value);
172
+ break;
173
+ case "guard":
174
+ meta.guards.push(value);
175
+ break;
176
+ case "interceptor":
177
+ meta.interceptors.push(value);
178
+ break;
179
+ case "exceptionFilter":
180
+ meta.exceptionFilters.push(value);
181
+ break;
182
+ default:
183
+ console.warn(`Unknown metadata type: ${type}`);
184
+ return;
185
+ }
186
+
187
+ this.extendedClasses.set(target, meta);
188
+ }
189
+
190
+ addMethodMetadata(
191
+ target: any,
192
+ propertyKey: string,
193
+ type: string,
194
+ value: any,
195
+ ): void {
196
+ const key = `${target.name}.${propertyKey}`;
197
+ let meta = this.extendedMethods.get(key);
198
+ if (!meta) {
199
+ const base = this.methods.get(key);
200
+ meta = {
201
+ ...(base || {
202
+ target,
203
+ propertyKey,
204
+ method: "",
205
+ path: "",
206
+ paramMetadata: [],
207
+ }),
208
+ middlewares: [],
209
+ guards: [],
210
+ interceptors: [],
211
+ exceptionFilters: [],
212
+ };
213
+ }
214
+ switch (type) {
215
+ case "middleware":
216
+ meta.middlewares!.push(value);
217
+ break;
218
+ case "guard":
219
+ meta.guards!.push(value);
220
+ break;
221
+ case "interceptor":
222
+ meta.interceptors!.push(value);
223
+ break;
224
+ case "exceptionFilter":
225
+ meta.exceptionFilters!.push(value);
226
+ break;
227
+ }
228
+ this.extendedMethods.set(key, meta);
229
+ }
230
+
231
+ getExtendedClass(target: any): ExtendedClassMetadata | undefined {
232
+ return this.extendedClasses.get(target);
233
+ }
234
+
235
+ getExtendedMethod(
236
+ target: any,
237
+ propertyKey: string,
238
+ ): ExtendedMethodMetadata | undefined {
239
+ const key = `${target.name}.${propertyKey}`;
240
+ return this.extendedMethods.get(key);
241
+ }
128
242
  }
@@ -9,6 +9,16 @@ import { MetadataStorage } from "../core/metadata/metadata-storage";
9
9
  import { METADATA_KEYS } from "../core/metadata/metadata-keys";
10
10
  import { ComponentScanner } from "../core/scanner/component-scanner";
11
11
  import { TypeORMModule } from "../typeorm/typeorm-module";
12
+ import {
13
+ MiddlewareClass,
14
+ GuardClass,
15
+ InterceptorClass,
16
+ ExceptionFilterClass,
17
+ Middleware,
18
+ Guard,
19
+ Interceptor,
20
+ ExceptionFilter,
21
+ } from "./interfaces";
12
22
 
13
23
  export class FragmentWebApplication {
14
24
  private app: Express;
@@ -286,6 +296,27 @@ export class FragmentWebApplication {
286
296
  const controller = this.container.resolve(controllerMetadata.target);
287
297
  const basePath = controllerMetadata.path || "";
288
298
 
299
+ // Get controller-level enhancements
300
+ const controllerMiddlewares = (Reflect.getMetadata(
301
+ METADATA_KEYS.USE_MIDDLEWARE,
302
+ controllerMetadata.target,
303
+ ) || []) as MiddlewareClass[];
304
+
305
+ const controllerGuards = (Reflect.getMetadata(
306
+ METADATA_KEYS.USE_GUARDS,
307
+ controllerMetadata.target,
308
+ ) || []) as GuardClass[];
309
+
310
+ const controllerInterceptors = (Reflect.getMetadata(
311
+ METADATA_KEYS.USE_INTERCEPTORS,
312
+ controllerMetadata.target,
313
+ ) || []) as InterceptorClass[];
314
+
315
+ const controllerFilters = (Reflect.getMetadata(
316
+ METADATA_KEYS.USE_FILTERS,
317
+ controllerMetadata.target,
318
+ ) || []) as ExceptionFilterClass[];
319
+
289
320
  const methods = this.metadataStorage
290
321
  .getAllMethods()
291
322
  .filter((m) => m.target === controllerMetadata.target);
@@ -307,10 +338,78 @@ export class FragmentWebApplication {
307
338
 
308
339
  totalRoutes++;
309
340
 
341
+ // Collect method-level enhancements
342
+ const methodMiddlewares = (Reflect.getMetadata(
343
+ METADATA_KEYS.USE_MIDDLEWARE,
344
+ controllerMetadata.target.prototype,
345
+ methodMetadata.propertyKey,
346
+ ) || []) as MiddlewareClass[];
347
+
348
+ const methodGuards = (Reflect.getMetadata(
349
+ METADATA_KEYS.USE_GUARDS,
350
+ controllerMetadata.target.prototype,
351
+ methodMetadata.propertyKey,
352
+ ) || []) as GuardClass[];
353
+
354
+ const methodInterceptors = (Reflect.getMetadata(
355
+ METADATA_KEYS.USE_INTERCEPTORS,
356
+ controllerMetadata.target.prototype,
357
+ methodMetadata.propertyKey,
358
+ ) || []) as InterceptorClass[];
359
+
360
+ const methodFilters = (Reflect.getMetadata(
361
+ METADATA_KEYS.USE_FILTERS,
362
+ controllerMetadata.target.prototype,
363
+ methodMetadata.propertyKey,
364
+ ) || []) as ExceptionFilterClass[];
365
+
366
+ // Combine: controller + method
367
+ const allMiddlewares = [
368
+ ...controllerMiddlewares,
369
+ ...methodMiddlewares,
370
+ ];
371
+ const allGuards = [...controllerGuards, ...methodGuards];
372
+ const allInterceptors = [
373
+ ...controllerInterceptors,
374
+ ...methodInterceptors,
375
+ ];
376
+ const allFilters = [...controllerFilters, ...methodFilters];
377
+
378
+ // Resolve via DI container
379
+ const resolvedMiddlewares = allMiddlewares.map(
380
+ (M) => this.container.resolve(M) as Middleware,
381
+ );
382
+ const resolvedGuards = allGuards.map(
383
+ (G) => this.container.resolve(G) as Guard,
384
+ );
385
+ const resolvedInterceptors = allInterceptors.map(
386
+ (I) => this.container.resolve(I) as Interceptor,
387
+ );
388
+
389
+ // Build Express route handler with pipeline
310
390
  (this.app as any)[httpMethod](
311
391
  fullPath,
392
+ // Middleware stack (Express-compatible)
393
+ ...resolvedMiddlewares.map(
394
+ (mw) => (req: Request, res: Response, next: NextFunction) =>
395
+ Promise.resolve(mw.use(req, res, next)).catch(next),
396
+ ),
397
+ // Final unified handler
312
398
  async (req: Request, res: Response, next: NextFunction) => {
313
399
  try {
400
+ // ➤ Step 1: Guards
401
+ for (const guard of resolvedGuards) {
402
+ const canActivate = await Promise.resolve(
403
+ guard.canActivate(req),
404
+ );
405
+ if (!canActivate) {
406
+ const error = new Error("Forbidden");
407
+ (error as any).status = 403;
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ // ➤ Step 2: Resolve parameters
314
413
  const args = this.resolveMethodParameters(
315
414
  methodMetadata,
316
415
  req,
@@ -320,18 +419,54 @@ export class FragmentWebApplication {
320
419
  console.log(
321
420
  `\n🔍 Handling ${httpMethod.toUpperCase()} ${fullPath}`,
322
421
  );
323
- // console.log(` Parameters:`, args);
324
422
 
325
- const result = await (controller as any)[
326
- methodMetadata.propertyKey
327
- ](...args);
423
+ // Step 3: Interceptors (wrap handler)
424
+ let result: any;
425
+ if (resolvedInterceptors.length > 0) {
426
+ // First, call the real handler
427
+ const rawResult = await (controller as any)[
428
+ methodMetadata.propertyKey
429
+ ](...args);
430
+
431
+ // Then pass result through interceptors in reverse order (or forward — your choice)
432
+ result = rawResult;
433
+ for (const interceptor of resolvedInterceptors) {
434
+ // Allow interceptor to transform the result
435
+ result = await Promise.resolve(
436
+ interceptor.intercept(req, res, result),
437
+ );
438
+ }
439
+ } else {
440
+ result = await (controller as any)[
441
+ methodMetadata.propertyKey
442
+ ](...args);
443
+ }
328
444
 
445
+ // ➤ Step 4: Send response
329
446
  if (!res.headersSent) {
330
447
  res.json(result);
331
448
  }
332
449
  } catch (error) {
333
- console.error(`❌ Error handling route ${fullPath}:`, error);
334
- next(error);
450
+ // Step 5: Exception Filters (method + controller level)
451
+ let handled = false;
452
+ for (const FilterClass of allFilters) {
453
+ const filter = this.container.resolve(
454
+ FilterClass,
455
+ ) as ExceptionFilter;
456
+ try {
457
+ const err =
458
+ error instanceof Error ? error : new Error(String(error));
459
+ filter.catch(err, req, res);
460
+ handled = true;
461
+ break;
462
+ } catch (e) {
463
+ console.error(`❌ Exception filter failed:`, e);
464
+ }
465
+ }
466
+
467
+ if (!handled) {
468
+ next(error);
469
+ }
335
470
  }
336
471
  },
337
472
  );
@@ -1,4 +1,4 @@
1
- import { Request, Response, NextFunction } from 'express';
1
+ import { Request, Response, NextFunction } from "express";
2
2
 
3
3
  export interface Middleware {
4
4
  use(req: Request, res: Response, next: NextFunction): void | Promise<void>;
@@ -9,9 +9,18 @@ export interface Guard {
9
9
  }
10
10
 
11
11
  export interface Interceptor {
12
- intercept(req: Request, res: Response, next: NextFunction): void | Promise<void>;
12
+ intercept(
13
+ req: Request,
14
+ res: Response,
15
+ next: NextFunction,
16
+ ): void | Promise<void>;
13
17
  }
14
18
 
15
19
  export interface ExceptionFilter {
16
20
  catch(exception: Error, req: Request, res: Response): void;
17
21
  }
22
+
23
+ export type MiddlewareClass = new () => Middleware;
24
+ export type GuardClass = new () => Guard;
25
+ export type InterceptorClass = new () => Interceptor;
26
+ export type ExceptionFilterClass = new () => ExceptionFilter;