2025-12-25 13:36:32 +00:00
|
|
|
/**
|
|
|
|
|
* API Request Schemas
|
|
|
|
|
* FE-TYPE-003: Create Zod schemas to validate API requests before sending
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2025-12-25 13:36:32 +00:00
|
|
|
* Provides Zod schemas for all API request types to ensure type safety
|
|
|
|
|
* and runtime validation of API requests before they are sent to the backend.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { uuidSchema } from './apiSchemas';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Email schema
|
|
|
|
|
*/
|
|
|
|
|
export const emailSchema = z.string().email('Invalid email format');
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Password schema (min 8 characters)
|
|
|
|
|
*/
|
2026-01-13 18:47:57 +00:00
|
|
|
export const passwordSchema = z
|
|
|
|
|
.string()
|
|
|
|
|
.min(8, 'Password must be at least 8 characters');
|
2025-12-25 13:36:32 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Username schema
|
|
|
|
|
*/
|
|
|
|
|
export const usernameSchema = z
|
|
|
|
|
.string()
|
|
|
|
|
.min(3, 'Username must be at least 3 characters')
|
|
|
|
|
.max(30, 'Username must be at most 30 characters')
|
2026-01-13 18:47:57 +00:00
|
|
|
.regex(
|
|
|
|
|
/^[a-zA-Z0-9_]+$/,
|
|
|
|
|
'Username can only contain letters, numbers, and underscores',
|
|
|
|
|
);
|
2025-12-25 13:36:32 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Login request schema
|
|
|
|
|
*/
|
|
|
|
|
export const loginRequestSchema = z.object({
|
|
|
|
|
email: emailSchema,
|
|
|
|
|
password: z.string().min(1, 'Password is required'),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type LoginRequest = z.infer<typeof loginRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register request schema
|
|
|
|
|
*/
|
|
|
|
|
export const registerRequestSchema = z.object({
|
|
|
|
|
username: usernameSchema,
|
|
|
|
|
email: emailSchema,
|
|
|
|
|
password: passwordSchema,
|
|
|
|
|
first_name: z.string().max(100).optional(),
|
|
|
|
|
last_name: z.string().max(100).optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type RegisterRequest = z.infer<typeof registerRequestSchema>;
|
|
|
|
|
|
2026-01-11 15:39:16 +00:00
|
|
|
/**
|
|
|
|
|
* Two-Factor Authentication (2FA) Request Schemas
|
|
|
|
|
* HIGH PRIORITY: Security feature - validation critical
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify 2FA code request schema
|
|
|
|
|
* POST /auth/2fa/verify
|
|
|
|
|
*/
|
|
|
|
|
export const verify2FARequestSchema = z.object({
|
2026-01-13 18:47:57 +00:00
|
|
|
code: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(6, 'TOTP code must be at least 6 characters')
|
|
|
|
|
.max(6, 'TOTP code must be exactly 6 characters'),
|
2026-01-11 15:39:16 +00:00
|
|
|
secret: z.string().min(1, 'Secret is required'),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type Verify2FARequest = z.infer<typeof verify2FARequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disable 2FA request schema
|
|
|
|
|
* POST /auth/2fa/disable
|
|
|
|
|
*/
|
|
|
|
|
export const disable2FARequestSchema = z.object({
|
|
|
|
|
password: passwordSchema,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type Disable2FARequest = z.infer<typeof disable2FARequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Note: POST /auth/2fa/setup does not require a request body
|
|
|
|
|
* It generates a secret and QR code for the authenticated user
|
|
|
|
|
*/
|
|
|
|
|
|
2025-12-25 13:36:32 +00:00
|
|
|
/**
|
|
|
|
|
* Create user request schema
|
|
|
|
|
*/
|
|
|
|
|
export const createUserRequestSchema = z.object({
|
|
|
|
|
username: usernameSchema,
|
|
|
|
|
email: emailSchema,
|
|
|
|
|
password: passwordSchema,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type CreateUserRequest = z.infer<typeof createUserRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update user request schema
|
|
|
|
|
*/
|
|
|
|
|
export const updateUserRequestSchema = z.object({
|
|
|
|
|
username: usernameSchema.optional(),
|
|
|
|
|
email: emailSchema.optional(),
|
|
|
|
|
password: passwordSchema.optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UpdateUserRequest = z.infer<typeof updateUserRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update profile request schema
|
|
|
|
|
*/
|
|
|
|
|
export const updateProfileRequestSchema = z.object({
|
|
|
|
|
first_name: z.string().max(100).optional(),
|
|
|
|
|
last_name: z.string().max(100).optional(),
|
|
|
|
|
username: usernameSchema.optional(),
|
|
|
|
|
bio: z.string().max(500).optional(),
|
|
|
|
|
location: z.string().max(100).optional(),
|
2026-01-13 18:47:57 +00:00
|
|
|
birthdate: z
|
|
|
|
|
.string()
|
|
|
|
|
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD')
|
|
|
|
|
.optional(),
|
2025-12-25 13:36:32 +00:00
|
|
|
gender: z.enum(['Male', 'Female', 'Other', 'Prefer not to say']).optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UpdateProfileRequest = z.infer<typeof updateProfileRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Send message request schema
|
|
|
|
|
*/
|
|
|
|
|
export const sendMessageRequestSchema = z.object({
|
|
|
|
|
conversation_id: uuidSchema,
|
|
|
|
|
content: z.string().min(1, 'Message content is required'),
|
|
|
|
|
message_type: z.enum(['text', 'image', 'audio', 'file']).optional(),
|
|
|
|
|
attachment_url: z.string().url().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type SendMessageRequest = z.infer<typeof sendMessageRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update message request schema
|
|
|
|
|
*/
|
|
|
|
|
export const updateMessageRequestSchema = z.object({
|
|
|
|
|
content: z.string().min(1, 'Message content is required').optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UpdateMessageRequest = z.infer<typeof updateMessageRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create conversation request schema
|
|
|
|
|
*/
|
|
|
|
|
export const createConversationRequestSchema = z.object({
|
|
|
|
|
name: z.string().min(1, 'Conversation name is required'),
|
|
|
|
|
type: z.enum(['direct', 'group']),
|
2026-01-13 18:47:57 +00:00
|
|
|
participant_ids: z
|
|
|
|
|
.array(uuidSchema)
|
|
|
|
|
.min(1, 'At least one participant is required'),
|
2025-12-25 13:36:32 +00:00
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type CreateConversationRequest = z.infer<
|
|
|
|
|
typeof createConversationRequestSchema
|
|
|
|
|
>;
|
2025-12-25 13:36:32 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update conversation request schema
|
|
|
|
|
*/
|
|
|
|
|
export const updateConversationRequestSchema = z.object({
|
|
|
|
|
name: z.string().min(1, 'Conversation name is required').optional(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type UpdateConversationRequest = z.infer<
|
|
|
|
|
typeof updateConversationRequestSchema
|
|
|
|
|
>;
|
2025-12-25 13:36:32 +00:00
|
|
|
|
2026-01-11 15:39:16 +00:00
|
|
|
/**
|
|
|
|
|
* Batch Operations Request Schemas
|
|
|
|
|
* MEDIUM PRIORITY: Core functionality
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Batch delete tracks request schema
|
|
|
|
|
* POST /tracks/batch/delete
|
|
|
|
|
*/
|
|
|
|
|
export const batchDeleteTracksRequestSchema = z.object({
|
|
|
|
|
track_ids: z.array(uuidSchema).min(1, 'At least one track ID is required'),
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type BatchDeleteTracksRequest = z.infer<
|
|
|
|
|
typeof batchDeleteTracksRequestSchema
|
|
|
|
|
>;
|
2026-01-11 15:39:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Upload Chunking Request Schemas
|
|
|
|
|
* MEDIUM PRIORITY: Core functionality for large file uploads
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initiate chunked upload request schema
|
|
|
|
|
* POST /tracks/initiate
|
|
|
|
|
*/
|
|
|
|
|
export const initiateChunkedUploadRequestSchema = z.object({
|
|
|
|
|
filename: z.string().min(1, 'Filename is required'),
|
|
|
|
|
total_chunks: z.number().int().min(1, 'Total chunks must be at least 1'),
|
|
|
|
|
total_size: z.number().int().min(1, 'Total size must be at least 1'),
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type InitiateChunkedUploadRequest = z.infer<
|
|
|
|
|
typeof initiateChunkedUploadRequestSchema
|
|
|
|
|
>;
|
2026-01-11 15:39:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Complete chunked upload request schema
|
|
|
|
|
* POST /tracks/complete
|
|
|
|
|
*/
|
|
|
|
|
export const completeChunkedUploadRequestSchema = z.object({
|
|
|
|
|
upload_id: z.string().min(1, 'Upload ID is required'),
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type CompleteChunkedUploadRequest = z.infer<
|
|
|
|
|
typeof completeChunkedUploadRequestSchema
|
|
|
|
|
>;
|
2026-01-11 15:39:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Upload chunk request schema
|
|
|
|
|
* POST /tracks/chunk
|
|
|
|
|
* Note: This endpoint uses formData (multipart/form-data), not JSON
|
|
|
|
|
* The schema validates the form fields, but chunk file is validated separately
|
|
|
|
|
*/
|
|
|
|
|
export const uploadChunkRequestSchema = z.object({
|
|
|
|
|
upload_id: z.string().min(1, 'Upload ID is required'),
|
|
|
|
|
chunk_number: z.number().int().min(0, 'Chunk number must be non-negative'),
|
|
|
|
|
total_chunks: z.number().int().min(1, 'Total chunks must be at least 1'),
|
|
|
|
|
total_size: z.number().int().min(1, 'Total size must be at least 1'),
|
|
|
|
|
filename: z.string().min(1, 'Filename is required'),
|
|
|
|
|
// chunk file is sent as FormData file field, validated separately
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UploadChunkRequest = z.infer<typeof uploadChunkRequestSchema>;
|
|
|
|
|
|
2026-01-11 15:40:28 +00:00
|
|
|
/**
|
|
|
|
|
* Analytics Request Schemas
|
|
|
|
|
* MEDIUM PRIORITY: Analytics tracking
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Record analytics event request schema
|
|
|
|
|
* POST /analytics/events
|
|
|
|
|
*/
|
|
|
|
|
export const recordEventRequestSchema = z.object({
|
2026-01-13 18:47:57 +00:00
|
|
|
event_name: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, 'Event name is required')
|
|
|
|
|
.max(100, 'Event name must be at most 100 characters'),
|
2026-01-11 15:40:28 +00:00
|
|
|
payload: z.record(z.any()).optional(), // Additional properties allowed
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type RecordEventRequest = z.infer<typeof recordEventRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Webhook Request Schemas
|
|
|
|
|
* MEDIUM PRIORITY: Webhook management
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create webhook request schema
|
|
|
|
|
* POST /webhooks
|
|
|
|
|
* Note: Schema inferred from endpoint usage - webhook creation typically requires url, events, secret
|
|
|
|
|
*/
|
|
|
|
|
export const createWebhookRequestSchema = z.object({
|
|
|
|
|
url: z.string().url('Invalid webhook URL'),
|
|
|
|
|
events: z.array(z.string()).min(1, 'At least one event is required'),
|
|
|
|
|
secret: z.string().min(1, 'Secret is required').optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type CreateWebhookRequest = z.infer<typeof createWebhookRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Frontend Logging Request Schemas
|
|
|
|
|
* LOW PRIORITY: Development/debugging feature
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Frontend log request schema
|
|
|
|
|
* POST /api/v1/logs/frontend
|
|
|
|
|
*/
|
|
|
|
|
export const frontendLogRequestSchema = z.object({
|
|
|
|
|
level: z.string().optional(),
|
|
|
|
|
message: z.string().optional(),
|
|
|
|
|
context: z.record(z.any()).optional(),
|
|
|
|
|
timestamp: z.string().optional(),
|
|
|
|
|
data: z.any().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type FrontendLogRequest = z.infer<typeof frontendLogRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Email Verification Request Schemas
|
|
|
|
|
* LOW PRIORITY: User account management
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resend verification email request schema
|
|
|
|
|
* POST /auth/resend-verification
|
|
|
|
|
*/
|
|
|
|
|
export const resendVerificationRequestSchema = z.object({
|
|
|
|
|
email: emailSchema,
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type ResendVerificationRequest = z.infer<
|
|
|
|
|
typeof resendVerificationRequestSchema
|
|
|
|
|
>;
|
2026-01-11 15:40:28 +00:00
|
|
|
|
2025-12-25 13:36:32 +00:00
|
|
|
/**
|
|
|
|
|
* Upload track request schema
|
|
|
|
|
* Note: File objects cannot be validated with Zod, so we validate metadata only
|
|
|
|
|
*/
|
|
|
|
|
export const uploadTrackRequestSchema = z.object({
|
|
|
|
|
title: z.string().min(1, 'Track title is required'),
|
|
|
|
|
artist_id: uuidSchema,
|
|
|
|
|
album_id: uuidSchema.optional(),
|
|
|
|
|
genre: z.string().min(1, 'Genre is required'),
|
|
|
|
|
// file and cover_art are File objects, validated separately
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UploadTrackRequest = z.infer<typeof uploadTrackRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update track request schema
|
|
|
|
|
*/
|
|
|
|
|
export const updateTrackRequestSchema = z.object({
|
|
|
|
|
title: z.string().min(1, 'Track title is required').optional(),
|
|
|
|
|
artist_id: uuidSchema.optional(),
|
|
|
|
|
album_id: uuidSchema.optional(),
|
|
|
|
|
genre: z.string().min(1, 'Genre is required').optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UpdateTrackRequest = z.infer<typeof updateTrackRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pagination params schema
|
|
|
|
|
*/
|
|
|
|
|
export const paginationParamsSchema = z.object({
|
|
|
|
|
page: z.number().int().positive().optional(),
|
|
|
|
|
limit: z.number().int().positive().max(100).optional(),
|
|
|
|
|
cursor: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type PaginationParams = z.infer<typeof paginationParamsSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List users request schema
|
|
|
|
|
*/
|
|
|
|
|
export const listUsersRequestSchema = paginationParamsSchema.extend({
|
|
|
|
|
query: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type ListUsersRequest = z.infer<typeof listUsersRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List messages request schema
|
|
|
|
|
*/
|
|
|
|
|
export const listMessagesRequestSchema = paginationParamsSchema.extend({
|
|
|
|
|
conversation_id: uuidSchema,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type ListMessagesRequest = z.infer<typeof listMessagesRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List conversations request schema
|
|
|
|
|
*/
|
|
|
|
|
export const listConversationsRequestSchema = paginationParamsSchema.extend({
|
|
|
|
|
query: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export type ListConversationsRequest = z.infer<
|
|
|
|
|
typeof listConversationsRequestSchema
|
|
|
|
|
>;
|
2025-12-25 13:36:32 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List tracks request schema
|
|
|
|
|
*/
|
|
|
|
|
export const listTracksRequestSchema = paginationParamsSchema.extend({
|
|
|
|
|
artist: z.string().optional(),
|
|
|
|
|
genre: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type ListTracksRequest = z.infer<typeof listTracksRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Search tracks request schema
|
|
|
|
|
*/
|
|
|
|
|
export const searchTracksRequestSchema = paginationParamsSchema.extend({
|
|
|
|
|
query: z.string().min(1, 'Search query is required'),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type SearchTracksRequest = z.infer<typeof searchTracksRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Upload file request schema
|
|
|
|
|
* Note: File object cannot be validated with Zod
|
|
|
|
|
*/
|
|
|
|
|
export const uploadFileRequestSchema = z.object({
|
|
|
|
|
type: z.enum(['image', 'audio', 'document']),
|
|
|
|
|
// file is a File object, validated separately
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type UploadFileRequest = z.infer<typeof uploadFileRequestSchema>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate API request data
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2025-12-25 13:36:32 +00:00
|
|
|
* @param schema - Zod schema to validate against
|
|
|
|
|
* @param data - Data to validate
|
|
|
|
|
* @returns Validated data
|
|
|
|
|
* @throws ZodError if validation fails
|
|
|
|
|
*/
|
|
|
|
|
export function validateApiRequest<T>(
|
|
|
|
|
schema: z.ZodSchema<T>,
|
|
|
|
|
data: unknown,
|
|
|
|
|
): T {
|
|
|
|
|
return schema.parse(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Safe validate API request (returns result instead of throwing)
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2025-12-25 13:36:32 +00:00
|
|
|
* @param schema - Zod schema to validate against
|
|
|
|
|
* @param data - Data to validate
|
|
|
|
|
* @returns Validation result
|
|
|
|
|
*/
|
|
|
|
|
export function safeValidateApiRequest<T>(
|
|
|
|
|
schema: z.ZodSchema<T>,
|
|
|
|
|
data: unknown,
|
|
|
|
|
): {
|
|
|
|
|
success: boolean;
|
|
|
|
|
data?: T;
|
|
|
|
|
error?: z.ZodError;
|
|
|
|
|
} {
|
|
|
|
|
try {
|
|
|
|
|
const validated = validateApiRequest(schema, data);
|
|
|
|
|
return { success: true, data: validated };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof z.ZodError) {
|
|
|
|
|
return { success: false, error };
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate request with custom error handling
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2025-12-25 13:36:32 +00:00
|
|
|
* @param schema - Zod schema to validate against
|
|
|
|
|
* @param data - Data to validate
|
|
|
|
|
* @param onError - Optional error handler
|
|
|
|
|
* @returns Validated data or throws
|
|
|
|
|
*/
|
|
|
|
|
export function validateApiRequestWithError<T>(
|
|
|
|
|
schema: z.ZodSchema<T>,
|
|
|
|
|
data: unknown,
|
|
|
|
|
onError?: (error: z.ZodError) => void,
|
|
|
|
|
): T {
|
|
|
|
|
try {
|
|
|
|
|
return schema.parse(data);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof z.ZodError && onError) {
|
|
|
|
|
onError(error);
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|