๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Boundary๋Š” HMR ์‹œ์Šคํ…œ์ด ๋ชจ๋“ˆ์„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๊ณ„๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. Sonamu๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ Boundary๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๋ฉด HMR์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Boundary๋ž€?

Boundary๋Š” โ€œ์žฌ๋กœ๋“œ ๊ฐ€๋Šฅํ•œ ๊ฒฝ๊ณ„โ€๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. HMR ์‹œ์Šคํ…œ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ทœ์น™์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค:
  • Boundary ํŒŒ์ผ: ๋ณ€๊ฒฝ ์‹œ ๋‹ค์‹œ ๋กœ๋“œ๋  ์ˆ˜ ์žˆ๋Š” ํŒŒ์ผ (Model, API, Entity ๋“ฑ)
  • Non-boundary ํŒŒ์ผ: ๋ณ€๊ฒฝ ์‹œ ์ „์ฒด ์„œ๋ฒ„ ์žฌ์‹œ์ž‘์ด ํ•„์š”ํ•œ ํŒŒ์ผ (์„ค์ •, ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋“ฑ)
โœ… Boundary ํŒŒ์ผ: ์ˆ˜์ • ์‹œ HMR๋กœ ์ฆ‰์‹œ ๋ฐ˜์˜ โŒ Non-boundary ํŒŒ์ผ: ์ˆ˜์ • ์‹œ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ํ•„์š”

์™œ Boundary๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?

์ •์  import๋Š” Node.js ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋Ÿฐํƒ€์ž„์— ๊ต์ฒดํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:
// โŒ ์ •์  import - HMR ๋ถˆ๊ฐ€
import { UserModel } from "./user.model";

// โœ… ๋™์  import - HMR ๊ฐ€๋Šฅ
const { UserModel } = await import("./user.model");
Boundary๋กœ ์ง€์ •๋œ ํŒŒ์ผ์€ ๋ฐ˜๋“œ์‹œ ๋™์ ์œผ๋กœ import๋˜์–ด์•ผ ํ•˜๋ฉฐ, ๊ทธ๋ž˜์•ผ๋งŒ ๋ณ€๊ฒฝ ์‹œ ์ตœ์‹  ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Sonamu์˜ Boundary ์„ค์ •

Sonamu๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ”„๋กœ์ ํŠธ์˜ ๋ชจ๋“  TypeScript ํŒŒ์ผ์„ Boundary๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค:
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [`./src/**/*.ts`],  // ๋ชจ๋“  .ts ํŒŒ์ผ์ด boundary
});
์ด ์„ค์ •์œผ๋กœ ์ธํ•ด src/ ์•„๋ž˜์˜ ๋ชจ๋“  ํŒŒ์ผ์€ HMR์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ณ€๊ฒฝ ์‹œ ์ž๋™์œผ๋กœ ์žฌ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

Boundary ํŒŒ์ผ ์˜ˆ์‹œ

Boundary ํŒŒ์ผ๋“ค (HMR ๊ฐ€๋Šฅ):
  • user.model.ts - Model ํŒŒ์ผ
  • user.api.ts - API ํŒŒ์ผ
  • user.entity.ts - Entity ์ •์˜
  • helpers.ts - ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
  • constants.ts - ์ƒ์ˆ˜ ์ •์˜
Non-boundary ํŒŒ์ผ๋“ค (์žฌ์‹œ์ž‘ ํ•„์š”):
  • sonamu.config.ts - Sonamu ์„ค์ •
  • .env - ํ™˜๊ฒฝ๋ณ€์ˆ˜
  • package.json - ์˜์กด์„ฑ

Boundary ๊ทœ์น™

1. ๋™์  Import ํ•„์ˆ˜

Boundary ํŒŒ์ผ์€ ๋ฐ˜๋“œ์‹œ ๋™์ ์œผ๋กœ import๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. โŒ ์ž˜๋ชป๋œ ์˜ˆ: ์ •์  import
// server.ts
import { UserModel } from "./application/user/user.model";

