Platform
    • FSD Layers
    • Entity Structure
Projects
  • API Connections & Tools
  1. Documentation
  2. Documentation

Business Domain (Entities)

Entities are the core business units of the project. This section describes the unified structure of any entity based on strict architectural rules.

Entities Layer Structure

Each business entity inside src/entities/* must follow a strict directory structure for logic and UI isolation.

File Structure

Standard Entity Modules

A complete entity consists of these required modules:

01/api

RTK Query endpoints for the entity. Converters run inside the API layer: transformResponse maps backend T*Backend to frontend models, and query maps params/body via map*ToBackend. Hooks already return domain models — UI must not re-map DTOs.

Learn RTK Query architecture
api/[entity-name].service.ts
1import { baseApi } from "@/shared/api/backend"; 2import type { IPaginationRequest, IPaginationResponse } from "@/shared/types"; 3import { 4 mapEntitiesQueryToBackend, 5 mapEntitiesToFrontend 6} from "../converters"; 7import type { TEntitiesBackend, IEntity } from "../types"; 8 9/** Converters run here — hooks return frontend models, not raw DTOs */ 10export const entityApi = baseApi.injectEndpoints({ 11 endpoints: (builder) => ({ 12 getEntities: builder.query< 13 IPaginationResponse<IEntity>, 14 IPaginationRequest 15 >({ 16 query: (filters) => ({ 17 url: "/entities", 18 params: mapEntitiesQueryToBackend(filters) 19 }), 20 transformResponse: (response: TEntitiesBackend) => 21 mapEntitiesToFrontend(response), 22 providesTags: [ENUM_API_TAGS.ENTITIES] 23 }) 24 }) 25});

02/converters

Data mappers. They transform a raw server DTO into a convenient Domain Model. This isolates UI components from sudden changes in backend contracts.

Converters architecture
converters/[entity-name].converters.ts
1export const mapEntityToFrontend = (data: TEntityBackend): IEntity => ({ 2 id: data.id, 3 type: data.type as ENUM_ENTITY_TYPE, 4 dateCreated: formatDate(data.date_created), 5 client: data.client, 6 status: data.status as ENUM_ENTITY_STATUS 7}); 8 9export const mapEntitiesToFrontend = ( 10 response: TEntitiesBackend 11): IPaginationResponse<IEntity> => ({ 12 data: response.items.map(mapEntityToFrontend), 13 total: response.total_count 14}); 15 16export const mapEntityToBackend = ( 17 data: Partial<IEntityDetail> 18): Partial<TEntityDetailBackend> => ({ 19 id: data.id, 20 type: data.type, 21 date_created: data.dateCreated, 22 client: data.client, 23 status: data.status 24}); 25

03/handlers

MSW (Mock Service Worker) handlers. Used to intercept network calls during development and in integration tests (Vitest / RTL).

How MSW works
handlers/[entity-name].handlers.ts
1import { HttpResponse } from "msw"; 2import { createMockHandler } from "@/shared/api/msw/utils"; 3import { listEntitiesBackend } from "../mock"; 4 5/** Backend DTO responses — converters run in RTK transformResponse */ 6export const entityHandlers = [ 7 createMockHandler( 8 { url: "/entities", method: "GET", _types: { body: undefined } }, 9 () => HttpResponse.json(listEntitiesBackend()) 10 ), 11 createMockHandler( 12 { url: "/entities/:id", method: "GET", _types: { body: undefined } }, 13 ({ params }) => { 14 const { items } = listEntitiesBackend(); 15 const entity = items.find((e) => e.id === params.id); 16 if (!entity) return new HttpResponse(null, { status: 404 }); 17 return HttpResponse.json(entity); 18 } 19 ) 20];

04/mock

Static fixtures and fake data (mock data) used to emulate server responses in conjunction with MSW handlers or for Storybook.

