/** * Contract for entities that can be created. * * @template T - The entity type */ interface Creatable { /** * Persists a new entity for the first time. * * @param entity - The entity to create */ create(entity: T): Promise; } /** * Contract for entities that can be deleted. * * @template T - The entity type */ interface Deletable { /** * Deletes an entity from the repository. * * @param entity - The entity to delete */ delete(entity: T): Promise; } import { Maybe } from "failcraft"; /** * Contract for entities that can be retrieved by their unique identifier. * * @template T - The entity type */ interface Findable { /** * Finds an entity by its unique identifier. * * @param id - The unique identifier of the entity * @returns The entity if found, `nothing()` if not found */ findById(id: string): Promise>; } /** * Contract for entities that can be persisted. * * @template T - The entity type */ interface Saveable { /** * Persists an existing entity. * * @param entity - The entity to save */ save(entity: T): Promise; } /** * Full CRUD repository contract, composing all segregated interfaces. * * Use this when the repository needs to support all operations. For more * constrained repositories, prefer composing only the interfaces you need. * * @template T - The entity type * * @example * ```ts * // full CRUD * interface UserRepository extends Repository { * findByEmail(email: string): Promise * } * * // read-only * interface ReportRepository extends Findable { * findByPeriod(start: Date, end: Date): Promise * } * * // append-only * interface AuditRepository extends Creatable {} * ``` */ interface Repository extends Findable, Saveable, Creatable, Deletable {} import { attempt, Either, from, Just, just, Left, left, Maybe as Maybe2, maybe, Nothing, nothing, Right, right } from "failcraft"; /** * Base contract for all domain events. * * A domain event represents something meaningful that happened in the domain. * Events are raised by aggregate roots and dispatched after successful persistence, * enabling decoupled side effects such as notifications, projections, and integrations. * * @example * ```ts * class UserCreatedEvent implements DomainEvent { * occurredAt = new Date() * * constructor(private readonly user: User) {} * * getAggregateId(): UniqueEntityId { * return this.user.id * } * } * ``` */ interface DomainEvent { /** * Returns the identifier of the aggregate root that raised this event. */ getAggregateId(): UniqueEntityId; /** * The date and time when the event occurred. */ occurredAt: Date; } /** * Base class for all domain entities. * * An entity is an object defined by its identity rather than its attributes. * Two entities are considered equal if they share the same `id`, regardless * of their other properties. * * All entities must implement a static `create` factory method to encapsulate * construction logic and keep the constructor protected. * * @template Props - The shape of the entity's properties * * @example * ```ts * interface UserProps { * id: UniqueEntityId * name: string * email: string * createdAt: Date * } * * class User extends Entity { * get name() { return this.props.name } * get email() { return this.props.email } * * static create(props: Optional): User { * return new User( * { ...props, createdAt: props.createdAt ?? new Date() }, * props.id ?? new UniqueEntityId(), * ) * } * } * ``` */ declare abstract class Entity { private readonly _id; protected props: Props; get id(): UniqueEntityId; protected constructor(props: Props, id?: UniqueEntityId); /** * Compares this entity with another by identity. * * @param entity - The entity to compare against * @returns `true` if both entities share the same `id` */ equals(entity: Entity): boolean; } /** * Base class for all aggregate roots in the domain. * * An aggregate root is the entry point to an aggregate — a cluster of domain * objects that are treated as a single unit. It is responsible for enforcing * invariants and coordinating domain events within its boundary. * * Domain events are collected internally and dispatched after the aggregate * is persisted, ensuring side effects only occur after a successful transaction. * * @template Props - The shape of the aggregate's properties * * @example * ```ts * class User extends AggregateRoot { * static create(props: Optional): User { * const user = new User( * { ...props, createdAt: props.createdAt ?? new Date() }, * props.id ?? new UniqueEntityId(), * ) * * user.addDomainEvent(new UserCreatedEvent(user)) * * return user * } * } * ``` */ declare abstract class AggregateRoot extends Entity { private readonly _domainEvents; /** * Returns all domain events that have been raised by this aggregate * and are pending dispatch. */ get domainEvents(): DomainEvent[]; /** * Registers a domain event to be dispatched after the aggregate is persisted. * Automatically marks this aggregate for dispatch in the {@link DomainEvents} registry. * * @param domainEvent - The domain event to register */ protected addDomainEvent(domainEvent: DomainEvent): void; /** * Clears all pending domain events from this aggregate. * Should be called by the infrastructure layer after events are dispatched. */ clearEvents(): void; } /** * @deprecated use `EventHandler` from `archstone/core` instead. * The new version supports generic typing via `EventHandler`. */ interface EventHandler { setupSubscriptions(): void; } /** * Callback function invoked when a domain event is dispatched. * * The bivariant method signature allows handlers typed to a specific * `DomainEvent` subtype to be registered — TypeScript enforces strict * contravariance on function types but not on method signatures. */ type DomainEventCallback = { bivarianceHack(event: DomainEvent): Promise; }["bivarianceHack"]; /** * Central registry and dispatcher for domain events. * * Aggregates register themselves for dispatch after raising events. * The infrastructure layer is responsible for calling {@link dispatchEventsForAggregate} * after successfully persisting an aggregate, ensuring events are only * dispatched after a successful transaction. * * @example * ```ts * // register a handler * DomainEvents.register( * (event) => sendWelcomeEmail(event as UserCreatedEvent), * UserCreatedEvent.name, * ) * * // dispatch after persistence * await userRepository.create(user) * DomainEvents.dispatchEventsForAggregate(user.id) * ``` */ declare class DomainEventsImplementation { private readonly handlersMap; private readonly markedAggregates; /** * Controls whether event dispatching is active. * * Set to `false` in tests that construct aggregates but do not want * side-effects to run, without having to clear and re-register handlers. * * @default true */ shouldRun: boolean; /** * Marks an aggregate root to have its events dispatched. * Called automatically by {@link AggregateRoot.addDomainEvent}. * * @param aggregate - The aggregate to mark for dispatch */ markAggregateForDispatch(aggregate: AggregateRoot): void; /** * Dispatches all pending events for the aggregate with the given id, * clears its events, and removes it from the dispatch list. * * Should be called by the infrastructure layer after persisting the aggregate. * * @param id - The identifier of the aggregate to dispatch events for */ dispatchEventsForAggregate(id: UniqueEntityId): void; /** * Registers an event handler for a given event class name. * * @param callback - The function to invoke when the event is dispatched * @param eventClassName - The name of the event class to listen for */ register(callback: DomainEventCallback, eventClassName: string): void; /** * Removes all registered event handlers. * Useful for test isolation. */ clearHandlers(): void; /** * Removes all aggregates from the dispatch list. * Useful for test isolation. */ clearMarkedAggregates(): void; private dispatchAggregateEvents; private removeAggregateFromMarkedDispatchList; private findMarkedAggregateByID; private dispatch; } /** * Singleton instance of the domain events registry. * Use this to register handlers and dispatch events across the application. */ declare const DomainEvents: DomainEventsImplementation; /** * Base contract for all domain event handlers. * * An event handler is responsible for subscribing to domain events * and executing side effects in response — such as persisting records, * broadcasting WebSocket messages, sending emails, or triggering * external integrations. * * Implement {@link setupSubscriptions} to register callbacks in the * {@link DomainEvents} registry, and {@link handle} to define the * reaction logic for the subscribed event. * * @typeParam T - The specific {@link DomainEvent} this handler reacts to. * * @example * ```ts * class OnUserCreated implements EventHandler { * constructor(private readonly mailer: Mailer) { * this.setupSubscriptions() * } * * setupSubscriptions(): void { * DomainEvents.register( * this.handle.bind(this), * UserCreatedEvent.name, * ) * } * * async handle(event: UserCreatedEvent): Promise { * await this.mailer.send(event.user.email) * } * } * ``` */ interface EventHandler2 { /** * Registers all event subscriptions for this handler. * * Should be called once during instantiation — typically * in the constructor — to ensure the handler is active * before any events are dispatched. */ setupSubscriptions(): void; /** * Executes the handler logic in response to a dispatched event. * * @param event - The domain event instance that was dispatched. */ handle(event: T): Promise; } /** * Make some property optional on type * * @example * ```typescript * type Post { * id: string; * name: string; * email: string; * } * * Optional * ``` */ type Optional< T, K extends keyof T > = Pick, K> & Omit; /** * Represents a unique identifier for a domain entity. * * Wraps a UUID v7 string, which is time-sortable and ideal for database * indexing. A new identifier is generated automatically if no value is provided. * * @example * ```ts * // auto-generated * const id = new UniqueEntityId() * * // from existing value (e.g. reconstructing from database) * const id = new UniqueEntityId("0195d810-5b3e-7000-8e3e-1a2b3c4d5e6f") * ``` */ declare class UniqueEntityId { private readonly value; constructor(value?: string); /** * Returns the identifier as a primitive string. * Useful for serialization and database persistence. */ toValue(): string; /** * Returns the string representation of the identifier. */ toString(): string; /** * Compares this identifier with another by value equality. * * @param id - The identifier to compare against * @returns `true` if both identifiers have the same value */ equals(id: UniqueEntityId): boolean; } /** * Base class for all value objects in the domain. * * A value object is an object defined entirely by its attributes — it has no * identity. Two value objects are considered equal if all their properties * are deeply equal, regardless of reference. * * Value objects should be immutable. Avoid mutating `props` directly; * instead, create a new instance with the updated values. * * All value objects must implement a static `create` factory method to * encapsulate construction and validation logic, keeping the constructor * protected from external instantiation. * * @template Props - The shape of the value object's properties * * @example * ```ts * interface EmailProps { * value: string * } * * class Email extends ValueObject { * get value() { return this.props.value } * * static create(email: string): Email { * if (!email.includes("@")) { * throw new ValidationError("Invalid email address.") * } * * return new Email({ value: email }) * } * } * ``` */ declare abstract class ValueObject { protected props: Props; protected constructor(props: Props); /** * Compares this value object with another by deep equality of their properties. * * @param other - The value object to compare against * @returns `true` if both value objects have deeply equal properties */ equals(other: ValueObject): boolean; } /** * Tracks additions and removals of items in a collection, enabling * efficient persistence of only what changed. * * Commonly used inside aggregate roots to manage one-to-many relationships * without rewriting the entire collection on every save — only new and * removed items are persisted. * * Subclasses must implement {@link compareItems} to define equality between items. * * @template T - The type of items in the list * * @example * ```ts * class TagList extends WatchedList { * compareItems(a: Tag, b: Tag): boolean { * return a.id.equals(b.id) * } * * static create(tags: Tag[]): TagList { * return new TagList(tags) * } * } * * const tags = TagList.create([existingTag]) * * tags.add(newTag) // tracked as new * tags.remove(oldTag) // tracked as removed * * tags.getNewItems() // [newTag] * tags.getRemovedItems() // [oldTag] * ``` */ declare abstract class WatchedList { protected currentItems: T[]; protected initial: T[]; protected new: T[]; protected removed: T[]; constructor(initialItems?: T[]); /** * Returns all current items in the list. */ getItems(): T[]; /** * Returns items that were added since the list was created. */ getNewItems(): T[]; /** * Returns items that were removed since the list was created. */ getRemovedItems(): T[]; /** * Returns whether the given item exists in the current list. * * @param item - The item to check */ exists(item: T): boolean; /** * Adds an item to the list, tracking it as new if it wasn't in the initial set. * If the item was previously removed, it is restored. * * @param item - The item to add */ add(item: T): void; /** * Removes an item from the list, tracking it as removed if it was in the initial set. * * @param item - The item to remove */ remove(item: T): void; /** * Replaces the entire list with a new set of items, automatically * computing what was added and what was removed. * * @param items - The new set of items */ update(items: T[]): void; private isCurrentItem; private isNewItem; private isRemovedItem; private removeFromNew; private removeFromCurrent; private removeFromRemoved; private wasAddedInitially; } /** * Base contract for all use case errors. * * Implement this interface to define semantic, domain-aware errors * that can be returned as the left side of an {@link Either}. * * @example * ```ts * class UserNotFoundError implements UseCaseError { * message = "User not found." * } * * type FindUserResult = Either * ``` */ interface UseCaseError { message: string; } /** * Represents the expected output shape of any use case. * * Three valid shapes: * - `Either`: returns a concrete value or fails with an error * - `Maybe`: returns an optional value when the error path is irrelevant * - `Nothing`: void result; covered since `Nothing extends Maybe` */ type UseCaseOutput = Either | Maybe2; /** * Base contract for all application use cases. * * A use case orchestrates domain logic for a single application operation. * It receives a typed input, interacts with the domain, and returns an * {@link Either} — never throwing exceptions directly. * * The left side carries a {@link UseCaseError} on failure. * The right side carries the success value. * * @template Input - The shape of the data required to execute the use case * @template Output - The expected {@link Either} output, constrained to {@link UseCaseOutput} * * @example * ```ts * type Input = { userId: string } * type Output = Either * * class GetUserUseCase implements UseCase { * constructor(private userRepository: UserRepository) {} * * async execute({ userId }: Input): Promise { * const user = await this.userRepository.findById(userId) * if (!user) return left(new UserNotFoundError(userId)) * return right(user) * } * } * ``` */ interface UseCase< Input, Output extends UseCaseOutput > { execute(input: Input): Promise; } export { DomainEvent, Creatable, Deletable, Findable, Saveable, Repository, UseCaseError, UseCase, Entity, AggregateRoot, EventHandler, DomainEvents, EventHandler2 as EventHandler1, Optional, UniqueEntityId, ValueObject, WatchedList, attempt, Either, from, Just, just, Left, left, Maybe2 as Maybe, maybe, Nothing, nothing, Right, right };