@a_jackie_z/fastify 1.1.4 → 1.1.6
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 +291 -9
- package/dist/index.d.ts +38 -3
- package/dist/index.js +221 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @a_jackie_z/fastify
|
|
|
13
13
|
### Basic Setup
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import { createFastify, runFastify } from '@a_jackie_z/fastify'
|
|
16
|
+
import { createFastify, runFastify, formatSuccess } from '@a_jackie_z/fastify'
|
|
17
17
|
import { z } from 'zod'
|
|
18
18
|
|
|
19
19
|
const app = await createFastify()
|
|
@@ -24,12 +24,16 @@ app.route({
|
|
|
24
24
|
schema: {
|
|
25
25
|
response: {
|
|
26
26
|
200: z.object({
|
|
27
|
-
|
|
27
|
+
status: z.number(),
|
|
28
|
+
success: z.boolean(),
|
|
29
|
+
data: z.object({
|
|
30
|
+
message: z.string(),
|
|
31
|
+
}),
|
|
28
32
|
}),
|
|
29
33
|
},
|
|
30
34
|
},
|
|
31
|
-
handler: async () => {
|
|
32
|
-
return { message: 'Hello World!' }
|
|
35
|
+
handler: async (_request, reply) => {
|
|
36
|
+
return reply.send(formatSuccess(200, { message: 'Hello World!' }))
|
|
33
37
|
},
|
|
34
38
|
})
|
|
35
39
|
|
|
@@ -40,8 +44,10 @@ await runFastify(app, '0.0.0.0', 3000)
|
|
|
40
44
|
|
|
41
45
|
- **JWT Authentication** - Built-in JWT support with global and route-level configuration
|
|
42
46
|
- **Rate Limiting** - Global rate limiting for API protection
|
|
43
|
-
- **Swagger Documentation** - Auto-generated API documentation with Zod schemas
|
|
47
|
+
- **Swagger Documentation** - Auto-generated API documentation with Zod schemas and bearer authentication
|
|
44
48
|
- **Zod Integration** - Type-safe request/response validation
|
|
49
|
+
- **Standardized Response Formatting** - Consistent response shapes with `formatSuccess` and `formatError` utilities
|
|
50
|
+
- **Automatic Error Handling** - Field-level validation errors, auth errors, and rate limit errors with consistent formatting
|
|
45
51
|
- **Custom Logger Support** - Integrate any Fastify-compatible logger
|
|
46
52
|
- **Health Check Plugin** - Ready-to-use health check endpoint
|
|
47
53
|
- **Plugin Helper** - Utility for creating reusable Fastify plugins
|
|
@@ -90,6 +96,8 @@ await runFastify(app, '0.0.0.0', 3000)
|
|
|
90
96
|
#### Login Route (Public)
|
|
91
97
|
|
|
92
98
|
```typescript
|
|
99
|
+
import { formatSuccess, formatError } from '@a_jackie_z/fastify'
|
|
100
|
+
|
|
93
101
|
app.route({
|
|
94
102
|
method: 'POST',
|
|
95
103
|
url: '/auth/login',
|
|
@@ -103,10 +111,17 @@ app.route({
|
|
|
103
111
|
}),
|
|
104
112
|
response: {
|
|
105
113
|
200: z.object({
|
|
106
|
-
|
|
114
|
+
status: z.number(),
|
|
115
|
+
success: z.boolean(),
|
|
116
|
+
data: z.object({
|
|
117
|
+
token: z.string(),
|
|
118
|
+
}),
|
|
107
119
|
}),
|
|
108
120
|
401: z.object({
|
|
121
|
+
status: z.number(),
|
|
122
|
+
success: z.boolean(),
|
|
109
123
|
error: z.string(),
|
|
124
|
+
message: z.string(),
|
|
110
125
|
}),
|
|
111
126
|
},
|
|
112
127
|
},
|
|
@@ -120,11 +135,12 @@ app.route({
|
|
|
120
135
|
role: 'admin',
|
|
121
136
|
permissions: ['read', 'write', 'delete'],
|
|
122
137
|
})
|
|
123
|
-
return { token }
|
|
138
|
+
return reply.send(formatSuccess(200, { token }))
|
|
124
139
|
}
|
|
125
140
|
|
|
126
|
-
reply.status(401)
|
|
127
|
-
|
|
141
|
+
return reply.status(401).send(
|
|
142
|
+
formatError(401, 'Unauthorized', 'Invalid credentials')
|
|
143
|
+
)
|
|
128
144
|
},
|
|
129
145
|
})
|
|
130
146
|
```
|
|
@@ -343,6 +359,133 @@ app.route({
|
|
|
343
359
|
})
|
|
344
360
|
```
|
|
345
361
|
|
|
362
|
+
### 9. Response Formatting
|
|
363
|
+
|
|
364
|
+
All responses should follow a consistent format using the provided utility functions:
|
|
365
|
+
|
|
366
|
+
#### Success Response Format
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { formatSuccess } from '@a_jackie_z/fastify'
|
|
370
|
+
|
|
371
|
+
app.route({
|
|
372
|
+
method: 'GET',
|
|
373
|
+
url: '/users/:id',
|
|
374
|
+
schema: {
|
|
375
|
+
params: z.object({
|
|
376
|
+
id: z.string(),
|
|
377
|
+
}),
|
|
378
|
+
response: {
|
|
379
|
+
200: z.object({
|
|
380
|
+
status: z.number(),
|
|
381
|
+
success: z.boolean(),
|
|
382
|
+
data: z.object({
|
|
383
|
+
id: z.string(),
|
|
384
|
+
name: z.string(),
|
|
385
|
+
email: z.string(),
|
|
386
|
+
}),
|
|
387
|
+
}),
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
handler: async (request, reply) => {
|
|
391
|
+
const { id } = request.params
|
|
392
|
+
const user = await findUser(id) // Your logic here
|
|
393
|
+
|
|
394
|
+
return reply.status(200).send(
|
|
395
|
+
formatSuccess(200, user)
|
|
396
|
+
)
|
|
397
|
+
// Returns: { status: 200, success: true, data: { id, name, email } }
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### Error Response Format
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { formatError } from '@a_jackie_z/fastify'
|
|
406
|
+
|
|
407
|
+
app.route({
|
|
408
|
+
method: 'DELETE',
|
|
409
|
+
url: '/users/:id',
|
|
410
|
+
handler: async (request, reply) => {
|
|
411
|
+
const { id } = request.params
|
|
412
|
+
|
|
413
|
+
const user = await findUser(id)
|
|
414
|
+
if (!user) {
|
|
415
|
+
return reply.status(404).send(
|
|
416
|
+
formatError(404, 'Not Found', `User with ID ${id} not found`)
|
|
417
|
+
)
|
|
418
|
+
// Returns: { status: 404, success: false, error: 'Not Found', message: '...' }
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
await deleteUser(id)
|
|
422
|
+
return reply.send(formatSuccess(200, { message: 'User deleted' }))
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Automatic Validation Error Formatting
|
|
428
|
+
|
|
429
|
+
Validation errors are automatically formatted with field-level details:
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
app.route({
|
|
433
|
+
method: 'POST',
|
|
434
|
+
url: '/users',
|
|
435
|
+
config: { jwt: false },
|
|
436
|
+
schema: {
|
|
437
|
+
body: z.object({
|
|
438
|
+
username: z.string().min(3, 'Username must be at least 3 characters'),
|
|
439
|
+
email: z.string().email('Invalid email format'),
|
|
440
|
+
age: z.number().int().min(18, 'Must be at least 18 years old'),
|
|
441
|
+
}),
|
|
442
|
+
},
|
|
443
|
+
handler: async (request, reply) => {
|
|
444
|
+
// If validation fails, automatic error response:
|
|
445
|
+
// {
|
|
446
|
+
// status: 400,
|
|
447
|
+
// success: false,
|
|
448
|
+
// error: 'Validation Error',
|
|
449
|
+
// message: 'Request validation failed',
|
|
450
|
+
// details: [
|
|
451
|
+
// { field: 'username', message: 'Username must be at least 3 characters' },
|
|
452
|
+
// { field: 'email', message: 'Invalid email format' },
|
|
453
|
+
// { field: 'age', message: 'Must be at least 18 years old' }
|
|
454
|
+
// ]
|
|
455
|
+
// }
|
|
456
|
+
|
|
457
|
+
return reply.send(formatSuccess(200, request.body))
|
|
458
|
+
},
|
|
459
|
+
})
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Automatic Error Handling
|
|
463
|
+
|
|
464
|
+
All errors follow the standardized format automatically:
|
|
465
|
+
|
|
466
|
+
- **Validation Errors (400)**: Field-level details with field name and message
|
|
467
|
+
- **Authentication Errors (401)**: JWT token missing or invalid
|
|
468
|
+
- **Authorization Errors (403)**: Insufficient permissions
|
|
469
|
+
- **Not Found Errors (404)**: Resource not found
|
|
470
|
+
- **Rate Limit Errors (429)**: Too many requests
|
|
471
|
+
- **Server Errors (500)**: Internal server errors
|
|
472
|
+
|
|
473
|
+
All errors are automatically logged via `fastify.log.error` with request context.
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
// Authentication error (automatic)
|
|
477
|
+
// GET /protected without JWT token
|
|
478
|
+
// { status: 401, success: false, error: 'Unauthorized', message: 'Invalid or missing JWT token' }
|
|
479
|
+
|
|
480
|
+
// Authorization error (automatic)
|
|
481
|
+
// GET /admin without admin role
|
|
482
|
+
// { status: 403, success: false, error: 'Forbidden', message: 'Authorization failed' }
|
|
483
|
+
|
|
484
|
+
// Rate limit error (automatic)
|
|
485
|
+
// Too many requests
|
|
486
|
+
// { status: 429, success: false, error: 'Too Many Requests', message: 'Rate limit exceeded' }
|
|
487
|
+
```
|
|
488
|
+
|
|
346
489
|
## Configuration Options
|
|
347
490
|
|
|
348
491
|
### `CreateFastifyOptions`
|
|
@@ -409,6 +552,145 @@ const app = await createFastify()
|
|
|
409
552
|
await runFastify(app, '0.0.0.0', 3000)
|
|
410
553
|
```
|
|
411
554
|
|
|
555
|
+
### Response Formatting Functions
|
|
556
|
+
|
|
557
|
+
#### `formatSuccess<T>(status: number, data: T): SuccessResponse<T>`
|
|
558
|
+
|
|
559
|
+
Creates a standardized success response.
|
|
560
|
+
|
|
561
|
+
**Parameters:**
|
|
562
|
+
- `status` - HTTP status code (e.g., 200, 201)
|
|
563
|
+
- `data` - Response data of any type
|
|
564
|
+
|
|
565
|
+
**Returns:** `SuccessResponse<T>`
|
|
566
|
+
```typescript
|
|
567
|
+
{
|
|
568
|
+
status: number
|
|
569
|
+
success: true
|
|
570
|
+
data: T
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Example:**
|
|
575
|
+
```typescript
|
|
576
|
+
return reply.send(formatSuccess(200, { id: '123', name: 'John' }))
|
|
577
|
+
// Returns: { status: 200, success: true, data: { id: '123', name: 'John' } }
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### `formatError(status: number, error: string, message: string, details?: ValidationDetail[]): ErrorResponse`
|
|
581
|
+
|
|
582
|
+
Creates a standardized error response.
|
|
583
|
+
|
|
584
|
+
**Parameters:**
|
|
585
|
+
- `status` - HTTP status code (e.g., 400, 404, 500)
|
|
586
|
+
- `error` - Error type/name (e.g., 'Not Found', 'Validation Error')
|
|
587
|
+
- `message` - Human-readable error message
|
|
588
|
+
- `details` - Optional array of validation error details
|
|
589
|
+
|
|
590
|
+
**Returns:** `ErrorResponse`
|
|
591
|
+
```typescript
|
|
592
|
+
{
|
|
593
|
+
status: number
|
|
594
|
+
success: false
|
|
595
|
+
error: string
|
|
596
|
+
message: string
|
|
597
|
+
details?: ValidationDetail[]
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Example:**
|
|
602
|
+
```typescript
|
|
603
|
+
return reply.status(404).send(
|
|
604
|
+
formatError(404, 'Not Found', 'User not found')
|
|
605
|
+
)
|
|
606
|
+
// Returns: { status: 404, success: false, error: 'Not Found', message: 'User not found' }
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Response Type Interfaces
|
|
610
|
+
|
|
611
|
+
#### `SuccessResponse<T>`
|
|
612
|
+
```typescript
|
|
613
|
+
interface SuccessResponse<T> {
|
|
614
|
+
status: number
|
|
615
|
+
success: true
|
|
616
|
+
data: T
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
#### `ErrorResponse`
|
|
621
|
+
```typescript
|
|
622
|
+
interface ErrorResponse {
|
|
623
|
+
status: number
|
|
624
|
+
success: false
|
|
625
|
+
error: string
|
|
626
|
+
message: string
|
|
627
|
+
details?: ValidationDetail[]
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### `ValidationDetail`
|
|
632
|
+
```typescript
|
|
633
|
+
interface ValidationDetail {
|
|
634
|
+
field: string
|
|
635
|
+
message: string
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Zod Schema Helpers
|
|
640
|
+
|
|
641
|
+
For defining response schemas with Zod validation:
|
|
642
|
+
|
|
643
|
+
#### `successResponseSchema<T>(dataSchema: T): ZodObject`
|
|
644
|
+
|
|
645
|
+
Creates a Zod schema for standardized success responses.
|
|
646
|
+
|
|
647
|
+
**Parameters:**
|
|
648
|
+
- `dataSchema` - Zod schema for the data payload
|
|
649
|
+
|
|
650
|
+
**Returns:** Zod object schema matching `SuccessResponse<T>`
|
|
651
|
+
|
|
652
|
+
**Example:**
|
|
653
|
+
```typescript
|
|
654
|
+
import { z } from 'zod'
|
|
655
|
+
import { successResponseSchema } from '@a_jackie_z/fastify'
|
|
656
|
+
|
|
657
|
+
const userSchema = z.object({
|
|
658
|
+
id: z.string(),
|
|
659
|
+
name: z.string(),
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
const loginResponseSchema = {
|
|
663
|
+
body: z.object({
|
|
664
|
+
username: z.string(),
|
|
665
|
+
password: z.string(),
|
|
666
|
+
}),
|
|
667
|
+
response: {
|
|
668
|
+
200: successResponseSchema(z.object({
|
|
669
|
+
accessToken: z.string(),
|
|
670
|
+
refreshToken: z.string(),
|
|
671
|
+
})),
|
|
672
|
+
},
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### `errorResponseSchema: ZodObject`
|
|
677
|
+
|
|
678
|
+
Zod schema for standardized error responses. Matches `ErrorResponse` interface.
|
|
679
|
+
|
|
680
|
+
**Example:**
|
|
681
|
+
```typescript
|
|
682
|
+
import { errorResponseSchema } from '@a_jackie_z/fastify'
|
|
683
|
+
|
|
684
|
+
const myRouteSchema = {
|
|
685
|
+
response: {
|
|
686
|
+
200: successResponseSchema(z.object({ success: z.boolean() })),
|
|
687
|
+
400: errorResponseSchema,
|
|
688
|
+
401: errorResponseSchema,
|
|
689
|
+
404: errorResponseSchema,
|
|
690
|
+
},
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
412
694
|
## Fastify JWT Service
|
|
413
695
|
|
|
414
696
|
A typed helper for issuing and verifying access/refresh tokens alongside the Fastify JWT plugin.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as fastify from 'fastify';
|
|
2
2
|
import { FastifyRequest, FastifyReply, FastifyServerOptions, FastifyInstance, RawServerDefault, FastifyBaseLogger, FastifyContextConfig, FastifyPluginCallback } from 'fastify';
|
|
3
3
|
export { FastifyReply, FastifyRequest } from 'fastify';
|
|
4
|
-
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
5
|
-
export * from 'fastify-type-provider-zod';
|
|
6
4
|
import { RateLimitPluginOptions } from '@fastify/rate-limit';
|
|
7
5
|
import { FastifyJWTOptions } from '@fastify/jwt';
|
|
6
|
+
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
7
|
+
export * from 'fastify-type-provider-zod';
|
|
8
8
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
|
|
@@ -42,6 +42,41 @@ interface CreateFastifyOptions {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
type FastifyServer = FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse, FastifyBaseLogger, ZodTypeProvider> & FastifyContextConfig;
|
|
45
|
+
|
|
46
|
+
interface ValidationDetail {
|
|
47
|
+
field: string;
|
|
48
|
+
message: string;
|
|
49
|
+
}
|
|
50
|
+
interface SuccessResponse<T> {
|
|
51
|
+
status: number;
|
|
52
|
+
success: true;
|
|
53
|
+
data: T;
|
|
54
|
+
}
|
|
55
|
+
interface ErrorResponse {
|
|
56
|
+
status: number;
|
|
57
|
+
success: false;
|
|
58
|
+
error: string;
|
|
59
|
+
message: string;
|
|
60
|
+
details?: ValidationDetail[];
|
|
61
|
+
}
|
|
62
|
+
declare function formatSuccess<T>(status: number, data: T): SuccessResponse<T>;
|
|
63
|
+
declare function formatError(status: number, error: string, message: string, details?: ValidationDetail[]): ErrorResponse;
|
|
64
|
+
declare const successResponseSchema: <T extends z.ZodTypeAny>(dataSchema: T) => z.ZodObject<{
|
|
65
|
+
status: z.ZodNumber;
|
|
66
|
+
success: z.ZodLiteral<true>;
|
|
67
|
+
data: T;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
declare const errorResponseSchema: z.ZodObject<{
|
|
70
|
+
status: z.ZodNumber;
|
|
71
|
+
success: z.ZodLiteral<false>;
|
|
72
|
+
error: z.ZodString;
|
|
73
|
+
message: z.ZodString;
|
|
74
|
+
details: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
75
|
+
field: z.ZodString;
|
|
76
|
+
message: z.ZodString;
|
|
77
|
+
}, z.core.$strip>>>;
|
|
78
|
+
}, z.core.$strip>;
|
|
79
|
+
|
|
45
80
|
declare function createFastify(options?: CreateFastifyOptions): Promise<FastifyServer>;
|
|
46
81
|
declare function runFastify(fastify: FastifyServer, host: string, port: number): Promise<void>;
|
|
47
82
|
|
|
@@ -155,4 +190,4 @@ declare function createFastifyPlugin(cb: FastifyPluginCallback): (fastify: Fasti
|
|
|
155
190
|
|
|
156
191
|
declare const healthPlugin: (fastify: FastifyServer, options: Parameters<fastify.FastifyPluginCallback>[1], done: Parameters<fastify.FastifyPluginCallback>[2]) => void;
|
|
157
192
|
|
|
158
|
-
export { type BaseAccessTokenPayload, type BaseRefreshTokenPayload, type CreateFastifyOptions, FastifyJwtService, type FastifyJwtServiceOptions, type FastifyServer, type JWTAuthorizationHandler, type JWTRouteConfig, type TokenPair, baseAccessTokenPayloadSchema, baseRefreshTokenPayloadSchema, createFastify, createFastifyPlugin, healthPlugin, runFastify };
|
|
193
|
+
export { type BaseAccessTokenPayload, type BaseRefreshTokenPayload, type CreateFastifyOptions, type ErrorResponse, FastifyJwtService, type FastifyJwtServiceOptions, type FastifyServer, type JWTAuthorizationHandler, type JWTRouteConfig, type SuccessResponse, type TokenPair, type ValidationDetail, baseAccessTokenPayloadSchema, baseRefreshTokenPayloadSchema, createFastify, createFastifyPlugin, errorResponseSchema, formatError, formatSuccess, healthPlugin, runFastify, successResponseSchema };
|
package/dist/index.js
CHANGED
|
@@ -1,87 +1,223 @@
|
|
|
1
1
|
// lib/fastify.ts
|
|
2
2
|
import Fastify from "fastify";
|
|
3
|
-
import {
|
|
4
|
-
serializerCompiler,
|
|
5
|
-
validatorCompiler
|
|
6
|
-
} from "fastify-type-provider-zod";
|
|
3
|
+
import { serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
|
|
7
4
|
import fastifyRateLimit from "@fastify/rate-limit";
|
|
8
|
-
|
|
5
|
+
|
|
6
|
+
// lib/response.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
function formatSuccess(status, data) {
|
|
9
|
+
return {
|
|
10
|
+
status,
|
|
11
|
+
success: true,
|
|
12
|
+
data
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function formatError(status, error, message, details) {
|
|
16
|
+
const response = {
|
|
17
|
+
status,
|
|
18
|
+
success: false,
|
|
19
|
+
error,
|
|
20
|
+
message
|
|
21
|
+
};
|
|
22
|
+
if (details && details.length > 0) {
|
|
23
|
+
response.details = details;
|
|
24
|
+
}
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
var successResponseSchema = (dataSchema) => z.object({
|
|
28
|
+
status: z.number(),
|
|
29
|
+
success: z.literal(true),
|
|
30
|
+
data: dataSchema
|
|
31
|
+
});
|
|
32
|
+
var errorResponseSchema = z.object({
|
|
33
|
+
status: z.number(),
|
|
34
|
+
success: z.literal(false),
|
|
35
|
+
error: z.string(),
|
|
36
|
+
message: z.string(),
|
|
37
|
+
details: z.array(z.object({
|
|
38
|
+
field: z.string(),
|
|
39
|
+
message: z.string()
|
|
40
|
+
})).optional()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// lib/error-handler.ts
|
|
44
|
+
function setupErrorHandler(fastify) {
|
|
45
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
46
|
+
fastify.log.error({
|
|
47
|
+
err: error,
|
|
48
|
+
url: request.url,
|
|
49
|
+
method: request.method
|
|
50
|
+
}, "Request error");
|
|
51
|
+
if (error.validation) {
|
|
52
|
+
const details = error.validation.map((issue) => {
|
|
53
|
+
const field = issue.instancePath || issue.dataPath || issue.params?.missingProperty || "unknown";
|
|
54
|
+
const cleanField = field.startsWith("/") ? field.slice(1).replace(/\//g, ".") : field;
|
|
55
|
+
return {
|
|
56
|
+
field: cleanField || "unknown",
|
|
57
|
+
message: issue.message || "Validation failed"
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
return reply.status(400).send(
|
|
61
|
+
formatError(400, "Validation Error", "Request validation failed", details)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (error.statusCode === 429) {
|
|
65
|
+
return reply.status(429).send(
|
|
66
|
+
formatError(429, "Too Many Requests", "Rate limit exceeded")
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (error.statusCode === 401) {
|
|
70
|
+
return reply.status(401).send(
|
|
71
|
+
formatError(401, "Unauthorized", error.message || "Authentication required")
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (error.statusCode === 403) {
|
|
75
|
+
return reply.status(403).send(
|
|
76
|
+
formatError(403, "Forbidden", error.message || "Access denied")
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (error.statusCode === 404) {
|
|
80
|
+
return reply.status(404).send(
|
|
81
|
+
formatError(404, "Not Found", error.message || "Resource not found")
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const statusCode = error.statusCode || 500;
|
|
85
|
+
return reply.status(statusCode).send(
|
|
86
|
+
formatError(
|
|
87
|
+
statusCode,
|
|
88
|
+
statusCode === 500 ? "Internal Server Error" : error.name || "Error",
|
|
89
|
+
error.message || "An unexpected error occurred"
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// lib/swagger-setup.ts
|
|
9
96
|
import fastifySwagger from "@fastify/swagger";
|
|
10
97
|
import fastifySwaggerUI from "@fastify/swagger-ui";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (options
|
|
14
|
-
|
|
98
|
+
import { jsonSchemaTransform } from "fastify-type-provider-zod";
|
|
99
|
+
async function setupSwagger(fastify, options) {
|
|
100
|
+
if (!options.swagger) {
|
|
101
|
+
return void 0;
|
|
15
102
|
}
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
103
|
+
const openApiConfig = {
|
|
104
|
+
openapi: {
|
|
105
|
+
info: {
|
|
106
|
+
title: options.swagger.title,
|
|
107
|
+
version: options.swagger.version,
|
|
108
|
+
description: options.swagger.description
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
transform: jsonSchemaTransform
|
|
112
|
+
};
|
|
113
|
+
if (options.jwt) {
|
|
114
|
+
openApiConfig.openapi.components = {
|
|
115
|
+
securitySchemes: {
|
|
116
|
+
bearerAuth: {
|
|
117
|
+
type: "http",
|
|
118
|
+
scheme: "bearer",
|
|
119
|
+
bearerFormat: "JWT"
|
|
27
120
|
}
|
|
28
121
|
}
|
|
29
|
-
});
|
|
30
|
-
let routePrefix = options.swagger.routePrefix || "/docs/";
|
|
31
|
-
if (!routePrefix.startsWith("/")) {
|
|
32
|
-
routePrefix = "/" + routePrefix;
|
|
33
|
-
}
|
|
34
|
-
if (!routePrefix.endsWith("/")) {
|
|
35
|
-
routePrefix = routePrefix + "/";
|
|
36
|
-
}
|
|
37
|
-
swaggerRoutePrefix = routePrefix;
|
|
38
|
-
await fastify.register(fastifySwaggerUI, { routePrefix });
|
|
39
|
-
}
|
|
40
|
-
if (options?.jwt) {
|
|
41
|
-
const jwtOptions = {
|
|
42
|
-
secret: options.jwt.secret
|
|
43
122
|
};
|
|
44
|
-
|
|
45
|
-
|
|
123
|
+
}
|
|
124
|
+
await fastify.register(fastifySwagger, openApiConfig);
|
|
125
|
+
let routePrefix = options.swagger.routePrefix || "/docs/";
|
|
126
|
+
if (!routePrefix.startsWith("/")) {
|
|
127
|
+
routePrefix = "/" + routePrefix;
|
|
128
|
+
}
|
|
129
|
+
if (!routePrefix.endsWith("/")) {
|
|
130
|
+
routePrefix = routePrefix + "/";
|
|
131
|
+
}
|
|
132
|
+
await fastify.register(fastifySwaggerUI, { routePrefix });
|
|
133
|
+
return routePrefix;
|
|
134
|
+
}
|
|
135
|
+
function setupSwaggerSecurityHook(fastify, options, swaggerRoutePrefix) {
|
|
136
|
+
if (!options.jwt || !options.swagger) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
fastify.addHook("onRoute", (routeOptions) => {
|
|
140
|
+
if (swaggerRoutePrefix && routeOptions.url.startsWith(swaggerRoutePrefix)) {
|
|
141
|
+
return;
|
|
46
142
|
}
|
|
47
|
-
|
|
48
|
-
|
|
143
|
+
const routeConfig = routeOptions.config || {};
|
|
144
|
+
const jwtConfig = routeConfig.jwt;
|
|
145
|
+
if (jwtConfig === false) {
|
|
146
|
+
return;
|
|
49
147
|
}
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
148
|
+
const requiresJWT = jwtConfig === true || typeof jwtConfig === "object" && jwtConfig.required !== false || jwtConfig === void 0 && options.jwt?.global === true;
|
|
149
|
+
if (requiresJWT) {
|
|
150
|
+
if (!routeOptions.schema) {
|
|
151
|
+
routeOptions.schema = {};
|
|
152
|
+
}
|
|
153
|
+
if (!routeOptions.schema.security) {
|
|
154
|
+
routeOptions.schema.security = [{ bearerAuth: [] }];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// lib/jwt-setup.ts
|
|
161
|
+
import fastifyJwt from "@fastify/jwt";
|
|
162
|
+
async function setupJWT(fastify, options, swaggerRoutePrefix) {
|
|
163
|
+
if (!options.jwt) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const jwtOptions = {
|
|
167
|
+
secret: options.jwt.secret
|
|
168
|
+
};
|
|
169
|
+
if (options.jwt.sign !== void 0) {
|
|
170
|
+
jwtOptions.sign = options.jwt.sign;
|
|
171
|
+
}
|
|
172
|
+
if (options.jwt.verify !== void 0) {
|
|
173
|
+
jwtOptions.verify = options.jwt.verify;
|
|
174
|
+
}
|
|
175
|
+
await fastify.register(fastifyJwt, jwtOptions);
|
|
176
|
+
if (options.jwt.global) {
|
|
177
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
178
|
+
if (swaggerRoutePrefix && request.url.startsWith(swaggerRoutePrefix)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const routeConfig = request.routeOptions.config || {};
|
|
182
|
+
const jwtConfig = routeConfig.jwt;
|
|
183
|
+
if (jwtConfig === false) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const shouldVerify = jwtConfig === true || typeof jwtConfig === "object" && jwtConfig.required !== false || jwtConfig === void 0 && options.jwt?.global === true;
|
|
187
|
+
if (shouldVerify) {
|
|
188
|
+
try {
|
|
189
|
+
await request.jwtVerify();
|
|
190
|
+
if (typeof jwtConfig === "object" && jwtConfig.authorize) {
|
|
191
|
+
const authorized = await jwtConfig.authorize(request, reply, request.user);
|
|
192
|
+
if (!authorized) {
|
|
193
|
+
return reply.status(403).send(
|
|
194
|
+
formatError(403, "Forbidden", "Authorization failed")
|
|
195
|
+
);
|
|
74
196
|
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
reply.status(401).send({
|
|
77
|
-
error: "Unauthorized",
|
|
78
|
-
message: "Invalid or missing JWT token"
|
|
79
|
-
});
|
|
80
197
|
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
return reply.status(401).send(
|
|
200
|
+
formatError(401, "Unauthorized", "Invalid or missing JWT token")
|
|
201
|
+
);
|
|
81
202
|
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
84
205
|
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// lib/fastify.ts
|
|
209
|
+
async function createFastify(options) {
|
|
210
|
+
const fastifyOptions = {};
|
|
211
|
+
if (options?.logger) {
|
|
212
|
+
fastifyOptions.loggerInstance = options.logger;
|
|
213
|
+
}
|
|
214
|
+
const fastify = Fastify(fastifyOptions).withTypeProvider();
|
|
215
|
+
fastify.setValidatorCompiler(validatorCompiler);
|
|
216
|
+
fastify.setSerializerCompiler(serializerCompiler);
|
|
217
|
+
setupErrorHandler(fastify);
|
|
218
|
+
const swaggerRoutePrefix = await setupSwagger(fastify, options || {});
|
|
219
|
+
setupSwaggerSecurityHook(fastify, options || {}, swaggerRoutePrefix);
|
|
220
|
+
await setupJWT(fastify, options || {}, swaggerRoutePrefix);
|
|
85
221
|
if (options?.rateLimit?.global) {
|
|
86
222
|
await fastify.register(fastifyRateLimit, {
|
|
87
223
|
global: true,
|
|
@@ -100,16 +236,16 @@ async function runFastify(fastify, host, port) {
|
|
|
100
236
|
}
|
|
101
237
|
|
|
102
238
|
// lib/jwt.service.ts
|
|
103
|
-
import { z } from "zod";
|
|
104
|
-
var baseAccessTokenPayloadSchema =
|
|
105
|
-
identityId:
|
|
106
|
-
type:
|
|
107
|
-
exp:
|
|
239
|
+
import { z as z2 } from "zod";
|
|
240
|
+
var baseAccessTokenPayloadSchema = z2.object({
|
|
241
|
+
identityId: z2.string(),
|
|
242
|
+
type: z2.literal("access"),
|
|
243
|
+
exp: z2.number()
|
|
108
244
|
});
|
|
109
|
-
var baseRefreshTokenPayloadSchema =
|
|
110
|
-
identityId:
|
|
111
|
-
type:
|
|
112
|
-
exp:
|
|
245
|
+
var baseRefreshTokenPayloadSchema = z2.object({
|
|
246
|
+
identityId: z2.string(),
|
|
247
|
+
type: z2.literal("refresh"),
|
|
248
|
+
exp: z2.number()
|
|
113
249
|
});
|
|
114
250
|
var FastifyJwtService = class {
|
|
115
251
|
constructor(app, options) {
|
|
@@ -172,7 +308,7 @@ var FastifyJwtService = class {
|
|
|
172
308
|
}
|
|
173
309
|
return validated;
|
|
174
310
|
} catch (error) {
|
|
175
|
-
if (error instanceof
|
|
311
|
+
if (error instanceof z2.ZodError) {
|
|
176
312
|
throw new Error("Invalid token payload");
|
|
177
313
|
}
|
|
178
314
|
throw error;
|
|
@@ -190,7 +326,7 @@ var FastifyJwtService = class {
|
|
|
190
326
|
}
|
|
191
327
|
return validated;
|
|
192
328
|
} catch (error) {
|
|
193
|
-
if (error instanceof
|
|
329
|
+
if (error instanceof z2.ZodError) {
|
|
194
330
|
throw new Error("Invalid token payload");
|
|
195
331
|
}
|
|
196
332
|
throw error;
|
|
@@ -277,7 +413,11 @@ export {
|
|
|
277
413
|
baseRefreshTokenPayloadSchema,
|
|
278
414
|
createFastify,
|
|
279
415
|
createFastifyPlugin,
|
|
416
|
+
errorResponseSchema,
|
|
417
|
+
formatError,
|
|
418
|
+
formatSuccess,
|
|
280
419
|
healthPlugin,
|
|
281
|
-
runFastify
|
|
420
|
+
runFastify,
|
|
421
|
+
successResponseSchema
|
|
282
422
|
};
|
|
283
423
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/fastify.ts","../lib/jwt.service.ts","../lib/plugin.ts","../lib/plugins/healthPlugin.ts","../index.ts"],"sourcesContent":["import Fastify, {\n FastifyBaseLogger,\n FastifyContextConfig,\n FastifyInstance,\n FastifyReply,\n FastifyServerOptions,\n RawServerDefault,\n} from 'fastify'\nimport type { FastifyRequest } from 'fastify'\nimport {\n serializerCompiler,\n validatorCompiler,\n ZodTypeProvider,\n} from 'fastify-type-provider-zod'\nimport fastifyRateLimit, { RateLimitPluginOptions } from '@fastify/rate-limit'\nimport fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'\nimport fastifySwagger, { SwaggerOptions } from '@fastify/swagger'\nimport fastifySwaggerUI, { FastifySwaggerUiOptions } from '@fastify/swagger-ui'\nimport { IncomingMessage, ServerResponse } from 'node:http'\n\nexport type JWTAuthorizationHandler = <TUser = unknown>(\n request: FastifyRequest,\n reply: FastifyReply,\n user: TUser\n) => Promise<boolean> | boolean\n\nexport interface JWTRouteConfig {\n jwt?:\n | boolean // true = require JWT, false = bypass JWT\n | {\n required?: boolean // Default: true if global enabled\n authorize?: JWTAuthorizationHandler // Check JWT info before route processing\n }\n}\n\n// Extend Fastify types to include custom config\ndeclare module 'fastify' {\n interface FastifyContextConfig {\n jwt?:\n | boolean\n | {\n required?: boolean\n authorize?: JWTAuthorizationHandler\n }\n }\n}\n\nexport interface CreateFastifyOptions {\n logger?: FastifyServerOptions['loggerInstance'],\n rateLimit?: {\n global?: RateLimitPluginOptions\n }\n jwt?: {\n secret: string\n sign?: FastifyJWTOptions['sign']\n verify?: FastifyJWTOptions['verify']\n global?: boolean // Enable JWT check globally for all routes\n }\n swagger?: {\n title: string\n version: string\n description: string\n routePrefix?: string\n }\n}\n\nexport type FastifyServer = FastifyInstance<\n RawServerDefault,\n IncomingMessage,\n ServerResponse,\n FastifyBaseLogger,\n ZodTypeProvider\n> & FastifyContextConfig\n\nexport async function createFastify(options?: CreateFastifyOptions): Promise<FastifyServer> {\n const fastifyOptions: FastifyServerOptions = {}\n\n if (options?.logger) {\n fastifyOptions.loggerInstance = options.logger\n }\n\n const fastify = Fastify(fastifyOptions).withTypeProvider<ZodTypeProvider>()\n\n // Set up Zod validation and serialization\n fastify.setValidatorCompiler(validatorCompiler)\n fastify.setSerializerCompiler(serializerCompiler)\n\n // Store Swagger route prefix for JWT bypass logic\n let swaggerRoutePrefix: string | undefined\n\n // Register Swagger first to capture all routes with Zod schemas\n if (options?.swagger) {\n await fastify.register(fastifySwagger, {\n openapi: {\n info: {\n title: options.swagger.title,\n version: options.swagger.version,\n description: options.swagger.description,\n },\n },\n } as SwaggerOptions)\n\n let routePrefix = options.swagger.routePrefix || '/docs/'\n\n if (!routePrefix.startsWith('/')) {\n routePrefix = '/' + routePrefix\n }\n\n if (!routePrefix.endsWith('/')) {\n routePrefix = routePrefix + '/'\n }\n\n swaggerRoutePrefix = routePrefix\n await fastify.register(fastifySwaggerUI, { routePrefix } as FastifySwaggerUiOptions)\n }\n\n // Register JWT authentication\n if (options?.jwt) {\n const jwtOptions: FastifyJWTOptions = {\n secret: options.jwt.secret,\n }\n if (options.jwt.sign !== undefined) {\n jwtOptions.sign = options.jwt.sign\n }\n if (options.jwt.verify !== undefined) {\n jwtOptions.verify = options.jwt.verify\n }\n await fastify.register(fastifyJwt, jwtOptions)\n\n // Global JWT checking hook\n if (options.jwt.global) {\n fastify.addHook('onRequest', async (request, reply) => {\n // Skip JWT verification for Swagger documentation routes\n if (swaggerRoutePrefix && request.url.startsWith(swaggerRoutePrefix)) {\n return\n }\n\n const routeConfig = (request.routeOptions.config as JWTRouteConfig) || {}\n const jwtConfig = routeConfig.jwt\n\n // Check if route explicitly bypasses JWT\n if (jwtConfig === false) {\n return\n }\n\n // Check if route explicitly requires JWT or uses global setting\n const shouldVerify =\n jwtConfig === true ||\n (typeof jwtConfig === 'object' && jwtConfig.required !== false) ||\n (jwtConfig === undefined && options?.jwt?.global === true)\n\n if (shouldVerify) {\n try {\n await request.jwtVerify()\n\n // Custom authorization - check JWT info before route processing\n if (typeof jwtConfig === 'object' && jwtConfig.authorize) {\n const authorized = await jwtConfig.authorize(request, reply, request.user)\n if (!authorized) {\n reply.status(403).send({\n error: 'Forbidden',\n message: 'Authorization failed',\n })\n return\n }\n }\n } catch (err) {\n reply.status(401).send({\n error: 'Unauthorized',\n message: 'Invalid or missing JWT token',\n })\n }\n }\n })\n }\n }\n\n // Register Rate Limiting\n if (options?.rateLimit?.global) {\n await fastify.register(fastifyRateLimit, {\n global: true,\n ...options.rateLimit.global,\n })\n }\n\n return fastify\n}\n\nexport async function runFastify(fastify: FastifyServer, host: string, port: number) {\n try {\n await fastify.listen({ host, port })\n } catch (err) {\n fastify.log.error(err)\n process.exit(1)\n }\n}\n","import { z } from 'zod'\nimport { FastifyServer } from './fastify.ts'\n\n/**\n * Base access token payload schema\n */\nexport const baseAccessTokenPayloadSchema = z.object({\n identityId: z.string(),\n type: z.literal('access'),\n exp: z.number(),\n})\n\n/**\n * Base refresh token payload schema\n */\nexport const baseRefreshTokenPayloadSchema = z.object({\n identityId: z.string(),\n type: z.literal('refresh'),\n exp: z.number(),\n})\n\n/**\n * Inferred types from base schemas\n */\nexport type BaseAccessTokenPayload = z.infer<typeof baseAccessTokenPayloadSchema>\nexport type BaseRefreshTokenPayload = z.infer<typeof baseRefreshTokenPayloadSchema>\n\n/**\n * Token pair interface\n */\nexport interface TokenPair {\n accessToken: string\n refreshToken: string\n}\n\n/**\n * FastifyJwtService configuration options\n */\nexport interface FastifyJwtServiceOptions {\n accessTokenExpiry: string\n refreshTokenExpiry: string\n renewalThreshold: string\n accessTokenSchema?: z.ZodSchema\n refreshTokenSchema?: z.ZodSchema\n}\n\n/**\n * Generic JWT service for Fastify applications\n *\n * @template TAccess - Access token payload type extending BaseAccessTokenPayload\n * @template TRefresh - Refresh token payload type extending BaseRefreshTokenPayload\n *\n * @example\n * // Using base schemas\n * const jwtService = new FastifyJwtService(app, {\n * accessTokenExpiry: '15m',\n * refreshTokenExpiry: '7d',\n * renewalThreshold: '1d',\n * })\n *\n * @example\n * // Using extended schemas\n * const customAccessSchema = baseAccessTokenPayloadSchema.extend({\n * roles: z.array(z.string())\n * })\n * const jwtService = new FastifyJwtService(app, {\n * accessTokenExpiry: '15m',\n * refreshTokenExpiry: '7d',\n * renewalThreshold: '1d',\n * accessTokenSchema: customAccessSchema,\n * })\n */\nexport class FastifyJwtService<\n TAccess extends BaseAccessTokenPayload = BaseAccessTokenPayload,\n TRefresh extends BaseRefreshTokenPayload = BaseRefreshTokenPayload\n> {\n private readonly accessTokenExpiry: string\n private readonly refreshTokenExpiry: string\n private readonly renewalThreshold: string\n private readonly accessTokenSchema: z.ZodSchema<TAccess>\n private readonly refreshTokenSchema: z.ZodSchema<TRefresh>\n\n constructor(\n private readonly app: FastifyServer,\n options: FastifyJwtServiceOptions\n ) {\n this.accessTokenExpiry = options.accessTokenExpiry\n this.refreshTokenExpiry = options.refreshTokenExpiry\n this.renewalThreshold = options.renewalThreshold\n this.accessTokenSchema = (options.accessTokenSchema ?? baseAccessTokenPayloadSchema) as z.ZodSchema<TAccess>\n this.refreshTokenSchema = (options.refreshTokenSchema ?? baseRefreshTokenPayloadSchema) as z.ZodSchema<TRefresh>\n }\n\n /**\n * Generate an access token for the given identity\n */\n generateAccessToken(identityId: string, extraPayload?: Partial<Omit<TAccess, 'identityId' | 'type' | 'exp'>>): string {\n const payload = {\n identityId,\n type: 'access' as const,\n ...extraPayload,\n }\n\n return this.app.jwt.sign(payload, {\n expiresIn: this.accessTokenExpiry,\n })\n }\n\n /**\n * Generate a refresh token for the given identity\n */\n generateRefreshToken(identityId: string, extraPayload?: Partial<Omit<TRefresh, 'identityId' | 'type' | 'exp'>>): string {\n const payload = {\n identityId,\n type: 'refresh' as const,\n ...extraPayload,\n }\n\n return this.app.jwt.sign(payload, {\n expiresIn: this.refreshTokenExpiry,\n })\n }\n\n /**\n * Generate both access and refresh tokens\n */\n generateTokenPair(identityId: string): TokenPair {\n return {\n accessToken: this.generateAccessToken(identityId),\n refreshToken: this.generateRefreshToken(identityId),\n }\n }\n\n /**\n * Verify and decode an access token\n */\n verifyAccessToken(token: string): TAccess {\n try {\n const payload = this.app.jwt.verify(token) as unknown\n const validated = this.accessTokenSchema.parse(payload)\n\n if (validated.type !== 'access') {\n throw new Error('Invalid token type')\n }\n\n return validated\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw new Error('Invalid token payload')\n }\n throw error\n }\n }\n\n /**\n * Verify and decode a refresh token\n */\n verifyRefreshToken(token: string): TRefresh {\n try {\n const payload = this.app.jwt.verify(token) as unknown\n const validated = this.refreshTokenSchema.parse(payload)\n\n if (validated.type !== 'refresh') {\n throw new Error('Invalid token type')\n }\n\n return validated\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw new Error('Invalid token payload')\n }\n throw error\n }\n }\n\n /**\n * Renew a refresh token for the given identity\n */\n renewRefreshToken(identityId: string): string {\n return this.generateRefreshToken(identityId)\n }\n\n /**\n * Check if a refresh token should be renewed based on its expiry\n */\n shouldRenewRefreshToken(exp: number): boolean {\n const now = Math.floor(Date.now() / 1000)\n const threshold = this.parseTimespan(this.renewalThreshold)\n return (exp - now) < threshold\n }\n\n /**\n * Parse timespan string to seconds\n */\n private parseTimespan(timespan: string): number {\n const match = timespan.match(/^(\\d+)([smhd])$/)\n if (!match || !match[1] || !match[2]) {\n throw new Error(`Invalid timespan format: ${timespan}`)\n }\n\n const value = parseInt(match[1]!, 10)\n const unit = match[2]!\n\n switch (unit) {\n case 's':\n return value\n case 'm':\n return value * 60\n case 'h':\n return value * 3600\n case 'd':\n return value * 86400\n default:\n throw new Error(`Unknown timespan unit: ${unit}`)\n }\n }\n}\n","import { FastifyPluginCallback } from 'fastify'\nimport { FastifyServer } from './fastify.ts'\nimport { ZodTypeProvider } from 'fastify-type-provider-zod'\n\nexport function createFastifyPlugin(cb: FastifyPluginCallback) {\n return function createFastifyPluginWrapper(\n fastify: FastifyServer,\n options: Parameters<FastifyPluginCallback>[1],\n done: Parameters<FastifyPluginCallback>[2],\n ) {\n const server = fastify.withTypeProvider<ZodTypeProvider>()\n let doneCalled = false\n\n const doneWrapper = (err?: Error) => {\n done(err)\n doneCalled = true\n }\n\n cb(server, options, doneWrapper)\n\n if (!doneCalled) {\n done()\n }\n }\n}\n","import { createFastifyPlugin } from '../plugin.ts'\nimport { FastifyServer } from '../fastify.ts'\n\nexport const healthPlugin = createFastifyPlugin((app: FastifyServer) => {\n app.get('/v1/health', {\n schema: {\n tags: ['health'],\n },\n config: {\n jwt: false,\n },\n handler: async () => {\n return {\n status: 200,\n message: 'ok',\n }\n },\n })\n})\n","export * from './lib/fastify.ts'\nexport * from './lib/jwt.service.ts'\nexport * from './lib/plugin.ts'\nexport * from './lib/plugins/healthPlugin.ts'\nexport * from 'fastify-type-provider-zod'\nexport type {\n FastifyRequest,\n FastifyReply,\n} from 'fastify'\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../lib/fastify.ts","../lib/response.ts","../lib/error-handler.ts","../lib/swagger-setup.ts","../lib/jwt-setup.ts","../lib/jwt.service.ts","../lib/plugin.ts","../lib/plugins/healthPlugin.ts","../index.ts"],"sourcesContent":["import Fastify, { FastifyServerOptions } from 'fastify'\nimport { serializerCompiler, validatorCompiler, ZodTypeProvider } from 'fastify-type-provider-zod'\nimport fastifyRateLimit from '@fastify/rate-limit'\nimport { setupErrorHandler } from './error-handler.ts'\nimport { setupSwagger, setupSwaggerSecurityHook } from './swagger-setup.ts'\nimport { setupJWT } from './jwt-setup.ts'\nimport type { CreateFastifyOptions, FastifyServer } from './types.ts'\n\n// Re-export types and utilities from other modules\nexport * from './response.ts'\nexport * from './types.ts'\n\nexport async function createFastify(options?: CreateFastifyOptions): Promise<FastifyServer> {\n const fastifyOptions: FastifyServerOptions = {}\n\n if (options?.logger) {\n fastifyOptions.loggerInstance = options.logger\n }\n\n const fastify = Fastify(fastifyOptions).withTypeProvider<ZodTypeProvider>()\n\n // Set up Zod validation and serialization\n fastify.setValidatorCompiler(validatorCompiler)\n fastify.setSerializerCompiler(serializerCompiler)\n\n // Set up error handler for standardized error responses\n setupErrorHandler(fastify)\n\n // Register Swagger first to capture all routes with Zod schemas\n const swaggerRoutePrefix = await setupSwagger(fastify, options || {})\n\n // Auto-inject security requirements for JWT-protected routes\n setupSwaggerSecurityHook(fastify, options || {}, swaggerRoutePrefix)\n\n // Register JWT authentication\n await setupJWT(fastify, options || {}, swaggerRoutePrefix)\n\n // Register Rate Limiting\n if (options?.rateLimit?.global) {\n await fastify.register(fastifyRateLimit, {\n global: true,\n ...options.rateLimit.global,\n })\n }\n\n return fastify\n}\n\nexport async function runFastify(fastify: FastifyServer, host: string, port: number) {\n try {\n await fastify.listen({ host, port })\n } catch (err) {\n fastify.log.error(err)\n process.exit(1)\n }\n}\n","import { z } from 'zod'\n\n// Response Types\nexport interface ValidationDetail {\n field: string\n message: string\n}\n\nexport interface SuccessResponse<T> {\n status: number\n success: true\n data: T\n}\n\nexport interface ErrorResponse {\n status: number\n success: false\n error: string\n message: string\n details?: ValidationDetail[]\n}\n\n// Response Formatters\nexport function formatSuccess<T>(status: number, data: T): SuccessResponse<T> {\n return {\n status,\n success: true,\n data,\n }\n}\n\nexport function formatError(\n status: number,\n error: string,\n message: string,\n details?: ValidationDetail[]\n): ErrorResponse {\n const response: ErrorResponse = {\n status,\n success: false,\n error,\n message,\n }\n if (details && details.length > 0) {\n response.details = details\n }\n return response\n}\n\n// Zod Schema Helpers for Standardized Responses\nexport const successResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) => z.object({\n status: z.number(),\n success: z.literal(true),\n data: dataSchema,\n})\n\nexport const errorResponseSchema = z.object({\n status: z.number(),\n success: z.literal(false),\n error: z.string(),\n message: z.string(),\n details: z.array(z.object({\n field: z.string(),\n message: z.string(),\n })).optional(),\n})\n","import type { FastifyError, FastifyInstance } from 'fastify'\nimport { formatError, type ValidationDetail } from './response.ts'\n\nexport function setupErrorHandler(fastify: FastifyInstance): void {\n fastify.setErrorHandler((error: FastifyError, request, reply) => {\n // Log all errors\n fastify.log.error({\n err: error,\n url: request.url,\n method: request.method,\n }, 'Request error')\n\n // Handle Zod validation errors\n if (error.validation) {\n const details: ValidationDetail[] = error.validation.map((issue: any) => {\n // Build field path from dataPath or instancePath\n const field = issue.instancePath || issue.dataPath || issue.params?.missingProperty || 'unknown'\n const cleanField = field.startsWith('/') ? field.slice(1).replace(/\\//g, '.') : field\n\n return {\n field: cleanField || 'unknown',\n message: issue.message || 'Validation failed',\n }\n })\n\n return reply.status(400).send(\n formatError(400, 'Validation Error', 'Request validation failed', details)\n )\n }\n\n // Handle rate limit errors\n if (error.statusCode === 429) {\n return reply.status(429).send(\n formatError(429, 'Too Many Requests', 'Rate limit exceeded')\n )\n }\n\n // Handle authentication errors\n if (error.statusCode === 401) {\n return reply.status(401).send(\n formatError(401, 'Unauthorized', error.message || 'Authentication required')\n )\n }\n\n // Handle authorization errors\n if (error.statusCode === 403) {\n return reply.status(403).send(\n formatError(403, 'Forbidden', error.message || 'Access denied')\n )\n }\n\n // Handle not found errors\n if (error.statusCode === 404) {\n return reply.status(404).send(\n formatError(404, 'Not Found', error.message || 'Resource not found')\n )\n }\n\n // Handle all other errors as internal server errors\n const statusCode = error.statusCode || 500\n return reply.status(statusCode).send(\n formatError(\n statusCode,\n statusCode === 500 ? 'Internal Server Error' : error.name || 'Error',\n error.message || 'An unexpected error occurred'\n )\n )\n })\n}\n","import type { FastifyInstance } from 'fastify'\nimport fastifySwagger, { SwaggerOptions } from '@fastify/swagger'\nimport fastifySwaggerUI, { FastifySwaggerUiOptions } from '@fastify/swagger-ui'\nimport { jsonSchemaTransform } from 'fastify-type-provider-zod'\nimport type { CreateFastifyOptions, JWTRouteConfig } from './types.ts'\n\nexport async function setupSwagger(\n fastify: FastifyInstance,\n options: CreateFastifyOptions\n): Promise<string | undefined> {\n if (!options.swagger) {\n return undefined\n }\n\n const openApiConfig: any = {\n openapi: {\n info: {\n title: options.swagger.title,\n version: options.swagger.version,\n description: options.swagger.description,\n },\n },\n transform: jsonSchemaTransform,\n }\n\n // Add bearer auth security scheme if JWT is enabled\n if (options.jwt) {\n openApiConfig.openapi.components = {\n securitySchemes: {\n bearerAuth: {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n },\n },\n }\n }\n\n await fastify.register(fastifySwagger, openApiConfig as SwaggerOptions)\n\n let routePrefix = options.swagger.routePrefix || '/docs/'\n\n if (!routePrefix.startsWith('/')) {\n routePrefix = '/' + routePrefix\n }\n\n if (!routePrefix.endsWith('/')) {\n routePrefix = routePrefix + '/'\n }\n\n await fastify.register(fastifySwaggerUI, { routePrefix } as FastifySwaggerUiOptions)\n\n return routePrefix\n}\n\nexport function setupSwaggerSecurityHook(\n fastify: FastifyInstance,\n options: CreateFastifyOptions,\n swaggerRoutePrefix: string | undefined\n): void {\n if (!options.jwt || !options.swagger) {\n return\n }\n\n // Auto-inject security requirements for JWT-protected routes\n fastify.addHook('onRoute', (routeOptions) => {\n // Skip Swagger routes\n if (swaggerRoutePrefix && routeOptions.url.startsWith(swaggerRoutePrefix)) {\n return\n }\n\n const routeConfig = (routeOptions.config as JWTRouteConfig) || {}\n const jwtConfig = routeConfig.jwt\n\n // Skip if JWT is explicitly bypassed\n if (jwtConfig === false) {\n return\n }\n\n // Determine if route requires JWT\n const requiresJWT =\n jwtConfig === true ||\n (typeof jwtConfig === 'object' && jwtConfig.required !== false) ||\n (jwtConfig === undefined && options.jwt?.global === true)\n\n // Inject security requirement if JWT is required\n if (requiresJWT) {\n if (!routeOptions.schema) {\n routeOptions.schema = {}\n }\n if (!routeOptions.schema.security) {\n routeOptions.schema.security = [{ bearerAuth: [] }]\n }\n }\n })\n}\n","import type { FastifyInstance } from 'fastify'\nimport fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'\nimport { formatError } from './response.ts'\nimport type { CreateFastifyOptions, JWTRouteConfig } from './types.ts'\n\nexport async function setupJWT(\n fastify: FastifyInstance,\n options: CreateFastifyOptions,\n swaggerRoutePrefix: string | undefined\n): Promise<void> {\n if (!options.jwt) {\n return\n }\n\n const jwtOptions: FastifyJWTOptions = {\n secret: options.jwt.secret,\n }\n if (options.jwt.sign !== undefined) {\n jwtOptions.sign = options.jwt.sign\n }\n if (options.jwt.verify !== undefined) {\n jwtOptions.verify = options.jwt.verify\n }\n await fastify.register(fastifyJwt, jwtOptions)\n\n // Global JWT checking hook\n if (options.jwt.global) {\n fastify.addHook('onRequest', async (request, reply) => {\n // Skip JWT verification for Swagger documentation routes\n if (swaggerRoutePrefix && request.url.startsWith(swaggerRoutePrefix)) {\n return\n }\n\n const routeConfig = (request.routeOptions.config as JWTRouteConfig) || {}\n const jwtConfig = routeConfig.jwt\n\n // Check if route explicitly bypasses JWT\n if (jwtConfig === false) {\n return\n }\n\n // Check if route explicitly requires JWT or uses global setting\n const shouldVerify =\n jwtConfig === true ||\n (typeof jwtConfig === 'object' && jwtConfig.required !== false) ||\n (jwtConfig === undefined && options.jwt?.global === true)\n\n if (shouldVerify) {\n try {\n await request.jwtVerify()\n\n // Custom authorization - check JWT info before route processing\n if (typeof jwtConfig === 'object' && jwtConfig.authorize) {\n const authorized = await jwtConfig.authorize(request, reply, request.user)\n if (!authorized) {\n return reply.status(403).send(\n formatError(403, 'Forbidden', 'Authorization failed')\n )\n }\n }\n } catch (err) {\n return reply.status(401).send(\n formatError(401, 'Unauthorized', 'Invalid or missing JWT token')\n )\n }\n }\n })\n }\n}\n","import { z } from 'zod'\nimport { FastifyServer } from './fastify.ts'\n\n/**\n * Base access token payload schema\n */\nexport const baseAccessTokenPayloadSchema = z.object({\n identityId: z.string(),\n type: z.literal('access'),\n exp: z.number(),\n})\n\n/**\n * Base refresh token payload schema\n */\nexport const baseRefreshTokenPayloadSchema = z.object({\n identityId: z.string(),\n type: z.literal('refresh'),\n exp: z.number(),\n})\n\n/**\n * Inferred types from base schemas\n */\nexport type BaseAccessTokenPayload = z.infer<typeof baseAccessTokenPayloadSchema>\nexport type BaseRefreshTokenPayload = z.infer<typeof baseRefreshTokenPayloadSchema>\n\n/**\n * Token pair interface\n */\nexport interface TokenPair {\n accessToken: string\n refreshToken: string\n}\n\n/**\n * FastifyJwtService configuration options\n */\nexport interface FastifyJwtServiceOptions {\n accessTokenExpiry: string\n refreshTokenExpiry: string\n renewalThreshold: string\n accessTokenSchema?: z.ZodSchema\n refreshTokenSchema?: z.ZodSchema\n}\n\n/**\n * Generic JWT service for Fastify applications\n *\n * @template TAccess - Access token payload type extending BaseAccessTokenPayload\n * @template TRefresh - Refresh token payload type extending BaseRefreshTokenPayload\n *\n * @example\n * // Using base schemas\n * const jwtService = new FastifyJwtService(app, {\n * accessTokenExpiry: '15m',\n * refreshTokenExpiry: '7d',\n * renewalThreshold: '1d',\n * })\n *\n * @example\n * // Using extended schemas\n * const customAccessSchema = baseAccessTokenPayloadSchema.extend({\n * roles: z.array(z.string())\n * })\n * const jwtService = new FastifyJwtService(app, {\n * accessTokenExpiry: '15m',\n * refreshTokenExpiry: '7d',\n * renewalThreshold: '1d',\n * accessTokenSchema: customAccessSchema,\n * })\n */\nexport class FastifyJwtService<\n TAccess extends BaseAccessTokenPayload = BaseAccessTokenPayload,\n TRefresh extends BaseRefreshTokenPayload = BaseRefreshTokenPayload\n> {\n private readonly accessTokenExpiry: string\n private readonly refreshTokenExpiry: string\n private readonly renewalThreshold: string\n private readonly accessTokenSchema: z.ZodSchema<TAccess>\n private readonly refreshTokenSchema: z.ZodSchema<TRefresh>\n\n constructor(\n private readonly app: FastifyServer,\n options: FastifyJwtServiceOptions\n ) {\n this.accessTokenExpiry = options.accessTokenExpiry\n this.refreshTokenExpiry = options.refreshTokenExpiry\n this.renewalThreshold = options.renewalThreshold\n this.accessTokenSchema = (options.accessTokenSchema ?? baseAccessTokenPayloadSchema) as z.ZodSchema<TAccess>\n this.refreshTokenSchema = (options.refreshTokenSchema ?? baseRefreshTokenPayloadSchema) as z.ZodSchema<TRefresh>\n }\n\n /**\n * Generate an access token for the given identity\n */\n generateAccessToken(identityId: string, extraPayload?: Partial<Omit<TAccess, 'identityId' | 'type' | 'exp'>>): string {\n const payload = {\n identityId,\n type: 'access' as const,\n ...extraPayload,\n }\n\n return this.app.jwt.sign(payload, {\n expiresIn: this.accessTokenExpiry,\n })\n }\n\n /**\n * Generate a refresh token for the given identity\n */\n generateRefreshToken(identityId: string, extraPayload?: Partial<Omit<TRefresh, 'identityId' | 'type' | 'exp'>>): string {\n const payload = {\n identityId,\n type: 'refresh' as const,\n ...extraPayload,\n }\n\n return this.app.jwt.sign(payload, {\n expiresIn: this.refreshTokenExpiry,\n })\n }\n\n /**\n * Generate both access and refresh tokens\n */\n generateTokenPair(identityId: string): TokenPair {\n return {\n accessToken: this.generateAccessToken(identityId),\n refreshToken: this.generateRefreshToken(identityId),\n }\n }\n\n /**\n * Verify and decode an access token\n */\n verifyAccessToken(token: string): TAccess {\n try {\n const payload = this.app.jwt.verify(token) as unknown\n const validated = this.accessTokenSchema.parse(payload)\n\n if (validated.type !== 'access') {\n throw new Error('Invalid token type')\n }\n\n return validated\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw new Error('Invalid token payload')\n }\n throw error\n }\n }\n\n /**\n * Verify and decode a refresh token\n */\n verifyRefreshToken(token: string): TRefresh {\n try {\n const payload = this.app.jwt.verify(token) as unknown\n const validated = this.refreshTokenSchema.parse(payload)\n\n if (validated.type !== 'refresh') {\n throw new Error('Invalid token type')\n }\n\n return validated\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw new Error('Invalid token payload')\n }\n throw error\n }\n }\n\n /**\n * Renew a refresh token for the given identity\n */\n renewRefreshToken(identityId: string): string {\n return this.generateRefreshToken(identityId)\n }\n\n /**\n * Check if a refresh token should be renewed based on its expiry\n */\n shouldRenewRefreshToken(exp: number): boolean {\n const now = Math.floor(Date.now() / 1000)\n const threshold = this.parseTimespan(this.renewalThreshold)\n return (exp - now) < threshold\n }\n\n /**\n * Parse timespan string to seconds\n */\n private parseTimespan(timespan: string): number {\n const match = timespan.match(/^(\\d+)([smhd])$/)\n if (!match || !match[1] || !match[2]) {\n throw new Error(`Invalid timespan format: ${timespan}`)\n }\n\n const value = parseInt(match[1]!, 10)\n const unit = match[2]!\n\n switch (unit) {\n case 's':\n return value\n case 'm':\n return value * 60\n case 'h':\n return value * 3600\n case 'd':\n return value * 86400\n default:\n throw new Error(`Unknown timespan unit: ${unit}`)\n }\n }\n}\n","import { FastifyPluginCallback } from 'fastify'\nimport { FastifyServer } from './fastify.ts'\nimport { ZodTypeProvider } from 'fastify-type-provider-zod'\n\nexport function createFastifyPlugin(cb: FastifyPluginCallback) {\n return function createFastifyPluginWrapper(\n fastify: FastifyServer,\n options: Parameters<FastifyPluginCallback>[1],\n done: Parameters<FastifyPluginCallback>[2],\n ) {\n const server = fastify.withTypeProvider<ZodTypeProvider>()\n let doneCalled = false\n\n const doneWrapper = (err?: Error) => {\n done(err)\n doneCalled = true\n }\n\n cb(server, options, doneWrapper)\n\n if (!doneCalled) {\n done()\n }\n }\n}\n","import { createFastifyPlugin } from '../plugin.ts'\nimport { FastifyServer } from '../fastify.ts'\n\nexport const healthPlugin = createFastifyPlugin((app: FastifyServer) => {\n app.get('/v1/health', {\n schema: {\n tags: ['health'],\n },\n config: {\n jwt: false,\n },\n handler: async () => {\n return {\n status: 200,\n message: 'ok',\n }\n },\n })\n})\n","export * from './lib/fastify.ts'\nexport * from './lib/response.ts'\nexport * from './lib/types.ts'\nexport * from './lib/jwt.service.ts'\nexport * from './lib/plugin.ts'\nexport * from './lib/plugins/healthPlugin.ts'\nexport * from 'fastify-type-provider-zod'\nexport type {\n FastifyRequest,\n FastifyReply,\n} from 'fastify'\n"],"mappings":"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","names":["z"]}
|
package/package.json
CHANGED