@shadow-library/fastify 1.1.0 → 1.3.0
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/README.md +361 -0
- package/cjs/decorators/http-controller.decorator.js +0 -2
- package/cjs/decorators/index.d.ts +1 -0
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/version.decorator.d.ts +11 -0
- package/cjs/decorators/version.decorator.js +19 -0
- package/cjs/interfaces/server-metadata.interface.d.ts +1 -0
- package/cjs/module/fastify-module.interface.d.ts +9 -0
- package/cjs/module/fastify-router.d.ts +1 -0
- package/cjs/module/fastify-router.js +11 -3
- package/esm/decorators/http-controller.decorator.js +0 -2
- package/esm/decorators/index.d.ts +1 -0
- package/esm/decorators/index.js +1 -0
- package/esm/decorators/version.decorator.d.ts +11 -0
- package/esm/decorators/version.decorator.js +16 -0
- package/esm/interfaces/server-metadata.interface.d.ts +1 -0
- package/esm/module/fastify-module.interface.d.ts +9 -0
- package/esm/module/fastify-router.d.ts +1 -0
- package/esm/module/fastify-router.js +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ A powerful TypeScript-first Fastify wrapper featuring decorator-based routing, a
|
|
|
14
14
|
- 📊 **Type Safety**: Full TypeScript support with schema generation
|
|
15
15
|
- 🎨 **Templating Ready**: Built-in support for templating engines
|
|
16
16
|
- ⚡ **Dynamic Module**: Configurable module with `forRoot()` and `forRootAsync()` methods
|
|
17
|
+
- 🔢 **API Versioning**: Built-in support for prefix-based API versioning
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -129,6 +130,7 @@ bootstrap();
|
|
|
129
130
|
@Delete(path?: string) // DELETE requests
|
|
130
131
|
@Options(path?: string) // OPTIONS requests
|
|
131
132
|
@Head(path?: string) // HEAD requests
|
|
133
|
+
@Version(version: number) // Set API version for the route
|
|
132
134
|
```
|
|
133
135
|
|
|
134
136
|
#### Parameter Decorators
|
|
@@ -183,6 +185,359 @@ export class ProtectedController {
|
|
|
183
185
|
}
|
|
184
186
|
```
|
|
185
187
|
|
|
188
|
+
### API Versioning
|
|
189
|
+
|
|
190
|
+
The framework provides a powerful versioning system that allows you to version your APIs using URL path prefixes. This is useful for maintaining multiple versions of your API simultaneously.
|
|
191
|
+
|
|
192
|
+
#### Enabling Versioning
|
|
193
|
+
|
|
194
|
+
To enable versioning, set `prefixVersioning: true` in your module configuration:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
@Module({
|
|
198
|
+
imports: [
|
|
199
|
+
FastifyModule.forRoot({
|
|
200
|
+
controllers: [UserController],
|
|
201
|
+
port: 3000,
|
|
202
|
+
|
|
203
|
+
// Enable prefix-based versioning
|
|
204
|
+
prefixVersioning: true,
|
|
205
|
+
}),
|
|
206
|
+
],
|
|
207
|
+
})
|
|
208
|
+
export class AppModule {}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### Using the @Version Decorator
|
|
212
|
+
|
|
213
|
+
Use the `@Version` decorator on your controllers to specify the API version:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { HttpController, Get, Post, Version, Body } from '@shadow-library/fastify';
|
|
217
|
+
|
|
218
|
+
@HttpController('/api/users')
|
|
219
|
+
@Version(1)
|
|
220
|
+
export class UserV1Controller {
|
|
221
|
+
@Get()
|
|
222
|
+
async getUsers() {
|
|
223
|
+
return [{ id: 1, name: 'John Doe' }];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@HttpController('/api/users')
|
|
228
|
+
@Version(2)
|
|
229
|
+
export class UserV2Controller {
|
|
230
|
+
@Get()
|
|
231
|
+
async getUsers() {
|
|
232
|
+
// v2 with additional fields
|
|
233
|
+
return [{ id: 1, name: 'John Doe', email: 'john@example.com', createdAt: new Date() }];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@Post()
|
|
237
|
+
async createUser(@Body() userData: CreateUserDto) {
|
|
238
|
+
// New features in v2
|
|
239
|
+
return { id: 2, ...userData };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Result**:
|
|
245
|
+
|
|
246
|
+
- v1 endpoints: `GET /v1/api/users`
|
|
247
|
+
- v2 endpoints: `GET /v2/api/users`, `POST /v2/api/users`
|
|
248
|
+
|
|
249
|
+
#### Default Version
|
|
250
|
+
|
|
251
|
+
If versioning is enabled but no `@Version` decorator is specified, routes default to `v1`:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
@HttpController('/api/products')
|
|
255
|
+
export class ProductController {
|
|
256
|
+
@Get()
|
|
257
|
+
async getProducts() {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Results in: GET /v1/api/products
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### Method-Level Versioning
|
|
265
|
+
|
|
266
|
+
You can also apply versioning at the method level for more granular control:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
@HttpController('/api/data')
|
|
270
|
+
export class DataController {
|
|
271
|
+
@Get('/stats')
|
|
272
|
+
@Version(1)
|
|
273
|
+
async getStatsV1() {
|
|
274
|
+
return { totalUsers: 100 };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@Get('/stats')
|
|
278
|
+
@Version(2)
|
|
279
|
+
async getStatsV2() {
|
|
280
|
+
return { totalUsers: 100, activeUsers: 75, newUsers: 10 };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@Get('/info')
|
|
284
|
+
async getInfo() {
|
|
285
|
+
// This defaults to v1 when versioning is enabled
|
|
286
|
+
return { version: 'v1' };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Result**:
|
|
292
|
+
|
|
293
|
+
- `GET /v1/api/data/stats` → `getStatsV1()`
|
|
294
|
+
- `GET /v2/api/data/stats` → `getStatsV2()`
|
|
295
|
+
- `GET /v1/api/data/info` → `getInfo()`
|
|
296
|
+
|
|
297
|
+
#### Versioning Best Practices
|
|
298
|
+
|
|
299
|
+
1. **Maintain Backward Compatibility**: Keep older versions running while you migrate clients to newer versions
|
|
300
|
+
2. **Version Breaking Changes**: Only increment versions for breaking changes in your API
|
|
301
|
+
3. **Document Version Differences**: Clearly document what changes between versions
|
|
302
|
+
4. **Deprecation Strategy**: Communicate deprecation timelines for older API versions
|
|
303
|
+
5. **Consistent Versioning**: Use consistent version numbers across related endpoints
|
|
304
|
+
|
|
305
|
+
#### Example: Complete Versioned API
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { Module } from '@shadow-library/app';
|
|
309
|
+
import { FastifyModule, HttpController, Get, Post, Version } from '@shadow-library/fastify';
|
|
310
|
+
|
|
311
|
+
// Version 1 Controllers
|
|
312
|
+
@HttpController('/api/users')
|
|
313
|
+
@Version(1)
|
|
314
|
+
class UserV1Controller {
|
|
315
|
+
@Get()
|
|
316
|
+
async list() {
|
|
317
|
+
return [{ id: 1, name: 'John' }];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@HttpController('/api/posts')
|
|
322
|
+
@Version(1)
|
|
323
|
+
class PostV1Controller {
|
|
324
|
+
@Get()
|
|
325
|
+
async list() {
|
|
326
|
+
return [{ id: 1, title: 'Hello' }];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Version 2 Controllers - with enhanced features
|
|
331
|
+
@HttpController('/api/users')
|
|
332
|
+
@Version(2)
|
|
333
|
+
class UserV2Controller {
|
|
334
|
+
@Get()
|
|
335
|
+
async list() {
|
|
336
|
+
return [{ id: 1, name: 'John', email: 'john@example.com', role: 'admin' }];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
@Post('/bulk')
|
|
340
|
+
async bulkCreate(@Body() users: CreateUserDto[]) {
|
|
341
|
+
// New feature in v2
|
|
342
|
+
return { created: users.length };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@HttpController('/api/posts')
|
|
347
|
+
@Version(2)
|
|
348
|
+
class PostV2Controller {
|
|
349
|
+
@Get()
|
|
350
|
+
async list() {
|
|
351
|
+
return [{ id: 1, title: 'Hello', content: 'World', tags: ['news'] }];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@Module({
|
|
356
|
+
imports: [
|
|
357
|
+
FastifyModule.forRoot({
|
|
358
|
+
controllers: [UserV1Controller, UserV2Controller, PostV1Controller, PostV2Controller],
|
|
359
|
+
prefixVersioning: true,
|
|
360
|
+
port: 3000,
|
|
361
|
+
}),
|
|
362
|
+
],
|
|
363
|
+
})
|
|
364
|
+
export class AppModule {}
|
|
365
|
+
|
|
366
|
+
// Available endpoints:
|
|
367
|
+
// GET /v1/api/users
|
|
368
|
+
// GET /v1/api/posts
|
|
369
|
+
// GET /v2/api/users
|
|
370
|
+
// POST /v2/api/users/bulk (new in v2)
|
|
371
|
+
// GET /v2/api/posts
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
#### Disabling Versioning
|
|
375
|
+
|
|
376
|
+
If you don't need versioning, simply omit the `prefixVersioning` option or set it to `false`:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
FastifyModule.forRoot({
|
|
380
|
+
controllers: [UserController],
|
|
381
|
+
prefixVersioning: false, // or omit entirely
|
|
382
|
+
port: 3000,
|
|
383
|
+
});
|
|
384
|
+
// Routes will be: GET /api/users (no version prefix)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Global Route Prefix
|
|
388
|
+
|
|
389
|
+
The `routePrefix` configuration option allows you to add a global prefix to all routes in your application. This is useful for:
|
|
390
|
+
|
|
391
|
+
- **API Namespacing**: Prefix all routes with `/api` to clearly distinguish API endpoints from other routes
|
|
392
|
+
- **Multi-tenant Applications**: Add tenant-specific prefixes
|
|
393
|
+
- **Microservices**: Namespace your service routes (e.g., `/user-service`, `/payment-service`)
|
|
394
|
+
- **API Gateways**: Organize routes by domain or service boundaries
|
|
395
|
+
|
|
396
|
+
#### Basic Usage
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
@Module({
|
|
400
|
+
imports: [
|
|
401
|
+
FastifyModule.forRoot({
|
|
402
|
+
controllers: [UserController, ProductController],
|
|
403
|
+
routePrefix: 'api',
|
|
404
|
+
port: 3000,
|
|
405
|
+
}),
|
|
406
|
+
],
|
|
407
|
+
})
|
|
408
|
+
export class AppModule {}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Example Controllers:**
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
@HttpController('/users')
|
|
415
|
+
export class UserController {
|
|
416
|
+
@Get()
|
|
417
|
+
async getUsers() {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@Get('/:id')
|
|
422
|
+
async getUser(@Params() params: { id: string }) {
|
|
423
|
+
return { id: params.id };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@HttpController('/products')
|
|
428
|
+
export class ProductController {
|
|
429
|
+
@Get()
|
|
430
|
+
async getProducts() {
|
|
431
|
+
return [];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Result**:
|
|
437
|
+
|
|
438
|
+
- `GET /api/users`
|
|
439
|
+
- `GET /api/users/:id`
|
|
440
|
+
- `GET /api/products`
|
|
441
|
+
|
|
442
|
+
#### Combining with Versioning
|
|
443
|
+
|
|
444
|
+
You can use `routePrefix` together with `prefixVersioning` for a well-organized API structure:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
@Module({
|
|
448
|
+
imports: [
|
|
449
|
+
FastifyModule.forRoot({
|
|
450
|
+
controllers: [UserV1Controller, UserV2Controller],
|
|
451
|
+
routePrefix: 'api',
|
|
452
|
+
prefixVersioning: true,
|
|
453
|
+
port: 3000,
|
|
454
|
+
}),
|
|
455
|
+
],
|
|
456
|
+
})
|
|
457
|
+
export class AppModule {}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**Result**:
|
|
461
|
+
|
|
462
|
+
- `GET /api/v1/users`
|
|
463
|
+
- `GET /api/v2/users`
|
|
464
|
+
|
|
465
|
+
The order is always: `/{routePrefix}/{version}/{controller-path}/{route-path}`
|
|
466
|
+
|
|
467
|
+
#### Multi-Service Example
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// User Service Module
|
|
471
|
+
@Module({
|
|
472
|
+
imports: [
|
|
473
|
+
FastifyModule.forRoot({
|
|
474
|
+
controllers: [UserController, AuthController],
|
|
475
|
+
routePrefix: 'user-service',
|
|
476
|
+
port: 3001,
|
|
477
|
+
}),
|
|
478
|
+
],
|
|
479
|
+
})
|
|
480
|
+
export class UserServiceModule {}
|
|
481
|
+
|
|
482
|
+
// Payment Service Module
|
|
483
|
+
@Module({
|
|
484
|
+
imports: [
|
|
485
|
+
FastifyModule.forRoot({
|
|
486
|
+
controllers: [PaymentController, InvoiceController],
|
|
487
|
+
routePrefix: 'payment-service',
|
|
488
|
+
port: 3002,
|
|
489
|
+
}),
|
|
490
|
+
],
|
|
491
|
+
})
|
|
492
|
+
export class PaymentServiceModule {}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Result**:
|
|
496
|
+
|
|
497
|
+
- User Service: `POST /user-service/auth/login`, `GET /user-service/users`
|
|
498
|
+
- Payment Service: `POST /payment-service/payments`, `GET /payment-service/invoices`
|
|
499
|
+
|
|
500
|
+
#### Dynamic Route Prefix
|
|
501
|
+
|
|
502
|
+
You can also set the prefix dynamically using `forRootAsync`:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
@Module({
|
|
506
|
+
imports: [
|
|
507
|
+
FastifyModule.forRootAsync({
|
|
508
|
+
useFactory: (configService: ConfigService) => ({
|
|
509
|
+
controllers: [UserController],
|
|
510
|
+
routePrefix: configService.get('API_PREFIX'), // e.g., 'api/v1'
|
|
511
|
+
port: configService.get('PORT'),
|
|
512
|
+
}),
|
|
513
|
+
inject: [ConfigService],
|
|
514
|
+
}),
|
|
515
|
+
],
|
|
516
|
+
})
|
|
517
|
+
export class AppModule {}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
#### Best Practices
|
|
521
|
+
|
|
522
|
+
1. **Use Consistent Prefixes**: Keep your route prefix consistent across your application
|
|
523
|
+
2. **Avoid Deep Nesting**: Keep prefixes shallow for better readability (prefer `/api` over `/api/v1/public`)
|
|
524
|
+
3. **Combine with Versioning**: Use `routePrefix` for namespace and `prefixVersioning` for versions
|
|
525
|
+
4. **Document Your Routes**: Clearly document the prefix structure for API consumers
|
|
526
|
+
5. **Environment-based Prefixes**: Consider different prefixes for different environments if needed
|
|
527
|
+
|
|
528
|
+
#### Without Route Prefix
|
|
529
|
+
|
|
530
|
+
If you don't need a global prefix, simply omit the `routePrefix` option:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
FastifyModule.forRoot({
|
|
534
|
+
controllers: [UserController],
|
|
535
|
+
// No routePrefix specified
|
|
536
|
+
port: 3000,
|
|
537
|
+
});
|
|
538
|
+
// Routes will be: GET /users (no prefix)
|
|
539
|
+
```
|
|
540
|
+
|
|
186
541
|
### Error Handling
|
|
187
542
|
|
|
188
543
|
```typescript
|
|
@@ -336,6 +691,12 @@ The module provides two configuration methods:
|
|
|
336
691
|
// Security
|
|
337
692
|
maskSensitiveData: true,
|
|
338
693
|
|
|
694
|
+
// Global route prefix
|
|
695
|
+
routePrefix: 'api',
|
|
696
|
+
|
|
697
|
+
// API Versioning
|
|
698
|
+
prefixVersioning: true,
|
|
699
|
+
|
|
339
700
|
// Request ID generation
|
|
340
701
|
requestIdLogLabel: 'rid',
|
|
341
702
|
genReqId: () => uuid(),
|
|
@@ -16,7 +16,5 @@ const constants_1 = require("../constants.js");
|
|
|
16
16
|
* Declaring the constants
|
|
17
17
|
*/
|
|
18
18
|
function HttpController(path = '') {
|
|
19
|
-
if (path.charAt(0) !== '/')
|
|
20
|
-
path = `/${path}`;
|
|
21
19
|
return target => (0, app_1.Controller)({ [constants_1.HTTP_CONTROLLER_TYPE]: 'router', path })(target);
|
|
22
20
|
}
|
package/cjs/decorators/index.js
CHANGED
|
@@ -20,3 +20,4 @@ __exportStar(require("./http-output.decorator.js"), exports);
|
|
|
20
20
|
__exportStar(require("./http-route.decorator.js"), exports);
|
|
21
21
|
__exportStar(require("./middleware.decorator.js"), exports);
|
|
22
22
|
__exportStar(require("./sensitive.decorator.js"), exports);
|
|
23
|
+
__exportStar(require("./version.decorator.js"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Integer } from 'type-fest';
|
|
2
|
+
/**
|
|
3
|
+
* Importing user defined packages
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Defining types
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Declaring the constants
|
|
10
|
+
*/
|
|
11
|
+
export declare function Version<T extends number>(version: Integer<T>): ClassDecorator & MethodDecorator;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Version = Version;
|
|
4
|
+
/**
|
|
5
|
+
* Importing npm packages
|
|
6
|
+
*/
|
|
7
|
+
const app_1 = require("@shadow-library/app");
|
|
8
|
+
/**
|
|
9
|
+
* Importing user defined packages
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Defining types
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Declaring the constants
|
|
16
|
+
*/
|
|
17
|
+
function Version(version) {
|
|
18
|
+
return (0, app_1.Route)({ version });
|
|
19
|
+
}
|
|
@@ -16,6 +16,7 @@ declare module '@shadow-library/app' {
|
|
|
16
16
|
interface RouteMetadata extends Omit<RouteShorthandOptions, 'config'> {
|
|
17
17
|
method?: HttpMethod;
|
|
18
18
|
path?: string;
|
|
19
|
+
version?: number;
|
|
19
20
|
schemas?: RouteInputSchemas & {
|
|
20
21
|
response?: Record<number | string, JSONSchema>;
|
|
21
22
|
};
|
|
@@ -50,6 +50,15 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
50
50
|
* @default true
|
|
51
51
|
*/
|
|
52
52
|
maskSensitiveData?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Enables prefix-based versioning for all routes in the Fastify instance.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
prefixVersioning?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The global route prefix for all routes in the Fastify instance
|
|
60
|
+
*/
|
|
61
|
+
routePrefix?: string;
|
|
53
62
|
}
|
|
54
63
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
55
64
|
/**
|
|
@@ -67,6 +67,7 @@ export declare class FastifyRouter extends Router {
|
|
|
67
67
|
private readonly sensitiveTransformer;
|
|
68
68
|
constructor(config: FastifyConfig, instance: FastifyInstance, context: ContextService);
|
|
69
69
|
getInstance(): FastifyInstance;
|
|
70
|
+
private joinPaths;
|
|
70
71
|
private registerRawBody;
|
|
71
72
|
private maskField;
|
|
72
73
|
private getRequestLogger;
|
|
@@ -59,6 +59,14 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
59
59
|
getInstance() {
|
|
60
60
|
return this.instance;
|
|
61
61
|
}
|
|
62
|
+
joinPaths(...parts) {
|
|
63
|
+
const path = parts
|
|
64
|
+
.filter(p => typeof p === 'string')
|
|
65
|
+
.map(p => p.replace(/^\/+|\/+$/g, ''))
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join('/');
|
|
68
|
+
return `/${path}`;
|
|
69
|
+
}
|
|
62
70
|
registerRawBody() {
|
|
63
71
|
const opts = { parseAs: 'buffer' };
|
|
64
72
|
const parser = this.instance.getDefaultJsonParser('error', 'error');
|
|
@@ -117,10 +125,10 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
117
125
|
switch (controller.metadata[constants_1.HTTP_CONTROLLER_TYPE]) {
|
|
118
126
|
case 'router': {
|
|
119
127
|
const { instance, metadata, metatype } = controller;
|
|
120
|
-
const basePath = metadata.path ?? '/';
|
|
121
128
|
for (const route of controller.routes) {
|
|
122
|
-
const
|
|
123
|
-
const
|
|
129
|
+
const version = route.metadata.version ?? 1;
|
|
130
|
+
const versionPrefix = this.config.prefixVersioning ? `/v${version}` : '';
|
|
131
|
+
const path = this.joinPaths(this.config.routePrefix, versionPrefix, metadata.path, route.metadata.path);
|
|
124
132
|
const parsedController = { ...route, instance, metatype };
|
|
125
133
|
parsedController.metadata.path = path;
|
|
126
134
|
parsedControllers.routes.push(parsedController);
|
|
@@ -13,7 +13,5 @@ import { HTTP_CONTROLLER_TYPE } from '../constants.js';
|
|
|
13
13
|
* Declaring the constants
|
|
14
14
|
*/
|
|
15
15
|
export function HttpController(path = '') {
|
|
16
|
-
if (path.charAt(0) !== '/')
|
|
17
|
-
path = `/${path}`;
|
|
18
16
|
return target => Controller({ [HTTP_CONTROLLER_TYPE]: 'router', path })(target);
|
|
19
17
|
}
|
package/esm/decorators/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Integer } from 'type-fest';
|
|
2
|
+
/**
|
|
3
|
+
* Importing user defined packages
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Defining types
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Declaring the constants
|
|
10
|
+
*/
|
|
11
|
+
export declare function Version<T extends number>(version: Integer<T>): ClassDecorator & MethodDecorator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing npm packages
|
|
3
|
+
*/
|
|
4
|
+
import { Route } from '@shadow-library/app';
|
|
5
|
+
/**
|
|
6
|
+
* Importing user defined packages
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Defining types
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Declaring the constants
|
|
13
|
+
*/
|
|
14
|
+
export function Version(version) {
|
|
15
|
+
return Route({ version });
|
|
16
|
+
}
|
|
@@ -16,6 +16,7 @@ declare module '@shadow-library/app' {
|
|
|
16
16
|
interface RouteMetadata extends Omit<RouteShorthandOptions, 'config'> {
|
|
17
17
|
method?: HttpMethod;
|
|
18
18
|
path?: string;
|
|
19
|
+
version?: number;
|
|
19
20
|
schemas?: RouteInputSchemas & {
|
|
20
21
|
response?: Record<number | string, JSONSchema>;
|
|
21
22
|
};
|
|
@@ -50,6 +50,15 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
50
50
|
* @default true
|
|
51
51
|
*/
|
|
52
52
|
maskSensitiveData?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Enables prefix-based versioning for all routes in the Fastify instance.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
prefixVersioning?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The global route prefix for all routes in the Fastify instance
|
|
60
|
+
*/
|
|
61
|
+
routePrefix?: string;
|
|
53
62
|
}
|
|
54
63
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
55
64
|
/**
|
|
@@ -67,6 +67,7 @@ export declare class FastifyRouter extends Router {
|
|
|
67
67
|
private readonly sensitiveTransformer;
|
|
68
68
|
constructor(config: FastifyConfig, instance: FastifyInstance, context: ContextService);
|
|
69
69
|
getInstance(): FastifyInstance;
|
|
70
|
+
private joinPaths;
|
|
70
71
|
private registerRawBody;
|
|
71
72
|
private maskField;
|
|
72
73
|
private getRequestLogger;
|
|
@@ -53,6 +53,14 @@ let FastifyRouter = class FastifyRouter extends Router {
|
|
|
53
53
|
getInstance() {
|
|
54
54
|
return this.instance;
|
|
55
55
|
}
|
|
56
|
+
joinPaths(...parts) {
|
|
57
|
+
const path = parts
|
|
58
|
+
.filter(p => typeof p === 'string')
|
|
59
|
+
.map(p => p.replace(/^\/+|\/+$/g, ''))
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join('/');
|
|
62
|
+
return `/${path}`;
|
|
63
|
+
}
|
|
56
64
|
registerRawBody() {
|
|
57
65
|
const opts = { parseAs: 'buffer' };
|
|
58
66
|
const parser = this.instance.getDefaultJsonParser('error', 'error');
|
|
@@ -111,10 +119,10 @@ let FastifyRouter = class FastifyRouter extends Router {
|
|
|
111
119
|
switch (controller.metadata[HTTP_CONTROLLER_TYPE]) {
|
|
112
120
|
case 'router': {
|
|
113
121
|
const { instance, metadata, metatype } = controller;
|
|
114
|
-
const basePath = metadata.path ?? '/';
|
|
115
122
|
for (const route of controller.routes) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
123
|
+
const version = route.metadata.version ?? 1;
|
|
124
|
+
const versionPrefix = this.config.prefixVersioning ? `/v${version}` : '';
|
|
125
|
+
const path = this.joinPaths(this.config.routePrefix, versionPrefix, metadata.path, route.metadata.path);
|
|
118
126
|
const parsedController = { ...route, instance, metatype };
|
|
119
127
|
parsedController.metadata.path = path;
|
|
120
128
|
parsedControllers.routes.push(parsedController);
|
package/package.json
CHANGED