// UserModel์„ ์ˆ˜์ •ํ•ด๋„ ๋ฐ˜์˜๋˜์ง€ ์•Š์Œ!
app.get("/users", async (req, res) => {
  const users = await UserModel.findMany();
  res.json(users);
});
โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ: ๋™์  import
// server.ts
app.get("/users", async (req, res) => {
  const { UserModel } = await import("./application/user/user.model");
  
  // UserModel์„ ์ˆ˜์ •ํ•˜๋ฉด ๋‹ค์Œ ์š”์ฒญ ์‹œ ์ตœ์‹  ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋จ!
  const users = await UserModel.findMany();
  res.json(users);
});

Sonamu๋Š” ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค

๋‹คํ–‰ํžˆ Sonamu์˜ Syncer๋Š” Entity ๊ธฐ๋ฐ˜ ํŒŒ์ผ๋“ค์„ ์ž๋™์œผ๋กœ ๋™์  importํ•ฉ๋‹ˆ๋‹ค:
// syncer.ts - autoloadModels()
async autoloadModels() {
  for (const entity of entities) {
    const modelPath = `${entity.path}/${entity.id}.model`;
    
    // ๋™์  import๋กœ ๋กœ๋“œ โœ…
    const module = await import(modelPath);
    this.models[entity.id] = module[`${entity.id}Model`];
  }
}
์ฆ‰, ์ผ๋ฐ˜์ ์ธ Sonamu ๊ฐœ๋ฐœ์—์„œ๋Š” ์‹ ๊ฒฝ ์“ธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!

2. Boundary ๊ฐ„ ์ •์  Import ํ—ˆ์šฉ

Boundary ํŒŒ์ผ๋ผ๋ฆฌ๋Š” ์ •์  import๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค (Sonamu์˜ ๊ฐœ์„ ์‚ฌํ•ญ):
// user.model.ts (boundary)
import { PostModel } from "./post.model";  // โœ… OK - ๋‘˜ ๋‹ค boundary

export class UserModel extends BaseModel {
  async getPosts() {
    return PostModel.findMany({ where: { userId: this.id } });
  }
}
์ด๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ ์ด์œ :
  • ๋‘ ํŒŒ์ผ ๋ชจ๋‘ Boundary์ด๋ฏ€๋กœ
  • ๋‘˜ ๋‹ค Syncer์— ์˜ํ•ด ๋™์ ์œผ๋กœ ๋กœ๋“œ๋จ
  • ์„œ๋กœ ์ฐธ์กฐํ•ด๋„ HMR์ด ์ •์ƒ ์ž‘๋™ํ•จ

3. Non-boundary์˜ Boundary Import ์ œํ•œ

Non-boundary ํŒŒ์ผ์ด Boundary๋ฅผ ์ •์  importํ•˜๋ฉด HMR์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:
// server.ts (non-boundary)
import { UserModel } from "./user.model";  // โŒ ์ „์ฒด ์žฌ์‹œ์ž‘ ํ•„์š”

// server.ts (non-boundary) - ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•
async function loadModels() {
  const { UserModel } = await import("./user.model");  // โœ… OK
}

์‹ค์ „ ์‹œ๋‚˜๋ฆฌ์˜ค

User-Post ๊ด€๊ณ„์—์„œ HMR ํ™œ์šฉํ•˜๊ธฐ

์ƒํ™ฉ: User Entity์˜ role ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , Post API์—์„œ ์ž‘์„ฑ์ž ๊ถŒํ•œ ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. 1๋‹จ๊ณ„: Entity ์ˆ˜์ •
// Sonamu UI์—์„œ User์— role ์ถ”๊ฐ€
role: EnumProp<"admin" | "user">
์ €์žฅํ•˜๋ฉด HMR์ด ์ž๋™์œผ๋กœ:
  • user.entity.ts ์—…๋ฐ์ดํŠธ
  • user.types.ts ์žฌ์ƒ์„ฑ
  • UserModel ์žฌ๋กœ๋“œ
