express-zod-safe 2.0.0 → 3.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
@@ -48,6 +48,7 @@ import validate from 'express-zod-safe';
48
48
  import { z } from 'zod';
49
49
 
50
50
  const app = express();
51
+ app.use(express.json());
51
52
 
52
53
  // Define your Zod schemas
53
54
  const params = {
@@ -72,6 +73,30 @@ app.listen(3000, () => console.log('Server running on port 3000'));
72
73
 
73
74
  **Note:** The `validate` middleware must be used **after** any other middleware that parses/modifies the request body, such as `express.json()` or `express.urlencoded()`.
74
75
 
76
+ ### 📄 Using `ValidatedRequest`
77
+ When you want to type the `Request` object outside of route handlers, you can use the `ValidatedRequest` utility to infer the validated types. Pass the same Zod schemas you provide to `validate` and the resulting `Request` type will be narrowed accordingly.
78
+
79
+ ```ts
80
+ import type { Response } from 'express';
81
+ import validate, { type ValidatedRequest } from 'express-zod-safe';
82
+ import { z } from 'zod';
83
+
84
+ const bodySchema = z.object({
85
+ title: z.string(),
86
+ description: z.string().max(200)
87
+ });
88
+
89
+ type CreatePostRequest = ValidatedRequest<{ body: typeof bodySchema }>;
90
+
91
+ function createPostHandler(req: CreatePostRequest, res: Response) {
92
+ // req.body.title -> string
93
+ // req.body.description -> string (max length validated by Zod)
94
+ res.json({ message: 'Created!' });
95
+ }
96
+
97
+ app.post('/posts', validate({ body: bodySchema }), createPostHandler);
98
+ ```
99
+
75
100
  ### 📦 Custom Error Handling
76
101
  By default, the `validate` middleware will send a 400 Bad Request response with a JSON body containing the error message. However, you can provide your own error handling function to customise the error response.
77
102
 
@@ -92,6 +117,17 @@ app.post('/user/:userId', validate({ handler, params, query, body }), (req, res)
92
117
  });
93
118
  ```
94
119
 
120
+ If you plan to use the same error handler across every route, use the `setGlobalErrorHandler`:
121
+
122
+
123
+ ```ts
124
+ import { setGlobalErrorHandler } from 'express-zod-safe';
125
+
126
+ setGlobalErrorHandler((errors, req, res) => {
127
+ // Your error handling here
128
+ })
129
+ ```
130
+
95
131
  ### ⚠️ Usage with Additional Middleware
96
132
  When using `express-zod-safe` with other middleware, it is important not to explicitly type the `Request` parameter in the middleware, as this will override the inferred type that `express-zod-safe` generates from your validation schemas. The best way to do this is to instead type your other middleware (or cast them) to `WeakRequestHandler`, a weakly typed version of the `RequestHandler` type from `express`.
97
133
 
@@ -151,8 +187,8 @@ const body = {
151
187
  app.post('/user', validate({ body }), (req, res) => {
152
188
  // req.body.name -> string
153
189
  // req.body.email -> string
154
- // req.params.age -> Property 'age' does not exist on type '{}'
155
- // req.query.age -> Property 'age' does not exist on type '{}'
190
+ // req.params.age -> Property 'age' does not exist on type unknown
191
+ // req.query.age -> Property 'age' does not exist on type unknown
156
192
  });
157
193
  ```
158
194
 
@@ -170,7 +206,7 @@ app.post('/user', validate({ body, params }), (req, res) => {
170
206
  // req.body.name -> string
171
207
  // req.body.email -> string
172
208
  // req.params.age -> any
173
- // req.query.age -> Property 'age' does not exist on type '{}'
209
+ // req.query.age -> Property 'age' does not exist on type unknown
174
210
  });
