Entities
Core business objects representing key concepts in the application domain. All entities are immutable and validated upon creation.
Domain Entities (domain/entities/)
Core business objects representing key concepts in the application domain. All entities are immutable and validated upon creation.
Why Entities Exist
Entities encapsulate the core business concepts:
- Type safety: Branded types prevent mixing up strings at compile time
- Validation: All inputs validated before entity creation
- Immutability: Entities can't be accidentally modified
- Self-documenting: Entity structure describes the domain
Entities
municipality.ts
Represents a New Jersey municipality name using branded types for compile-time safety.
export type MunicipalityName = Brand<string, "MunicipalityName">;
export const createMunicipalityName = (name: string): MunicipalityName
Validation: Non-empty, trimmed strings
address.ts
Validated user address input with normalization and security validation.
export type AddressInput = Brand<string, "AddressInput">;
export const createAddressInput = (text: string): AddressInput
Features:
- Unicode normalization (NFC)
- Whitespace normalization
- XSS protection (rejects HTML/JavaScript patterns)
- Length limits (max 500 characters)
geocoding-result.ts
Geocoding response including location, municipality, and metadata.
export interface GeocodingResult {
inputAddress: AddressInput;
formattedAddress: string;
municipality: MunicipalityName;
coordinates: { latitude: number; longitude: number };
components?: AddressComponents;
score: number;
fromCache: boolean;
}
Factory: createGeocodingResult() with validation
cache-entry.ts
Cache storage structure with TTL and expiration tracking.
export interface CacheEntry<T> {
key: string;
value: T;
createdAt: number;
expiresAt: number;
accessCount: number;
lastAccessed: number;
}
Features:
- Automatic expiration calculation
- Access tracking for LFU eviction
- Immutable after creation
bulk-job.ts
Batch address geocoding operation with progress tracking and result management.
export interface BulkJob {
id: string;
addresses: readonly string[];
status: BulkJobStatus;
results: BulkAddressResult[];
successCount: number;
errorCount: number;
createdAt: string;
startedAt?: string;
completedAt?: string;
processingTimeMs?: number;
}
export type BulkJobStatus = "pending" | "processing" | "completed" | "failed";
Features:
- Process up to 1,000 addresses per job
- Per-address success/error tracking
- State machine: pending → processing → completed/failed
- Processing time metrics
Factory functions: createBulkJob(), startBulkJob(), completeBulkJob(), addBulkJobResult()
health-status.ts
System health monitoring for operational dashboards and alerting.
export interface HealthStatus {
state: HealthState;
components: readonly ComponentHealth[];
uptimeMs: number;
timestamp: string;
}
export type HealthState = "healthy" | "degraded" | "unhealthy";
export type HealthComponent =
| "application"
| "cache"
| "nj-suggestions-api"
| "nj-geocoding-api";
Features:
- Three-state health model (healthy/degraded/unhealthy)
- Component-level health tracking
- Response time and error capture
- Worst-state aggregation for overall health
Factory functions: createComponentHealth(), createHealthStatus()
Patterns
Branded Types
All entities use branded types to prevent string confusion at compile time:
const munName: MunicipalityName = createMunicipalityName("Newark");
const address: AddressInput = createAddressInput("123 Main St");
// Compile error: Type 'AddressInput' is not assignable to type 'MunicipalityName'
const wrong: MunicipalityName = address; // ❌
Factory Functions
Entities are created through factory functions that validate inputs:
createAddressInput("valid address"); // ✅ Returns AddressInput
createAddressInput("<script>alert()</script>"); // ❌ Throws ValidationError
Immutability
All entities are read-only. Modifications create new instances:
const entry = createCacheEntry("key", value, 7);
// entry.key = "new"; // ❌ Compile error: readonly property
Testing
Each entity has unit tests covering:
- Valid creation scenarios
- Validation edge cases
- Error conditions
- Serialization (toJSON)
- Type guards and invariants
Domain
The domain layer contains pure business logic with zero dependencies on external frameworks or libraries. This is the heart of the application following Clean A
Errors
Type-safe error classes for domain-specific failure modes. All errors are created using the `error-factory` pattern for consistency and type safety.