์ฝ˜์†” ์ถœ๋ ฅ:
๐Ÿ”„ Invalidated:
- src/application/user/user.entity.ts
- src/application/user/user.model.ts
2๋‹จ๊ณ„: Model ๋กœ์ง ์ถ”๊ฐ€
// user.model.ts
export class UserModel extends BaseModel {
  isAdmin(): boolean {
    return this.role === "admin";
  }
  
  canCreatePost(): boolean {
    return this.isAdmin() || this.role === "user";
  }
}
์ €์žฅํ•˜๋ฉด HMR์ด:
  • UserModel ์žฌ๋กœ๋“œ
  • UserModel์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  API ์žฌ๋กœ๋“œ
3๋‹จ๊ณ„: API์— ๊ถŒํ•œ ์ฒดํฌ ์ถ”๊ฐ€
// post.api.ts
import { UserModel } from "../user/user.model";  // โœ… ๋‘˜ ๋‹ค boundary

@api({ httpMethod: "POST" })
async create(body: PostForm) {
  const user = await UserModel.findById(body.userId);
  
  if (!user.canCreatePost()) {  // ๐Ÿ‘ˆ ๋ฐฉ๊ธˆ ์ถ”๊ฐ€ํ•œ ๋ฉ”์„œ๋“œ ๋ฐ”๋กœ ์‚ฌ์šฉ!
    throw new UnauthorizedError("You don't have permission to create posts");
  }
  
  return PostModel.save(body);
}
์ €์žฅํ•˜๋ฉด HMR์ด:
  • PostApi.create ์žฌ๋“ฑ๋ก
  • ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์—†์ด ๋ฐ”๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ!
๐Ÿ”„ Invalidated:
- src/application/post/post.api.ts

โœจ API re-registered: POST /api/post/create

์ „ํ†ต์  ๋ฐฉ์‹์ด๋ผ๋ฉด:
  1. Entity ์ˆ˜์ •
  2. Types ํŒŒ์ผ ์ˆ˜๋™ ์ƒ์„ฑ
  3. Model ํŒŒ์ผ ์ˆ˜์ •
  4. ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ (30์ดˆ)
  5. API ํŒŒ์ผ ์ˆ˜์ •
  6. ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ (30์ดˆ)
  7. ํ…Œ์ŠคํŠธ
Sonamu + HMR:
  1. Entity ์ˆ˜์ • (์ž๋™ ์ƒ์„ฑ)
  2. Model ํŒŒ์ผ ์ˆ˜์ • (์ž๋™ ์žฌ๋กœ๋“œ)
  3. API ํŒŒ์ผ ์ˆ˜์ • (์ž๋™ ์žฌ๋กœ๋“œ)
  4. ํ…Œ์ŠคํŠธ โœ…
3๋ฒˆ์˜ ์žฌ์‹œ์ž‘(90์ดˆ)์ด 0๋ฒˆ์œผ๋กœ!

๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฐœ๋ฐœ

์ƒํ™ฉ: ์ฃผ๋ฌธ ์ƒ์„ฑ ์‹œ ์žฌ๊ณ  ํ™•์ธ, ๊ฒฐ์ œ ์ฒ˜๋ฆฌ, ์•Œ๋ฆผ ๋ฐœ์†ก์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ๊ตฌ์กฐ:

1๋‹จ๊ณ„: ์žฌ๊ณ  ์ฒดํฌ ๋กœ์ง
// product.model.ts
export class ProductModel extends BaseModel {
  hasStock(quantity: number): boolean {
    return this.stock >= quantity;
  }
  
