메인 콘텐츠로 건너뛰기
Sonamu는 Entity 정의를 기반으로 완전한 타입 안전성을 제공하는 TypeScript 타입을 자동 생성합니다. 이 문서는 Entity의 각 필드 타입이 어떻게 TypeScript 타입으로 변환되는지 설명합니다.

타입 변환 개요

필드 타입

Entity Prop → TypeScript Type 자동 변환 및 타입 안전성

Nullable 지원

nullable: true → T | null 선택적 null 처리

Array 타입

type[] → T[] 배열 타입 자동 생성

Relation 타입

BelongsToOne → number (FK) 관계 필드 타입 변환

기본 타입 변환

Entity의 각 필드 타입이 TypeScript 타입으로 어떻게 변환되는지 보여줍니다.

숫자 타입

{
  "props": [
    { "name": "id", "type": "integer" },
    { "name": "view_count", "type": "integer", "nullable": true },
    { "name": "user_id", "type": "bigInteger" }
  ]
}
Entity TypeTypeScript Type설명
integernumber32bit 정수
integer[]number[]정수 배열
bigIntegerbigint64bit 정수 (큰 숫자)
bigInteger[]bigint[]큰 정수 배열
bigInteger vs integer: ID나 카운트는 integer로 충분하지만, 타임스탬프(milliseconds)나 매우 큰 숫자는 bigInteger를 사용하세요.

문자열 타입

{
  "props": [
    { "name": "email", "type": "string", "length": 100 },
    { "name": "bio", "type": "string" },
    { "name": "tags", "type": "string[]" }
  ]
}
Entity TypeTypeScript Type설명
stringstring문자열
string[]string[]문자열 배열
length 속성은 TypeScript 타입에 영향을 주지 않지만, Zod validation에서 .max(length) 검증으로 변환됩니다.

불린 타입

{
  "props": [
    { "name": "is_active", "type": "boolean" },
    { "name": "permissions", "type": "boolean[]" }
  ]
}

날짜 타입

{
  "props": [
    { "name": "created_at", "type": "date" },
    { "name": "login_history", "type": "date[]", "nullable": true }
  ]
}
JSON 직렬화: API 응답에서 Date는 ISO 문자열(string)로 전송됩니다. Zod의 z.date()는 자동으로 문자열을 Date 객체로 변환합니다.
// API 응답 (JSON)
{ "created_at": "2024-01-01T00:00:00.000Z" }

// Zod 파싱 후
{ created_at: Date }  // Date 객체로 자동 변환

UUID 타입

{
  "props": [
    { "name": "uuid", "type": "uuid" },
    { "name": "related_ids", "type": "uuid[]" }
  ]
}
TypeScript에는 UUID 전용 타입이 없으므로 string으로 표현됩니다. Zod validation에서 .uuid()로 형식을 검증합니다.

숫자 정밀도 타입

PostgreSQL의 고정밀 숫자를 다루는 두 가지 타입입니다.

number vs numeric

{
  "props": [
    { 
      "name": "price", 
      "type": "number",
      "precision": 10,
      "scale": 2
    },
    { 
      "name": "balance", 
      "type": "numeric",
      "precision": 20,
      "scale": 8
    }
  ]
}
Entity TypeTypeScript TypePostgreSQL Type용도
numbernumberreal, double precision, numeric일반 계산 (부동소수점)
numericstringnumeric금융 계산 (고정소수점)
number vs numeric 선택:number 사용 시기:
  • 일반적인 수치 계산
  • 소수점 정밀도가 중요하지 않은 경우
  • 과학 계산, 통계
numeric 사용 시기:
  • 금융 계산 (돈, 가격, 잔액)
  • 정확한 소수점이 필요한 경우
  • 암호화폐 금액
// number - 부동소수점 오차 발생 가능
0.1 + 0.2 === 0.3; // false (0.30000000000000004)

// numeric - 문자열로 정확한 값 보존
import Decimal from "decimal.js";
new Decimal("0.1").plus("0.2").equals("0.3"); // true

Enum 타입

Entity의 Enum이 TypeScript Union 타입으로 변환됩니다.
{
  "enums": {
    "UserRole": {
      "admin": "관리자",
      "moderator": "운영자",
      "normal": "일반 사용자"
    }
  },
  "props": [
    { "name": "role", "type": "enum", "id": "UserRole" },
    { "name": "badges", "type": "enum[]", "id": "UserBadge" }
  ]
}
장점:
  • 자동완성 지원
  • 타입 안전성 보장
  • 잘못된 값 컴파일 에러
// ✅ 올바른 사용
const user: User = {
  role: "admin", // 자동완성됨
};

// ❌ 컴파일 에러
const user: User = {
  role: "guest", // Error: Type '"guest"' is not assignable
};

JSON 타입

복잡한 객체 구조를 JSON으로 저장할 때 사용합니다.
{
  "props": [
    { "name": "metadata", "type": "json", "id": "PostMetadata" }
  ]
}
json 타입은 id로 지정한 Zod 스키마를 참조합니다. 이 스키마는 {entity} .types.ts에서 직접 정의해야 합니다.

