domains/User.ts
import * as Either from 'fp-ts/Either';
import type {Opaque} from 'type-fest';
import {z} from 'zod';
// Validation rules
export const userValidationSchema = z.object({
id: z.string().uuid(),
fullName: z.string().min(2).trim(),
email: z.string().email(),
emailVerifiedAt: z.date(),
createdAt: z.date(),
updatedAt: z.date(),
});
// Opaque types are used to prevent accidental type coercion
export type User = Opaque<z.infer<typeof userValidationSchema>, 'User'>;
// 1. Parse don’t validate
// 2. Handle error as an Either.left
export function parseUser(value: unknown): Either.Either<Error, User> {
return Either.tryCatch(
() => userValidationSchema.parse(value) as User,
(error) => new Error(error.message)
);
}