> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 문제 해결

> HMR 관련 문제 해결 가이드

export const FileItem = ({name, description, children}) => {
  const isLeaf = !children;
  const ext = getFileExtension(name);
  const extStyle = ext ? getExtensionStyle(ext) : null;
  return <div style={{
    marginLeft: "30px"
  }}>
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "4px"
  }}>
        <span style={{
    position: "relative",
    display: "inline-block"
  }}>
          <span className="file-icon">{isLeaf && !name.endsWith("/") ? "📄" : "📁"}</span>
          {ext && <span style={{
    position: "absolute",
    bottom: "2px",
    right: "0px",
    fontSize: "5px",
    fontWeight: "bold",
    padding: "1px",
    borderRadius: "2px",
    lineHeight: "1",
    minWidth: "8px",
    textAlign: "center",
    ...extStyle
  }}>
              {ext}
            </span>}
        </span>
        <code>{name}</code>
        {description && <span className="description"> - {description}</span>}
      </div>
      {children}
    </div>;
};

export const FileTree = ({children}) => {
  return <div className="file-tree" style={{
    margin: "20px",
    marginLeft: "-30px"
  }}>
      {children}
    </div>;
};

HMR 사용 중 발생할 수 있는 문제와 실전 해결 방법입니다.

## 변경사항이 반영되지 않음

### 상황

```typescript theme={null}
// user.model.ts 수정
export class UserModel extends BaseModelClass {
  isActive(): boolean {
    return this.status === "active"; // 이 로직을 추가했는데...
  }
}
```

저장했지만 API 호출 시 여전히 `isActive is not a function` 에러가 발생합니다.

### 원인 1: 정적 Import 사용

어딘가에서 UserModel을 정적으로 import했을 가능성이 높습니다.

**확인 방법:**

```bash theme={null}
# 프로젝트에서 정적 import 찾기
grep -r "import.*UserModel.*from" src/

# 출력 예시:
# src/some-file.ts:import { UserModel } from "./user.model";  ← 🚨 이게 문제!
```

**해결 방법: 동적 import로 변경**

```typescript theme={null}
// Before - ❌
import { UserModel } from "./application/user/user.model";

app.get("/users", async (req, res) => {
  const users = await UserModel.findMany();
  res.json(users);
});

// After - ✅
app.get("/users", async (req, res) => {
  const { UserModel } = await import("./application/user/user.model");
  const users = await UserModel.findMany();
  res.json(users);
});
```

<Note>
  Sonamu의 Syncer는 Entity 기반 파일들을 자동으로 동적 import하므로, 일반적인 Model/API 파일에서는
  이 문제가 발생하지 않습니다. 주로 커스텀 스크립트나 초기화 코드에서 발생합니다.
</Note>

### 원인 2: 콘솔에 에러가 숨어있음

HMR 중 에러가 발생했지만 놓쳤을 수 있습니다.

**확인 방법:**

터미널을 스크롤해서 다음과 같은 에러가 있는지 확인:

```bash theme={null}
🔄 Invalidated:
- src/user/user.model.ts

❌ Error loading module: /path/to/user.model.ts
SyntaxError: Unexpected token '}'
    at Module._compile (internal/modules/cjs/loader.js:1137:14)
```

**해결 방법:**

에러 메시지의 파일과 라인 번호를 확인하고 수정합니다.

### 원인 3: 캐시가 꼬임

드물지만 ESM 캐시가 완전히 제거되지 않을 수 있습니다.

**해결 방법: 서버 재시작**

```bash theme={null}
# Ctrl+C로 종료 후
pnpm dev
```

## "File not imported dynamically" 에러

### 상황

```
FileNotImportedDynamicallyException: File /path/to/user.model.ts must be imported dynamically
```

### 원인

Boundary 파일(user.model.ts)이 어딘가에서 정적 import로 로드되었습니다.

### 빠른 해결

**옵션 1: 정적 import를 동적 import로 변경 (권장)**

에러 메시지에 나온 파일을 찾아서:

```typescript theme={null}
// Before
import { UserModel } from "./user.model";

// After
const { UserModel } = await import("./user.model");
```

**옵션 2: 해당 파일을 Boundary에서 제외**

정말 동적 import로 바꿀 수 없는 경우:

```typescript theme={null}
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    `./src/**/*.ts`,
    `!./src/config/**/*`, // config 폴더 제외
  ],
});
```

**옵션 3: 에러를 무시 (권장하지 않음)**

```typescript theme={null}
await hot.init({
  boundaries: [`./src/**/*.ts`],
  throwWhenBoundariesAreNotDynamicallyImported: false, // 에러 무시
});
```