175
211
  ```
176
212
 
package/dist/index.d.mts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { Request, Response, NextFunction, RequestHandler } from 'express';
2
- import { ZodType, ZodRawShape, z, ZodError } from 'zod';
2
+ import { ZodType, ZodRawShape, ZodError, z } from 'zod';
3
3
 
4
4
  declare const types: readonly ["query", "params", "body"];
5
- declare const emptyObjectSchema: z.ZodObject<{}, z.core.$strict>;
6
- type EmptyValidationSchema = typeof emptyObjectSchema;
5
+ declare const defaultErrorHandler: ErrorRequestHandler;
7
6
  /**
8
7
  * Generates a middleware function for Express.js that validates request params, query, and body.
9
8
  * This function uses Zod schemas to perform validation against the provided schema definitions.
@@ -21,6 +20,7 @@ type EmptyValidationSchema = typeof emptyObjectSchema;
21
20
  * import { z } from 'zod';
22
21
  *
23
22
  * const app = express();
23
+ * app.use(express.json());
24
24
  *
25
25
  * // Define your Zod schemas
26
26
  * const params = {
@@ -42,7 +42,8 @@ type EmptyValidationSchema = typeof emptyObjectSchema;
42
42
  *
43
43
  * app.listen(3000, () => console.log('Server running on port 3000'));
44
44
  */
45
- declare function validate<TParams extends ValidationSchema = EmptyValidationSchema, TQuery extends ValidationSchema = EmptyValidationSchema, TBody extends ValidationSchema = EmptyValidationSchema>(schemas: CompleteValidationSchema<TParams, TQuery, TBody>): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>>;
45
+ declare function validate<TParams extends ValidationSchema, TQuery extends ValidationSchema, TBody extends ValidationSchema>(schemas: CompleteValidationSchema<TParams, TQuery, TBody>): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>>;
46
+ declare function setGlobalErrorHandler(handler: ErrorRequestHandler): void;
46
47
  /**
47
48
  * Describes the types of data that can be validated: 'query', 'params', or 'body'.
48
49
  */
@@ -55,10 +56,11 @@ interface ErrorListItem {
55
56
  type: DataType;
56
57
  errors: ZodError;
57
58
  }
59
+ type Unvalidated = unknown;
58
60
  /**
59
61
  * Represents an Express.js error request handler where the params, query and body are of unknown type as validation failed.
60
62
  */
61
- type ErrorRequestHandler<P = unknown, ResBody = any, ReqBody = unknown, ReqQuery = unknown, LocalsObj extends Record<string, any> = Record<string, any>> = (err: ErrorListItem[], req: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>, res: Response<ResBody, LocalsObj>, next: NextFunction) => void | Promise<void>;
63
+ type ErrorRequestHandler<P = Unvalidated, ResBody = any, ReqBody = Unvalidated, ReqQuery = unknown, LocalsObj extends Record<string, any> = Record<string, any>> = (err: ErrorListItem[], req: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>, res: Response<ResBody, LocalsObj>, next: NextFunction) => void | Promise<void>;
62
64
  /**
63
65
  * Represents a generic type for route validation, which can be applied to params, query, or body.
64
66
  * Each key-value pair represents a field and its corresponding Zod validation schema.
@@ -73,7 +75,7 @@ type ValidationSchema = ZodType | ZodRawShape;
73
75
  * @template TQuery - Type definition for query schema.
74
76
  * @template TBody - Type definition for body schema.
75
77
  */
76
- interface CompleteValidationSchema<TParams extends ValidationSchema = EmptyValidationSchema, TQuery extends ValidationSchema = EmptyValidationSchema, TBody extends ValidationSchema = EmptyValidationSchema> {
78
+ interface CompleteValidationSchema<TParams extends ValidationSchema = ValidationSchema, TQuery extends ValidationSchema = ValidationSchema, TBody extends ValidationSchema = ValidationSchema> {
77
79
  handler?: ErrorRequestHandler;
78
80
  params?: TParams;
79
81
  query?: TQuery;
@@ -86,10 +88,41 @@ interface CompleteValidationSchema<TParams extends ValidationSchema = EmptyValid
86
88
  *
87
89
  * @template T - The validation type (params, query, or body).
88
90
  */
89
- type ZodOutput<T extends ValidationSchema> = z.output<T extends ZodRawShape ? z.ZodObject<T> : T>;
91
+ type ZodOutput<T extends ValidationSchema | undefined> = T extends ValidationSchema ? z.output<T extends ZodRawShape ? z.ZodObject<T> : T> : Unvalidated;
90
92
  /**
91
93
  * A utility type to ensure other middleware types don't conflict with the validate middleware.
92
94
  */
93
- type WeakRequestHandler = RequestHandler<unknown, unknown, unknown, Record<string, unknown>>;
95
+ type WeakRequestHandler = RequestHandler<Unvalidated, Unvalidated, Unvalidated, Unvalidated>;
96
+ /**
97
+ * A utility type to ensure the Request is typed correctly.
98
+ * @template T - The validation schema to be applied to the request params, query and body.
99
+ * @example
100
+ * import { ValidatedRequest } from 'express-zod-safe';
101
+ * import { z } from 'zod';
102
+ *
103
+ * const schema = {
104
+ * query: {
105
+ * name: z.string().min(3).max(10),
106
+ * age: z.coerce.number().min(18)
107
+ * },
108
+ * body: {
109
+ * title: z.string().max(4)
110
+ * },
111
+ * params: {
112
+ * id: z.coerce.number()
113
+ * }
114
+ * };
115
+ *
116
+ * const requestHandler = (req: ValidatedRequest<typeof schema>, res: Response) => {
117
+ * const { name, age } = req.query;
118
+ * const { id } = req.params;
119
+ * const { title } = req.body;
120
+ *
121
+ * res.send(`Hello ${title} ${name}! (Your age is ${age} and your ID is ${id})`);
122
+ * };
123
+ *
124
+ * app.post('/handler/:id', validate(schema), requestHandler);
125
+ */
126
+ type ValidatedRequest<T extends CompleteValidationSchema> = Request<ZodOutput<T['params']>, any, ZodOutput<T['body']>, ZodOutput<T['query']>>;
94
127
 
95
- export { type CompleteValidationSchema, type EmptyValidationSchema, type ErrorListItem, type ErrorRequestHandler, type ValidationSchema, type WeakRequestHandler, type ZodOutput, validate as default };
128
+ export { type CompleteValidationSchema, type ErrorListItem, type ErrorRequestHandler, type Unvalidated, type ValidatedRequest, type ValidationSchema, type WeakRequestHandler, type ZodOutput, validate as default, defaultErrorHandler, setGlobalErrorHandler };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { Request, Response, NextFunction, RequestHandler } from 'express';
2
- import { ZodType, ZodRawShape, z, ZodError } from 'zod';
2
+ import { ZodType, ZodRawShape, ZodError, z } from 'zod';
3
3
 
4
4
  declare const types: readonly ["query", "params", "body"];
5
- declare const emptyObjectSchema: z.ZodObject<{}, z.core.$strict>;
6
- type EmptyValidationSchema = typeof emptyObjectSchema;
5
+ declare const defaultErrorHandler: ErrorRequestHandler;
7
6
  /**
8
7
  * Generates a middleware function for Express.js that validates request params, query, and body.
9
8
  * This function uses Zod schemas to perform validation against the provided schema definitions.
@@ -21,6 +20,7 @@ type EmptyValidationSchema = typeof emptyObjectSchema;
21
20
  * import { z } from 'zod';
22
21
  *
23
22
  * const app = express();
23
+ * app.use(express.json());
24
24
  *
25
25
  * // Define your Zod schemas
26
26
  * const params = {
@@ -42,7 +42,8 @@ type EmptyValidationSchema = typeof emptyObjectSchema;
42
42
  *
43
43
  * app.listen(3000, () => console.log('Server running on port 3000'));
44
44
  */
45
- declare function validate<TParams extends ValidationSchema = EmptyValidationSchema, TQuery extends ValidationSchema = EmptyValidationSchema, TBody extends ValidationSchema = EmptyValidationSchema>(schemas: CompleteValidationSchema<TParams, TQuery, TBody>): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>>;
45
+ declare function validate<TParams extends ValidationSchema, TQuery extends ValidationSchema, TBody extends ValidationSchema>(schemas: CompleteValidationSchema<TParams, TQuery, TBody>): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>>;
46
+ declare function setGlobalErrorHandler(handler: ErrorRequestHandler): void;
46
47
  /**
47
48
  * Describes the types of data that can be validated: 'query', 'params', or 'body'.
48
49
  */
@@ -55,10 +56,11 @@ interface ErrorListItem {
55
56
  type: DataType;
56
57
  errors: ZodError;
57
58
  }
59
+ type Unvalidated = unknown;
58
60
  /**
59
61
  * Represents an Express.js error request handler where the params, query and body are of unknown type as validation failed.
60
62
  */
61
- type ErrorRequestHandler<P = unknown, ResBody = any, ReqBody = unknown, ReqQuery = unknown, LocalsObj extends Record<string, any> = Record<string, any>> = (err: ErrorListItem[], req: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>, res: Response<ResBody, LocalsObj>, next: NextFunction) => void | Promise<void>;
63
+ type ErrorRequestHandler<P = Unvalidated, ResBody = any, ReqBody = Unvalidated, ReqQuery = unknown, LocalsObj extends Record<string, any> = Record<string, any>> = (err: ErrorListItem[], req: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>, res: Response<ResBody, LocalsObj>, next: NextFunction) => void | Promise<void>;
62
64
  /**
63
65
  * Represents a generic type for route validation, which can be applied to params, query, or body.
64
66
  * Each key-value pair represents a field and its corresponding Zod validation schema.
@@ -73,7 +75,7 @@ type ValidationSchema = ZodType | ZodRawShape;
73
75
  * @template TQuery - Type definition for query schema.
74
76
  * @template TBody - Type definition for body schema.
75
77
  */
76
- interface CompleteValidationSchema<TParams extends ValidationSchema = EmptyValidationSchema, TQuery extends ValidationSchema = EmptyValidationSchema, TBody extends ValidationSchema = EmptyValidationSchema> {
78
+ interface CompleteValidationSchema<TParams extends ValidationSchema = ValidationSchema, TQuery extends ValidationSchema = ValidationSchema, TBody extends ValidationSchema = ValidationSchema> {
77
79
  handler?: ErrorRequestHandler;
78
80
  params?: TParams;
79
81
  query?: TQuery;
@@ -86,12 +88,43 @@ interface CompleteValidationSchema<TParams extends ValidationSchema = EmptyValid
86
88
  *
87
89
  * @template T - The validation type (params, query, or body).
88
90
  */
89
- type ZodOutput<T extends ValidationSchema> = z.output<T extends ZodRawShape ? z.ZodObject<T> : T>;
91
+ type ZodOutput<T extends ValidationSchema | undefined> = T extends ValidationSchema ? z.output<T extends ZodRawShape ? z.ZodObject<T> : T> : Unvalidated;
90
92
  /**
91
93
  * A utility type to ensure other middleware types don't conflict with the validate middleware.
92
94
  */
93
- type WeakRequestHandler = RequestHandler<unknown, unknown, unknown, Record<string, unknown>>;
95
+ type WeakRequestHandler = RequestHandler<Unvalidated, Unvalidated, Unvalidated, Unvalidated>;
96
+ /**
97
+ * A utility type to ensure the Request is typed correctly.
98
+ * @template T - The validation schema to be applied to the request params, query and body.
99
+ * @example
100
+ * import { ValidatedRequest } from 'express-zod-safe';
101
+ * import { z } from 'zod';
102
+ *
103
+ * const schema = {
104
+ * query: {
105
+ * name: z.string().min(3).max(10),
106
+ * age: z.coerce.number().min(18)
107
+ * },
108
+ * body: {
109
+ * title: z.string().max(4)
110
+ * },
111
+ * params: {
112
+ * id: z.coerce.number()
113
+ * }
114
+ * };
115
+ *
116
+ * const requestHandler = (req: ValidatedRequest<typeof schema>, res: Response) => {
117
+ * const { name, age } = req.query;
118
+ * const { id } = req.params;
119
+ * const { title } = req.body;
120
+ *
121
+ * res.send(`Hello ${title} ${name}! (Your age is ${age} and your ID is ${id})`);
122
+ * };
123
+ *
124
+ * app.post('/handler/:id', validate(schema), requestHandler);
125
+ */
126
+ type ValidatedRequest<T extends CompleteValidationSchema> = Request<ZodOutput<T['params']>, any, ZodOutput<T['body']>, ZodOutput<T['query']>>;
94
127
 
95
128
  // @ts-ignore
96
129
  export = validate;
97
- export type { CompleteValidationSchema, EmptyValidationSchema, ErrorListItem, ErrorRequestHandler, ValidationSchema, WeakRequestHandler, ZodOutput };
130
+ export { type CompleteValidationSchema, type ErrorListItem, type ErrorRequestHandler, type Unvalidated, type ValidatedRequest, type ValidationSchema, type WeakRequestHandler, type ZodOutput, defaultErrorHandler, setGlobalErrorHandler };
package/dist/index.js CHANGED
@@ -2,7 +2,10 @@
2
2
  var _express = require('express'); var _express2 = _interopRequireDefault(_express);
3
3
  var _zod = require('zod');
4
4
  var types = ["query", "params", "body"];
5
- var emptyObjectSchema = _zod.z.object({}).strict();
5
+ var defaultErrorHandler = (errors, _req, res) => {
6
+ res.status(400).send(errors.map((error) => ({ type: error.type, errors: error.errors.issues })));
7
+ };
8
+ var globalErrorHandler = defaultErrorHandler;
6
9
  function isZodType(schema) {
7
10
  return !!schema && typeof schema.safeParseAsync === "function";
8
11
  }
@@ -34,16 +37,18 @@ function validate(schemas) {
34
37
  else errors.push({ type, errors: parsed.error });
35
38
  }
36
39
  if (errors.length > 0) {
37
- if (schemas.handler) return schemas.handler(errors, req, res, next);
38
- res.status(400).send(errors.map((error) => ({ type: error.type, errors: error.errors })));
39
- return;
40
+ const handler = _nullishCoalesce(schemas.handler, () => ( globalErrorHandler));
41
+ return handler(errors, req, res, next);
40
42
  }
41
43
  return next();
42
44
  };
43
45
  }
46
+ function setGlobalErrorHandler(handler) {
47
+ globalErrorHandler = handler;
48
+ }
49
+
44
50
 
45
51
 
46
- exports.default = validate;
47
52
 
48
- module.exports = exports.default;
53
+ exports.default = validate; exports.defaultErrorHandler = defaultErrorHandler; exports.setGlobalErrorHandler = setGlobalErrorHandler;
49
54
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/express-zod-safe/express-zod-safe/dist/index.js","../src/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/express-zod-safe/express-zod-safe/dist/index.js","sourcesContent":[null,"/// <reference types=\"./express.d.ts\" />\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport express from 'express';\nimport { type ZodError, type ZodRawShape, type ZodType, z } from 'zod';\n\nconst types = ['query', 'params', 'body'] as const;\nconst emptyObjectSchema = z.object({}).strict();\nexport type EmptyValidationSchema = typeof emptyObjectSchema;\n\n/**\n * A ZodType type guard.\n * @param schema The Zod schema to check.\n * @returns Whether the provided schema is a ZodType.\n */\nfunction isZodType(schema: unknown): schema is ZodType {\n\treturn !!schema && typeof (schema as ZodType).safeParseAsync === 'function';\n}\n\n// Override express@^5 request.query getter to provider setter\nconst descriptor = Object.getOwnPropertyDescriptor(express.request, 'query');\nif (descriptor) {\n\tObject.defineProperty(express.request, 'query', {\n\t\tget(this: Request) {\n\t\t\tif (Object.hasOwn(this, '_query')) return this._query;\n\t\t\treturn descriptor?.get?.call(this);\n\t\t},\n\t\tset(this: Request, query: unknown) {\n\t\t\tthis._query = query;\n\t\t},\n\t\tconfigurable: true,\n\t\tenumerable: true\n\t});\n}\n\n/**\n * Generates a middleware function for Express.js that validates request params, query, and body.\n * This function uses Zod schemas to perform validation against the provided schema definitions.\n *\n * @param schemas - An object containing Zod schemas for params, query, and body. Optional handler for custom error handling.\n * @returns An Express.js middleware function that validates the request based on the provided schemas.\n * It attaches validated data to the request object and sends error details if validation fails.\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n * @example\n * // Example usage in an Express.js route\n * import express from 'express';\n * import validate from 'express-zod-safe';\n * import { z } from 'zod';\n *\n * const app = express();\n *\n * // Define your Zod schemas\n * const params = {\n * userId: z.string().uuid(),\n * };\n * const query = {\n * age: z.coerce.number().optional(),\n * };\n * const body = {\n * name: z.string(),\n * email: z.string().email(),\n * };\n *\n * // Use the validate middleware in your route\n * app.post('/user/:userId', validate({ params, query, body }), (req, res) => {\n * // Your route logic here\n * res.send('User data is valid!');\n * });\n *\n * app.listen(3000, () => console.log('Server running on port 3000'));\n */\nexport default function validate<\n\tTParams extends ValidationSchema = EmptyValidationSchema,\n\tTQuery extends ValidationSchema = EmptyValidationSchema,\n\tTBody extends ValidationSchema = EmptyValidationSchema\n>(schemas: CompleteValidationSchema<TParams, TQuery, TBody>): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>> {\n\t// Create validation objects for each type\n\tconst validation = {\n\t\tparams: isZodType(schemas.params) ? schemas.params : z.strictObject(schemas.params ?? {}),\n\t\tquery: isZodType(schemas.query) ? schemas.query : z.strictObject(schemas.query ?? {}),\n\t\tbody: isZodType(schemas.body) ? schemas.body : z.strictObject(schemas.body ?? {})\n\t};\n\n\treturn async (req, res, next): Promise<void> => {\n\t\tconst errors: ErrorListItem[] = [];\n\n\t\t// Validate all types (params, query, body)\n\t\tfor (const type of types) {\n\t\t\tconst parsed = await validation[type].safeParseAsync(req[type] ?? {});\n\t\t\tif (parsed.success) req[type] = parsed.data as any;\n\t\t\telse errors.push({ type, errors: parsed.error });\n\t\t}\n\n\t\t// Return all errors if there are any\n\t\tif (errors.length > 0) {\n\t\t\t// If a custom error handler is provided, use it\n\t\t\tif (schemas.handler) return schemas.handler(errors, req, res, next);\n\n\t\t\tres.status(400).send(errors.map(error => ({ type: error.type, errors: error.errors })));\n\t\t\treturn;\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\n/**\n * Describes the types of data that can be validated: 'query', 'params', or 'body'.\n */\ntype DataType = (typeof types)[number];\n\n/**\n * Defines the structure of an error item, containing the type of validation that failed (params, query, or body)\n * and the associated ZodError.\n */\nexport interface ErrorListItem {\n\ttype: DataType;\n\terrors: ZodError;\n}\n\n/**\n * Represents an Express.js error request handler where the params, query and body are of unknown type as validation failed.\n */\nexport type ErrorRequestHandler<\n\tP = unknown,\n\tResBody = any,\n\tReqBody = unknown,\n\tReqQuery = unknown,\n\tLocalsObj extends Record<string, any> = Record<string, any>\n> = (\n\terr: ErrorListItem[],\n\treq: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>,\n\tres: Response<ResBody, LocalsObj>,\n\tnext: NextFunction\n) => void | Promise<void>;\n\n/**\n * Represents a generic type for route validation, which can be applied to params, query, or body.\n * Each key-value pair represents a field and its corresponding Zod validation schema.\n */\nexport type ValidationSchema = ZodType | ZodRawShape;\n\n/**\n * Defines the structure for the schemas provided to the validate middleware.\n * Each property corresponds to a different part of the request (params, query, body)\n * and should be a record of Zod types for validation. Optional handler for custom error handling.\n *\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n */\nexport interface CompleteValidationSchema<\n\tTParams extends ValidationSchema = EmptyValidationSchema,\n\tTQuery extends ValidationSchema = EmptyValidationSchema,\n\tTBody extends ValidationSchema = EmptyValidationSchema\n> {\n\thandler?: ErrorRequestHandler;\n\tparams?: TParams;\n\tquery?: TQuery;\n\tbody?: TBody;\n}\n\n/**\n * Represents the output type of a Zod validation schema.\n * This is used to infer the TypeScript type from a Zod schema,\n * providing typesafe access to the validated data.\n *\n * @template T - The validation type (params, query, or body).\n */\nexport type ZodOutput<T extends ValidationSchema> = z.output<T extends ZodRawShape ? z.ZodObject<T> : T>;\n\n/**\n * A utility type to ensure other middleware types don't conflict with the validate middleware.\n */\nexport type WeakRequestHandler = RequestHandler<unknown, unknown, unknown, Record<string, unknown>>;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/express-zod-safe/express-zod-safe/dist/index.js","../src/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/express-zod-safe/express-zod-safe/dist/index.js","sourcesContent":[null,"/// <reference types=\"./express.d.ts\" />\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport express from 'express';\nimport { type ZodError, type ZodRawShape, type ZodType, z } from 'zod';\n\nconst types = ['query', 'params', 'body'] as const;\n\nexport const defaultErrorHandler: ErrorRequestHandler = (errors, _req, res) => {\n\tres.status(400).send(errors.map(error => ({ type: error.type, errors: error.errors.issues })));\n};\n\nlet globalErrorHandler = defaultErrorHandler;\n\n/**\n * A ZodType type guard.\n * @param schema The Zod schema to check.\n * @returns Whether the provided schema is a ZodType.\n */\nfunction isZodType(schema: unknown): schema is ZodType {\n\treturn !!schema && typeof (schema as ZodType).safeParseAsync === 'function';\n}\n\n// Override express@^5 request.query getter to provider setter\nconst descriptor = Object.getOwnPropertyDescriptor(express.request, 'query');\nif (descriptor) {\n\tObject.defineProperty(express.request, 'query', {\n\t\tget(this: Request) {\n\t\t\tif (Object.hasOwn(this, '_query')) return this._query;\n\t\t\treturn descriptor?.get?.call(this);\n\t\t},\n\t\tset(this: Request, query: unknown) {\n\t\t\tthis._query = query;\n\t\t},\n\t\tconfigurable: true,\n\t\tenumerable: true\n\t});\n}\n\n/**\n * Generates a middleware function for Express.js that validates request params, query, and body.\n * This function uses Zod schemas to perform validation against the provided schema definitions.\n *\n * @param schemas - An object containing Zod schemas for params, query, and body. Optional handler for custom error handling.\n * @returns An Express.js middleware function that validates the request based on the provided schemas.\n * It attaches validated data to the request object and sends error details if validation fails.\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n * @example\n * // Example usage in an Express.js route\n * import express from 'express';\n * import validate from 'express-zod-safe';\n * import { z } from 'zod';\n *\n * const app = express();\n * app.use(express.json());\n *\n * // Define your Zod schemas\n * const params = {\n * userId: z.string().uuid(),\n * };\n * const query = {\n * age: z.coerce.number().optional(),\n * };\n * const body = {\n * name: z.string(),\n * email: z.string().email(),\n * };\n *\n * // Use the validate middleware in your route\n * app.post('/user/:userId', validate({ params, query, body }), (req, res) => {\n * // Your route logic here\n * res.send('User data is valid!');\n * });\n *\n * app.listen(3000, () => console.log('Server running on port 3000'));\n */\nexport default function validate<TParams extends ValidationSchema, TQuery extends ValidationSchema, TBody extends ValidationSchema>(\n\tschemas: CompleteValidationSchema<TParams, TQuery, TBody>\n): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>> {\n\t// Create validation objects for each type\n\tconst validation = {\n\t\tparams: isZodType(schemas.params) ? schemas.params : z.strictObject(schemas.params ?? {}),\n\t\tquery: isZodType(schemas.query) ? schemas.query : z.strictObject(schemas.query ?? {}),\n\t\tbody: isZodType(schemas.body) ? schemas.body : z.strictObject(schemas.body ?? {})\n\t};\n\n\treturn async (req, res, next): Promise<void> => {\n\t\tconst errors: ErrorListItem[] = [];\n\n\t\t// Validate all types (params, query, body)\n\t\tfor (const type of types) {\n\t\t\tconst parsed = await validation[type].safeParseAsync(req[type] ?? {});\n\t\t\tif (parsed.success) req[type] = parsed.data as any;\n\t\t\telse errors.push({ type, errors: parsed.error });\n\t\t}\n\n\t\t// Return all errors if there are any\n\t\tif (errors.length > 0) {\n\t\t\t// If a custom error handler is provided, use it\n\t\t\tconst handler = schemas.handler ?? globalErrorHandler;\n\t\t\treturn handler(errors, req, res, next);\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\nexport function setGlobalErrorHandler(handler: ErrorRequestHandler): void {\n\tglobalErrorHandler = handler;\n}\n\n/**\n * Describes the types of data that can be validated: 'query', 'params', or 'body'.\n */\ntype DataType = (typeof types)[number];\n\n/**\n * Defines the structure of an error item, containing the type of validation that failed (params, query, or body)\n * and the associated ZodError.\n */\nexport interface ErrorListItem {\n\ttype: DataType;\n\terrors: ZodError;\n}\n\nexport type Unvalidated = unknown;\n\n/**\n * Represents an Express.js error request handler where the params, query and body are of unknown type as validation failed.\n */\nexport type ErrorRequestHandler<\n\tP = Unvalidated,\n\tResBody = any,\n\tReqBody = Unvalidated,\n\tReqQuery = unknown,\n\tLocalsObj extends Record<string, any> = Record<string, any>\n> = (\n\terr: ErrorListItem[],\n\treq: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>,\n\tres: Response<ResBody, LocalsObj>,\n\tnext: NextFunction\n) => void | Promise<void>;\n\n/**\n * Represents a generic type for route validation, which can be applied to params, query, or body.\n * Each key-value pair represents a field and its corresponding Zod validation schema.\n */\nexport type ValidationSchema = ZodType | ZodRawShape;\n\n/**\n * Defines the structure for the schemas provided to the validate middleware.\n * Each property corresponds to a different part of the request (params, query, body)\n * and should be a record of Zod types for validation. Optional handler for custom error handling.\n *\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n */\nexport interface CompleteValidationSchema<\n\tTParams extends ValidationSchema = ValidationSchema,\n\tTQuery extends ValidationSchema = ValidationSchema,\n\tTBody extends ValidationSchema = ValidationSchema\n> {\n\thandler?: ErrorRequestHandler;\n\tparams?: TParams;\n\tquery?: TQuery;\n\tbody?: TBody;\n}\n\n/**\n * Represents the output type of a Zod validation schema.\n * This is used to infer the TypeScript type from a Zod schema,\n * providing typesafe access to the validated data.\n *\n * @template T - The validation type (params, query, or body).\n */\nexport type ZodOutput<T extends ValidationSchema | undefined> = T extends ValidationSchema\n\t? z.output<T extends ZodRawShape ? z.ZodObject<T> : T>\n\t: Unvalidated;\n\n/**\n * A utility type to ensure other middleware types don't conflict with the validate middleware.\n */\nexport type WeakRequestHandler = RequestHandler<Unvalidated, Unvalidated, Unvalidated, Unvalidated>;\n\n/**\n * A utility type to ensure the Request is typed correctly.\n * @template T - The validation schema to be applied to the request params, query and body.\n * @example\n * import { ValidatedRequest } from 'express-zod-safe';\n * import { z } from 'zod';\n *\n * const schema = {\n * \tquery: {\n * \t\tname: z.string().min(3).max(10),\n * \t\tage: z.coerce.number().min(18)\n * \t},\n * \tbody: {\n * \t\ttitle: z.string().max(4)\n * \t},\n * \tparams: {\n * \t\tid: z.coerce.number()\n * \t}\n * };\n *\n * const requestHandler = (req: ValidatedRequest<typeof schema>, res: Response) => {\n * \tconst { name, age } = req.query;\n * \tconst { id } = req.params;\n * const { title } = req.body;\n *\n * \tres.send(`Hello ${title} ${name}! (Your age is ${age} and your ID is ${id})`);\n * };\n *\n * app.post('/handler/:id', validate(schema), requestHandler);\n */\nexport type ValidatedRequest<T extends CompleteValidationSchema> = Request<\n\tZodOutput<T['params']>,\n\tany,\n\tZodOutput<T['body']>,\n\tZodOutput<T['query']>\n>;\n"]}
package/dist/index.mjs CHANGED
@@ -2,7 +2,10 @@
2
2
  import express from "express";
3
3
  import { z } from "zod";
4
4
  var types = ["query", "params", "body"];
5
- var emptyObjectSchema = z.object({}).strict();
5
+ var defaultErrorHandler = (errors, _req, res) => {
6
+ res.status(400).send(errors.map((error) => ({ type: error.type, errors: error.errors.issues })));
7
+ };
8
+ var globalErrorHandler = defaultErrorHandler;
6
9
  function isZodType(schema) {
7
10
  return !!schema && typeof schema.safeParseAsync === "function";
8
11
  }
@@ -34,14 +37,18 @@ function validate(schemas) {
34
37
  else errors.push({ type, errors: parsed.error });
35
38
  }
36
39
  if (errors.length > 0) {
37
- if (schemas.handler) return schemas.handler(errors, req, res, next);
38
- res.status(400).send(errors.map((error) => ({ type: error.type, errors: error.errors })));
39
- return;
40
+ const handler = schemas.handler ?? globalErrorHandler;
41
+ return handler(errors, req, res, next);
40
42
  }
41
43
  return next();
42
44
  };
43
45
  }
46
+ function setGlobalErrorHandler(handler) {
47
+ globalErrorHandler = handler;
48
+ }
44
49
  export {
45
- validate as default
50
+ validate as default,
51
+ defaultErrorHandler,
52
+ setGlobalErrorHandler
46
53
  };
47
54
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/// <reference types=\"./express.d.ts\" />\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport express from 'express';\nimport { type ZodError, type ZodRawShape, type ZodType, z } from 'zod';\n\nconst types = ['query', 'params', 'body'] as const;\nconst emptyObjectSchema = z.object({}).strict();\nexport type EmptyValidationSchema = typeof emptyObjectSchema;\n\n/**\n * A ZodType type guard.\n * @param schema The Zod schema to check.\n * @returns Whether the provided schema is a ZodType.\n */\nfunction isZodType(schema: unknown): schema is ZodType {\n\treturn !!schema && typeof (schema as ZodType).safeParseAsync === 'function';\n}\n\n// Override express@^5 request.query getter to provider setter\nconst descriptor = Object.getOwnPropertyDescriptor(express.request, 'query');\nif (descriptor) {\n\tObject.defineProperty(express.request, 'query', {\n\t\tget(this: Request) {\n\t\t\tif (Object.hasOwn(this, '_query')) return this._query;\n\t\t\treturn descriptor?.get?.call(this);\n\t\t},\n\t\tset(this: Request, query: unknown) {\n\t\t\tthis._query = query;\n\t\t},\n\t\tconfigurable: true,\n\t\tenumerable: true\n\t});\n}\n\n/**\n * Generates a middleware function for Express.js that validates request params, query, and body.\n * This function uses Zod schemas to perform validation against the provided schema definitions.\n *\n * @param schemas - An object containing Zod schemas for params, query, and body. Optional handler for custom error handling.\n * @returns An Express.js middleware function that validates the request based on the provided schemas.\n * It attaches validated data to the request object and sends error details if validation fails.\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n * @example\n * // Example usage in an Express.js route\n * import express from 'express';\n * import validate from 'express-zod-safe';\n * import { z } from 'zod';\n *\n * const app = express();\n *\n * // Define your Zod schemas\n * const params = {\n * userId: z.string().uuid(),\n * };\n * const query = {\n * age: z.coerce.number().optional(),\n * };\n * const body = {\n * name: z.string(),\n * email: z.string().email(),\n * };\n *\n * // Use the validate middleware in your route\n * app.post('/user/:userId', validate({ params, query, body }), (req, res) => {\n * // Your route logic here\n * res.send('User data is valid!');\n * });\n *\n * app.listen(3000, () => console.log('Server running on port 3000'));\n */\nexport default function validate<\n\tTParams extends ValidationSchema = EmptyValidationSchema,\n\tTQuery extends ValidationSchema = EmptyValidationSchema,\n\tTBody extends ValidationSchema = EmptyValidationSchema\n>(schemas: CompleteValidationSchema<TParams, TQuery, TBody>): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>> {\n\t// Create validation objects for each type\n\tconst validation = {\n\t\tparams: isZodType(schemas.params) ? schemas.params : z.strictObject(schemas.params ?? {}),\n\t\tquery: isZodType(schemas.query) ? schemas.query : z.strictObject(schemas.query ?? {}),\n\t\tbody: isZodType(schemas.body) ? schemas.body : z.strictObject(schemas.body ?? {})\n\t};\n\n\treturn async (req, res, next): Promise<void> => {\n\t\tconst errors: ErrorListItem[] = [];\n\n\t\t// Validate all types (params, query, body)\n\t\tfor (const type of types) {\n\t\t\tconst parsed = await validation[type].safeParseAsync(req[type] ?? {});\n\t\t\tif (parsed.success) req[type] = parsed.data as any;\n\t\t\telse errors.push({ type, errors: parsed.error });\n\t\t}\n\n\t\t// Return all errors if there are any\n\t\tif (errors.length > 0) {\n\t\t\t// If a custom error handler is provided, use it\n\t\t\tif (schemas.handler) return schemas.handler(errors, req, res, next);\n\n\t\t\tres.status(400).send(errors.map(error => ({ type: error.type, errors: error.errors })));\n\t\t\treturn;\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\n/**\n * Describes the types of data that can be validated: 'query', 'params', or 'body'.\n */\ntype DataType = (typeof types)[number];\n\n/**\n * Defines the structure of an error item, containing the type of validation that failed (params, query, or body)\n * and the associated ZodError.\n */\nexport interface ErrorListItem {\n\ttype: DataType;\n\terrors: ZodError;\n}\n\n/**\n * Represents an Express.js error request handler where the params, query and body are of unknown type as validation failed.\n */\nexport type ErrorRequestHandler<\n\tP = unknown,\n\tResBody = any,\n\tReqBody = unknown,\n\tReqQuery = unknown,\n\tLocalsObj extends Record<string, any> = Record<string, any>\n> = (\n\terr: ErrorListItem[],\n\treq: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>,\n\tres: Response<ResBody, LocalsObj>,\n\tnext: NextFunction\n) => void | Promise<void>;\n\n/**\n * Represents a generic type for route validation, which can be applied to params, query, or body.\n * Each key-value pair represents a field and its corresponding Zod validation schema.\n */\nexport type ValidationSchema = ZodType | ZodRawShape;\n\n/**\n * Defines the structure for the schemas provided to the validate middleware.\n * Each property corresponds to a different part of the request (params, query, body)\n * and should be a record of Zod types for validation. Optional handler for custom error handling.\n *\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n */\nexport interface CompleteValidationSchema<\n\tTParams extends ValidationSchema = EmptyValidationSchema,\n\tTQuery extends ValidationSchema = EmptyValidationSchema,\n\tTBody extends ValidationSchema = EmptyValidationSchema\n> {\n\thandler?: ErrorRequestHandler;\n\tparams?: TParams;\n\tquery?: TQuery;\n\tbody?: TBody;\n}\n\n/**\n * Represents the output type of a Zod validation schema.\n * This is used to infer the TypeScript type from a Zod schema,\n * providing typesafe access to the validated data.\n *\n * @template T - The validation type (params, query, or body).\n */\nexport type ZodOutput<T extends ValidationSchema> = z.output<T extends ZodRawShape ? z.ZodObject<T> : T>;\n\n/**\n * A utility type to ensure other middleware types don't conflict with the validate middleware.\n */\nexport type WeakRequestHandler = RequestHandler<unknown, unknown, unknown, Record<string, unknown>>;\n"],"mappings":";AAEA,OAAO,aAAa;AACpB,SAAwD,SAAS;AAEjE,IAAM,QAAQ,CAAC,SAAS,UAAU,MAAM;AACxC,IAAM,oBAAoB,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO;AAQ9C,SAAS,UAAU,QAAoC;AACtD,SAAO,CAAC,CAAC,UAAU,OAAQ,OAAmB,mBAAmB;AAClE;AAGA,IAAM,aAAa,OAAO,yBAAyB,QAAQ,SAAS,OAAO;AAC3E,IAAI,YAAY;AACf,SAAO,eAAe,QAAQ,SAAS,SAAS;AAAA,IAC/C,MAAmB;AAClB,UAAI,OAAO,OAAO,MAAM,QAAQ,EAAG,QAAO,KAAK;AAC/C,aAAO,YAAY,KAAK,KAAK,IAAI;AAAA,IAClC;AAAA,IACA,IAAmB,OAAgB;AAClC,WAAK,SAAS;AAAA,IACf;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC;AACF;AAwCe,SAAR,SAIL,SAAyI;AAE1I,QAAM,aAAa;AAAA,IAClB,QAAQ,UAAU,QAAQ,MAAM,IAAI,QAAQ,SAAS,EAAE,aAAa,QAAQ,UAAU,CAAC,CAAC;AAAA,IACxF,OAAO,UAAU,QAAQ,KAAK,IAAI,QAAQ,QAAQ,EAAE,aAAa,QAAQ,SAAS,CAAC,CAAC;AAAA,IACpF,MAAM,UAAU,QAAQ,IAAI,IAAI,QAAQ,OAAO,EAAE,aAAa,QAAQ,QAAQ,CAAC,CAAC;AAAA,EACjF;AAEA,SAAO,OAAO,KAAK,KAAK,SAAwB;AAC/C,UAAM,SAA0B,CAAC;AAGjC,eAAW,QAAQ,OAAO;AACzB,YAAM,SAAS,MAAM,WAAW,IAAI,EAAE,eAAe,IAAI,IAAI,KAAK,CAAC,CAAC;AACpE,UAAI,OAAO,QAAS,KAAI,IAAI,IAAI,OAAO;AAAA,UAClC,QAAO,KAAK,EAAE,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,IAChD;AAGA,QAAI,OAAO,SAAS,GAAG;AAEtB,UAAI,QAAQ,QAAS,QAAO,QAAQ,QAAQ,QAAQ,KAAK,KAAK,IAAI;AAElE,UAAI,OAAO,GAAG,EAAE,KAAK,OAAO,IAAI,YAAU,EAAE,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO,EAAE,CAAC;AACtF;AAAA,IACD;AAEA,WAAO,KAAK;AAAA,EACb;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/// <reference types=\"./express.d.ts\" />\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport express from 'express';\nimport { type ZodError, type ZodRawShape, type ZodType, z } from 'zod';\n\nconst types = ['query', 'params', 'body'] as const;\n\nexport const defaultErrorHandler: ErrorRequestHandler = (errors, _req, res) => {\n\tres.status(400).send(errors.map(error => ({ type: error.type, errors: error.errors.issues })));\n};\n\nlet globalErrorHandler = defaultErrorHandler;\n\n/**\n * A ZodType type guard.\n * @param schema The Zod schema to check.\n * @returns Whether the provided schema is a ZodType.\n */\nfunction isZodType(schema: unknown): schema is ZodType {\n\treturn !!schema && typeof (schema as ZodType).safeParseAsync === 'function';\n}\n\n// Override express@^5 request.query getter to provider setter\nconst descriptor = Object.getOwnPropertyDescriptor(express.request, 'query');\nif (descriptor) {\n\tObject.defineProperty(express.request, 'query', {\n\t\tget(this: Request) {\n\t\t\tif (Object.hasOwn(this, '_query')) return this._query;\n\t\t\treturn descriptor?.get?.call(this);\n\t\t},\n\t\tset(this: Request, query: unknown) {\n\t\t\tthis._query = query;\n\t\t},\n\t\tconfigurable: true,\n\t\tenumerable: true\n\t});\n}\n\n/**\n * Generates a middleware function for Express.js that validates request params, query, and body.\n * This function uses Zod schemas to perform validation against the provided schema definitions.\n *\n * @param schemas - An object containing Zod schemas for params, query, and body. Optional handler for custom error handling.\n * @returns An Express.js middleware function that validates the request based on the provided schemas.\n * It attaches validated data to the request object and sends error details if validation fails.\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n * @example\n * // Example usage in an Express.js route\n * import express from 'express';\n * import validate from 'express-zod-safe';\n * import { z } from 'zod';\n *\n * const app = express();\n * app.use(express.json());\n *\n * // Define your Zod schemas\n * const params = {\n * userId: z.string().uuid(),\n * };\n * const query = {\n * age: z.coerce.number().optional(),\n * };\n * const body = {\n * name: z.string(),\n * email: z.string().email(),\n * };\n *\n * // Use the validate middleware in your route\n * app.post('/user/:userId', validate({ params, query, body }), (req, res) => {\n * // Your route logic here\n * res.send('User data is valid!');\n * });\n *\n * app.listen(3000, () => console.log('Server running on port 3000'));\n */\nexport default function validate<TParams extends ValidationSchema, TQuery extends ValidationSchema, TBody extends ValidationSchema>(\n\tschemas: CompleteValidationSchema<TParams, TQuery, TBody>\n): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>> {\n\t// Create validation objects for each type\n\tconst validation = {\n\t\tparams: isZodType(schemas.params) ? schemas.params : z.strictObject(schemas.params ?? {}),\n\t\tquery: isZodType(schemas.query) ? schemas.query : z.strictObject(schemas.query ?? {}),\n\t\tbody: isZodType(schemas.body) ? schemas.body : z.strictObject(schemas.body ?? {})\n\t};\n\n\treturn async (req, res, next): Promise<void> => {\n\t\tconst errors: ErrorListItem[] = [];\n\n\t\t// Validate all types (params, query, body)\n\t\tfor (const type of types) {\n\t\t\tconst parsed = await validation[type].safeParseAsync(req[type] ?? {});\n\t\t\tif (parsed.success) req[type] = parsed.data as any;\n\t\t\telse errors.push({ type, errors: parsed.error });\n\t\t}\n\n\t\t// Return all errors if there are any\n\t\tif (errors.length > 0) {\n\t\t\t// If a custom error handler is provided, use it\n\t\t\tconst handler = schemas.handler ?? globalErrorHandler;\n\t\t\treturn handler(errors, req, res, next);\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\nexport function setGlobalErrorHandler(handler: ErrorRequestHandler): void {\n\tglobalErrorHandler = handler;\n}\n\n/**\n * Describes the types of data that can be validated: 'query', 'params', or 'body'.\n */\ntype DataType = (typeof types)[number];\n\n/**\n * Defines the structure of an error item, containing the type of validation that failed (params, query, or body)\n * and the associated ZodError.\n */\nexport interface ErrorListItem {\n\ttype: DataType;\n\terrors: ZodError;\n}\n\nexport type Unvalidated = unknown;\n\n/**\n * Represents an Express.js error request handler where the params, query and body are of unknown type as validation failed.\n */\nexport type ErrorRequestHandler<\n\tP = Unvalidated,\n\tResBody = any,\n\tReqBody = Unvalidated,\n\tReqQuery = unknown,\n\tLocalsObj extends Record<string, any> = Record<string, any>\n> = (\n\terr: ErrorListItem[],\n\treq: Request<P, ResBody, ReqBody, ReqQuery, LocalsObj>,\n\tres: Response<ResBody, LocalsObj>,\n\tnext: NextFunction\n) => void | Promise<void>;\n\n/**\n * Represents a generic type for route validation, which can be applied to params, query, or body.\n * Each key-value pair represents a field and its corresponding Zod validation schema.\n */\nexport type ValidationSchema = ZodType | ZodRawShape;\n\n/**\n * Defines the structure for the schemas provided to the validate middleware.\n * Each property corresponds to a different part of the request (params, query, body)\n * and should be a record of Zod types for validation. Optional handler for custom error handling.\n *\n * @template TParams - Type definition for params schema.\n * @template TQuery - Type definition for query schema.\n * @template TBody - Type definition for body schema.\n */\nexport interface CompleteValidationSchema<\n\tTParams extends ValidationSchema = ValidationSchema,\n\tTQuery extends ValidationSchema = ValidationSchema,\n\tTBody extends ValidationSchema = ValidationSchema\n> {\n\thandler?: ErrorRequestHandler;\n\tparams?: TParams;\n\tquery?: TQuery;\n\tbody?: TBody;\n}\n\n/**\n * Represents the output type of a Zod validation schema.\n * This is used to infer the TypeScript type from a Zod schema,\n * providing typesafe access to the validated data.\n *\n * @template T - The validation type (params, query, or body).\n */\nexport type ZodOutput<T extends ValidationSchema | undefined> = T extends ValidationSchema\n\t? z.output<T extends ZodRawShape ? z.ZodObject<T> : T>\n\t: Unvalidated;\n\n/**\n * A utility type to ensure other middleware types don't conflict with the validate middleware.\n */\nexport type WeakRequestHandler = RequestHandler<Unvalidated, Unvalidated, Unvalidated, Unvalidated>;\n\n/**\n * A utility type to ensure the Request is typed correctly.\n * @template T - The validation schema to be applied to the request params, query and body.\n * @example\n * import { ValidatedRequest } from 'express-zod-safe';\n * import { z } from 'zod';\n *\n * const schema = {\n * \tquery: {\n * \t\tname: z.string().min(3).max(10),\n * \t\tage: z.coerce.number().min(18)\n * \t},\n * \tbody: {\n * \t\ttitle: z.string().max(4)\n * \t},\n * \tparams: {\n * \t\tid: z.coerce.number()\n * \t}\n * };\n *\n * const requestHandler = (req: ValidatedRequest<typeof schema>, res: Response) => {\n * \tconst { name, age } = req.query;\n * \tconst { id } = req.params;\n * const { title } = req.body;\n *\n * \tres.send(`Hello ${title} ${name}! (Your age is ${age} and your ID is ${id})`);\n * };\n *\n * app.post('/handler/:id', validate(schema), requestHandler);\n */\nexport type ValidatedRequest<T extends CompleteValidationSchema> = Request<\n\tZodOutput<T['params']>,\n\tany,\n\tZodOutput<T['body']>,\n\tZodOutput<T['query']>\n>;\n"],"mappings":";AAEA,OAAO,aAAa;AACpB,SAAwD,SAAS;AAEjE,IAAM,QAAQ,CAAC,SAAS,UAAU,MAAM;AAEjC,IAAM,sBAA2C,CAAC,QAAQ,MAAM,QAAQ;AAC9E,MAAI,OAAO,GAAG,EAAE,KAAK,OAAO,IAAI,YAAU,EAAE,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO,OAAO,EAAE,CAAC;AAC9F;AAEA,IAAI,qBAAqB;AAOzB,SAAS,UAAU,QAAoC;AACtD,SAAO,CAAC,CAAC,UAAU,OAAQ,OAAmB,mBAAmB;AAClE;AAGA,IAAM,aAAa,OAAO,yBAAyB,QAAQ,SAAS,OAAO;AAC3E,IAAI,YAAY;AACf,SAAO,eAAe,QAAQ,SAAS,SAAS;AAAA,IAC/C,MAAmB;AAClB,UAAI,OAAO,OAAO,MAAM,QAAQ,EAAG,QAAO,KAAK;AAC/C,aAAO,YAAY,KAAK,KAAK,IAAI;AAAA,IAClC;AAAA,IACA,IAAmB,OAAgB;AAClC,WAAK,SAAS;AAAA,IACf;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC;AACF;AAyCe,SAAR,SACN,SAC+E;AAE/E,QAAM,aAAa;AAAA,IAClB,QAAQ,UAAU,QAAQ,MAAM,IAAI,QAAQ,SAAS,EAAE,aAAa,QAAQ,UAAU,CAAC,CAAC;AAAA,IACxF,OAAO,UAAU,QAAQ,KAAK,IAAI,QAAQ,QAAQ,EAAE,aAAa,QAAQ,SAAS,CAAC,CAAC;AAAA,IACpF,MAAM,UAAU,QAAQ,IAAI,IAAI,QAAQ,OAAO,EAAE,aAAa,QAAQ,QAAQ,CAAC,CAAC;AAAA,EACjF;AAEA,SAAO,OAAO,KAAK,KAAK,SAAwB;AAC/C,UAAM,SAA0B,CAAC;AAGjC,eAAW,QAAQ,OAAO;AACzB,YAAM,SAAS,MAAM,WAAW,IAAI,EAAE,eAAe,IAAI,IAAI,KAAK,CAAC,CAAC;AACpE,UAAI,OAAO,QAAS,KAAI,IAAI,IAAI,OAAO;AAAA,UAClC,QAAO,KAAK,EAAE,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,IAChD;AAGA,QAAI,OAAO,SAAS,GAAG;AAEtB,YAAM,UAAU,QAAQ,WAAW;AACnC,aAAO,QAAQ,QAAQ,KAAK,KAAK,IAAI;AAAA,IACtC;AAEA,WAAO,KAAK;AAAA,EACb;AACD;AAEO,SAAS,sBAAsB,SAAoC;AACzE,uBAAqB;AACtB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-zod-safe",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "TypeScript-friendly middleware designed for Express applications, leveraging the robustness of Zod schemas to validate incoming request bodies, parameters, and queries.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -52,7 +52,7 @@
52
52
  "express": "^4.0.0 || ^5.0.0",
53
53
  "zod": "^4.0.0"
54
54
  },
55
- "packageManager": "pnpm@10.11.0",
55
+ "packageManager": "pnpm@10.17.0",
56
56
  "scripts": {
57
57
  "build": "tsup",
58
58
  "lint": "biome check --fix",