Files
kandev/apps/web/app/actions/features.ts
T
2026-08-02 15:36:21 +01:00

59 lines
2.0 KiB
TypeScript

"use server";
import { getBackendConfig } from "@/lib/config";
import { defaultFeatureFlags, 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 `defaultFeatureFlags`: every declared key is read
// from the response and only actual booleans are accepted; missing or
// non-boolean values remain all-off. Adding a flag is therefore one entry in
// the declaration — 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: FeatureFlags = { ...defaultFeatureFlags };
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;
}