Files
HealthLog/src/lib/analytics/range-shared.ts
T
Marc-André Bombeck a87d6b94e4 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)
2026-06-02 17:41:33 +02:00

59 lines
2.0 KiB
TypeScript

/**
* 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;
}