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.
@@ -0,0 +1,365 @@
1
+ import { NextApiRequest, NextApiResponse } from 'next';
2
+ import { S as SupportedRequest, V as ValidatorAdapter, a as ValidationErrors } from './ZodAdapter-D7D3Sc-a.js';
3
+ export { A as AppRouterHandler, P as PagesRouterHandler, R as RequestData, c as ValidationConfig, b as ValidationResult, Z as ZodAdapter, i as isAppRouterRequest, d as isPagesRouterRequest } from './ZodAdapter-D7D3Sc-a.js';
4
+ import 'zod';
5
+
6
+ /**
7
+ * Abstract base class for form request validation.
8
+ * Inspired by Laravel's Form Request pattern.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { FormRequest, ZodAdapter } from 'next-request';
13
+ * import { z } from 'zod';
14
+ *
15
+ * const schema = z.object({
16
+ * email: z.string().email(),
17
+ * password: z.string().min(8),
18
+ * });
19
+ *
20
+ * export class LoginRequest extends FormRequest<z.infer<typeof schema>> {
21
+ * rules() {
22
+ * return new ZodAdapter(schema);
23
+ * }
24
+ *
25
+ * async authorize() {
26
+ * return true; // Allow all requests
27
+ * }
28
+ *
29
+ * beforeValidation() {
30
+ * this.body.email = this.body.email?.toLowerCase().trim();
31
+ * }
32
+ * }
33
+ * ```
34
+ */
35
+ declare abstract class FormRequest<TValidated = unknown> {
36
+ /**
37
+ * The original request object
38
+ */
39
+ protected request: SupportedRequest;
40
+ /**
41
+ * Parsed request body (mutable for beforeValidation hook)
42
+ */
43
+ protected body: Record<string, unknown>;
44
+ /**
45
+ * Query parameters from the URL
46
+ */
47
+ protected query: Record<string, string | string[] | undefined>;
48
+ /**
49
+ * Route parameters (e.g., /users/[id])
50
+ */
51
+ protected params: Record<string, string>;
52
+ /**
53
+ * Request headers
54
+ */
55
+ protected headers: Record<string, string | string[] | undefined>;
56
+ /**
57
+ * Validated data (populated after successful validation)
58
+ */
59
+ private _validated;
60
+ /**
61
+ * Partial validated data (fields that passed validation)
62
+ */
63
+ private _safe;
64
+ /**
65
+ * Define the validation rules for this request.
66
+ * Return a ValidatorAdapter instance (e.g., ZodAdapter, YupAdapter).
67
+ */
68
+ abstract rules(): ValidatorAdapter<TValidated>;
69
+ /**
70
+ * Determine if the user is authorized to make this request.
71
+ * Override this method to add authorization logic.
72
+ *
73
+ * @returns true if authorized, false otherwise
74
+ */
75
+ authorize(): boolean | Promise<boolean>;
76
+ /**
77
+ * Called before validation runs.
78
+ * Use this to normalize or transform input data.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * beforeValidation() {
83
+ * this.body.email = this.body.email?.toLowerCase().trim();
84
+ * }
85
+ * ```
86
+ */
87
+ beforeValidation(): void | Promise<void>;
88
+ /**
89
+ * Called after validation succeeds.
90
+ * Use this for logging, analytics, or post-processing.
91
+ *
92
+ * @param data The validated data
93
+ */
94
+ afterValidation(_data: TValidated): void | Promise<void>;
95
+ /**
96
+ * Called when validation fails.
97
+ * Use this for logging or custom error handling.
98
+ *
99
+ * @param errors The validation errors
100
+ */
101
+ onValidationFailed(_errors: ValidationErrors): void | Promise<void>;
102
+ /**
103
+ * Called when authorization fails.
104
+ * Use this for logging or custom error handling.
105
+ */
106
+ onAuthorizationFailed(): void | Promise<void>;
107
+ /**
108
+ * Custom validation error messages.
109
+ * Keys can be field names or "field.rule" patterns.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * messages() {
114
+ * return {
115
+ * 'email.email': 'Please provide a valid email address',
116
+ * 'password.min': 'Password must be at least 8 characters',
117
+ * };
118
+ * }
119
+ * ```
120
+ */
121
+ messages(): Record<string, string>;
122
+ /**
123
+ * Custom attribute names for error messages.
124
+ * Used to replace field names in error messages.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * attributes() {
129
+ * return {
130
+ * 'email': 'email address',
131
+ * 'dob': 'date of birth',
132
+ * };
133
+ * }
134
+ * ```
135
+ */
136
+ attributes(): Record<string, string>;
137
+ /**
138
+ * Create a FormRequest instance from an App Router Request.
139
+ *
140
+ * @param request The incoming Request object
141
+ * @param params Route parameters (from params in route handler)
142
+ */
143
+ static fromAppRouter<T extends FormRequest>(this: new () => T, request: Request, params?: Record<string, string>): Promise<T>;
144
+ /**
145
+ * Create a FormRequest instance from a Pages Router NextApiRequest.
146
+ *
147
+ * @param request The incoming NextApiRequest object
148
+ * @param params Route parameters
149
+ */
150
+ static fromPagesRouter<T extends FormRequest>(this: new () => T, request: NextApiRequest, params?: Record<string, string>): Promise<T>;
151
+ /**
152
+ * Run validation and return the validated data.
153
+ * Throws ValidationError if validation fails.
154
+ * Throws AuthorizationError if authorization fails.
155
+ *
156
+ * @returns The validated data with full type inference
157
+ */
158
+ validate(): Promise<TValidated>;
159
+ /**
160
+ * Get the validated data (after calling validate()).
161
+ * Throws if validate() hasn't been called successfully.
162
+ */
163
+ validated(): TValidated;
164
+ /**
165
+ * Get only the fields that passed validation.
166
+ * Safe to call even if validation hasn't completed.
167
+ */
168
+ safe(): Partial<TValidated>;
169
+ /**
170
+ * Get raw input data (body merged with query for GET requests).
171
+ */
172
+ all(): Record<string, unknown>;
173
+ /**
174
+ * Get a specific input value.
175
+ */
176
+ input<T = unknown>(key: string, defaultValue?: T): T | undefined;
177
+ /**
178
+ * Check if input has a specific key.
179
+ */
180
+ has(key: string): boolean;
181
+ /**
182
+ * Get only specified keys from input.
183
+ */
184
+ only<K extends string>(...keys: K[]): Pick<Record<string, unknown>, K>;
185
+ /**
186
+ * Get all input except specified keys.
187
+ */
188
+ except(...keys: string[]): Record<string, unknown>;
189
+ /**
190
+ * Get the original request object.
191
+ */
192
+ getRequest(): SupportedRequest;
193
+ /**
194
+ * Check if this is an App Router request.
195
+ */
196
+ isAppRouter(): boolean;
197
+ /**
198
+ * Get a header value.
199
+ */
200
+ header(name: string): string | string[] | undefined;
201
+ /**
202
+ * Get a route parameter.
203
+ */
204
+ param(name: string): string | undefined;
205
+ /**
206
+ * Get the data that will be validated.
207
+ * Override this to customize what data is passed to the validator.
208
+ */
209
+ protected getDataForValidation(): unknown;
210
+ }
211
+
212
+ /**
213
+ * Error thrown when request validation fails
214
+ */
215
+ declare class ValidationError extends Error {
216
+ readonly errors: ValidationErrors;
217
+ constructor(errors: ValidationErrors);
218
+ /**
219
+ * Get all error messages as a flat array
220
+ */
221
+ getAllMessages(): string[];
222
+ /**
223
+ * Get errors for a specific field
224
+ */
225
+ getFieldErrors(field: string): string[];
226
+ /**
227
+ * Check if a specific field has errors
228
+ */
229
+ hasFieldError(field: string): boolean;
230
+ /**
231
+ * Convert to JSON-serializable object
232
+ */
233
+ toJSON(): {
234
+ name: string;
235
+ message: string;
236
+ errors: ValidationErrors;
237
+ };
238
+ }
239
+ /**
240
+ * Error thrown when request authorization fails
241
+ */
242
+ declare class AuthorizationError extends Error {
243
+ constructor(message?: string);
244
+ toJSON(): {
245
+ name: string;
246
+ message: string;
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Type for FormRequest constructor
252
+ */
253
+ type FormRequestClass<TValidated> = {
254
+ new (): FormRequest<TValidated>;
255
+ fromAppRouter(request: Request, params?: Record<string, string>): Promise<FormRequest<TValidated>>;
256
+ fromPagesRouter(request: NextApiRequest, params?: Record<string, string>): Promise<FormRequest<TValidated>>;
257
+ };
258
+ /**
259
+ * Handler function for App Router
260
+ */
261
+ type AppRouterHandler<TValidated> = (validated: TValidated, request: Request, formRequest: FormRequest<TValidated>) => Response | Promise<Response>;
262
+ /**
263
+ * Handler function for Pages Router
264
+ */
265
+ type PagesRouterHandler<TValidated> = (validated: TValidated, req: NextApiRequest, res: NextApiResponse, formRequest: FormRequest<TValidated>) => void | Promise<void>;
266
+ /**
267
+ * Context parameter for App Router (Next.js 13+)
268
+ */
269
+ interface AppRouterContext {
270
+ params?: Record<string, string> | Promise<Record<string, string>>;
271
+ }
272
+ /**
273
+ * Wrap an App Router route handler with form request validation.
274
+ *
275
+ * The handler receives validated data and only executes if:
276
+ * 1. Authorization passes (authorize() returns true)
277
+ * 2. Validation passes (rules() validates successfully)
278
+ *
279
+ * Errors are thrown, not auto-handled - catch ValidationError and
280
+ * AuthorizationError in your handler or error boundary.
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * // app/api/users/route.ts
285
+ * import { withRequest } from 'next-request';
286
+ * import { CreateUserRequest } from '@/requests/CreateUserRequest';
287
+ *
288
+ * export const POST = withRequest(CreateUserRequest, async (data, request) => {
289
+ * const user = await db.users.create({ data });
290
+ * return Response.json({ user }, { status: 201 });
291
+ * });
292
+ * ```
293
+ */
294
+ declare function withRequest<TValidated>(RequestClass: FormRequestClass<TValidated>, handler: AppRouterHandler<TValidated>): (request: Request, context?: AppRouterContext) => Promise<Response>;
295
+ /**
296
+ * Wrap a Pages Router API handler with form request validation.
297
+ *
298
+ * The handler receives validated data and only executes if:
299
+ * 1. Authorization passes (authorize() returns true)
300
+ * 2. Validation passes (rules() validates successfully)
301
+ *
302
+ * Errors are thrown, not auto-handled - catch ValidationError and
303
+ * AuthorizationError in your handler.
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * // pages/api/users.ts
308
+ * import { withApiRequest } from 'next-request';
309
+ * import { CreateUserRequest } from '@/requests/CreateUserRequest';
310
+ *
311
+ * export default withApiRequest(CreateUserRequest, async (data, req, res) => {
312
+ * const user = await db.users.create({ data });
313
+ * res.status(201).json({ user });
314
+ * });
315
+ * ```
316
+ */
317
+ declare function withApiRequest<TValidated>(RequestClass: FormRequestClass<TValidated>, handler: PagesRouterHandler<TValidated>): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
318
+ /**
319
+ * Create a wrapper with custom error handling for App Router.
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * import { createAppRouterWrapper, ValidationError, AuthorizationError } from 'next-request';
324
+ *
325
+ * const withValidation = createAppRouterWrapper({
326
+ * onValidationError: (error) => Response.json({ errors: error.errors }, { status: 422 }),
327
+ * onAuthorizationError: () => Response.json({ message: 'Forbidden' }, { status: 403 }),
328
+ * });
329
+ *
330
+ * export const POST = withValidation(CreateUserRequest, async (data) => {
331
+ * const user = await db.users.create({ data });
332
+ * return Response.json({ user }, { status: 201 });
333
+ * });
334
+ * ```
335
+ */
336
+ declare function createAppRouterWrapper(options: {
337
+ onValidationError?: (error: ValidationError) => Response;
338
+ onAuthorizationError?: (error: AuthorizationError) => Response;
339
+ onError?: (error: unknown) => Response;
340
+ }): <TValidated>(RequestClass: FormRequestClass<TValidated>, handler: AppRouterHandler<TValidated>) => (request: Request, context?: AppRouterContext) => Promise<Response>;
341
+ /**
342
+ * Create a wrapper with custom error handling for Pages Router.
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * import { createPagesRouterWrapper, ValidationError, AuthorizationError } from 'next-request';
347
+ *
348
+ * const withValidation = createPagesRouterWrapper({
349
+ * onValidationError: (error, req, res) => res.status(422).json({ errors: error.errors }),
350
+ * onAuthorizationError: (error, req, res) => res.status(403).json({ message: 'Forbidden' }),
351
+ * });
352
+ *
353
+ * export default withValidation(CreateUserRequest, async (data, req, res) => {
354
+ * const user = await db.users.create({ data });
355
+ * res.status(201).json({ user });
356
+ * });
357
+ * ```
358
+ */
359
+ declare function createPagesRouterWrapper(options: {
360
+ onValidationError?: (error: ValidationError, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
361
+ onAuthorizationError?: (error: AuthorizationError, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
362
+ onError?: (error: unknown, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
363
+ }): <TValidated>(RequestClass: FormRequestClass<TValidated>, handler: PagesRouterHandler<TValidated>) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
364
+
365
+ export { AuthorizationError, FormRequest, SupportedRequest, ValidationError, ValidationErrors, ValidatorAdapter, createAppRouterWrapper, createPagesRouterWrapper, withApiRequest, withRequest };