메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
pnpm stub λͺ…λ Ήμ–΄λŠ” 반볡 μž‘μ—…μ„ 쀄이기 μœ„ν•œ μ½”λ“œ ν…œν”Œλ¦Ώμ„ μžλ™ μƒμ„±ν•©λ‹ˆλ‹€. Practice μŠ€ν¬λ¦½νŠΈλ‚˜ μƒˆ Entityλ₯Ό λΉ λ₯΄κ²Œ λ§Œλ“€ 수 μžˆμ–΄ 개발 속도λ₯Ό λ†’μž…λ‹ˆλ‹€.

λͺ…λ Ήμ–΄

practice - Practice 슀크립트 생성

μž„μ‹œ μ‹€ν—˜μ΄λ‚˜ 데이터 처리λ₯Ό μœ„ν•œ 슀크립트λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
pnpm stub practice <name>
μ˜ˆμ‹œ:
pnpm stub practice test-api

# 생성됨:
# src/practices/p1-test-api.ts
# VS Codeμ—μ„œ μžλ™μœΌλ‘œ μ—΄λ¦Ό
# μ‹€ν–‰ λͺ…λ Ήμ–΄κ°€ ν΄λ¦½λ³΄λ“œμ— 볡사됨
μƒμ„±λœ 파일:
src/practices/p1-test-api.ts
import { Sonamu } from "sonamu";

console.clear();
console.log("p1-test-api.ts");

Sonamu.runScript(async () => {
  // TODO
});
μ‹€ν–‰:
# ν΄λ¦½λ³΄λ“œμ—μ„œ λΆ™μ—¬λ„£κΈ° (Cmd+V)
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-test-api.js

entity - Entity 생성

μƒˆλ‘œμš΄ Entityλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
pnpm stub entity <entityId>
μ˜ˆμ‹œ:
pnpm stub entity Product

# 생성됨:
# src/entities/Product.entity.ts
μƒμ„±λœ 파일:
src/entities/Product.entity.ts
import type { EntityType } from "sonamu";

export const ProductEntity = {
  properties: [
    {
      name: "id",
      type: "int",
      primaryKey: true,
      autoIncrement: true,
    },
    {
      name: "title",
      type: "string",
      length: 255,
    },
    {
      name: "created_at",
      type: "datetime",
      default: "CURRENT_TIMESTAMP",
    },
  ],
} satisfies EntityType;

Practice 슀크립트

μ‚¬μš© μ‹œλ‚˜λ¦¬μ˜€

Practice μŠ€ν¬λ¦½νŠΈλŠ” λ‹€μŒκ³Ό 같은 μž‘μ—…μ— ν™œμš©λ©λ‹ˆλ‹€:
μš©λ„μ„€λͺ…μ˜ˆμ‹œ
API ν…ŒμŠ€νŠΈμ™ΈλΆ€ API 호좜 ν…ŒμŠ€νŠΈp1-test-payment-api.ts
데이터 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μΌνšŒμ„± 데이터 λ³€ν™˜p2-migrate-user-data.ts
λ””λ²„κΉ…νŠΉμ • 둜직 검증p3-debug-cache-issue.ts
데이터 μƒμ„±ν…ŒμŠ€νŠΈ 데이터 생성p4-create-sample-posts.ts
뢄석데이터 톡계 확인p5-analyze-user-activity.ts

μˆœμ„œ 관리

Practice νŒŒμΌμ€ μžλ™μœΌλ‘œ λ²ˆν˜Έκ°€ λΆ€μ—¬λ©λ‹ˆλ‹€:
πŸ“src/practices/
πŸ“„TSp1-first-practice.ts
πŸ“„TSp2-second-practice.ts
πŸ“„TSp3-third-practice.ts - λ‹€μŒμ€ p4
μƒˆ practiceλ₯Ό μƒμ„±ν•˜λ©΄ κ°€μž₯ 큰 번호 + 1이 μžλ™μœΌλ‘œ ν• λ‹Ήλ©λ‹ˆλ‹€.

μ‹€μ „ 예제

1. API ν…ŒμŠ€νŠΈ

src/practices/p1-test-payment-api.ts
import { Sonamu } from "sonamu";

console.clear();
console.log("p1-test-payment-api.ts");

Sonamu.runScript(async () => {
  const paymentService = new PaymentService();
  
  // ν…ŒμŠ€νŠΈ 결제 μ‹œλ„
  const result = await paymentService.charge({
    amount: 10000,
    orderId: "TEST-001",
  });
  
  console.log("결제 결과:", result);
});

2. 데이터 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜

src/practices/p2-migrate-user-data.ts
import { Sonamu } from "sonamu";
import { UserModel } from "../models/User.model";

console.clear();
console.log("p2-migrate-user-data.ts");

Sonamu.runScript(async () => {
  // λͺ¨λ“  μ‚¬μš©μž 쑰회
  const users = await UserModel.findAll();
  
  for (const user of users) {
    // legacy ν•„λ“œλ₯Ό μƒˆ ν•„λ“œλ‘œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜
    await UserModel.update(user.id, {
      new_field: transformData(user.legacy_field),
    });
    
    console.log(`βœ“ Migrated user ${user.id}`);
  }
  
  console.log(`\nβœ“ Total: ${users.length} users migrated`);
});

3. 배치 μž‘μ—…

src/practices/p3-send-welcome-emails.ts
import { Sonamu } from "sonamu";
import { UserModel } from "../models/User.model";

console.clear();
console.log("p3-send-welcome-emails.ts");

