@efebia/fastify-zod-reply 1.0.2 → 1.0.3

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.
@@ -31,3 +31,5 @@ declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPlug
31
31
  export default _default;
32
32
  export * from './error.js';
33
33
  export * from './route.js';
34
+ export * from './routeV4.js';
35
+ export * from './types.js';
package/lib/cjs/index.js CHANGED
@@ -46,3 +46,5 @@ exports.default = (0, fastify_plugin_1.default)(async (fastify, opts) => {
46
46
  });
47
47
  __exportStar(require("./error.js"), exports);
48
48
  __exportStar(require("./route.js"), exports);
49
+ __exportStar(require("./routeV4.js"), exports);
50
+ __exportStar(require("./types.js"), exports);
@@ -1,11 +1,5 @@
1
- import { type RawReplyDefaultExpression, type RawRequestDefaultExpression, type RawServerDefault, type RouteGenericInterface, type RouteHandlerMethod, type RouteShorthandOptions } from 'fastify';
2
1
  import { z } from 'zod';
3
- export type APIOptions<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteShorthandOptions<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
4
- export type APIHandler<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
5
- export interface RouteTag {
6
- }
7
- export interface RouteSecurity {
8
- }
2
+ import { APIHandler, APIOptions, RouteSecurity, RouteTag } from './types.js';
9
3
  export type BaseZodSchema = {
10
4
  Body?: z.ZodTypeAny;
11
5
  Params?: z.ZodTypeAny;
package/lib/cjs/route.js CHANGED
@@ -20,7 +20,7 @@ const findStatusCode = (statusCode, availableStatusCodes) => {
20
20
  });
21
21
  };
22
22
  const route = (schema, handler) => {
23
- const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Body) })), (schema.Params && { params: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Params) })), (schema.Query && { querystring: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Query) })), (schema.Headers && { headers: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Headers) })), { response: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Reply.partial(), {
23
+ const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Body, { $refStrategy: 'none' }) })), (schema.Params && { params: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Params, { $refStrategy: 'none' }) })), (schema.Query && { querystring: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Query, { $refStrategy: 'none' }) })), (schema.Headers && { headers: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Headers, { $refStrategy: 'none' }) })), { response: (0, zod_to_json_schema_1.zodToJsonSchema)(schema.Reply.partial(), {
24
24
  $refStrategy: 'none',
25
25
  strictUnions: true,
26
26
  })['properties'] }), (schema.Security && { security: schema.Security })), (schema.Tags && { tags: schema.Tags }));
@@ -58,9 +58,7 @@ const route = (schema, handler) => {
58
58
  if (serialized.success) {
59
59
  return done(null, serialized.data);
60
60
  }
61
- return done(null, {
62
- message: mapZodError(serialized.error, 'reply'),
63
- });
61
+ return done(new Error(mapZodError(serialized.error, 'reply')));
64
62
  },
65
63
  };
