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
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!