MSW and mock data section
mock/[entity-name].mock.ts
1import type { TEntityBackend } from "../types"; 2 3export const ENTITIES_MOCK: TEntityBackend[] = [ 4 { 5 id: "ENT-001", 6 type: "individual", 7 date_created: "2024-03-15T12:00:00Z", 8 client: "John Doe", 9 pax: 2, 10 status_code: "CONFIRMED" 11 } 12];

05/schema

Single Source of Truth for entity data types. Contains Zod objects with built-in internationalization (i18n) of errors.

Using Zod
schema/[entity-name]-create.schema.ts
1import { z } from "zod"; 2import { i18nKey } from "@/shared/config"; 3// прямой путь к form.types — не barrel "../types" 4import { ENUM_FORM_ENTITY_CREATE } from "../types/entity-create-form.types"; 5 6const msg = i18nKey<TEntityKeys>(); 7const F = ENUM_FORM_ENTITY_CREATE; 8 9/** Runtime only — inferred form types belong in types/ (одна пара на операцию) */ 10export const ENTITY_CREATE_SCHEMA = z.object({ 11 [F.CLIENT]: z.string().min(1, { message: msg("errors.client.required") }), 12 [F.STATUS]: z.enum(["PENDING", "CONFIRMED"]), 13 [F.PAX]: z.number().min(1) 14});
types/[entity-name]-create-form.types.ts
1import type { FieldValues } from "react-hook-form"; 2import { z } from "zod"; 3// только import type — в рантайме зависимость schema → form.types односторонняя 4import type { ENTITY_CREATE_SCHEMA } from "../schema"; 5 6export const ENUM_FORM_ENTITY_CREATE = { 7 CLIENT: "client", 8 STATUS: "status", 9 PAX: "pax" 10} as const; 11 12export type ENUM_FORM_ENTITY_CREATE_TYPE = 13 (typeof ENUM_FORM_ENTITY_CREATE)[keyof typeof ENUM_FORM_ENTITY_CREATE]; 14 15export type TEntityCreateSchema = z.infer<typeof ENTITY_CREATE_SCHEMA> & 16 FieldValues;

06/slice

Redux state of the entity (if it stores global data). Selectors and actions often reside here as well. Note: state should depend solely on the Domain Model, not on DTOs.

slice/[entity-name].slice.ts
1import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 3interface IEntitySliceState { 4 selectedId: string | null; 5} 6 7const initialState: IEntitySliceState = { 8 selectedId: null 9}; 10 11export const entitySlice = createSlice({ 12 name: "entity", 13 initialState, 14 reducers: { 15 setSelectedEntity: (state, action: PayloadAction<string>) => { 16 state.selectedId = action.payload; 17 } 18 } 19});

07/ui

Dump or Smart components at the entity level. For example: UserCard, UserAvatar. Can be reused across different features and widgets.

ui/[entity-name]-card.tsx
1interface IEntityCardProps { 2 entity: IEntity; 3 onSelect: (id: string) => void; 4} 5 6export const EntityCard = ({ entity, onSelect }: IEntityCardProps) => ( 7 <Card onClick={() => onSelect(entity.id)}> 8 <CardHeader>{entity.client}</CardHeader> 9 <CardContent>Pax: {entity.pax}</CardContent> 10 </Card> 11);

08/types

Global TS interfaces and types for a specific entity (e.g., Domain Model interface if Zod doesn't cover all logic). Type inference from Zod schemas is recommended, but when not possible, types are defined here.

Type inference in Zod
types/[entity-name].interface.ts
1import type { IPaginationRequest, IPaginationResponse } from "@/shared/types"; 2 3export interface IEntityDates { 4 from: string; 5 to: string; 6} 7 8export interface IEntity { 9 id: string; 10 type: ENUM_ENTITY_TYPE; 11 dateCreated: string; 12 client: string; 13 pax: number; 14 dates: IEntityDates; 15 status: ENUM_ENTITY_STATUS; 16} 17 18export type TEntitiesPage = IPaginationResponse<IEntity>; 19export interface IEntitiesQuery extends IPaginationRequest {} 20