66
64
  };
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod/v4';
2
+ import { APIHandler, APIOptions, RouteSecurity, RouteTag } from './types.js';
3
+ export type BaseZodV4Schema = {
4
+ Body?: z.ZodTypeAny;
5
+ Params?: z.ZodTypeAny;
6
+ Query?: z.ZodTypeAny;
7
+ Headers?: z.ZodTypeAny;
8
+ Reply: z.ZodObject;
9
+ Security?: (RouteSecurity[keyof RouteSecurity])[];
10
+ Tags?: (keyof RouteTag)[];
11
+ };
12
+ export type FastifyZodV4Schema<TZodSchema extends BaseZodV4Schema> = {
13
+ Body: TZodSchema['Body'] extends z.ZodTypeAny ? z.output<TZodSchema['Body']> : undefined;
14
+ Params: TZodSchema['Params'] extends z.ZodTypeAny ? z.output<TZodSchema['Params']> : undefined;
15
+ Querystring: TZodSchema['Query'] extends z.ZodTypeAny ? z.output<TZodSchema['Query']> : undefined;
16
+ Reply: TZodSchema['Reply'] extends z.ZodTypeAny ? z.input<TZodSchema['Reply']>[keyof z.input<TZodSchema['Reply']>] : undefined;
17
+ };
18
+ export declare const routeV4: <TSchema extends BaseZodV4Schema, FastifySchema extends FastifyZodV4Schema<TSchema> = FastifyZodV4Schema<TSchema>>(schema: TSchema, handler: APIHandler<FastifySchema>) => APIOptions<FastifySchema> & {
19
+ handler: APIHandler<FastifySchema>;
20
+ };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.routeV4 = void 0;
4
+ const v4_1 = require("zod/v4");
5
+ const mapZodError = (zodError, prefix) => zodError.issues.map(issue => `Error at ${prefix}->${issue.path.join('->')}`).join(';\n');
6
+ const parse = async (schema, payload, tag) => {
7
+ const result = await schema.safeParseAsync(payload);
8
+ return Object.assign(Object.assign({}, result), { tag });
9
+ };
10
+ const findStatusCode = (statusCode, availableStatusCodes) => {
11
+ return availableStatusCodes.find(([key]) => {
12
+ if (!['number', 'string'].includes(typeof key))
13
+ return false;
14
+ if (typeof key === 'number')
15
+ return statusCode === key;
16
+ if (/^[0-9]{3}$/.test(key))
17
+ return statusCode === parseInt(key);
18
+ if (/^[0-9]xx$/i.test(key))
19
+ return statusCode.toString()[0] === key[0];
20
+ });
21
+ };
22
+ const routeV4 = (schema, handler) => {
23
+ const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: v4_1.z.toJSONSchema(schema.Body, { reused: 'inline' }) })), (schema.Params && { params: v4_1.z.toJSONSchema(schema.Params, { reused: 'inline' }) })), (schema.Query && { querystring: v4_1.z.toJSONSchema(schema.Query, { reused: 'inline' }) })), (schema.Headers && { headers: v4_1.z.toJSONSchema(schema.Headers, { reused: 'inline' }) })), { response: v4_1.z.toJSONSchema(schema.Reply.partial(), { reused: 'inline' })['properties'] }), (schema.Security && { security: schema.Security })), (schema.Tags && { tags: schema.Tags }));
24
+ return {
25
+ schema: finalResult,
26
+ handler,
27
+ preHandler: async (request, reply) => {
28
+ var _a, _b, _c;
29
+ const results = await Promise.all([
30
+ ...(schema.Body ? [parse(schema.Body, request.body, 'body')] : []),
31
+ ...(schema.Params ? [parse(schema.Params, request.params, 'params')] : []),
32
+ ...(schema.Query ? [parse(schema.Query, request.query, 'query')] : []),
33
+ ]);
34
+ for (const result of results) {
35
+ if (!result.success) {
36
+ return reply
37
+ .code(400)
38
+ .type('application/json')
39
+ .send({
40
+ message: mapZodError(result.error, result.tag),
41
+ });
42
+ }
43
+ }
44
+ request.body = ((_a = results.find(r => r.tag === 'body')) === null || _a === void 0 ? void 0 : _a.data) || {};
45
+ request.params = ((_b = results.find(r => r.tag === 'params')) === null || _b === void 0 ? void 0 : _b.data) || {};
46
+ request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
47
+ },
48
+ preSerialization: (request, reply, payload, done) => {
49
+ const foundSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
50
+ if (!foundSchema) {
51
+ request.log.warn(`[@efebia/fastify-zod-reply]: Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode}`);
52
+ return done(null, payload);
53
+ }
54
+ const serialized = foundSchema[1].safeParse(payload);
55
+ if (serialized.success) {
56
+ return done(null, serialized.data);
57
+ }
58
+ return done(new Error(mapZodError(serialized.error, 'reply')));
59
+ },
60
+ };
61
+ };
62
+ exports.routeV4 = routeV4;
@@ -0,0 +1,7 @@
1
+ import { type RawReplyDefaultExpression, type RawRequestDefaultExpression, type RawServerDefault, type RouteGenericInterface, type RouteHandlerMethod, type RouteShorthandOptions } from 'fastify';
2
+ export type APIOptions<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteShorthandOptions<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
3
+ export type APIHandler<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
4
+ export interface RouteTag {
5
+ }
6
+ export interface RouteSecurity {
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -31,3 +31,5 @@ declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPlug
31
31
  export default _default;
32
32
  export * from './error.js';
33
33
  export * from './route.js';
34
+ export * from './routeV4.js';
35
+ export * from './types.js';
package/lib/esm/index.js CHANGED
@@ -27,3 +27,5 @@ export default fp(async (fastify, opts) => {
27
27
  });
28
28
  export * from './error.js';
29
29
  export * from './route.js';
30
+ export * from './routeV4.js';
31
+ export * from './types.js';
@@ -1,11 +1,5 @@
1
- import { type RawReplyDefaultExpression, type RawRequestDefaultExpression, type RawServerDefault, type RouteGenericInterface, type RouteHandlerMethod, type RouteShorthandOptions } from 'fastify';
2
1
  import { z } from 'zod';
3
- export type APIOptions<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteShorthandOptions<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
4
- export type APIHandler<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
5
- export interface RouteTag {
6
- }
7
- export interface RouteSecurity {
8
- }
2
+ import { APIHandler, APIOptions, RouteSecurity, RouteTag } from './types.js';
9
3
  export type BaseZodSchema = {
10
4
  Body?: z.ZodTypeAny;
11
5
  Params?: z.ZodTypeAny;
package/lib/esm/route.js CHANGED
@@ -17,7 +17,7 @@ const findStatusCode = (statusCode, availableStatusCodes) => {
17
17
  });
18
18
  };
