@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 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
- message: z.string(),
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
- token: z.string(),
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
- return { error: 'Invalid credentials' }
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
- import fastifyJwt from "@fastify/jwt";
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
- async function createFastify(options) {
12
- const fastifyOptions = {};
13
- if (options?.logger) {
14
- fastifyOptions.loggerInstance = options.logger;
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 fastify = Fastify(fastifyOptions).withTypeProvider();
17
- fastify.setValidatorCompiler(validatorCompiler);
18
- fastify.setSerializerCompiler(serializerCompiler);
19
- let swaggerRoutePrefix;
20
- if (options?.swagger) {
21
- await fastify.register(fastifySwagger, {
22
- openapi: {
23
- info: {
24
- title: options.swagger.title,
25
- version: options.swagger.version,
26
- description: options.swagger.description
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
- if (options.jwt.sign !== void 0) {
45
- jwtOptions.sign = options.jwt.sign;
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
- if (options.jwt.verify !== void 0) {
48
- jwtOptions.verify = options.jwt.verify;
143
+ const routeConfig = routeOptions.config || {};
144
+ const jwtConfig = routeConfig.jwt;
145
+ if (jwtConfig === false) {
146
+ return;
49
147
  }
50
- await fastify.register(fastifyJwt, jwtOptions);
51
- if (options.jwt.global) {
52
- fastify.addHook("onRequest", async (request, reply) => {
53
- if (swaggerRoutePrefix && request.url.startsWith(swaggerRoutePrefix)) {
54
- return;
55
- }
56
- const routeConfig = request.routeOptions.config || {};
57
- const jwtConfig = routeConfig.jwt;
58
- if (jwtConfig === false) {
59
- return;
60
- }
61
- const shouldVerify = jwtConfig === true || typeof jwtConfig === "object" && jwtConfig.required !== false || jwtConfig === void 0 && options?.jwt?.global === true;
62
- if (shouldVerify) {
63
- try {
64
- await request.jwtVerify();
65
- if (typeof jwtConfig === "object" && jwtConfig.authorize) {
66
- const authorized = await jwtConfig.authorize(request, reply, request.user);
67
- if (!authorized) {
68
- reply.status(403).send({
69
- error: "Forbidden",
70
- message: "Authorization failed"
71
- });
72
- return;
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 = z.object({
105
- identityId: z.string(),
106
- type: z.literal("access"),
107
- exp: z.number()
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 = z.object({
110
- identityId: z.string(),
111
- type: z.literal("refresh"),
112
- exp: z.number()
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 z.ZodError) {
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 z.ZodError) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a_jackie_z/fastify",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "A collection of Fastify plugins and utilities for building robust web applications.",
5
5
  "license": "MIT",
6
6
  "author": "Sang Lu <connect.with.sang@gmail.com>",