@avleon/core 0.0.46 → 0.0.49

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 (142) hide show
  1. package/License +21 -21
  2. package/README.md +666 -666
  3. package/dist/chunk-9hOWP6kD.cjs +64 -0
  4. package/dist/chunk-DORXReHP.js +37 -0
  5. package/dist/index-CWUcfHYp.d.ts +1283 -0
  6. package/dist/index-CzAzauXZ.d.cts +1282 -0
  7. package/dist/index.cjs +3194 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.js +3022 -79
  10. package/dist/index.js.map +1 -0
  11. package/dist/lib-Bk8hUm06.cjs +7847 -0
  12. package/dist/lib-Bk8hUm06.cjs.map +1 -0
  13. package/dist/lib-CvDxBMkR.js +7843 -0
  14. package/dist/lib-CvDxBMkR.js.map +1 -0
  15. package/package.json +41 -103
  16. package/dist/application.test.d.ts +0 -1
  17. package/dist/application.test.js +0 -15
  18. package/dist/authentication.d.ts +0 -13
  19. package/dist/authentication.js +0 -16
  20. package/dist/cache.d.ts +0 -12
  21. package/dist/cache.js +0 -78
  22. package/dist/cache.test.d.ts +0 -1
  23. package/dist/cache.test.js +0 -36
  24. package/dist/collection.d.ts +0 -43
  25. package/dist/collection.js +0 -231
  26. package/dist/collection.test.d.ts +0 -1
  27. package/dist/collection.test.js +0 -59
  28. package/dist/config.d.ts +0 -18
  29. package/dist/config.js +0 -58
  30. package/dist/config.test.d.ts +0 -1
  31. package/dist/config.test.js +0 -40
  32. package/dist/constants.d.ts +0 -1
  33. package/dist/constants.js +0 -4
  34. package/dist/container.d.ts +0 -30
  35. package/dist/container.js +0 -55
  36. package/dist/controller.d.ts +0 -50
  37. package/dist/controller.js +0 -71
  38. package/dist/controller.test.d.ts +0 -1
  39. package/dist/controller.test.js +0 -97
  40. package/dist/core/application.d.ts +0 -74
  41. package/dist/core/application.js +0 -424
  42. package/dist/core/router.d.ts +0 -44
  43. package/dist/core/router.js +0 -520
  44. package/dist/core/testing.d.ts +0 -21
  45. package/dist/core/testing.js +0 -104
  46. package/dist/core/types.d.ts +0 -67
  47. package/dist/core/types.js +0 -2
  48. package/dist/decorators.d.ts +0 -15
  49. package/dist/decorators.js +0 -41
  50. package/dist/environment-variables.d.ts +0 -49
  51. package/dist/environment-variables.js +0 -130
  52. package/dist/environment-variables.test.d.ts +0 -1
  53. package/dist/environment-variables.test.js +0 -70
  54. package/dist/event-dispatcher.d.ts +0 -22
  55. package/dist/event-dispatcher.js +0 -97
  56. package/dist/event-subscriber.d.ts +0 -14
  57. package/dist/event-subscriber.js +0 -87
  58. package/dist/exceptions/http-exceptions.d.ts +0 -50
  59. package/dist/exceptions/http-exceptions.js +0 -85
  60. package/dist/exceptions/index.d.ts +0 -1
  61. package/dist/exceptions/index.js +0 -17
  62. package/dist/exceptions/system-exception.d.ts +0 -22
  63. package/dist/exceptions/system-exception.js +0 -26
  64. package/dist/file-storage.d.ts +0 -69
  65. package/dist/file-storage.js +0 -323
  66. package/dist/file-storage.test.d.ts +0 -1
  67. package/dist/file-storage.test.js +0 -117
  68. package/dist/helpers.d.ts +0 -11
  69. package/dist/helpers.js +0 -27
  70. package/dist/helpers.test.d.ts +0 -1
  71. package/dist/helpers.test.js +0 -95
  72. package/dist/index.d.ts +0 -57
  73. package/dist/interfaces/avleon-application.d.ts +0 -75
  74. package/dist/interfaces/avleon-application.js +0 -2
  75. package/dist/kenx-provider.d.ts +0 -7
  76. package/dist/kenx-provider.js +0 -44
  77. package/dist/kenx-provider.test.d.ts +0 -1
  78. package/dist/kenx-provider.test.js +0 -36
  79. package/dist/logger.d.ts +0 -12
  80. package/dist/logger.js +0 -87
  81. package/dist/logger.test.d.ts +0 -1
  82. package/dist/logger.test.js +0 -42
  83. package/dist/map-types.d.ts +0 -17
  84. package/dist/map-types.js +0 -89
  85. package/dist/middleware.d.ts +0 -34
  86. package/dist/middleware.js +0 -73
  87. package/dist/middleware.test.d.ts +0 -1
  88. package/dist/middleware.test.js +0 -121
  89. package/dist/multipart.d.ts +0 -17
  90. package/dist/multipart.js +0 -70
  91. package/dist/multipart.test.d.ts +0 -1
  92. package/dist/multipart.test.js +0 -87
  93. package/dist/openapi.d.ts +0 -410
  94. package/dist/openapi.js +0 -59
  95. package/dist/openapi.test.d.ts +0 -1
  96. package/dist/openapi.test.js +0 -111
  97. package/dist/params.d.ts +0 -17
  98. package/dist/params.js +0 -64
  99. package/dist/params.test.d.ts +0 -1
  100. package/dist/params.test.js +0 -83
  101. package/dist/queue.d.ts +0 -29
  102. package/dist/queue.js +0 -84
  103. package/dist/response.d.ts +0 -16
  104. package/dist/response.js +0 -56
  105. package/dist/results.d.ts +0 -20
  106. package/dist/results.js +0 -32
  107. package/dist/route-methods.d.ts +0 -25
  108. package/dist/route-methods.js +0 -60
  109. package/dist/route-methods.test.d.ts +0 -1
  110. package/dist/route-methods.test.js +0 -129
  111. package/dist/swagger-schema.d.ts +0 -37
  112. package/dist/swagger-schema.js +0 -454
  113. package/dist/swagger-schema.test.d.ts +0 -1
  114. package/dist/swagger-schema.test.js +0 -125
  115. package/dist/types/app-builder.interface.d.ts +0 -15
  116. package/dist/types/app-builder.interface.js +0 -8
  117. package/dist/types/application.interface.d.ts +0 -8
  118. package/dist/types/application.interface.js +0 -2
  119. package/dist/utils/common-utils.d.ts +0 -17
  120. package/dist/utils/common-utils.js +0 -108
  121. package/dist/utils/di-utils.d.ts +0 -1
  122. package/dist/utils/di-utils.js +0 -22
  123. package/dist/utils/hash.d.ts +0 -2
  124. package/dist/utils/hash.js +0 -11
  125. package/dist/utils/index.d.ts +0 -2
  126. package/dist/utils/index.js +0 -18
  127. package/dist/utils/object-utils.d.ts +0 -11
  128. package/dist/utils/object-utils.js +0 -198
  129. package/dist/utils/optional-require.d.ts +0 -8
  130. package/dist/utils/optional-require.js +0 -70
  131. package/dist/utils/validation-utils.d.ts +0 -13
  132. package/dist/utils/validation-utils.js +0 -119
  133. package/dist/validation.d.ts +0 -39
  134. package/dist/validation.js +0 -108
  135. package/dist/validation.test.d.ts +0 -1
  136. package/dist/validation.test.js +0 -61
  137. package/dist/validator-extend.d.ts +0 -7
  138. package/dist/validator-extend.js +0 -28
  139. package/dist/websocket.d.ts +0 -10
  140. package/dist/websocket.js +0 -21
  141. package/dist/websocket.test.d.ts +0 -1
  142. package/dist/websocket.test.js +0 -27
