Files
kandev/apps/web/app/actions/features.ts
T
2026-06-15 13:33:45 +01:00

60 lines
2.1 KiB
TypeScript

"use server";
import { getBackendConfig } from "@/lib/config";
import { defaultFeaturesState } from "@/lib/state/slices/features/features-slice";
import type { FeatureFlags } from "@/lib/state/slices/features/types";
import type { RuntimeFlagsResponse } from "@/lib/types/runtime-flags";
const { apiBaseUrl } = getBackendConfig();
// Server-side feature-flag fetch. Called once per request from the root
// layout so every page renders with the deployment's flags already in the
// store. Falls back to defaults (all-off) when the backend is unreachable,
// so a dev-server restart doesn't crash page rendering.
//
// Parsing is type-driven via `defaultFeaturesState.features`: every key
// declared on FeatureFlags is read from the response and Boolean-coerced;
// missing or non-bool values fall through to the default. Adding a flag
// is therefore one entry in FeatureFlags + the default — no edit here.
//
// See docs/decisions/0007-runtime-feature-flags.md.
export async function getFeatureFlagsAction(): Promise<FeatureFlags> {
const url = `${apiBaseUrl}/api/v1/features`;
const defaults = defaultFeaturesState.features;
try {
const response = await fetch(url, { cache: "no-store" });
if (!response.ok) {
return defaults;
}
const data = (await response.json()) as Record<string, unknown>;
return normalizeFlags(data, defaults);
} catch {
return defaults;
}
}
export async function getRuntimeDebugModeAction(): Promise<boolean> {
const url = `${apiBaseUrl}/api/v1/runtime-flags`;
try {
const response = await fetch(url, { cache: "no-store" });
if (!response.ok) {
return false;
}
const data = (await response.json()) as RuntimeFlagsResponse;
return data.flags.some((flag) => flag.key === "debug.devMode" && flag.effective_value);
} catch {
return false;
}
}
function normalizeFlags(data: Record<string, unknown>, defaults: FeatureFlags): FeatureFlags {
const out = { ...defaults };
for (const key of Object.keys(out) as (keyof FeatureFlags)[]) {
const value = data[key];
if (typeof value === "boolean") {
out[key] = value;
}
}
return out;
}