- `<profileDir>/settings.json` atomic write (temp + rename)
- 손상 JSON / 파일 없음 → 빈 객체 fallback (no throw)
- in-memory cache (load 1회 file read)
- zod .strict() schema for ollama { endpoint: URL, model: string }
- 단위 +6 cases
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.4 KiB
TypeScript
48 lines
1.4 KiB
TypeScript
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
import { join, dirname } from 'node:path';
|
|
import { z } from 'zod';
|
|
|
|
const OllamaSettingsSchema = z.object({
|
|
endpoint: z.string().url(),
|
|
model: z.string().min(1)
|
|
}).strict();
|
|
|
|
const SettingsSchema = z.object({
|
|
ollama: OllamaSettingsSchema.optional()
|
|
}).strict();
|
|
|
|
export type Settings = z.infer<typeof SettingsSchema>;
|
|
export type OllamaSettings = z.infer<typeof OllamaSettingsSchema>;
|
|
|
|
export class SettingsService {
|
|
private filePath: string;
|
|
private cache: Settings | null = null;
|
|
|
|
constructor(profileDir: string) {
|
|
this.filePath = join(profileDir, 'settings.json');
|
|
}
|
|
|
|
async load(): Promise<Settings> {
|
|
if (this.cache !== null) return this.cache;
|
|
try {
|
|
const raw = await readFile(this.filePath, 'utf8');
|
|
const parsed = JSON.parse(raw);
|
|
this.cache = SettingsSchema.parse(parsed);
|
|
} catch {
|
|
this.cache = {};
|
|
}
|
|
return this.cache;
|
|
}
|
|
|
|
async setOllama(value: OllamaSettings): Promise<void> {
|
|
const validated = OllamaSettingsSchema.parse(value);
|
|
const current = await this.load();
|
|
const next: Settings = { ...current, ollama: validated };
|
|
await mkdir(dirname(this.filePath), { recursive: true });
|
|
const tmpPath = this.filePath + '.tmp';
|
|
await writeFile(tmpPath, JSON.stringify(next, null, 2), 'utf8');
|
|
await rename(tmpPath, this.filePath);
|
|
this.cache = next;
|
|
}
|
|
}
|