fix(insights): keep the server-only rollup readers out of the insights client bundle

The v1.9.0 time-range work imported ANALYTICS_RANGES as a value into the
time-range pills and the insights layout-prefs hook from range-delta.ts.
That module also pulls the rollup readers and the Postgres client, so the
value import dragged pg (dns/fs/net/tls) into the browser bundle and broke
the production build with eight "Module not found" errors.

Move the client-safe pieces — the range list, its type, the per-range
window length, and the period-over-period result shapes — into a new
range-shared module that imports nothing server-side. The insights client
components import from there; range-delta.ts re-exports the same names so
the server route and the OpenAPI registry keep their import paths. The
window-composition logic and the rollup/db imports stay server-side.

No runtime behaviour change — a pure module-boundary split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 75124e76c1e987d8a022fb39d891049340199b38)
This commit is contained in:
Marc-André Bombeck committed 2026-06-02 17:41:33 +02:00
1 parent 1c91abcf2a
commit a87d6b94e4
6 files changed
+80 -48

No files matched your search

@@ -10,7 +10,7 @@ import {
type TrendDirectionSentiment,
} from "@/lib/insights/trend-sentiment";
import type { AnalyticsRangeData } from "@/hooks/use-analytics-range";
import type { AnalyticsRange } from "@/lib/analytics/range-delta";
import type { AnalyticsRange } from "@/lib/analytics/range-shared";
/**
* v1.9.0 — period-over-period delta caption beside the time-range pills.
+1 -1
View File
@@ -7,7 +7,7 @@ import { cn } from "@/lib/utils";
import {
ANALYTICS_RANGES,
type AnalyticsRange,
} from "@/lib/analytics/range-delta";
} from "@/lib/analytics/range-shared";
/**
* v1.9.0 — `7d / 30d / 90d / 1y` segmented control for the Insights metric
+1 -1
View File
@@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { queryKeys } from "@/lib/query-keys";
import type { AnalyticsRange } from "@/lib/analytics/range-delta";
import type { AnalyticsRange } from "@/lib/analytics/range-shared";
/**
* v1.9.0 — client reader for the single-metric period-over-period range
+1 -1
View File
@@ -12,7 +12,7 @@ import {
import {
ANALYTICS_RANGES,
type AnalyticsRange,
} from "@/lib/analytics/range-delta";
} from "@/lib/analytics/range-shared";
const RANGE_STORAGE_KEY = "healthlog.insights.range";
const DEFAULT_RANGE: AnalyticsRange = "30d";
+18 -44
View File
@@ -33,53 +33,27 @@ import {
readBestGranularityRollups,
type RollupBucketRow,
} from "@/lib/rollups/measurement-read-wmy";
import {
ANALYTICS_RANGES,
rangeWindowDays,
type AnalyticsRange,
type RangeDeltaResult,
type WindowAggregate,
} from "@/lib/analytics/range-shared";
const DAY_MS = 24 * 60 * 60 * 1000;
/** The user-selectable ranges. */
export const ANALYTICS_RANGES = ["7d", "30d", "90d", "1y"] as const;
export type AnalyticsRange = (typeof ANALYTICS_RANGES)[number];
/** Trailing-window length, in days, for each range. */
const RANGE_WINDOW_DAYS: Record<AnalyticsRange, number> = {
"7d": 7,
"30d": 30,
"90d": 90,
"1y": 365,
// The range constants + result shapes live in the client-safe
// `range-shared` module so the insights client bundle never pulls the
// server-only rollup readers (and `pg`) through a range import. Re-exported
// here so existing server-side callers keep importing from `range-delta`.
export {
ANALYTICS_RANGES,
rangeWindowDays,
type AnalyticsRange,
type RangeDeltaResult,
type WindowAggregate,
};
export function rangeWindowDays(range: AnalyticsRange): number {
return RANGE_WINDOW_DAYS[range];
}
/** A composed window aggregate. `mean`/etc. are null when the window is empty. */
export interface WindowAggregate {
count: number;
min: number | null;
max: number | null;
mean: number | null;
sum: number | null;
}
export interface RangeDeltaResult {
range: AnalyticsRange;
windowDays: number;
/** The granularity the underlying rollup read resolved against. */
granularity: string;
current: WindowAggregate;
previous: WindowAggregate;
/**
* `current.mean - previous.mean`, or null when either window has no mean
* (no data in the current or prior window) — never a misleading 0.
*/
delta: number | null;
/**
* `delta / previous.mean` as a fraction (0.03 = +3 %), or null when the
* prior window has no mean / a zero mean (no divide-by-zero, no misleading
* 0 %). The client renders "no prior-period data" in that case.
*/
deltaPct: number | null;
}
const DAY_MS = 24 * 60 * 60 * 1000;
/**
* Slice the resolved buckets into the current half (`[now-N, now)`) and the
+58
View File
@@ -0,0 +1,58 @@
/**
* v1.9.0 — client-safe range constants and types for the Insights metric
* pages.
*
* The user-selectable range list, its type, the per-range window length, and
* the period-over-period result shapes are shared between the client (the
* pills, the delta caption, the range hook, the layout-prefs hook) and the
* server (`/api/analytics/range` + `range-delta.ts`). This module imports
* NOTHING server-side — no rollup readers, no `db`, no Prisma client — so the
* client bundle never transitively pulls `pg` (dns/fs/net/tls) through a range
* import. The server-only window-composition logic lives in `range-delta.ts`,
* which re-exports these names for its own callers.
*/
/** The user-selectable ranges. */
export const ANALYTICS_RANGES = ["7d", "30d", "90d", "1y"] as const;
export type AnalyticsRange = (typeof ANALYTICS_RANGES)[number];
/** Trailing-window length, in days, for each range. */
const RANGE_WINDOW_DAYS: Record<AnalyticsRange, number> = {
"7d": 7,
"30d": 30,
"90d": 90,
"1y": 365,
};
export function rangeWindowDays(range: AnalyticsRange): number {
return RANGE_WINDOW_DAYS[range];
}
/** A composed window aggregate. `mean`/etc. are null when the window is empty. */
export interface WindowAggregate {
count: number;
min: number | null;
max: number | null;
mean: number | null;
sum: number | null;
}
export interface RangeDeltaResult {
range: AnalyticsRange;
windowDays: number;
/** The granularity the underlying rollup read resolved against. */
granularity: string;
current: WindowAggregate;
previous: WindowAggregate;
/**
* `current.mean - previous.mean`, or null when either window has no mean
* (no data in the current or prior window) — never a misleading 0.
*/
delta: number | null;
/**
* `delta / previous.mean` as a fraction (0.03 = +3 %), or null when the
* prior window has no mean / a zero mean (no divide-by-zero, no misleading
* 0 %). The client renders "no prior-period data" in that case.
*/
deltaPct: number | null;
}