Virtual 타입

데이터베이스에 저장되지 않는 계산 필드입니다.
{
  "props": [
    { "name": "full_name", "type": "virtual", "id": "UserFullName" }
  ]
}
Virtual 필드는 Model의 Enhancer에서 계산됩니다:
const enhancers = this.createEnhancers({
  A: (row) => ({
    ...row,
    full_name: `${row.first_name} ${row.last_name}`,
  }),
});

Vector 타입

벡터 검색 (pgvector)을 위한 타입입니다.
{
  "props": [
    { "name": "embedding", "type": "vector", "dimensions": 1536 },
    { "name": "embeddings_history", "type": "vector[]", "dimensions": 1536 }
  ]
}
dimensions는 TypeScript 타입에 영향을 주지 않지만, PostgreSQL 스키마와 Zod validation에서 사용됩니다.
// Zod validation
z.array(z.number()).length(1536); // dimensions 검증

Relation 타입

Entity 간 관계를 나타내는 필드입니다.

BelongsToOne (다대일)

{
  "props": [
    {
      "name": "user",
      "type": "relation",
      "relationType": "BelongsToOne",
      "with": "User"
    }
  ]
}
필드명 변환: useruser_idEntity에서는 user로 정의하지만, 실제 TypeScript 타입은 user_id가 됩니다.

OneToOne (일대일)

{
  "props": [
    {
      "name": "profile",
      "type": "relation",
      "relationType": "OneToOne",
      "with": "Profile",
      "hasJoinColumn": true
    }
  ]
}

HasMany / ManyToMany

{
  "props": [
    {
      "name": "posts",
      "type": "relation",
      "relationType": "HasMany",
      "with": "Post",
      "joinColumn": "user_id"
    }
  ]
}
HasManyManyToMany는 base 타입에 포함되지 않습니다. Subset을 정의하면 해당 타입이 Subset 타입에 포함됩니다.

Nullable 처리

nullable: true는 TypeScript Union 타입으로 변환됩니다.
{
  "props": [
    { "name": "nickname", "type": "string", "nullable": true },
    { "name": "tags", "type": "string[]", "nullable": true },
    {
      "name": "manager",
      "type": "relation",
      "relationType": "BelongsToOne",
      "with": "User",
      "nullable": true
    }
  ]
}
배열의 nullable: string[]nullable: true배열 자체가 null 가능함을 의미합니다.
// nullable: true
tags: string[] | null

// ✅ 가능
const user = { tags: null };
const user = { tags: [] };
const user = { tags: ["a", "b"] };

// ❌ 불가능
const user = { tags: undefined };

// 배열 요소가 nullable하려면 별도 타입 정의 필요
tags: (string | null)[]  // 이건 커스텀 타입으로 정의

타입 변환 전체 매핑

모든 Entity Prop 타입과 TypeScript 타입의 매핑표입니다.
Entity TypeTypeScript Type비고
integernumber32bit 정수
integer[]number[]정수 배열
bigIntegerbigint64bit 정수
bigInteger[]bigint[]큰 정수 배열
stringstring문자열
string[]string[]문자열 배열
numbernumber부동소수점
number[]number[]부동소수점 배열
numericstring고정소수점 (문자열로 정밀도 보존)
numeric[]string[]고정소수점 배열
booleanboolean불린
boolean[]boolean[]불린 배열
dateDate날짜 (JSON에서는 string)
date[]Date[]날짜 배열
uuidstringUUID 문자열
uuid[]string[]UUID 배열
enumEnumTypeUnion 타입
enum[]EnumType[]Union 타입 배열
jsonCustomType커스텀 객체 타입
virtualCustomType계산 필드
vectornumber[]벡터 (pgvector)
vector[]number[][]벡터 배열
tsvectorstring전문 검색 (PostgreSQL)
BelongsToOnenumber외래 키 (FK)
OneToOne (hasJoinColumn)number외래 키 (FK)
HasMany-Base 타입 미포함
ManyToMany-Base 타입 미포함

실전 예시

실제 User Entity의 타입 변환 예시입니다.
{
  "id": "User",
  "table": "users",
  "props": [
    { "name": "id", "type": "integer" },
    { "name": "email", "type": "string", "length": 100 },
    { "name": "username", "type": "string", "length": 50 },
    { "name": "role", "type": "enum", "id": "UserRole" },
    { "name": "is_active", "type": "boolean" },
    { "name": "bio", "type": "string", "nullable": true },
    { "name": "follower_count", "type": "integer" },
    { "name": "created_at", "type": "date" },
    { "name": "updated_at", "type": "date" },
    {
      "name": "profile",
      "type": "relation",
      "relationType": "OneToOne",
      "with": "Profile",
      "hasJoinColumn": true,
      "nullable": true
    }
  ],
  "enums": {
    "UserRole": {
      "admin": "관리자",
      "moderator": "운영자",
      "normal": "일반 사용자"
    }
  }
}

다음 단계