Skip to main content
Problems that may occur during HMR usage and practical solutions.

Changes Not Reflected

Situation

// Modified user.model.ts
export class UserModel extends BaseModel {
  isActive(): boolean {
    return this.status === "active";  // Added this logic but...
  }
}
Saved but still getting isActive is not a function error on API call.

Cause 1: Using Static Import

There’s likely a static import of UserModel somewhere. How to check:
# Find static imports in project
grep -r "import.*UserModel.*from" src/

# Example output:
# src/some-file.ts:import { UserModel } from "./user.model";  ← 🚨 This is the problem!
Solution: Change to dynamic import
// 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);
});
Sonamu’s Syncer automatically handles dynamic imports for Entity-based files, so this problem doesn’t occur in typical Model/API files. It mainly occurs in custom scripts or initialization code.

Cause 2: Error Hidden in Console

An error may have occurred during HMR but you missed it. How to check: Scroll through the terminal to check for errors like:
🔄 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)
Solution: Check the file and line number in the error message and fix it.

Cause 3: Cache Corruption

Rarely, ESM cache may not be completely removed. Solution: Server restart
# After stopping with Ctrl+C
pnpm dev

”File not imported dynamically” Error

Situation

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

Cause

A Boundary file (user.model.ts) was loaded via static import somewhere.

Quick Fix

Option 1: Change static import to dynamic import (Recommended) Find the file mentioned in the error message:
// Before
import { UserModel } from "./user.model";

// After
const { UserModel } = await import("./user.model");
Option 2: Exclude the file from Boundary If you really can’t change to dynamic import:
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    `./src/**/*.ts`,
    `!./src/config/**/*`,  // Exclude config folder
  ],
});
Option 3: Ignore the error (Not recommended)
await hot.init({
  boundaries: [`./src/**/*.ts`],
  throwWhenBoundariesAreNotDynamicallyImported: false,  // Ignore error
});
Using Option 3 means HMR won’t work for that file. Not recommended as it significantly reduces development convenience.

Memory Increase on Reload

Situation

# Initially
RSS: 150MB, Heap: 80MB

# After modifying files 10 times
RSS: 350MB, Heap: 200MB  # 🚨 Memory keeps increasing!

Cause

Resources from previous modules (timers, event listeners, connections, etc.) aren’t cleaned up, causing leaks. How to check:
// Runs on each reload
console.log("Active handles:", process._getActiveHandles().length);
console.log("Active requests:", process._getActiveRequests().length);

// If numbers keep increasing, resource leak is occurring!

Solution

Clean up resources with import.meta.hot.dispose(): Example 1: Timers
// notification-polling.ts
const timer = setInterval(async () => {
  await checkNewNotifications();
}, 5000);

// ✅ Clean up timer before reload
import.meta.hot?.dispose(() => {
  clearInterval(timer);
  console.log("✨ Timer cleaned up");
});
Example 2: WebSocket
// websocket-client.ts
const ws = new WebSocket("ws://notification-server.com");

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

// ✅ Close connection before reload
import.meta.hot?.dispose(() => {
  ws.close();
  console.log("✨ WebSocket closed");
});
Example 3: Event Listeners
// event-handler.ts
import { EventEmitter } from "events";

const emitter = new EventEmitter();

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

emitter.on("data", handleData);

// ✅ Remove listener before reload
import.meta.hot?.dispose(() => {
  emitter.removeListener("data", handleData);
  console.log("✨ Event listener removed");
});
Example 4: Database Connection Pool
// custom-db-pool.ts
import { Pool } from "pg";

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

// ✅ Close pool before reload
import.meta.hot?.dispose(async () => {
  await pool.end();
  console.log("✨ DB pool closed");
});

API Registered Multiple Times

Situation

⚠️ Warning: Route POST /api/user/create is already registered
⚠️ Warning: Route GET /api/user/list is already registered
Server log shows warnings about the same API being registered multiple times.

Cause

Existing APIs weren’t removed when Model file was reloaded.

Check

Check if API removal is shown in Syncer logs:
🔄 Invalidated:
- src/user/user.model.ts (with 8 APIs)  # ← API count should be shown

# If "with X APIs" is missing, they weren't removed!

Solution

Syncer normally handles this automatically, but if problem persists: 1. Server restart
# Simplest solution
Ctrl+C
pnpm dev
2. Check Syncer logic (rare cases)
// Check 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;
}
If this logic isn’t executing, it may be a Sonamu bug. Please report to GitHub Issues.

Circular Dependency Issues

Situation

# When modifying a file
🔄 Invalidated:
- src/user/user.model.ts
- src/post/post.model.ts
- src/user/user.model.ts  # ← 🚨 Appears again!
- src/post/post.model.ts  # ← 🚨 Infinite loop!

# Or
RangeError: Maximum call stack size exceeded
HMR gets stuck in infinite loop or some modules don’t load.

Cause

Circular reference between Models:
// user.model.ts
import { PostModel } from "../post/post.model";

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

// post.model.ts
import { UserModel } from "../user/user.model";  // ← 🚨 Circular reference!

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

Solutions

Option 1: Import only types (Recommended)
// user.model.ts
import type { PostSaved } from "../post/post.types";  // ✅ Types only

