next-form-request 0.1.1 → 2.0.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/dist/index.d.mts CHANGED
@@ -1,7 +1,229 @@
1
1
  import { NextApiRequest, NextApiResponse } from 'next';
2
- import { S as SupportedRequest, V as ValidatorAdapter, a as ValidationErrors } from './ZodAdapter-D7D3Sc-a.mjs';
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.mjs';
4
- import 'zod';
2
+ import { S as SupportedRequest, V as ValidatorAdapter, a as ValidationErrors, b as ValidationConfig, c as ValidationResult } from './ZodAdapter-Ni7VwvnR.mjs';
3
+ export { A as AppRouterHandler, P as PagesRouterHandler, R as RequestData, Z as ZodAdapter, i as isAppRouterRequest, d as isPagesRouterRequest } from './ZodAdapter-Ni7VwvnR.mjs';
4
+ import { z } from 'zod';
5
+
6
+ /**
7
+ * Rate limit configuration
8
+ */
9
+ interface RateLimitConfig {
10
+ /**
11
+ * Maximum number of requests allowed in the time window
12
+ */
13
+ maxAttempts: number;
14
+ /**
15
+ * Time window in milliseconds
16
+ */
17
+ windowMs: number;
18
+ /**
19
+ * Function to generate the rate limit key from the request
20
+ * Common options: IP address, user ID, API key, etc.
21
+ * @default Uses IP address
22
+ */
23
+ key?: (request: Request | NextApiRequest) => string | Promise<string>;
24
+ /**
25
+ * Custom store for rate limit data
26
+ * Defaults to in-memory store (not suitable for production with multiple instances)
27
+ */
28
+ store?: RateLimitStore;
29
+ /**
30
+ * Whether to skip rate limiting for certain requests
31
+ */
32
+ skip?: (request: Request | NextApiRequest) => boolean | Promise<boolean>;
33
+ /**
34
+ * Custom error message when rate limited
35
+ */
36
+ message?: string;
37
+ }
38
+ /**
39
+ * Rate limit state for a specific key
40
+ */
41
+ interface RateLimitState {
42
+ /** Number of requests made in the current window */
43
+ count: number;
44
+ /** Timestamp when the window resets */
45
+ resetAt: number;
46
+ }
47
+ /**
48
+ * Rate limit check result
49
+ */
50
+ interface RateLimitResult {
51
+ /** Whether the request is allowed */
52
+ allowed: boolean;
53
+ /** Number of requests remaining in the current window */
54
+ remaining: number;
55
+ /** Total limit */
56
+ limit: number;
57
+ /** Timestamp when the window resets */
58
+ resetAt: number;
59
+ /** Number of seconds until reset */
60
+ retryAfter: number;
61
+ }
62
+ /**
63
+ * Interface for rate limit storage
64
+ */
65
+ interface RateLimitStore {
66
+ /** Get the current state for a key */
67
+ get(key: string): Promise<RateLimitState | null>;
68
+ /** Set the state for a key */
69
+ set(key: string, state: RateLimitState, ttlMs: number): Promise<void>;
70
+ /** Increment the count for a key, returns new state */
71
+ increment(key: string, windowMs: number): Promise<RateLimitState>;
72
+ }
73
+ /**
74
+ * In-memory rate limit store (for development/testing only)
75
+ * In production, use Redis or another distributed store
76
+ */
77
+ declare class MemoryRateLimitStore implements RateLimitStore {
78
+ private store;
79
+ get(key: string): Promise<RateLimitState | null>;
80
+ set(key: string, state: RateLimitState, _ttlMs: number): Promise<void>;
81
+ increment(key: string, windowMs: number): Promise<RateLimitState>;
82
+ private cleanup;
83
+ }
84
+ /**
85
+ * Set a custom default rate limit store
86
+ * Useful for setting up Redis or other distributed stores
87
+ */
88
+ declare function setDefaultRateLimitStore(store: RateLimitStore): void;
89
+ /**
90
+ * Check rate limit for a request
91
+ */
92
+ declare function checkRateLimit(request: Request | NextApiRequest, config: RateLimitConfig): Promise<RateLimitResult>;
93
+ /**
94
+ * Rate limit error thrown when a request exceeds the rate limit
95
+ */
96
+ declare class RateLimitError extends Error {
97
+ readonly retryAfter: number;
98
+ readonly remaining: number;
99
+ readonly limit: number;
100
+ readonly resetAt: number;
101
+ constructor(result: RateLimitResult, message?: string);
102
+ /**
103
+ * Get headers to send with the rate limit response
104
+ */
105
+ getHeaders(): Record<string, string>;
106
+ }
107
+ /**
108
+ * Create rate limit configuration with sensible defaults
109
+ */
110
+ declare function rateLimit(config: Partial<RateLimitConfig> & {
111
+ maxAttempts: number;
112
+ windowMs: number;
113
+ }): RateLimitConfig;
114
+
115
+ /**
116
+ * Coercion utilities for automatically converting string values from form data
117
+ * to their appropriate JavaScript types.
118
+ */
119
+ /**
120
+ * Options for coercion
121
+ */
122
+ interface CoercionOptions {
123
+ /** Coerce "true"/"false" strings to booleans */
124
+ booleans?: boolean;
125
+ /** Coerce numeric strings to numbers */
126
+ numbers?: boolean;
127
+ /** Coerce ISO date strings to Date objects */
128
+ dates?: boolean;
129
+ /** Coerce "null" string to null */
130
+ nulls?: boolean;
131
+ /** Coerce empty strings to undefined */
132
+ emptyStrings?: boolean;
133
+ /** Coerce JSON strings to objects/arrays */
134
+ json?: boolean;
135
+ /** Custom coercion functions for specific fields */
136
+ fields?: Record<string, (value: unknown) => unknown>;
137
+ }
138
+ /**
139
+ * Coerce form data values to their appropriate JavaScript types.
140
+ *
141
+ * Form submissions typically send everything as strings. This function
142
+ * automatically converts common patterns:
143
+ * - "true" / "false" → boolean
144
+ * - "123" / "45.67" → number
145
+ * - "2024-01-01" → Date
146
+ * - "null" → null
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const formData = {
151
+ * name: "John",
152
+ * age: "25",
153
+ * active: "true",
154
+ * createdAt: "2024-01-01",
155
+ * };
156
+ *
157
+ * const coerced = coerceFormData(formData);
158
+ * // {
159
+ * // name: "John",
160
+ * // age: 25,
161
+ * // active: true,
162
+ * // createdAt: Date("2024-01-01"),
163
+ * // }
164
+ * ```
165
+ */
166
+ declare function coerceFormData<T extends Record<string, unknown>>(data: T, options?: CoercionOptions): T;
167
+ /**
168
+ * Create a Zod preprocessor for automatic coercion
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * import { z } from 'zod';
173
+ * import { zodCoerce } from 'next-request';
174
+ *
175
+ * const schema = z.preprocess(
176
+ * zodCoerce(),
177
+ * z.object({
178
+ * name: z.string(),
179
+ * age: z.number(),
180
+ * active: z.boolean(),
181
+ * })
182
+ * );
183
+ * ```
184
+ */
185
+ declare function zodCoerce(options?: CoercionOptions): (data: unknown) => unknown;
186
+ /**
187
+ * Common coercion presets
188
+ */
189
+ declare const coercionPresets: {
190
+ /** Coerce all supported types */
191
+ all: {
192
+ booleans: true;
193
+ numbers: true;
194
+ dates: true;
195
+ nulls: true;
196
+ emptyStrings: true;
197
+ json: true;
198
+ };
199
+ /** Only coerce booleans and numbers (safest) */
200
+ safe: {
201
+ booleans: true;
202
+ numbers: true;
203
+ dates: false;
204
+ nulls: false;
205
+ emptyStrings: false;
206
+ json: false;
207
+ };
208
+ /** Coerce booleans, numbers, and dates */
209
+ standard: {
210
+ booleans: true;
211
+ numbers: true;
212
+ dates: true;
213
+ nulls: false;
214
+ emptyStrings: false;
215
+ json: false;
216
+ };
217
+ /** No coercion */
218
+ none: {
219
+ booleans: false;
220
+ numbers: false;
221
+ dates: false;
222
+ nulls: false;
223
+ emptyStrings: false;
224
+ json: false;
225
+ };
226
+ };
5
227
 
