next-form-request 0.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 ADDED
@@ -0,0 +1,294 @@
1
+ # next-request
2
+
3
+ Laravel-inspired Form Request validation for Next.js API routes.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/next-request.svg)](https://www.npmjs.com/package/next-request)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - **Laravel-style Form Requests** - Familiar `rules()`, `authorize()`, `beforeValidation()` hooks
11
+ - **Validator Agnostic** - Use Zod, Yup, or bring your own validator
12
+ - **Full TypeScript Support** - Complete type inference for validated data
13
+ - **Works with Both Routers** - App Router and Pages Router support
14
+ - **Flexible API** - Manual instantiation or convenient wrapper functions
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install next-request
20
+ ```
21
+
22
+ With Zod (recommended):
23
+ ```bash
24
+ npm install next-request zod
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Define a Form Request
30
+
31
+ ```typescript
32
+ // requests/CreateUserRequest.ts
33
+ import { FormRequest, ZodAdapter } from 'next-request';
34
+ import { z } from 'zod';
35
+
36
+ const schema = z.object({
37
+ email: z.string().email(),
38
+ name: z.string().min(2),
39
+ password: z.string().min(8),
40
+ });
41
+
42
+ export class CreateUserRequest extends FormRequest<z.infer<typeof schema>> {
43
+ rules() {
44
+ return new ZodAdapter(schema);
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### 2. Use in Your API Route
50
+
51
+ **App Router (Next.js 13+)**
52
+ ```typescript
53
+ // app/api/users/route.ts
54
+ import { CreateUserRequest, ValidationError, AuthorizationError } from 'next-request';
55
+
56
+ export async function POST(request: Request) {
57
+ try {
58
+ const form = await CreateUserRequest.fromAppRouter(request);
59
+ const data = await form.validate();
60
+
61
+ // data is fully typed as { email: string; name: string; password: string }
62
+ const user = await db.users.create({ data });
63
+ return Response.json({ user }, { status: 201 });
64
+ } catch (error) {
65
+ if (error instanceof ValidationError) {
66
+ return Response.json({ errors: error.errors }, { status: 422 });
67
+ }
68
+ if (error instanceof AuthorizationError) {
69
+ return Response.json({ message: 'Forbidden' }, { status: 403 });
70
+ }
71
+ throw error;
72
+ }
73
+ }
74
+ ```
75
+
76
+ **Pages Router**
77
+ ```typescript
78
+ // pages/api/users.ts
79
+ import { CreateUserRequest, ValidationError } from 'next-request';
80
+ import type { NextApiRequest, NextApiResponse } from 'next';
81
+
82
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
83
+ try {
84
+ const form = await CreateUserRequest.fromPagesRouter(req);
85
+ const data = await form.validate();
86
+
87
+ const user = await db.users.create({ data });
88
+ return res.status(201).json({ user });
89
+ } catch (error) {
90
+ if (error instanceof ValidationError) {
91
+ return res.status(422).json({ errors: error.errors });
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ ```
97
+
98
+ ## Using Wrapper Functions
99
+
100
+ For cleaner code, use the wrapper functions:
101
+
102
+ ```typescript
103
+ // app/api/users/route.ts
104
+ import { withRequest } from 'next-request';
105
+ import { CreateUserRequest } from '@/requests/CreateUserRequest';
106
+
107
+ export const POST = withRequest(CreateUserRequest, async (data, request) => {
108
+ const user = await db.users.create({ data });
109
+ return Response.json({ user }, { status: 201 });
110
+ });
111
+ ```
112
+
113
+ ### With Custom Error Handling
114
+
115
+ ```typescript
116
+ import { createAppRouterWrapper, ValidationError, AuthorizationError } from 'next-request';
117
+
118
+ const withValidation = createAppRouterWrapper({
119
+ onValidationError: (error) =>
120
+ Response.json({ errors: error.errors }, { status: 422 }),
121
+ onAuthorizationError: () =>
122
+ Response.json({ message: 'Forbidden' }, { status: 403 }),
123
+ });
124
+
125
+ export const POST = withValidation(CreateUserRequest, async (data) => {
126
+ const user = await db.users.create({ data });
127
+ return Response.json({ user }, { status: 201 });
128
+ });
129
+ ```
130
+
131
+ ## Lifecycle Hooks
132
+
133
+ Form Requests support Laravel-style hooks:
134
+
135
+ ```typescript
136
+ class CreateUserRequest extends FormRequest<UserData> {
137
+ rules() {
138
+ return new ZodAdapter(schema);
139
+ }
140
+
141
+ // Check if the user is authorized to make this request
142
+ async authorize() {
143
+ const session = await getSession(this.request);
144
+ return session?.user?.role === 'admin';
145
+ }
146
+
147
+ // Transform data before validation
148
+ beforeValidation() {
149
+ if (this.body.email) {
150
+ this.body.email = this.body.email.toLowerCase().trim();
151
+ }
152
+ }
153
+
154
+ // Called after successful validation
155
+ afterValidation(data: UserData) {
156
+ console.log('Creating user:', data.email);
157
+ }
158
+
159
+ // Called when validation fails
160
+ onValidationFailed(errors: ValidationErrors) {
161
+ console.error('Validation failed:', errors);
162
+ }
163
+
164
+ // Called when authorization fails
165
+ onAuthorizationFailed() {
166
+ console.error('Unauthorized request attempt');
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Custom Messages
172
+
173
+ Override error messages and attribute names:
174
+
175
+ ```typescript
176
+ class CreateUserRequest extends FormRequest<UserData> {
177
+ rules() {
178
+ return new ZodAdapter(schema);
179
+ }
180
+
181
+ messages() {
182
+ return {
183
+ 'email.invalid_string': 'Please provide a valid email address',
184
+ 'password.too_small': 'Password must be at least 8 characters',
185
+ };
186
+ }
187
+
188
+ attributes() {
189
+ return {
190
+ email: 'email address',
191
+ dob: 'date of birth',
192
+ };
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Helper Methods
198
+
199
+ Access request data with convenient helpers:
200
+
201
+ ```typescript
202
+ const form = await MyRequest.fromAppRouter(request, { id: '123' });
203
+
204
+ // Get input values
205
+ form.input('email'); // Get a value
206
+ form.input('missing', 'default'); // With default
207
+ form.has('email'); // Check existence
208
+ form.all(); // Get all body data
209
+
210
+ // Filter input
211
+ form.only('email', 'name'); // Only these keys
212
+ form.except('password'); // All except these
213
+
214
+ // Route params & headers
215
+ form.param('id'); // Route parameter
216
+ form.header('content-type'); // Header value
217
+
218
+ // After validation
219
+ const data = await form.validate();
220
+ form.validated(); // Get validated data again
221
+ ```
222
+
223
+ ## Creating Custom Validator Adapters
224
+
225
+ Implement the `ValidatorAdapter` interface to use any validation library:
226
+
227
+ ```typescript
228
+ import type { ValidatorAdapter, ValidationResult, ValidationConfig } from 'next-request';
229
+ import * as yup from 'yup';
230
+
231
+ class YupAdapter<T> implements ValidatorAdapter<T> {
232
+ constructor(private schema: yup.Schema<T>) {}
233
+
234
+ async validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>> {
235
+ try {
236
+ const validated = await this.schema.validate(data, { abortEarly: false });
237
+ return { success: true, data: validated };
238
+ } catch (error) {
239
+ if (error instanceof yup.ValidationError) {
240
+ const errors: Record<string, string[]> = {};
241
+ for (const err of error.inner) {
242
+ const path = err.path || '_root';
243
+ errors[path] = errors[path] || [];
244
+ errors[path].push(err.message);
245
+ }
246
+ return { success: false, errors };
247
+ }
248
+ throw error;
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ ## API Reference
255
+
256
+ ### FormRequest
257
+
258
+ | Method | Description |
259
+ |--------|-------------|
260
+ | `rules()` | **Required.** Return a ValidatorAdapter instance |
261
+ | `authorize()` | Return `true` to allow, `false` to reject |
262
+ | `beforeValidation()` | Transform `this.body` before validation |
263
+ | `afterValidation(data)` | Called after successful validation |
264
+ | `onValidationFailed(errors)` | Called when validation fails |
265
+ | `onAuthorizationFailed()` | Called when authorization fails |
266
+ | `messages()` | Custom error messages |
267
+ | `attributes()` | Custom attribute names |
268
+
269
+ ### Static Factory Methods
270
+
271
+ | Method | Description |
272
+ |--------|-------------|
273
+ | `fromAppRouter(request, params?)` | Create from App Router Request |
274
+ | `fromPagesRouter(request, params?)` | Create from Pages Router NextApiRequest |
275
+
276
+ ### Wrapper Functions
277
+
278
+ | Function | Description |
279
+ |----------|-------------|
280
+ | `withRequest(RequestClass, handler)` | Wrap App Router handler |
281
+ | `withApiRequest(RequestClass, handler)` | Wrap Pages Router handler |
282
+ | `createAppRouterWrapper(options)` | Create wrapper with custom error handling |
283
+ | `createPagesRouterWrapper(options)` | Create wrapper with custom error handling |
284
+
285
+ ### Error Classes
286
+
287
+ | Class | Description |
288
+ |-------|-------------|
289
+ | `ValidationError` | Thrown when validation fails. Has `.errors` property |
290
+ | `AuthorizationError` | Thrown when `authorize()` returns false |
291
+
292
+ ## License
293
+
294
+ MIT
@@ -0,0 +1,95 @@
1
+ import { ZodSchema } from 'zod';
2
+ import * as next from 'next';
3
+ import { NextApiRequest } from 'next';
4
+
5
+ /**
6
+ * Validation errors mapped by field name to array of error messages
7
+ */
8
+ type ValidationErrors = Record<string, string[]>;
9
+ /**
10
+ * Result of a validation operation
11
+ */
12
+ interface ValidationResult<T> {
13
+ success: boolean;
14
+ data?: T;
15
+ errors?: ValidationErrors;
16
+ }
17
+ /**
18
+ * Configuration options for custom error messages and attribute names
19
+ */
20
+ interface ValidationConfig {
21
+ messages?: Record<string, string>;
22
+ attributes?: Record<string, string>;
23
+ }
24
+ /**
25
+ * Adapter interface for validation libraries (Zod, Yup, etc.)
26
+ */
27
+ interface ValidatorAdapter<T> {
28
+ /**
29
+ * Validate data asynchronously
30
+ */
31
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
32
+ /**
33
+ * Validate data synchronously (optional)
34
+ */
35
+ validateSync?(data: unknown, config?: ValidationConfig): ValidationResult<T>;
36
+ }
37
+ /**
38
+ * Data extracted from the incoming request
39
+ */
40
+ interface RequestData {
41
+ body: unknown;
42
+ query: Record<string, string | string[] | undefined>;
43
+ params: Record<string, string>;
44
+ headers: Record<string, string | string[] | undefined>;
45
+ }
46
+ /**
47
+ * Union type for supported request types
48
+ */
49
+ type SupportedRequest = Request | NextApiRequest;
50
+ /**
51
+ * Type guard to check if request is App Router Request
52
+ */
53
+ declare function isAppRouterRequest(request: SupportedRequest): request is Request;
54
+ /**
55
+ * Type guard to check if request is Pages Router NextApiRequest
56
+ */
57
+ declare function isPagesRouterRequest(request: SupportedRequest): request is NextApiRequest;
58
+ /**
59
+ * Handler function for App Router withRequest wrapper
60
+ */
61
+ type AppRouterHandler<TValidated> = (validated: TValidated, request: Request) => Response | Promise<Response>;
62
+ /**
63
+ * Handler function for Pages Router withApiRequest wrapper
64
+ */
65
+ type PagesRouterHandler<TValidated> = (validated: TValidated, request: NextApiRequest, response: next.NextApiResponse) => void | Promise<void>;
66
+
67
+ /**
68
+ * Validator adapter for Zod schemas
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { z } from 'zod';
73
+ * import { ZodAdapter } from 'next-request/zod';
74
+ *
75
+ * const schema = z.object({
76
+ * email: z.string().email(),
77
+ * name: z.string().min(2),
78
+ * });
79
+ *
80
+ * class MyRequest extends FormRequest<z.infer<typeof schema>> {
81
+ * rules() {
82
+ * return new ZodAdapter(schema);
83
+ * }
84
+ * }
85
+ * ```
86
+ */
87
+ declare class ZodAdapter<T> implements ValidatorAdapter<T> {
88
+ private schema;
89
+ constructor(schema: ZodSchema<T>);
90
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
91
+ validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T>;
92
+ private formatZodErrors;
93
+ }
94
+
95
+ export { type AppRouterHandler as A, type PagesRouterHandler as P, type RequestData as R, type SupportedRequest as S, type ValidatorAdapter as V, ZodAdapter as Z, type ValidationErrors as a, type ValidationResult as b, type ValidationConfig as c, isPagesRouterRequest as d, isAppRouterRequest as i };
@@ -0,0 +1,95 @@
1
+ import { ZodSchema } from 'zod';
2
+ import * as next from 'next';
3
+ import { NextApiRequest } from 'next';
4
+
5
+ /**
6
+ * Validation errors mapped by field name to array of error messages
7
+ */
8
+ type ValidationErrors = Record<string, string[]>;
9
+ /**
10
+ * Result of a validation operation
11
+ */
12
+ interface ValidationResult<T> {
13
+ success: boolean;
14
+ data?: T;
15
+ errors?: ValidationErrors;
16
+ }
17
+ /**
18
+ * Configuration options for custom error messages and attribute names
19
+ */
20
+ interface ValidationConfig {
21
+ messages?: Record<string, string>;
22
+ attributes?: Record<string, string>;
23
+ }
24
+ /**
25
+ * Adapter interface for validation libraries (Zod, Yup, etc.)
26
+ */
27
+ interface ValidatorAdapter<T> {
28
+ /**
29
+ * Validate data asynchronously
30
+ */
31
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
32
+ /**
33
+ * Validate data synchronously (optional)
34
+ */
35
+ validateSync?(data: unknown, config?: ValidationConfig): ValidationResult<T>;
36
+ }
37
+ /**
38
+ * Data extracted from the incoming request
39
+ */
40
+ interface RequestData {
41
+ body: unknown;
42
+ query: Record<string, string | string[] | undefined>;
43
+ params: Record<string, string>;
44
+ headers: Record<string, string | string[] | undefined>;
45
+ }
46
+ /**
47
+ * Union type for supported request types
48
+ */
49
+ type SupportedRequest = Request | NextApiRequest;
50
+ /**
51
+ * Type guard to check if request is App Router Request
52
+ */
53
+ declare function isAppRouterRequest(request: SupportedRequest): request is Request;
54
+ /**
55
+ * Type guard to check if request is Pages Router NextApiRequest
56
+ */
57
+ declare function isPagesRouterRequest(request: SupportedRequest): request is NextApiRequest;
58
+ /**
59
+ * Handler function for App Router withRequest wrapper
60
+ */
61
+ type AppRouterHandler<TValidated> = (validated: TValidated, request: Request) => Response | Promise<Response>;
62
+ /**
63
+ * Handler function for Pages Router withApiRequest wrapper
64
+ */
65
+ type PagesRouterHandler<TValidated> = (validated: TValidated, request: NextApiRequest, response: next.NextApiResponse) => void | Promise<void>;
66
+
67
+ /**
68
+ * Validator adapter for Zod schemas
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { z } from 'zod';
73
+ * import { ZodAdapter } from 'next-request/zod';
74
+ *
75
+ * const schema = z.object({
76
+ * email: z.string().email(),
77
+ * name: z.string().min(2),
78
+ * });
79
+ *
80
+ * class MyRequest extends FormRequest<z.infer<typeof schema>> {
81
+ * rules() {
82
+ * return new ZodAdapter(schema);
83
+ * }
84
+ * }
85
+ * ```
86
+ */
87
+ declare class ZodAdapter<T> implements ValidatorAdapter<T> {
88
+ private schema;
89
+ constructor(schema: ZodSchema<T>);
90
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
91
+ validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T>;
92
+ private formatZodErrors;
93
+ }
94
+
95
+ export { type AppRouterHandler as A, type PagesRouterHandler as P, type RequestData as R, type SupportedRequest as S, type ValidatorAdapter as V, ZodAdapter as Z, type ValidationErrors as a, type ValidationResult as b, type ValidationConfig as c, isPagesRouterRequest as d, isAppRouterRequest as i };
@@ -0,0 +1,3 @@
1
+ import 'zod';
2
+ export { Z as ZodAdapter } from '../../ZodAdapter-D7D3Sc-a.mjs';
3
+ import 'next';
@@ -0,0 +1,3 @@
1
+ import 'zod';
2
+ export { Z as ZodAdapter } from '../../ZodAdapter-D7D3Sc-a.js';
3
+ import 'next';
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ // src/adapters/validators/ZodAdapter.ts
4
+ var ZodAdapter = class {
5
+ constructor(schema) {
6
+ this.schema = schema;
7
+ }
8
+ async validate(data, config) {
9
+ return this.validateSync(data, config);
10
+ }
11
+ validateSync(data, config) {
12
+ const result = this.schema.safeParse(data);
13
+ if (result.success) {
14
+ return {
15
+ success: true,
16
+ data: result.data
17
+ };
18
+ }
19
+ const errors = this.formatZodErrors(result.error, config);
20
+ return {
21
+ success: false,
22
+ errors
23
+ };
24
+ }
25
+ formatZodErrors(error, config) {
26
+ const errors = {};
27
+ const customMessages = config?.messages ?? {};
28
+ const customAttributes = config?.attributes ?? {};
29
+ for (const issue of error.issues) {
30
+ const path = issue.path.join(".");
31
+ const fieldName = path || "_root";
32
+ const attributeName = customAttributes[fieldName] ?? fieldName;
33
+ const messageKey = `${fieldName}.${issue.code}`;
34
+ let message;
35
+ if (customMessages[messageKey]) {
36
+ message = customMessages[messageKey];
37
+ } else if (customMessages[fieldName]) {
38
+ message = customMessages[fieldName];
39
+ } else {
40
+ message = issue.message.replace(
41
+ new RegExp(`\\b${fieldName}\\b`, "gi"),
42
+ attributeName
43
+ );
44
+ }
45
+ if (!errors[fieldName]) {
46
+ errors[fieldName] = [];
47
+ }
48
+ errors[fieldName].push(message);
49
+ }
50
+ return errors;
51
+ }
52
+ };
53
+
54
+ exports.ZodAdapter = ZodAdapter;
55
+ //# sourceMappingURL=ZodAdapter.js.map
56
+ //# sourceMappingURL=ZodAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/validators/ZodAdapter.ts"],"names":[],"mappings":";;;AAuBO,IAAM,aAAN,MAAmD;AAAA,EACxD,YAAoB,MAAA,EAAsB;AAAtB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAuB;AAAA,EAE3C,MAAM,QAAA,CAAS,IAAA,EAAe,MAAA,EAAyD;AACrF,IAAA,OAAO,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,MAAM,CAAA;AAAA,EACvC;AAAA,EAEA,YAAA,CAAa,MAAe,MAAA,EAAgD;AAC1E,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AAEzC,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,IAAA;AAAA,QACT,MAAM,MAAA,CAAO;AAAA,OACf;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,eAAA,CAAgB,MAAA,CAAO,OAAO,MAAM,CAAA;AAExD,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA;AAAA,MACT;AAAA,KACF;AAAA,EACF;AAAA,EAEQ,eAAA,CAAgB,OAAiB,MAAA,EAA6C;AACpF,IAAA,MAAM,SAA2B,EAAC;AAClC,IAAA,MAAM,cAAA,GAAiB,MAAA,EAAQ,QAAA,IAAY,EAAC;AAC5C,IAAA,MAAM,gBAAA,GAAmB,MAAA,EAAQ,UAAA,IAAc,EAAC;AAEhD,IAAA,KAAA,MAAW,KAAA,IAAS,MAAM,MAAA,EAAQ;AAChC,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAChC,MAAA,MAAM,YAAY,IAAA,IAAQ,OAAA;AAG1B,MAAA,MAAM,aAAA,GAAgB,gBAAA,CAAiB,SAAS,CAAA,IAAK,SAAA;AAGrD,MAAA,MAAM,UAAA,GAAa,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,MAAM,IAAI,CAAA,CAAA;AAG7C,MAAA,IAAI,OAAA;AACJ,MAAA,IAAI,cAAA,CAAe,UAAU,CAAA,EAAG;AAC9B,QAAA,OAAA,GAAU,eAAe,UAAU,CAAA;AAAA,MACrC,CAAA,MAAA,IAAW,cAAA,CAAe,SAAS,CAAA,EAAG;AACpC,QAAA,OAAA,GAAU,eAAe,SAAS,CAAA;AAAA,MACpC,CAAA,MAAO;AAEL,QAAA,OAAA,GAAU,MAAM,OAAA,CAAQ,OAAA;AAAA,UACtB,IAAI,MAAA,CAAO,CAAA,GAAA,EAAM,SAAS,OAAO,IAAI,CAAA;AAAA,UACrC;AAAA,SACF;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAA,CAAO,SAAS,CAAA,EAAG;AACtB,QAAA,MAAA,CAAO,SAAS,IAAI,EAAC;AAAA,MACvB;AACA,MAAA,MAAA,CAAO,SAAS,CAAA,CAAE,IAAA,CAAK,OAAO,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"ZodAdapter.js","sourcesContent":["import type { ZodSchema, ZodError } from 'zod';\nimport type { ValidatorAdapter, ValidationResult, ValidationConfig, ValidationErrors } from '../../core/types';\n\n/**\n * Validator adapter for Zod schemas\n *\n * @example\n * ```typescript\n * import { z } from 'zod';\n * import { ZodAdapter } from 'next-request/zod';\n *\n * const schema = z.object({\n * email: z.string().email(),\n * name: z.string().min(2),\n * });\n *\n * class MyRequest extends FormRequest<z.infer<typeof schema>> {\n * rules() {\n * return new ZodAdapter(schema);\n * }\n * }\n * ```\n */\nexport class ZodAdapter<T> implements ValidatorAdapter<T> {\n constructor(private schema: ZodSchema<T>) {}\n\n async validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>> {\n return this.validateSync(data, config);\n }\n\n validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T> {\n const result = this.schema.safeParse(data);\n\n if (result.success) {\n return {\n success: true,\n data: result.data,\n };\n }\n\n const errors = this.formatZodErrors(result.error, config);\n\n return {\n success: false,\n errors,\n };\n }\n\n private formatZodErrors(error: ZodError, config?: ValidationConfig): ValidationErrors {\n const errors: ValidationErrors = {};\n const customMessages = config?.messages ?? {};\n const customAttributes = config?.attributes ?? {};\n\n for (const issue of error.issues) {\n const path = issue.path.join('.');\n const fieldName = path || '_root';\n\n // Get custom attribute name if available\n const attributeName = customAttributes[fieldName] ?? fieldName;\n\n // Build the message key for custom messages (e.g., \"email.email\", \"password.min\")\n const messageKey = `${fieldName}.${issue.code}`;\n\n // Check for custom message, otherwise use Zod's message with custom attribute\n let message: string;\n if (customMessages[messageKey]) {\n message = customMessages[messageKey];\n } else if (customMessages[fieldName]) {\n message = customMessages[fieldName];\n } else {\n // Replace field references in Zod's default message\n message = issue.message.replace(\n new RegExp(`\\\\b${fieldName}\\\\b`, 'gi'),\n attributeName\n );\n }\n\n if (!errors[fieldName]) {\n errors[fieldName] = [];\n }\n errors[fieldName].push(message);\n }\n\n return errors;\n }\n}\n"]}
@@ -0,0 +1,54 @@
1
+ // src/adapters/validators/ZodAdapter.ts
2
+ var ZodAdapter = class {
3
+ constructor(schema) {
4
+ this.schema = schema;
5
+ }
6
+ async validate(data, config) {
7
+ return this.validateSync(data, config);
8
+ }
9
+ validateSync(data, config) {
10
+ const result = this.schema.safeParse(data);
11
+ if (result.success) {
12
+ return {
13
+ success: true,
14
+ data: result.data
15
+ };
16
+ }
17
+ const errors = this.formatZodErrors(result.error, config);
18
+ return {
19
+ success: false,
20
+ errors
21
+ };
22
+ }
23
+ formatZodErrors(error, config) {
24
+ const errors = {};
25
+ const customMessages = config?.messages ?? {};
26
+ const customAttributes = config?.attributes ?? {};
27
+ for (const issue of error.issues) {
28
+ const path = issue.path.join(".");
29
+ const fieldName = path || "_root";
30
+ const attributeName = customAttributes[fieldName] ?? fieldName;
31
+ const messageKey = `${fieldName}.${issue.code}`;
32
+ let message;
33
+ if (customMessages[messageKey]) {
34
+ message = customMessages[messageKey];
35
+ } else if (customMessages[fieldName]) {
36
+ message = customMessages[fieldName];
37
+ } else {
38
+ message = issue.message.replace(
39
+ new RegExp(`\\b${fieldName}\\b`, "gi"),
40
+ attributeName
41
+ );
42
+ }
43
+ if (!errors[fieldName]) {
44
+ errors[fieldName] = [];
45
+ }
46
+ errors[fieldName].push(message);
47
+ }
48
+ return errors;
49
+ }
50
+ };
51
+
52
+ export { ZodAdapter };
53
+ //# sourceMappingURL=ZodAdapter.mjs.map
54
+ //# sourceMappingURL=ZodAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/validators/ZodAdapter.ts"],"names":[],"mappings":";AAuBO,IAAM,aAAN,MAAmD;AAAA,EACxD,YAAoB,MAAA,EAAsB;AAAtB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAuB;AAAA,EAE3C,MAAM,QAAA,CAAS,IAAA,EAAe,MAAA,EAAyD;AACrF,IAAA,OAAO,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,MAAM,CAAA;AAAA,EACvC;AAAA,EAEA,YAAA,CAAa,MAAe,MAAA,EAAgD;AAC1E,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AAEzC,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,IAAA;AAAA,QACT,MAAM,MAAA,CAAO;AAAA,OACf;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,eAAA,CAAgB,MAAA,CAAO,OAAO,MAAM,CAAA;AAExD,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA;AAAA,MACT;AAAA,KACF;AAAA,EACF;AAAA,EAEQ,eAAA,CAAgB,OAAiB,MAAA,EAA6C;AACpF,IAAA,MAAM,SAA2B,EAAC;AAClC,IAAA,MAAM,cAAA,GAAiB,MAAA,EAAQ,QAAA,IAAY,EAAC;AAC5C,IAAA,MAAM,gBAAA,GAAmB,MAAA,EAAQ,UAAA,IAAc,EAAC;AAEhD,IAAA,KAAA,MAAW,KAAA,IAAS,MAAM,MAAA,EAAQ;AAChC,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAChC,MAAA,MAAM,YAAY,IAAA,IAAQ,OAAA;AAG1B,MAAA,MAAM,aAAA,GAAgB,gBAAA,CAAiB,SAAS,CAAA,IAAK,SAAA;AAGrD,MAAA,MAAM,UAAA,GAAa,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,MAAM,IAAI,CAAA,CAAA;AAG7C,MAAA,IAAI,OAAA;AACJ,MAAA,IAAI,cAAA,CAAe,UAAU,CAAA,EAAG;AAC9B,QAAA,OAAA,GAAU,eAAe,UAAU,CAAA;AAAA,MACrC,CAAA,MAAA,IAAW,cAAA,CAAe,SAAS,CAAA,EAAG;AACpC,QAAA,OAAA,GAAU,eAAe,SAAS,CAAA;AAAA,MACpC,CAAA,MAAO;AAEL,QAAA,OAAA,GAAU,MAAM,OAAA,CAAQ,OAAA;AAAA,UACtB,IAAI,MAAA,CAAO,CAAA,GAAA,EAAM,SAAS,OAAO,IAAI,CAAA;AAAA,UACrC;AAAA,SACF;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAA,CAAO,SAAS,CAAA,EAAG;AACtB,QAAA,MAAA,CAAO,SAAS,IAAI,EAAC;AAAA,MACvB;AACA,MAAA,MAAA,CAAO,SAAS,CAAA,CAAE,IAAA,CAAK,OAAO,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"ZodAdapter.mjs","sourcesContent":["import type { ZodSchema, ZodError } from 'zod';\nimport type { ValidatorAdapter, ValidationResult, ValidationConfig, ValidationErrors } from '../../core/types';\n\n/**\n * Validator adapter for Zod schemas\n *\n * @example\n * ```typescript\n * import { z } from 'zod';\n * import { ZodAdapter } from 'next-request/zod';\n *\n * const schema = z.object({\n * email: z.string().email(),\n * name: z.string().min(2),\n * });\n *\n * class MyRequest extends FormRequest<z.infer<typeof schema>> {\n * rules() {\n * return new ZodAdapter(schema);\n * }\n * }\n * ```\n */\nexport class ZodAdapter<T> implements ValidatorAdapter<T> {\n constructor(private schema: ZodSchema<T>) {}\n\n async validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>> {\n return this.validateSync(data, config);\n }\n\n validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T> {\n const result = this.schema.safeParse(data);\n\n if (result.success) {\n return {\n success: true,\n data: result.data,\n };\n }\n\n const errors = this.formatZodErrors(result.error, config);\n\n return {\n success: false,\n errors,\n };\n }\n\n private formatZodErrors(error: ZodError, config?: ValidationConfig): ValidationErrors {\n const errors: ValidationErrors = {};\n const customMessages = config?.messages ?? {};\n const customAttributes = config?.attributes ?? {};\n\n for (const issue of error.issues) {\n const path = issue.path.join('.');\n const fieldName = path || '_root';\n\n // Get custom attribute name if available\n const attributeName = customAttributes[fieldName] ?? fieldName;\n\n // Build the message key for custom messages (e.g., \"email.email\", \"password.min\")\n const messageKey = `${fieldName}.${issue.code}`;\n\n // Check for custom message, otherwise use Zod's message with custom attribute\n let message: string;\n if (customMessages[messageKey]) {\n message = customMessages[messageKey];\n } else if (customMessages[fieldName]) {\n message = customMessages[fieldName];\n } else {\n // Replace field references in Zod's default message\n message = issue.message.replace(\n new RegExp(`\\\\b${fieldName}\\\\b`, 'gi'),\n attributeName\n );\n }\n\n if (!errors[fieldName]) {\n errors[fieldName] = [];\n }\n errors[fieldName].push(message);\n }\n\n return errors;\n }\n}\n"]}