// Parse a JSON-valued settings row. // // `settings.value` is TEXT (db/schema.sql), so every JSON-shaped key — // hero_layout, and now theme_visual / brand_assets / nav_* — is stored // stringified and arrives as a string. Consumers must parse it, and the parse // has to be fail-safe: a malformed or wrong-shaped value is treated as // **absent** (the surface falls back to its BRAND_* env / theme.css / NAV // default), never as an error and never as a half-applied object. That is the // same posture parseLayout already takes on the client // (client/src/lib/heroLayout.js). // // See docs/website/THEMING_AND_NAV.md §4.4. /** * @param {string|null|undefined} str the raw stored value * @param {(value: unknown) => boolean} [validator] shape check; anything it * rejects is treated as absent * @returns {object|null} the parsed object, or null when absent/malformed */ function parseJsonSetting(str, validator) { if (typeof str !== 'string' || str === '') return null let parsed try { parsed = JSON.parse(str) } catch { return null } // Only plain objects. A stored `null`, `4`, `"x"` or array is as unusable to // every consumer of these keys as a syntax error is. if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null if (validator && !validator(parsed)) return null return parsed } module.exports = { parseJsonSetting }