<Warning>
  옵션 3을 사용하면 해당 파일의 HMR이 작동하지 않습니다. 개발 편의성이 크게 떨어지므로 권장하지
  않습니다.
</Warning>

## 재로드 시 메모리 증가

### 상황

```bash theme={null}
# 처음
RSS: 150MB, Heap: 80MB

# 파일을 10번 수정 후
RSS: 350MB, Heap: 200MB  # 🚨 메모리가 계속 증가!
```

### 원인

이전 모듈의 리소스(타이머, 이벤트 리스너, 연결 등)가 정리되지 않아 누수가 발생합니다.

**확인 방법:**

```typescript theme={null}
// 재로드할 때마다 실행
console.log("Active handles:", process._getActiveHandles().length);
console.log("Active requests:", process._getActiveRequests().length);

// 숫자가 계속 증가하면 리소스 누수 발생 중!
```

### 해결 방법

`import.meta.hot.dispose()`로 리소스를 정리합니다:

**예시 1: 타이머**

```typescript theme={null}
// notification-polling.ts
const timer = setInterval(async () => {
  await checkNewNotifications();
}, 5000);

// ✅ 재로드 전 타이머 정리
import.meta.hot?.dispose(() => {
  clearInterval(timer);
  console.log("✨ Timer cleaned up");
});
```

**예시 2: WebSocket**

```typescript theme={null}
// websocket-client.ts
const ws = new WebSocket("ws://notification-server.com");

ws.on("message", (data) => {
  console.log("Notification:", data);
});

// ✅ 재로드 전 연결 종료
import.meta.hot?.dispose(() => {
  ws.close();
  console.log("✨ WebSocket closed");
});
```

**예시 3: 이벤트 리스너**

```typescript theme={null}
// event-handler.ts
import { EventEmitter } from "events";

const emitter = new EventEmitter();

function handleData(data: any) {
  console.log("Data received:", data);
}

emitter.on("data", handleData);

// ✅ 재로드 전 리스너 제거
import.meta.hot?.dispose(() => {
  emitter.removeListener("data", handleData);
  console.log("✨ Event listener removed");
});
```

**예시 4: 데이터베이스 커넥션 풀**

```typescript theme={null}
// custom-db-pool.ts
import { Pool } from "pg";

const pool = new Pool({
  host: "localhost",
  port: 5432,
  database: "mydb",
});

// ✅ 재로드 전 풀 종료
import.meta.hot?.dispose(async () => {
  await pool.end();
  console.log("✨ DB pool closed");
});
```

## API가 중복 등록됨

### 상황

```bash theme={null}
⚠️ Warning: Route POST /api/user/create is already registered
⚠️ Warning: Route GET /api/user/list is already registered
```

서버 로그에 같은 API가 여러 번 등록되었다는 경고가 표시됩니다.

### 원인

Model 파일 재로드 시 기존 API가 제거되지 않았습니다.

### 확인

Syncer 로그에서 API 제거 여부 확인:

```bash theme={null}
🔄 Invalidated:
- src/user/user.model.ts (with 8 APIs)  # ← API 개수가 표시되어야 함

# 만약 "with X APIs"가 없다면 제거되지 않은 것!
```

### 해결 방법

일반적으로 Syncer가 자동 처리하지만, 문제가 계속되면:

**1. 서버 재시작**

```bash theme={null}
# 가장 간단한 방법
Ctrl+C
pnpm dev
```

**2. Syncer 로직 확인 (드문 경우)**

```typescript theme={null}
// syncer.ts 확인
removeInvalidatedRegisteredApis(invalidatedPath: AbsolutePath) {
  if (!invalidatedPath.endsWith(".model.ts")) {
    return [];
  }

  const entityId = EntityManager.getEntityIdFromPath(invalidatedPath);
  const toRemove = registeredApis.filter(
    api => api.modelName === `${entityId}Model`
  );

  for (const api of toRemove) {
    registeredApis.splice(registeredApis.indexOf(api), 1);
  }

  return toRemove;
}
```

이 로직이 실행되지 않는다면 Sonamu 버그일 수 있습니다. GitHub Issues에 보고해주세요.

## 순환 의존성 문제

### 상황

```bash theme={null}
# 파일을 수정하면
🔄 Invalidated:
- src/user/user.model.ts
- src/post/post.model.ts
- src/user/user.model.ts  # ← 🚨 다시 등장!
- src/post/post.model.ts  # ← 🚨 무한 반복!

# 또는
RangeError: Maximum call stack size exceeded
```

HMR이 무한 루프에 빠지거나 일부 모듈이 로드되지 않습니다.

### 원인

Model 간 순환 참조:

