Debugging
Thunder exposes the V8 Inspector Protocol for debugging TypeScript and JavaScript edge functions with breakpoints, variable inspection, and step-through execution. Three clients are supported: VS Code, Chrome DevTools, and Neovim (via nvim-dap).
Prerequisites
Section titled “Prerequisites”- The
thunderbinary (a debug build viacargo buildis recommended for development). - One of the supported debug clients:
- VS Code — JavaScript Debugger extension (built-in since VS Code 1.60).
- Chrome — any Chromium-based browser (Chrome, Edge, Brave).
- Neovim —
nvim-dap+nvim-dap-vscode-js(or direct adapter).
All three clients use the Chrome DevTools Protocol (CDP) over WebSocket. The same runtime flags and ports work identically across them.
Debugging modes
Section titled “Debugging modes”watch —inspect (attach and continue)
Section titled “watch —inspect (attach and continue)”Starts the inspector server and continues running immediately. Breakpoints set in your source files are hit when matching requests arrive.
thunder watch --path ./examples/hello/hello.ts --inspectThe inspector defaults to port 9229. To use a different port:
thunder watch --path ./examples/hello/hello.ts --inspect 9230watch —inspect-brk (break on first statement)
Section titled “watch —inspect-brk (break on first statement)”Pauses execution before the first line of user code runs. The process blocks until a debugger attaches. Use this to debug module initialization or the very first request.
thunder watch --path ./examples/hello/hello.ts --inspect --inspect-brkNote: --inspect is required alongside --inspect-brk. The --inspect-brk flag sets the break-on-first-statement behavior; --inspect enables the server.
test —inspect (debug a test file)
Section titled “test —inspect (debug a test file)”Attaches the inspector to a single test run. When --inspect is active, exactly one test file must be selected.
thunder test --path ./tests/js/my-test.ts --inspectVS Code setup
Section titled “VS Code setup”Launch configurations
Section titled “Launch configurations”Add these to your .vscode/launch.json:
{ "version": "0.2.0", "configurations": [ { "name": "Attach Thunder (9229)", "type": "node", "request": "attach", "port": 9229, "continueOnAttach": true, "sourceMaps": true }, { "name": "Attach Thunder (inspect-brk, 9229)", "type": "node", "request": "attach", "port": 9229, "sourceMaps": true } ]}The first configuration is for --inspect mode (does not pause on attach). The second is for --inspect-brk mode (pauses at the first statement).
Key settings:
"type": "node"— required because the runtime advertises itself as a Node target in the/json/listendpoint."request": "attach"— connects to an already-running process."sourceMaps": true— enables automatic TypeScript source map resolution."continueOnAttach": true— prevents VS Code from pausing when it connects (use this for--inspect, omit for--inspect-brk).
Step-by-step workflow
Section titled “Step-by-step workflow”-
Start the runtime with the inspector enabled:
Terminal window thunder watch --path ./examples/hello/hello.ts --inspectYou should see:
Inspector server started on 127.0.0.1:9229 (target: hello) -
In VS Code, open the Run and Debug panel (
Ctrl+Shift+D/Cmd+Shift+D). -
Select “Attach Thunder (9229)” and press F5.
-
Set a breakpoint in your TypeScript source file.
-
Trigger the function:
Terminal window curl http://localhost:9000/hello -
VS Code stops at the breakpoint. Inspect variables, step through code, and use the debug console.
Chrome DevTools setup
Section titled “Chrome DevTools setup”-
Start the runtime with
--inspect:Terminal window thunder watch --path ./examples/hello/hello.ts --inspect -
Open a Chromium-based browser and navigate to:
chrome://inspect -
Under “Discover network targets”, click Configure… and add:
127.0.0.1:9229 -
The function target (e.g.,
hello) appears under “Remote Target”. Click inspect. -
A DevTools window opens. Go to the Sources tab to set breakpoints in your TypeScript files.
-
Trigger the function:
Terminal window curl http://localhost:9000/hello
For --inspect-brk mode, open Chrome DevTools before execution starts. Chrome pauses at the first statement immediately.
Limitations:
- Chrome DevTools does not persist breakpoints between sessions.
- The Console tab cannot call
fetch()against localhost due to browser sandbox restrictions. Use curl instead.
Neovim nvim-dap setup
Section titled “Neovim nvim-dap setup”Dependencies
Section titled “Dependencies”Install using lazy.nvim (or your plugin manager):
{ "mfussenegger/nvim-dap", dependencies = { "microsoft/vscode-js-debug", "mxsdev/nvim-dap-vscode-js", "rcarriga/nvim-dap-ui", -- optional "nvim-neotest/nvim-nio", -- required by nvim-dap-ui },}Build the adapter once:
cd ~/.local/share/nvim/lazy/vscode-js-debugnpm install && npx gulp vsDebugServerBundlemv dist outConfiguration
Section titled “Configuration”Add to your Neovim config:
local dap = require("dap")local dap_vscode_js = require("dap-vscode-js")
dap_vscode_js.setup({ debugger_path = vim.fn.stdpath("data") .. "/lazy/vscode-js-debug", adapters = { "pwa-node" },})
dap.configurations.typescript = { { type = "pwa-node", request = "attach", name = "Attach Thunder (9229)", port = 9229, address = "127.0.0.1", continueOnAttach = true, sourceMaps = true, resolveSourceMapLocations = { "${workspaceFolder}/**" }, }, { type = "pwa-node", request = "attach", name = "Attach Thunder (inspect-brk, 9229)", port = 9229, address = "127.0.0.1", sourceMaps = true, resolveSourceMapLocations = { "${workspaceFolder}/**" }, },}
dap.configurations.javascript = dap.configurations.typescriptWorkflow
Section titled “Workflow”- Start the runtime:
thunder watch --path ./examples/hello/hello.ts --inspect - Open the TypeScript file in Neovim.
- Set a breakpoint:
:lua require("dap").toggle_breakpoint() - Start the session:
:lua require("dap").continue()and select “Attach Thunder (9229)”. - Trigger the function:
curl http://localhost:9000/hello - Use standard nvim-dap keymaps to step through code.
Suggested keymaps:
| Key | Action |
|---|---|
<F5> | Continue |
<F10> | Step over |
<F11> | Step into |
<F12> | Step out |
<leader>db | Toggle breakpoint |
<leader>dr | Open REPL |
Minimal setup without vscode-js-debug
Section titled “Minimal setup without vscode-js-debug”If you prefer not to install the full adapter, connect directly to the inspector:
local dap = require("dap")
dap.adapters.node2 = { type = "server", host = "127.0.0.1", port = 9229,}
dap.configurations.typescript = { { type = "node2", request = "attach", name = "Attach Thunder direct (9229)", sourceMaps = true, continueOnAttach = true, },}This skips the adapter process entirely. Source map resolution is more limited compared to the full vscode-js-debug adapter, but basic breakpoints and variable inspection work.
Source maps
Section titled “Source maps”Source maps work automatically. TypeScript files are compiled and bundled into .eszip archives during the watch build step. The compiler emits a source map for each module, stored alongside the compiled output inside the bundle.
When a module is loaded into V8, the runtime attaches the source map as an inline base64 data URL. V8 notifies the debugger via CDP, and the debug client resolves original TypeScript file paths from the sources array automatically.
No manual source map configuration is required.
Using debugger statements
Section titled “Using debugger statements”Add debugger; anywhere in your source to force a breakpoint without setting one in the IDE:
export default async function handler(req: Request): Promise<Response> { debugger; // pauses here when a debugger is attached const body = await req.json(); return Response.json({ received: body });}With --inspect, debugger; statements are hit only after the client is attached. With --inspect-brk, the process waits for attachment first, so all debugger; statements are reachable.
Debugging multiple functions
Section titled “Debugging multiple functions”When multiple functions are loaded, each gets its own inspector port, assigned sequentially from the base port:
thunder watch --path ./examples --inspect 9229# hello -> port 9229# json-api -> port 9230# cors -> port 9231# ...Add a separate launch configuration for each function you want to debug:
{ "name": "Attach Thunder (json-api:9230)", "type": "node", "request": "attach", "port": 9230, "continueOnAttach": true, "sourceMaps": true}Troubleshooting
Section titled “Troubleshooting”Cannot connect or connection refused
Section titled “Cannot connect or connection refused”- Ensure the runtime is running before attaching.
- Verify the port matches between
--inspectand your launch configuration. - Check that nothing else is using the port:
lsof -i :9229.
Breakpoints not hit
Section titled “Breakpoints not hit”- Ensure
sourceMaps: trueis set in the launch configuration. - Confirm the breakpoint is in a file that is part of the loaded function.
- Trigger the function with an HTTP request. The runtime only executes handler code when a request arrives.
Port already in use after a crash
Section titled “Port already in use after a crash”The inspector TCP port is held until the process fully exits. If the port is stuck after a crash:
lsof -ti :9229 | xargs kill -9“Unknown Source” in the call stack
Section titled ““Unknown Source” in the call stack”This happens when the debugger receives a pause event before processing the script-parsed event. Try --inspect-brk instead of --inspect to force an event loop flush before any code runs.
Security note
Section titled “Security note”The inspector binds to 127.0.0.1 by default and should only be used in development. The --inspect-allow-remote flag binds on 0.0.0.0, exposing debugger endpoints to the network. Never enable the inspector in production.