Sonamu는 Entity 정의로부터 다양한 타입을 자동 생성합니다. Base 타입뿐만 아니라 API 파라미터, Subset 타입, Enum 등이 자동으로 생성되어 완벽한 타입 안전성을 제공합니다.
생성 타입 개요
Base 타입 Entity의 기본 타입 User, Post, Comment
Subset 타입 부분 조회 타입 UserA, UserP, UserSS
Params 타입 API 파라미터 타입 ListParams, SaveParams
Enum 타입 Enum과 헬퍼 함수 UserRole, userRoleLabel
Base 타입
Entity의 모든 필드를 포함하는 기본 타입입니다.
user.entity.json
user.types.ts (자동 생성)
{
"id" : "User" ,
"props" : [
{ "name" : "id" , "type" : "integer" },
{ "name" : "email" , "type" : "string" },
{ "name" : "username" , "type" : "string" },
{ "name" : "created_at" , "type" : "date" }
]
}
특징 :
Relation의 HasMany, ManyToMany는 제외
BelongsToOne, OneToOne(hasJoinColumn)은 {name}_id 형태로 포함
Virtual 필드는 타입만 정의, 값은 Enhancer에서 계산
Subset 타입
Entity의 부분 필드만 포함하는 타입입니다.
user.entity.json
sonamu.generated.ts (자동 생성)
{
"subsets" : {
"A" : [ "id" , "email" , "username" , "created_at" ],
"P" : [ "id" , "email" , "employee.id" , "employee.department.name" ],
"SS" : [ "id" , "email" ]
}
}
사용 예시 :
import type { UserSubsetKey , UserSubsetMapping } from "./sonamu.generated" ;
// 제네릭으로 Subset 타입 선택
async function findMany < T extends UserSubsetKey >(
subset : T
) : Promise < UserSubsetMapping [ T ][]> {
// ...
}
const users = await findMany ( "A" ); // UserA[] 타입
const profiles = await findMany ( "P" ); // UserP[] 타입
ListParams 타입
목록 조회 API의 파라미터 타입입니다.
user.entity.json
user.types.ts (자동 생성)
{
"props" : [
{ "name" : "email" , "type" : "string" , "toFilter" : true },
{ "name" : "username" , "type" : "string" , "toFilter" : true }
]
}
사용 예시 :
// API 호출
const result = await findManyUsers ({
num: 20 ,
page: 1 ,
search: "email" ,
keyword: "john" ,
orderBy: "created_at-desc" ,
email: [ "[email protected] " , "[email protected] " ], // 배열도 가능
});
zArrayable : 단일 값과 배열을 모두 허용하는 헬퍼입니다.
SaveParams 타입
저장 API의 파라미터 타입입니다.
user.types.ts (자동 생성)
사용 예시
// SaveParams = Base 타입의 id를 optional로
export const UserSaveParams = User . partial ({ id: true });
export type UserSaveParams = z . infer < typeof UserSaveParams >;
// 동일:
// type UserSaveParams = {
// id?: number;
// email: string;
// username: string;
// created_at: Date;
// }
특징 :
id만 optional, 나머지 필드는 필수
id가 있으면 UPDATE, 없으면 INSERT
배열로 여러 레코드 동시 저장 가능
Enum 타입
Entity의 Enum이 자동으로 생성됩니다.
user.entity.json
sonamu.generated.ts (자동 생성)
{
"enums" : {
"UserRole" : {
"admin" : "관리자" ,
"moderator" : "운영자" ,
"normal" : "일반 사용자"
}
}
}
사용 예시 :
import { UserRole , userRoleLabel } from "./sonamu.generated" ;
// 타입 안전한 Enum 사용
const role : UserRole = "admin" ;
// 라벨 표시
console . log ( userRoleLabel ( role )); // "관리자"
// UI에서 선택 옵션 생성
const options = UserRole . options . map (( value ) => ({
value ,
label: userRoleLabel ( value ),
}));
// [
// { value: "admin", label: "관리자" },
// { value: "moderator", label: "운영자" },
// { value: "normal", label: "일반 사용자" }
// ]
커스텀 Params 타입
Model에서 추가 API를 만들 때 커스텀 파라미터 타입을 정의합니다.
user.types.ts
user.model.ts
// 커스텀 파라미터 추가
export const UserLoginParams = z . object ({
email: z . string (). email (),
password: z . string (). min ( 8 ),
rememberMe: z . boolean (). optional (),
});
export type UserLoginParams = z . infer < typeof UserLoginParams >;
export const UserRegisterParams = User . pick ({
email: true ,
username: true ,
}). extend ({
password: z . string (). min ( 8 ),
passwordConfirm: z . string (). min ( 8 ),
}). refine (
( data ) => data . password === data . passwordConfirm ,
{ message: "비밀번호가 일치하지 않습니다" , path: [ "passwordConfirm" ] }
);
export type UserRegisterParams = z . infer < typeof UserRegisterParams >;
자동 생성 :
// services.generated.ts
export async function loginUser (
params : UserLoginParams
) : Promise <{ user : User ; token : string }> {
const { data } = await axios . post ( "/user/login" , { params });
return data ;
}
export function useLoginUser () {
return useMutation ({
mutationFn : ( params : UserLoginParams ) => loginUser ( params ),
});
}
Action Params 타입
특정 액션을 위한 파라미터 타입입니다.
user.types.ts
user.model.ts
// ID 배열 파라미터
export const UserBulkDeleteParams = z . object ({
ids: z . number (). int (). array (). min ( 1 ),
});
export type UserBulkDeleteParams = z . infer < typeof UserBulkDeleteParams >;
// 상태 변경 파라미터
export const UserStatusUpdateParams = z . object ({
ids: z . number (). int (). array (). min ( 1 ),
status: z . enum ([ "active" , "suspended" , "deleted" ]),
reason: z . string (). optional (),
});
export type UserStatusUpdateParams = z . infer < typeof UserStatusUpdateParams >;
ListResult 타입
목록 조회 결과의 타입입니다.
export type ListResult <
LP extends { num ?: number ; page ?: number ; queryMode ?: string },
T
> = LP [ "queryMode" ] extends "list"
? { rows : T [] }
: LP [ "queryMode" ] extends "count"
? { total : number }
: { rows : T []; total : number };
사용 예시 :
// queryMode에 따라 타입이 자동으로 결정됨
// queryMode: "both" (기본값)
const result = await findManyUsers ({ num: 10 , page: 1 });
// ListResult<UserListParams, User> = { rows: User[]; total: number }
// queryMode: "list"
const result = await findManyUsers ({ num: 10 , page: 1 , queryMode: "list" });
// ListResult<UserListParams, User> = { rows: User[] }
// queryMode: "count"
const result = await findManyUsers ({ queryMode: "count" });
// ListResult<UserListParams, User> = { total: number }
조건부 타입 : ListResult는 TypeScript의 조건부 타입을 활용하여
queryMode에 따라 다른 타입을 반환합니다.
타입 내보내기
생성된 타입은 자동으로 export됩니다.
// Entity별 타입 전부 re-export
export * from "./user/user.model" ;
export * from "./post/post.model" ;
export * from "./comment/comment.model" ;
// Model 인스턴스도 export
export { UserModel } from "./user/user.model" ;
export { PostModel } from "./post/post.model" ;
사용 :
// 한 곳에서 import
import {
User ,
UserListParams ,
UserSaveParams ,
UserRole ,
} from "./sonamu.generated" ;
// 또는 개별 import
import type { User } from "./user/user.types" ;
import { UserModel } from "./user/user.model" ;
타입 유틸리티
Sonamu가 제공하는 유용한 타입 유틸리티입니다.
zArrayable
단일 값과 배열을 모두 허용하는 타입입니다.
import { zArrayable } from "sonamu" ;
const UserListParams = z . object ({
id: zArrayable ( z . number (). int ()). optional (),
email: zArrayable ( z . string ()). optional (),
});
// 둘 다 가능
{
id : 1 ;
}
{
id : [ 1 , 2 , 3 ];
}
DistributiveOmit
일반 Omit보다 Union 타입에서 안전합니다.
import type { DistributiveOmit } from "sonamu" ;
type UserOrAdmin =
| { type : "user" ; userId : number }
| { type : "admin" ; adminId : number };
// DistributiveOmit은 각 타입에 개별 적용
type WithoutId = DistributiveOmit < UserOrAdmin , "userId" | "adminId" >;
// = { type: "user" } | { type: "admin" }
타입 생성 커스터마이징
자동 생성된 타입을 확장하거나 수정할 수 있습니다.
Params 확장
// 기본 ListParams 확장
export const ExtendedUserListParams = UserListParams . extend ({
includeDeleted: z . boolean (). optional (),
dateRange: z
. object ({
from: z . date (),
to: z . date (),
})
. optional (),
});
export type ExtendedUserListParams = z . infer < typeof ExtendedUserListParams >;
Subset 타입 확장
// Subset 타입에 계산 필드 추가
export type UserAWithStats = UserA & {
post_count : number ;
follower_count : number ;
};
조건부 타입 생성
// 역할에 따라 다른 타입
export type UserByRole < T extends UserRole > = T extends "admin"
? UserA & { permissions : string [] }
: T extends "normal"
? UserSS
: User ;
실전 예시
실제 프로젝트에서 생성 타입을 활용하는 예시입니다.
user.model.ts
UserList.tsx
import type {
UserSubsetKey ,
UserSubsetMapping ,
UserListParams ,
UserSaveParams ,
UserLoginParams ,
} from "./user.types" ;
class UserModelClass extends BaseModelClass <
UserSubsetKey ,
UserSubsetMapping ,
typeof userSubsetQueries ,
typeof userLoaderQueries
> {
> @ api ({ httpMethod: "GET" })
> async findMany < T extends UserSubsetKey >(
subset : T ,
params ?: UserListParams
) : Promise < ListResult < UserListParams , UserSubsetMapping [ T ]>> {
// 타입 안전한 구현
}
@ api ({ httpMethod: "POST" })
async save ( params : UserSaveParams []) : Promise < number []> {
// 타입 안전한 구현
}
@ api ({ httpMethod: "POST" })
async login ( params : UserLoginParams ) : Promise <{ user : User ; token : string }> {
// 타입 안전한 구현
}
}
다음 단계