6
228
  /**
7
229
  * Abstract base class for form request validation.
@@ -73,6 +295,38 @@ declare abstract class FormRequest<TValidated = unknown> {
73
295
  * @returns true if authorized, false otherwise
74
296
  */
75
297
  authorize(): boolean | Promise<boolean>;
298
+ /**
299
+ * Define rate limiting for this request.
300
+ * Override this method to add rate limiting.
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * rateLimit() {
305
+ * return {
306
+ * maxAttempts: 5,
307
+ * windowMs: 60000, // 1 minute
308
+ * key: (req) => this.input('email') || 'anonymous',
309
+ * };
310
+ * }
311
+ * ```
312
+ */
313
+ rateLimit(): RateLimitConfig | null;
314
+ /**
315
+ * Define coercion options for form data.
316
+ * Override this method to enable automatic type coercion.
317
+ *
318
+ * @example
319
+ * ```typescript
320
+ * coercion() {
321
+ * return {
322
+ * booleans: true, // "true" → true
323
+ * numbers: true, // "123" → 123
324
+ * dates: true, // "2024-01-01" → Date
325
+ * };
326
+ * }
327
+ * ```
328
+ */
329
+ coercion(): CoercionOptions | null;
76
330
  /**
77
331
  * Called before validation runs.
78
332
  * Use this to normalize or transform input data.
@@ -152,6 +406,7 @@ declare abstract class FormRequest<TValidated = unknown> {
152
406
  * Run validation and return the validated data.
153
407
  * Throws ValidationError if validation fails.
154
408
  * Throws AuthorizationError if authorization fails.
409
+ * Throws RateLimitError if rate limit is exceeded.
155
410
  *
156
411
  * @returns The validated data with full type inference
157
412
  */
