mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
refactor(cycle,medications): break the gate and compliance import cycles
The module gate and the cycle gate imported each other for one pure predicate. isCycleEnabled moves to lib/cycle/enabled.ts and both gates import it from there. The band minter read DOSE_WINDOW_DEFAULTS through the analytics/compliance re-export barrel, which loads the compliance display and with it the scheduling modules that import the minter. It now imports the constant from the module that defines it. Runtime import cycles fall from 6 to 4.
This commit is contained in:
1 parent
78ac2c5aeb
commit
b1400a8130
11 files changed
+36
-44
No files matched your search
@@ -1,27 +1,12 @@
|
||||
{
|
||||
"$comment": "Runtime import cycles under src/: the back-edge count and the strongly connected components. Maintained by src/__tests__/import-cycle-guard.test.ts.",
|
||||
"cycles": 6,
|
||||
"cycles": 4,
|
||||
"components": [
|
||||
[
|
||||
"components/command-palette/command-palette.tsx",
|
||||
"components/command-palette/palette-store.ts",
|
||||
"components/layout/global-shortcuts.tsx"
|
||||
],
|
||||
[
|
||||
"lib/analytics/compliance.ts",
|
||||
"lib/analytics/compliance/display.ts",
|
||||
"lib/analytics/compliance/ledger.ts",
|
||||
"lib/analytics/compliance/occurrences.ts",
|
||||
"lib/analytics/compliance/windows.ts",
|
||||
"lib/medications/scheduling/band-minter.ts",
|
||||
"lib/medications/scheduling/cadence.ts",
|
||||
"lib/medications/scheduling/compliance.ts",
|
||||
"lib/medications/scheduling/schedule-eras.ts"
|
||||
],
|
||||
[
|
||||
"lib/cycle/gate.ts",
|
||||
"lib/modules/gate.ts"
|
||||
],
|
||||
[
|
||||
"lib/insights/features.ts",
|
||||
"lib/insights/signals-of-day.ts"
|
||||
|
||||
@@ -27,7 +27,7 @@ import { auditLog } from "@/lib/auth/audit";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit";
|
||||
import { getOrCreateCycleProfile } from "@/lib/cycle/profile";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/enabled";
|
||||
import { invalidateUserHealthContext } from "@/lib/cache/invalidate";
|
||||
import { toCycleProfileDTO } from "@/lib/cycle/dto";
|
||||
import { cyclePrefsSchema } from "@/lib/validations/cycle";
|
||||
|
||||
@@ -51,7 +51,7 @@ import { buildAvatarUrl } from "@/lib/avatar";
|
||||
import { decrypt } from "@/lib/crypto";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { resolveStoredTimezone } from "@/lib/tz/resolver";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/enabled";
|
||||
import {
|
||||
resolveModuleMap,
|
||||
getOperatorModuleAvailability,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { requireCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/enabled";
|
||||
import { toCycleProfileDTO } from "@/lib/cycle/dto";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
parseManagedRecordSettingsPatch,
|
||||
resolveGuardianRecordSettingsAccess,
|
||||
} from "@/lib/record-settings";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/enabled";
|
||||
import {
|
||||
resolveInsightsLayout,
|
||||
serializeInsightsLayout,
|
||||
|
||||
@@ -18,8 +18,8 @@ vi.mock("@/lib/cycle/profile", () => ({
|
||||
getOrCreateCycleProfileMock(...args),
|
||||
}));
|
||||
|
||||
import { isCycleEnabled } from "../enabled";
|
||||
import {
|
||||
isCycleEnabled,
|
||||
isCycleAvailableForUser,
|
||||
requireCycleEnabled,
|
||||
CYCLE_DISABLED_ERROR_CODE,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* The per-user (LAYER 2) cycle-tracking decision, pure.
|
||||
*
|
||||
* A leaf, imported by both the module gate (`@/lib/modules/gate`, which
|
||||
* AND-s it with the operator kill-switch) and the cycle route guard
|
||||
* (`./gate.ts`, which asks the module gate). Kept apart from both so the
|
||||
* two gates do not import each other. See `./gate.ts` for the layering.
|
||||
*/
|
||||
/**
|
||||
* Resolve whether cycle tracking is enabled for an account.
|
||||
*
|
||||
* @param gender the user's `gender` column (`"MALE" | "FEMALE" | null`).
|
||||
* @param profile the user's `CycleProfile`, or just its
|
||||
* `cycleTrackingEnabled` field (NULL ⇒ derive from gender).
|
||||
*/
|
||||
export function isCycleEnabled(
|
||||
gender: string | null | undefined,
|
||||
profile: { cycleTrackingEnabled: boolean | null } | null | undefined,
|
||||
): boolean {
|
||||
const toggle = profile?.cycleTrackingEnabled;
|
||||
if (toggle === true) return true;
|
||||
if (toggle === false) return false;
|
||||
// NULL toggle → derive from gender.
|
||||
return gender === "FEMALE";
|
||||
}
|
||||
+2
-20
@@ -5,13 +5,13 @@
|
||||
* (a) the per-user `CycleProfile.cycleTrackingEnabled` toggle, and
|
||||
* (b) a gender-derived default when that toggle is NULL.
|
||||
*
|
||||
* `isCycleEnabled` is the per-user (LAYER 2) source of truth. The server
|
||||
* `isCycleEnabled` (`./enabled.ts`) is the per-user (LAYER 2) source of truth. The server
|
||||
* gender enum ships UPPERCASE (`"MALE" | "FEMALE" | null`); the gate keys
|
||||
* on `"FEMALE"`. A NULL toggle derives `gender === "FEMALE"`; an explicit
|
||||
* `true`/`false` overrides the derivation (so a non-FEMALE account can
|
||||
* opt in and a FEMALE account can opt out).
|
||||
*
|
||||
* v1.18.0 — `isCycleEnabled` is the USER layer only and stays pure: the
|
||||
* `isCycleEnabled` is the USER layer only and stays pure: the
|
||||
* module foundation's `resolveModuleEnabled` AND-s it with the operator
|
||||
* server-wide kill-switch (LAYER 1). Every SERVER-SIDE consumer that wants
|
||||
* the fully-resolved decision (routes, the cycle-reminder cron, the coach
|
||||
@@ -51,24 +51,6 @@ export function isCycleAvailableForUser(userId: string): Promise<boolean> {
|
||||
return isModuleEnabled(userId, "cycle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve whether cycle tracking is enabled for an account.
|
||||
*
|
||||
* @param gender the user's `gender` column (`"MALE" | "FEMALE" | null`).
|
||||
* @param profile the user's `CycleProfile`, or just its
|
||||
* `cycleTrackingEnabled` field (NULL ⇒ derive from gender).
|
||||
*/
|
||||
export function isCycleEnabled(
|
||||
gender: string | null | undefined,
|
||||
profile: { cycleTrackingEnabled: boolean | null } | null | undefined,
|
||||
): boolean {
|
||||
const toggle = profile?.cycleTrackingEnabled;
|
||||
if (toggle === true) return true;
|
||||
if (toggle === false) return false;
|
||||
// NULL toggle → derive from gender.
|
||||
return gender === "FEMALE";
|
||||
}
|
||||
|
||||
/**
|
||||
* Route-guard outcome: either the resolved profile (enabled) or a ready
|
||||
* 403 envelope (disabled) to return verbatim.
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
PERIOD_CONFIRM_GRACE_DAYS,
|
||||
FERTILE_SOON_LEAD_DAYS,
|
||||
} from "../cycle-reminder";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/enabled";
|
||||
import type { NotificationPayload } from "@/lib/notifications/types";
|
||||
import type { DispatchOutcome } from "@/lib/notifications/dispatcher";
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
*/
|
||||
import { RRule } from "rrule";
|
||||
|
||||
import { DOSE_WINDOW_DEFAULTS } from "@/lib/analytics/compliance";
|
||||
import { DOSE_WINDOW_DEFAULTS } from "@/lib/medications/scheduling/dose-window-defaults";
|
||||
import {
|
||||
buildSlotBands,
|
||||
type SlotBand,
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
*/
|
||||
import { apiError } from "@/lib/api-response";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { isCycleEnabled } from "@/lib/cycle/enabled";
|
||||
import { getAssistantFlags } from "@/lib/feature-flags";
|
||||
import { memoizePerRequest } from "@/lib/request-cache";
|
||||
import {
|
||||
|
||||
Reference in new issue
Block a user