type ConfigScope = "global" | "user" | "both"; type ConfigValueType = "string" | "number" | "boolean" | "json"; export type SupportedValue = string | number | boolean | Record | unknown[]; export type ConfigDefinition = { key: string; scope: ConfigScope; valueType: ConfigValueType; defaultValue: T; userOverridable: boolean; validate?: (value: T) => boolean; }; function defineConfig(config: ConfigDefinition): ConfigDefinition { return config; } function isValidEmail(value: string): boolean { const trimmed = value.trim(); if (!trimmed.length) { return true; } return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed); } const CONFIG_REGISTRY = { siteName: defineConfig({ key: "siteName", scope: "global", valueType: "string", defaultValue: "My Small Site", userOverridable: false, validate: (value: string) => value.length <= 64, }), allowRegister: defineConfig({ key: "allowRegister", scope: "global", valueType: "boolean", defaultValue: true, userOverridable: false, }), theme: defineConfig({ key: "theme", scope: "both", valueType: "string", defaultValue: "light", userOverridable: true, validate: (value: string) => ["light", "dark", "system"].includes(value), }), smtpHost: defineConfig({ key: "smtpHost", scope: "global", valueType: "string", defaultValue: "", userOverridable: false, validate: (value: string) => value.length <= 256, }), smtpPort: defineConfig({ key: "smtpPort", scope: "global", valueType: "number", defaultValue: 587, userOverridable: false, validate: (value: number) => Number.isInteger(value) && value >= 1 && value <= 65535, }), smtpUser: defineConfig({ key: "smtpUser", scope: "global", valueType: "string", defaultValue: "", userOverridable: false, validate: (value: string) => value.length <= 256, }), smtpPass: defineConfig({ key: "smtpPass", scope: "global", valueType: "string", defaultValue: "", userOverridable: false, validate: (value: string) => value.length <= 256, }), smtpFrom: defineConfig({ key: "smtpFrom", scope: "global", valueType: "string", defaultValue: "", userOverridable: false, validate: (value: string) => value.length <= 256, }), smtpSecure: defineConfig({ key: "smtpSecure", scope: "global", valueType: "boolean", defaultValue: false, userOverridable: false, }), siteIcon: defineConfig({ key: "siteIcon", scope: "global", valueType: "string", defaultValue: "", userOverridable: false, validate: (value: string) => value.length <= 512, }), } as const; export type KnownConfigKey = keyof typeof CONFIG_REGISTRY; type KnownConfigDefinition = (typeof CONFIG_REGISTRY)[K]; export type KnownConfigValue = KnownConfigDefinition["defaultValue"]; export type KnownConfigItem = (typeof CONFIG_REGISTRY)[KnownConfigKey]; export const KNOWN_CONFIG_KEYS = Object.keys(CONFIG_REGISTRY) as KnownConfigKey[]; export function isKnownConfigKey(key: string): key is KnownConfigKey { return key in CONFIG_REGISTRY; } export function getConfigDefinition(key: K): KnownConfigDefinition { return CONFIG_REGISTRY[key]; } export function getConfigDefaultValue(key: K): KnownConfigValue { return CONFIG_REGISTRY[key].defaultValue; } export function getConfigScope(key: K) { return CONFIG_REGISTRY[key].scope; } export function canUserOverride(key: K) { return CONFIG_REGISTRY[key].userOverridable; } export function getConfigValueType(key: K) { return CONFIG_REGISTRY[key].valueType; } function isSupportedValueType(valueType: ConfigValueType, value: unknown) { if (valueType === "string") return typeof value === "string"; if (valueType === "number") return typeof value === "number" && Number.isFinite(value); if (valueType === "boolean") return typeof value === "boolean"; if (valueType === "json") return value !== undefined; return false; } export function validateConfigValue(key: K, value: unknown): value is KnownConfigValue { const definition = getConfigDefinition(key); if (!isSupportedValueType(definition.valueType, value)) { return false; } const validator = (definition as ConfigDefinition>).validate; if (!validator) { return true; } return validator(value as KnownConfigValue); } export function serializeConfigValue(key: K, value: KnownConfigValue): string { const valueType = getConfigValueType(key); if (valueType === "json") { return JSON.stringify(value); } return String(value); } export function deserializeConfigValue(key: K, rawValue: string): KnownConfigValue { const valueType = getConfigValueType(key); switch (valueType) { case "string": return rawValue as KnownConfigValue; case "number": return Number(rawValue) as unknown as KnownConfigValue; case "boolean": return (rawValue === "true") as unknown as KnownConfigValue; case "json": default: return JSON.parse(rawValue) as KnownConfigValue; } }