메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
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"
    }
  ]
}
ν•„λ“œλͺ… λ³€ν™˜: user β†’ user_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"
    }
  ]
}
HasMany와 ManyToManyλŠ” 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": "일반 μ‚¬μš©μž"
    }
  }
}

λ‹€μŒ 단계