export class UserModel extends BaseModel {
  async getPosts(): Promise<PostSaved[]> {
    // Load actual class via dynamic 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";  // ✅ Types only

export class PostModel extends BaseModel {
  async getAuthor(): Promise<UserSaved> {
    const { UserModel } = await import("../user/user.model");
    return UserModel.findById(this.userId);
  }
}
Option 2: Separate common types file
// 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";
Option 3: Redesign dependency direction
// Reference user.model.ts only from post.model.ts
import { UserModel } from "../user/user.model";  // ✅ Unidirectional

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

// Don't reference post.model.ts from user.model.ts
// (Handle in PostApi if needed)

HMR Not Working for Specific Files

Situation

// user.model.ts modification → ✅ HMR works
// post.model.ts modification → ✅ HMR works
// admin-helper.ts modification → ❌ Not reflected

Cause 1: Not Matching Boundary Pattern

Check:
const dump = await hot.dump();
const helper = dump.find(d => d.nodePath.includes("admin-helper.ts"));

if (!helper) {
  console.log("❌ This file is not a Boundary!");
}
Fix:
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    `./src/**/*.ts`,  // Should already be included
  ],
});

Cause 2: File Not Imported

HMR only tracks files that are actually imported. Check:
const dump = await hot.dump();
console.log(`Total tracked files: ${dump.length}`);

// If admin-helper.ts isn't in the list, nothing is importing it!
Fix: Import the file somewhere, or delete it if not needed.

SSR File Changes Not Reloading

Situation

// Modify src/ssr/routes.ts
// Save but not reflected

Cause

SSR files are handled specially.

Fix

Syncer automatically handles SSR file changes, but if manual reload is needed:
# Server restart
Ctrl+C
pnpm dev
Or in code:
// Check SSR file handling in syncer.ts
if (diffFilePath.includes("/src/ssr/")) {
  console.log("SSR config changed - reloading...");
  await hot.invalidateFile(diffFilePath, event);
  await this.autoloadSSRRoutes();
}

HMR Too Slow

Situation

# Save file
# 5 seconds later...
🔄 Invalidated:  # ← 🚨 Too slow!

Cause

Dependency tree is too deep or wide, invalidating many files. Check:
const dump = await hot.dump();
const userModel = dump.find(d => d.nodePath.includes("user.model.ts"));

console.log(`Dependencies: ${userModel?.children?.length}`);
// If over 100, that's a problem!

Solutions

1. Minimize imports
// ❌ Bad example - import all Models
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 more

// ✅ Good example - only what's needed
import { UserModel } from "./user.model";
import { PostModel } from "./post.model";
2. Optimize library imports like lodash
// ❌ Bad example
import * as lodash from "lodash";

// ✅ Good example
import { pick, omit } from "lodash";
3. Reduce Boundary scope
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    // Only frequently changed files instead of all
    `./src/**/*.model.ts`,
    `./src/**/*.api.ts`,
  ],
});

Debugging Tips

Enable HMR Logs

To see detailed HMR operation:
DEBUG=hmr-hook:* pnpm dev
Output example:
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

Check Dependency Tree

const dump = await hot.dump();

// Check dependencies for specific file
const userModel = dump.find(d => d.nodePath.includes("user.model.ts"));
console.log("Children:", userModel?.children);

// Save entire tree as JSON
import { writeFileSync } from "fs";
writeFileSync("dependency-tree.json", JSON.stringify(dump, null, 2));

Track Invalidated Files

Pay close attention to logs Syncer prints to console:
🔄 Invalidated:
- src/user/user.model.ts (with 8 APIs)
- src/user/user.api.ts
- src/post/post.api.ts  # ← Why post.api.ts too?

# user.model.ts changed but post.api.ts also reloaded
# → post.api.ts uses UserModel

Monitor Memory Usage

// 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);  // Every 10 seconds

Last Resort: Full Restart

When all solutions fail:
# 1. Stop process
Ctrl+C

# 2. Delete build directory
rm -rf dist/

# 3. Delete checksums
rm -rf .sonamu/checksums/

# 4. Delete node_modules (for really serious cases)
rm -rf node_modules/
pnpm install

# 5. Restart
pnpm dev

Getting Help

If the problem still isn’t resolved:

1. Report to GitHub Issues

Create a new issue at: https://github.com/cartanova-ai/sonamu/issues

2. Include the Following Information

Please provide this information for problem resolution:
  • Sonamu version (package.json)
  • Node.js version (node -v)
  • Error message that occurred
  • Minimal reproducible example
  • HMR logs (DEBUG=hmr-hook:* pnpm dev)

3. Include File Structure Example

Clearly show the file structure where the problem occurs:

Please include an example description:
Symptoms:
- Modify and save user.model.ts
- Console shows "Invalidated: user.model.ts"
- But user.api.ts still executes old code
- Server restart fixes it

Attempted solutions:
1. Check dynamic import → Already dynamically loaded
2. Server restart → Temporarily fixed but recurs
3. Cache deletion (rm -rf dist/) → No effect

HMR logs:
[Attach log content]
Detailed descriptions like this help you get faster responses!