@@ -248,13 +503,13 @@ declare class AuthorizationError extends Error {
248
503
  }
249
504
 
250
505
  /**
251
- * Type for FormRequest constructor
506
+ * Extract the validated type from a FormRequest constructor
507
+ * This works by:
508
+ * 1. Getting the instance type from the constructor: InstanceType<T>
509
+ * 2. Checking if that instance extends FormRequest<infer V>
510
+ * 3. Extracting V which is the TValidated generic parameter
252
511
  */
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
- };
512
+ type InferValidatedType$1<T extends new () => any> = InstanceType<T> extends FormRequest<infer V> ? V : never;
258
513
  /**
259
514
  * Handler function for App Router
260
515
  */
@@ -266,7 +521,7 @@ type PagesRouterHandler<TValidated> = (validated: TValidated, req: NextApiReques
266
521
  /**
267
522
  * Context parameter for App Router (Next.js 13+)
268
523
  */
269
- interface AppRouterContext {
524
+ interface AppRouterContext$1 {
270
525
  params?: Record<string, string> | Promise<Record<string, string>>;
271
526
  }
272
527
  /**
@@ -291,7 +546,9 @@ interface AppRouterContext {
291
546
  * });
292
547
  * ```
293
548
  */
294
- declare function withRequest<TValidated>(RequestClass: FormRequestClass<TValidated>, handler: AppRouterHandler<TValidated>): (request: Request, context?: AppRouterContext) => Promise<Response>;
549
+ declare function withRequest<T extends new () => FormRequest<any>>(RequestClass: T & {
550
+ fromAppRouter(request: Request, params?: Record<string, string>): Promise<InstanceType<T>>;
551
+ }, handler: AppRouterHandler<InferValidatedType$1<T>>): (request: Request, context?: AppRouterContext$1) => Promise<Response>;
295
552
  /**
296
553
  * Wrap a Pages Router API handler with form request validation.
297
554
  *
@@ -314,7 +571,9 @@ declare function withRequest<TValidated>(RequestClass: FormRequestClass<TValidat
314
571
  * });
315
572
  * ```
316
573
  */
317
- declare function withApiRequest<TValidated>(RequestClass: FormRequestClass<TValidated>, handler: PagesRouterHandler<TValidated>): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
574
+ declare function withApiRequest<T extends new () => FormRequest<any>>(RequestClass: T & {
575
+ fromPagesRouter(request: NextApiRequest, params?: Record<string, string>): Promise<InstanceType<T>>;
576
+ }, handler: PagesRouterHandler<InferValidatedType$1<T>>): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
318
577
  /**
319
578
  * Create a wrapper with custom error handling for App Router.
320
579
  *
@@ -337,7 +596,9 @@ declare function createAppRouterWrapper(options: {
337
596
  onValidationError?: (error: ValidationError) => Response;
338
597
  onAuthorizationError?: (error: AuthorizationError) => Response;
339
598
  onError?: (error: unknown) => Response;
340
- }): <TValidated>(RequestClass: FormRequestClass<TValidated>, handler: AppRouterHandler<TValidated>) => (request: Request, context?: AppRouterContext) => Promise<Response>;
599
+ }): <T extends new () => FormRequest<any>>(RequestClass: T & {
600
+ fromAppRouter(request: Request, params?: Record<string, string>): Promise<InstanceType<T>>;
601
+ }, handler: AppRouterHandler<InferValidatedType$1<T>>) => (request: Request, context?: AppRouterContext$1) => Promise<Response>;
341
602
  /**
342
603
  * Create a wrapper with custom error handling for Pages Router.
343
604
  *
@@ -360,6 +621,579 @@ declare function createPagesRouterWrapper(options: {
360
621
  onValidationError?: (error: ValidationError, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
361
622
  onAuthorizationError?: (error: AuthorizationError, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
362
623
  onError?: (error: unknown, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
363
- }): <TValidated>(RequestClass: FormRequestClass<TValidated>, handler: PagesRouterHandler<TValidated>) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
624
+ }): <T extends new () => FormRequest<any>>(RequestClass: T & {
625
+ fromPagesRouter(request: NextApiRequest, params?: Record<string, string>): Promise<InstanceType<T>>;
626
+ }, handler: PagesRouterHandler<InferValidatedType$1<T>>) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
627
+
628
+ /**
629
+ * Infer the validated type from a ValidationAdapter
630
+ * This extracts the generic type parameter T from ValidatorAdapter<T>
631
+ */
632
+ type InferValidatedType<T> = T extends ValidatorAdapter<infer V> ? V : never;
633
+ /**
634
+ * Handler function for App Router with schema validation
635
+ */
636
+ type SchemaHandler<TValidated> = (data: TValidated, request: Request) => Response | Promise<Response>;
637
+ /**
638
+ * Handler function for Pages Router with schema validation
639
+ */
640
+ type ApiSchemaHandler<TValidated> = (data: TValidated, req: NextApiRequest, res: NextApiResponse) => void | Promise<void>;
641
+ /**
642
+ * Context parameter for App Router (Next.js 13+)
643
+ */
644
+ interface AppRouterContext {
645
+ params?: Record<string, string> | Promise<Record<string, string>>;
646
+ }
647
+ /**
648
+ * Wrap an App Router route handler with simple schema validation.
649
+ *
650
+ * This is a lightweight alternative to withRequest for cases where you don't need
651
+ * hooks or authorization - just schema validation.
652
+ *
653
+ * The handler receives validated data and only executes if validation passes.
654
+ * ValidationError is thrown on validation failure.
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * // app/api/users/route.ts
659
+ * import { withSchema, ZodAdapter } from 'next-request';
660
+ * import { z } from 'zod';
661
+ *
662
+ * const userSchema = z.object({
663
+ * name: z.string().min(2),
664
+ * email: z.string().email(),
665
+ * });
666
+ *
667
+ * export const POST = withSchema(new ZodAdapter(userSchema), async (data) => {
668
+ * // data is typed as { name: string; email: string }
669
+ * const user = await db.users.create({ data });
670
+ * return Response.json({ user }, { status: 201 });
671
+ * });
672
+ * ```
673
+ */
674
+ declare function withSchema<T extends ValidatorAdapter<any>>(adapter: T, handler: SchemaHandler<InferValidatedType<T>>): (request: Request, context?: AppRouterContext) => Promise<Response>;
675
+ /**
676
+ * Wrap a Pages Router API handler with simple schema validation.
677
+ *
678
+ * This is a lightweight alternative to withApiRequest for cases where you don't need
679
+ * hooks or authorization - just schema validation.
680
+ *
681
+ * The handler receives validated data and only executes if validation passes.
682
+ * ValidationError is thrown on validation failure.
683
+ *
684
+ * @example
685
+ * ```typescript
686
+ * // pages/api/users.ts
687
+ * import { withApiSchema, ZodAdapter } from 'next-request';
688
+ * import { z } from 'zod';
689
+ *
690
+ * const userSchema = z.object({
691
+ * name: z.string().min(2),
692
+ * email: z.string().email(),
693
+ * });
694
+ *
695
+ * export default withApiSchema(new ZodAdapter(userSchema), async (data, req, res) => {
696
+ * // data is typed as { name: string; email: string }
697
+ * const user = await db.users.create({ data });
698
+ * res.status(201).json({ user });
699
+ * });
700
+ * ```
701
+ */
702
+ declare function withApiSchema<T extends ValidatorAdapter<any>>(adapter: T, handler: ApiSchemaHandler<InferValidatedType<T>>): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
703
+
704
+ interface YupSchema<T> {
705
+ validate(data: unknown, options?: {
706
+ abortEarly?: boolean;
707
+ stripUnknown?: boolean;
708
+ }): Promise<T>;
709
+ validateSync(data: unknown, options?: {
710
+ abortEarly?: boolean;
711
+ stripUnknown?: boolean;
712
+ }): T;
713
+ }
714
+ /**
715
+ * Validator adapter for Yup schemas
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * import * as yup from 'yup';
720
+ * import { YupAdapter } from 'next-request';
721
+ *
722
+ * const schema = yup.object({
723
+ * email: yup.string().email().required(),
724
+ * name: yup.string().min(2).required(),
725
+ * });
726
+ *
727
+ * class MyRequest extends FormRequest<yup.InferType<typeof schema>> {
728
+ * rules() {
729
+ * return new YupAdapter(schema);
730
+ * }
731
+ * }
732
+ * ```
733
+ */
734
+ declare class YupAdapter<T> implements ValidatorAdapter<T> {
735
+ private schema;
736
+ constructor(schema: YupSchema<T>);
737
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
738
+ validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T>;
739
+ private isYupValidationError;
740
+ private formatYupErrors;
741
+ }
742
+
743
+ interface ValibotIssue {
744
+ path?: Array<{
745
+ key: string | number;
746
+ } | string | number>;
747
+ message?: string;
748
+ type?: string;
749
+ }
750
+ interface SafeParseResult<T> {
751
+ success: boolean;
752
+ output?: T;
753
+ issues?: ValibotIssue[];
754
+ }
755
+ interface ValibotSchema<TOutput = unknown> {
756
+ _output?: TOutput;
757
+ }
758
+ /**
759
+ * Validator adapter for Valibot schemas.
760
+ *
761
+ * Since valibot is an optional peer dependency, you must pass the safeParse function
762
+ * from your valibot installation.
763
+ *
764
+ * @example
765
+ * ```typescript
766
+ * import * as v from 'valibot';
767
+ * import { ValibotAdapter } from 'next-request';
768
+ *
769
+ * const schema = v.object({
770
+ * email: v.pipe(v.string(), v.email()),
771
+ * name: v.pipe(v.string(), v.minLength(2)),
772
+ * });
773
+ *
774
+ * class MyRequest extends FormRequest<v.InferOutput<typeof schema>> {
775
+ * rules() {
776
+ * return new ValibotAdapter(schema, v.safeParse);
777
+ * }
778
+ * }
779
+ * ```
780
+ */
781
+ declare class ValibotAdapter<T> implements ValidatorAdapter<T> {
782
+ private schema;
783
+ private safeParseFunc;
784
+ constructor(schema: ValibotSchema<T>, safeParse: (schema: ValibotSchema<T>, data: unknown) => SafeParseResult<T>);
785
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
786
+ validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T>;
787
+ private formatValibotErrors;
788
+ }
789
+
790
+ interface ArkTypeSchema<T = unknown> {
791
+ (data: unknown): T | ArkTypeErrors;
792
+ }
793
+ interface ArkTypeErrors {
794
+ summary: string;
795
+ errors?: Array<{
796
+ path: string[];
797
+ message: string;
798
+ code?: string;
799
+ }>;
800
+ [Symbol.iterator]?: () => Iterator<{
801
+ path: string[];
802
+ message: string;
803
+ }>;
804
+ }
805
+ /**
806
+ * Validator adapter for ArkType schemas
807
+ *
808
+ * @example
809
+ * ```typescript
810
+ * import { type } from 'arktype';
811
+ * import { ArkTypeAdapter } from 'next-request';
812
+ *
813
+ * const schema = type({
814
+ * email: 'string.email',
815
+ * name: 'string >= 2',
816
+ * });
817
+ *
818
+ * class MyRequest extends FormRequest<typeof schema.infer> {
819
+ * rules() {
820
+ * return new ArkTypeAdapter(schema);
821
+ * }
822
+ * }
823
+ * ```
824
+ */
825
+ declare class ArkTypeAdapter<T> implements ValidatorAdapter<T> {
826
+ private schema;
827
+ constructor(schema: ArkTypeSchema<T>);
828
+ validate(data: unknown, config?: ValidationConfig): Promise<ValidationResult<T>>;
829
+ validateSync(data: unknown, config?: ValidationConfig): ValidationResult<T>;
830
+ private formatArkTypeErrors;
831
+ }
832
+
833
+ /**
834
+ * Options for file validation
835
+ */
836
+ interface FormFileOptions {
837
+ /**
838
+ * Maximum file size. Can be a number (bytes) or a string like "5mb"
839
+ */
840
+ maxSize?: string | number;
841
+ /**
842
+ * Minimum file size. Can be a number (bytes) or a string like "1kb"
843
+ */
844
+ minSize?: string | number;
845
+ /**
846
+ * Allowed MIME types. Supports wildcards like "image/*"
847
+ * @example ['image/*', 'application/pdf']
848
+ */
849
+ types?: string[];
850
+ /**
851
+ * Allowed file extensions (without the dot)
852
+ * @example ['jpg', 'png', 'pdf']
853
+ */
854
+ extensions?: string[];
855
+ /**
856
+ * Whether the file is required
857
+ * @default true
858
+ */
859
+ required?: boolean;
860
+ }
861
+ /**
862
+ * Validated file data returned after successful validation
863
+ */
864
+ interface ValidatedFile {
865
+ /** Original file name */
866
+ name: string;
867
+ /** File size in bytes */
868
+ size: number;
869
+ /** MIME type */
870
+ type: string;
871
+ /** The File object (browser) or Blob */
872
+ file: File | Blob;
873
+ /** File extension (lowercase, without dot) */
874
+ extension: string;
875
+ /** Get file as ArrayBuffer */
876
+ arrayBuffer(): Promise<ArrayBuffer>;
877
+ /** Get file as text */
878
+ text(): Promise<string>;
879
+ /** Get file as readable stream */
880
+ stream(): ReadableStream<Uint8Array>;
881
+ }
882
+ /**
883
+ * Create a Zod schema for file validation with common file constraints.
884
+ *
885
+ * @example
886
+ * ```typescript
887
+ * import { formFile } from 'next-request';
888
+ * import { z } from 'zod';
889
+ *
890
+ * const schema = z.object({
891
+ * avatar: formFile({ maxSize: '5mb', types: ['image/*'] }),
892
+ * document: formFile({ maxSize: '10mb', types: ['application/pdf'] }),
893
+ * attachment: formFile({ maxSize: '20mb', extensions: ['pdf', 'doc', 'docx'] }),
894
+ * });
895
+ * ```
896
+ */
897
+ declare function formFile(options?: FormFileOptions): z.ZodType<ValidatedFile>;
898
+ /**
899
+ * Create a Zod schema for multiple file uploads
900
+ *
901
+ * @example
902
+ * ```typescript
903
+ * import { formFiles } from 'next-request';
904
+ * import { z } from 'zod';
905
+ *
906
+ * const schema = z.object({
907
+ * images: formFiles({ maxSize: '5mb', types: ['image/*'], maxFiles: 5 }),
908
+ * });
909
+ * ```
910
+ */
911
+ declare function formFiles(options?: FormFileOptions & {
912
+ /** Minimum number of files */
913
+ minFiles?: number;
914
+ /** Maximum number of files */
915
+ maxFiles?: number;
916
+ }): z.ZodType<ValidatedFile[]>;
917
+ /**
918
+ * Type helper to extract the inferred type from a formFile schema
919
+ */
920
+ type InferFormFile = ValidatedFile;
921
+ type InferFormFiles = ValidatedFile[];
922
+
923
+ /**
924
+ * Options for error formatting
925
+ */
926
+ interface ErrorFormattingOptions {
927
+ /** Include field path in error messages */
928
+ includePath?: boolean;
929
+ /** Custom path separator for nested fields */
930
+ pathSeparator?: string;
931
+ /** Maximum number of errors per field */
932
+ maxErrorsPerField?: number;
933
+ /** Include array indices in paths */
934
+ includeArrayIndices?: boolean;
935
+ }
936
+ /**
937
+ * Structured error format for nested objects and arrays
938
+ */
939
+ interface StructuredErrors {
940
+ /** Flat errors (field path → messages) */
941
+ flat: ValidationErrors;
942
+ /** Nested errors matching the original object structure */
943
+ nested: Record<string, unknown>;
944
+ /** Array of all error messages */
945
+ all: string[];
946
+ /** Count of total errors */
947
+ count: number;
948
+ /** Check if a specific field has errors */
949
+ has(field: string): boolean;
950
+ /** Get errors for a specific field */
951
+ get(field: string): string[];
952
+ /** Get first error for a specific field */
953
+ first(field: string): string | undefined;
954
+ }
955
+ /**
956
+ * Format validation errors with improved support for nested objects and arrays.
957
+ *
958
+ * @example
959
+ * ```typescript
960
+ * const errors = {
961
+ * 'user.email': ['Invalid email'],
962
+ * 'items.0.name': ['Name is required'],
963
+ * 'items.1.price': ['Price must be positive'],
964
+ * };
965
+ *
966
+ * const formatted = formatErrors(errors);
967
+ * // formatted.nested = {
968
+ * // user: { email: ['Invalid email'] },
969
+ * // items: [
970
+ * // { name: ['Name is required'] },
971
+ * // { price: ['Price must be positive'] },
972
+ * // ],
973
+ * // }
974
+ * ```
975
+ */
976
+ declare function formatErrors(errors: ValidationErrors, options?: ErrorFormattingOptions): StructuredErrors;
977
+ /**
978
+ * Flatten nested errors into dot-notation paths
979
+ *
980
+ * @example
981
+ * ```typescript
982
+ * const nested = {
983
+ * user: { email: ['Invalid email'] },
984
+ * items: [
985
+ * { name: ['Name is required'] },
986
+ * ],
987
+ * };
988
+ *
989
+ * const flat = flattenErrors(nested);
990
+ * // { 'user.email': ['Invalid email'], 'items.0.name': ['Name is required'] }
991
+ * ```
992
+ */
993
+ declare function flattenErrors(nested: Record<string, unknown>, options?: ErrorFormattingOptions): ValidationErrors;
994
+ /**
995
+ * Get human-readable error summary
996
+ */
997
+ declare function summarizeErrors(errors: ValidationErrors): string;
998
+ /**
999
+ * Filter errors to only include specific fields
1000
+ */
1001
+ declare function filterErrors(errors: ValidationErrors, fields: string[]): ValidationErrors;
1002
+ /**
1003
+ * Merge multiple error objects
1004
+ */
1005
+ declare function mergeErrors(...errorSets: ValidationErrors[]): ValidationErrors;
1006
+
1007
+ /**
1008
+ * Result of a mock validation
1009
+ */
1010
+ interface MockValidationResult<T> {
1011
+ /** Whether validation succeeded */
1012
+ success: boolean;
1013
+ /** Validated data (if successful) */
1014
+ data?: T;
1015
+ /** Validation errors (if failed) */
1016
+ errors?: ValidationErrors;
1017
+ /** Whether authorization was denied */
1018
+ unauthorized?: boolean;
1019
+ /** The FormRequest instance */
1020
+ instance: FormRequest<T>;
1021
+ }
1022
+ /**
1023
+ * Options for creating a mock request
1024
+ */
1025
+ interface MockRequestOptions {
1026
+ /** HTTP method */
1027
+ method?: string;
1028
+ /** Request headers */
1029
+ headers?: Record<string, string>;
1030
+ /** Query parameters */
1031
+ query?: Record<string, string>;
1032
+ /** Route parameters */
1033
+ params?: Record<string, string>;
1034
+ /** Base URL for the request */
1035
+ baseUrl?: string;
1036
+ }
1037
+ /**
1038
+ * Create a mock Request object for testing
1039
+ */
1040
+ declare function createMockRequest(body: unknown, options?: MockRequestOptions): Request;
1041
+ /**
1042
+ * Test a FormRequest with mock data
1043
+ *
1044
+ * @example
1045
+ * ```typescript
1046
+ * import { testFormRequest } from 'next-request/testing';
1047
+ *
1048
+ * describe('CreateUserRequest', () => {
1049
+ * it('should validate valid data', async () => {
1050
+ * const result = await testFormRequest(CreateUserRequest, {
1051
+ * email: 'user@example.com',
1052
+ * password: 'securepassword123',
1053
+ * });
1054
+ *
1055
+ * expect(result.success).toBe(true);
1056
+ * expect(result.data?.email).toBe('user@example.com');
1057
+ * });
1058
+ *
1059
+ * it('should reject invalid email', async () => {
1060
+ * const result = await testFormRequest(CreateUserRequest, {
1061
+ * email: 'invalid-email',
1062
+ * password: 'securepassword123',
1063
+ * });
1064
+ *
1065
+ * expect(result.success).toBe(false);
1066
+ * expect(result.errors?.email).toBeDefined();
1067
+ * });
1068
+ * });
1069
+ * ```
1070
+ */
1071
+ declare function testFormRequest<T>(RequestClass: new () => FormRequest<T>, body: unknown, options?: MockRequestOptions): Promise<MockValidationResult<T>>;
1072
+ /**
1073
+ * Add static mock method to FormRequest class
1074
+ * This allows calling CreateUserRequest.mock({ ... }) directly
1075
+ *
1076
+ * @example
1077
+ * ```typescript
1078
+ * import { addMockMethod } from 'next-request/testing';
1079
+ *
1080
+ * // Add mock method to a specific request class
1081
+ * addMockMethod(CreateUserRequest);
1082
+ *
1083
+ * // Now you can use it directly
1084
+ * const result = await CreateUserRequest.mock({ email: 'invalid' });
1085
+ * expect(result.errors?.email).toBeDefined();
1086
+ * ```
1087
+ */
1088
+ declare function addMockMethod<T>(RequestClass: new () => FormRequest<T>): void;
1089
+ /**
1090
+ * Type helper for FormRequest classes with mock method
1091
+ */
1092
+ interface MockableFormRequest<T> {
1093
+ new (): FormRequest<T>;
1094
+ mock(body: unknown, options?: MockRequestOptions): Promise<MockValidationResult<T>>;
1095
+ }
1096
+ /**
1097
+ * Assert that validation passes
1098
+ */
1099
+ declare function expectValid<T>(result: MockValidationResult<T>): asserts result is MockValidationResult<T> & {
1100
+ success: true;
1101
+ data: T;
1102
+ };
1103
+ /**
1104
+ * Assert that validation fails
1105
+ */
1106
+ declare function expectInvalid<T>(result: MockValidationResult<T>): asserts result is MockValidationResult<T> & {
1107
+ success: false;
1108
+ errors: ValidationErrors;
1109
+ };
1110
+ /**
1111
+ * Assert that a specific field has errors
1112
+ */
1113
+ declare function expectFieldError<T>(result: MockValidationResult<T>, field: string, messagePattern?: string | RegExp): void;
1114
+
1115
+ /**
1116
+ * Create an authenticated request base class
1117
+ *
1118
+ * @example
1119
+ * ```typescript
1120
+ * const AuthenticatedRequest = createAuthenticatedRequest(async (request) => {
1121
+ * const token = request.header('authorization');
1122
+ * return !!token && await verifyToken(token);
1123
+ * });
1124
+ *
1125
+ * class MyRequest extends AuthenticatedRequest<MyData> {
1126
+ * rules() { return new ZodAdapter(schema); }
1127
+ * }
1128
+ * ```
1129
+ */
1130
+ declare function createAuthenticatedRequest<TBase = unknown>(authorizeFn: (request: FormRequest<TBase>) => boolean | Promise<boolean>): abstract new <T>() => FormRequest<T>;
1131
+ /**
1132
+ * Compose multiple authorization checks
1133
+ *
1134
+ * @example
1135
+ * ```typescript
1136
+ * const authorize = composeAuthorization(
1137
+ * isAuthenticated,
1138
+ * hasRole('admin'),
1139
+ * hasPermission('products.create')
1140
+ * );
1141
+ *
1142
+ * class CreateProductRequest extends FormRequest<CreateProductData> {
1143
+ * authorize = authorize;
1144
+ * rules() { return new ZodAdapter(schema); }
1145
+ * }
1146
+ * ```
1147
+ */
1148
+ declare function composeAuthorization(...checks: Array<(request: FormRequest<unknown>) => boolean | Promise<boolean>>): (this: FormRequest<unknown>) => Promise<boolean>;
1149
+ /**
1150
+ * Common authorization helpers
1151
+ */
1152
+ declare const authHelpers: {
1153
+ /**
1154
+ * Check if request has a specific header
1155
+ */
1156
+ hasHeader: (headerName: string) => (request: FormRequest<unknown>) => boolean;
1157
+ /**
1158
+ * Check if request has authorization header
1159
+ */
1160
+ isAuthenticated: (request: FormRequest<unknown>) => boolean;
1161
+ /**
1162
+ * Check if request has a bearer token
1163
+ */
1164
+ hasBearerToken: (request: FormRequest<unknown>) => boolean;
1165
+ /**
1166
+ * Extract bearer token from request
1167
+ */
1168
+ getBearerToken: (request: FormRequest<unknown>) => string | null;
1169
+ /**
1170
+ * Check if request has API key
1171
+ */
1172
+ hasApiKey: (headerName?: string) => (request: FormRequest<unknown>) => boolean;
1173
+ };
1174
+ /**
1175
+ * Lifecycle hook composition utilities
1176
+ */
1177
+ declare const hookHelpers: {
1178
+ /**
1179
+ * Compose multiple beforeValidation hooks
1180
+ */
1181
+ beforeValidation: (...hooks: Array<(this: FormRequest<unknown>) => void | Promise<void>>) => (this: FormRequest<unknown>) => Promise<void>;
1182
+ /**
1183
+ * Compose multiple afterValidation hooks
1184
+ */
1185
+ afterValidation: <T>(...hooks: Array<(this: FormRequest<T>, data: T) => void | Promise<void>>) => (this: FormRequest<T>, data: T) => Promise<void>;
1186
+ /**
1187
+ * Common beforeValidation transformations
1188
+ */
1189
+ transforms: {
1190
+ /** Trim all string values */
1191
+ trimStrings: (this: FormRequest<unknown>) => void;
1192
+ /** Lowercase specific fields */
1193
+ lowercase: (...fields: string[]) => (this: FormRequest<unknown>) => void;
1194
+ /** Uppercase specific fields */
1195
+ uppercase: (...fields: string[]) => (this: FormRequest<unknown>) => void;
1196
+ };
1197
+ };
364
1198
 
365
- export { AuthorizationError, FormRequest, SupportedRequest, ValidationError, ValidationErrors, ValidatorAdapter, createAppRouterWrapper, createPagesRouterWrapper, withApiRequest, withRequest };
1199
+ export { ArkTypeAdapter, AuthorizationError, type CoercionOptions, type ErrorFormattingOptions, type FormFileOptions, FormRequest, type InferFormFile, type InferFormFiles, MemoryRateLimitStore, type MockRequestOptions, type MockValidationResult, type MockableFormRequest, type RateLimitConfig, RateLimitError, type RateLimitResult, type RateLimitState, type RateLimitStore, type StructuredErrors, SupportedRequest, ValibotAdapter, type ValidatedFile, ValidationConfig, ValidationError, ValidationErrors, ValidationResult, ValidatorAdapter, YupAdapter, addMockMethod, authHelpers, checkRateLimit, coerceFormData, coercionPresets, composeAuthorization, createAppRouterWrapper, createAuthenticatedRequest, createMockRequest, createPagesRouterWrapper, expectFieldError, expectInvalid, expectValid, filterErrors, flattenErrors, formFile, formFiles, formatErrors, hookHelpers, mergeErrors, rateLimit, setDefaultRateLimitStore, summarizeErrors, testFormRequest, withApiRequest, withApiSchema, withRequest, withSchema, zodCoerce };