```typescript theme={null}
// user.model.ts
import { PostModel } from "../post/post.model";

export class UserModel extends BaseModelClass {
  async getPosts() {
    return PostModel.findMany({ where: { userId: this.id } });
  }
}

// post.model.ts
import { UserModel } from "../user/user.model"; // ← 🚨 순환 참조!

export class PostModel extends BaseModelClass {
  async getAuthor() {
    return UserModel.findById(this.userId);
  }
}
```

### 해결 방법

**옵션 1: 타입만 import (권장)**

```typescript theme={null}
// user.model.ts
import type { PostSaved } from "../post/post.types"; // ✅ 타입만

export class UserModel extends BaseModelClass {
  async getPosts(): Promise<PostSaved[]> {
    // 동적 import로 실제 클래스 로드
    const { PostModel } = await import("../post/post.model");
    return PostModel.findMany({ where: { userId: this.id } });
  }
}

// post.model.ts
import type { UserSaved } from "../user/user.types"; // ✅ 타입만

export class PostModel extends BaseModelClass {
  async getAuthor(): Promise<UserSaved> {
    const { UserModel } = await import("../user/user.model");
    return UserModel.findById(this.userId);
  }
}
```

**옵션 2: 공통 타입 파일 분리**

```typescript theme={null}
// shared-types.ts
export type UserSaved = {
  /* ... */
};
export type PostSaved = {
  /* ... */
};

// user.model.ts
import type { PostSaved } from "./shared-types";

// post.model.ts
import type { UserSaved } from "./shared-types";
```

**옵션 3: 의존성 방향 재설계**

```typescript theme={null}
// post.model.ts에서만 user.model.ts를 참조
import { UserModel } from "../user/user.model"; // ✅ 단방향

export class PostModel extends BaseModelClass {
  async getAuthor() {
    return UserModel.findById(this.userId);
  }
}

// user.model.ts에서는 post.model.ts를 참조하지 않음
// (필요하면 PostApi에서 처리)
```

## 특정 파일만 HMR이 안 됨

### 상황

```typescript theme={null}
// user.model.ts 수정 → ✅ HMR 작동
// post.model.ts 수정 → ✅ HMR 작동
// admin-helper.ts 수정 → ❌ 반영 안 됨
```

### 원인 1: Boundary 패턴에 매칭되지 않음

**확인:**

```typescript theme={null}
const dump = await hot.dump();
const helper = dump.find((d) => d.nodePath.includes("admin-helper.ts"));

if (!helper) {
  console.log("❌ 이 파일은 Boundary가 아닙니다!");
}
```

**해결:**

```typescript theme={null}
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    `./src/**/*.ts`, // 이미 포함되어 있어야 함
  ],
});
```

### 원인 2: 파일이 import되지 않음

HMR은 **실제로 import된 파일**만 추적합니다.

**확인:**

```typescript theme={null}
const dump = await hot.dump();
console.log(`Total tracked files: ${dump.length}`);

// admin-helper.ts가 목록에 없다면 아무도 import하지 않는 것!
```

**해결:**

해당 파일을 어딘가에서 import하거나, 필요 없다면 삭제합니다.

## SSR 파일 변경 시 재로드 안 됨

### 상황

```typescript theme={null}
// src/ssr/routes.ts 수정
// 저장해도 반영 안 됨
```

### 원인

SSR 파일은 특별히 처리됩니다.

### 해결

Syncer가 SSR 파일 변경을 감지하면 자동으로 처리하지만, 수동 재로드가 필요한 경우:

```bash theme={null}
# 서버 재시작
Ctrl+C
pnpm dev
```

또는 코드에서:

```typescript theme={null}
// syncer.ts에서 SSR 파일 처리 확인
if (diffFilePath.includes("/src/ssr/")) {
  console.log("SSR config changed - reloading...");
  await hot.invalidateFile(diffFilePath, event);
  await this.autoloadSSRRoutes();
}
```

## HMR이 너무 느림

### 상황

```bash theme={null}
# 파일 저장
# 5초 후...
🔄 Invalidated:  # ← 🚨 너무 느림!
```

### 원인

의존성 트리가 너무 깊거나 넓어서 많은 파일이 무효화됩니다.

**확인:**

```typescript theme={null}
const dump = await hot.dump();
const userModel = dump.find((d) => d.nodePath.includes("user.model.ts"));

console.log(`Dependencies: ${userModel?.children?.length}`);
// 100개 이상이면 문제!
```

### 해결 방법

**1. import 최소화**

```typescript theme={null}
// ❌ 나쁜 예 - 모든 Model import
import { UserModel } from "./user.model";
import { PostModel } from "./post.model";
import { CommentModel } from "./comment.model";
import { CategoryModel } from "./category.model";
import { TagModel } from "./tag.model";
// ... 20개 더

// ✅ 좋은 예 - 필요한 것만
import { UserModel } from "./user.model";
import { PostModel } from "./post.model";
```