  async reserveStock(quantity: number) {
    if (!this.hasStock(quantity)) {
      throw new BadRequestError("Out of stock");
    }
    
    await this.update({ stock: this.stock - quantity });
  }
}
์ €์žฅ โ†’ 2์ดˆ ํ›„ โ†’ ์ฆ‰์‹œ ๋ฐ˜์˜ โœ… 2๋‹จ๊ณ„: ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง
// order.model.ts
import { ProductModel } from "../product/product.model";  // โœ… OK
import { PaymentService } from "../payment/payment.service";

export class OrderModel extends BaseModel {
  static async createOrder(productId: number, quantity: number, userId: number) {
    const product = await ProductModel.findById(productId);
    
    // ProductModel.reserveStock()์„ ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!
    await product.reserveStock(quantity);
    
    const order = await this.save({
      productId,
      quantity,
      userId,
      totalPrice: product.price * quantity,
      status: "pending",
    });
    
    return order;
  }
}
์ €์žฅ โ†’ ProductModel ์˜์กด์„ฑ๋„ ์ž๋™ ์žฌ๋กœ๋“œ โœ… 3๋‹จ๊ณ„: API ์—”๋“œํฌ์ธํŠธ
// order.api.ts
import { OrderModel } from "./order.model";

@api({ httpMethod: "POST" })
async create(body: { productId: number; quantity: number }) {
  const userId = getSonamuContext().userId!;
  
  // OrderModel.createOrder()๋ฅผ ๋ฐ”๋กœ ์‚ฌ์šฉ!
  const order = await OrderModel.createOrder(
    body.productId,
    body.quantity,
    userId
  );
  
  return order;
}
์ €์žฅ โ†’ API ์žฌ๋“ฑ๋ก โ†’ ์ฆ‰์‹œ Postman์—์„œ ํ…Œ์ŠคํŠธ! HMR ๋กœ๊ทธ:
๐Ÿ”„ Invalidated:
- src/application/product/product.model.ts
- src/application/order/order.model.ts
- src/application/order/order.api.ts (with 3 APIs)

โœจ API re-registered: POST /api/order/create

โœ… All files are synced!

import.meta.hot API

Boundary ํŒŒ์ผ์—์„œ๋Š” import.meta.hot API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HMR ๋™์ž‘์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

dispose() - ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ

๋ชจ๋“ˆ์ด ์žฌ๋กœ๋“œ๋˜๊ธฐ ์ „์— ์ •๋ฆฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค:
// notification.service.ts
export class NotificationService {
  private static timer: NodeJS.Timeout;
  
  static startPolling() {
    this.timer = setInterval(() => {
      console.log("Checking notifications...");
    }, 5000);
  }
}

// ์žฌ๋กœ๋“œ ์ „ ํƒ€์ด๋จธ ์ •๋ฆฌ
import.meta.hot?.dispose(() => {
  clearInterval(NotificationService.timer);
  console.log("Cleaned up notification polling timer");
});
๋ฆฌ์†Œ์Šค ์ •๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ:
  • ํƒ€์ด๋จธ/์ธํ„ฐ๋ฒŒ ์ •๋ฆฌ
  • ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ
  • WebSocket ์—ฐ๊ฒฐ ์ข…๋ฃŒ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜ ํ’€ ์ •๋ฆฌ
  • ํŒŒ์ผ ํ•ธ๋“ค ๋‹ซ๊ธฐ
์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ:
// websocket-manager.ts
class WebSocketManager {
  private static connections = new Map<string, WebSocket>();
  
  static connect(url: string) {
    const ws = new WebSocket(url);
    this.connections.set(url, ws);
    return ws;
  }
}

import.meta.hot?.dispose(() => {
  // ๋ชจ๋“  WebSocket ์—ฐ๊ฒฐ ์ข…๋ฃŒ
  for (const [url, ws] of WebSocketManager.connections) {
    ws.close();
    console.log(`Closed WebSocket: ${url}`);
  }
  WebSocketManager.connections.clear();
});