package/README.md CHANGED
@@ -1,667 +1,667 @@
1
- # Avleon
2
-
3
- ![npm version](https://img.shields.io/npm/v/@avleon/core.svg)
4
- ![Build](https://github.com/avleonjs/avleon-core/actions/workflows/release.yml/badge.svg)
5
- ![License](https://img.shields.io/npm/l/@avleon/core.svg)
6
-
7
- > **🚧 This project is in active development. APIs may change between versions.**
8
-
9
- Avleon is a TypeScript-first web framework built on top of [Fastify](https://fastify.dev), designed for building scalable, maintainable REST APIs with minimal boilerplate. It provides decorator-based routing, built-in dependency injection, automatic OpenAPI documentation, and first-class validation support.
10
-
11
- ---
12
-
13
- ## Table of Contents
14
-
15
- - [Features](#features)
16
- - [Installation](#installation)
17
- - [Quick Start](#quick-start)
18
- - [Core Concepts](#core-concepts)
19
- - [Application](#application)
20
- - [Controllers](#controllers)
21
- - [Route Methods](#route-methods)
22
- - [Parameter Decorators](#parameter-decorators)
23
- - [Error Handling](#error-handling)
24
- - [Middleware](#middleware)
25
- - [Authorization](#authorization)
26
- - [Validation](#validation)
27
- - [OpenAPI Documentation](#openapi-documentation)
28
- - [Advanced Features](#advanced-features)
29
- - [Database — Knex](#database--knex)
30
- - [Database — TypeORM](#database--typeorm)
31
- - [File Uploads](#file-uploads)
32
- - [Static Files](#static-files)
33
- - [WebSocket (Socket.IO)](#websocket-socketio)
34
- - [Route Mapping (Functional Style)](#route-mapping-functional-style)
35
- - [Testing](#testing)
36
- - [License](#license)
37
-
38
- ---
39
-
40
- ## Features
41
-
42
- - 🎯 **Decorator-based routing** — define controllers and routes with TypeScript decorators
43
- - 💉 **Dependency injection** — powered by [TypeDI](https://github.com/typestack/typedi)
44
- - 📄 **OpenAPI / Swagger** — automatic docs with Swagger UI or [Scalar](https://scalar.com)
45
- - ✅ **Validation** — request validation via [class-validator](https://github.com/typestack/class-validator)
46
- - 🔒 **Authorization** — flexible middleware-based auth system
47
- - 📁 **File uploads** — multipart form support out of the box
48
- - 🗄️ **Database** — TypeORM and Knex integrations
49
- - 🔌 **WebSocket** — Socket.IO integration
50
- - 🧪 **Testing** — built-in test utilities
51
-
52
- ---
53
-
54
- ## Installation
55
-
56
- Scaffold a new project using the CLI:
57
-
58
- ```bash
59
- npx @avleon/cli new myapp
60
- # or
61
- yarn dlx @avleon/cli new myapp
62
- # or
63
- pnpm dlx @avleon/cli new myapp
64
- ```
65
-
66
- Or install manually:
67
-
68
- ```bash
69
- npm install @avleon/core reflect-metadata class-validator class-transformer
70
- ```
71
-
72
- ---
73
-
74
- ## Quick Start
75
-
76
- ### Minimal (functional style)
77
-
78
- ```typescript
79
- import { Avleon } from '@avleon/core';
80
-
81
- const app = Avleon.createApplication();
82
-
83
- app.mapGet('/', () => ({ message: 'Hello, Avleon!' }));
84
-
85
- app.run(4000);
86
- ```
87
-
88
- ### Controller style
89
-
90
- ```typescript
91
- import { Avleon, ApiController, Get } from '@avleon/core';
92
-
93
- @ApiController('/')
94
- class HelloController {
95
- @Get()
96
- sayHello() {
97
- return { message: 'Hello, Avleon!' };
98
- }
99
- }
100
-
101
- const app = Avleon.createApplication();
102
- app.useControllers([HelloController]);
103
- app.run(4000);
104
- ```
105
-
106
- ---
107
-
108
- ## Core Concepts
109
-
110
- ### Application
111
-
112
- ```typescript
113
- import { Avleon } from '@avleon/core';
114
-
115
- const app = Avleon.createApplication();
116
-
117
- app.useCors({ origin: '*' });
118
- app.useControllers([UserController]);
119
- // Auto-discover controllers from a directory:
120
- // app.useControllers({ auto: true, path: 'src/controllers' });
121
-
122
- app.run(4000);
123
- ```
124
-
125
- ---
126
-
127
- ### Controllers
128
-
129
- ```typescript
130
- import { ApiController, Get, Post, Put, Delete } from '@avleon/core';
131
-
132
- @ApiController('/users')
133
- class UserController {
134
- @Get('/')
135
- getAll() { ... }
136
-
137
- @Post('/')
138
- create() { ... }
139
-
140
- @Put('/:id')
141
- update() { ... }
142
-
143
- @Delete('/:id')
144
- remove() { ... }
145
- }
146
- ```
147
-
148
- ---
149
-
150
- ### Route Methods
151
-
152
- | Decorator | HTTP Method |
153
- |-----------|-------------|
154
- | `@Get(path?)` | GET |
155
- | `@Post(path?)` | POST |
156
- | `@Put(path?)` | PUT |
157
- | `@Patch(path?)` | PATCH |
158
- | `@Delete(path?)` | DELETE |
159
-
160
- ---
161
-
162
- ### Parameter Decorators
163
-
164
- ```typescript
165
- @Get('/:id')
166
- async getUser(
167
- @Param('id') id: string,
168
- @Query('include') include: string,
169
- @Query() query: UserQuery, // maps full query to a DTO
170
- @Body() body: CreateUserDto,
171
- @Header('authorization') token: string,
172
- @AuthUser() user: CurrentUser,
173
- ) {
174
- // ...
175
- }
176
- ```
177
-
178
- | Decorator | Source |
179
- |-----------|--------|
180
- | `@Param(key?)` | Route path params |
181
- | `@Query(key?)` | Query string |
182
- | `@Body()` | Request body |
183
- | `@Header(key?)` | Request headers |
184
- | `@AuthUser()` | Current authenticated user |
185
-
186
- ---
187
-
188
- ### Error Handling
189
-
190
- ```typescript
191
- import { HttpExceptions, HttpResponse } from '@avleon/core';
192
-
193
- @Get('/:id')
194
- async getUser(@Param('id') id: string) {
195
- const user = await this.userService.findById(id);
196
-
197
- if (!user) {
198
- throw HttpExceptions.NotFound('User not found');
199
- }
200
-
201
- return HttpResponse.Ok(user);
202
- }
203
- ```
204
-
205
- Available exceptions: `NotFound`, `BadRequest`, `Unauthorized`, `Forbidden`, `InternalServerError`.
206
-
207
- ---
208
-
209
- ### Middleware
210
-
211
- ```typescript
212
- import { Middleware, AppMiddleware, IRequest, UseMiddleware } from '@avleon/core';
213
-
214
- @Middleware
215
- class LoggingMiddleware extends AppMiddleware {
216
- async invoke(req: IRequest) {
217
- console.log(`${req.method} ${req.url}`);
218
- return req;
219
- }
220
- }
221
-
222
- // Apply to entire controller
223
- @UseMiddleware(LoggingMiddleware)
224
- @ApiController('/users')
225
- class UserController { ... }
226
-
227
- // Or apply to a specific route
228
- @ApiController('/users')
229
- class UserController {
230
- @UseMiddleware(LoggingMiddleware)
231
- @Get('/')
232
- getAll() { ... }
233
- }
234
- ```
235
-
236
- ---
237
-
238
- ### Authorization
239
-
240
- **1 — Define your authorization class:**
241
-
242
- ```typescript
243
- import { CanAuthorize, AuthorizeMiddleware, IRequest } from '@avleon/core';
244
-
245
- @CanAuthorize
246
- class JwtAuthorization extends AuthorizeMiddleware {
247
- async authorize(req: IRequest, options?: any) {
248
- const token = req.headers['authorization']?.split(' ')[1];
249
- if (!token) throw HttpExceptions.Unauthorized('Missing token');
250
- req.user = verifyToken(token); // attach user to request
251
- }
252
- }
253
- ```
254
-
255
- **2 — Register with the app:**
256
-
257
- ```typescript
258
- app.useAuthorization(JwtAuthorization);
259
- ```
260
-
261
- **3 — Protect controllers or routes:**
262
-
263
- ```typescript
264
- // Protect entire controller
265
- @Authorized()
266
- @ApiController('/admin')
267
- class AdminController {
268
- @Get('/')
269
- dashboard(@AuthUser() user: User) {
270
- return user;
271
- }
272
- }
273
-
274
- // Protect specific route with roles
275
- @ApiController('/admin')
276
- class AdminController {
277
- @Authorized({ roles: ['admin'] })
278
- @Get('/stats')
279
- stats() { ... }
280
- }
281
- ```
282
-
283
- ---
284
-
285
- ### Validation
286
-
287
- Validation is powered by `class-validator`. Decorate your DTOs and Avleon validates automatically:
288
-
289
- ```typescript
290
- import { IsString, IsEmail, IsInt, Min, Max, IsOptional } from 'class-validator';
291
-
292
- class CreateUserDto {
293
- @IsString()
294
- @IsNotEmpty()
295
- name: string;
296
-
297
- @IsEmail()
298
- email: string;
299
-
300
- @IsInt()
301
- @Min(0)
302
- @Max(120)
303
- age: number;
304
-
305
- @IsOptional()
306
- @IsString()
307
- role?: string;
308
- }
309
-
310
- @Post('/')
311
- async createUser(@Body() body: CreateUserDto) {
312
- return this.userService.create(body);
313
- }
314
- ```
315
-
316
- ---
317
-
318
- ### OpenAPI Documentation
319
-
320
- **Inline config:**
321
-
322
- ```typescript
323
- app.useOpenApi({
324
- info: {
325
- title: 'User API',
326
- version: '1.0.0',
327
- description: 'API for managing users',
328
- },
329
- servers: [{ url: 'http://localhost:4000', description: 'Dev server' }],
330
- components: {
331
- securitySchemes: {
332
- bearerAuth: {
333
- type: 'http',
334
- scheme: 'bearer',
335
- bearerFormat: 'JWT',
336
- },
337
- },
338
- },
339
- });
340
- ```
341
-
342
- **Config class:**
343
-
344
- ```typescript
345
- import { AppConfig, IConfig, Environment } from '@avleon/core';
346
-
347
- @AppConfig
348
- export class OpenApiConfig implements IConfig {
349
- config(env: Environment) {
350
- return {
351
- info: { title: 'My API', version: '1.0.0' },
352
- routePrefix: '/docs',
353
- provider: 'scalar', // or 'default' for Swagger UI
354
- };
355
- }
356
- }
357
-
358
- // In app.ts
359
- if (app.isDevelopment()) {
360
- app.useOpenApi(OpenApiConfig);
361
- }
362
- ```
363
-
364
- **Route-level docs with `@OpenApi`:**
365
-
366
- ```typescript
367
- import { OpenApi, OpenApiProperty, OpenApiSchema } from '@avleon/core';
368
-
369
- @OpenApiSchema()
370
- export class UserQuery {
371
- @OpenApiProperty({ type: 'string', example: 'john', required: false })
372
- @IsOptional()
373
- search?: string;
374
-
375
- @OpenApiProperty({ type: 'integer', example: 1, required: false })
376
- @IsOptional()
377
- page?: number;
378
- }
379
-
380
- @OpenApi({
381
- summary: 'Get all users',
382
- tags: ['users'],
383
- security: [{ bearerAuth: [] }],
384
- response: {
385
- 200: {
386
- description: 'List of users',
387
- type: 'object',
388
- properties: {
389
- data: { type: 'array' },
390
- total: { type: 'integer', example: 100 },
391
- },
392
- },
393
- 401: { description: 'Unauthorized' },
394
- },
395
- })
396
- @Get('/')
397
- getAll(@Query() query: UserQuery) { ... }
398
- ```
399
-
400
- ---
401
-
402
- ## Advanced Features
403
-
404
- ### Database — Knex
405
-
406
- ```typescript
407
- app.useKnex({
408
- client: 'mysql',
409
- connection: {
410
- host: '127.0.0.1',
411
- port: 3306,
412
- user: 'root',
413
- password: 'password',
414
- database: 'myapp',
415
- },
416
- });
417
- ```
418
-
419
- Using a config class:
420
-
421
- ```typescript
422
- @AppConfig
423
- export class KnexConfig implements IConfig {
424
- config(env: Environment) {
425
- return {
426
- client: 'mysql',
427
- connection: {
428
- host: env.get('DB_HOST') || '127.0.0.1',
429
- port: env.get('DB_PORT') || 3306,
430
- user: env.get('DB_USER') || 'root',
431
- password: env.get('DB_PASS') || 'password',
432
- database: env.get('DB_NAME') || 'myapp',
433
- },
434
- };
435
- }
436
- }
437
-
438
- app.useKnex(KnexConfig);
439
- ```
440
-
441
- Using in a service:
442
-
443
- ```typescript
444
- import { DB, AppService } from '@avleon/core';
445
-
446
- @AppService
447
- export class UsersService {
448
- constructor(private readonly db: DB) {}
449
-
450
- async findAll() {
451
- return this.db.client.select('*').from('users');
452
- }
453
- }
454
- ```
455
-
456
- ---
457
-
458
- ### Database — TypeORM
459
-
460
- ```typescript
461
- app.useDataSource({
462
- type: 'postgres',
463
- host: 'localhost',
464
- port: 5432,
465
- username: 'postgres',
466
- password: 'password',
467
- database: 'avleon',
468
- entities: [User],
469
- synchronize: true,
470
- });
471
- ```
472
-
473
- Using a config class:
474
-
475
- ```typescript
476
- @AppConfig
477
- export class DataSourceConfig implements IConfig {
478
- config(env: Environment) {
479
- return {
480
- type: 'postgres',
481
- host: env.get('DB_HOST') || 'localhost',
482
- port: Number(env.get('DB_PORT')) || 5432,
483
- username: env.get('DB_USER') || 'postgres',
484
- password: env.get('DB_PASS') || 'password',
485
- database: env.get('DB_NAME') || 'avleon',
486
- entities: [User],
487
- synchronize: true,
488
- };
489
- }
490
- }
491
-
492
- app.useDataSource(DataSourceConfig);
493
- ```
494
-
495
- Using in a service:
496
-
497
- ```typescript
498
- import { AppService, InjectRepository } from '@avleon/core';
499
- import { Repository } from 'typeorm';
500
- import { User } from './user.entity';
501
-
502
- @AppService
503
- export class UserService {
504
- constructor(
505
- @InjectRepository(User)
506
- private readonly userRepo: Repository<User>,
507
- ) {}
508
-
509
- async findAll() {
510
- return this.userRepo.find();
511
- }
512
- }
513
- ```
514
-
515
- ---
516
-
517
- ### File Uploads
518
-
519
- ```typescript
520
- // Configure multipart support
521
- app.useMultipart({
522
- destination: path.join(process.cwd(), 'public/uploads'),
523
- limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
524
- });
525
- ```
526
-
527
- ```typescript
528
- import { FileStorage, UploadFile, MultipartFile } from '@avleon/core';
529
-
530
- @ApiController('/files')
531
- class FileController {
532
- constructor(private readonly fileStorage: FileStorage) {}
533
-
534
- @OpenApi({
535
- description: 'Upload a single file',
536
- body: {
537
- type: 'object',
538
- properties: {
539
- file: { type: 'string', format: 'binary' },
540
- },
541
- required: ['file'],
542
- },
543
- })
544
- @Post('/upload')
545
- async upload(@UploadFile('file') file: MultipartFile) {
546
- const result = await this.fileStorage.save(file);
547
- // optionally rename: this.fileStorage.save(file, { as: 'newname.jpg' })
548
- return result;
549
- // { uploadPath: '/uploads/...', staticPath: '/static/...' }
550
- }
551
- }
552
- ```
553
-
554
- ---
555
-
556
- ### Static Files
557
-
558
- ```typescript
559
- import path from 'path';
560
-
561
- app.useStaticFiles({
562
- path: path.join(process.cwd(), 'public'),
563
- prefix: '/static/',
564
- });
565
- ```
566
-
567
- ---
568
-
569
- ### WebSocket (Socket.IO)
570
-
571
- ```typescript
572
- app.useSocketIo({ cors: { origin: '*' } });
573
- ```
574
-
575
- Dispatch events from services:
576
-
577
- ```typescript
578
- import { AppService, EventDispatcher } from '@avleon/core';
579
-
580
- @AppService
581
- export class UserService {
582
- constructor(private readonly dispatcher: EventDispatcher) {}
583
-
584
- async create(data: any) {
585
- const user = await this.save(data);
586
- await this.dispatcher.dispatch('users:created', { userId: user.id });
587
- return user;
588
- }
589
- }
590
- ```
591
-
592
- ---
593
-
594
- ## Route Mapping (Functional Style)
595
-
596
- For simple routes without a controller class:
597
-
598
- ```typescript
599
- app.mapGet('/users', async (req, res) => {
600
- return { users: [] };
601
- });
602
-
603
- app.mapPost('/users', async (req, res) => {
604
- return { success: true };
605
- });
606
-
607
- app.mapPut('/users/:id', async (req, res) => {
608
- return { success: true };
609
- });
610
-
611
- app.mapDelete('/users/:id', async (req, res) => {
612
- return { success: true };
613
- });
614
- ```
615
-
616
- Add middleware and OpenAPI docs to functional routes:
617
-
618
- ```typescript
619
- app
620
- .mapGet('/users', async (req, res) => {
621
- return { users: [] };
622
- })
623
- .useMiddleware([AuthMiddleware])
624
- .useOpenApi({
625
- summary: 'Get all users',
626
- tags: ['users'],
627
- security: [{ bearerAuth: [] }],
628
- response: {
629
- 200: {
630
- description: 'List of users',
631
- type: 'array',
632
- },
633
- },
634
- });
635
- ```
636
-
637
- ---
638
-
639
- ## Testing
640
-
641
- ```typescript
642
- import { AvleonTest } from '@avleon/core';
643
- import { UserController } from './user.controller';
644
-
645
- describe('UserController', () => {
646
- let controller: UserController;
647
-
648
- beforeAll(() => {
649
- controller = AvleonTest.getController(UserController);
650
- });
651
-
652
- it('should be defined', () => {
653
- expect(controller).toBeDefined();
654
- });
655
-
656
- it('should return users', async () => {
657
- const result = await controller.getAll();
658
- expect(Array.isArray(result)).toBe(true);
659
- });
660
- });
661
- ```
662
-
663
- ---
664
-
665
- ## License
666
-
1
+ # Avleon
2
+
3
+ ![npm version](https://img.shields.io/npm/v/@avleon/core.svg)
4
+ ![Build](https://github.com/avleonjs/avleon-core/actions/workflows/release.yml/badge.svg)
5
+ ![License](https://img.shields.io/npm/l/@avleon/core.svg)
6
+
7
+ > **🚧 This project is in active development. APIs may change between versions.**
8
+
9
+ Avleon is a TypeScript-first web framework built on top of [Fastify](https://fastify.dev), designed for building scalable, maintainable REST APIs with minimal boilerplate. It provides decorator-based routing, built-in dependency injection, automatic OpenAPI documentation, and first-class validation support.
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Core Concepts](#core-concepts)
19
+ - [Application](#application)
20
+ - [Controllers](#controllers)
21
+ - [Route Methods](#route-methods)
22
+ - [Parameter Decorators](#parameter-decorators)
23
+ - [Error Handling](#error-handling)
24
+ - [Middleware](#middleware)
25
+ - [Authorization](#authorization)
26
+ - [Validation](#validation)
27
+ - [OpenAPI Documentation](#openapi-documentation)
28
+ - [Advanced Features](#advanced-features)
29
+ - [Database — Knex](#database--knex)
30
+ - [Database — TypeORM](#database--typeorm)
31
+ - [File Uploads](#file-uploads)
32
+ - [Static Files](#static-files)
33
+ - [WebSocket (Socket.IO)](#websocket-socketio)
34
+ - [Route Mapping (Functional Style)](#route-mapping-functional-style)
35
+ - [Testing](#testing)
36
+ - [License](#license)
37
+
38
+ ---
39
+
40
+ ## Features
41
+
42
+ - 🎯 **Decorator-based routing** — define controllers and routes with TypeScript decorators
43
+ - 💉 **Dependency injection** — powered by [TypeDI](https://github.com/typestack/typedi)
44
+ - 📄 **OpenAPI / Swagger** — automatic docs with Swagger UI or [Scalar](https://scalar.com)
45
+ - ✅ **Validation** — request validation via [class-validator](https://github.com/typestack/class-validator)
46
+ - 🔒 **Authorization** — flexible middleware-based auth system
47
+ - 📁 **File uploads** — multipart form support out of the box
48
+ - 🗄️ **Database** — TypeORM and Knex integrations
49
+ - 🔌 **WebSocket** — Socket.IO integration
50
+ - 🧪 **Testing** — built-in test utilities
51
+
52
+ ---
53
+
54
+ ## Installation
55
+
56
+ Scaffold a new project using the CLI:
57
+
58
+ ```bash
59
+ npx @avleon/cli new myapp
60
+ # or
61
+ yarn dlx @avleon/cli new myapp
62
+ # or
63
+ pnpm dlx @avleon/cli new myapp
64
+ ```
65
+
66
+ Or install manually:
67
+
68
+ ```bash
69
+ npm install @avleon/core reflect-metadata class-validator class-transformer
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Quick Start
75
+
76
+ ### Minimal (functional style)
77
+
78
+ ```typescript
79
+ import { Avleon } from '@avleon/core';
80
+
81
+ const app = Avleon.createApplication();
82
+
83
+ app.mapGet('/', () => ({ message: 'Hello, Avleon!' }));
84
+
85
+ app.run(4000);
86
+ ```
87
+
88
+ ### Controller style
89
+
90
+ ```typescript
91
+ import { Avleon, ApiController, Get } from '@avleon/core';
92
+
93
+ @ApiController('/')
94
+ class HelloController {
95
+ @Get()
96
+ sayHello() {
97
+ return { message: 'Hello, Avleon!' };
98
+ }
99
+ }
100
+
101
+ const app = Avleon.createApplication();
102
+ app.useControllers([HelloController]);
103
+ app.run(4000);
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Core Concepts
109
+
110
+ ### Application
111
+
112
+ ```typescript
113
+ import { Avleon } from '@avleon/core';
114
+
115
+ const app = Avleon.createApplication();
116
+
117
+ app.useCors({ origin: '*' });
118
+ app.useControllers([UserController]);
119
+ // Auto-discover controllers from a directory:
120
+ // app.useControllers({ auto: true, path: 'src/controllers' });
121
+
122
+ app.run(4000);
123
+ ```
124
+
125
+ ---
126
+
127
+ ### Controllers
128
+
129
+ ```typescript
130
+ import { ApiController, Get, Post, Put, Delete } from '@avleon/core';
131
+
132
+ @ApiController('/users')
133
+ class UserController {
134
+ @Get('/')
135
+ getAll() { ... }
136
+
137
+ @Post('/')
138
+ create() { ... }
139
+
140
+ @Put('/:id')
141
+ update() { ... }
142
+
143
+ @Delete('/:id')
144
+ remove() { ... }
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ### Route Methods
151
+
152
+ | Decorator | HTTP Method |
153
+ |-----------|-------------|
154
+ | `@Get(path?)` | GET |
155
+ | `@Post(path?)` | POST |
156
+ | `@Put(path?)` | PUT |
157
+ | `@Patch(path?)` | PATCH |
158
+ | `@Delete(path?)` | DELETE |
159
+
160
+ ---
161
+
162
+ ### Parameter Decorators
163
+
164
+ ```typescript
165
+ @Get('/:id')
166
+ async getUser(
167
+ @Param('id') id: string,
168
+ @Query('include') include: string,
169
+ @Query() query: UserQuery, // maps full query to a DTO
170
+ @Body() body: CreateUserDto,
171
+ @Header('authorization') token: string,
172
+ @AuthUser() user: CurrentUser,
173
+ ) {
174
+ // ...
175
+ }
176
+ ```
177
+
178
+ | Decorator | Source |
179
+ |-----------|--------|
180
+ | `@Param(key?)` | Route path params |
181
+ | `@Query(key?)` | Query string |
182
+ | `@Body()` | Request body |
183
+ | `@Header(key?)` | Request headers |
184
+ | `@AuthUser()` | Current authenticated user |
185
+
186
+ ---
187
+
188
+ ### Error Handling
189
+
190
+ ```typescript
191
+ import { HttpExceptions, HttpResponse } from '@avleon/core';
192
+
193
+ @Get('/:id')
194
+ async getUser(@Param('id') id: string) {
195
+ const user = await this.userService.findById(id);
196
+
197
+ if (!user) {
198
+ throw HttpExceptions.NotFound('User not found');
199
+ }
200
+
201
+ return HttpResponse.Ok(user);
202
+ }
203
+ ```
204
+
205
+ Available exceptions: `NotFound`, `BadRequest`, `Unauthorized`, `Forbidden`, `InternalServerError`.
206
+
207
+ ---
208
+
209
+ ### Middleware
210
+
211
+ ```typescript
212
+ import { Middleware, AppMiddleware, IRequest, UseMiddleware } from '@avleon/core';
213
+
214
+ @Middleware
215
+ class LoggingMiddleware extends AppMiddleware {
216
+ async invoke(req: IRequest) {
217
+ console.log(`${req.method} ${req.url}`);
218
+ return req;
219
+ }
220
+ }
221
+
222
+ // Apply to entire controller
223
+ @UseMiddleware(LoggingMiddleware)
224
+ @ApiController('/users')
225
+ class UserController { ... }
226
+
227
+ // Or apply to a specific route
228
+ @ApiController('/users')
229
+ class UserController {
230
+ @UseMiddleware(LoggingMiddleware)
231
+ @Get('/')
232
+ getAll() { ... }
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ### Authorization
239
+
240
+ **1 — Define your authorization class:**
241
+
242
+ ```typescript
243
+ import { CanAuthorize, AuthorizeMiddleware, IRequest } from '@avleon/core';
244
+
245
+ @CanAuthorize
246
+ class JwtAuthorization extends AuthorizeMiddleware {
247
+ async authorize(req: IRequest, options?: any) {
248
+ const token = req.headers['authorization']?.split(' ')[1];
249
+ if (!token) throw HttpExceptions.Unauthorized('Missing token');
250
+ req.user = verifyToken(token); // attach user to request
251
+ }
252
+ }
253
+ ```
254
+
255
+ **2 — Register with the app:**
256
+
257
+ ```typescript
258
+ app.useAuthorization(JwtAuthorization);
259
+ ```
260
+
261
+ **3 — Protect controllers or routes:**
262
+
263
+ ```typescript
264
+ // Protect entire controller
265
+ @Authorized()
266
+ @ApiController('/admin')
267
+ class AdminController {
268
+ @Get('/')
269
+ dashboard(@AuthUser() user: User) {
270
+ return user;
271
+ }
272
+ }
273
+
274
+ // Protect specific route with roles
275
+ @ApiController('/admin')
276
+ class AdminController {
277
+ @Authorized({ roles: ['admin'] })
278
+ @Get('/stats')
279
+ stats() { ... }
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ ### Validation
286
+
287
+ Validation is powered by `class-validator`. Decorate your DTOs and Avleon validates automatically:
288
+
289
+ ```typescript
290
+ import { IsString, IsEmail, IsInt, Min, Max, IsOptional } from 'class-validator';
291
+
292
+ class CreateUserDto {
293
+ @IsString()
294
+ @IsNotEmpty()
295
+ name: string;
296
+
297
+ @IsEmail()
298
+ email: string;
299
+
300
+ @IsInt()
301
+ @Min(0)
302
+ @Max(120)
303
+ age: number;
304
+
305
+ @IsOptional()
306
+ @IsString()
307
+ role?: string;
308
+ }
309
+
310
+ @Post('/')
311
+ async createUser(@Body() body: CreateUserDto) {
312
+ return this.userService.create(body);
313
+ }
314
+ ```
315
+
316
+ ---
317
+
318
+ ### OpenAPI Documentation
319
+
320
+ **Inline config:**
321
+
322
+ ```typescript
323
+ app.useOpenApi({
324
+ info: {
325
+ title: 'User API',
326
+ version: '1.0.0',
327
+ description: 'API for managing users',
328
+ },
329
+ servers: [{ url: 'http://localhost:4000', description: 'Dev server' }],
330
+ components: {
331
+ securitySchemes: {
332
+ bearerAuth: {
333
+ type: 'http',
334
+ scheme: 'bearer',
335
+ bearerFormat: 'JWT',
336
+ },
337
+ },
338
+ },
339
+ });
340
+ ```
341
+
342
+ **Config class:**
343
+
344
+ ```typescript
345
+ import { AppConfig, IConfig, Environment } from '@avleon/core';
346
+
347
+ @AppConfig
348
+ export class OpenApiConfig implements IConfig {
349
+ config(env: Environment) {
350
+ return {
351
+ info: { title: 'My API', version: '1.0.0' },
352
+ routePrefix: '/docs',
353
+ provider: 'scalar', // or 'default' for Swagger UI
354
+ };
355
+ }
356
+ }
357
+
358
+ // In app.ts
359
+ if (app.isDevelopment()) {
360
+ app.useOpenApi(OpenApiConfig);
361
+ }
362
+ ```
363
+
364
+ **Route-level docs with `@OpenApi`:**
365
+
366
+ ```typescript
367
+ import { OpenApi, OpenApiProperty, OpenApiSchema } from '@avleon/core';
368
+
369
+ @OpenApiSchema()
370
+ export class UserQuery {
371
+ @OpenApiProperty({ type: 'string', example: 'john', required: false })
372
+ @IsOptional()
373
+ search?: string;
374
+
375
+ @OpenApiProperty({ type: 'integer', example: 1, required: false })
376
+ @IsOptional()
377
+ page?: number;
378
+ }
379
+
380
+ @OpenApi({
381
+ summary: 'Get all users',
382
+ tags: ['users'],
383
+ security: [{ bearerAuth: [] }],
384
+ response: {
385
+ 200: {
386
+ description: 'List of users',
387
+ type: 'object',
388
+ properties: {
389
+ data: { type: 'array' },
390
+ total: { type: 'integer', example: 100 },
391
+ },
392
+ },
393
+ 401: { description: 'Unauthorized' },
394
+ },
395
+ })
396
+ @Get('/')
397
+ getAll(@Query() query: UserQuery) { ... }
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Advanced Features
403
+
404
+ ### Database — Knex
405
+
406
+ ```typescript
407
+ app.useKnex({
408
+ client: 'mysql',
409
+ connection: {
410
+ host: '127.0.0.1',
411
+ port: 3306,
412
+ user: 'root',
413
+ password: 'password',
414
+ database: 'myapp',
415
+ },
416
+ });
417
+ ```
418
+
419
+ Using a config class:
420
+
421
+ ```typescript
422
+ @AppConfig
423
+ export class KnexConfig implements IConfig {
424
+ config(env: Environment) {
425
+ return {
426
+ client: 'mysql',
427
+ connection: {
428
+ host: env.get('DB_HOST') || '127.0.0.1',
429
+ port: env.get('DB_PORT') || 3306,
430
+ user: env.get('DB_USER') || 'root',
431
+ password: env.get('DB_PASS') || 'password',
432
+ database: env.get('DB_NAME') || 'myapp',
433
+ },
434
+ };
435
+ }
436
+ }
437
+
438
+ app.useKnex(KnexConfig);
439
+ ```
440
+
441
+ Using in a service:
442
+
443
+ ```typescript
444
+ import { DB, AppService } from '@avleon/core';
445
+
446
+ @AppService
447
+ export class UsersService {
448
+ constructor(private readonly db: DB) {}
449
+
450
+ async findAll() {
451
+ return this.db.client.select('*').from('users');
452
+ }
453
+ }
454
+ ```
455
+
456
+ ---
457
+
458
+ ### Database — TypeORM
459
+
460
+ ```typescript
461
+ app.useDataSource({
462
+ type: 'postgres',
463
+ host: 'localhost',
464
+ port: 5432,
465
+ username: 'postgres',
466
+ password: 'password',
467
+ database: 'avleon',
468
+ entities: [User],
469
+ synchronize: true,
470
+ });
471
+ ```
472
+
473
+ Using a config class:
474
+
475
+ ```typescript
476
+ @AppConfig
477
+ export class DataSourceConfig implements IConfig {
478
+ config(env: Environment) {
479
+ return {
480
+ type: 'postgres',
481
+ host: env.get('DB_HOST') || 'localhost',
482
+ port: Number(env.get('DB_PORT')) || 5432,
483
+ username: env.get('DB_USER') || 'postgres',
484
+ password: env.get('DB_PASS') || 'password',
485
+ database: env.get('DB_NAME') || 'avleon',
486
+ entities: [User],
487
+ synchronize: true,
488
+ };
489
+ }
490
+ }
491
+
492
+ app.useDataSource(DataSourceConfig);
493
+ ```
494
+
495
+ Using in a service:
496
+
497
+ ```typescript
498
+ import { AppService, InjectRepository } from '@avleon/core';
499
+ import { Repository } from 'typeorm';
500
+ import { User } from './user.entity';
501
+
502
+ @AppService
503
+ export class UserService {
504
+ constructor(
505
+ @InjectRepository(User)
506
+ private readonly userRepo: Repository<User>,
507
+ ) {}
508
+
509
+ async findAll() {
510
+ return this.userRepo.find();
511
+ }
512
+ }
513
+ ```
514
+
515
+ ---
516
+
517
+ ### File Uploads
518
+
519
+ ```typescript
520
+ // Configure multipart support
521
+ app.useMultipart({
522
+ destination: path.join(process.cwd(), 'public/uploads'),
523
+ limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
524
+ });
525
+ ```
526
+
527
+ ```typescript
528
+ import { FileStorage, UploadFile, MultipartFile } from '@avleon/core';
529
+
530
+ @ApiController('/files')
531
+ class FileController {
532
+ constructor(private readonly fileStorage: FileStorage) {}
533
+
534
+ @OpenApi({
535
+ description: 'Upload a single file',
536
+ body: {
537
+ type: 'object',
538
+ properties: {
539
+ file: { type: 'string', format: 'binary' },
540
+ },
541
+ required: ['file'],
542
+ },
543
+ })
544
+ @Post('/upload')
545
+ async upload(@UploadFile('file') file: MultipartFile) {
546
+ const result = await this.fileStorage.save(file);
547
+ // optionally rename: this.fileStorage.save(file, { as: 'newname.jpg' })
548
+ return result;
549
+ // { uploadPath: '/uploads/...', staticPath: '/static/...' }
550
+ }
551
+ }
552
+ ```
553
+
554
+ ---
555
+
556
+ ### Static Files
557
+
558
+ ```typescript
559
+ import path from 'path';
560
+
561
+ app.useStaticFiles({
562
+ path: path.join(process.cwd(), 'public'),
563
+ prefix: '/static/',
564
+ });
565
+ ```
566
+
567
+ ---
568
+
569
+ ### WebSocket (Socket.IO)
570
+
571
+ ```typescript
572
+ app.useSocketIo({ cors: { origin: '*' } });
573
+ ```
574
+
575
+ Dispatch events from services:
576
+
577
+ ```typescript
578
+ import { AppService, EventDispatcher } from '@avleon/core';
579
+
580
+ @AppService
581
+ export class UserService {
582
+ constructor(private readonly dispatcher: EventDispatcher) {}
583
+
584
+ async create(data: any) {
585
+ const user = await this.save(data);
586
+ await this.dispatcher.dispatch('users:created', { userId: user.id });
587
+ return user;
588
+ }
589
+ }
590
+ ```
591
+
592
+ ---
593
+
594
+ ## Route Mapping (Functional Style)
595
+
596
+ For simple routes without a controller class:
597
+
598
+ ```typescript
599
+ app.mapGet('/users', async (req, res) => {
600
+ return { users: [] };
601
+ });
602
+
603
+ app.mapPost('/users', async (req, res) => {
604
+ return { success: true };
605
+ });
606
+
607
+ app.mapPut('/users/:id', async (req, res) => {
608
+ return { success: true };
609
+ });
610
+
611
+ app.mapDelete('/users/:id', async (req, res) => {
612
+ return { success: true };
613
+ });
614
+ ```
615
+
616
+ Add middleware and OpenAPI docs to functional routes:
617
+
618
+ ```typescript
619
+ app
620
+ .mapGet('/users', async (req, res) => {
621
+ return { users: [] };
622
+ })
623
+ .useMiddleware([AuthMiddleware])
624
+ .useOpenApi({
625
+ summary: 'Get all users',
626
+ tags: ['users'],
627
+ security: [{ bearerAuth: [] }],
628
+ response: {
629
+ 200: {
630
+ description: 'List of users',
631
+ type: 'array',
632
+ },
633
+ },
634
+ });
635
+ ```
636
+
637
+ ---
638
+
639
+ ## Testing
640
+
641
+ ```typescript
642
+ import { AvleonTest } from '@avleon/core';
643
+ import { UserController } from './user.controller';
644
+
645
+ describe('UserController', () => {
646
+ let controller: UserController;
647
+
648
+ beforeAll(() => {
649
+ controller = AvleonTest.getController(UserController);
650
+ });
651
+
652
+ it('should be defined', () => {
653
+ expect(controller).toBeDefined();
654
+ });
655
+
656
+ it('should return users', async () => {
657
+ const result = await controller.getAll();
658
+ expect(Array.isArray(result)).toBe(true);
659
+ });
660
+ });
661
+ ```
662
+
663
+ ---
664
+
665
+ ## License
666
+
667
667
  ISC © [Tareq Hossain](https://github.com/xtareq)