**2. lodash 등 라이브러리 import 최적화**

```typescript theme={null}
// ❌ 나쁜 예
import * as lodash from "lodash";

// ✅ 좋은 예
import { pick, omit } from "lodash";
```

**3. Boundary 범위 축소**

```typescript theme={null}
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    // 모든 파일 대신 자주 변경되는 것만
    `./src/**/*.model.ts`,
    `./src/**/*.api.ts`,
  ],
});
```

## 디버깅 팁

### HMR 로그 활성화

상세한 HMR 동작을 확인하려면:

```bash theme={null}
DEBUG=hmr-hook:* pnpm dev
```

출력 예시:

```bash theme={null}
hmr-hook:loader File change src/user/user.model.ts
hmr-hook:dependency_tree Invalidating /path/to/user.model.ts
hmr-hook:dependency_tree Finding dependents...
hmr-hook:dependency_tree Found 3 dependent files
hmr-hook:loader Invalidated 3 files
```

### 의존성 트리 확인

```typescript theme={null}
const dump = await hot.dump();

// 특정 파일의 의존성 확인
const userModel = dump.find((d) => d.nodePath.includes("user.model.ts"));
console.log("Children:", userModel?.children);

// 전체 트리를 JSON으로 저장
import { writeFileSync } from "fs";
writeFileSync("dependency-tree.json", JSON.stringify(dump, null, 2));
```

### 무효화된 파일 추적

Syncer가 콘솔에 출력하는 로그를 주의깊게 확인:

```bash theme={null}
🔄 Invalidated:
- src/user/user.model.ts (with 8 APIs)
- src/user/user.api.ts
- src/post/post.api.ts  # ← 왜 post.api.ts까지?

# user.model.ts가 변경되었는데 post.api.ts도 재로드됨
# → post.api.ts에서 UserModel을 사용 중
```

### 메모리 사용량 모니터링

```typescript theme={null}
// memory-monitor.ts
setInterval(() => {
  const used = process.memoryUsage();
  const timestamp = new Date().toISOString();

  console.log(`[${timestamp}] Memory:`, {
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    external: `${Math.round(used.external / 1024 / 1024)}MB`,
  });
}, 10000); // 10초마다
```

## 마지막 수단: 완전 재시작

모든 해결 방법이 실패한 경우:

```bash theme={null}
# 1. 프로세스 종료
Ctrl+C

# 2. 빌드 디렉토리 삭제
rm -rf dist/

# 3. 체크섬 삭제
rm -rf .sonamu/checksums/

# 4. node_modules 삭제 (정말 심각한 경우)
rm -rf node_modules/
pnpm install

# 5. 재시작
pnpm dev
```

## 도움 요청하기

여전히 문제가 해결되지 않으면:

### 1. GitHub Issues에 보고하세요

아래 링크에서 새로운 이슈를 생성해주세요:

[https://github.com/cartanova-ai/sonamu/issues](https://github.com/cartanova-ai/sonamu/issues)

### 2. 다음 정보를 포함해주세요

문제 해결을 위해 다음 정보를 함께 제공해주세요:

* Sonamu 버전 (`package.json`)
* Node.js 버전 (`node -v`)
* 발생한 에러 메시지
* 재현 가능한 최소 예제
* HMR 로그 (`DEBUG=hmr-hook:* pnpm dev`)

### 3. 파일 구조 예시를 작성해주세요

문제가 발생하는 파일 구조를 아래와 같이 명확하게 표시해주세요:

<Card>
  <FileTree>
    <FileItem name="src/">
      <FileItem name="user/">
        <FileItem name="user.model.ts" description="이 파일을 수정하면" />

        <FileItem name="user.api.ts" description="HMR이 작동하지 않음" />
      </FileItem>

      <FileItem name="post/">
        <FileItem name="post.model.ts" />
      </FileItem>
    </FileItem>
  </FileTree>
</Card>

**예시 설명을 함께 작성해주세요**:

```
증상:
- user.model.ts를 수정하고 저장
- 콘솔에 "Invalidated: user.model.ts" 표시됨
- 하지만 user.api.ts에서 여전히 이전 코드 실행
- 서버 재시작하면 정상 작동

시도한 해결 방법:
1. 동적 import 확인 → 이미 동적으로 로드됨
2. 서버 재시작 → 임시로 해결되지만 다시 발생
3. 캐시 삭제 (rm -rf dist/) → 효과 없음

HMR 로그:
[로그 내용 첨부]
```

이렇게 상세하게 작성하면 빠른 답변을 받을 수 있습니다!
