import type { FeatureFlagRules } from "./types.js"; export type EvaluatedFeatureFlags = | Record | { flags?: Record; values?: Record }; export function evaluatedFeatureFlagValues( result: EvaluatedFeatureFlags | undefined, ): Record { if (!result) return {}; const envelope = result as { flags?: unknown; values?: unknown; }; if ( envelope.flags && typeof envelope.flags === "object" && !Array.isArray(envelope.flags) ) { return envelope.flags as Record; } if ( envelope.values && typeof envelope.values === "object" && !Array.isArray(envelope.values) ) { return envelope.values as Record; } return result as Record; } export function featureFlagValue( values: Record, key: string, ): boolean { return values[key] === true; } export function normalizeFeatureFlagPercentage(value: unknown): number { const percentage = Number(value); return Number.isFinite(percentage) ? Math.floor(Math.max(0, Math.min(100, percentage))) : 0; } /** * Keep the shared editor safe while a remote app is upgrading or returning an * optimistic/transient rule envelope. The fleet contract still validates the * authoritative response; this only prevents absent collection fields from * crashing the operator UI between refreshes. */ export function normalizeFeatureFlagRules( rules: Partial | null | undefined, ): FeatureFlagRules { const mode = rules?.mode === "off" || rules?.mode === "on" || rules?.mode === "rules" ? rules.mode : "rules"; const percentage = normalizeFeatureFlagPercentage(rules?.percentage); return { ...rules, version: 1, mode, emails: Array.isArray(rules?.emails) ? rules.emails.filter( (value): value is string => typeof value === "string", ) : [], orgIds: Array.isArray(rules?.orgIds) ? rules.orgIds.filter( (value): value is string => typeof value === "string", ) : [], percentage, }; }