Type Implementation Guide - tonglam/letletme_data GitHub Wiki
Overview
This guide outlines the standardized approach for implementing domain types in our codebase. We follow a functional programming approach using fp-ts
, zod
for validation, and strict TypeScript typing.
File Structure
Each domain type file should follow this structure:
// ============ Branded Types ============
// ============ Types ============
// ============ Schemas ============
// ============ Type Transformers ============
// ============ Repository Interface ============
// ============ Persistence Types ============
// ============ Converters ============
Detailed Implementation Guide
1. Branded Types
Use branded types for domain-specific identifiers to ensure type safety:
import { Branded, createBrandedType } from './base.type';
export type TeamId = Branded<number, 'TeamId'>;
export const TeamId = createBrandedType<number, 'TeamId'>(
'TeamId',
(val): val is number => typeof val === 'number' && val > 0 && Number.isInteger(val),
);
2. Types
Define three main type categories:
2.1 API Response Types (snake_case)
export interface TeamResponse {
readonly id: number;
readonly short_name: string;
// ... other snake_case properties
}
2.2 Domain Types (camelCase)
export interface Team {
readonly id: TeamId;
readonly shortName: string;
// ... other camelCase properties
}
export type Teams = readonly Team[];
2.3 Persistence Types
export interface PrismaTeam {
readonly id: number;
readonly shortName: string;
readonly createdAt: Date;
// ... other properties
}
export interface PrismaTeamCreate extends Omit<PrismaTeam, 'id' | 'createdAt'> {
readonly id?: number;
readonly createdAt: Date;
}
3. Schemas
Use Zod for validation:
export const TeamResponseSchema = z.object({
id: z.number(),
short_name: z.string(),
// ... other validations
});
export const TeamsResponseSchema = z.array(TeamResponseSchema);
4. Type Transformers
Implement pure functions for type transformation:
export const toDomainTeam = (raw: TeamResponse): E.Either<string, Team> =>
pipe(
TeamId.validate(raw.id),
E.map((id) => ({
id,
shortName: raw.short_name,
// ... transform other properties
})),
);
5. Repository Interface
Use the base repository interface:
export type TeamRepository = BaseRepository<PrismaTeam, PrismaTeamCreate, TeamId>;
6. Converters
Implement converters between different type representations:
export const convertPrismaTeams = (teams: readonly PrismaTeam[]): TE.TaskEither<APIError, Teams> =>
pipe(
teams,
TE.traverseArray((team) =>
pipe(
prismaToResponse(team),
toDomainTeam,
TE.fromEither,
TE.mapLeft((error) => createError('Failed to convert team', error)),
),
),
);
export const prismaToResponse = (team: PrismaTeam): TeamResponse => ({
id: team.id,
short_name: team.shortName,
// ... convert other properties
});
Best Practices
1. Type Safety
- Always use
readonly
for immutability - Use branded types for domain identifiers
- Avoid type assertions (
as
) unless absolutely necessary - Use strict null checks
2. Validation
- Use Zod schemas for runtime validation
- Validate at domain boundaries
- Handle all possible error cases
3. Transformation
- Use pure functions for transformations
- Handle errors using
Either
- Use
pipe
for function composition - Maintain type safety throughout transformations
4. Naming Conventions
- Response types: snake_case (matching API)
- Domain types: camelCase
- Persistence types: camelCase
- Type names: PascalCase
- Functions: camelCase
5. Error Handling
- Use
Either
for synchronous operations - Use
TaskEither
for asynchronous operations - Provide descriptive error messages
- Handle all edge cases
Example Implementation
See src/types/teams.type.ts
for a complete example implementation following these guidelines.
Common Pitfalls to Avoid
- Mixing snake_case and camelCase within the same type
- Using type assertions instead of proper validation
- Not handling null/undefined cases
- Forgetting to make properties readonly
- Not using branded types for domain identifiers
- Direct type casting without validation
Testing
- Write unit tests for type transformations
- Test validation edge cases
- Test error handling
- Test type conversions
Dependencies
fp-ts
for functional programming utilitieszod
for runtime type validation- TypeScript for static typing