Files
kycnotme/web/src/lib/zodUtils.ts
2025-05-28 13:48:27 +00:00

72 lines
2.2 KiB
TypeScript

import { z, type ZodTypeAny } from 'astro/zod'
import { round } from 'lodash-es'
const addZodPipe = (schema: ZodTypeAny, zodPipe?: ZodTypeAny) => {
return zodPipe ? schema.pipe(zodPipe) : schema
}
/**
* The difference between this and `z.coerce.number()` is that an empty string won't be coerced to 0.
*
* If you don't accept 0, just use `z.coerce.number().int().positive()` instead.
*/
export const zodCohercedNumber = (zodPipe?: ZodTypeAny) =>
addZodPipe(z.number().or(z.string().nonempty()), zodPipe)
export const zodUrlOptionalProtocol = z.preprocess(
(input) => {
if (typeof input !== 'string') return input
const cleanInput = input.trim().replace(/\/$/, '')
return !/^\w+:\/\//i.test(cleanInput) ? `https://${cleanInput}` : cleanInput
},
z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z0-9]{2,})[^\s$.?#].[^\s]*$/i.test(value), {
message: 'Invalid URL',
})
)
const stringToArrayFactory = (delimiter: RegExp | string = ',') => {
return <T>(input: T) =>
typeof input !== 'string'
? (input ?? undefined)
: input
.split(delimiter)
.map((item) => item.trim())
.filter((item) => item !== '')
}
export const stringListOfUrlsSchema = z.preprocess(
stringToArrayFactory(/[\s,\n]+/),
z.array(zodUrlOptionalProtocol).default([])
)
export const stringListOfUrlsSchemaRequired = z.preprocess(
stringToArrayFactory(/[\s,\n]+/),
z.array(zodUrlOptionalProtocol).min(1)
)
export const MAX_IMAGE_SIZE = 5 * 1024 * 1024 // 5MB
export const ACCEPTED_IMAGE_TYPES = [
'image/svg+xml',
'image/png',
'image/jpeg',
'image/avif',
'image/webp',
] as const satisfies string[]
export const imageFileSchema = z
.instanceof(File)
.optional()
.nullable()
.transform((file) => (!file || file.size === 0 || !file.name ? undefined : file))
.refine(
(file) => !file || file.size <= MAX_IMAGE_SIZE,
`Max image size is ${round(MAX_IMAGE_SIZE / 1024 / 1024, 3).toLocaleString()}MB.`
)
.refine(
(file) => !file || ACCEPTED_IMAGE_TYPES.some((type) => file.type === type),
'Only SVG, PNG, JPG, AVIF, WebP formats are supported.'
)
export const imageFileSchemaRequired = imageFileSchema.refine((file) => !!file, 'Required')