19
19
  export const route = (schema, handler) => {
20
- const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: zodToJsonSchema(schema.Body) })), (schema.Params && { params: zodToJsonSchema(schema.Params) })), (schema.Query && { querystring: zodToJsonSchema(schema.Query) })), (schema.Headers && { headers: zodToJsonSchema(schema.Headers) })), { response: zodToJsonSchema(schema.Reply.partial(), {
20
+ const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: zodToJsonSchema(schema.Body, { $refStrategy: 'none' }) })), (schema.Params && { params: zodToJsonSchema(schema.Params, { $refStrategy: 'none' }) })), (schema.Query && { querystring: zodToJsonSchema(schema.Query, { $refStrategy: 'none' }) })), (schema.Headers && { headers: zodToJsonSchema(schema.Headers, { $refStrategy: 'none' }) })), { response: zodToJsonSchema(schema.Reply.partial(), {
21
21
  $refStrategy: 'none',
22
22
  strictUnions: true,
23
23
  })['properties'] }), (schema.Security && { security: schema.Security })), (schema.Tags && { tags: schema.Tags }));
@@ -55,9 +55,7 @@ export const route = (schema, handler) => {
55
55
  if (serialized.success) {
56
56
  return done(null, serialized.data);
57
57
  }
58
- return done(null, {
59
- message: mapZodError(serialized.error, 'reply'),
60
- });
58
+ return done(new Error(mapZodError(serialized.error, 'reply')));
61
59
  },
62
60
  };
