breakinto
v1.0.3
Published
A powerful interactive debugger for Node.js - pause execution, inspect variables, hot-patch code, and snapshot state into tests
Maintainers
Keywords
Readme
🛑 Breakinto
Node.js Interactive Debugger with Hot-Reload & REPL-Driven Development
Debug Node.js without restart! Drop into an interactive REPL at any point to inspect local variables at runtime, edit code while running, and generate tests from live state. A better alternative to node inspect and console.log debugging.
✨ Features
- 🔍 Interactive REPL - Pause execution anywhere and explore your application state with REPL-driven development
- 🎯 True Context Evaluation - Inspect local variables at runtime on the paused call stack via V8 Inspector Protocol
- 🔥 Hot-Reload Debugging - Edit code while running Node.js and reload it instantly without restarting your application
- 📸 Snap & Replay - Generate tests from live state: serialize complex objects (including circular refs) into Vitest/Jest test files
- 👀 Code Context - View surrounding source code with syntax highlighting
- 🎨 Beautiful Output - Colorized terminal output with chalk
- 🏗️ Split-Process Architecture - Robust separation between debugged app and CLI tool
🚀 Installation
npm install breakinto📖 Quick Start
1. Add Breakinto to Your Code
import breakinto from 'breakinto';
async function processOrder(order) {
const user = await fetchUser(order.userId);
const inventory = checkInventory(order.items);
// 🛑 Pause here to inspect state
breakinto();
return finalizeOrder(user, inventory);
}2. Run Your Application
node your-app.js3. Interactive Session Starts Automatically
Breakinto: Waiting for debugger connection on port 9847...
> 15 breakinto();
16
breakinto> user
{ id: 42, name: 'Alice', email: '[email protected]' }
breakinto> inventory
{ available: true, quantity: 5 }
breakinto> user.name = 'Bob' // Modify state on the fly!
'Bob'
breakinto> .continue // Resume execution🎮 Commands
| Command | Shortcut | Description |
|---------|----------|-------------|
| .continue | .c | Resume execution and close the debugger |
| .whereami | - | Show surrounding source code with current line highlighted |
| .reload [file] | - | Hot-reload the current source file (re-patches the running function) |
| .snap [filename] | - | Generate a snapshot test file from current scope variables |
JavaScript Evaluation
Any JavaScript expression you type is evaluated in the context of the paused call frame:
breakinto> const total = items.reduce((sum, item) => sum + item.price, 0)
150
breakinto> user.permissions.includes('admin')
true
breakinto> await fetchData(user.id) // Async/await works!
{ ... }📚 Usage Examples
Example 1: Debugging a Bug
import breakinto from 'breakinto';
async function calculateDiscount(user, cart) {
const basePrice = cart.total;
const discountRate = user.tier === 'premium' ? 0.2 : 0.1;
// 🤔 Why is discount calculation wrong?
breakinto();
return basePrice * (1 - discountRate);
}In the REPL:
breakinto> user.tier
'premium'
breakinto> cart.total
100
breakinto> discountRate
0.2
breakinto> basePrice * (1 - discountRate)
80 # ✅ Calculation looks correct!
breakinto> .continueExample 2: Hot-Patching Code
import breakinto from 'breakinto';
async function greet(name) {
breakinto();
return `Hello, ${name}!`;
}
greet('World').then(console.log);Interactive Session:
breakinto> .whereami
4 async function greet(name) {
> 5 breakinto();
6 return `Hello, ${name}!`;
7 }
# Edit src/demo.js - change "Hello" to "Hi"
breakinto> .reload
✓ Reloaded successfully
breakinto> .continue
# Output: Hi, World!Example 3: Snapshot to Test
import breakinto from 'breakinto';
async function main() {
const user = {
name: 'Alice',
settings: { theme: 'dark' },
friends: []
};
user.friends.push(user); // Circular reference!
breakinto();
}
main();In the REPL:
breakinto> .snap user_state.test.js
✓ Snapshot saved to user_state.test.js
breakinto> .continueGenerated Test File:
import { test, expect } from 'vitest';
test('snapshot state', () => {
const user = {
name: 'Alice',
settings: { theme: 'dark' },
friends: [/* Circular(user) */ null]
};
// Add assertions here
console.log('Snapshot loaded');
});🏗️ Architecture
Breakinto uses a split-process architecture for maximum robustness:
┌────────────────────────────────────────────┐
│ Your Application Process │
│ ┌──────────────────────────────────────┐ │
│ │ await breakinto() │ │
│ │ 1. Opens V8 Inspector on random port│ │
│ │ 2. Spawns CLI tool subprocess │ │
│ │ 3. Triggers debugger; breakpoint │ │
│ └──────────────────────────────────────┘ │
└────────────┬───────────────────────────────┘
│ V8 Inspector Protocol
│ (WebSocket)
┌────────────▼───────────────────────────────┐
│ CLI Tool (Separate Node Process) │
│ ┌──────────────────────────────────────┐ │
│ │ 1. Connects to Inspector WebSocket │ │
│ │ 2. Receives Debugger.paused event │ │
│ │ 3. Starts interactive REPL │ │
│ │ 4. Evaluates on paused call stack │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘Key Components
📁 src/index.js - Main Entry Point
- Exports the
breakinto()function - Opens V8 Inspector on a random port
- Spawns the CLI tool as a child process
- Triggers
debugger;statement to pause execution
📁 src/cli.js - CLI Tool
- Connects to the Inspector WebSocket
- Handles
Debugger.pausedevents - Manages the REPL session
- Coordinates command execution
📁 src/repl.js - REPL Engine
- Creates interactive prompt
- Evaluates expressions using
Debugger.evaluateOnCallFrame - Handles object inspection with
util.inspect - Registers custom commands
📁 src/inspector_client.js - V8 Inspector Client
- Wraps
node:inspectorSession API - Provides promisified
post()method - Manages Debugger/Runtime domain enable/disable
📁 src/snapshot.js - Snapshot Generator
- Serializes complex objects in-context (avoids JSON limitations)
- Handles circular references with WeakMap tracking
- Generates Vitest/Jest compatible test files
- Uses
Runtime.callFunctionOnfor safe serialization
📁 src/commands/whereami.js - Context Display
- Reads source files
- Highlights current execution line
- Shows surrounding code context
🔧 Advanced Usage
Accessing Closure Variables
function outer() {
const secret = 'hidden';
async function inner() {
breakinto();
console.log(secret);
}
return inner();
}breakinto> secret
'hidden' # ✅ Closure variables are accessible!Inspecting Complex Objects
const complexObj = {
nested: { deeply: { value: 42 } },
circular: null,
date: new Date(),
regex: /test/gi
};
complexObj.circular = complexObj;
breakinto();breakinto> complexObj
{
nested: { deeply: { value: 42 } },
circular: [Circular *1],
date: 2026-01-02T05:15:49.000Z,
regex: /test/gi
}Debugging Async Operations
async function fetchUserData(userId) {
const user = await db.users.findOne({ id: userId });
breakinto();
const posts = await db.posts.find({ authorId: user.id });
return { user, posts };
}breakinto> user
{ id: 123, name: 'Alice' }
breakinto> await db.posts.count({ authorId: user.id })
15 # Run async queries in the REPL!🆚 Why Choose Breakinto?
Better Than console.log Debugging
- No more restart cycles - Modify variables and test fixes instantly
- Full context access - See all local variables, not just what you logged
- Interactive exploration - Run any expression in the paused context
- Professional workflow - Generate reproducible tests from debugging sessions
Alternative to node inspect
- Automatic setup - No need to restart with
--inspectflag - Better UX - Colorized output, syntax highlighting, and intuitive commands
- Runtime inspection - Drop breakpoints anywhere in running code
- Hot-reload capability - Edit and reload code without restarting the debugger
- REPL-driven development - Interactive workflow similar to Ruby's
pryand Python'sipdb
Debug Node.js Without Restart
Stop wasting time in the edit-restart-debug cycle:
- Add
breakinto()where you need to inspect - Run your app normally (no special flags needed)
- Inspect local variables, modify state, and test fixes interactively
- Hot-reload your changes and continue debugging
- Generate test snapshots from live state
🎯 Use Cases
1. Production Debugging (with caution)
Add breakinto conditionally to debug production issues:
if (process.env.DEBUG_USER === userId) {
breakinto();
}2. Learning Codebases
Explore unfamiliar code by dropping breakpoints:
// What does this legacy function even do?
breakinto();3. Test-Driven Debugging
Capture failing state as a test:
if (isUnexpectedState) {
breakinto(); // .snap to create regression test
}4. Live Code Experimentation
Try different approaches without restarting:
breakinto(); // Edit algorithm in .reload🐛 Troubleshooting
"Inspector already open"
Problem: Another debugger is already attached.
Solution: Close other debuggers or restart your application.
"Failed to open inspector on port X"
Problem: Port is in use.
Solution: Breakinto automatically tries random ports. If it persists, check for firewall/permission issues.
"Cannot read properties of undefined"
Problem: Variable might be optimized away by V8.
Solution: Use the variable before calling breakinto() to prevent optimization:
console.log(myVar); // Force V8 to keep it
breakinto();Hot-Reload Not Working
Problem: .reload didn't update the function.
Limitation: Hot-reloading has limitations with:
- Module-level code (only function bodies are patched)
- Native modules
- Cached requires
🤝 Contributing
Contributions are welcome! Here are some ideas:
- [ ] Add breakpoint management (
.break,.delete) - [ ] Support step-over/step-into debugging
- [ ] Add watch expressions
- [ ] Improve syntax highlighting
- [ ] Add configuration file support
- [ ] Create VS Code extension
📄 License
MIT
🙏 Acknowledgments
Inspired by:
Built with:
- V8 Inspector Protocol
- chalk - Terminal styling
- cli-highlight - Syntax highlighting
- ws - WebSocket client
📞 Support
Found a bug? Have a feature request? Open an issue!
Break into your code! 🔍🛑
