@shadow-library/fastify 1.0.1 → 1.2.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 +294 -1
- 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 +11 -0
- package/cjs/module/fastify-router.d.ts +1 -1
- package/cjs/module/fastify-router.js +11 -3
- 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 +11 -0
- package/esm/module/fastify-router.d.ts +1 -1
- 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,205 @@ 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
|
+
|
|
186
387
|
### Error Handling
|
|
187
388
|
|
|
188
389
|
```typescript
|
|
@@ -223,6 +424,89 @@ export class RoutesController {
|
|
|
223
424
|
}
|
|
224
425
|
```
|
|
225
426
|
|
|
427
|
+
#### Child Routes Configuration
|
|
428
|
+
|
|
429
|
+
Child routes enable server-side route resolution, commonly used for SSR (Server-Side Rendering) and internal API composition. When enabled, you can make internal HTTP requests to your own routes without going through the network layer.
|
|
430
|
+
|
|
431
|
+
**Basic Setup:**
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
@Module({
|
|
435
|
+
imports: [
|
|
436
|
+
FastifyModule.forRoot({
|
|
437
|
+
controllers: [UserController, DataController],
|
|
438
|
+
|
|
439
|
+
// Enable child routes functionality
|
|
440
|
+
enableChildRoutes: true,
|
|
441
|
+
|
|
442
|
+
// Optional: Provide custom headers for child route requests
|
|
443
|
+
childRouteHeaders: () => ({
|
|
444
|
+
'x-correlation-id': '123',
|
|
445
|
+
}),
|
|
446
|
+
}),
|
|
447
|
+
],
|
|
448
|
+
})
|
|
449
|
+
export class AppModule {}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Usage in Controllers:**
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
@HttpController('/api')
|
|
456
|
+
export class DataAggregatorController {
|
|
457
|
+
constructor(@Inject(Router) private readonly fastifyRouter: FastifyRouter) {}
|
|
458
|
+
|
|
459
|
+
@Get('/dashboard')
|
|
460
|
+
async getDashboardData() {
|
|
461
|
+
// Make internal requests to other routes
|
|
462
|
+
const [users, posts, analytics] = await Promise.all([
|
|
463
|
+
this.fastifyRouter.resolveChildRoute('/api/users'),
|
|
464
|
+
this.fastifyRouter.resolveChildRoute('/api/posts?limit=10'),
|
|
465
|
+
this.fastifyRouter.resolveChildRoute('/api/analytics/summary'),
|
|
466
|
+
]);
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
dashboard: {
|
|
470
|
+
users,
|
|
471
|
+
posts,
|
|
472
|
+
analytics,
|
|
473
|
+
timestamp: new Date().toISOString(),
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Custom Headers Function:**
|
|
481
|
+
|
|
482
|
+
The `childRouteHeaders` function is called for each child route request, allowing you to:
|
|
483
|
+
|
|
484
|
+
- Pass authentication context from the parent request
|
|
485
|
+
- Include tenant/user-specific information
|
|
486
|
+
- Add tracing or correlation IDs
|
|
487
|
+
- Set internal service flags
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
// Dynamic headers based on current request context
|
|
491
|
+
childRouteHeaders: (contextService) => {
|
|
492
|
+
const request = contextService.getRequest();
|
|
493
|
+
return {
|
|
494
|
+
'x-user-id': contextService.get('currentUserId'),
|
|
495
|
+
'x-request-id': contextService.getRID(),
|
|
496
|
+
'x-forwarded-from': 'internal-aggregator',
|
|
497
|
+
'x-correlation-id': request.headers['x-correlation-id'],
|
|
498
|
+
};
|
|
499
|
+
},
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Important Notes:**
|
|
503
|
+
|
|
504
|
+
- Child routes always include the header `x-service: 'internal-child-route'`
|
|
505
|
+
- Custom headers are merged with the default service header
|
|
506
|
+
- If you provide an `x-service` header, it will be overridden with the default value
|
|
507
|
+
- Child routes create isolated contexts, preventing middleware conflicts
|
|
508
|
+
- Enable only when needed, as it adds routing overhead
|
|
509
|
+
|
|
226
510
|
## Configuration
|
|
227
511
|
|
|
228
512
|
### Dynamic Module Configuration
|
|
@@ -253,6 +537,9 @@ The module provides two configuration methods:
|
|
|
253
537
|
// Security
|
|
254
538
|
maskSensitiveData: true,
|
|
255
539
|
|
|
540
|
+
// API Versioning
|
|
541
|
+
prefixVersioning: true,
|
|
542
|
+
|
|
256
543
|
// Request ID generation
|
|
257
544
|
requestIdLogLabel: 'rid',
|
|
258
545
|
genReqId: () => uuid(),
|
|
@@ -269,6 +556,12 @@ The module provides two configuration methods:
|
|
|
269
556
|
'5xx': ErrorResponseSchema,
|
|
270
557
|
},
|
|
271
558
|
|
|
559
|
+
// Child routes configuration (for SSR and internal route resolution)
|
|
560
|
+
enableChildRoutes: true,
|
|
561
|
+
childRouteHeaders: contextService => ({
|
|
562
|
+
'x-correlation-id': contextService.getRequest().headers['x-correlation-id'],
|
|
563
|
+
}),
|
|
564
|
+
|
|
272
565
|
// Extend Fastify instance before registering controllers
|
|
273
566
|
fastifyFactory: async fastify => {
|
|
274
567
|
// Register plugins, add hooks, or configure Fastify
|
|
@@ -800,7 +1093,7 @@ Check out the [examples](./examples) directory for complete working examples:
|
|
|
800
1093
|
|
|
801
1094
|
- **hello-world**: Basic HTTP controller with GET/POST routes
|
|
802
1095
|
- **user-auth**: Advanced example with authentication guards
|
|
803
|
-
- **child-routes**: Route resolution and
|
|
1096
|
+
- **child-routes**: Route resolution, unified endpoints, and custom headers for SSR
|
|
804
1097
|
|
|
805
1098
|
## Contributing
|
|
806
1099
|
|
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
|
};
|
|
@@ -9,6 +9,7 @@ import { Promisable } from 'type-fest';
|
|
|
9
9
|
* Importing user defined packages
|
|
10
10
|
*/
|
|
11
11
|
import { ErrorHandler } from '../interfaces/index.js';
|
|
12
|
+
import { ContextService } from '../services/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* Defining types
|
|
14
15
|
*/
|
|
@@ -39,11 +40,21 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
39
40
|
* @default false
|
|
40
41
|
*/
|
|
41
42
|
enableChildRoutes?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Function to provide custom headers for internal child route requests.
|
|
45
|
+
* Useful for passing authentication tokens or other necessary headers.
|
|
46
|
+
*/
|
|
47
|
+
childRouteHeaders?: (contextService: ContextService) => Record<string, string>;
|
|
42
48
|
/**
|
|
43
49
|
* Masks fields marked as sensitive in API inputs (body, query, and URL params) when written to logs.
|
|
44
50
|
* @default true
|
|
45
51
|
*/
|
|
46
52
|
maskSensitiveData?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Enables prefix-based versioning for all routes in the Fastify instance.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
prefixVersioning?: boolean;
|
|
47
58
|
}
|
|
48
59
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
49
60
|
/**
|
|
@@ -82,7 +82,7 @@ export declare class FastifyRouter extends Router {
|
|
|
82
82
|
* during SSR. Automatically reuses middleware results from the parent context to avoid
|
|
83
83
|
* redundant execution and ensures correct context isolation for nested route data fetching.
|
|
84
84
|
*/
|
|
85
|
-
resolveChildRoute<T extends JsonValue = JsonObject>(url: string): Promise<T>;
|
|
85
|
+
resolveChildRoute<T extends JsonValue = JsonObject>(url: string, headers?: Record<string, string>): Promise<T>;
|
|
86
86
|
mockRequest(): MockRequestChain;
|
|
87
87
|
mockRequest(options: MockRequestOptions): Promise<MockResponse>;
|
|
88
88
|
}
|
|
@@ -119,8 +119,14 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
119
119
|
const { instance, metadata, metatype } = controller;
|
|
120
120
|
const basePath = metadata.path ?? '/';
|
|
121
121
|
for (const route of controller.routes) {
|
|
122
|
+
/** Prepare path with versioning if enabled */
|
|
122
123
|
const routePath = route.metadata.path ?? '';
|
|
123
|
-
|
|
124
|
+
let path = basePath + routePath;
|
|
125
|
+
if (this.config.prefixVersioning) {
|
|
126
|
+
const version = route.metadata.version ?? 1;
|
|
127
|
+
const connector = path.startsWith('/') ? '' : '/';
|
|
128
|
+
path = '/v' + version + connector + path;
|
|
129
|
+
}
|
|
124
130
|
const parsedController = { ...route, instance, metatype };
|
|
125
131
|
parsedController.metadata.path = path;
|
|
126
132
|
parsedControllers.routes.push(parsedController);
|
|
@@ -285,10 +291,12 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
285
291
|
* during SSR. Automatically reuses middleware results from the parent context to avoid
|
|
286
292
|
* redundant execution and ensures correct context isolation for nested route data fetching.
|
|
287
293
|
*/
|
|
288
|
-
async resolveChildRoute(url) {
|
|
294
|
+
async resolveChildRoute(url, headers = {}) {
|
|
289
295
|
if (!this.childRouter)
|
|
290
296
|
throw new common_1.InternalError('Child routes are not enabled');
|
|
291
|
-
const
|
|
297
|
+
const childHeaders = this.config.childRouteHeaders?.(this.context) ?? {};
|
|
298
|
+
Object.assign(headers, childHeaders, { 'x-service': 'internal-child-route' });
|
|
299
|
+
const response = await this.instance.inject({ method: 'GET', url, headers });
|
|
292
300
|
return response.json();
|
|
293
301
|
}
|
|
294
302
|
mockRequest(options) {
|
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
|
};
|
|
@@ -9,6 +9,7 @@ import { Promisable } from 'type-fest';
|
|
|
9
9
|
* Importing user defined packages
|
|
10
10
|
*/
|
|
11
11
|
import { ErrorHandler } from '../interfaces/index.js';
|
|
12
|
+
import { ContextService } from '../services/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* Defining types
|
|
14
15
|
*/
|
|
@@ -39,11 +40,21 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
39
40
|
* @default false
|
|
40
41
|
*/
|
|
41
42
|
enableChildRoutes?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Function to provide custom headers for internal child route requests.
|
|
45
|
+
* Useful for passing authentication tokens or other necessary headers.
|
|
46
|
+
*/
|
|
47
|
+
childRouteHeaders?: (contextService: ContextService) => Record<string, string>;
|
|
42
48
|
/**
|
|
43
49
|
* Masks fields marked as sensitive in API inputs (body, query, and URL params) when written to logs.
|
|
44
50
|
* @default true
|
|
45
51
|
*/
|
|
46
52
|
maskSensitiveData?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Enables prefix-based versioning for all routes in the Fastify instance.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
prefixVersioning?: boolean;
|
|
47
58
|
}
|
|
48
59
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
49
60
|
/**
|
|
@@ -82,7 +82,7 @@ export declare class FastifyRouter extends Router {
|
|
|
82
82
|
* during SSR. Automatically reuses middleware results from the parent context to avoid
|
|
83
83
|
* redundant execution and ensures correct context isolation for nested route data fetching.
|
|
84
84
|
*/
|
|
85
|
-
resolveChildRoute<T extends JsonValue = JsonObject>(url: string): Promise<T>;
|
|
85
|
+
resolveChildRoute<T extends JsonValue = JsonObject>(url: string, headers?: Record<string, string>): Promise<T>;
|
|
86
86
|
mockRequest(): MockRequestChain;
|
|
87
87
|
mockRequest(options: MockRequestOptions): Promise<MockResponse>;
|
|
88
88
|
}
|
|
@@ -113,8 +113,14 @@ let FastifyRouter = class FastifyRouter extends Router {
|
|
|
113
113
|
const { instance, metadata, metatype } = controller;
|
|
114
114
|
const basePath = metadata.path ?? '/';
|
|
115
115
|
for (const route of controller.routes) {
|
|
116
|
+
/** Prepare path with versioning if enabled */
|
|
116
117
|
const routePath = route.metadata.path ?? '';
|
|
117
|
-
|
|
118
|
+
let path = basePath + routePath;
|
|
119
|
+
if (this.config.prefixVersioning) {
|
|
120
|
+
const version = route.metadata.version ?? 1;
|
|
121
|
+
const connector = path.startsWith('/') ? '' : '/';
|
|
122
|
+
path = '/v' + version + connector + path;
|
|
123
|
+
}
|
|
118
124
|
const parsedController = { ...route, instance, metatype };
|
|
119
125
|
parsedController.metadata.path = path;
|
|
120
126
|
parsedControllers.routes.push(parsedController);
|
|
@@ -279,10 +285,12 @@ let FastifyRouter = class FastifyRouter extends Router {
|
|
|
279
285
|
* during SSR. Automatically reuses middleware results from the parent context to avoid
|
|
280
286
|
* redundant execution and ensures correct context isolation for nested route data fetching.
|
|
281
287
|
*/
|
|
282
|
-
async resolveChildRoute(url) {
|
|
288
|
+
async resolveChildRoute(url, headers = {}) {
|
|
283
289
|
if (!this.childRouter)
|
|
284
290
|
throw new InternalError('Child routes are not enabled');
|
|
285
|
-
const
|
|
291
|
+
const childHeaders = this.config.childRouteHeaders?.(this.context) ?? {};
|
|
292
|
+
Object.assign(headers, childHeaders, { 'x-service': 'internal-child-route' });
|
|
293
|
+
const response = await this.instance.inject({ method: 'GET', url, headers });
|
|
286
294
|
return response.json();
|
|
287
295
|
}
|
|
288
296
|
mockRequest(options) {
|
package/package.json
CHANGED