236 lines
7.8 KiB
TypeScript
236 lines
7.8 KiB
TypeScript
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<UserEntity, UserDTO> {
|
|
* 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<TEntity extends BaseEntity = BaseEntity, TDTO = TEntity> {
|
|
/**
|
|
* 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<TEntity>): Partial<TDTO>;
|
|
|
|
/**
|
|
* Transform the payload before an `edit()` call.
|
|
* If not provided, falls back to `transformToDTO`.
|
|
*/
|
|
transformEditPayload?(entity: Partial<TEntity>): Partial<TDTO>;
|
|
|
|
/**
|
|
* Transform the filter payload before a `getMany()` call.
|
|
* If not provided, falls back to identity.
|
|
*/
|
|
transformPayloadFilter?(filter: Record<string, any>): Record<string, any>;
|
|
}
|
|
|
|
// ─── 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<BookingEntity, BookingDTO> {
|
|
* 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<TEntity extends BaseEntity = BaseEntity, TDTO = TEntity>
|
|
implements IDataTransformer<TEntity, TDTO>
|
|
{
|
|
/**
|
|
* 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<TEntity>): Partial<TDTO> {
|
|
return this.transformToDTO(entity as TEntity) as Partial<TDTO>;
|
|
}
|
|
|
|
/**
|
|
* 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<TEntity>): Partial<TDTO> {
|
|
return this.transformToDTO(entity as TEntity) as Partial<TDTO>;
|
|
}
|
|
|
|
/**
|
|
* 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<string, any>): Record<string, any> {
|
|
return filter;
|
|
}
|
|
}
|