A social knowledge tool for researchers built on ATProto
at development 94 lines 2.3 kB view raw
1import { ValueObject } from '../../../shared/domain/ValueObject'; 2import { Result, ok, err } from '../../../shared/core/Result'; 3 4export class InvalidHandleError extends Error { 5 constructor(message: string) { 6 super(message); 7 this.name = 'InvalidHandleError'; 8 } 9} 10 11interface HandleProps { 12 value: string; 13} 14 15export class Handle extends ValueObject<HandleProps> { 16 get value(): string { 17 return this.props.value; 18 } 19 20 private constructor(props: HandleProps) { 21 super(props); 22 } 23 24 public static create(handle: string): Result<Handle, InvalidHandleError> { 25 if (!handle || handle.trim().length === 0) { 26 return err(new InvalidHandleError('Handle cannot be empty')); 27 } 28 29 const trimmedHandle = handle.trim(); 30 31 // Basic domain validation - must contain at least one dot and valid characters 32 if (!Handle.isValidDomain(trimmedHandle)) { 33 return err(new InvalidHandleError('Handle must be a valid domain')); 34 } 35 36 return ok(new Handle({ value: trimmedHandle })); 37 } 38 39 private static isValidDomain(domain: string): boolean { 40 // Basic domain validation 41 // Must contain at least one dot 42 if (!domain.includes('.')) { 43 return false; 44 } 45 46 // Must not start or end with dot or hyphen 47 if ( 48 domain.startsWith('.') || 49 domain.endsWith('.') || 50 domain.startsWith('-') || 51 domain.endsWith('-') 52 ) { 53 return false; 54 } 55 56 // Split by dots and validate each part 57 const parts = domain.split('.'); 58 if (parts.length < 2) { 59 return false; 60 } 61 62 for (const part of parts) { 63 if (!Handle.isValidDomainPart(part)) { 64 return false; 65 } 66 } 67 68 return true; 69 } 70 71 private static isValidDomainPart(part: string): boolean { 72 // Each part must be 1-63 characters 73 if (part.length === 0 || part.length > 63) { 74 return false; 75 } 76 77 // Must not start or end with hyphen 78 if (part.startsWith('-') || part.endsWith('-')) { 79 return false; 80 } 81 82 // Must contain only alphanumeric characters and hyphens 83 const validChars = /^[a-zA-Z0-9-]+$/; 84 return validChars.test(part); 85 } 86 87 public toString(): string { 88 return this.props.value; 89 } 90 91 public equals(other: Handle): boolean { 92 return this.props.value === other.props.value; 93 } 94}