@a_jackie_z/fastify 1.0.0 → 1.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @a_jackie_z/fastify
2
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.
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 Zod validation.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,8 +13,8 @@ npm install @a_jackie_z/fastify
13
13
  ### Basic Setup
14
14
 
15
15
  ```typescript
16
- import { createFastify } from '@a_jackie_z/fastify'
17
- import { Type } from '@sinclair/typebox'
16
+ import { createFastify, runFastify } from '@a_jackie_z/fastify'
17
+ import { z } from 'zod'
18
18
 
19
19
  const app = await createFastify()
20
20
 
@@ -23,8 +23,8 @@ app.route({
23
23
  url: '/hello',
24
24
  schema: {
25
25
  response: {
26
- 200: Type.Object({
27
- message: Type.String(),
26
+ 200: z.object({
27
+ message: z.string(),
28
28
  }),
29
29
  },
30
30
  },
@@ -33,26 +33,31 @@ app.route({
33
33
  },
34
34
  })
35
35
 
36
- await app.listen({ port: 3000 })
36
+ await runFastify(app, '0.0.0.0', 3000)
37
37
  ```
38
38
 
39
39
  ## Features
40
40
 
41
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
42
+ - **Rate Limiting** - Global rate limiting for API protection
43
+ - **Swagger Documentation** - Auto-generated API documentation with Zod schemas
44
+ - **Zod Integration** - Type-safe request/response validation
45
+ - **Custom Logger Support** - Integrate any Fastify-compatible logger
46
+ - **Health Check Plugin** - Ready-to-use health check endpoint
47
+ - **Plugin Helper** - Utility for creating reusable Fastify plugins
46
48
 
47
49
  ## Examples
48
50
 
49
51
  ### 1. Complete Setup with All Features
50
52
 
51
53
  ```typescript
52
- import { createFastify } from '@a_jackie_z/fastify'
53
- import { Type } from '@sinclair/typebox'
54
+ import { createFastify, runFastify } from '@a_jackie_z/fastify'
55
+ import { z } from 'zod'
54
56
 
55
57
  const app = await createFastify({
58
+ // Optional: Integrate your logger
59
+ // logger: yourFastifyLogger,
60
+
56
61
  // Rate limiting
57
62
  rateLimit: {
58
63
  global: {
@@ -77,7 +82,7 @@ const app = await createFastify({
77
82
  },
78
83
  })
79
84
 
80
- await app.listen({ port: 3000 })
85
+ await runFastify(app, '0.0.0.0', 3000)
81
86
  ```
82
87
 
83
88
  ### 2. Authentication Flow