decline() - ์ „์ฒด ์žฌ์‹œ์ž‘ ์š”๊ตฌ

ํŠน์ • ๋ชจ๋“ˆ์„ HMR์—์„œ ์ œ์™ธํ•˜๊ณ  ์ „์ฒด ์žฌ์‹œ์ž‘์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค:
// config.ts
export const config = {
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT),
  },
  redis: {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT),
  },
};

// ์ด ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ „์ฒด ์žฌ์‹œ์ž‘
import.meta.hot?.decline();
decline()์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ:
  • ์ „์—ญ ์„ค์ • ํŒŒ์ผ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ค์ •
  • ์ดˆ๊ธฐํ™”๊ฐ€ ๋ณต์žกํ•œ ์‹ฑ๊ธ€ํ†ค
  • ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๊นŒ๋‹ค๋กœ์šด ๋ชจ๋“ˆ
์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ:
// database.config.ts
import { Sonamu } from "@sonamu-kit/sonamu";

export const dbPool = new Pool({
  host: Sonamu.config.database.host,
  port: Sonamu.config.database.port,
  user: Sonamu.config.database.user,
  password: Sonamu.config.database.password,
  database: Sonamu.config.database.database,
});

// DB ์—ฐ๊ฒฐ ์„ค์ • ๋ณ€๊ฒฝ์€ ์žฌ์‹œ์ž‘ ํ•„์š”
import.meta.hot?.decline();

boundary ๊ฐ์ฒด - ์ƒํƒœ ๊ณต์œ 

Boundary ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜์—ฌ ์žฌ๋กœ๋“œ ํ›„์—๋„ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค:
// cache-manager.ts
const cache = import.meta.hot?.boundary.userCache || new Map();

export class CacheManager {
  static set(key: string, value: any) {
    cache.set(key, value);
  }
  
  static get(key: string) {
    return cache.get(key);
  }
}

// ์žฌ๋กœ๋“œ ์‹œ์—๋„ ์บ์‹œ ์œ ์ง€
import.meta.hot?.boundary.userCache = cache;
์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ:
// rate-limiter.ts
const requestCounts = import.meta.hot?.boundary.requestCounts || new Map<string, number>();

export class RateLimiter {
  static check(userId: string): boolean {
    const count = requestCounts.get(userId) || 0;
    
    if (count >= 100) {
      return false;  // Rate limit exceeded
    }
    
    requestCounts.set(userId, count + 1);
    return true;
  }
}

// HMR ํ›„์—๋„ ์š”์ฒญ ์นด์šดํŠธ ์œ ์ง€
import.meta.hot?.boundary.requestCounts = requestCounts;
boundary ๊ฐ์ฒด๋Š” HMR ์‚ฌ์ดํด ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜์ง€๋งŒ, ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ์—๋Š” ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค. ์˜๊ตฌ ์ €์žฅ์ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ Redis๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

ํƒ€์ž… ์ •์˜

TypeScript์—์„œ import.meta.hot์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํƒ€์ž… ์ •์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:
// src/types/import-meta.d.ts
interface ImportMeta {
  readonly hot?: {
    dispose(callback: () => Promise<void> | void): void;
    decline(): void;
    boundary: Record<string, any>;
  };
}
Sonamu ํ”„๋กœ์ ํŠธ์—๋Š” ์ด๋ฏธ ํƒ€์ž… ์ •์˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๋ณ„๋„ ์„ค์ •์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

๋””๋ฒ„๊น…

Boundary ์„ค์ • ํ™•์ธ

ํŠน์ • ํŒŒ์ผ์ด Boundary๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ:
const dump = await hot.dump();
const userModel = dump.find(d => d.nodePath.includes("user.model.ts"));