63
61
  };
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod/v4';
2
+ import { APIHandler, APIOptions, RouteSecurity, RouteTag } from './types.js';
3
+ export type BaseZodV4Schema = {
4
+ Body?: z.ZodTypeAny;
5
+ Params?: z.ZodTypeAny;
6
+ Query?: z.ZodTypeAny;
7
+ Headers?: z.ZodTypeAny;
8
+ Reply: z.ZodObject;
9
+ Security?: (RouteSecurity[keyof RouteSecurity])[];
10
+ Tags?: (keyof RouteTag)[];
11
+ };
12
+ export type FastifyZodV4Schema<TZodSchema extends BaseZodV4Schema> = {
13
+ Body: TZodSchema['Body'] extends z.ZodTypeAny ? z.output<TZodSchema['Body']> : undefined;
14
+ Params: TZodSchema['Params'] extends z.ZodTypeAny ? z.output<TZodSchema['Params']> : undefined;
15
+ Querystring: TZodSchema['Query'] extends z.ZodTypeAny ? z.output<TZodSchema['Query']> : undefined;
16
+ Reply: TZodSchema['Reply'] extends z.ZodTypeAny ? z.input<TZodSchema['Reply']>[keyof z.input<TZodSchema['Reply']>] : undefined;
17
+ };
18
+ export declare const routeV4: <TSchema extends BaseZodV4Schema, FastifySchema extends FastifyZodV4Schema<TSchema> = FastifyZodV4Schema<TSchema>>(schema: TSchema, handler: APIHandler<FastifySchema>) => APIOptions<FastifySchema> & {
19
+ handler: APIHandler<FastifySchema>;
20
+ };
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod/v4';
2
+ const mapZodError = (zodError, prefix) => zodError.issues.map(issue => `Error at ${prefix}->${issue.path.join('->')}`).join(';\n');
3
+ const parse = async (schema, payload, tag) => {
4
+ const result = await schema.safeParseAsync(payload);
5
+ return Object.assign(Object.assign({}, result), { tag });
6
+ };
7
+ const findStatusCode = (statusCode, availableStatusCodes) => {
8
+ return availableStatusCodes.find(([key]) => {
9
+ if (!['number', 'string'].includes(typeof key))
10
+ return false;
11
+ if (typeof key === 'number')
12
+ return statusCode === key;
13
+ if (/^[0-9]{3}$/.test(key))
14
+ return statusCode === parseInt(key);
15
+ if (/^[0-9]xx$/i.test(key))
16
+ return statusCode.toString()[0] === key[0];
17
+ });
18
+ };
19
+ export const routeV4 = (schema, handler) => {
20
+ const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: z.toJSONSchema(schema.Body, { reused: 'inline' }) })), (schema.Params && { params: z.toJSONSchema(schema.Params, { reused: 'inline' }) })), (schema.Query && { querystring: z.toJSONSchema(schema.Query, { reused: 'inline' }) })), (schema.Headers && { headers: z.toJSONSchema(schema.Headers, { reused: 'inline' }) })), { response: z.toJSONSchema(schema.Reply.partial(), { reused: 'inline' })['properties'] }), (schema.Security && { security: schema.Security })), (schema.Tags && { tags: schema.Tags }));
21
+ return {
22
+ schema: finalResult,
23
+ handler,
24
+ preHandler: async (request, reply) => {
25
+ var _a, _b, _c;
26
+ const results = await Promise.all([
27
+ ...(schema.Body ? [parse(schema.Body, request.body, 'body')] : []),
28
+ ...(schema.Params ? [parse(schema.Params, request.params, 'params')] : []),
29
+ ...(schema.Query ? [parse(schema.Query, request.query, 'query')] : []),
30
+ ]);
31
+ for (const result of results) {
32
+ if (!result.success) {
33
+ return reply
34
+ .code(400)
35
+ .type('application/json')
36
+ .send({
37
+ message: mapZodError(result.error, result.tag),
38
+ });
39
+ }
40
+ }
41
+ request.body = ((_a = results.find(r => r.tag === 'body')) === null || _a === void 0 ? void 0 : _a.data) || {};
42
+ request.params = ((_b = results.find(r => r.tag === 'params')) === null || _b === void 0 ? void 0 : _b.data) || {};
43
+ request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
44
+ },
45
+ preSerialization: (request, reply, payload, done) => {
46
+ const foundSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
47
+ if (!foundSchema) {
48
+ request.log.warn(`[@efebia/fastify-zod-reply]: Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode}`);
49
+ return done(null, payload);
50
+ }
51
+ const serialized = foundSchema[1].safeParse(payload);
52
+ if (serialized.success) {
53
+ return done(null, serialized.data);
54
+ }
55
+ return done(new Error(mapZodError(serialized.error, 'reply')));
56
+ },
57
+ };
58
+ };
@@ -0,0 +1,7 @@
1
+ import { type RawReplyDefaultExpression, type RawRequestDefaultExpression, type RawServerDefault, type RouteGenericInterface, type RouteHandlerMethod, type RouteShorthandOptions } from 'fastify';
2
+ export type APIOptions<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteShorthandOptions<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
3
+ export type APIHandler<RouteInterface extends RouteGenericInterface = RouteGenericInterface> = RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, RouteInterface>;
4
+ export interface RouteTag {
5
+ }
6
+ export interface RouteSecurity {
7
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@efebia/fastify-zod-reply",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "license": "MIT",
5
5
  "dependencies": {
6
6
  "fastify": "^5.3.0",
7
7
  "fastify-plugin": "^5.0.1",
8
- "zod": "^3.24.2",
8
+ "zod": "^3.25.47",
9
9
  "zod-to-json-schema": "^3.24.5"
10
10
  },
11
11
  "files": [