Skill: ivangdavila/nodejs (version 1.0.1)

--- name: NodeJS slug: nodejs version: 1.0.1 description: Write reliable Node.js avoiding event loop blocking, async pitfalls, ESM gotchas, and memory leaks. metadata: {"clawdbot":{"emoji":"💚","requires":{"bins":["node"]},"os":["linux","darwin","win32"]}} --- ## Quick Reference | Topic | File | |-------|------| | Callbacks, Promises, async/await, event loop | `async.md` | | CommonJS vs ESM, require vs import | `modules.md` | | Error handling, uncaught exceptions | `errors.md` | | Readable, Writable, Transform, backpressure | `streams.md` | | Memory leaks, event loop blocking, profiling | `performance.md` | | Input validation, dependencies, env vars | `security.md` | | Jest, Mocha, mocking, integration tests | `testing.md` | | npm, package.json, lockfiles, publishing | `packages.md` | ## Critical Traps - `fs.readFileSync` blocks entire server — use `fs.promises.readFile` - Unhandled rejection crashes Node 15+ — always `.catch()` or try/catch - `process.env` values are strings — `"3000"` not `3000`, parseInt needed - `JSON.parse` throws on invalid — wrap in try/catch - `require()` cached — same object, mutations visible everywhere - Circular deps return incomplete export — restructure to avoid - Event listeners accumulate — `removeListener` or `once()` - `async` always returns Promise — even for plain return - `pipeline()` over `.pipe()` — handles errors and cleanup - No `__dirname` in ESM — use `fileURLToPath(import.meta.url)` - `Buffer.from(string)` — encoding matters, default UTF-8

path: async.md
content: # Async Traps

## Event Loop
- `setImmediate` runs after I/O — `process.nextTick` runs before (order matters)
- Long loops starve I/O — break with `setImmediate` every N iterations

## Promises
- `Promise.all()` fails fast — one rejection kills all, use `allSettled` for partial results
- Forgetting `await` — function continues without waiting, no error thrown
- `return await` redundant — except inside try/catch (needed to catch rejection)
- Sequential `await` in loop is slow — use `Promise.all(items.map(...))` for parallel

## Callbacks
- Callback errors don't propagate — try/catch won't catch them, handle in callback
- Zalgo: mixing sync/async — always be consistent, async callback even for cached values


path: errors.md
content: # Error Handling Traps

- Unhandled rejection crashes Node 15+ — always `.catch()` or try/catch with await
- Callback errors don't throw — must check `if (err)` in callback, try/catch useless
- `process.on('uncaughtException')` — log and EXIT, don't try to continue
- `process.on('unhandledRejection')` — same, log and exit gracefully
- Throwing non-Error — loses stack trace, always `throw new Error()`
- `error` event on streams — unhandled crashes process, must attach handler


path: modules.md
content: # Module Traps

## CommonJS
- `exports = x` breaks — must use `module.exports = x` (exports is just reference)
- `require()` cached — same object returned, mutations visible everywhere
- Circular deps return incomplete export — whatever was assigned at require time

## ESM
- No `__dirname` — use `fileURLToPath(import.meta.url)` + `dirname()`
- Can't `require()` ESM from CJS — must use dynamic `import()` which returns Promise
- `import` is hoisted — runs before other code, can't conditionally import

## Interop
- ESM importing CJS — only default import works, no named exports
- `"type": "module"` affects all `.js` — use `.cjs` extension for CommonJS files


path: packages.md
content: # Package Traps

- `^1.2.3` allows minor updates — can break, use exact versions for apps
- `npm install` may update lockfile — use `npm ci` in CI/production
- `devDependencies` in production — `npm install --production` or `npm ci --omit=dev`
- `peerDependencies` not auto-installed — npm 7+ installs them, can conflict
- Lockfile not committed — different installs across machines
- `npm audit fix --force` — may do major version bumps, breaks things
- `node_modules` in Docker — use multi-stage build, don't copy from host


path: performance.md
content: # Performance Traps

## Event Loop Blocking
- `fs.readFileSync` blocks entire server — use `fs.promises.readFile`
- CPU-intensive code blocks — no I/O processed, use worker_threads
- Large `JSON.parse` blocks — consider streaming parser for big files
- RegEx catastrophic backtracking — `/(a+)+$/` hangs on "aaaaaaaaaaaaaaX"

## Memory Leaks
- Event listeners accumulate — `removeListener` or use `once()`
- `setInterval` without `clearInterval` — keeps running forever
- Closures hold references — large objects stay in memory
- Global caches grow unbounded — use LRU with max size

## Worker Threads
- Thread creation has overhead — don't spawn per-request, use pool
- `postMessage` copies data — use SharedArrayBuffer for large data


path: security.md
content: # Security Traps

- `process.env` values are strings — `PORT=3000` is `"3000"`, parseInt needed
- Missing env var is `undefined` — no error, check explicitly on startup
- `eval()` with user input — remote code execution
- `exec(userInput)` — command injection, use `execFile` with args array
- Path traversal — `../../../etc/passwd`, validate with `path.resolve` + prefix check
- Prototype pollution — `obj[userKey] = val` can modify `__proto__`
- `npm audit` regularly — dependencies have vulnerabilities
- Never log secrets — sanitize before logging, check for tokens/passwords


path: streams.md
content: # Stream Traps

- `write()` returns false — buffer full, keep writing = memory bloat
- Must wait for `drain` event — before resuming writes after false
- `.pipe()` doesn't propagate errors — source error won't reach destination handler
- `pipeline()` over `.pipe()` — handles errors AND cleanup on all streams
- Missing error handler crashes — `stream.on('error', handler)` on EVERY stream
- `readable.destroy()` — call on error to prevent leaks, pipeline does this


path: testing.md
content: # Testing Traps

- `jest.mock()` hoisted — runs before imports, can't use variables from file
- Mock not cleaned between tests — `jest.clearAllMocks()` in `beforeEach`
- `async` test without `await` — test passes before promise resolves/rejects
- `toEqual` vs `toBe` — `toBe` uses `===`, fails on equal objects
- Timer mocks — `jest.useFakeTimers()` needed, real timers make tests flaky
- Snapshot too big — small changes break test, snapshot should be focused
- `done()` callback timeout — forgetting to call `done` hangs test


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)