fragment-ts 1.0.34 → 1.0.35
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/API.md +248 -38
- package/DOCS.md +327 -63
- package/NewCLIGENERATECOMMANDS.txt +5 -0
- package/README.md +168 -3
- package/USAGE.md +395 -2
- package/dist/cli/commands/build.command.d.ts.map +1 -1
- package/dist/cli/commands/build.command.js +20 -8
- package/dist/cli/commands/build.command.js.map +1 -1
- package/dist/cli/commands/diagnostics.command.d.ts +1 -2
- package/dist/cli/commands/diagnostics.command.d.ts.map +1 -1
- package/dist/cli/commands/diagnostics.command.js +37 -23
- package/dist/cli/commands/diagnostics.command.js.map +1 -1
- package/dist/cli/commands/generate.command.d.ts +5 -1
- package/dist/cli/commands/generate.command.d.ts.map +1 -1
- package/dist/cli/commands/generate.command.js +171 -39
- package/dist/cli/commands/generate.command.js.map +1 -1
- package/dist/cli/commands/init.command.d.ts.map +1 -1
- package/dist/cli/commands/init.command.js +98 -28
- package/dist/cli/commands/init.command.js.map +1 -1
- package/dist/cli/commands/migrate.command.d.ts +10 -17
- package/dist/cli/commands/migrate.command.d.ts.map +1 -1
- package/dist/cli/commands/migrate.command.js +133 -170
- package/dist/cli/commands/migrate.command.js.map +1 -1
- package/dist/cli/commands/serve.command.d.ts.map +1 -1
- package/dist/cli/commands/serve.command.js +9 -4
- package/dist/cli/commands/serve.command.js.map +1 -1
- package/dist/cli/commands/test.command.d.ts.map +1 -1
- package/dist/cli/commands/test.command.js +24 -6
- package/dist/cli/commands/test.command.js.map +1 -1
- package/dist/core/scanner/component-scanner.d.ts +12 -0
- package/dist/core/scanner/component-scanner.d.ts.map +1 -1
- package/dist/core/scanner/component-scanner.js +72 -14
- package/dist/core/scanner/component-scanner.js.map +1 -1
- package/dist/shared/config.utils.d.ts +58 -0
- package/dist/shared/config.utils.d.ts.map +1 -0
- package/dist/shared/config.utils.js +137 -0
- package/dist/shared/config.utils.js.map +1 -0
- package/dist/shared/env.utils.d.ts +27 -0
- package/dist/shared/env.utils.d.ts.map +1 -0
- package/dist/shared/env.utils.js +68 -0
- package/dist/shared/env.utils.js.map +1 -0
- package/dist/shared/tsconfig.utils.d.ts +122 -0
- package/dist/shared/tsconfig.utils.d.ts.map +1 -0
- package/dist/shared/tsconfig.utils.js +305 -0
- package/dist/shared/tsconfig.utils.js.map +1 -0
- package/dist/testing/runner.d.ts +9 -1
- package/dist/testing/runner.d.ts.map +1 -1
- package/dist/testing/runner.js +50 -10
- package/dist/testing/runner.js.map +1 -1
- package/dist/typeorm/typeorm-module.d.ts +1 -0
- package/dist/typeorm/typeorm-module.d.ts.map +1 -1
- package/dist/typeorm/typeorm-module.js +193 -85
- package/dist/typeorm/typeorm-module.js.map +1 -1
- package/dist/web/application.d.ts +0 -1
- package/dist/web/application.d.ts.map +1 -1
- package/dist/web/application.js +4 -26
- package/dist/web/application.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/build.command.ts +24 -9
- package/src/cli/commands/diagnostics.command.ts +42 -30
- package/src/cli/commands/generate.command.ts +212 -52
- package/src/cli/commands/init.command.ts +100 -29
- package/src/cli/commands/migrate.command.ts +145 -198
- package/src/cli/commands/serve.command.ts +181 -170
- package/src/cli/commands/test.command.ts +25 -11
- package/src/core/scanner/component-scanner.ts +100 -18
- package/src/shared/config.utils.ts +148 -0
- package/src/shared/env.utils.ts +72 -0
- package/src/shared/tsconfig.utils.ts +360 -0
- package/src/testing/runner.ts +62 -14
- package/src/typeorm/typeorm-module.ts +209 -86
- package/src/web/application.ts +4 -33
package/DOCS.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
---
|
|
1
2
|
|
|
2
3
|
# In-Depth Documentation
|
|
3
4
|
|
|
@@ -9,6 +10,7 @@ Fragment TS follows a component-based architecture with dependency injection at
|
|
|
9
10
|
2. **Separation of Concerns**: Clear separation between controllers, services, and repositories
|
|
10
11
|
3. **Convention over Configuration**: Automatic component scanning and registration
|
|
11
12
|
4. **Type Safety**: Leveraging TypeScript's type system for compile-time validation
|
|
13
|
+
5. **Enhancement Pipeline**: Modular request/response processing through middleware, guards, interceptors, and exception filters
|
|
12
14
|
|
|
13
15
|

|
|
14
16
|
|
|
@@ -19,6 +21,7 @@ Fragment TS follows a component-based architecture with dependency injection at
|
|
|
19
21
|
3. **Component Scanner**: Discovers and registers components automatically
|
|
20
22
|
4. **Web Application**: Configures and starts the Express server
|
|
21
23
|
5. **TypeORM Module**: Integrates TypeORM for database operations
|
|
24
|
+
6. **Enhancement Pipeline**: Processes requests through middleware → guards → interceptors → handlers → exception filters
|
|
22
25
|
|
|
23
26
|
## Dependency Injection System
|
|
24
27
|
|
|
@@ -30,11 +33,13 @@ The dependency injection system in Fragment TS works through a combination of de
|
|
|
30
33
|
- Class decorators (`@Service()`, `@Controller()`, etc.) register components with the metadata storage
|
|
31
34
|
- Property decorators (`@Autowired()`, `@InjectRepository()`, etc.) store metadata about dependencies
|
|
32
35
|
- Method and parameter decorators store route and handler information
|
|
36
|
+
- **Enhancement decorators** (`@FragmentMiddleware`, `@FragmentGuard`, etc.) store pipeline metadata
|
|
33
37
|
|
|
34
38
|
2. **Component Registration**:
|
|
35
39
|
- During application bootstrap, the component scanner loads all relevant files
|
|
36
40
|
- The metadata storage is populated with component definitions
|
|
37
41
|
- Components are registered with the DI container
|
|
42
|
+
- Enhancement components are resolved through the same DI container
|
|
38
43
|
|
|
39
44
|
3. **Dependency Resolution**:
|
|
40
45
|
- When a component is requested, the container creates an instance
|
|
@@ -51,13 +56,13 @@ Fragment TS supports three scopes for components:
|
|
|
51
56
|
3. **Transient**: New instance every time it's requested
|
|
52
57
|
|
|
53
58
|
```typescript
|
|
54
|
-
@Injectable(
|
|
59
|
+
@Injectable("singleton")
|
|
55
60
|
class DatabaseConnection {}
|
|
56
61
|
|
|
57
|
-
@Injectable(
|
|
62
|
+
@Injectable("request")
|
|
58
63
|
class RequestContext {}
|
|
59
64
|
|
|
60
|
-
@Injectable(
|
|
65
|
+
@Injectable("transient")
|
|
61
66
|
class RandomGenerator {}
|
|
62
67
|
```
|
|
63
68
|
|
|
@@ -94,17 +99,17 @@ Fragment TS components follow a well-defined lifecycle:
|
|
|
94
99
|
@Service()
|
|
95
100
|
class LifecycleService {
|
|
96
101
|
constructor() {
|
|
97
|
-
console.log(
|
|
102
|
+
console.log("Constructor called");
|
|
98
103
|
}
|
|
99
|
-
|
|
104
|
+
|
|
100
105
|
@PostConstruct()
|
|
101
106
|
init() {
|
|
102
|
-
console.log(
|
|
107
|
+
console.log("Post-construct called - dependencies are available");
|
|
103
108
|
}
|
|
104
|
-
|
|
109
|
+
|
|
105
110
|
@PreDestroy()
|
|
106
111
|
cleanup() {
|
|
107
|
-
console.log(
|
|
112
|
+
console.log("Pre-destroy called - cleanup resources");
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
```
|
|
@@ -113,20 +118,171 @@ class LifecycleService {
|
|
|
113
118
|
|
|
114
119
|
### Request Processing Pipeline
|
|
115
120
|
|
|
121
|
+
Fragment TS implements a sophisticated request processing pipeline that executes enhancements in a specific order:
|
|
122
|
+
|
|
116
123
|
1. **Route Registration**:
|
|
117
124
|
- Controllers and routes are registered during application bootstrap
|
|
118
125
|
- Route metadata is stored in the metadata storage
|
|
126
|
+
- Enhancement metadata (middleware, guards, interceptors, filters) is collected
|
|
127
|
+
|
|
128
|
+
2. **Request Handling Pipeline**:
|
|
129
|
+
- **Express Middleware**: Standard Express middleware runs first
|
|
130
|
+
- **Fragment Middleware**: `@FragmentMiddleware` components execute (class-level then method-level)
|
|
131
|
+
- **Guards**: `@FragmentGuard` components validate access (class-level then method-level)
|
|
132
|
+
- **Parameter Resolution**: Route parameters are resolved and injected
|
|
133
|
+
- **Handler Execution**: Controller method is invoked
|
|
134
|
+
- **Interceptors**: `@FragmentInterceptor` components transform the result (class-level then method-level)
|
|
135
|
+
- **Response**: Final result is sent as JSON
|
|
136
|
+
|
|
137
|
+
3. **Error Handling Pipeline**:
|
|
138
|
+
- If any step throws an error, **Exception Filters** are executed
|
|
139
|
+
- `@FragmentExceptionFilter` components handle errors (method-level then class-level)
|
|
140
|
+
- If no filter handles the error, it falls back to Express error handling
|
|
141
|
+
|
|
142
|
+
### Enhancement Decorators
|
|
143
|
+
|
|
144
|
+
#### Middleware (`@FragmentMiddleware`)
|
|
145
|
+
|
|
146
|
+
Middleware components implement the `Middleware` interface and can modify the request/response cycle or terminate it early.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Middleware interface
|
|
150
|
+
interface Middleware {
|
|
151
|
+
use(req: Request, res: Response, next: NextFunction): void | Promise<void>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Implementation
|
|
155
|
+
@Injectable()
|
|
156
|
+
class LoggingMiddleware implements Middleware {
|
|
157
|
+
use(req: Request, res: Response, next: NextFunction): void {
|
|
158
|
+
console.log(`📥 ${req.method} ${req.path}`);
|
|
159
|
+
next();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Usage
|
|
164
|
+
@FragmentMiddleware(LoggingMiddleware)
|
|
165
|
+
@Controller("/api")
|
|
166
|
+
class ApiController {}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Guards (`@FragmentGuard`)
|
|
170
|
+
|
|
171
|
+
Guard components implement the `Guard` interface and control whether a route handler should be executed.
|
|
119
172
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
- Handler method is invoked
|
|
173
|
+
```typescript
|
|
174
|
+
// Guard interface
|
|
175
|
+
interface Guard {
|
|
176
|
+
canActivate(req: Request): boolean | Promise<boolean>;
|
|
177
|
+
}
|
|
126
178
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
179
|
+
// Implementation
|
|
180
|
+
@Injectable()
|
|
181
|
+
class AuthGuard implements Guard {
|
|
182
|
+
canActivate(req: Request): boolean {
|
|
183
|
+
const token = req.headers.authorization;
|
|
184
|
+
return token === "Bearer valid-token";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Usage
|
|
189
|
+
@FragmentGuard(AuthGuard)
|
|
190
|
+
@Controller("/secure")
|
|
191
|
+
class SecureController {}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### Interceptors (`@FragmentInterceptor`)
|
|
195
|
+
|
|
196
|
+
Interceptor components implement the `Interceptor` interface and can transform the handler's result before it's sent to the client.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// Interceptor interface
|
|
200
|
+
interface Interceptor {
|
|
201
|
+
intercept(req: Request, res: Response, result: any): any | Promise<any>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Implementation
|
|
205
|
+
@Injectable()
|
|
206
|
+
class TransformInterceptor implements Interceptor {
|
|
207
|
+
intercept(req: Request, res: Response, result: any): any {
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
data: result,
|
|
211
|
+
timestamp: new Date().toISOString(),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Usage
|
|
217
|
+
@FragmentInterceptor(TransformInterceptor)
|
|
218
|
+
@Controller("/api")
|
|
219
|
+
class DataController {}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Exception Filters (`@FragmentExceptionFilter`)
|
|
223
|
+
|
|
224
|
+
Exception filter components implement the `ExceptionFilter` interface and handle errors thrown during request processing.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// ExceptionFilter interface
|
|
228
|
+
interface ExceptionFilter {
|
|
229
|
+
catch(exception: Error, req: Request, res: Response): void;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Implementation
|
|
233
|
+
@Injectable()
|
|
234
|
+
class HttpExceptionFilter implements ExceptionFilter {
|
|
235
|
+
catch(exception: Error, req: Request, res: Response): void {
|
|
236
|
+
if (exception.name === "ValidationError") {
|
|
237
|
+
res
|
|
238
|
+
.status(400)
|
|
239
|
+
.json({ error: "Validation failed", message: exception.message });
|
|
240
|
+
} else if (exception.name === "NotFoundError") {
|
|
241
|
+
res.status(404).json({ error: "Not found", message: exception.message });
|
|
242
|
+
} else {
|
|
243
|
+
res.status(500).json({ error: "Internal server error" });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Usage
|
|
249
|
+
@FragmentExceptionFilter(HttpExceptionFilter)
|
|
250
|
+
@FragmentApplication()
|
|
251
|
+
class Application {}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Enhancement Composition
|
|
255
|
+
|
|
256
|
+
All enhancement decorators can be combined and applied at both class and method levels:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
@FragmentMiddleware(LoggingMiddleware)
|
|
260
|
+
@FragmentGuard(AuthGuard)
|
|
261
|
+
@FragmentInterceptor(TransformInterceptor)
|
|
262
|
+
@FragmentExceptionFilter(HttpExceptionFilter)
|
|
263
|
+
@Controller("/api")
|
|
264
|
+
class ComplexController {
|
|
265
|
+
@Get("/public")
|
|
266
|
+
@FragmentGuard(PublicGuard) // Overrides class-level guard for this method
|
|
267
|
+
publicData() {
|
|
268
|
+
return { message: "Public data" };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@Post("/admin")
|
|
272
|
+
@FragmentGuard(AdminGuard) // Additional guard
|
|
273
|
+
@FragmentInterceptor(CacheInterceptor) // Additional interceptor
|
|
274
|
+
createAdminData(@Body() data: any) {
|
|
275
|
+
return { created: true, data };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The execution order is:
|
|
281
|
+
|
|
282
|
+
1. **Middleware**: Class → Method
|
|
283
|
+
2. **Guards**: Class → Method (all must pass)
|
|
284
|
+
3. **Interceptors**: Class → Method (applied in reverse order on result)
|
|
285
|
+
4. **Exception Filters**: Method → Class (first one to handle stops propagation)
|
|
130
286
|
|
|
131
287
|
### Middleware Integration
|
|
132
288
|
|
|
@@ -136,8 +292,8 @@ Fragment TS integrates with Express middleware through the underlying Express ap
|
|
|
136
292
|
const app = new FragmentWebApplication();
|
|
137
293
|
const expressApp = app.getExpressApp();
|
|
138
294
|
|
|
139
|
-
// Add custom middleware
|
|
140
|
-
expressApp.use(
|
|
295
|
+
// Add custom Express middleware
|
|
296
|
+
expressApp.use("/api", apiMiddleware);
|
|
141
297
|
expressApp.use(errorHandler);
|
|
142
298
|
```
|
|
143
299
|
|
|
@@ -171,10 +327,10 @@ Fragment TS encourages the repository pattern for data access:
|
|
|
171
327
|
export class User {
|
|
172
328
|
@PrimaryGeneratedColumn()
|
|
173
329
|
id: number;
|
|
174
|
-
|
|
330
|
+
|
|
175
331
|
@Column()
|
|
176
332
|
name: string;
|
|
177
|
-
|
|
333
|
+
|
|
178
334
|
@Column()
|
|
179
335
|
email: string;
|
|
180
336
|
}
|
|
@@ -191,15 +347,15 @@ export interface UserRepository {
|
|
|
191
347
|
export class TypeOrmUserRepository implements UserRepository {
|
|
192
348
|
@InjectRepository(User)
|
|
193
349
|
private repo!: Repository<User>;
|
|
194
|
-
|
|
350
|
+
|
|
195
351
|
async findAll(): Promise<User[]> {
|
|
196
352
|
return this.repo.find();
|
|
197
353
|
}
|
|
198
|
-
|
|
354
|
+
|
|
199
355
|
async findById(id: number): Promise<User | null> {
|
|
200
356
|
return this.repo.findOneBy({ id });
|
|
201
357
|
}
|
|
202
|
-
|
|
358
|
+
|
|
203
359
|
async save(user: User): Promise<User> {
|
|
204
360
|
return this.repo.save(user);
|
|
205
361
|
}
|
|
@@ -225,7 +381,7 @@ class DefaultLoggerService implements LoggerService {}
|
|
|
225
381
|
|
|
226
382
|
// Only register if environment variable is set
|
|
227
383
|
@Service()
|
|
228
|
-
@ConditionalOnProperty(
|
|
384
|
+
@ConditionalOnProperty("ENABLE_CACHE", "true")
|
|
229
385
|
class CacheService {}
|
|
230
386
|
```
|
|
231
387
|
|
|
@@ -236,13 +392,13 @@ Application configuration can be injected from environment variables or config f
|
|
|
236
392
|
```typescript
|
|
237
393
|
@Service()
|
|
238
394
|
class AppConfig {
|
|
239
|
-
@Value(
|
|
395
|
+
@Value("${PORT:3000}")
|
|
240
396
|
port!: number;
|
|
241
|
-
|
|
242
|
-
@Value(
|
|
397
|
+
|
|
398
|
+
@Value("${DATABASE_URL}")
|
|
243
399
|
databaseUrl!: string;
|
|
244
|
-
|
|
245
|
-
@Value(
|
|
400
|
+
|
|
401
|
+
@Value("${FEATURE_FLAG_NEW_UI:false}")
|
|
246
402
|
featureFlagNewUi!: boolean;
|
|
247
403
|
}
|
|
248
404
|
```
|
|
@@ -253,32 +409,51 @@ Fragment TS is designed for testability with minimal dependencies on framework i
|
|
|
253
409
|
|
|
254
410
|
```typescript
|
|
255
411
|
// Unit test example
|
|
256
|
-
describe(
|
|
412
|
+
describe("UserService", () => {
|
|
257
413
|
let userService: UserService;
|
|
258
414
|
let mockUserRepository: jest.Mocked<UserRepository>;
|
|
259
|
-
|
|
415
|
+
|
|
260
416
|
beforeEach(() => {
|
|
261
417
|
mockUserRepository = {
|
|
262
418
|
findAll: jest.fn(),
|
|
263
|
-
findById: jest.fn()
|
|
419
|
+
findById: jest.fn(),
|
|
264
420
|
} as any;
|
|
265
|
-
|
|
421
|
+
|
|
266
422
|
userService = new UserService();
|
|
267
|
-
Object.defineProperty(userService,
|
|
268
|
-
value: mockUserRepository
|
|
423
|
+
Object.defineProperty(userService, "userRepository", {
|
|
424
|
+
value: mockUserRepository,
|
|
269
425
|
});
|
|
270
426
|
});
|
|
271
|
-
|
|
272
|
-
it(
|
|
273
|
-
const mockUsers = [{ id: 1, name:
|
|
427
|
+
|
|
428
|
+
it("should return all users", async () => {
|
|
429
|
+
const mockUsers = [{ id: 1, name: "John" }];
|
|
274
430
|
mockUserRepository.findAll.mockResolvedValue(mockUsers);
|
|
275
|
-
|
|
431
|
+
|
|
276
432
|
const result = await userService.findAll();
|
|
277
|
-
|
|
433
|
+
|
|
278
434
|
expect(result).toEqual(mockUsers);
|
|
279
435
|
expect(mockUserRepository.findAll).toHaveBeenCalled();
|
|
280
436
|
});
|
|
281
437
|
});
|
|
438
|
+
|
|
439
|
+
// Testing guards
|
|
440
|
+
describe("AuthGuard", () => {
|
|
441
|
+
let guard: AuthGuard;
|
|
442
|
+
|
|
443
|
+
beforeEach(() => {
|
|
444
|
+
guard = new AuthGuard();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should allow valid tokens", () => {
|
|
448
|
+
const mockReq = { headers: { authorization: "Bearer valid-token" } } as any;
|
|
449
|
+
expect(guard.canActivate(mockReq)).toBe(true);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("should reject invalid tokens", () => {
|
|
453
|
+
const mockReq = { headers: { authorization: "Bearer invalid" } } as any;
|
|
454
|
+
expect(guard.canActivate(mockReq)).toBe(false);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
282
457
|
```
|
|
283
458
|
|
|
284
459
|
## Performance Considerations
|
|
@@ -297,9 +472,17 @@ Component scanning and dependency resolution happen during application startup.
|
|
|
297
472
|
- Request-scoped components are created per request and should be lightweight
|
|
298
473
|
- Transient components are created on-demand and not cached
|
|
299
474
|
|
|
475
|
+
### Enhancement Performance
|
|
476
|
+
|
|
477
|
+
- **Middleware**: Should be fast and non-blocking; avoid heavy async operations
|
|
478
|
+
- **Guards**: Should be lightweight validation; avoid database calls when possible
|
|
479
|
+
- **Interceptors**: Can be more expensive since they run after handler execution
|
|
480
|
+
- **Exception Filters**: Should handle errors quickly without additional async operations
|
|
481
|
+
|
|
300
482
|
### Optimization Techniques
|
|
301
483
|
|
|
302
484
|
1. **Lazy Loading**:
|
|
485
|
+
|
|
303
486
|
```typescript
|
|
304
487
|
@Service()
|
|
305
488
|
class HeavyService {
|
|
@@ -310,9 +493,10 @@ class HeavyService {
|
|
|
310
493
|
```
|
|
311
494
|
|
|
312
495
|
2. **Selective Component Scanning**:
|
|
496
|
+
|
|
313
497
|
```typescript
|
|
314
498
|
@FragmentApplication({
|
|
315
|
-
autoScan: false // Disable automatic scanning
|
|
499
|
+
autoScan: false, // Disable automatic scanning
|
|
316
500
|
})
|
|
317
501
|
class Application {}
|
|
318
502
|
|
|
@@ -323,6 +507,7 @@ container.register(UserController);
|
|
|
323
507
|
```
|
|
324
508
|
|
|
325
509
|
3. **Production Build**:
|
|
510
|
+
|
|
326
511
|
- Always use compiled JavaScript in production
|
|
327
512
|
- Enable tree-shaking and minification
|
|
328
513
|
- Use environment variables for configuration instead of config files when possible
|
|
@@ -341,13 +526,16 @@ class CustomErrorHandler {
|
|
|
341
526
|
@PostConstruct()
|
|
342
527
|
register() {
|
|
343
528
|
const expressApp = app.getExpressApp();
|
|
344
|
-
expressApp.use(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
529
|
+
expressApp.use(
|
|
530
|
+
(err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
531
|
+
console.error("Global error:", err);
|
|
532
|
+
res.status(500).json({
|
|
533
|
+
error: "Internal Server Error",
|
|
534
|
+
message:
|
|
535
|
+
process.env.NODE_ENV === "development" ? err.message : undefined,
|
|
536
|
+
});
|
|
537
|
+
},
|
|
538
|
+
);
|
|
351
539
|
}
|
|
352
540
|
}
|
|
353
541
|
```
|
|
@@ -360,18 +548,29 @@ Define custom error types for better error handling:
|
|
|
360
548
|
export class NotFoundError extends Error {
|
|
361
549
|
constructor(message: string) {
|
|
362
550
|
super(message);
|
|
363
|
-
this.name =
|
|
551
|
+
this.name = "NotFoundError";
|
|
364
552
|
}
|
|
365
553
|
}
|
|
366
554
|
|
|
367
555
|
export class ValidationError extends Error {
|
|
368
556
|
constructor(public errors: any[]) {
|
|
369
|
-
super(
|
|
370
|
-
this.name =
|
|
557
|
+
super("Validation failed");
|
|
558
|
+
this.name = "ValidationError";
|
|
371
559
|
}
|
|
372
560
|
}
|
|
373
561
|
```
|
|
374
562
|
|
|
563
|
+
### Exception Filter Hierarchy
|
|
564
|
+
|
|
565
|
+
Exception filters follow a specific hierarchy for error handling:
|
|
566
|
+
|
|
567
|
+
1. **Method-level filters** are checked first
|
|
568
|
+
2. **Controller-level filters** are checked next
|
|
569
|
+
3. **Application-level filters** (on `@FragmentApplication` class) are checked last
|
|
570
|
+
4. **Global Express error handler** is the final fallback
|
|
571
|
+
|
|
572
|
+
This allows for fine-grained error handling at different levels of your application.
|
|
573
|
+
|
|
375
574
|
## Extensibility
|
|
376
575
|
|
|
377
576
|
### Custom Decorators
|
|
@@ -380,7 +579,11 @@ Create custom decorators by leveraging the metadata storage:
|
|
|
380
579
|
|
|
381
580
|
```typescript
|
|
382
581
|
function CustomDecorator(metadata: any): MethodDecorator {
|
|
383
|
-
return (
|
|
582
|
+
return (
|
|
583
|
+
target: any,
|
|
584
|
+
propertyKey: string | symbol,
|
|
585
|
+
descriptor: PropertyDescriptor,
|
|
586
|
+
) => {
|
|
384
587
|
const storage = MetadataStorage.getInstance();
|
|
385
588
|
storage.addCustomMetadata(target.constructor, propertyKey, metadata);
|
|
386
589
|
};
|
|
@@ -388,11 +591,19 @@ function CustomDecorator(metadata: any): MethodDecorator {
|
|
|
388
591
|
|
|
389
592
|
// Usage
|
|
390
593
|
class MyController {
|
|
391
|
-
@CustomDecorator({ role:
|
|
594
|
+
@CustomDecorator({ role: "admin" })
|
|
392
595
|
adminMethod() {}
|
|
393
596
|
}
|
|
394
597
|
```
|
|
395
598
|
|
|
599
|
+
### Custom Enhancement Types
|
|
600
|
+
|
|
601
|
+
You can extend the enhancement pipeline with custom types by:
|
|
602
|
+
|
|
603
|
+
1. Adding new metadata keys to `METADATA_KEYS`
|
|
604
|
+
2. Creating new decorators that store metadata
|
|
605
|
+
3. Updating the route registration logic to process your custom enhancements
|
|
606
|
+
|
|
396
607
|
### Custom Modules
|
|
397
608
|
|
|
398
609
|
Extend the framework with custom modules:
|
|
@@ -401,9 +612,9 @@ Extend the framework with custom modules:
|
|
|
401
612
|
class CustomModule {
|
|
402
613
|
static async initialize() {
|
|
403
614
|
// Custom initialization logic
|
|
404
|
-
console.log(
|
|
615
|
+
console.log("Custom module initialized");
|
|
405
616
|
}
|
|
406
|
-
|
|
617
|
+
|
|
407
618
|
static getDependency() {
|
|
408
619
|
// Return dependency for injection
|
|
409
620
|
return new CustomDependency();
|
|
@@ -426,10 +637,19 @@ class Application {
|
|
|
426
637
|
1. **Single Responsibility**: Each component should have one clear responsibility
|
|
427
638
|
2. **Loose Coupling**: Depend on abstractions (interfaces) rather than concrete implementations
|
|
428
639
|
3. **High Cohesion**: Related functionality should be grouped together
|
|
640
|
+
4. **Enhancement Separation**: Keep middleware, guards, interceptors, and filters focused on their specific concerns
|
|
641
|
+
|
|
642
|
+
### Enhancement Patterns
|
|
643
|
+
|
|
644
|
+
1. **Middleware**: Use for cross-cutting concerns like logging, authentication headers, request ID generation
|
|
645
|
+
2. **Guards**: Use for authorization, rate limiting, feature flags
|
|
646
|
+
3. **Interceptors**: Use for response transformation, caching, metrics collection
|
|
647
|
+
4. **Exception Filters**: Use for standardized error responses, logging, alerting
|
|
429
648
|
|
|
430
649
|
### Code Organization
|
|
431
650
|
|
|
432
651
|
1. **Feature-based Structure**:
|
|
652
|
+
|
|
433
653
|
```
|
|
434
654
|
src/
|
|
435
655
|
├── features/
|
|
@@ -442,16 +662,26 @@ src/
|
|
|
442
662
|
│ └── product/
|
|
443
663
|
│ ├── product.controller.ts
|
|
444
664
|
│ └── ...
|
|
665
|
+
├── enhancements/
|
|
666
|
+
│ ├── middleware/
|
|
667
|
+
│ ├── guards/
|
|
668
|
+
│ ├── interceptors/
|
|
669
|
+
│ └── filters/
|
|
445
670
|
```
|
|
446
671
|
|
|
447
672
|
2. **Layer-based Structure**:
|
|
673
|
+
|
|
448
674
|
```
|
|
449
675
|
src/
|
|
450
676
|
├── controllers/
|
|
451
677
|
├── services/
|
|
452
678
|
├── repositories/
|
|
453
679
|
├── entities/
|
|
454
|
-
|
|
680
|
+
├── dtos/
|
|
681
|
+
├── middleware/
|
|
682
|
+
├── guards/
|
|
683
|
+
├── interceptors/
|
|
684
|
+
└── filters/
|
|
455
685
|
```
|
|
456
686
|
|
|
457
687
|
### Testing Strategy
|
|
@@ -459,26 +689,29 @@ src/
|
|
|
459
689
|
1. **Unit Tests**: Test individual components in isolation
|
|
460
690
|
2. **Integration Tests**: Test component interactions
|
|
461
691
|
3. **End-to-End Tests**: Test full request/response cycle
|
|
692
|
+
4. **Enhancement Tests**: Specifically test middleware, guards, interceptors, and filters in isolation
|
|
462
693
|
|
|
463
694
|
## Migration Guide
|
|
464
695
|
|
|
465
696
|
### From Express
|
|
466
697
|
|
|
467
698
|
1. Create a Fragment application class:
|
|
699
|
+
|
|
468
700
|
```typescript
|
|
469
701
|
@FragmentApplication()
|
|
470
702
|
class Application {}
|
|
471
703
|
```
|
|
472
704
|
|
|
473
705
|
2. Convert Express routes to controllers:
|
|
706
|
+
|
|
474
707
|
```typescript
|
|
475
708
|
// Before (Express)
|
|
476
|
-
app.get(
|
|
709
|
+
app.get("/users", (req, res) => {
|
|
477
710
|
// handler
|
|
478
711
|
});
|
|
479
712
|
|
|
480
713
|
// After (Fragment)
|
|
481
|
-
@Controller(
|
|
714
|
+
@Controller("/users")
|
|
482
715
|
class UserController {
|
|
483
716
|
@Get()
|
|
484
717
|
findAll() {
|
|
@@ -488,9 +721,10 @@ class UserController {
|
|
|
488
721
|
```
|
|
489
722
|
|
|
490
723
|
3. Extract business logic to services:
|
|
724
|
+
|
|
491
725
|
```typescript
|
|
492
726
|
// Before
|
|
493
|
-
app.post(
|
|
727
|
+
app.post("/users", async (req, res) => {
|
|
494
728
|
const user = await db.users.create(req.body);
|
|
495
729
|
res.json(user);
|
|
496
730
|
});
|
|
@@ -500,17 +734,17 @@ app.post('/users', async (req, res) => {
|
|
|
500
734
|
class UserService {
|
|
501
735
|
@Autowired()
|
|
502
736
|
private userRepository!: UserRepository;
|
|
503
|
-
|
|
737
|
+
|
|
504
738
|
async create(userData: any) {
|
|
505
739
|
return this.userRepository.save(userData);
|
|
506
740
|
}
|
|
507
741
|
}
|
|
508
742
|
|
|
509
|
-
@Controller(
|
|
743
|
+
@Controller("/users")
|
|
510
744
|
class UserController {
|
|
511
745
|
@Autowired()
|
|
512
746
|
private userService!: UserService;
|
|
513
|
-
|
|
747
|
+
|
|
514
748
|
@Post()
|
|
515
749
|
async create(@Body() userData: any) {
|
|
516
750
|
return this.userService.create(userData);
|
|
@@ -518,6 +752,31 @@ class UserController {
|
|
|
518
752
|
}
|
|
519
753
|
```
|
|
520
754
|
|
|
755
|
+
4. Convert Express middleware to Fragment middleware:
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
// Before
|
|
759
|
+
app.use("/api", (req, res, next) => {
|
|
760
|
+
console.log("API request");
|
|
761
|
+
next();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// After
|
|
765
|
+
@Injectable()
|
|
766
|
+
class ApiLoggingMiddleware implements Middleware {
|
|
767
|
+
use(req: Request, res: Response, next: NextFunction) {
|
|
768
|
+
if (req.path.startsWith("/api")) {
|
|
769
|
+
console.log("API request");
|
|
770
|
+
}
|
|
771
|
+
next();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
@FragmentMiddleware(ApiLoggingMiddleware)
|
|
776
|
+
@Controller("/api")
|
|
777
|
+
class ApiController {}
|
|
778
|
+
```
|
|
779
|
+
|
|
521
780
|
### From NestJS
|
|
522
781
|
|
|
523
782
|
Fragment TS shares many concepts with NestJS:
|
|
@@ -525,11 +784,14 @@ Fragment TS shares many concepts with NestJS:
|
|
|
525
784
|
1. **Dependency Injection**: Similar `@Injectable()`, `@Autowired()` patterns
|
|
526
785
|
2. **Controllers and Services**: Nearly identical structure
|
|
527
786
|
3. **TypeORM Integration**: Similar repository patterns
|
|
787
|
+
4. **Enhancement Pipeline**: Similar to NestJS interceptors, guards, and filters
|
|
528
788
|
|
|
529
789
|
Key differences:
|
|
790
|
+
|
|
530
791
|
- Fragment TS uses property injection by default instead of constructor injection
|
|
531
792
|
- Simpler configuration with less boilerplate
|
|
532
793
|
- More lightweight with fewer dependencies
|
|
794
|
+
- Enhancement decorators work at both class and method levels with intuitive composition
|
|
533
795
|
|
|
534
796
|
## Roadmap
|
|
535
797
|
|
|
@@ -540,6 +802,8 @@ Key differences:
|
|
|
540
802
|
3. **Swagger Integration**: Automatic API documentation
|
|
541
803
|
4. **WebSockets Support**: Real-time communication capabilities
|
|
542
804
|
5. **CLI Tooling**: Project generation and management commands
|
|
805
|
+
6. **Async Local Storage**: Better request context propagation
|
|
806
|
+
7. **Performance Monitoring**: Built-in metrics and tracing
|
|
543
807
|
|
|
544
808
|
### Community Contributions
|
|
545
809
|
|