Sonamu.runScript(async () => {
  // μ‹ κ·œ κ°€μž… μ‚¬μš©μž
  const newUsers = await UserModel.findMany({
    where: { welcome_email_sent: false },
  });
  
  console.log(`Sending welcome emails to ${newUsers.length} users...`);
  
  for (const user of newUsers) {
    try {
      await sendWelcomeEmail(user.email);
      await UserModel.update(user.id, {
        welcome_email_sent: true,
      });
      console.log(`βœ“ Sent to ${user.email}`);
    } catch (error) {
      console.error(`βœ— Failed to send to ${user.email}:`, error);
    }
  }
});

Entity 생성

기본 ꡬ쑰

stub entity둜 μƒμ„±λœ EntityλŠ” κΈ°λ³Έ ν•„λ“œλ“€μ„ ν¬ν•¨ν•©λ‹ˆλ‹€:
export const ProductEntity = {
  properties: [
    { name: "id", type: "int", primaryKey: true, autoIncrement: true },
    { name: "title", type: "string", length: 255 },
    { name: "created_at", type: "datetime", default: "CURRENT_TIMESTAMP" },
  ],
} satisfies EntityType;

μ»€μŠ€ν„°λ§ˆμ΄μ§•

생성 ν›„ ν•„μš”ν•œ ν•„λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€:
export const ProductEntity = {
  properties: [
    // κΈ°λ³Έ ν•„λ“œ
    { name: "id", type: "int", primaryKey: true, autoIncrement: true },
    { name: "title", type: "string", length: 255 },
    
    // μΆ”κ°€ ν•„λ“œ
    { name: "description", type: "text", nullable: true },
    { name: "price", type: "decimal", precision: 10, scale: 2 },
    { name: "stock", type: "int", default: 0 },
    { name: "category_id", type: "int" },
    
    // νƒ€μž„μŠ€νƒ¬ν”„
    { name: "created_at", type: "datetime", default: "CURRENT_TIMESTAMP" },
    { name: "updated_at", type: "datetime", onUpdate: "CURRENT_TIMESTAMP" },
  ],
  
  indexes: [
    { fields: ["category_id"] },
    { fields: ["title"], type: "fulltext" },
  ],
  
  belongsTo: [
    { entityId: "Category", as: "category" },
  ],
} satisfies EntityType;

개발 μ›Œν¬ν”Œλ‘œμš°

1. 아이디어 검증

# λΉ λ₯΄κ²Œ practice 생성
pnpm stub practice test-idea

# μ½”λ“œ μž‘μ„± 및 μ‹€ν–‰
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-test-idea.js

# λ™μž‘ 확인 ν›„ μ‚­μ œ λ˜λŠ” 보관

2. Entity μΆ”κ°€

# Entity 생성
pnpm stub entity Order

# Entity μ •μ˜ μž‘μ„±
# src/entities/Order.entity.ts

# λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 생성 및 적용
pnpm migrate run

# Model μŠ€μΊν΄λ”©
pnpm scaffold model Order

3. 데이터 μž‘μ—…

# Practice 슀크립트 생성
pnpm stub practice import-orders

# 데이터 처리 둜직 μž‘μ„±
# src/practices/p1-import-orders.ts

# μ‹€ν–‰
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-import-orders.js

μ‹€μ „ 팁

1. Practice 슀크립트 μž¬μ‚¬μš©

// βœ… μž¬μ‚¬μš© κ°€λŠ₯ν•˜λ„λ‘ ν•¨μˆ˜ν™”
async function processUsers(filter: any) {
  const users = await UserModel.findMany({ where: filter });
  // 처리 둜직...
}

Sonamu.runScript(async () => {
  // λ‹€μ–‘ν•œ ν•„ν„°λ‘œ μž¬μ‚¬μš©
  await processUsers({ role: "admin" });
  await processUsers({ role: "user" });
});

2. μ—λŸ¬ 처리

Sonamu.runScript(async () => {
  try {
    await riskyOperation();
  } catch (error) {
    console.error("Error occurred:", error);
    process.exit(1);  // μ‹€νŒ¨ μ‹œ μ’…λ£Œ
  }
});

3. μ§„ν–‰ 상황 ν‘œμ‹œ

Sonamu.runScript(async () => {
  const users = await UserModel.findAll();
  
  for (let i = 0; i < users.length; i++) {
    await processUser(users[i]);
    
    // μ§„ν–‰λ₯  ν‘œμ‹œ
    const progress = Math.round(((i + 1) / users.length) * 100);
    console.log(`Progress: ${progress}% (${i + 1}/${users.length})`);
  }
});

4. Practice 정리

# μ™„λ£Œλœ practiceλŠ” μ‚­μ œ
rm src/practices/p1-old-practice.ts

# λ˜λŠ” λ³΄κ΄€μš© λ””λ ‰ν† λ¦¬λ‘œ 이동
mkdir src/practices/archived
mv src/practices/p1-*.ts src/practices/archived/

μ£Όμ˜μ‚¬ν•­

Practice 슀크립트 μ‚¬μš© μ‹œ μ£Όμ˜μ‚¬ν•­:
  1. ν”„λ‘œλ•μ…˜ 데이터: PracticeλŠ” μ‹€μ œ DBλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. μ‘°μ‹¬ν•˜μ„Έμš”!
  2. λ°±μ—…: μ€‘μš”ν•œ 데이터 μž‘μ—… μ „μ—λŠ” λ°˜λ“œμ‹œ λ°±μ—…ν•˜μ„Έμš”.
  3. νŠΈλžœμž­μ…˜: 데이터 λ³€κ²½ μ‹œ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜μ„Έμš”.
  4. ν…ŒμŠ€νŠΈ: ν”„λ‘œλ•μ…˜ μ‹€ν–‰ μ „ 개발 ν™˜κ²½μ—μ„œ λ¨Όμ € ν…ŒμŠ€νŠΈν•˜μ„Έμš”.

λ‹€μŒ 단계