aidxnCC/lib/domains/utils.ts

590 lines
18 KiB
TypeScript

/**
* Domain utility functions for date calculations and data enrichment.
*
* @remarks
* This module provides convenience wrapper functions around DomainService methods,
* offering a simpler API for common domain operations like date calculations,
* ownership metrics, and timeline generation.
*
* **Key features:**
* - Date extraction (registration, expiration, renewal)
* - Ownership duration calculations (years, months, days)
* - Expiration warnings and progress tracking
* - Timeline event generation
* - TLD extraction
*
* **Design pattern:**
* Most functions delegate to {@link DomainService.enrichDomain} which computes
* all metrics at once, making these wrappers efficient for accessing individual metrics.
*
* @example
* ```ts
* import { getOwnershipDuration, isExpiringSoon } from '@/lib/domains/utils'
*
* const years = getOwnershipDuration(domain)
* const expiring = isExpiringSoon(domain, 90) // 90 days threshold
* ```
*
* @module lib/domains/utils
* @category Domains
* @public
*/
import type { Domain } from '@/lib/types'
import { DomainService } from '@/lib/services'
import type { DomainTimelineEvent } from '@/lib/types/domain'
export type { Domain, Renewal } from '@/lib/types'
/**
* Gets the registration date for a domain.
* @param domain - Domain object
* @returns Registration date
* @see {@link DomainService.enrichDomain}
* @category Domains
* @public
*/
export function getRegistrationDate(domain: Domain): Date {
const enriched = DomainService.enrichDomain(domain)
return enriched.registrationDate
}
/**
* Gets the expiration date for a domain based on renewal history.
*
* @param domain - Domain object with renewal records
* @returns Computed expiration date
*
* @remarks
* This function delegates to {@link DomainService.enrichDomain} which computes
* the expiration date by summing all renewal periods from the initial registration.
* The calculation accounts for multi-year renewals and uses the last renewal's
* end date as the expiration.
*
* @example
* ```ts
* import { getExpirationDate } from '@/lib/domains/utils'
*
* const expirationDate = getExpirationDate(domain)
* console.log(expirationDate) // 2026-03-15T00:00:00.000Z
* ```
*
* @example
* ```ts
* // Check if domain is expired
* const expDate = getExpirationDate(domain)
* const isExpired = expDate < new Date()
* if (isExpired) {
* console.log('Domain has expired!')
* }
* ```
*
* @see {@link DomainService.enrichDomain}
* @category Domains
* @public
*/
export function getExpirationDate(domain: Domain): Date {
const enriched = DomainService.enrichDomain(domain)
return enriched.expirationDate
}
/**
* Gets the ownership duration in years for a domain.
*
* @param domain - Domain object with renewal history
* @returns Ownership duration in whole years (floor value)
*
* @remarks
* This function calculates how many full years the domain has been owned
* since the initial registration date. The value is floored, so a domain
* owned for 2 years and 11 months returns 2.
*
* Uses {@link DomainService.enrichDomain} for efficient metric computation.
*
* @example
* ```ts
* import { getOwnershipDuration } from '@/lib/domains/utils'
*
* const years = getOwnershipDuration(domain)
* console.log(`Owned for ${years} years`) // "Owned for 3 years"
* ```
*
* @example
* ```ts
* // Display ownership milestone
* const years = getOwnershipDuration(domain)
* if (years >= 5) {
* console.log('Long-term domain!')
* }
* ```
*
* @see {@link DomainService.enrichDomain}
* @see {@link getOwnershipMonths} For month-level precision
* @see {@link getOwnershipDays} For day-level precision
* @category Domains
* @public
*/
export function getOwnershipDuration(domain: Domain): number {
const enriched = DomainService.enrichDomain(domain)
return enriched.ownershipYears
}
/**
* Gets the ownership duration in months for a domain.
*
* @param domain - Domain object with renewal history
* @returns Total ownership duration in months
*
* @remarks
* This function calculates the precise number of months between the registration
* date and the current date. Month boundaries are respected - if the current
* day is earlier than the registration day, the month count is decremented.
*
* For year-level precision, use {@link getOwnershipDuration}.
*
* @example
* ```ts
* import { getOwnershipMonths } from '@/lib/domains/utils'
*
* const months = getOwnershipMonths(domain)
* console.log(`Owned for ${months} months`) // "Owned for 38 months"
* ```
*
* @example
* ```ts
* // Calculate years and remaining months
* const totalMonths = getOwnershipMonths(domain)
* const years = Math.floor(totalMonths / 12)
* const months = totalMonths % 12
* console.log(`${years}y ${months}mo`) // "3y 2mo"
* ```
*
* @see {@link DomainService.enrichDomain}
* @see {@link getOwnershipDuration} For year-level precision
* @see {@link getOwnershipDays} For day-level precision
* @category Domains
* @public
*/
export function getOwnershipMonths(domain: Domain): number {
const enriched = DomainService.enrichDomain(domain)
return enriched.ownershipMonths
}
/**
* Gets the ownership duration in days for a domain.
*
* @param domain - Domain object with renewal history
* @returns Total ownership duration in days
*
* @remarks
* This function provides the most precise ownership duration metric by
* calculating the exact number of days between registration and the current date.
* Uses ceiling to ensure partial days are counted.
*
* For less granular metrics, use {@link getOwnershipDuration} or {@link getOwnershipMonths}.
*
* @example
* ```ts
* import { getOwnershipDays } from '@/lib/domains/utils'
*
* const days = getOwnershipDays(domain)
* console.log(`Owned for ${days} days`) // "Owned for 1,247 days"
* ```
*
* @example
* ```ts
* // Check if domain is newly registered
* const days = getOwnershipDays(domain)
* if (days < 30) {
* console.log('New domain!')
* }
* ```
*
* @see {@link DomainService.enrichDomain}
* @see {@link getOwnershipDuration} For year-level precision
* @see {@link getOwnershipMonths} For month-level precision
* @category Domains
* @public
*/
export function getOwnershipDays(domain: Domain): number {
const enriched = DomainService.enrichDomain(domain)
return enriched.ownershipDays
}
/**
* Calculates the total renewal period in days from registration to expiration.
*
* @param domain - Domain object with renewal history
* @returns Total days from initial registration to current expiration date
*
* @remarks
* This function computes the complete lifecycle span of all renewals combined.
* Unlike {@link getOwnershipDays} which measures time from registration to now,
* this measures the total committed period (registration to expiration).
*
* Useful for calculating average renewal period length or total commitment duration.
*
* @example
* ```ts
* import { getTotalRenewalPeriodDays } from '@/lib/domains/utils'
*
* const totalDays = getTotalRenewalPeriodDays(domain)
* console.log(`Total period: ${totalDays} days`) // "Total period: 1,825 days"
* ```
*
* @example
* ```ts
* // Calculate average renewal length
* const totalDays = getTotalRenewalPeriodDays(domain)
* const renewalCount = domain.renewals.length
* const avgDays = totalDays / renewalCount
* console.log(`Avg renewal: ${Math.round(avgDays / 365)} years`)
* ```
*
* @see {@link getOwnershipDays} For current ownership duration
* @see {@link getDaysUntilExpiration} For remaining days
* @category Domains
* @public
*/
export function getTotalRenewalPeriodDays(domain: Domain): number {
const registrationDate = getRegistrationDate(domain)
const expirationDate = getExpirationDate(domain)
const diffTime = expirationDate.getTime() - registrationDate.getTime()
return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
}
/**
* Gets the renewal progress as a percentage (0-100).
*
* @param domain - Domain object with renewal history
* @returns Percentage of current renewal period elapsed (0-100)
*
* @remarks
* This function calculates how far through the total renewal period
* the domain ownership currently is. The value represents the percentage
* from registration date to expiration date.
*
* - 0% = just registered
* - 50% = halfway through renewal period
* - 100% = at or past expiration
*
* @example
* ```ts
* import { getRenewalProgress } from '@/lib/domains/utils'
*
* const progress = getRenewalProgress(domain)
* console.log(`${progress.toFixed(1)}% complete`) // "67.3% complete"
* ```
*
* @example
* ```ts
* // Display progress bar
* const progress = getRenewalProgress(domain)
* const barWidth = Math.round(progress)
* console.log('█'.repeat(barWidth) + '░'.repeat(100 - barWidth))
* ```
*
* @see {@link DomainService.enrichDomain}
* @see {@link getDaysUntilExpiration} For days remaining
* @category Domains
* @public
*/
export function getRenewalProgress(domain: Domain): number {
const enriched = DomainService.enrichDomain(domain)
return enriched.renewalProgressPercent
}
/**
* Gets the number of days until domain expiration.
*
* @param domain - Domain object with renewal history
* @returns Days until expiration (negative if already expired)
*
* @remarks
* This function calculates days from the current date to the domain's
* expiration date. The value can be:
*
* - Positive: Days remaining before expiration
* - Zero: Expires today
* - Negative: Days since expiration (domain expired)
*
* Commonly used with {@link isExpiringSoon} for renewal alerts.
*
* @example
* ```ts
* import { getDaysUntilExpiration } from '@/lib/domains/utils'
*
* const days = getDaysUntilExpiration(domain)
* console.log(`${days} days until expiration`) // "45 days until expiration"
* ```
*
* @example
* ```ts
* // Display appropriate message based on status
* const days = getDaysUntilExpiration(domain)
* if (days < 0) {
* console.log(`Expired ${Math.abs(days)} days ago!`)
* } else if (days === 0) {
* console.log('Expires today!')
* } else if (days < 30) {
* console.log(`Renew soon - ${days} days left`)
* } else {
* console.log(`${days} days remaining`)
* }
* ```
*
* @see {@link DomainService.enrichDomain}
* @see {@link isExpiringSoon} For boolean expiration check
* @category Domains
* @public
*/
export function getDaysUntilExpiration(domain: Domain): number {
const enriched = DomainService.enrichDomain(domain)
return enriched.daysUntilExpiration
}
/**
* Checks if a domain is expiring soon based on a configurable threshold.
*
* @param domain - Domain object with renewal history
* @param thresholdDays - Number of days to consider "expiring soon" (default: 90)
* @returns True if domain expires within the threshold period
*
* @remarks
* This function provides a simple boolean check for domains requiring renewal attention.
* The default 90-day threshold is typical for domain renewal planning, but can be
* adjusted based on your renewal workflow.
*
* Returns true if:
* - Domain expires in fewer days than the threshold
* - Domain is already expired (negative days)
*
* @example
* ```ts
* import { isExpiringSoon } from '@/lib/domains/utils'
*
* // Check with default 90-day threshold
* if (isExpiringSoon(domain)) {
* console.log('Renewal required soon!')
* }
* ```
*
* @example
* ```ts
* // Custom threshold for urgent renewals
* const urgent = isExpiringSoon(domain, 30)
* const warning = isExpiringSoon(domain, 90)
*
* if (urgent) {
* console.log('🚨 URGENT: Renew within 30 days!')
* } else if (warning) {
* console.log('⚠️ Renewal recommended')
* }
* ```
*
* @example
* ```ts
* // Filter domains needing renewal
* const domains = DomainService.getAllDomains()
* const needsRenewal = domains.filter(d => isExpiringSoon(d, 60))
* console.log(`${needsRenewal.length} domains need renewal`)
* ```
*
* @see {@link getDaysUntilExpiration} For exact day count
* @category Domains
* @public
*/
export function isExpiringSoon(domain: Domain, thresholdDays: number = 90): boolean {
return getDaysUntilExpiration(domain) <= thresholdDays
}
/**
* Formats a Date object to a human-readable string.
*
* @param date - Date object to format
* @returns Formatted date string in "MMM DD, YYYY" format (e.g., "Jan 15, 2025")
*
* @remarks
* Uses US locale formatting with abbreviated month names. The format is
* consistent across the application for displaying domain-related dates.
*
* Output format: "Month Day, Year" where:
* - Month: 3-letter abbreviation (Jan, Feb, Mar, etc.)
* - Day: Numeric day of month
* - Year: Full 4-digit year
*
* @example
* ```ts
* import { formatDate } from '@/lib/domains/utils'
*
* const date = new Date('2025-03-15')
* console.log(formatDate(date)) // "Mar 15, 2025"
* ```
*
* @example
* ```ts
* // Format expiration date for display
* import { getExpirationDate, formatDate } from '@/lib/domains/utils'
*
* const expDate = getExpirationDate(domain)
* const formatted = formatDate(expDate)
* console.log(`Expires: ${formatted}`) // "Expires: Dec 31, 2026"
* ```
*
* @example
* ```ts
* // Format all renewal dates
* domain.renewals.forEach(renewal => {
* const date = formatDate(new Date(renewal.date))
* console.log(`${date}: ${renewal.years} year(s)`)
* })
* ```
*
* @category Domains
* @public
*/
export function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
/**
* Generates a timeline of renewal events from domain renewal history.
*
* @param domain - Domain object with renewal records
* @returns Array of timeline events with dates, types, and renewal periods
*
* @remarks
* This function transforms the domain's renewal array into a structured
* timeline suitable for visualization. Each event includes:
*
* - `date`: Date object of the renewal/registration
* - `type`: 'registration' for first renewal, 'renewal' for subsequent ones
* - `years`: Number of years renewed for that period
*
* The first renewal in the array is always treated as the initial registration.
*
* @example
* ```ts
* import { getRenewalTimeline } from '@/lib/domains/utils'
*
* const timeline = getRenewalTimeline(domain)
* timeline.forEach(event => {
* console.log(`${event.type}: ${event.date} (${event.years}y)`)
* })
* // Output:
* // registration: 2020-01-15 (2y)
* // renewal: 2022-01-15 (3y)
* // renewal: 2025-01-15 (1y)
* ```
*
* @example
* ```ts
* // Build visual timeline
* const timeline = getRenewalTimeline(domain)
* const formatted = timeline.map(event => ({
* ...event,
* icon: event.type === 'registration' ? '🎯' : '🔄',
* label: `${formatDate(event.date)} - ${event.years} year(s)`
* }))
* ```
*
* @see {@link DomainTimelineEvent} For event type definition
* @category Domains
* @public
*/
export function getRenewalTimeline(domain: Domain): DomainTimelineEvent[] {
return domain.renewals.map((renewal, index) => ({
date: new Date(renewal.date),
type: index === 0 ? 'registration' : 'renewal',
years: renewal.years
}))
}
/**
* Extracts the top-level domain (TLD) from a domain name.
*
* @param domain - Domain object
* @returns TLD including the dot (e.g., '.com', '.dev', '.co.uk')
*
* @remarks
* This function extracts the TLD portion of the domain name, which includes
* the dot separator. For complex TLDs like '.co.uk', only the final segment
* is returned ('.uk').
*
* Uses {@link DomainService.enrichDomain} for consistent TLD extraction logic.
*
* @example
* ```ts
* import { getTLD } from '@/lib/domains/utils'
*
* const domain = { domain: 'example.com', ... }
* console.log(getTLD(domain)) // ".com"
* ```
*
* @example
* ```ts
* // Group domains by TLD
* const domains = DomainService.getAllDomains()
* const byTLD = domains.reduce((acc, d) => {
* const tld = getTLD(d)
* acc[tld] = (acc[tld] || 0) + 1
* return acc
* }, {} as Record<string, number>)
* console.log(byTLD) // { '.com': 15, '.dev': 3, '.io': 2 }
* ```
*
* @see {@link DomainService.enrichDomain}
* @category Domains
* @public
*/
export function getTLD(domain: Domain): string {
const enriched = DomainService.enrichDomain(domain)
return enriched.tld
}
/**
* Gets the next renewal date for a domain (alias for expiration date).
*
* @param domain - Domain object with renewal history
* @returns Next renewal date (same as expiration date)
*
* @remarks
* This function is a semantic alias for {@link getExpirationDate}. Both return
* the same value - the date when the current renewal period ends and the domain
* must be renewed to avoid expiration.
*
* Use this function when you want to emphasize the "renewal" aspect rather than
* the "expiration" aspect of the date.
*
* @example
* ```ts
* import { getNextRenewalDate } from '@/lib/domains/utils'
*
* const renewalDate = getNextRenewalDate(domain)
* console.log(`Next renewal: ${renewalDate}`) // "Next renewal: 2026-03-15..."
* ```
*
* @example
* ```ts
* // Calculate time until next renewal
* import { getNextRenewalDate, formatDate } from '@/lib/domains/utils'
*
* const nextRenewal = getNextRenewalDate(domain)
* const daysUntil = Math.ceil((nextRenewal.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
* console.log(`Renewal in ${daysUntil} days (${formatDate(nextRenewal)})`)
* ```
*
* @see {@link DomainService.enrichDomain}
* @see {@link getExpirationDate} For the same value with different semantics
* @see {@link getDaysUntilExpiration} For days until renewal needed
* @category Domains
* @public
*/
export function getNextRenewalDate(domain: Domain): Date {
const enriched = DomainService.enrichDomain(domain)
return enriched.nextRenewalDate
}