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λ₯Ό μμ±ν©λλ€. κΈ°λ³Έμ μΌλ‘ Entity μμ± ν ν
νλ¦Ώ coneμ΄ μλμΌλ‘ ν¨κ» μμ±λ©λλ€.
pnpm stub entity <entityId> [μ΅μ
]
μ΅μ
:
| μ΅μ
| μ€λͺ
| μμ |
|---|
| (μμ) | Entity μμ± ν ν
νλ¦Ώ cone μλ μμ± (κΈ°λ³Έ λμ) | pnpm stub entity Product |
--ai | Entity μμ± ν LLMμΌλ‘ cone μμ± (ANTHROPIC_API_KEY νμ) | pnpm stub entity Product --ai |
--no-cones | Entityλ§ μμ±νκ³ cone μμ±μ μ€ν΅ | pnpm stub entity Product --no-cones |
μμ:
# κΈ°λ³Έ: ν
νλ¦Ώ coneκ³Ό ν¨κ» Entity μμ±
pnpm stub entity Product
# β Entity 'Product' created with template cones
# π‘ Tip: Run 'pnpm sonamu cone gen Product' to improve with AI
# AIλ₯Ό νμ©ν cone μμ±
pnpm stub entity Product --ai
# β Entity 'Product' created
# π Generating AI-powered cones...
# β
Done (1234 tokens)
# cone μμ΄ Entityλ§ μμ±
pnpm stub entity Product --no-cones
# β Entity 'Product' created without cones
ν
νλ¦Ώ coneμ ANTHROPIC_API_KEY μμ΄λ μμ±ν μ μμ΅λλ€. λμ€μ cone gen λͺ
λ Ήμ΄λ‘ AIλ₯Ό νμ©ν΄ μ
κ·Έλ μ΄λν μ μμ΅λλ€.
μμ±λ νμΌ:
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 μ€ν¬λ¦½νΈ μ¬μ© μ μ£Όμμ¬ν:
- νλ‘λμ
λ°μ΄ν°: Practiceλ μ€μ DBλ₯Ό μ¬μ©ν©λλ€. μ‘°μ¬νμΈμ!
- λ°±μ
: μ€μν λ°μ΄ν° μμ
μ μλ λ°λμ λ°±μ
νμΈμ.
- νΈλμμ
: λ°μ΄ν° λ³κ²½ μ νΈλμμ
μ μ¬μ©νμΈμ.
- ν
μ€νΈ: νλ‘λμ
μ€ν μ κ°λ° νκ²½μμ λ¨Όμ ν
μ€νΈνμΈμ.
λ€μ λ¨κ³