@@ -92,16 +97,16 @@ app.route({
92
97
  jwt: false, // Bypass JWT check for login
93
98
  },
94
99
  schema: {
95
- body: Type.Object({
96
- username: Type.String(),
97
- password: Type.String(),
100
+ body: z.object({
101
+ username: z.string(),
102
+ password: z.string(),
98
103
  }),
99
104
  response: {
100
- 200: Type.Object({
101
- token: Type.String(),
105
+ 200: z.object({
106
+ token: z.string(),
102
107
  }),
103
- 401: Type.Object({
104
- error: Type.String(),
108
+ 401: z.object({
109
+ error: z.string(),
105
110
  }),
106
111
  },
107
112
  },
@@ -135,8 +140,8 @@ app.route({
135
140
  },
136
141
  schema: {
137
142
  response: {
138
- 200: Type.Object({
139
- message: Type.String(),
143
+ 200: z.object({
144
+ message: z.string(),
140
145
  }),
141
146
  },
142
147
  },
@@ -155,9 +160,9 @@ app.route({
155
160
  // No config needed - global JWT setting applies
156
161
  schema: {
157
162
  response: {
158
- 200: Type.Object({
159
- message: Type.String(),
160
- user: Type.Unknown(),
163
+ 200: z.object({
164
+ message: z.string(),
165
+ user: z.unknown(),
161
166
  }),
162
167
  },
163
168
  },
@@ -186,12 +191,12 @@ app.route({
186
191
  },
187
192
  schema: {
188
193
  response: {
189
- 200: Type.Object({
190
- message: Type.String(),
194
+ 200: z.object({
195
+ message: z.string(),
191
196
  }),
192
- 403: Type.Object({
193
- error: Type.String(),
194
- message: Type.String(),
197
+ 403: z.object({
198
+ error: z.string(),
199
+ message: z.string(),
195
200
  }),
196
201
  },
197
202
  },
@@ -216,12 +221,12 @@ app.route({
216
221
  },
217
222
  },
218
223
  schema: {
219
- params: Type.Object({
220
- id: Type.String(),
224
+ params: z.object({
225
+ id: z.string(),
221
226
  }),
222
227
  response: {
223
- 200: Type.Object({
224
- message: Type.String(),
228
+ 200: z.object({
229
+ message: z.string(),
225
230
  }),
226
231
  },
227
232
  },
@@ -259,61 +264,71 @@ app.route({
259
264
  })
260
265
  ```
261
266
 
262
- ### 6. Rate Limiting
263
-
264
- #### Route-Specific Rate Limit
267
+ ### 6. Health Check Plugin
265
268
 
266
269
  ```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
- })
270
+ import { createFastify, runFastify, healthPlugin } from '@a_jackie_z/fastify'
271
+
272
+ const app = await createFastify()
273
+
274
+ // Register health check plugin
275
+ await app.register(healthPlugin)
276
+
277
+ // Health endpoint available at: GET /v1/health
278
+ // Response: { status: 200, message: 'ok' }
279
+
280
+ await runFastify(app, '0.0.0.0', 3000)
280
281
  ```
281
282
 
282
- #### Disable Rate Limit for Specific Route
283
+ ### 7. Creating Custom Plugins
283
284
 
284
285
  ```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
- },
286
+ import { createFastify, runFastify, createFastifyPlugin } from '@a_jackie_z/fastify'
287
+ import { z } from 'zod'
288
+
289
+ // Create a reusable plugin
290
+ const myPlugin = createFastifyPlugin((app) => {
291
+ app.get('/plugin-route', {
292
+ schema: {
293
+ response: {
294
+ 200: z.object({
295
+ message: z.string(),
296
+ }),
297
+ },
298
+ },
299
+ handler: async () => {
300
+ return { message: 'From plugin' }
301
+ },
302
+ })
294
303
  })
304
+
305
+ // Use the plugin
306
+ const app = await createFastify()
307
+ await app.register(myPlugin)
308
+
309
+ await runFastify(app, '0.0.0.0', 3000)
295
310
  ```
296
311
 
297
- ### 7. TypeBox Schema Validation
312
+ ### 8. Zod Schema Validation
298
313
 
299
314
  ```typescript
300
315
  app.route({
301
316
  method: 'POST',
302
317
  url: '/users',
303
318
  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 })),
319
+ body: z.object({
320
+ name: z.string().min(1).max(100),
321
+ email: z.string().email(),
322
+ age: z.number().min(0).max(150).optional(),
308
323
  }),
309
324
  response: {
310
- 201: Type.Object({
311
- id: Type.String(),
312
- name: Type.String(),
313
- email: Type.String(),
325
+ 201: z.object({
326
+ id: z.string(),
327
+ name: z.string(),
328
+ email: z.string(),
314
329
  }),
315
- 400: Type.Object({
316
- error: Type.String(),
330
+ 400: z.object({
331
+ error: z.string(),
317
332
  }),
318
333
  },
319
334
  },
@@ -334,6 +349,7 @@ app.route({
334
349
 
335
350
  ```typescript
336
351
  interface CreateFastifyOptions {
352
+ logger?: FastifyServerOptions['loggerInstance'],
337
353
  rateLimit?: {
338
354
  global?: RateLimitPluginOptions
339
355
  }
@@ -347,7 +363,7 @@ interface CreateFastifyOptions {
347
363
  title: string
348
364
  version: string
349
365
  description: string
350
- routePrefix?: string // Default: '/documentation'
366
+ routePrefix?: string // Default: '/docs/'
351
367
  }
352
368
  }
353
369
  ```
@@ -371,6 +387,27 @@ type JWTAuthorizationHandler = (
371
387
  ) => Promise<boolean> | boolean
372
388
  ```
373
389
 
390
+ ## API Reference
391
+
392
+ ### `createFastify(options?: CreateFastifyOptions): Promise<FastifyServer>`
393
+
394
+ Creates and configures a Fastify server instance with Zod support and optional plugins.
395
+
396
+ ### `runFastify(fastify: FastifyServer, host: string, port: number): Promise<void>`
397
+
398
+ Starts the Fastify server. Handles errors and exits the process if the server fails to start.
399
+
400
+ **Parameters:**
401
+ - `fastify` - The Fastify server instance
402
+ - `host` - The host to bind to (e.g., '0.0.0.0' or 'localhost')
403
+ - `port` - The port number to listen on
404
+
405
+ **Example:**
406
+ ```typescript
407
+ const app = await createFastify()
408
+ await runFastify(app, '0.0.0.0', 3000)
409
+ ```
410
+
374
411
  ## Usage Patterns
375
412
 
376
413
  ### Pattern 1: Global JWT with Selective Public Routes
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as fastify from 'fastify';
2
2
  import { FastifyRequest, FastifyReply, FastifyServerOptions, FastifyInstance, RawServerDefault, FastifyBaseLogger, FastifyContextConfig, FastifyPluginCallback } from 'fastify';
3
- import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
3
+ import { ZodTypeProvider } from 'fastify-type-provider-zod';
4
4
  import { RateLimitPluginOptions } from '@fastify/rate-limit';
5
5
  import { FastifyJWTOptions } from '@fastify/jwt';
6
6
  import { IncomingMessage, ServerResponse } from 'node:http';
@@ -38,7 +38,7 @@ interface CreateFastifyOptions {
38
38
  routePrefix?: string;
39
39
  };
40
40
  }
41
- type FastifyServer = FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse, FastifyBaseLogger, TypeBoxTypeProvider> & FastifyContextConfig;
41
+ type FastifyServer = FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse, FastifyBaseLogger, ZodTypeProvider> & FastifyContextConfig;
42
42
  declare function createFastify(options?: CreateFastifyOptions): Promise<FastifyServer>;
43
43
  declare function runFastify(fastify: FastifyServer, host: string, port: number): Promise<void>;
44
44
 
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  // lib/fastify.ts
2
2
  import Fastify from "fastify";
3
+ import {
4
+ serializerCompiler,
5
+ validatorCompiler
6
+ } from "fastify-type-provider-zod";
3
7
  import fastifyRateLimit from "@fastify/rate-limit";
4
8
  import fastifyJwt from "@fastify/jwt";
5
9
  import fastifySwagger from "@fastify/swagger";
@@ -10,6 +14,8 @@ async function createFastify(options) {
10
14
  fastifyOptions.loggerInstance = options.logger;
11
15
  }
12
16
  const fastify = Fastify(fastifyOptions).withTypeProvider();
17
+ fastify.setValidatorCompiler(validatorCompiler);
18
+ fastify.setSerializerCompiler(serializerCompiler);
13
19
  if (options?.swagger) {
14
20
  await fastify.register(fastifySwagger, {
15
21
  openapi: {
package/dist/index.js.map CHANGED
@@ -1 +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":[]}
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 {\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 // 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 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a_jackie_z/fastify",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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>",
@@ -24,10 +24,9 @@
24
24
  "@fastify/rate-limit": "^10.3.0",
25
25
  "@fastify/swagger": "^9.6.1",
26
26
  "@fastify/swagger-ui": "^5.2.4",
27
- "@fastify/type-provider-typebox": "^6.1.0",
28
- "@sinclair/typebox": "^0.34.47",
29
27
  "fastify": "^5.7.1",
30
- "typebox": "^1.0.79"
28
+ "fastify-type-provider-zod": "^6.1.0",
29
+ "zod": "^4.3.5"
31
30
  },
32
31
  "devDependencies": {
33
32
  "@types/node": "^25.0.9",