Files
trackgo-fe/packages/core-api/src/data-services/base-data.transformer.ts
T

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;
}
}