emr-types
Version:
Comprehensive TypeScript Types Library for Electronic Medical Record (EMR) Applications - Domain-Driven Design with Zod Validation
314 lines • 11.5 kB
JavaScript
import { z } from 'zod';
import { UserRole, UserStatus } from '../domains/user';
// ============================================================================
// BASE SCHEMAS
// ============================================================================
export const UserIdSchema = z.string().uuid().brand();
export const EmailSchema = z.string().email().brand();
export const PasswordSchema = z.string().min(8).regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain at least one lowercase letter, one uppercase letter, and one number'
}).brand();
// ============================================================================
// ENUM SCHEMAS
// ============================================================================
export const UserRoleSchema = z.nativeEnum(UserRole);
export const UserStatusSchema = z.nativeEnum(UserStatus);
// ============================================================================
// VALUE OBJECT SCHEMAS
// ============================================================================
export const UserIdValueObjectSchema = z.object({
value: UserIdSchema,
format: z.enum(['uuid', 'auto-increment', 'custom']),
createdAt: z.date(),
metadata: z.record(z.unknown()).optional()
});
export const EmailValueObjectSchema = z.object({
value: EmailSchema,
isVerified: z.boolean(),
verifiedAt: z.date().optional(),
createdAt: z.date()
});
export const PasswordValueObjectSchema = z.object({
value: PasswordSchema,
hashedValue: z.string(),
salt: z.string(),
algorithm: z.enum(['bcrypt', 'argon2', 'scrypt']),
iterations: z.number().positive(),
createdAt: z.date(),
lastChangedAt: z.date()
});
// ============================================================================
// ENTITY SCHEMAS
// ============================================================================
export const UserProfileSchema = z.object({
id: UserIdSchema,
firstName: z.string().min(1).max(50),
lastName: z.string().min(1).max(50),
displayName: z.string().max(100).optional(),
avatar: z.string().url().optional(),
bio: z.string().max(500).optional(),
dateOfBirth: z.date().optional(),
phoneNumber: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
address: z.object({
street: z.string().max(200),
city: z.string().max(100),
state: z.string().max(100),
postalCode: z.string().max(20),
country: z.string().max(100)
}).optional(),
preferences: z.record(z.unknown()).optional(),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: UserIdSchema.optional(),
updatedBy: UserIdSchema.optional()
});
export const UserSessionSchema = z.object({
id: z.string().uuid(),
userId: UserIdSchema,
token: z.string(),
refreshToken: z.string().optional(),
deviceInfo: z.object({
userAgent: z.string(),
ipAddress: z.string().ip(),
deviceType: z.enum(['desktop', 'mobile', 'tablet']),
os: z.string(),
browser: z.string()
}).optional(),
isActive: z.boolean(),
expiresAt: z.date(),
lastActivityAt: z.date(),
createdAt: z.date()
});
export const UserSchema = z.object({
id: UserIdSchema,
email: EmailValueObjectSchema,
password: PasswordValueObjectSchema,
role: UserRoleSchema,
status: UserStatusSchema,
profile: UserProfileSchema,
sessions: z.array(UserSessionSchema).optional(),
permissions: z.array(z.string()).optional(),
lastLoginAt: z.date().optional(),
loginAttempts: z.number().min(0).max(10).optional(),
lockedUntil: z.date().optional(),
emailVerifiedAt: z.date().optional(),
twoFactorEnabled: z.boolean().optional(),
twoFactorSecret: z.string().optional(),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: UserIdSchema.optional(),
updatedBy: UserIdSchema.optional(),
deletedAt: z.date().optional()
});
// ============================================================================
// REQUEST/INPUT SCHEMAS
// ============================================================================
export const CreateUserRequestSchema = z.object({
email: EmailSchema,
password: PasswordSchema,
firstName: z.string().min(1).max(50),
lastName: z.string().min(1).max(50),
role: UserRoleSchema.optional(),
displayName: z.string().max(100).optional(),
phoneNumber: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
address: z.object({
street: z.string().max(200),
city: z.string().max(100),
state: z.string().max(100),
postalCode: z.string().max(20),
country: z.string().max(100)
}).optional(),
preferences: z.record(z.unknown()).optional()
});
export const UpdateUserRequestSchema = z.object({
firstName: z.string().min(1).max(50).optional(),
lastName: z.string().min(1).max(50).optional(),
displayName: z.string().max(100).optional(),
phoneNumber: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
address: z.object({
street: z.string().max(200),
city: z.string().max(100),
state: z.string().max(100),
postalCode: z.string().max(20),
country: z.string().max(100)
}).optional(),
preferences: z.record(z.unknown()).optional(),
role: UserRoleSchema.optional(),
status: UserStatusSchema.optional()
});
export const ChangePasswordRequestSchema = z.object({
currentPassword: z.string(),
newPassword: PasswordSchema,
confirmPassword: z.string()
}).refine((data) => data.newPassword === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"]
});
export const LoginRequestSchema = z.object({
email: EmailSchema,
password: z.string(),
rememberMe: z.boolean().optional(),
deviceInfo: z.object({
userAgent: z.string(),
ipAddress: z.string().ip(),
deviceType: z.enum(['desktop', 'mobile', 'tablet']).optional(),
os: z.string().optional(),
browser: z.string().optional()
}).optional()
});
export const ResetPasswordRequestSchema = z.object({
email: EmailSchema
});
export const VerifyEmailRequestSchema = z.object({
token: z.string()
});
export const EnableTwoFactorRequestSchema = z.object({
secret: z.string(),
code: z.string().length(6)
});
export const DisableTwoFactorRequestSchema = z.object({
password: z.string(),
code: z.string().length(6)
});
// ============================================================================
// RESPONSE SCHEMAS
// ============================================================================
export const UserResponseSchema = z.object({
id: UserIdSchema,
email: z.string().email(),
role: UserRoleSchema,
status: UserStatusSchema,
profile: z.object({
firstName: z.string(),
lastName: z.string(),
displayName: z.string().optional(),
avatar: z.string().url().optional(),
phoneNumber: z.string().optional()
}),
lastLoginAt: z.date().optional(),
emailVerifiedAt: z.date().optional(),
twoFactorEnabled: z.boolean(),
createdAt: z.date(),
updatedAt: z.date()
});
export const UserListResponseSchema = z.object({
users: z.array(UserResponseSchema),
pagination: z.object({
page: z.number().positive(),
limit: z.number().positive(),
total: z.number().nonnegative(),
totalPages: z.number().nonnegative()
})
});
export const LoginResponseSchema = z.object({
user: UserResponseSchema,
session: z.object({
token: z.string(),
refreshToken: z.string(),
expiresAt: z.date()
})
});
export const PasswordResetResponseSchema = z.object({
message: z.string(),
resetToken: z.string().optional()
});
// ============================================================================
// FILTER/SEARCH SCHEMAS
// ============================================================================
export const UserSearchFiltersSchema = z.object({
query: z.string().optional(),
role: UserRoleSchema.optional(),
status: UserStatusSchema.optional(),
emailVerified: z.boolean().optional(),
twoFactorEnabled: z.boolean().optional(),
createdAfter: z.date().optional(),
createdBefore: z.date().optional(),
lastLoginAfter: z.date().optional(),
lastLoginBefore: z.date().optional()
});
export const UserSortOptionsSchema = z.enum([
'createdAt',
'updatedAt',
'lastLoginAt',
'firstName',
'lastName',
'email',
'role',
'status'
]);
// ============================================================================
// VALIDATION UTILITIES
// ============================================================================
export const UserValidationUtils = {
isStrongPassword: (password) => {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(password);
},
isValidEmail: (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
},
isPhoneNumber: (phone) => {
return /^\+?[1-9]\d{1,14}$/.test(phone);
},
validatePasswordStrength: (password) => {
const errors = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters long');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain at least one number');
}
if (!/[@$!%*?&]/.test(password)) {
errors.push('Password must contain at least one special character (@$!%*?&)');
}
return {
isValid: errors.length === 0,
errors
};
}
};
// ============================================================================
// SCHEMA EXPORTS
// ============================================================================
export const UserSchemas = {
// Base schemas
UserId: UserIdSchema,
Email: EmailSchema,
Password: PasswordSchema,
// Enum schemas
UserRole: UserRoleSchema,
UserStatus: UserStatusSchema,
// Value object schemas
UserIdValueObject: UserIdValueObjectSchema,
EmailValueObject: EmailValueObjectSchema,
PasswordValueObject: PasswordValueObjectSchema,
// Entity schemas
User: UserSchema,
UserProfile: UserProfileSchema,
UserSession: UserSessionSchema,
// Request schemas
CreateUserRequest: CreateUserRequestSchema,
UpdateUserRequest: UpdateUserRequestSchema,
ChangePasswordRequest: ChangePasswordRequestSchema,
LoginRequest: LoginRequestSchema,
ResetPasswordRequest: ResetPasswordRequestSchema,
VerifyEmailRequest: VerifyEmailRequestSchema,
EnableTwoFactorRequest: EnableTwoFactorRequestSchema,
DisableTwoFactorRequest: DisableTwoFactorRequestSchema,
// Response schemas
UserResponse: UserResponseSchema,
UserListResponse: UserListResponseSchema,
LoginResponse: LoginResponseSchema,
PasswordResetResponse: PasswordResetResponseSchema,
// Filter schemas
UserSearchFilters: UserSearchFiltersSchema,
UserSortOptions: UserSortOptionsSchema,
// Utilities
ValidationUtils: UserValidationUtils
};
//# sourceMappingURL=user-schemas.js.map