import { getSettings, writeSettings, type SiteSettings, } from "yt-dlp-transcript-common/lib/settings"; // THE EDITOR'S ONE SETTINGS WRITER (one-core phase 3 slice 4a). // // Every server action that changes settings.json calls `saveSettings(patch)` // with only what it changed; this reads the current settings, merges the // patch, and hands the result to `writeSettings` — which is imported by this // file and by no other file under editor/app. // // THE MERGE IS ONE LEVEL DEEP, and no deeper — the same rule e2e's // `writeSettings` helper (editor/e2e/helpers.ts) applies to the fixture: // // - a patch key whose value is a PLAIN OBJECT merges over the current block's // keys: `{ syncScheduler: { enabled: true } }` changes one flag and keeps the // rest of the scheduler; // - ARRAYS and SCALARS replace: `workers: []` means no workers; // - an object NESTED INSIDE a block replaces: `autoQueue: { digest: {…} }` // replaces the digest lane whole (a half-merged policy tree would be a // worse surprise than a replaced one), and `channelPriority: { channels }` // replaces the whole channel map. // // WHY A PATCH OF THE WHOLE SETTINGS OBJECT, NOT `saveSettingsBlock(block, // patch)` as plans/one-core.md sketched: the channel-priority action writes // `channelPriority` AND the four `autoQueue` roots compiled from it in ONE // write. A one-block signature would split that into two, and a reader between // them would see a priority model and trees that disagree. // // NOT a "use server" module: it is a helper the action files call, never an // action a client can invoke with an arbitrary patch. type Patch = Partial; function isPlainObject(v: unknown): v is Record { return typeof v === "object" && v !== null && !Array.isArray(v); } // Pure, so the merge rule is testable without a settings file. export function mergeSettingsPatch( current: SiteSettings, patch: Patch, ): SiteSettings { const merged: Record = { ...current }; for (const [key, value] of Object.entries(patch)) { if (value === undefined) continue; const base = (current as Record)[key]; merged[key] = isPlainObject(value) && isPlainObject(base) ? { ...base, ...value } : value; } return merged as SiteSettings; } export async function saveSettings(patch: Patch): Promise { await writeSettings(mergeSettingsPatch(getSettings(), patch)); }