import type { BaseEntity } from './types'; // ─── Core Transformer Interface ───────────────────────────────── /** * Minimal contract for bidirectional data transformation * between API Data Transfer Objects (DTOs) and domain entities. * * Implement this interface when you only need the two core * mapping methods without operation-specific hooks. * * @typeParam TEntity - The frontend domain entity type * @typeParam TDTO - The API response/request DTO shape * * @example * ```ts * class UserTransformer implements IDataTransformer { * transformToEntity(dto: UserDTO): UserEntity { * return { id: dto.id, fullName: `${dto.first_name} ${dto.last_name}` }; * } * transformToDTO(entity: UserEntity): UserDTO { * const [first, ...rest] = entity.fullName.split(' '); * return { id: entity.id, first_name: first, last_name: rest.join(' ') }; * } * } * ``` */ export interface IDataTransformer { /** * Transform an API DTO into a domain entity. * * Called after receiving data from the API. Use this to map * snake_case fields to camelCase, flatten nested structures, * compute derived fields, or apply any normalization. * * @param dto - Raw data from the API response * @returns The mapped domain entity */ transformToEntity(dto: TDTO): TEntity; /** * Transform a domain entity into an API DTO. * * Called before sending data to the API. Use this to map * camelCase fields to snake_case, restructure nested objects, * or strip frontend-only computed fields. * * @param entity - Domain entity from the frontend * @returns The mapped DTO for the API request */ transformToDTO(entity: TEntity): TDTO; // ─── Optional Operation-Specific Hooks ────────────────────── /** * Transform the response of a `getOne()` call. * If not provided, falls back to `transformToEntity`. */ transformGetOneResponse?(dto: TDTO): TEntity; /** * Transform the response of a `getMany()` call. * If not provided, falls back to mapping each item via `transformToEntity`. */ transformGetManyResponse?(dtos: TDTO[]): TEntity[]; /** * Transform the payload before a `create()` call. * If not provided, falls back to `transformToDTO`. */ transformCreatePayload?(entity: Partial): Partial; /** * Transform the payload before an `edit()` call. * If not provided, falls back to `transformToDTO`. */ transformEditPayload?(entity: Partial): Partial; /** * Transform the filter payload before a `getMany()` call. * If not provided, falls back to identity. */ transformPayloadFilter?(filter: Record): Record; } // ─── Abstract Base Transformer ────────────────────────────────── /** * Abstract base class providing operation-specific transformation * hooks with sensible passthrough defaults. * * Extends the core `IDataTransformer` contract with granular hooks * for each CRUD operation. Override only the hooks you need — * un overridden hooks delegate to the core `transformToEntity` / * `transformToDTO` methods. * * **Design rationale (from legacy analysis):** * The legacy `BaseTransformer` used operation-specific methods * (`transformerGetOne`, `transformerCreate`, etc.) because different * operations often need different transformations. For example, * `getOne` might need to compute derived fields, while `create` * might need to strip IDs. This pattern is preserved here with * proper typing. * * @typeParam TEntity - The frontend domain entity type * @typeParam TDTO - The API response/request DTO shape * * @example * ```ts * class BookingTransformer extends BaseDataTransformer { * transformToEntity(dto: BookingDTO): BookingEntity { * return { * id: dto.id, * bookingCode: dto.booking_code, * customerName: dto.customer_name, * }; * } * * transformToDTO(entity: BookingEntity): BookingDTO { * return { * id: entity.id, * booking_code: entity.bookingCode, * customer_name: entity.customerName, * }; * } * * // Override only when getOne needs extra computed fields * transformGetOneResponse(dto: BookingDTO): BookingEntity { * const entity = this.transformToEntity(dto); * return { ...entity, durationNights: computeNights(dto) }; * } * } * ``` */ export abstract class BaseDataTransformer implements IDataTransformer { /** * Core DTO → Entity transformation. * * Default implementation performs an identity cast. * Override this in concrete transformers to provide * the actual mapping logic. * * @param dto - Raw data from the API response * @returns The mapped domain entity */ transformToEntity(dto: TDTO): TEntity { return dto as unknown as TEntity; } /** * Core Entity → DTO transformation. * * Default implementation performs an identity cast. * Override this in concrete transformers to provide * the actual mapping logic. * * @param entity - Domain entity from the frontend * @returns The mapped DTO for the API request */ transformToDTO(entity: TEntity): TDTO { return entity as unknown as TDTO; } // ─── Operation-Specific Hooks ─────────────────────────────── /** * Transform the response of a `getOne()` call. * * Override this when `getOne` needs additional computed fields * or different mapping than the default `transformToEntity`. * * @param dto - Single DTO from the API response * @returns The mapped domain entity */ transformGetOneResponse(dto: TDTO): TEntity { return this.transformToEntity(dto); } /** * Transform the response of a `getMany()` call. * * Override this when list responses need bulk transformations * (e.g., sorting, filtering, or status normalization) beyond * per-item mapping. * * @param dtos - Array of DTOs from the API response * @returns Array of mapped domain entities */ transformGetManyResponse(dtos: TDTO[]): TEntity[] { return dtos.map((dto) => this.transformToEntity(dto)); } /** * Transform the payload before a `create()` call. * * Override this when create payloads need special handling * (e.g., stripping IDs, formatting dates, converting nested * structures). * * @param entity - Partial entity data from the frontend * @returns The mapped partial DTO for the API request */ transformCreatePayload(entity: Partial): Partial { return this.transformToDTO(entity as TEntity) as Partial; } /** * Transform the payload before an `edit()` call. * * Override this when edit payloads need special handling * that differs from create (e.g., preserving certain * read-only fields, handling delta updates). * * @param entity - Partial entity data from the frontend * @returns The mapped partial DTO for the API request */ transformEditPayload(entity: Partial): Partial { return this.transformToDTO(entity as TEntity) as Partial; } /** * Transform the filter payload before a `getMany()` call. * * Override this when getMany filters need special formatting * (e.g., date formats, converting arrays to comma-separated strings). * * @param filter - The filter params from the frontend * @returns The mapped filter params for the API query */ transformPayloadFilter(filter: Record): Record { return filter; } }