feat(settings): add cached SettingsService (setting() equivalent)
This commit is contained in:
1 parent
edbd5c89ba
commit
c2366fe6c9
3 files changed
+83
No files matched your search
@@ -0,0 +1,3 @@
|
||||
export { CurrencyType, currencyLabel } from "./currency-type.js";
|
||||
export { SettingsService } from "./settings-service.js";
|
||||
export type { SettingRow, SettingsFetcher } from "./settings-service.js";
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { SettingsService } from "./settings-service.js";
|
||||
|
||||
const rows = [
|
||||
{ key: "hotel_name", value: "AtomHotel" },
|
||||
{ key: "maintenance_enabled", value: "1" },
|
||||
{ key: "radio_enabled", value: "0" },
|
||||
];
|
||||
|
||||
describe("SettingsService", () => {
|
||||
it("returns a value by key", async () => {
|
||||
const svc = new SettingsService(async () => rows);
|
||||
expect(await svc.get("hotel_name")).toBe("AtomHotel");
|
||||
});
|
||||
|
||||
it("returns the default when key is missing", async () => {
|
||||
const svc = new SettingsService(async () => rows);
|
||||
expect(await svc.get("missing", "fallback")).toBe("fallback");
|
||||
expect(await svc.get("missing")).toBeNull();
|
||||
});
|
||||
|
||||
it("coerces '1'/'0' string booleans", async () => {
|
||||
const svc = new SettingsService(async () => rows);
|
||||
expect(await svc.getBool("maintenance_enabled")).toBe(true);
|
||||
expect(await svc.getBool("radio_enabled")).toBe(false);
|
||||
expect(await svc.getBool("missing", true)).toBe(true);
|
||||
});
|
||||
|
||||
it("fetches only once and caches until cleared", async () => {
|
||||
const fetcher = vi.fn(async () => rows);
|
||||
const svc = new SettingsService(fetcher);
|
||||
await svc.get("hotel_name");
|
||||
await svc.get("radio_enabled");
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
svc.clear();
|
||||
await svc.get("hotel_name");
|
||||
expect(fetcher).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
export interface SettingRow {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type SettingsFetcher = () => Promise<SettingRow[]>;
|
||||
|
||||
/**
|
||||
* Mirrors Laravel's App\Services\SettingsService: loads the whole
|
||||
* website_settings table into a key->value map, caches it in-process,
|
||||
* and reads with a default fallback. Booleans are stored as '1'/'0'.
|
||||
*/
|
||||
export class SettingsService {
|
||||
private cache: Map<string, string> | null = null;
|
||||
|
||||
constructor(private readonly fetcher: SettingsFetcher) {}
|
||||
|
||||
private async load(): Promise<Map<string, string>> {
|
||||
if (this.cache === null) {
|
||||
const rows = await this.fetcher();
|
||||
this.cache = new Map(rows.map((r) => [r.key, r.value]));
|
||||
}
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
async get(key: string, def: string | null = null): Promise<string | null> {
|
||||
const map = await this.load();
|
||||
return map.has(key) ? (map.get(key) as string) : def;
|
||||
}
|
||||
|
||||
async getBool(key: string, def = false): Promise<boolean> {
|
||||
const map = await this.load();
|
||||
if (!map.has(key)) return def;
|
||||
const v = (map.get(key) as string).toLowerCase();
|
||||
return v === "1" || v === "true";
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.cache = null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user