next-leak 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
1
  import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
2
2
  import {
3
3
  effectiveVerdict
4
- } from "./chunk-4FYSLLSX.js";
4
+ } from "./chunk-4NKGNMEA.js";
5
5
  import {
6
6
  assessPeakPressure,
7
- describePeakPressure
8
- } from "./chunk-XHPUAMJG.js";
7
+ describePeakPressure,
8
+ retainedAfterLoad
9
+ } from "./chunk-E464SO7G.js";
9
10
 
10
11
  // src/html-report.ts
11
12
  var MB = 1024 * 1024;
@@ -13,7 +14,10 @@ var VERDICT_COLOR = {
13
14
  leak: "#c0392b",
14
15
  stable: "#27ae60",
15
16
  inconclusive: "#e67e22",
16
- saturating: "#2980b9"
17
+ saturating: "#2980b9",
18
+ // Its own colour, not the leak red: nothing was retained, and nothing is
19
+ // green about a process that reached a ceiling it cannot come back from.
20
+ pressure: "#8e44ad"
17
21
  };
18
22
  function escapeHtml(value) {
19
23
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -46,7 +50,7 @@ function ownerCell(attribution) {
46
50
  return `${attribution.owner}${source}${packageName}`;
47
51
  }
48
52
  function peakBlock(route, parameters) {
49
- const retained = route.memorySamples.at(-1)?.heapUsed;
53
+ const retained = retainedAfterLoad(route.memorySamples);
50
54
  if (retained === void 0 || route.peaks === void 0) {
51
55
  return "";
52
56
  }
@@ -6,6 +6,12 @@ var HEAP_LIMIT_SHARE = 0.75;
6
6
  var RSS_OVER_RETAINED = 8;
7
7
  var RSS_FLOOR_BYTES = 512 * MB;
8
8
  var maxOf = (peaks, read) => peaks.reduce((highest, peak) => Math.max(highest, read(peak)), 0);
9
+ function retainedAfterLoad(memorySamples) {
10
+ const cycles = memorySamples.slice(1);
11
+ return cycles.length === 0 ? void 0 : cycles.reduce((lowest, sample) => Math.min(lowest, sample.heapUsed), Infinity);
12
+ }
13
+ var reachesHeapCeiling = (heapUsed, heapLimitBytes) => heapUsed >= heapLimitBytes * HEAP_LIMIT_SHARE;
14
+ var reachesRssCeiling = (rss, retainedHeapBytes) => rss >= RSS_FLOOR_BYTES && rss >= retainedHeapBytes * RSS_OVER_RETAINED;
9
15
  function assessPeakPressure(input) {
10
16
  const sampled = input.peaks.filter((peak) => peak.polls > 0);
11
17
  if (sampled.length === 0) {
@@ -14,7 +20,7 @@ function assessPeakPressure(input) {
14
20
  const heapLimitBytes = input.maxOldSpaceMb * MB;
15
21
  const peakHeap = maxOf(sampled, (peak) => peak.heapUsed);
16
22
  const peakRss = maxOf(sampled, (peak) => peak.rss);
17
- if (peakHeap >= heapLimitBytes * HEAP_LIMIT_SHARE) {
23
+ if (reachesHeapCeiling(peakHeap, heapLimitBytes)) {
18
24
  return {
19
25
  class: "heap",
20
26
  peakBytes: peakHeap,
@@ -22,7 +28,7 @@ function assessPeakPressure(input) {
22
28
  heapLimitBytes
23
29
  };
24
30
  }
25
- if (peakRss >= RSS_FLOOR_BYTES && peakRss >= input.retainedHeapBytes * RSS_OVER_RETAINED) {
31
+ if (reachesRssCeiling(peakRss, input.retainedHeapBytes)) {
26
32
  return {
27
33
  class: "rss",
28
34
  peakBytes: peakRss,
@@ -32,6 +38,35 @@ function assessPeakPressure(input) {
32
38
  }
33
39
  return null;
34
40
  }
41
+ var PRESSURE_MIN_SETTLED_CYCLES = 2;
42
+ var reachesCeiling = (peak, pressureClass, input) => pressureClass === "heap" ? reachesHeapCeiling(peak.heapUsed, input.maxOldSpaceMb * MB) : reachesRssCeiling(peak.rss, input.retainedHeapBytes);
43
+ function isSustained(input, pressureClass) {
44
+ if (!input.peaks.every((peak) => peak.polls > 0)) {
45
+ return false;
46
+ }
47
+ const settled = input.peaks.slice(1);
48
+ if (settled.length < PRESSURE_MIN_SETTLED_CYCLES) {
49
+ return false;
50
+ }
51
+ return settled.every((peak) => reachesCeiling(peak, pressureClass, input));
52
+ }
53
+ function assessPressureVerdict(input) {
54
+ if (input.trend.verdict !== "stable" && input.trend.verdict !== "saturating") {
55
+ return input.trend;
56
+ }
57
+ const pressure = assessPeakPressure({
58
+ peaks: input.peaks,
59
+ retainedHeapBytes: input.retainedHeapBytes,
60
+ maxOldSpaceMb: input.maxOldSpaceMb
61
+ });
62
+ if (pressure === null) {
63
+ return input.trend;
64
+ }
65
+ if (!isSustained(input, pressure.class)) {
66
+ return input.trend;
67
+ }
68
+ return { ...input.trend, verdict: "pressure" };
69
+ }
35
70
  var mb = (bytes) => `${(bytes / MB).toFixed(1)} MB`;
36
71
  function describePeakPressure(pressure) {
37
72
  if (pressure.class === "heap") {
@@ -41,6 +76,8 @@ function describePeakPressure(pressure) {
41
76
  }
42
77
 
43
78
  export {
79
+ retainedAfterLoad,
44
80
  assessPeakPressure,
81
+ assessPressureVerdict,
45
82
  describePeakPressure
46
83
  };
@@ -1,8 +1,9 @@
1
1
  import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
2
2
  import {
3
3
  assessPeakPressure,
4
- describePeakPressure
5
- } from "./chunk-XHPUAMJG.js";
4
+ describePeakPressure,
5
+ retainedAfterLoad
6
+ } from "./chunk-E464SO7G.js";
6
7
 
7
8
  // src/issue-report.ts
8
9
  import path from "path";
@@ -50,7 +51,7 @@ next-leak audits its own run and reports these limits. They do not overturn the
50
51
 
51
52
  ` + route.confidence.warnings.map((warning) => `- ${warning.detail}`).join("\n") + `
52
53
  `;
53
- const retainedHeap = route.memorySamples.at(-1)?.heapUsed;
54
+ const retainedHeap = retainedAfterLoad(route.memorySamples);
54
55
  const pressure = retainedHeap === void 0 || route.peaks === void 0 ? null : assessPeakPressure({
55
56
  peaks: route.peaks,
56
57
  retainedHeapBytes: retainedHeap,
package/dist/cli.js CHANGED
@@ -22,11 +22,11 @@ import {
22
22
  runSelfCheck,
23
23
  unregisterChild,
24
24
  validateTarget
25
- } from "./chunk-QNYUT2XF.js";
25
+ } from "./chunk-5VXBMWDN.js";
26
26
  import {
27
27
  classifyTrend
28
- } from "./chunk-4FYSLLSX.js";
29
- import "./chunk-XHPUAMJG.js";
28
+ } from "./chunk-4NKGNMEA.js";
29
+ import "./chunk-E464SO7G.js";
30
30
  import "./chunk-6XYFBOL2.js";
31
31
 
32
32
  // src/cli.ts
@@ -21,6 +21,22 @@ import { type TrendResult, type TrendVerdict } from "./trend.js";
21
21
  * resting size
22
22
  */
23
23
  export type WarningCode = "unsettled" | "settle-unverified" | "load-incomplete" | "abandon-ineffective" | "abandon-before-response" | "spiky-growth" | "near-threshold" | "thin-evidence" | "near-heap-ceiling" | "warm-up-baseline"
24
+ /**
25
+ * The load filled a cache the route never had to hold, and the run has no
26
+ * second experiment that would settle how much of the growth that was.
27
+ *
28
+ * A cache filling up and memory going missing both retain and both climb;
29
+ * only a bounded-key re-measurement tells them apart. On an ISR route that
30
+ * re-measurement is not available: a bounded key set is served from the
31
+ * cache, so the run drives revalidation to reach the renderer and lands on a
32
+ * different path of Next's. Measured on the vercel/next.js#99077
33
+ * reproduction, `/plain` — the same app with the leak taken out — reported
34
+ * `leak (+180.39 MB/1000 req)` with a fresh key per request and `stable
35
+ * (+1.81)` bounded, and the two builds that retain 7x apart came out at
36
+ * +1809.97 and +1815.05 once driven. The number stands as measured; what
37
+ * cannot stand is a paste-ready draft built on it.
38
+ */
39
+ | "cache-residency"
24
40
  /**
25
41
  * Repeated measurements of the same route did not agree.
26
42
  *
@@ -61,6 +77,11 @@ export type ConfidenceInput = {
61
77
  maxOldSpaceMb?: number;
62
78
  /** Warm-up requests the run sent before the baseline, for the warm-up check. */
63
79
  warmupRequests?: number;
80
+ /**
81
+ * Whether this route is served from the ISR cache. Decides whether the
82
+ * cache-residency remedy exists — see `cacheResidencyWarnings`.
83
+ */
84
+ revalidatesFromCache?: boolean;
64
85
  };
65
86
  /**
66
87
  * Cycles a re-measurement uses when a verdict came back `inconclusive` — the
@@ -11,6 +11,27 @@ export type HeapSample = {
11
11
  rss: number;
12
12
  external: number;
13
13
  arrayBuffers: number;
14
+ /**
15
+ * Identity of the process that produced this sample. Optional in the type so
16
+ * fixtures stay readable; the wire schema in `control-client.ts` requires it,
17
+ * so a real sample always carries it.
18
+ */
19
+ pid?: number;
20
+ ppid?: number;
21
+ argv?: readonly string[];
22
+ cwd?: string;
23
+ /**
24
+ * HTTP requests this process has served since boot, counted by the probe in
25
+ * `bootstrap.ts`. Recorded as evidence of how much traffic reached the
26
+ * process behind a reading, not checked anywhere: a clustered server counts
27
+ * in whichever process took the connection. `undefined` when the probe was
28
+ * not installed.
29
+ */
30
+ servedRequests?: number | undefined;
31
+ };
32
+ /** Where `bootstrap.ts` publishes the served-request count. */
33
+ export declare const requestProbe: typeof globalThis & {
34
+ __nextLeakServedRequests?: number;
14
35
  };
15
36
  export type ControlServerOptions = {
16
37
  /** Directory where heap snapshots are written. */
@@ -1,9 +1,9 @@
1
1
  import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
2
2
  import {
3
3
  renderHtmlReport
4
- } from "./chunk-YLHE4N5G.js";
5
- import "./chunk-4FYSLLSX.js";
6
- import "./chunk-XHPUAMJG.js";
4
+ } from "./chunk-7WUE3WOI.js";
5
+ import "./chunk-4NKGNMEA.js";
6
+ import "./chunk-E464SO7G.js";
7
7
  import "./chunk-6XYFBOL2.js";
8
8
  export {
9
9
  renderHtmlReport
package/dist/index.js CHANGED
@@ -41,17 +41,17 @@ import {
41
41
  sourceIndexAt,
42
42
  summarizeBaseline,
43
43
  validateTarget
44
- } from "./chunk-QNYUT2XF.js";
44
+ } from "./chunk-5VXBMWDN.js";
45
45
  import {
46
46
  renderHtmlReport
47
- } from "./chunk-YLHE4N5G.js";
47
+ } from "./chunk-7WUE3WOI.js";
48
48
  import {
49
49
  classifyTrend
50
- } from "./chunk-4FYSLLSX.js";
50
+ } from "./chunk-4NKGNMEA.js";
51
51
  import {
52
52
  renderIssueMarkdown
53
- } from "./chunk-AUUMRTZZ.js";
54
- import "./chunk-XHPUAMJG.js";
53
+ } from "./chunk-YPVBX3QA.js";
54
+ import "./chunk-E464SO7G.js";
55
55
  import "./chunk-6XYFBOL2.js";
56
56
  export {
57
57
  LaunchError,
package/dist/isr.d.ts CHANGED
@@ -25,6 +25,10 @@ export type RevalidationPlan = {
25
25
  kind: "drive";
26
26
  headers: Record<string, string>;
27
27
  }
28
+ /** ISR, but every request asks for a key the cache has never held. */
29
+ | {
30
+ kind: "no-cache-to-drive";
31
+ }
28
32
  /** ISR, but the manifest cannot supply what an authentic request needs. */
29
33
  | {
30
34
  kind: "cannot-drive";
@@ -35,5 +39,17 @@ export type RevalidationPlan = {
35
39
  *
36
40
  * A header the user set themselves wins untouched: someone driving a bespoke
37
41
  * revalidation path knows more about it than the manifest does.
42
+ *
43
+ * `requestPath` decides whether driving is needed at all. The header does not
44
+ * merely bypass the cache: it makes Next serve the request through its
45
+ * revalidation path instead of its normal one, and a route whose render count
46
+ * depends on the normal path is then measured somewhere its users never go.
47
+ * Measured on the vercel/next.js#99077 reproduction: with the header, the
48
+ * `partialPrefetching: true` and `false` builds both created 1.95 timers per
49
+ * request and were reported at the same +1970 MB/1000 req; without it they
50
+ * created 3.80 and 0.99 and separated 5x, matching the issue. So the header is
51
+ * sent only where it buys something — when the load revisits keys the cache
52
+ * can already hold. With `{n}` every request carries a key the route has never
53
+ * served, so there is no cache to bypass and driving only distorts.
38
54
  */
39
- export declare function planRevalidation(manifest: PrerenderManifest | undefined, route: string, userHeaders: Record<string, string> | undefined): RevalidationPlan;
55
+ export declare function planRevalidation(manifest: PrerenderManifest | undefined, route: string, userHeaders: Record<string, string> | undefined, requestPath?: string): RevalidationPlan;
@@ -1,8 +1,8 @@
1
1
  import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
2
2
  import {
3
3
  renderIssueMarkdown
4
- } from "./chunk-AUUMRTZZ.js";
5
- import "./chunk-XHPUAMJG.js";
4
+ } from "./chunk-YPVBX3QA.js";
5
+ import "./chunk-E464SO7G.js";
6
6
  import "./chunk-6XYFBOL2.js";
7
7
  export {
8
8
  renderIssueMarkdown
@@ -1,3 +1,5 @@
1
+ import type { TrendResult } from "./trend.js";
2
+ import type { HeapSample } from "./control-server.js";
1
3
  import type { PeakSample } from "./ritual.js";
2
4
  /**
3
5
  * Which ceiling the process came closest to.
@@ -23,17 +25,80 @@ export type PeakPressureInput = {
23
25
  retainedHeapBytes: number;
24
26
  maxOldSpaceMb: number;
25
27
  };
28
+ /**
29
+ * What a route retains once it has served traffic: the floor of the post-GC
30
+ * cycle samples, not the last of them.
31
+ *
32
+ * Every sample here follows a forced collection, but "after a forced
33
+ * collection" is not "after everything collectable was collected" — a sample
34
+ * can carry memory the collector had not reached yet. Measured on the
35
+ * vercel/next.js#92287 reproduction, 2026-09-24, one run's cycle samples were
36
+ * 217.1 / 125.0 / 45.2 / 194.9 MB, and the next run's were 69.3 / 121.2 / 48.4
37
+ * / 41.0. Taking the last put the figure everything here divides by at 194.9 MB
38
+ * on one run and 41.0 MB on the other, which by itself decided whether the same
39
+ * app under the same load was reported at all.
40
+ *
41
+ * The floor is the honest reading: what a process comes back down to is what it
42
+ * holds, and everything above it is memory not yet reclaimed. The baseline is
43
+ * excluded because it precedes any traffic — a route retains nothing before it
44
+ * has served anything, and dividing by that would make every route look
45
+ * disproportionate.
46
+ */
47
+ export declare function retainedAfterLoad(memorySamples: readonly HeapSample[]): number | undefined;
26
48
  /**
27
49
  * Whether a route's peak is far enough from the memory its verdict was
28
50
  * computed on to be worth saying out loud.
29
51
  *
30
- * Deliberately outside the verdict: `leak`/`stable`/`inconclusive` are
31
- * statements about retention after GC, calibrated against real leaks with no
32
- * false positives, and a peak is a different axis. A process that climbs to
33
- * 3.5 GB and hands it all back is honestly `stable` — and still OOM-killed in
34
- * a 1 GB container.
52
+ * On its own this stays outside the verdict, and for the original reason:
53
+ * `leak`/`stable` are statements about retention after GC, calibrated against
54
+ * real leaks with no false positives, and a peak is a different axis. A single
55
+ * high peak is a size, not a direction — an app that reserves 600 MB on its
56
+ * first cycle and holds that level is doing nothing wrong.
57
+ *
58
+ * What the note alone could not carry is the rest of that sentence: a process
59
+ * that climbs to 3.5 GB and hands it all back is honestly `stable`, and still
60
+ * OOM-killed in a 1 GB container. `assessPressureVerdict` below is where a run
61
+ * that reaches the ceiling on every cycle stops being a footnote under a `✔`.
35
62
  */
36
63
  export declare function assessPeakPressure(input: PeakPressureInput): PeakPressure | null;
64
+ export type PressureVerdictInput = {
65
+ /** The post-GC verdict, exactly as the classifier produced it. */
66
+ trend: TrendResult;
67
+ peaks: readonly PeakSample[];
68
+ retainedHeapBytes: number;
69
+ maxOldSpaceMb: number;
70
+ };
71
+ /**
72
+ * Whether a run that retains nothing is nonetheless heading for the ceiling.
73
+ *
74
+ * Every number a verdict is computed from is taken after a forced collection,
75
+ * and production never runs those. That makes the verdict structurally blind to
76
+ * a whole class of death: memory a full GC does reclaim, allocated faster than
77
+ * the runtime reclaims it on its own. Measured on the vercel/next.js#92287
78
+ * reproduction, 2026-09-23 — the app grew ~1 MB of `arrayBuffers` per request
79
+ * to over 3 GB and died, and this tool called it `stable` at -9.00 MB/1000
80
+ * requests, because a forced GC handed all of it back before every sample. That
81
+ * is the trap next-leak exists to warn other people about.
82
+ *
83
+ * Three conditions, and no threshold of its own: the ceiling rules are
84
+ * `assessPeakPressure`'s, asked of each cycle instead of the highest reading.
85
+ *
86
+ * - **The post-GC verdict is `stable` or `saturating`.** `leak` is already the
87
+ * worse news and `inconclusive` is an admission that the series did not
88
+ * decide — promoting *that* to an accusation would be inventing a finding out
89
+ * of a measurement that failed.
90
+ * - **The peak is far enough from what the route retains to be remarked on** —
91
+ * `assessPeakPressure`, thresholds unchanged.
92
+ * - **Every settled cycle reached that ceiling**, not just the highest one.
93
+ * This is the condition that makes the verdict safe. A single high peak is an
94
+ * episode and gets only the note; a process that returns to the ceiling every
95
+ * time it serves traffic is describing what it does under load, which is the
96
+ * thing a container is sized against.
97
+ *
98
+ * Returns the trend unchanged when it does not qualify, so `trend.verdict`
99
+ * stays the raw record everywhere else.
100
+ */
101
+ export declare function assessPressureVerdict(input: PressureVerdictInput): TrendResult;
37
102
  /**
38
103
  * One line, phrased so it never contradicts the verdict next to it. A peak is
39
104
  * the highest value *sampled*: a spike shorter than the poll interval is not
@@ -0,0 +1,18 @@
1
+ import type { Server } from "node:net";
2
+ /**
3
+ * Wraps a module's `createServer` so every server it hands out counts the
4
+ * requests it serves.
5
+ *
6
+ * Its own module rather than a helper inside `bootstrap.ts`, because the
7
+ * bootstrap is an entry point that runs on import and lands inside the
8
+ * *measured* process — where no coverage instrumentation reaches it, and where
9
+ * a mistake is only visible as a run whose numbers came from a process that
10
+ * served nothing.
11
+ *
12
+ * `http.createServer` and `https.createServer` have different signatures, so
13
+ * there is no shared type to borrow. Only the return value is touched, and the
14
+ * arguments are forwarded untyped and unchanged.
15
+ */
16
+ export declare function countServedRequests<T extends {
17
+ createServer: (...args: never[]) => Server;
18
+ }>(module: T): void;
@@ -42,6 +42,29 @@ export declare function boundedMarkerOf(value: string): {
42
42
  marker: string;
43
43
  bound: number;
44
44
  } | null;
45
+ /**
46
+ * Whether a request path mixes both load markers across its segments.
47
+ *
48
+ * A single sample value carrying both is rejected when the config loads, but
49
+ * two params of the same route can each carry a different one, and the mix has
50
+ * no coherent meaning: the load phase resolves the bounded marker per request
51
+ * and leaves `{n}` in the path as a literal, so every request asks for a URL
52
+ * with `%7Bn%7D` in it. The route is refused rather than measured that way.
53
+ */
54
+ export declare function mixesMarkers(requestPath: string): boolean;
55
+ /**
56
+ * A concrete path to ask for on behalf of a request path that still carries
57
+ * load markers.
58
+ *
59
+ * The readiness probe has to ask for what the load will ask for, but expanding
60
+ * the markers belongs to the load phase. Sent as they are, the probe requests a
61
+ * literal `/posts/post-%7Bn%7D` — a key no request of the run will ever revisit,
62
+ * planted in the route's cache before the baseline snapshot is taken.
63
+ *
64
+ * `0` is the value the build is most likely to have prerendered already, and it
65
+ * is one the bounded sequence visits anyway.
66
+ */
67
+ export declare function probeRequestPath(requestPath: string): string;
45
68
  /**
46
69
  * Substitutes sample values into a dynamic route template and returns a
47
70
  * URL-safe request path (or null when a param has no configured value;
package/dist/runner.d.ts CHANGED
@@ -108,12 +108,18 @@ export type RouteReport = {
108
108
  */
109
109
  keyCardinality?: number;
110
110
  /**
111
- * Seconds of the ISR revalidation period this route was driven through,
112
- * when it has one. Absent on routes not served from the ISR cache.
113
- * Recorded because a curve measured against a cache and one measured
114
- * against a re-render are different experiments.
111
+ * Seconds of this route's ISR revalidation period. Absent on routes not
112
+ * served from the ISR cache. Recorded because a curve measured against a
113
+ * cache and one measured against a re-render are different experiments.
115
114
  */
116
115
  revalidatedEverySeconds?: number;
116
+ /**
117
+ * Whether the load carried the build's own revalidation header. Set apart
118
+ * from the period because the two answer different questions: the period
119
+ * says the ISR cache is in play, this says which of Next's two paths
120
+ * served the requests that produced the curve.
121
+ */
122
+ revalidationDriven?: true;
117
123
  /** RSS growth per 1000 requests, computed like the heap figure. */
118
124
  rssPer1000Requests: number;
119
125
  /** Wall-clock per phase — explains where a long run spent its time. */
package/dist/trend.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type TrendVerdict = "leak" | "stable" | "inconclusive" | "saturating";
1
+ export type TrendVerdict = "leak" | "stable" | "inconclusive" | "saturating" | "pressure";
2
2
  export type TrendResult = {
3
3
  verdict: TrendVerdict;
4
4
  /** Mean retained-heap growth per cycle (bytes) over the analyzed window. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-leak",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Find out whether your Next.js app actually leaks memory — how much, on which route, and whose fault it is.",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -51,9 +51,11 @@
51
51
  "esbuild": ">=0.28.1",
52
52
  "qs": ">=6.15.2",
53
53
  "js-yaml": ">=4.3.1 <5",
54
- "fast-uri": ">=3.1.5 <4",
54
+ "fast-uri": ">=3.1.6 <4",
55
55
  "ip-address": ">=10.3.1 <11",
56
- "postcss": ">=8.5.23 <9"
56
+ "postcss": ">=8.5.23 <9",
57
+ "browserslist": ">=4.28.7",
58
+ "@puppeteer/browsers": "^3.2.2"
57
59
  }
58
60
  },
59
61
  "devDependencies": {