Trace the source
Copies tied to async or collection work deserve a closer look.
Static analysis for memory
MemGuard spots JavaScript and TypeScript patterns that may cost memory, handles, and headroom.
Built to report evidence, not anxiety.
A finding, with context
Every report says what it saw, why scale matters, and what to consider next. It never calls a heuristic a leak.
MemGuard
────────────────────────────────────────
⚠ MEM003 Potentially unbounded async concurrency
src/order.service.js:3
2 │ const orders = await Order.find({});
> 3 │ await Promise.all(orders.map((o) => enrich(o)));
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 │ return orders;
Problem:
Promise.all() may start one operation per element of a collection whose size
is not bounded here
Why it matters:
Every element produces a promise before any of them settle, so peak memory
and open handles scale with the collection size. A collection of 10,000 items
becomes 10,000 simultaneous operations.
Consider:
• Process the collection in fixed-size batches, awaiting each batch
• Apply a concurrency limiter such as p-limit or p-map
• Truncate the collection with .slice() before mapping when a cap is acceptable
Severity: WARNING
────────────────────────────────────────Signal, not noise
A spread by itself is ordinary code. MemGuard waits for evidence: a loop, an awaited source, or a cache without eviction.
Copies tied to async or collection work deserve a closer look.
Limits, pagination, and eviction can turn a risky pattern into a safe one.
Nothing defaults to error. You choose when a finding becomes a gate.
Small surface area. Specific evidence. Useful output on the first run.
⚠ MEM003 Potentially unbounded async concurrency src/order.service.js:3 2 │ const orders = await Order.find({}); > 3 │ await Promise.all(orders.map((o) => enrich(o))); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 │ return orders; Why it matters: Every element produces a promise before any of them settle, so peak memory and open handles scale with the collection size. Consider: • Process the collection in fixed-size batches, awaiting each batch • Apply a concurrency limiter such as p-limit or p-map
For CI
Machine-readable JSON and deliberate exit codes let a pipeline distinguish code findings from tool failures.
npx memguard run --json --strictPer-file analysis. An eviction in another file may not be visible to the rule.
Names are clues. MEM005 cannot know whether a query returns ten rows or ten million.
No automatic fixes. Changing aliasing or cache behavior needs an engineering decision.
The wider plan
MemGuard reads your source from the command line today. What follows runs the same analysis where the code is written, in the process while it runs, and on the pull request before it merges.
Reads JavaScript and TypeScript source and reports the patterns that may cost memory.
AvailableRuns the same rules in your editor as you type, so a finding arrives while you write the line.
PlannedStreams heap growth, collection pauses, retained objects, and open handles as the process runs.
PlannedA GitHub Action that posts findings as review comments on the lines a pull request changed.
PlannedEvery stage has to earn the next one. None of them rewrite your code for you, because changing how memory is held changes how an application behaves.
Find the pattern in the source.
Say why the size of the collection is what costs you.
Offer a change a person can accept or reject.
Confirm the change in a process that is actually running.
Shed load before the process runs out of memory.
Last, and only where behavior stays predictable.
Start here
npx memguard setup && npx memguard runnpm i memguardNode 22+. Zero runtime dependencies beyond TypeScript.