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 +294 -0
- package/dist/ZodAdapter-D7D3Sc-a.d.mts +95 -0
- package/dist/ZodAdapter-D7D3Sc-a.d.ts +95 -0
- package/dist/adapters/validators/ZodAdapter.d.mts +3 -0
- package/dist/adapters/validators/ZodAdapter.d.ts +3 -0
- package/dist/adapters/validators/ZodAdapter.js +56 -0
- package/dist/adapters/validators/ZodAdapter.js.map +1 -0
- package/dist/adapters/validators/ZodAdapter.mjs +54 -0
- package/dist/adapters/validators/ZodAdapter.mjs.map +1 -0
- package/dist/index.d.mts +365 -0
- package/dist/index.d.ts +365 -0
- package/dist/index.js +497 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +486 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
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
|
+
[](https://www.npmjs.com/package/next-request)
|
|
6
|
+
[](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,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"]}
|