feat(config): persist config.patch updates atomically

This commit is contained in:
William Valentin
2026-02-15 22:03:21 -08:00
parent c314e0f067
commit 0220ec10dd
13 changed files with 205 additions and 11 deletions
+27 -2
View File
@@ -4,6 +4,7 @@ import type { Config } from '../../config/index.js';
export interface ConfigHandlerDeps {
config: Config;
persistConfig?: (nextConfig: Config) => Promise<void> | void;
}
/**
@@ -140,6 +141,7 @@ export function createConfigHandlers(deps: ConfigHandlerDeps) {
const applied: string[] = [];
const rejected: string[] = [];
const draft = JSON.parse(JSON.stringify(deps.config)) as Config;
for (const [key, value] of Object.entries(patches as Record<string, unknown>)) {
const patcher = PATCHABLE_KEYS[key];
@@ -147,7 +149,7 @@ export function createConfigHandlers(deps: ConfigHandlerDeps) {
rejected.push(key);
continue;
}
const ok = patcher(deps.config, value);
const ok = patcher(draft, value);
if (ok) {
applied.push(key);
} else {
@@ -155,7 +157,30 @@ export function createConfigHandlers(deps: ConfigHandlerDeps) {
}
}
return makeResponse(request.id, { applied, rejected });
if (applied.length === 0) {
return makeResponse(request.id, { applied, rejected, persisted: false });
}
if (deps.persistConfig) {
try {
await deps.persistConfig(draft);
} catch (err) {
return makeResponse(request.id, {
applied: [],
rejected,
persisted: false,
persistError: err instanceof Error ? err.message : String(err),
});
}
}
// Update in-memory runtime config only after a successful persist (or when persistence is not configured).
for (const key of Object.keys(deps.config)) {
delete (deps.config as Record<string, unknown>)[key];
}
Object.assign(deps.config as Record<string, unknown>, draft as Record<string, unknown>);
return makeResponse(request.id, { applied, rejected, persisted: Boolean(deps.persistConfig) });
},
};
}