console.log(`Boundary: ${userModel?.boundary}`);       // true
console.log(`Reloadable: ${userModel?.reloadable}`);   // true
console.log(`Children:`, userModel?.children);          // ์˜์กดํ•˜๋Š” ํŒŒ์ผ๋“ค
์ถœ๋ ฅ ์˜ˆ์‹œ:
{
  "nodePath": "/project/src/application/user/user.model.ts",
  "boundary": true,
  "reloadable": true,
  "children": [
    "/project/src/application/user/user.api.ts",
    "/project/src/application/post/post.api.ts",
    "/project/src/application/admin/admin.api.ts"
  ]
}

์˜์กด์„ฑ ํŠธ๋ฆฌ ์‹œ๊ฐํ™”

const dump = await hot.dump();

for (const node of dump) {
  if (node.boundary) {
    console.log(`๐Ÿ“ฆ ${node.nodePath}`);
    
    for (const child of node.children || []) {
      console.log(`   โ””โ”€ ${child}`);
    }
  }
}
์ถœ๋ ฅ:
๐Ÿ“ฆ /project/src/application/user/user.model.ts
   โ””โ”€ /project/src/application/user/user.api.ts
   โ””โ”€ /project/src/application/post/post.api.ts

๐Ÿ“ฆ /project/src/application/post/post.model.ts
   โ””โ”€ /project/src/application/post/post.api.ts
๊ฐœ๋ฐœ ์ค‘ HMR์ด ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด:
  1. ํ•ด๋‹น ํŒŒ์ผ์ด Boundary๋กœ ์ง€์ •๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ
  2. ๋™์ ์œผ๋กœ import๋˜๋Š”์ง€ ํ™•์ธ
  3. ์ˆœํ™˜ ์˜์กด์„ฑ์ด ์—†๋Š”์ง€ ํ™•์ธ

์„ฑ๋Šฅ ์ตœ์ ํ™”

๋ถˆํ•„์š”ํ•œ Boundary ์ œ์™ธ

๋ชจ๋“  ํŒŒ์ผ์„ Boundary๋กœ ์„ค์ •ํ•˜๋ฉด ์˜์กด์„ฑ ํŠธ๋ฆฌ๊ฐ€ ์ปค์ ธ์„œ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    // ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ํŒŒ์ผ๋งŒ Boundary๋กœ
    "./src/**/*.model.ts",
    "./src/**/*.api.ts",
    "./src/**/*.service.ts",
  ],
});

์˜์กด์„ฑ ์ตœ์†Œํ™”

Model ๊ฐ„ ์ˆœํ™˜ ์ฐธ์กฐ๋ฅผ ํ”ผํ•˜๊ณ , ํ•„์š”ํ•œ ๊ฒƒ๋งŒ import:
// โŒ ๋‚˜์œ ์˜ˆ
import { UserModel } from "../user/user.model";
import { PostModel } from "../post/post.model";
import { CommentModel } from "../comment/comment.model";
// ... ๋งŽ์€ import

// โœ… ์ข‹์€ ์˜ˆ
import { UserModel } from "../user/user.model";  // ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋งŒ

์š”์•ฝ

Sonamu์˜ Boundary ์‹œ์Šคํ…œ: โœ… ์ž๋™ ์ฒ˜๋ฆฌ: Syncer๊ฐ€ Entity ๊ธฐ๋ฐ˜ ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ๋™์  import
โœ… Boundary ๊ฐ„ ์ฐธ์กฐ ๊ฐ€๋Šฅ: Model๋ผ๋ฆฌ ์ •์  import ํ—ˆ์šฉ
โœ… ์„ธ๋ฐ€ํ•œ ์ œ์–ด: import.meta.hot API๋กœ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ, ์ƒํƒœ ์œ ์ง€
โœ… ๋””๋ฒ„๊น… ๋„๊ตฌ: hot.dump()๋กœ ์˜์กด์„ฑ ํŠธ๋ฆฌ ํ™•์ธ
๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ Boundary๋ฅผ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†์ด Sonamu๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค!