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.
- package/API.md +752 -0
- package/DOCS.md +555 -0
- package/README.md +76 -339
- package/USAGE.md +309 -1306
- package/dist/cli/commands/init.command.js +1 -1
- package/dist/core/container/di-container.d.ts.map +1 -1
- package/dist/core/container/di-container.js +62 -106
- package/dist/core/container/di-container.js.map +1 -1
- package/dist/core/decorators/exception-filter.decorator.d.ts +5 -0
- package/dist/core/decorators/exception-filter.decorator.d.ts.map +1 -0
- package/dist/core/decorators/exception-filter.decorator.js +23 -0
- package/dist/core/decorators/exception-filter.decorator.js.map +1 -0
- package/dist/core/decorators/guard.decorator.d.ts +5 -0
- package/dist/core/decorators/guard.decorator.d.ts.map +1 -0
- package/dist/core/decorators/guard.decorator.js +23 -0
- package/dist/core/decorators/guard.decorator.js.map +1 -0
- package/dist/core/decorators/injection.decorators.d.ts.map +1 -1
- package/dist/core/decorators/injection.decorators.js +5 -0
- package/dist/core/decorators/injection.decorators.js.map +1 -1
- package/dist/core/decorators/interceptor.decorator.d.ts +5 -0
- package/dist/core/decorators/interceptor.decorator.d.ts.map +1 -0
- package/dist/core/decorators/interceptor.decorator.js +23 -0
- package/dist/core/decorators/interceptor.decorator.js.map +1 -0
- package/dist/core/decorators/middleware.decorator.d.ts +8 -0
- package/dist/core/decorators/middleware.decorator.d.ts.map +1 -0
- package/dist/core/decorators/middleware.decorator.js +28 -0
- package/dist/core/decorators/middleware.decorator.js.map +1 -0
- package/dist/core/metadata/metadata-keys.d.ts +1 -0
- package/dist/core/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/core/metadata/metadata-keys.js +1 -0
- package/dist/core/metadata/metadata-keys.js.map +1 -1
- package/dist/core/metadata/metadata-storage.d.ts +20 -4
- package/dist/core/metadata/metadata-storage.d.ts.map +1 -1
- package/dist/core/metadata/metadata-storage.js +94 -14
- package/dist/core/metadata/metadata-storage.js.map +1 -1
- package/dist/web/application.d.ts.map +1 -1
- package/dist/web/application.js +79 -10
- package/dist/web/application.js.map +1 -1
- package/dist/web/interfaces.d.ts +5 -1
- package/dist/web/interfaces.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/init.command.ts +1 -1
- package/src/core/container/di-container.ts +95 -177
- package/src/core/decorators/exception-filter.decorator.ts +20 -0
- package/src/core/decorators/guard.decorator.ts +20 -0
- package/src/core/decorators/injection.decorators.ts +5 -0
- package/src/core/decorators/interceptor.decorator.ts +20 -0
- package/src/core/decorators/middleware.decorator.ts +25 -0
- package/src/core/metadata/metadata-keys.ts +1 -0
- package/src/core/metadata/metadata-storage.ts +128 -24
- package/src/web/application.ts +207 -53
- package/src/web/interfaces.ts +11 -2
package/USAGE.md
CHANGED
|
@@ -1,1439 +1,442 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Usage Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This guide demonstrates practical usage patterns for Fragment TS framework.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- [Basic REST API](#basic-rest-api)
|
|
7
|
-
- [Authentication & Authorization](#authentication--authorization)
|
|
8
|
-
- [Database Operations](#database-operations)
|
|
9
|
-
- [AI-Powered Applications](#ai-powered-applications)
|
|
10
|
-
- [Dependency Injection Patterns](#dependency-injection-patterns)
|
|
11
|
-
- [Advanced Routing](#advanced-routing)
|
|
12
|
-
- [Custom Middleware](#custom-middleware)
|
|
13
|
-
- [Testing](#testing)
|
|
14
|
-
- [Production Deployment](#production-deployment)
|
|
15
|
-
- [CLI Reference](#cli-reference)
|
|
16
|
-
- [Best Practices](#best-practices)
|
|
17
|
-
- [Troubleshooting](#troubleshooting)
|
|
5
|
+
## Project Structure
|
|
18
6
|
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Quick Start
|
|
22
|
-
|
|
23
|
-
### Installation & Project Initialization
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# Install Fragment CLI globally
|
|
27
|
-
npm install -g fragment
|
|
28
|
-
|
|
29
|
-
# Option 1: Initialize in current directory
|
|
30
|
-
mkdir my-app && cd my-app
|
|
31
|
-
fragment init .
|
|
32
|
-
|
|
33
|
-
# Option 2: Create new directory and initialize
|
|
34
|
-
fragment init my-app
|
|
35
|
-
cd my-app
|
|
36
|
-
|
|
37
|
-
# Option 3: Use specific template
|
|
38
|
-
fragment init my-api --template=api
|
|
39
|
-
|
|
40
|
-
# Option 4: Select features interactively
|
|
41
|
-
fragment init my-app --features=auth,ai,database
|
|
42
|
-
|
|
43
|
-
# Option 5: Skip npm install (install manually later)
|
|
44
|
-
fragment init my-app --skip-install
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Yes, `fragment init .` initializes in the current directory!**
|
|
48
|
-
|
|
49
|
-
### Start Development
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
# Start with hot reload (auto-restarts on file changes)
|
|
53
|
-
fragment serve
|
|
54
|
-
|
|
55
|
-
# Start on custom port
|
|
56
|
-
fragment serve --port=4000
|
|
57
|
-
|
|
58
|
-
# Start without watch mode
|
|
59
|
-
fragment serve --no-watch
|
|
60
|
-
|
|
61
|
-
# Build for production
|
|
62
|
-
fragment build
|
|
63
|
-
|
|
64
|
-
# Run production build
|
|
65
|
-
npm start
|
|
66
7
|
```
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
8
|
+
src/
|
|
9
|
+
├── app.ts # Application entry point
|
|
10
|
+
├── config/
|
|
11
|
+
│ └── fragment.json # TypeORM configuration
|
|
12
|
+
├── controllers/
|
|
13
|
+
│ ├── user.controller.ts
|
|
14
|
+
│ └── auth.controller.ts
|
|
15
|
+
├── services/
|
|
16
|
+
│ ├── user.service.ts
|
|
17
|
+
│ └── auth.service.ts
|
|
18
|
+
├── repositories/
|
|
19
|
+
│ └── user.repository.ts
|
|
20
|
+
├── entities/
|
|
21
|
+
│ └── user.entity.ts
|
|
22
|
+
└── dtos/
|
|
23
|
+
└── create-user.dto.ts
|
|
82
24
|
```
|
|
83
25
|
|
|
84
|
-
|
|
26
|
+
## Dependency Injection
|
|
85
27
|
|
|
86
|
-
|
|
28
|
+
### Basic Property Injection
|
|
87
29
|
|
|
88
30
|
```typescript
|
|
89
|
-
import {
|
|
90
|
-
import {
|
|
31
|
+
import { Service, Autowired } from 'fragment-ts';
|
|
32
|
+
import { UserRepository } from '../repositories/user.repository';
|
|
33
|
+
import { LoggerService } from './logger.service';
|
|
91
34
|
|
|
92
|
-
@
|
|
93
|
-
export class
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
35
|
+
@Service()
|
|
36
|
+
export class UserService {
|
|
37
|
+
@Autowired()
|
|
38
|
+
private userRepository!: UserRepository;
|
|
39
|
+
|
|
40
|
+
@Autowired()
|
|
41
|
+
private logger!: LoggerService;
|
|
42
|
+
|
|
97
43
|
async findAll() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@Get("/:id")
|
|
102
|
-
async findOne(@Param("id") id: string) {
|
|
103
|
-
return this.productService.findOne(id);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@Post()
|
|
107
|
-
async create(@Body() body: any) {
|
|
108
|
-
return this.productService.create(body);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
@Put("/:id")
|
|
112
|
-
async update(@Param("id") id: string, @Body() body: any) {
|
|
113
|
-
return this.productService.update(id, body);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
@Delete("/:id")
|
|
117
|
-
async delete(@Param("id") id: string) {
|
|
118
|
-
return this.productService.delete(id);
|
|
44
|
+
this.logger.info('Fetching all users');
|
|
45
|
+
return this.userRepository.findAll();
|
|
119
46
|
}
|
|
120
47
|
}
|
|
121
48
|
```
|
|
122
49
|
|
|
123
|
-
|
|
50
|
+
### Optional Dependencies
|
|
124
51
|
|
|
125
52
|
```typescript
|
|
126
|
-
import { Service } from
|
|
127
|
-
import {
|
|
53
|
+
import { Service, Autowired, Optional } from 'fragment-ts';
|
|
54
|
+
import { CacheService } from './cache.service';
|
|
128
55
|
|
|
129
56
|
@Service()
|
|
130
57
|
export class ProductService {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!product) {
|
|
140
|
-
throw new Error("Product not found");
|
|
58
|
+
@Autowired()
|
|
59
|
+
@Optional()
|
|
60
|
+
private cacheService?: CacheService;
|
|
61
|
+
|
|
62
|
+
async getProduct(id: string) {
|
|
63
|
+
if (this.cacheService) {
|
|
64
|
+
const cached = await this.cacheService.get(`product:${id}`);
|
|
65
|
+
if (cached) return cached;
|
|
141
66
|
}
|
|
67
|
+
|
|
68
|
+
// Fetch from database
|
|
69
|
+
const product = await this.productRepository.findById(id);
|
|
70
|
+
|
|
71
|
+
if (this.cacheService) {
|
|
72
|
+
await this.cacheService.set(`product:${id}`, product);
|
|
73
|
+
}
|
|
74
|
+
|
|
142
75
|
return product;
|
|
143
76
|
}
|
|
144
|
-
|
|
145
|
-
async create(data: any) {
|
|
146
|
-
return this.productRepository.create(data);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async update(id: string, data: any) {
|
|
150
|
-
return this.productRepository.update(parseInt(id), data);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async delete(id: string) {
|
|
154
|
-
return this.productRepository.delete(parseInt(id));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
**src/entities/product.entity.ts**
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
|
|
163
|
-
|
|
164
|
-
@Entity()
|
|
165
|
-
export class Product {
|
|
166
|
-
@PrimaryGeneratedColumn()
|
|
167
|
-
id: number;
|
|
168
|
-
|
|
169
|
-
@Column()
|
|
170
|
-
name: string;
|
|
171
|
-
|
|
172
|
-
@Column("decimal", { precision: 10, scale: 2 })
|
|
173
|
-
price: number;
|
|
174
|
-
|
|
175
|
-
@Column({ nullable: true })
|
|
176
|
-
description: string;
|
|
177
|
-
|
|
178
|
-
@Column({ default: 0 })
|
|
179
|
-
stock: number;
|
|
180
|
-
|
|
181
|
-
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
182
|
-
createdAt: Date;
|
|
183
|
-
|
|
184
|
-
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
185
|
-
updatedAt: Date;
|
|
186
77
|
}
|
|
187
78
|
```
|
|
188
79
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
# Start server
|
|
193
|
-
fragment serve
|
|
194
|
-
|
|
195
|
-
# Verify routes are registered
|
|
196
|
-
fragment routes
|
|
197
|
-
|
|
198
|
-
# Test endpoints
|
|
199
|
-
curl http://localhost:3000/products
|
|
200
|
-
curl -X POST http://localhost:3000/products \
|
|
201
|
-
-H "Content-Type: application/json" \
|
|
202
|
-
-d '{"name":"Laptop","price":999.99,"stock":10}'
|
|
203
|
-
curl http://localhost:3000/products/1
|
|
204
|
-
curl -X PUT http://localhost:3000/products/1 \
|
|
205
|
-
-H "Content-Type: application/json" \
|
|
206
|
-
-d '{"price":899.99}'
|
|
207
|
-
curl -X DELETE http://localhost:3000/products/1
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## Authentication & Authorization
|
|
213
|
-
|
|
214
|
-
### Use Case 2: User Authentication System
|
|
215
|
-
|
|
216
|
-
Complete authentication with JWT tokens, password hashing, and role-based access.
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
# Initialize with auth feature
|
|
220
|
-
fragment init auth-api --features=auth,database
|
|
221
|
-
cd auth-api
|
|
222
|
-
|
|
223
|
-
# Generate user resource
|
|
224
|
-
fragment generate resource user
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
**src/entities/user.entity.ts**
|
|
80
|
+
### Value Injection (Environment Variables)
|
|
228
81
|
|
|
229
82
|
```typescript
|
|
230
|
-
import {
|
|
231
|
-
|
|
232
|
-
@Entity()
|
|
233
|
-
export class User {
|
|
234
|
-
@PrimaryGeneratedColumn()
|
|
235
|
-
id: number;
|
|
236
|
-
|
|
237
|
-
@Column({ unique: true })
|
|
238
|
-
email: string;
|
|
239
|
-
|
|
240
|
-
@Column()
|
|
241
|
-
password: string;
|
|
242
|
-
|
|
243
|
-
@Column()
|
|
244
|
-
name: string;
|
|
245
|
-
|
|
246
|
-
@Column("simple-array", { default: "user" })
|
|
247
|
-
roles: string[];
|
|
248
|
-
|
|
249
|
-
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
250
|
-
createdAt: Date;
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**src/services/auth.service.ts**
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
import { Service } from "fragment";
|
|
258
|
-
import { AuthModule, NotFoundError, UnauthorizedError } from "fragment-ts";
|
|
259
|
-
import { UserRepository } from "../repositories/user.repository";
|
|
83
|
+
import { Service, Value } from 'fragment-ts';
|
|
260
84
|
|
|
261
85
|
@Service()
|
|
262
|
-
export class
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const token = AuthModule.generateToken({
|
|
279
|
-
userId: user.id.toString(),
|
|
280
|
-
email: user.email,
|
|
281
|
-
roles: user.roles,
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
return { user: { id: user.id, email, name }, token };
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async login(email: string, password: string) {
|
|
288
|
-
// Find user
|
|
289
|
-
const user = await this.userRepository.findByEmail(email);
|
|
290
|
-
if (!user) {
|
|
291
|
-
throw new UnauthorizedError("Invalid credentials");
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Verify password
|
|
295
|
-
const isValid = await AuthModule.comparePassword(password, user.password);
|
|
296
|
-
if (!isValid) {
|
|
297
|
-
throw new UnauthorizedError("Invalid credentials");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Generate token
|
|
301
|
-
const token = AuthModule.generateToken({
|
|
302
|
-
userId: user.id.toString(),
|
|
303
|
-
email: user.email,
|
|
304
|
-
roles: user.roles,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
return { user: { id: user.id, email, name: user.name }, token };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async getCurrentUser(userId: string) {
|
|
311
|
-
const user = await this.userRepository.findById(parseInt(userId));
|
|
312
|
-
if (!user) {
|
|
313
|
-
throw new NotFoundError("User not found");
|
|
314
|
-
}
|
|
315
|
-
return { id: user.id, email: user.email, name: user.name };
|
|
86
|
+
export class ConfigService {
|
|
87
|
+
@Value('${PORT:3000}')
|
|
88
|
+
private port!: number;
|
|
89
|
+
|
|
90
|
+
@Value('${DB_HOST:localhost}')
|
|
91
|
+
private dbHost!: string;
|
|
92
|
+
|
|
93
|
+
@Value('${FEATURE_ENABLED:false}')
|
|
94
|
+
private featureEnabled!: boolean;
|
|
95
|
+
|
|
96
|
+
getPort(): number {
|
|
97
|
+
return this.port;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
isFeatureEnabled(): boolean {
|
|
101
|
+
return this.featureEnabled;
|
|
316
102
|
}
|
|
317
103
|
}
|
|
318
104
|
```
|
|
319
105
|
|
|
320
|
-
|
|
106
|
+
## Controllers & Routes
|
|
321
107
|
|
|
322
|
-
|
|
323
|
-
import { Controller, Post, Get, Body, Req } from "fragment-ts";
|
|
324
|
-
import { AuthModule } from "fragment-ts";
|
|
325
|
-
import { AuthService } from "../services/auth.service";
|
|
108
|
+
### Basic Controller
|
|
326
109
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
110
|
+
```typescript
|
|
111
|
+
import { Controller, Get, Post, Body, Param, Query } from 'fragment-ts';
|
|
112
|
+
import { UserService } from '../services/user.service';
|
|
113
|
+
import { CreateUserDto } from '../dtos/create-user.dto';
|
|
330
114
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
115
|
+
@Controller('/users')
|
|
116
|
+
export class UserController {
|
|
117
|
+
constructor(private userService: UserService) {}
|
|
118
|
+
|
|
119
|
+
@Get()
|
|
120
|
+
async findAll(@Query('page') page: number = 1) {
|
|
121
|
+
return this.userService.findAll(page);
|
|
334
122
|
}
|
|
335
|
-
|
|
336
|
-
@
|
|
337
|
-
async
|
|
338
|
-
return this.
|
|
123
|
+
|
|
124
|
+
@Get('/:id')
|
|
125
|
+
async findById(@Param('id') id: string) {
|
|
126
|
+
return this.userService.findById(id);
|
|
339
127
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
@
|
|
343
|
-
|
|
344
|
-
constructor(private authService: AuthService) {}
|
|
345
|
-
|
|
346
|
-
// Protected route - requires authentication
|
|
347
|
-
@Get("/me")
|
|
348
|
-
async getProfile(@Req() req: any) {
|
|
349
|
-
// In production, use middleware to attach user to request
|
|
350
|
-
const authHeader = req.headers.authorization;
|
|
351
|
-
if (!authHeader) {
|
|
352
|
-
throw new UnauthorizedError("Missing authorization header");
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const token = authHeader.replace("Bearer ", "");
|
|
356
|
-
const payload = AuthModule.verifyToken(token);
|
|
357
|
-
|
|
358
|
-
return this.authService.getCurrentUser(payload.userId);
|
|
128
|
+
|
|
129
|
+
@Post()
|
|
130
|
+
async create(@Body() createUserDto: CreateUserDto) {
|
|
131
|
+
return this.userService.create(createUserDto);
|
|
359
132
|
}
|
|
360
133
|
}
|
|
361
134
|
```
|
|
362
135
|
|
|
363
|
-
|
|
136
|
+
### Route Parameters and Query Parameters
|
|
364
137
|
|
|
365
138
|
```typescript
|
|
366
|
-
import
|
|
367
|
-
import { FragmentApplication } from "fragment-ts";
|
|
368
|
-
import { FragmentWebApplication } from "fragment-ts";
|
|
369
|
-
import { AuthModule } from "fragment-ts";
|
|
370
|
-
|
|
371
|
-
@FragmentApplication({
|
|
372
|
-
port: 3000,
|
|
373
|
-
autoScan: true,
|
|
374
|
-
})
|
|
375
|
-
class Application {}
|
|
376
|
-
|
|
377
|
-
async function bootstrap() {
|
|
378
|
-
const app = new FragmentWebApplication();
|
|
379
|
-
|
|
380
|
-
// Apply auth middleware to protected routes
|
|
381
|
-
const expressApp = app.getExpressApp();
|
|
382
|
-
expressApp.use("/api/*", AuthModule.authMiddleware());
|
|
383
|
-
|
|
384
|
-
// Apply role-based guards
|
|
385
|
-
expressApp.use("/admin/*", AuthModule.roleGuard("admin"));
|
|
386
|
-
|
|
387
|
-
await app.bootstrap(Application);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
bootstrap();
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
**Testing Authentication:**
|
|
394
|
-
|
|
395
|
-
```bash
|
|
396
|
-
# Register
|
|
397
|
-
curl -X POST http://localhost:3000/auth/register \
|
|
398
|
-
-H "Content-Type: application/json" \
|
|
399
|
-
-d '{"email":"user@example.com","password":"secret123","name":"John Doe"}'
|
|
400
|
-
|
|
401
|
-
# Response: {"user":{"id":1,"email":"user@example.com","name":"John Doe"},"token":"eyJ..."}
|
|
402
|
-
|
|
403
|
-
# Login
|
|
404
|
-
curl -X POST http://localhost:3000/auth/login \
|
|
405
|
-
-H "Content-Type: application/json" \
|
|
406
|
-
-d '{"email":"user@example.com","password":"secret123"}'
|
|
407
|
-
|
|
408
|
-
# Access protected route
|
|
409
|
-
curl http://localhost:3000/api/me \
|
|
410
|
-
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
|
411
|
-
```
|
|
139
|
+
import { Controller, Get, Param, Query } from 'fragment-ts';
|
|
412
140
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
# Run all pending migrations
|
|
424
|
-
fragment migrate:run
|
|
425
|
-
|
|
426
|
-
# Check migration status
|
|
427
|
-
fragment migrate:status
|
|
428
|
-
|
|
429
|
-
# Revert last migration
|
|
430
|
-
fragment migrate:revert
|
|
431
|
-
|
|
432
|
-
# Refresh all migrations (drop and re-run - CAREFUL!)
|
|
433
|
-
fragment migrate:refresh
|
|
434
|
-
|
|
435
|
-
# Sync schema (development only - not for production!)
|
|
436
|
-
fragment schema:sync
|
|
437
|
-
|
|
438
|
-
# Drop all tables (DANGEROUS!)
|
|
439
|
-
fragment schema:drop
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
**Example Migration: src/migrations/1234567890-AddProductCategories.ts**
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
import {
|
|
446
|
-
MigrationInterface,
|
|
447
|
-
QueryRunner,
|
|
448
|
-
Table,
|
|
449
|
-
TableForeignKey,
|
|
450
|
-
} from "typeorm";
|
|
451
|
-
|
|
452
|
-
export class AddProductCategories1234567890 implements MigrationInterface {
|
|
453
|
-
async up(queryRunner: QueryRunner): Promise<void> {
|
|
454
|
-
// Create categories table
|
|
455
|
-
await queryRunner.createTable(
|
|
456
|
-
new Table({
|
|
457
|
-
name: "category",
|
|
458
|
-
columns: [
|
|
459
|
-
{
|
|
460
|
-
name: "id",
|
|
461
|
-
type: "int",
|
|
462
|
-
isPrimary: true,
|
|
463
|
-
isGenerated: true,
|
|
464
|
-
generationStrategy: "increment",
|
|
465
|
-
},
|
|
466
|
-
{ name: "name", type: "varchar", isUnique: true },
|
|
467
|
-
{ name: "description", type: "text", isNullable: true },
|
|
468
|
-
{
|
|
469
|
-
name: "createdAt",
|
|
470
|
-
type: "timestamp",
|
|
471
|
-
default: "CURRENT_TIMESTAMP",
|
|
472
|
-
},
|
|
473
|
-
],
|
|
474
|
-
}),
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
// Add category column to products
|
|
478
|
-
await queryRunner.query(`ALTER TABLE product ADD COLUMN categoryId INT`);
|
|
479
|
-
|
|
480
|
-
// Add foreign key
|
|
481
|
-
await queryRunner.createForeignKey(
|
|
482
|
-
"product",
|
|
483
|
-
new TableForeignKey({
|
|
484
|
-
columnNames: ["categoryId"],
|
|
485
|
-
referencedTableName: "category",
|
|
486
|
-
referencedColumnNames: ["id"],
|
|
487
|
-
onDelete: "SET NULL",
|
|
488
|
-
}),
|
|
489
|
-
);
|
|
141
|
+
@Controller('/products')
|
|
142
|
+
export class ProductController {
|
|
143
|
+
@Get()
|
|
144
|
+
async search(
|
|
145
|
+
@Query('q') query: string,
|
|
146
|
+
@Query('category') category?: string,
|
|
147
|
+
@Query('minPrice') minPrice?: number,
|
|
148
|
+
@Query('maxPrice') maxPrice?: number
|
|
149
|
+
) {
|
|
150
|
+
// Implementation
|
|
490
151
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (foreignKey) {
|
|
499
|
-
await queryRunner.dropForeignKey("product", foreignKey);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Drop column and table
|
|
503
|
-
await queryRunner.dropColumn("product", "categoryId");
|
|
504
|
-
await queryRunner.dropTable("category");
|
|
152
|
+
|
|
153
|
+
@Get('/:id/reviews')
|
|
154
|
+
async getProductReviews(
|
|
155
|
+
@Param('id') productId: string,
|
|
156
|
+
@Query('rating') minRating?: number
|
|
157
|
+
) {
|
|
158
|
+
// Implementation
|
|
505
159
|
}
|
|
506
160
|
}
|
|
507
161
|
```
|
|
508
162
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
```bash
|
|
512
|
-
# Create seed
|
|
513
|
-
fragment seed:create InitialData
|
|
514
|
-
|
|
515
|
-
# Run all seeds
|
|
516
|
-
fragment seed
|
|
517
|
-
```
|
|
163
|
+
## Repositories
|
|
518
164
|
|
|
519
|
-
|
|
165
|
+
### TypeORM Repository Injection
|
|
520
166
|
|
|
521
167
|
```typescript
|
|
522
|
-
import {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
('Novel', 14.99, 100, 3)
|
|
542
|
-
`);
|
|
543
|
-
|
|
544
|
-
console.log("✅ Initial data seeded successfully");
|
|
168
|
+
import { Repository, InjectRepository } from 'fragment-ts';
|
|
169
|
+
import { User } from '../entities/user.entity';
|
|
170
|
+
import { Repository as TypeOrmRepository } from 'typeorm';
|
|
171
|
+
|
|
172
|
+
@Repository()
|
|
173
|
+
export class UserRepository {
|
|
174
|
+
@InjectRepository(User)
|
|
175
|
+
private repo!: TypeOrmRepository<User>;
|
|
176
|
+
|
|
177
|
+
async findAll(): Promise<User[]> {
|
|
178
|
+
return this.repo.find();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async findById(id: string): Promise<User | null> {
|
|
182
|
+
return this.repo.findOneBy({ id });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async save(user: User): Promise<User> {
|
|
186
|
+
return this.repo.save(user);
|
|
545
187
|
}
|
|
546
188
|
}
|
|
547
189
|
```
|
|
548
190
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
## AI-Powered Applications
|
|
552
|
-
|
|
553
|
-
### Use Case 4: AI Chat Service
|
|
554
|
-
|
|
555
|
-
```bash
|
|
556
|
-
# Initialize with AI feature
|
|
557
|
-
fragment init ai-app --features=ai
|
|
558
|
-
cd ai-app
|
|
559
|
-
|
|
560
|
-
# Generate AI components
|
|
561
|
-
fragment generate controller chat
|
|
562
|
-
fragment generate service chat
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
**src/services/chat.service.ts**
|
|
191
|
+
### Custom Repository Methods
|
|
566
192
|
|
|
567
193
|
```typescript
|
|
568
|
-
import {
|
|
569
|
-
import {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const response = await this.aiModule.complete(messages, {
|
|
590
|
-
model: "gpt-3.5-turbo",
|
|
591
|
-
temperature: 0.7,
|
|
592
|
-
maxTokens: 500,
|
|
194
|
+
import { Repository, InjectRepository } from 'fragment-ts';
|
|
195
|
+
import { User } from '../entities/user.entity';
|
|
196
|
+
import { Repository as TypeOrmRepository } from 'typeorm';
|
|
197
|
+
|
|
198
|
+
@Repository()
|
|
199
|
+
export class UserRepository {
|
|
200
|
+
@InjectRepository(User)
|
|
201
|
+
private repo!: TypeOrmRepository<User>;
|
|
202
|
+
|
|
203
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
204
|
+
return this.repo.findOneBy({ email });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async findActiveUsers(): Promise<User[]> {
|
|
208
|
+
return this.repo.find({ where: { isActive: true } });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async findWithRelations(id: string): Promise<User | null> {
|
|
212
|
+
return this.repo.findOne({
|
|
213
|
+
where: { id },
|
|
214
|
+
relations: ['orders', 'profile']
|
|
593
215
|
});
|
|
594
|
-
|
|
595
|
-
return {
|
|
596
|
-
response,
|
|
597
|
-
history: [
|
|
598
|
-
...history,
|
|
599
|
-
{ role: "user", content: message },
|
|
600
|
-
{ role: "assistant", content: response },
|
|
601
|
-
],
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
async streamChat(message: string, onChunk: (chunk: string) => void) {
|
|
606
|
-
const messages = [
|
|
607
|
-
{ role: "system" as const, content: "You are a helpful assistant." },
|
|
608
|
-
{ role: "user" as const, content: message },
|
|
609
|
-
];
|
|
610
|
-
|
|
611
|
-
await this.aiModule.streamComplete(
|
|
612
|
-
messages,
|
|
613
|
-
{ model: "gpt-3.5-turbo" },
|
|
614
|
-
onChunk,
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
async generateEmbeddings(texts: string[]) {
|
|
619
|
-
return this.aiModule.embeddings(texts);
|
|
620
216
|
}
|
|
621
217
|
}
|
|
622
218
|
```
|
|
623
219
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
import { Controller, Post, Body, Res } from "fragment-ts";
|
|
628
|
-
import { ChatService } from "../services/chat.service";
|
|
629
|
-
import { Response } from "express";
|
|
220
|
+
## Conditional Components
|
|
630
221
|
|
|
631
|
-
|
|
632
|
-
export class ChatController {
|
|
633
|
-
constructor(private chatService: ChatService) {}
|
|
222
|
+
### Conditional On Class
|
|
634
223
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
res.setHeader("Connection", "keep-alive");
|
|
645
|
-
|
|
646
|
-
await this.chatService.streamChat(body.message, (chunk) => {
|
|
647
|
-
res.write(`data: ${JSON.stringify({ chunk })}\n\n`);
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
res.write("data: [DONE]\n\n");
|
|
651
|
-
res.end();
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
@Post("/embeddings")
|
|
655
|
-
async getEmbeddings(@Body() body: any) {
|
|
656
|
-
const embeddings = await this.chatService.generateEmbeddings(body.texts);
|
|
657
|
-
return { embeddings };
|
|
658
|
-
}
|
|
224
|
+
```typescript
|
|
225
|
+
import { AutoConfiguration, ConditionalOnClass } from 'fragment-ts';
|
|
226
|
+
import { RedisCacheService } from './redis-cache.service';
|
|
227
|
+
import { Redis } from 'ioredis';
|
|
228
|
+
|
|
229
|
+
@AutoConfiguration()
|
|
230
|
+
@ConditionalOnClass(Redis)
|
|
231
|
+
export class RedisAutoConfiguration {
|
|
232
|
+
// This configuration only loads if Redis class is available
|
|
659
233
|
}
|
|
660
234
|
```
|
|
661
235
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
```env
|
|
665
|
-
NODE_ENV=development
|
|
666
|
-
PORT=3000
|
|
667
|
-
OPENAI_API_KEY=sk-your-key-here
|
|
668
|
-
OLLAMA_URL=http://localhost:11434
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
**Testing AI Endpoints:**
|
|
672
|
-
|
|
673
|
-
```bash
|
|
674
|
-
# Chat
|
|
675
|
-
curl -X POST http://localhost:3000/chat/message \
|
|
676
|
-
-H "Content-Type: application/json" \
|
|
677
|
-
-d '{"message":"What is TypeScript?"}'
|
|
678
|
-
|
|
679
|
-
# Stream (watch responses appear in real-time)
|
|
680
|
-
curl -X POST http://localhost:3000/chat/stream \
|
|
681
|
-
-H "Content-Type: application/json" \
|
|
682
|
-
-d '{"message":"Write a short story"}' \
|
|
683
|
-
--no-buffer
|
|
684
|
-
|
|
685
|
-
# Embeddings (vector representations of text)
|
|
686
|
-
curl -X POST http://localhost:3000/chat/embeddings \
|
|
687
|
-
-H "Content-Type: application/json" \
|
|
688
|
-
-d '{"texts":["Hello world","Fragment Framework"]}'
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
---
|
|
692
|
-
|
|
693
|
-
## Dependency Injection Patterns
|
|
694
|
-
|
|
695
|
-
### Use Case 5: Complex DI with Multiple Services
|
|
236
|
+
### Conditional On Property
|
|
696
237
|
|
|
697
238
|
```typescript
|
|
698
|
-
|
|
699
|
-
import {
|
|
239
|
+
import { Service, ConditionalOnProperty } from 'fragment-ts';
|
|
240
|
+
import { CloudStorageService } from './cloud-storage.service';
|
|
700
241
|
|
|
701
242
|
@Service()
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
// Email sending logic (SendGrid, Mailgun, etc.)
|
|
706
|
-
return { sent: true };
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// src/services/notification.service.ts
|
|
711
|
-
import { Service, Autowired } from "fragment-ts";
|
|
712
|
-
import { EmailService } from "./email.service";
|
|
713
|
-
|
|
714
|
-
@Service()
|
|
715
|
-
export class NotificationService {
|
|
716
|
-
// Property injection using @Autowired
|
|
717
|
-
@Autowired()
|
|
718
|
-
private emailService: EmailService;
|
|
719
|
-
|
|
720
|
-
async notifyUser(userId: string, message: string) {
|
|
721
|
-
// Send email notification
|
|
722
|
-
await this.emailService.sendEmail(
|
|
723
|
-
`user${userId}@example.com`,
|
|
724
|
-
"Notification",
|
|
725
|
-
message,
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// src/services/order.service.ts
|
|
731
|
-
import { Service } from "fragment-ts";
|
|
732
|
-
import { NotificationService } from "./notification.service";
|
|
733
|
-
import { ProductRepository } from "../repositories/product.repository";
|
|
734
|
-
|
|
735
|
-
@Service()
|
|
736
|
-
export class OrderService {
|
|
737
|
-
// Constructor injection
|
|
738
|
-
constructor(
|
|
739
|
-
private notificationService: NotificationService,
|
|
740
|
-
private productRepository: ProductRepository,
|
|
741
|
-
) {}
|
|
742
|
-
|
|
743
|
-
async createOrder(userId: string, productId: number, quantity: number) {
|
|
744
|
-
const product = await this.productRepository.findById(productId);
|
|
745
|
-
|
|
746
|
-
if (!product || product.stock < quantity) {
|
|
747
|
-
throw new Error("Insufficient stock");
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Update stock
|
|
751
|
-
await this.productRepository.update(productId, {
|
|
752
|
-
stock: product.stock - quantity,
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
// Notify user
|
|
756
|
-
await this.notificationService.notifyUser(
|
|
757
|
-
userId,
|
|
758
|
-
`Your order for ${quantity}x ${product.name} has been placed!`,
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
return { orderId: Date.now(), product, quantity };
|
|
762
|
-
}
|
|
243
|
+
@ConditionalOnProperty('USE_CLOUD_STORAGE', 'true')
|
|
244
|
+
export class CloudStorageService {
|
|
245
|
+
// Only registered if USE_CLOUD_STORAGE env var is 'true'
|
|
763
246
|
}
|
|
764
247
|
```
|
|
765
248
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
## Advanced Routing
|
|
769
|
-
|
|
770
|
-
### Use Case 6: Complex Route Parameters
|
|
249
|
+
### Conditional On Missing Bean
|
|
771
250
|
|
|
772
251
|
```typescript
|
|
773
|
-
import {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
Post,
|
|
777
|
-
Param,
|
|
778
|
-
Query,
|
|
779
|
-
Header,
|
|
780
|
-
Body,
|
|
781
|
-
Req,
|
|
782
|
-
Res,
|
|
783
|
-
} from "fragment-ts";
|
|
784
|
-
import { Request, Response } from "express";
|
|
785
|
-
|
|
786
|
-
@Controller("/api/v1/users")
|
|
787
|
-
export class UserController {
|
|
788
|
-
// Nested route parameters
|
|
789
|
-
// Route: GET /api/v1/users/:userId/orders/:orderId
|
|
790
|
-
@Get("/:userId/orders/:orderId")
|
|
791
|
-
async getUserOrder(
|
|
792
|
-
@Param("userId") userId: string,
|
|
793
|
-
@Param("orderId") orderId: string,
|
|
794
|
-
) {
|
|
795
|
-
return { userId, orderId };
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Query parameters
|
|
799
|
-
// Route: GET /api/v1/users?page=1&limit=10&sort=name
|
|
800
|
-
@Get()
|
|
801
|
-
async getUsers(
|
|
802
|
-
@Query("page") page: string,
|
|
803
|
-
@Query("limit") limit: string,
|
|
804
|
-
@Query("sort") sort: string,
|
|
805
|
-
) {
|
|
806
|
-
return {
|
|
807
|
-
page: parseInt(page) || 1,
|
|
808
|
-
limit: parseInt(limit) || 10,
|
|
809
|
-
sort: sort || "id",
|
|
810
|
-
users: [],
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// Header extraction
|
|
815
|
-
@Get("/profile")
|
|
816
|
-
async getProfile(@Header("Authorization") auth: string) {
|
|
817
|
-
return { authorized: !!auth };
|
|
818
|
-
}
|
|
252
|
+
import { Service, ConditionalOnMissingBean } from 'fragment-ts';
|
|
253
|
+
import { DefaultLoggerService } from './default-logger.service';
|
|
254
|
+
import { LoggerService } from './logger.service.interface';
|
|
819
255
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return { created: users.length };
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Full request/response access
|
|
828
|
-
@Get("/advanced")
|
|
829
|
-
async advancedRoute(@Req() req: Request, @Res() res: Response) {
|
|
830
|
-
res.status(200).json({
|
|
831
|
-
method: req.method,
|
|
832
|
-
path: req.path,
|
|
833
|
-
headers: req.headers,
|
|
834
|
-
});
|
|
835
|
-
}
|
|
256
|
+
@Service()
|
|
257
|
+
@ConditionalOnMissingBean(LoggerService)
|
|
258
|
+
export class DefaultLoggerService implements LoggerService {
|
|
259
|
+
// Only registered if no other LoggerService is available
|
|
836
260
|
}
|
|
837
261
|
```
|
|
838
262
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
## Custom Middleware
|
|
263
|
+
## Lifecycle Hooks
|
|
842
264
|
|
|
843
|
-
###
|
|
265
|
+
### PostConstruct & PreDestroy
|
|
844
266
|
|
|
845
267
|
```typescript
|
|
846
|
-
|
|
847
|
-
import {
|
|
848
|
-
|
|
849
|
-
export class LoggerMiddleware {
|
|
850
|
-
use(req: Request, res: Response, next: NextFunction) {
|
|
851
|
-
const start = Date.now();
|
|
852
|
-
|
|
853
|
-
res.on("finish", () => {
|
|
854
|
-
const duration = Date.now() - start;
|
|
855
|
-
console.log(
|
|
856
|
-
`${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`,
|
|
857
|
-
);
|
|
858
|
-
});
|
|
268
|
+
import { Service, PostConstruct, PreDestroy } from 'fragment-ts';
|
|
269
|
+
import { ConnectionPool } from './connection-pool';
|
|
859
270
|
|
|
860
|
-
|
|
271
|
+
@Service()
|
|
272
|
+
export class DatabaseService {
|
|
273
|
+
private connectionPool!: ConnectionPool;
|
|
274
|
+
|
|
275
|
+
@PostConstruct()
|
|
276
|
+
init() {
|
|
277
|
+
console.log('Initializing database connection pool');
|
|
278
|
+
this.connectionPool = new ConnectionPool();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getConnection() {
|
|
282
|
+
return this.connectionPool.getConnection();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@PreDestroy()
|
|
286
|
+
cleanup() {
|
|
287
|
+
console.log('Cleaning up database connections');
|
|
288
|
+
this.connectionPool.closeAll();
|
|
861
289
|
}
|
|
862
290
|
}
|
|
863
|
-
|
|
864
|
-
// src/main.ts
|
|
865
|
-
import "reflect-metadata";
|
|
866
|
-
import { FragmentApplication, FragmentWebApplication } from "fragment-ts";
|
|
867
|
-
import { LoggerMiddleware } from "./middlewares/logger.middleware";
|
|
868
|
-
|
|
869
|
-
@FragmentApplication({
|
|
870
|
-
port: 3000,
|
|
871
|
-
autoScan: true,
|
|
872
|
-
})
|
|
873
|
-
class Application {}
|
|
874
|
-
|
|
875
|
-
async function bootstrap() {
|
|
876
|
-
const app = new FragmentWebApplication();
|
|
877
|
-
const expressApp = app.getExpressApp();
|
|
878
|
-
|
|
879
|
-
// Apply middleware
|
|
880
|
-
const logger = new LoggerMiddleware();
|
|
881
|
-
expressApp.use(logger.use.bind(logger));
|
|
882
|
-
|
|
883
|
-
await app.bootstrap(Application);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
bootstrap();
|
|
887
291
|
```
|
|
888
292
|
|
|
889
|
-
---
|
|
890
|
-
|
|
891
293
|
## Testing
|
|
892
294
|
|
|
893
|
-
###
|
|
894
|
-
|
|
895
|
-
```bash
|
|
896
|
-
# Generate test file alongside controller
|
|
897
|
-
fragment generate controller users
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
**test/users.spec.ts**
|
|
295
|
+
### Unit Testing a Service
|
|
901
296
|
|
|
902
297
|
```typescript
|
|
903
|
-
import {
|
|
904
|
-
import {
|
|
905
|
-
import {
|
|
298
|
+
import { UserService } from './user.service';
|
|
299
|
+
import { UserRepository } from '../repositories/user.repository';
|
|
300
|
+
import { User } from '../entities/user.entity';
|
|
906
301
|
|
|
907
|
-
describe(
|
|
302
|
+
describe('UserService', () => {
|
|
908
303
|
let userService: UserService;
|
|
909
|
-
|
|
304
|
+
let mockUserRepository: jest.Mocked<UserRepository>;
|
|
305
|
+
|
|
910
306
|
beforeEach(() => {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
307
|
+
mockUserRepository = {
|
|
308
|
+
findAll: jest.fn(),
|
|
309
|
+
findById: jest.fn(),
|
|
310
|
+
save: jest.fn()
|
|
311
|
+
} as any;
|
|
312
|
+
|
|
313
|
+
userService = new UserService();
|
|
314
|
+
// Use reflection to set the private property
|
|
315
|
+
Object.defineProperty(userService, 'userRepository', {
|
|
316
|
+
value: mockUserRepository
|
|
920
317
|
});
|
|
921
|
-
|
|
922
|
-
expect(user).toBeTruthy();
|
|
923
|
-
expect(user.email).toBe("test@example.com");
|
|
924
|
-
expect(user.name).toBe("Test User");
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
it("should find user by id", async () => {
|
|
928
|
-
const user = await userService.findOne("1");
|
|
929
|
-
expect(user).toBeTruthy();
|
|
930
|
-
expect(user.id).toBe(1);
|
|
931
318
|
});
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
319
|
+
|
|
320
|
+
describe('findAll', () => {
|
|
321
|
+
it('should return all users', async () => {
|
|
322
|
+
const mockUsers = [{ id: '1', name: 'John' }, { id: '2', name: 'Jane' }];
|
|
323
|
+
mockUserRepository.findAll.mockResolvedValue(mockUsers);
|
|
324
|
+
|
|
325
|
+
const result = await userService.findAll();
|
|
326
|
+
|
|
327
|
+
expect(result).toEqual(mockUsers);
|
|
328
|
+
expect(mockUserRepository.findAll).toHaveBeenCalled();
|
|
329
|
+
});
|
|
940
330
|
});
|
|
941
331
|
});
|
|
942
332
|
```
|
|
943
333
|
|
|
944
|
-
|
|
945
|
-
# Run tests
|
|
946
|
-
fragment test
|
|
334
|
+
## Environment Configuration
|
|
947
335
|
|
|
948
|
-
|
|
949
|
-
fragment test --coverage
|
|
336
|
+
### fragment.json Example
|
|
950
337
|
|
|
951
|
-
|
|
952
|
-
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"type": "mysql",
|
|
341
|
+
"host": "localhost",
|
|
342
|
+
"port": 3306,
|
|
343
|
+
"username": "root",
|
|
344
|
+
"password": "password",
|
|
345
|
+
"database": "my_app",
|
|
346
|
+
"entities": ["dist/entities/**/*.js"],
|
|
347
|
+
"migrations": ["dist/migrations/**/*.js"],
|
|
348
|
+
"subscribers": ["dist/subscribers/**/*.js"],
|
|
349
|
+
"logging": true,
|
|
350
|
+
"synchronize": false,
|
|
351
|
+
"migrationsRun": true
|
|
352
|
+
}
|
|
953
353
|
```
|
|
954
354
|
|
|
955
|
-
|
|
355
|
+
### Environment Variables
|
|
956
356
|
|
|
957
|
-
|
|
357
|
+
```
|
|
358
|
+
# .env file
|
|
359
|
+
PORT=3000
|
|
360
|
+
DB_HOST=production-db.example.com
|
|
361
|
+
DB_USER=app_user
|
|
362
|
+
DB_PASSWORD=secure_password
|
|
363
|
+
FEATURE_FLAG_NEW_UI=true
|
|
364
|
+
```
|
|
958
365
|
|
|
959
|
-
|
|
366
|
+
## Production Deployment
|
|
960
367
|
|
|
961
|
-
|
|
368
|
+
### Building for Production
|
|
962
369
|
|
|
963
370
|
```bash
|
|
964
|
-
#
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
# Verify build
|
|
968
|
-
ls dist/
|
|
371
|
+
# Install dependencies
|
|
372
|
+
npm install
|
|
969
373
|
|
|
970
|
-
#
|
|
971
|
-
|
|
972
|
-
export PORT=8080
|
|
973
|
-
export DATABASE_URL=postgresql://user:pass@host:5432/db
|
|
974
|
-
export JWT_SECRET=super-secret-production-key
|
|
374
|
+
# Build TypeScript to JavaScript
|
|
375
|
+
frg build
|
|
975
376
|
|
|
976
|
-
#
|
|
977
|
-
fragment migrate:run
|
|
978
|
-
|
|
979
|
-
# Start production server
|
|
377
|
+
# Start the application
|
|
980
378
|
npm start
|
|
981
379
|
```
|
|
982
380
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
**Dockerfile:**
|
|
381
|
+
### Docker Configuration
|
|
986
382
|
|
|
987
383
|
```dockerfile
|
|
988
|
-
FROM node:
|
|
384
|
+
FROM node:16-alpine
|
|
989
385
|
|
|
990
386
|
WORKDIR /app
|
|
991
387
|
|
|
992
|
-
# Copy package
|
|
388
|
+
# Copy package.json and install dependencies
|
|
993
389
|
COPY package*.json ./
|
|
994
|
-
|
|
995
|
-
# Install production dependencies
|
|
996
390
|
RUN npm ci --only=production
|
|
997
391
|
|
|
998
|
-
# Copy
|
|
999
|
-
COPY
|
|
392
|
+
# Copy built application
|
|
393
|
+
COPY dist ./dist
|
|
394
|
+
COPY fragment.json ./
|
|
1000
395
|
|
|
1001
|
-
# Build TypeScript
|
|
1002
|
-
RUN npm run build
|
|
1003
|
-
|
|
1004
|
-
# Expose port
|
|
1005
396
|
EXPOSE 3000
|
|
1006
397
|
|
|
1007
|
-
|
|
1008
|
-
HEALTHCHECK --interval=30s --timeout=3s \
|
|
1009
|
-
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
|
|
1010
|
-
|
|
1011
|
-
# Start application
|
|
1012
|
-
CMD ["npm", "start"]
|
|
1013
|
-
```
|
|
1014
|
-
|
|
1015
|
-
**docker-compose.yml:**
|
|
1016
|
-
|
|
1017
|
-
```yaml
|
|
1018
|
-
version: "3.8"
|
|
1019
|
-
|
|
1020
|
-
services:
|
|
1021
|
-
app:
|
|
1022
|
-
build: .
|
|
1023
|
-
ports:
|
|
1024
|
-
- "3000:3000"
|
|
1025
|
-
environment:
|
|
1026
|
-
- NODE_ENV=production
|
|
1027
|
-
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
|
|
1028
|
-
- JWT_SECRET=your-secret-key
|
|
1029
|
-
depends_on:
|
|
1030
|
-
- db
|
|
1031
|
-
restart: unless-stopped
|
|
1032
|
-
|
|
1033
|
-
db:
|
|
1034
|
-
image: postgres:15-alpine
|
|
1035
|
-
environment:
|
|
1036
|
-
- POSTGRES_DB=myapp
|
|
1037
|
-
- POSTGRES_USER=postgres
|
|
1038
|
-
- POSTGRES_PASSWORD=password
|
|
1039
|
-
volumes:
|
|
1040
|
-
- db-data:/var/lib/postgresql/data
|
|
1041
|
-
restart: unless-stopped
|
|
1042
|
-
|
|
1043
|
-
volumes:
|
|
1044
|
-
db-data:
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
```bash
|
|
1048
|
-
# Build and start services
|
|
1049
|
-
docker-compose up -d
|
|
1050
|
-
|
|
1051
|
-
# View logs
|
|
1052
|
-
docker-compose logs -f app
|
|
1053
|
-
|
|
1054
|
-
# Run migrations in container
|
|
1055
|
-
docker-compose exec app fragment migrate:run
|
|
1056
|
-
|
|
1057
|
-
# Stop services
|
|
1058
|
-
docker-compose down
|
|
1059
|
-
|
|
1060
|
-
# Stop and remove volumes
|
|
1061
|
-
docker-compose down -v
|
|
398
|
+
CMD ["node", "dist/app.js"]
|
|
1062
399
|
```
|
|
1063
400
|
|
|
1064
|
-
---
|
|
1065
|
-
|
|
1066
|
-
## CLI Reference
|
|
1067
|
-
|
|
1068
|
-
### Complete CLI Command Reference
|
|
1069
|
-
|
|
1070
|
-
**Project Management**
|
|
1071
|
-
|
|
1072
|
-
```bash
|
|
1073
|
-
fragment init [dir] # Initialize new project
|
|
1074
|
-
fragment init . --features=auth # Initialize with features in current dir
|
|
1075
|
-
fragment serve # Start development server
|
|
1076
|
-
fragment serve --port=4000 # Start on custom port
|
|
1077
|
-
fragment serve --no-watch # Disable hot reload
|
|
1078
|
-
fragment build # Build for production
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
**Code Generation**
|
|
1082
|
-
|
|
1083
|
-
```bash
|
|
1084
|
-
fragment generate <type> <name> # Generate component
|
|
1085
|
-
fragment g <type> <name> # Shorthand
|
|
1086
|
-
|
|
1087
|
-
# Types:
|
|
1088
|
-
fragment generate controller users
|
|
1089
|
-
fragment generate service auth
|
|
1090
|
-
fragment generate entity product
|
|
1091
|
-
fragment generate repository users
|
|
1092
|
-
fragment generate dto create-user
|
|
1093
|
-
fragment generate resource product # Generates all of the above
|
|
1094
|
-
|
|
1095
|
-
# With options:
|
|
1096
|
-
fragment generate service users --with-repository
|
|
1097
|
-
```
|
|
1098
|
-
|
|
1099
|
-
**Database Operations**
|
|
1100
|
-
|
|
1101
|
-
```bash
|
|
1102
|
-
fragment migrate # Run pending migrations
|
|
1103
|
-
fragment migrate:create <name> # Create new migration
|
|
1104
|
-
fragment migrate:run # Run migrations
|
|
1105
|
-
fragment migrate:revert # Revert last migration
|
|
1106
|
-
fragment migrate:refresh # Drop and re-run all migrations
|
|
1107
|
-
fragment migrate:status # Show migration status
|
|
1108
|
-
fragment schema:sync # Sync schema (dev only)
|
|
1109
|
-
fragment schema:drop # Drop all tables
|
|
1110
|
-
fragment seed # Run seeds
|
|
1111
|
-
fragment seed:create <name> # Create seed file
|
|
1112
|
-
```
|
|
1113
|
-
|
|
1114
|
-
**Diagnostics**
|
|
1115
|
-
|
|
1116
|
-
```bash
|
|
1117
|
-
fragment routes # List all routes (auto-detect mode)
|
|
1118
|
-
fragment routes --env=dev # List routes from TypeScript src/
|
|
1119
|
-
fragment routes --env=prod # List routes from compiled dist/
|
|
1120
|
-
fragment beans # List all beans
|
|
1121
|
-
fragment beans --tree # Show beans as tree
|
|
1122
|
-
fragment info # Show app information
|
|
1123
|
-
fragment config # Show configuration
|
|
1124
|
-
fragment version # Show Fragment version
|
|
1125
|
-
```
|
|
1126
|
-
|
|
1127
|
-
### Diagnostics & Debugging
|
|
1128
|
-
|
|
1129
|
-
**Environment-Specific Diagnostics**
|
|
1130
|
-
|
|
1131
|
-
```bash
|
|
1132
|
-
# Auto-detect (prefers src/ if available, falls back to dist/)
|
|
1133
|
-
fragment routes
|
|
1134
|
-
fragment beans
|
|
1135
|
-
|
|
1136
|
-
# Force development mode (use TypeScript source)
|
|
1137
|
-
fragment routes --env=dev
|
|
1138
|
-
fragment beans --env=dev --tree
|
|
1139
|
-
|
|
1140
|
-
# Force production mode (use compiled JavaScript)
|
|
1141
|
-
fragment routes --env=prod
|
|
1142
|
-
fragment beans --env=prod
|
|
1143
|
-
|
|
1144
|
-
# Show application info
|
|
1145
|
-
fragment info
|
|
1146
|
-
# Output:
|
|
1147
|
-
# 📊 Application Information:
|
|
1148
|
-
# Name: my-app
|
|
1149
|
-
# Version: 1.0.0
|
|
1150
|
-
# Node: v18.17.0
|
|
1151
|
-
# Platform: darwin
|
|
1152
|
-
# Architecture: arm64
|
|
1153
|
-
# Environment: development
|
|
1154
|
-
# Source: ✓ src/
|
|
1155
|
-
# Built: ✓ dist/
|
|
1156
|
-
|
|
1157
|
-
# View configuration
|
|
1158
|
-
fragment config
|
|
1159
|
-
# Shows contents of fragment.json
|
|
1160
|
-
|
|
1161
|
-
# Check version
|
|
1162
|
-
fragment version
|
|
1163
|
-
# ✨ Fragment Framework v1.0.0
|
|
1164
|
-
```
|
|
1165
|
-
|
|
1166
|
-
---
|
|
1167
|
-
|
|
1168
|
-
## Best Practices
|
|
1169
|
-
|
|
1170
|
-
### Development
|
|
1171
|
-
|
|
1172
|
-
1. **Always use decorators** - Leverage `@Controller`, `@Service`, `@Injectable` for clean architecture
|
|
1173
|
-
2. **Keep services focused** - One responsibility per service (Single Responsibility Principle)
|
|
1174
|
-
3. **Use DTOs** - Define Data Transfer Objects for request/response validation
|
|
1175
|
-
4. **Type everything** - Take advantage of TypeScript's type system
|
|
1176
|
-
5. **Use environment variables** - Never hardcode secrets or configuration
|
|
1177
|
-
|
|
1178
|
-
### Database
|
|
1179
|
-
|
|
1180
|
-
1. **Use migrations in production** - Never use `synchronize: true` in production
|
|
1181
|
-
2. **Version your migrations** - Keep migration files in version control
|
|
1182
|
-
3. **Test migrations** - Always test both `up` and `down` migrations
|
|
1183
|
-
4. **Use transactions** - Wrap related operations in database transactions
|
|
1184
|
-
5. **Index strategically** - Add indexes for frequently queried columns
|
|
1185
|
-
|
|
1186
|
-
### Security
|
|
1187
|
-
|
|
1188
|
-
1. **Hash passwords** - Always use `AuthModule.hashPassword()`
|
|
1189
|
-
2. **Validate input** - Use DTOs with class-validator
|
|
1190
|
-
3. **Rate limiting** - Implement rate limiting on public endpoints
|
|
1191
|
-
4. **CORS configuration** - Configure CORS appropriately for your frontend
|
|
1192
|
-
5. **Environment-specific secrets** - Use different secrets for dev/prod
|
|
1193
|
-
|
|
1194
|
-
### Testing
|
|
1195
|
-
|
|
1196
|
-
1. **Test business logic** - Focus on service and repository tests
|
|
1197
|
-
2. **Mock external dependencies** - Don't hit real databases or APIs in tests
|
|
1198
|
-
3. **Test edge cases** - Test error conditions and boundary cases
|
|
1199
|
-
4. **Use descriptive test names** - Make test failures easy to understand
|
|
1200
|
-
|
|
1201
|
-
### Deployment
|
|
1202
|
-
|
|
1203
|
-
1. **Use Docker** - Containerize your application for consistent deployments
|
|
1204
|
-
2. **Health checks** - Implement health check endpoints
|
|
1205
|
-
3. **Logging** - Use structured logging for production
|
|
1206
|
-
4. **Monitoring** - Set up monitoring and alerting
|
|
1207
|
-
5. **Backup strategy** - Regular database backups
|
|
1208
|
-
|
|
1209
|
-
---
|
|
1210
|
-
|
|
1211
401
|
## Troubleshooting
|
|
1212
402
|
|
|
1213
|
-
### Common Issues
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
```
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
# - Check network connectivity
|
|
1254
|
-
```
|
|
1255
|
-
|
|
1256
|
-
#### Issue: "Decorators not working"
|
|
1257
|
-
|
|
1258
|
-
```bash
|
|
1259
|
-
# Ensure tsconfig.json has these settings:
|
|
1260
|
-
{
|
|
1261
|
-
"compilerOptions": {
|
|
1262
|
-
"experimentalDecorators": true,
|
|
1263
|
-
"emitDecoratorMetadata": true
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
# Rebuild after changes
|
|
1268
|
-
npm run build
|
|
1269
|
-
```
|
|
1270
|
-
|
|
1271
|
-
#### Issue: "Routes not registering"
|
|
1272
|
-
|
|
1273
|
-
```bash
|
|
1274
|
-
# Check if components are being scanned
|
|
1275
|
-
fragment routes
|
|
1276
|
-
|
|
1277
|
-
# If no routes shown:
|
|
1278
|
-
# 1. Ensure files follow naming convention (*.controller.ts)
|
|
1279
|
-
# 2. Verify decorators are applied correctly
|
|
1280
|
-
# 3. Check if autoScan is enabled in @FragmentApplication
|
|
1281
|
-
# 4. Try manual import in main.ts
|
|
1282
|
-
|
|
1283
|
-
# Force specific environment
|
|
1284
|
-
fragment routes --env=dev
|
|
1285
|
-
fragment routes --env=prod
|
|
1286
|
-
```
|
|
1287
|
-
|
|
1288
|
-
#### Issue: "Circular dependency detected"
|
|
1289
|
-
|
|
1290
|
-
```bash
|
|
1291
|
-
# Error: Circular dependency detected: UserService
|
|
1292
|
-
|
|
1293
|
-
# Solution: Use property injection instead of constructor injection
|
|
1294
|
-
# Before (circular):
|
|
1295
|
-
@Service()
|
|
1296
|
-
class UserService {
|
|
1297
|
-
constructor(private authService: AuthService) {}
|
|
1298
|
-
}
|
|
1299
|
-
@Service()
|
|
1300
|
-
class AuthService {
|
|
1301
|
-
constructor(private userService: UserService) {} // Circular!
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
# After (fixed):
|
|
1305
|
-
@Service()
|
|
1306
|
-
class UserService {
|
|
1307
|
-
@Autowired()
|
|
1308
|
-
private authService: AuthService; // Property injection breaks cycle
|
|
1309
|
-
}
|
|
1310
|
-
```
|
|
1311
|
-
|
|
1312
|
-
#### Issue: "Hot reload not working"
|
|
1313
|
-
|
|
1314
|
-
```bash
|
|
1315
|
-
# Check if chokidar is installed
|
|
1316
|
-
npm install --save-dev chokidar
|
|
1317
|
-
|
|
1318
|
-
# Restart with watch mode explicitly enabled
|
|
1319
|
-
fragment serve --watch
|
|
1320
|
-
|
|
1321
|
-
# Check file watch patterns in serve command
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
#### Issue: "Migration fails"
|
|
1325
|
-
|
|
1326
|
-
```bash
|
|
1327
|
-
# Revert last migration
|
|
1328
|
-
fragment migrate:revert
|
|
1329
|
-
|
|
1330
|
-
# Check migration status
|
|
1331
|
-
fragment migrate:status
|
|
1332
|
-
|
|
1333
|
-
# Fix migration file and re-run
|
|
1334
|
-
fragment migrate:run
|
|
1335
|
-
|
|
1336
|
-
# If all else fails, drop and recreate (DEV ONLY!)
|
|
1337
|
-
fragment schema:drop
|
|
1338
|
-
fragment migrate:run
|
|
1339
|
-
```
|
|
1340
|
-
|
|
1341
|
-
#### Issue: "TypeScript compilation errors"
|
|
1342
|
-
|
|
1343
|
-
```bash
|
|
1344
|
-
# Check TypeScript version compatibility
|
|
1345
|
-
npm list typescript
|
|
1346
|
-
|
|
1347
|
-
# Clear build artifacts
|
|
1348
|
-
rm -rf dist/
|
|
1349
|
-
npm run build
|
|
1350
|
-
|
|
1351
|
-
# Check for conflicting type definitions
|
|
1352
|
-
npm list @types
|
|
1353
|
-
```
|
|
1354
|
-
|
|
1355
|
-
---
|
|
1356
|
-
|
|
1357
|
-
## Performance Tips
|
|
1358
|
-
|
|
1359
|
-
### Optimize Database Queries
|
|
1360
|
-
|
|
1361
|
-
```typescript
|
|
1362
|
-
// Use eager loading to avoid N+1 queries
|
|
1363
|
-
const users = await userRepository.find({
|
|
1364
|
-
relations: ["posts", "comments"],
|
|
1365
|
-
});
|
|
1366
|
-
|
|
1367
|
-
// Use indexes for frequently queried columns
|
|
1368
|
-
@Entity()
|
|
1369
|
-
export class User {
|
|
1370
|
-
@Index()
|
|
1371
|
-
@Column({ unique: true })
|
|
1372
|
-
email: string;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
// Use pagination for large datasets
|
|
1376
|
-
const [users, total] = await userRepository.findAndCount({
|
|
1377
|
-
skip: (page - 1) * limit,
|
|
1378
|
-
take: limit,
|
|
1379
|
-
});
|
|
1380
|
-
```
|
|
1381
|
-
|
|
1382
|
-
### Caching Strategies
|
|
1383
|
-
|
|
1384
|
-
```typescript
|
|
1385
|
-
// In-memory cache for frequently accessed data
|
|
1386
|
-
@Service()
|
|
1387
|
-
export class CacheService {
|
|
1388
|
-
private cache = new Map<string, any>();
|
|
1389
|
-
|
|
1390
|
-
get(key: string) {
|
|
1391
|
-
return this.cache.get(key);
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
set(key: string, value: any, ttl: number = 3600000) {
|
|
1395
|
-
this.cache.set(key, value);
|
|
1396
|
-
setTimeout(() => this.cache.delete(key), ttl);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
```
|
|
1400
|
-
|
|
1401
|
-
### Connection Pooling
|
|
1402
|
-
|
|
1403
|
-
```json
|
|
1404
|
-
// fragment.json
|
|
1405
|
-
{
|
|
1406
|
-
"database": {
|
|
1407
|
-
"poolSize": ${DB_POOL_SIZE},
|
|
1408
|
-
"maxQueryExecutionTime": ${DB_MAX_QUERY_TIME}
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
```
|
|
1412
|
-
|
|
1413
|
-
---
|
|
1414
|
-
|
|
1415
|
-
## Summary
|
|
1416
|
-
|
|
1417
|
-
Fragment Framework provides:
|
|
1418
|
-
|
|
1419
|
-
- ✅ **Quick project initialization** - `fragment init .` or `fragment init my-app`
|
|
1420
|
-
- ✅ **Hot reload development** - Automatic server restart on file changes
|
|
1421
|
-
- ✅ **Comprehensive CLI** - Code generation, migrations, diagnostics
|
|
1422
|
-
- ✅ **Built-in authentication** - JWT, password hashing, role guards
|
|
1423
|
-
- ✅ **Database migrations** - TypeORM integration with full migration support
|
|
1424
|
-
- ✅ **AI integrations** - OpenAI, Ollama, and custom providers
|
|
1425
|
-
- ✅ **Production-ready** - Docker, health checks, logging
|
|
1426
|
-
- ✅ **Type-safe DI** - Automatic dependency injection
|
|
1427
|
-
- ✅ **Convention over configuration** - Sensible defaults, minimal setup
|
|
1428
|
-
- ✅ **Environment flexibility** - Dev/prod modes with `--env` flag
|
|
1429
|
-
|
|
1430
|
-
**Start building your next TypeScript application with Fragment today!**
|
|
1431
|
-
|
|
1432
|
-
```bash
|
|
1433
|
-
npm install -g fragment
|
|
1434
|
-
fragment init my-app
|
|
1435
|
-
cd my-app
|
|
1436
|
-
fragment serve
|
|
1437
|
-
```
|
|
1438
|
-
|
|
1439
|
-
Visit [https://fragment.digitwhale.com](https://fragment.digitwhale.com) for more documentation and examples.
|
|
403
|
+
### Common Issues
|
|
404
|
+
|
|
405
|
+
1. **"Cannot read properties of undefined" errors**: This typically means dependency injection failed. Check:
|
|
406
|
+
- All services/controllers have appropriate decorators (`@Service()`, `@Controller()`)
|
|
407
|
+
- TypeScript compiler option `emitDecoratorMetadata` is enabled in `tsconfig.json`
|
|
408
|
+
- All dependencies are properly registered before use
|
|
409
|
+
|
|
410
|
+
2. **TypeORM connection issues**:
|
|
411
|
+
- Verify `fragment.json` configuration is correct
|
|
412
|
+
- Ensure database is accessible from your application
|
|
413
|
+
- Check entity paths match your compiled output directory
|
|
414
|
+
|
|
415
|
+
3. **Route registration problems**:
|
|
416
|
+
- Verify controller classes have `@Controller()` decorator
|
|
417
|
+
- Check route methods have appropriate HTTP decorators (`@Get()`, `@Post()`, etc.)
|
|
418
|
+
- Ensure controller classes are exported and discoverable
|
|
419
|
+
|
|
420
|
+
### Debugging Tips
|
|
421
|
+
|
|
422
|
+
1. Enable detailed logging by setting environment variable:
|
|
423
|
+
```bash
|
|
424
|
+
DEBUG=fragment:* npm run dev
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
2. Use the container API to inspect registered components:
|
|
428
|
+
```typescript
|
|
429
|
+
import { DIContainer } from 'fragment-ts';
|
|
430
|
+
|
|
431
|
+
const container = DIContainer.getInstance();
|
|
432
|
+
console.log('Registered components:', container.getAllInstances());
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
3. Verify component scanning is working:
|
|
436
|
+
```typescript
|
|
437
|
+
import { MetadataStorage } from 'fragment-ts';
|
|
438
|
+
|
|
439
|
+
const storage = MetadataStorage.getInstance();
|
|
440
|
+
console.log('Discovered classes:', storage.getAllClasses());
|
|
441
|
+
console.log('Discovered methods:', storage.getAllMethods());
|
|
442
|
+
```
|