@a_jackie_z/fastify 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # @a_jackie_z/fastify
2
+
3
+ A collection of Fastify plugins and utilities for building robust web applications with built-in support for JWT authentication, rate limiting, Swagger documentation, and TypeBox validation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @a_jackie_z/fastify
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### Basic Setup
14
+
15
+ ```typescript
16
+ import { createFastify } from '@a_jackie_z/fastify'
17
+ import { Type } from '@sinclair/typebox'
18
+
19
+ const app = await createFastify()
20
+
21
+ app.route({
22
+ method: 'GET',
23
+ url: '/hello',
24
+ schema: {
25
+ response: {
26
+ 200: Type.Object({
27
+ message: Type.String(),
28
+ }),
29
+ },
30
+ },
31
+ handler: async () => {
32
+ return { message: 'Hello World!' }
33
+ },
34
+ })
35
+
36
+ await app.listen({ port: 3000 })
37
+ ```
38
+
39
+ ## Features
40
+
41
+ - **JWT Authentication** - Built-in JWT support with global and route-level configuration
42
+ - **Rate Limiting** - Flexible rate limiting at global and route levels
43
+ - **Swagger Documentation** - Auto-generated API documentation with TypeBox schemas
44
+ - **TypeBox Integration** - Type-safe request/response validation
45
+ - **Logger Integration** - Uses `@a_jackie_z/logger` for consistent logging
46
+
47
+ ## Examples
48
+
49
+ ### 1. Complete Setup with All Features
50
+
51
+ ```typescript
52
+ import { createFastify } from '@a_jackie_z/fastify'
53
+ import { Type } from '@sinclair/typebox'
54
+
55
+ const app = await createFastify({
56
+ // Rate limiting
57
+ rateLimit: {
58
+ global: {
59
+ max: 100,
60
+ timeWindow: '1 minute',
61
+ },
62
+ },
63
+ // JWT authentication
64
+ jwt: {
65
+ secret: 'your-secret-key',
66
+ sign: {
67
+ expiresIn: '1h',
68
+ },
69
+ global: true, // Require JWT for all routes by default
70
+ },
71
+ // Swagger documentation
72
+ swagger: {
73
+ title: 'My API',
74
+ version: '1.0.0',
75
+ description: 'API documentation',
76
+ routePrefix: '/docs',
77
+ },
78
+ })
79
+
80
+ await app.listen({ port: 3000 })
81
+ ```
82
+
83
+ ### 2. Authentication Flow
84
+
85
+ #### Login Route (Public)
86
+
87
+ ```typescript
88
+ app.route({
89
+ method: 'POST',
90
+ url: '/auth/login',
91
+ config: {
92
+ jwt: false, // Bypass JWT check for login
93
+ },
94
+ schema: {
95
+ body: Type.Object({
96
+ username: Type.String(),
97
+ password: Type.String(),
98
+ }),
99
+ response: {
100
+ 200: Type.Object({
101
+ token: Type.String(),
102
+ }),
103
+ 401: Type.Object({
104
+ error: Type.String(),
105
+ }),
106
+ },
107
+ },
108
+ handler: async (request, reply) => {
109
+ const { username, password } = request.body
110
+
111
+ // Validate credentials
112
+ if (username === 'admin' && password === 'secret') {
113
+ const token = app.jwt.sign({
114
+ username,
115
+ role: 'admin',
116
+ permissions: ['read', 'write', 'delete'],
117
+ })
118
+ return { token }
119
+ }
120
+
121
+ reply.status(401)
122
+ return { error: 'Invalid credentials' }
123
+ },
124
+ })
125
+ ```
126
+
127
+ #### Public Route
128
+
129
+ ```typescript
130
+ app.route({
131
+ method: 'GET',
132
+ url: '/public',
133
+ config: {
134
+ jwt: false, // No authentication required
135
+ },
136
+ schema: {
137
+ response: {
138
+ 200: Type.Object({
139
+ message: Type.String(),
140
+ }),
141
+ },
142
+ },
143
+ handler: async () => {
144
+ return { message: 'Public endpoint - no auth required' }
145
+ },
146
+ })
147
+ ```
148
+
149
+ #### Protected Route (Auto-Protected by Global JWT)
150
+
151
+ ```typescript
152
+ app.route({
153
+ method: 'GET',
154
+ url: '/protected',
155
+ // No config needed - global JWT setting applies
156
+ schema: {
157
+ response: {
158
+ 200: Type.Object({
159
+ message: Type.String(),
160
+ user: Type.Unknown(),
161
+ }),
162
+ },
163
+ },
164
+ handler: async (request) => {
165
+ return {
166
+ message: 'Protected data',
167
+ user: request.user, // JWT payload
168
+ }
169
+ },
170
+ })
171
+ ```
172
+
173
+ ### 3. Role-Based Authorization
174
+
175
+ ```typescript
176
+ app.route({
177
+ method: 'GET',
178
+ url: '/admin',
179
+ config: {
180
+ jwt: {
181
+ authorize: async (_request, _reply, user) => {
182
+ // Check if user has admin role
183
+ return user?.role === 'admin'
184
+ },
185
+ },
186
+ },
187
+ schema: {
188
+ response: {
189
+ 200: Type.Object({
190
+ message: Type.String(),
191
+ }),
192
+ 403: Type.Object({
193
+ error: Type.String(),
194
+ message: Type.String(),
195
+ }),
196
+ },
197
+ },
198
+ handler: async () => {
199
+ return { message: 'Admin-only content' }
200
+ },
201
+ })
202
+ ```
203
+
204
+ ### 4. Permission-Based Authorization
205
+
206
+ ```typescript
207
+ app.route({
208
+ method: 'DELETE',
209
+ url: '/operations/:id',
210
+ config: {
211
+ jwt: {
212
+ authorize: async (_request, _reply, user) => {
213
+ // Check if user has delete permission
214
+ return user?.permissions?.includes('delete') === true
215
+ },
216
+ },
217
+ },
218
+ schema: {
219
+ params: Type.Object({
220
+ id: Type.String(),
221
+ }),
222
+ response: {
223
+ 200: Type.Object({
224
+ message: Type.String(),
225
+ }),
226
+ },
227
+ },
228
+ handler: async (request) => {
229
+ const { id } = request.params
230
+ return { message: `Operation ${id} deleted` }
231
+ },
232
+ })
233
+ ```
234
+
235
+ ### 5. Custom Authorization Logic
236
+
237
+ ```typescript
238
+ app.route({
239
+ method: 'POST',
240
+ url: '/custom-auth',
241
+ config: {
242
+ jwt: {
243
+ authorize: async (request, _reply, user) => {
244
+ // Custom logic: only allow specific users
245
+ if (user?.username === 'special-user') {
246
+ return true
247
+ }
248
+ // Check request headers
249
+ if (request.headers['x-custom-header'] === 'allowed') {
250
+ return true
251
+ }
252
+ return false
253
+ },
254
+ },
255
+ },
256
+ handler: async () => {
257
+ return { message: 'Custom authorization passed' }
258
+ },
259
+ })
260
+ ```
261
+
262
+ ### 6. Rate Limiting
263
+
264
+ #### Route-Specific Rate Limit
265
+
266
+ ```typescript
267
+ app.route({
268
+ method: 'GET',
269
+ url: '/limited',
270
+ config: {
271
+ rateLimit: {
272
+ max: 5,
273
+ timeWindow: '1 minute',
274
+ },
275
+ },
276
+ handler: async () => {
277
+ return { message: 'Strict rate limiting applied' }
278
+ },
279
+ })
280
+ ```
281
+
282
+ #### Disable Rate Limit for Specific Route
283
+
284
+ ```typescript
285
+ app.route({
286
+ method: 'GET',
287
+ url: '/unlimited',
288
+ config: {
289
+ rateLimit: false,
290
+ },
291
+ handler: async () => {
292
+ return { message: 'No rate limiting' }
293
+ },
294
+ })
295
+ ```
296
+
297
+ ### 7. TypeBox Schema Validation
298
+
299
+ ```typescript
300
+ app.route({
301
+ method: 'POST',
302
+ url: '/users',
303
+ schema: {
304
+ body: Type.Object({
305
+ name: Type.String({ minLength: 1, maxLength: 100 }),
306
+ email: Type.String({ format: 'email' }),
307
+ age: Type.Optional(Type.Number({ minimum: 0, maximum: 150 })),
308
+ }),
309
+ response: {
310
+ 201: Type.Object({
311
+ id: Type.String(),
312
+ name: Type.String(),
313
+ email: Type.String(),
314
+ }),
315
+ 400: Type.Object({
316
+ error: Type.String(),
317
+ }),
318
+ },
319
+ },
320
+ handler: async (request, reply) => {
321
+ const user = request.body
322
+ reply.status(201)
323
+ return {
324
+ id: 'generated-id',
325
+ ...user,
326
+ }
327
+ },
328
+ })
329
+ ```
330
+
331
+ ## Configuration Options
332
+
333
+ ### `CreateFastifyOptions`
334
+
335
+ ```typescript
336
+ interface CreateFastifyOptions {
337
+ rateLimit?: {
338
+ global?: RateLimitPluginOptions
339
+ }
340
+ jwt?: {
341
+ secret: string
342
+ sign?: FastifyJWTOptions['sign']
343
+ verify?: FastifyJWTOptions['verify']
344
+ global?: boolean // Enable JWT check globally for all routes
345
+ }
346
+ swagger?: {
347
+ title: string
348
+ version: string
349
+ description: string
350
+ routePrefix?: string // Default: '/documentation'
351
+ }
352
+ }
353
+ ```
354
+
355
+ ### Route JWT Configuration
356
+
357
+ ```typescript
358
+ interface JWTRouteConfig {
359
+ jwt?:
360
+ | boolean // true = require JWT, false = bypass JWT
361
+ | {
362
+ required?: boolean // Default: true if global enabled
363
+ authorize?: JWTAuthorizationHandler // Custom authorization logic
364
+ }
365
+ }
366
+
367
+ type JWTAuthorizationHandler = (
368
+ request: FastifyRequest,
369
+ reply: FastifyReply,
370
+ user: any
371
+ ) => Promise<boolean> | boolean
372
+ ```
373
+
374
+ ## Usage Patterns
375
+
376
+ ### Pattern 1: Global JWT with Selective Public Routes
377
+
378
+ ```typescript
379
+ const app = await createFastify({
380
+ jwt: {
381
+ secret: 'your-secret',
382
+ global: true, // All routes require JWT by default
383
+ },
384
+ })
385
+
386
+ // Public routes explicitly bypass JWT
387
+ app.route({
388
+ method: 'POST',
389
+ url: '/auth/login',
390
+ config: { jwt: false },
391
+ handler: async () => { /* ... */ },
392
+ })
393
+
394
+ // Protected routes don't need config
395
+ app.route({
396
+ method: 'GET',
397
+ url: '/data',
398
+ handler: async () => { /* ... */ },
399
+ })
400
+ ```
401
+
402
+ ### Pattern 2: Opt-In JWT Protection
403
+
404
+ ```typescript
405
+ const app = await createFastify({
406
+ jwt: {
407
+ secret: 'your-secret',
408
+ global: false, // JWT not required by default
409
+ },
410
+ })
411
+
412
+ // Unprotected by default
413
+ app.route({
414
+ method: 'GET',
415
+ url: '/public',
416
+ handler: async () => { /* ... */ },
417
+ })
418
+
419
+ // Explicitly require JWT
420
+ app.route({
421
+ method: 'GET',
422
+ url: '/protected',
423
+ config: { jwt: true },
424
+ handler: async () => { /* ... */ },
425
+ })
426
+ ```
427
+
428
+ ### Pattern 3: Multi-Tier Authorization
429
+
430
+ ```typescript
431
+ const app = await createFastify({
432
+ jwt: {
433
+ secret: 'your-secret',
434
+ global: true,
435
+ },
436
+ })
437
+
438
+ // Tier 1: Any authenticated user
439
+ app.route({
440
+ method: 'GET',
441
+ url: '/user/profile',
442
+ handler: async (request) => {
443
+ return request.user
444
+ },
445
+ })
446
+
447
+ // Tier 2: Admin users only
448
+ app.route({
449
+ method: 'GET',
450
+ url: '/admin/users',
451
+ config: {
452
+ jwt: {
453
+ authorize: async (_req, _reply, user) => user?.role === 'admin',
454
+ },
455
+ },
456
+ handler: async () => { /* ... */ },
457
+ })
458
+
459
+ // Tier 3: Super admin with specific permission
460
+ app.route({
461
+ method: 'DELETE',
462
+ url: '/admin/system',
463
+ config: {
464
+ jwt: {
465
+ authorize: async (_req, _reply, user) =>
466
+ user?.role === 'admin' && user?.permissions?.includes('system:delete'),
467
+ },
468
+ },
469
+ handler: async () => { /* ... */ },
470
+ })
471
+ ```
472
+
473
+ ## Testing Your API
474
+
475
+ 1. Start your server
476
+ 2. Access Swagger documentation at `http://localhost:3000/docs`
477
+ 3. Test authentication:
478
+
479
+ ```bash
480
+ # Login
481
+ curl -X POST http://localhost:3000/auth/login \
482
+ -H "Content-Type: application/json" \
483
+ -d '{"username":"admin","password":"secret"}'
484
+
485
+ # Use token
486
+ curl http://localhost:3000/protected \
487
+ -H "Authorization: Bearer YOUR_TOKEN"
488
+ ```
489
+
490
+ ## License
491
+
492
+ MIT
@@ -0,0 +1,49 @@
1
+ import * as fastify from 'fastify';
2
+ import { FastifyRequest, FastifyReply, FastifyServerOptions, FastifyInstance, RawServerDefault, FastifyBaseLogger, FastifyContextConfig, FastifyPluginCallback } from 'fastify';
3
+ import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
4
+ import { RateLimitPluginOptions } from '@fastify/rate-limit';
5
+ import { FastifyJWTOptions } from '@fastify/jwt';
6
+ import { IncomingMessage, ServerResponse } from 'node:http';
7
+
8
+ type JWTAuthorizationHandler = <TUser = unknown>(request: FastifyRequest, reply: FastifyReply, user: TUser) => Promise<boolean> | boolean;
9
+ interface JWTRouteConfig {
10
+ jwt?: boolean | {
11
+ required?: boolean;
12
+ authorize?: JWTAuthorizationHandler;
13
+ };
14
+ }
15
+ declare module 'fastify' {
16
+ interface FastifyContextConfig {
17
+ jwt?: boolean | {
18
+ required?: boolean;
19
+ authorize?: JWTAuthorizationHandler;
20
+ };
21
+ }
22
+ }
23
+ interface CreateFastifyOptions {
24
+ logger?: FastifyServerOptions['loggerInstance'];
25
+ rateLimit?: {
26
+ global?: RateLimitPluginOptions;
27
+ };
28
+ jwt?: {
29
+ secret: string;
30
+ sign?: FastifyJWTOptions['sign'];
31
+ verify?: FastifyJWTOptions['verify'];
32
+ global?: boolean;
33
+ };
34
+ swagger?: {
35
+ title: string;
36
+ version: string;
37
+ description: string;
38
+ routePrefix?: string;
39
+ };
40
+ }
41
+ type FastifyServer = FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse, FastifyBaseLogger, TypeBoxTypeProvider> & FastifyContextConfig;
42
+ declare function createFastify(options?: CreateFastifyOptions): Promise<FastifyServer>;
43
+ declare function runFastify(fastify: FastifyServer, host: string, port: number): Promise<void>;
44
+
45
+ declare function createFastifyPlugin(cb: FastifyPluginCallback): FastifyPluginCallback;
46
+
47
+ declare const healthPlugin: fastify.FastifyPluginCallback;
48
+
49
+ export { type CreateFastifyOptions, type FastifyServer, type JWTAuthorizationHandler, type JWTRouteConfig, createFastify, createFastifyPlugin, healthPlugin, runFastify };
package/dist/index.js ADDED
@@ -0,0 +1,116 @@
1
+ // lib/fastify.ts
2
+ import Fastify from "fastify";
3
+ import fastifyRateLimit from "@fastify/rate-limit";
4
+ import fastifyJwt from "@fastify/jwt";
5
+ import fastifySwagger from "@fastify/swagger";
6
+ import fastifySwaggerUI from "@fastify/swagger-ui";
7
+ async function createFastify(options) {
8
+ const fastifyOptions = {};
9
+ if (options?.logger) {
10
+ fastifyOptions.loggerInstance = options.logger;
11
+ }
12
+ const fastify = Fastify(fastifyOptions).withTypeProvider();
13
+ if (options?.swagger) {
14
+ await fastify.register(fastifySwagger, {
15
+ openapi: {
16
+ info: {
17
+ title: options.swagger.title,
18
+ version: options.swagger.version,
19
+ description: options.swagger.description
20
+ }
21
+ }
22
+ });
23
+ let routePrefix = options.swagger.routePrefix || "/docs/";
24
+ if (!routePrefix.startsWith("/")) {
25
+ routePrefix = "/" + routePrefix;
26
+ }
27
+ if (!routePrefix.endsWith("/")) {
28
+ routePrefix = routePrefix + "/";
29
+ }
30
+ await fastify.register(fastifySwaggerUI, { routePrefix });
31
+ }
32
+ if (options?.jwt) {
33
+ const jwtOptions = {
34
+ secret: options.jwt.secret
35
+ };
36
+ if (options.jwt.sign !== void 0) {
37
+ jwtOptions.sign = options.jwt.sign;
38
+ }
39
+ if (options.jwt.verify !== void 0) {
40
+ jwtOptions.verify = options.jwt.verify;
41
+ }
42
+ await fastify.register(fastifyJwt, jwtOptions);
43
+ if (options.jwt.global) {
44
+ fastify.addHook("onRequest", async (request, reply) => {
45
+ const routeConfig = request.routeOptions.config || {};
46
+ const jwtConfig = routeConfig.jwt;
47
+ if (jwtConfig === false) {
48
+ return;
49
+ }
50
+ const shouldVerify = jwtConfig === true || typeof jwtConfig === "object" && jwtConfig.required !== false || jwtConfig === void 0 && options?.jwt?.global === true;
51
+ if (shouldVerify) {
52
+ try {
53
+ await request.jwtVerify();
54
+ if (typeof jwtConfig === "object" && jwtConfig.authorize) {
55
+ const authorized = await jwtConfig.authorize(request, reply, request.user);
56
+ if (!authorized) {
57
+ reply.status(403).send({
58
+ error: "Forbidden",
59
+ message: "Authorization failed"
60
+ });
61
+ return;
62
+ }
63
+ }
64
+ } catch (err) {
65
+ reply.status(401).send({
66
+ error: "Unauthorized",
67
+ message: "Invalid or missing JWT token"
68
+ });
69
+ }
70
+ }
71
+ });
72
+ }
73
+ }
74
+ if (options?.rateLimit?.global) {
75
+ await fastify.register(fastifyRateLimit, {
76
+ global: true,
77
+ ...options.rateLimit.global
78
+ });
79
+ }
80
+ return fastify;
81
+ }
82
+ async function runFastify(fastify, host, port) {
83
+ try {
84
+ await fastify.listen({ host, port });
85
+ } catch (err) {
86
+ fastify.log.error(err);
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ // lib/plugin.ts
92
+ function createFastifyPlugin(cb) {
93
+ return cb;
94
+ }
95
+
96
+ // lib/plugins/healthPlugin.ts
97
+ var healthPlugin = createFastifyPlugin((app) => {
98
+ app.get("/v1/health", {
99
+ schema: {
100
+ tags: ["health"]
101
+ },
102
+ handler: async () => {
103
+ return {
104
+ status: 200,
105
+ message: "ok"
106
+ };
107
+ }
108
+ });
109
+ });
110
+ export {
111
+ createFastify,
112
+ createFastifyPlugin,
113
+ healthPlugin,
114
+ runFastify
115
+ };
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../lib/fastify.ts","../lib/plugin.ts","../lib/plugins/healthPlugin.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 { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'\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 TypeBoxTypeProvider\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<TypeBoxTypeProvider>()\n\n // Register Swagger first to capture all routes with TypeBox 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 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 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 { FastifyPluginCallback } from 'fastify'\n\nexport function createFastifyPlugin(cb: FastifyPluginCallback) {\n return cb;\n}\n","import { createFastifyPlugin } from '../plugin.ts'\n\nexport const healthPlugin = createFastifyPlugin((app) => {\n app.get('/v1/health', {\n schema: {\n tags: ['health'],\n },\n handler: async () => {\n return {\n status: 200,\n message: 'ok',\n }\n },\n })\n})\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@a_jackie_z/fastify",
3
+ "version": "1.0.0",
4
+ "description": "A collection of Fastify plugins and utilities for building robust web applications.",
5
+ "license": "MIT",
6
+ "author": "Sang Lu <connect.with.sang@gmail.com>",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup"
21
+ },
22
+ "dependencies": {
23
+ "@fastify/jwt": "^10.0.0",
24
+ "@fastify/rate-limit": "^10.3.0",
25
+ "@fastify/swagger": "^9.6.1",
26
+ "@fastify/swagger-ui": "^5.2.4",
27
+ "@fastify/type-provider-typebox": "^6.1.0",
28
+ "@sinclair/typebox": "^0.34.47",
29
+ "fastify": "^5.7.1",
30
+ "typebox": "^1.0.79"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.0.9",
34
+ "tsup": "^8.5.1",
35
+ "tsx": "^4.7.0",
36
+ "typescript": "^5.9.3"
37
+ }
38
+ }