feat: redesign user host defaults system

This commit is contained in:
LukeGus committed 2026-09-30 06:19:56 -05:00
1 parent 3028ab1176
commit a4a4d4f27b
144 files changed
+24609 -2555

No files matched your search

+67 -5
View File
@@ -372,6 +372,21 @@ export function createPluginHosts({ manifest, bag, audit }: Deps): PluginHosts {
}
};
/** Host settings a plugin writes are the host's own, not its defaults. */
const markSettingsOwn = async (
hostId: number,
writes: Awaited<ReturnType<typeof checkSettings>>,
) => {
if (writes.length === 0) return;
const { changeHostOverrides } =
await import("../hosts/defaults/overrides.js");
await changeHostOverrides([hostId], {
own: writes.flatMap(({ manifest: owner, values }) =>
Object.keys(values).map((key): [string, string] => [owner.id, key]),
),
});
};
return {
list: async () => {
await requireRead();
@@ -467,12 +482,25 @@ export function createPluginHosts({ manifest, bag, audit }: Deps): PluginHosts {
const writes = await checkSettings(settings);
const { createCurrentHostRepository } =
await import("../database/repositories/factory.js");
const { applyHostDefaultsToWrite, applyDefaultsAfterHostWrite } =
await import("../hosts/defaults/index.js");
const columns: Record<string, unknown> = { ...row, userId };
columns.defaultOverrides = JSON.stringify(
await applyHostDefaultsToWrite({
ownerId: userId,
hostId: null,
columns,
body: host as unknown as Record<string, unknown>,
}),
);
const created =
await createCurrentHostRepository().createEncryptedForUser(userId, {
...row,
await createCurrentHostRepository().createEncryptedForUser(
userId,
});
columns,
);
await applyDefaultsAfterHostWrite(created.id);
await writeSettings(created.id, writes);
await markSettingsOwn(created.id, writes);
await audit("hosts_create", `host ${created.id}`, { success: true });
return (
await toRecords([created as unknown as Record<string, unknown>])
@@ -497,13 +525,47 @@ export function createPluginHosts({ manifest, bag, audit }: Deps): PluginHosts {
const writes = await checkSettings(settings);
const { createCurrentHostRepository } =
await import("../database/repositories/factory.js");
const { applyHostDefaultsToWrite, applyDefaultsAfterHostWrite } =
await import("../hosts/defaults/index.js");
const { createCurrentHostDefaultsRepository } =
await import("../database/repositories/factory.js");
const stored = (
await createCurrentHostDefaultsRepository().listHosts({
hostIds: [hostId],
})
)[0];
const columns: Record<string, unknown> = { ...row };
if (stored && stored.userId === userId) {
columns.defaultOverrides = JSON.stringify(
await applyHostDefaultsToWrite({
ownerId: userId,
hostId,
columns,
body: patch as unknown as Record<string, unknown>,
stored,
}),
);
}
const updated =
await createCurrentHostRepository().updateEncryptedForUser(
userId,
hostId,
row,
columns,
);
if (updated) await writeSettings(hostId, writes);
if (updated) {
await writeSettings(hostId, writes);
await markSettingsOwn(hostId, writes);
await applyDefaultsAfterHostWrite(hostId, {
moved:
!!stored &&
((columns.folder !== undefined &&
(columns.folder ?? null) !== (stored.folder ?? null)) ||
(columns.parentHostId !== undefined &&
(columns.parentHostId ?? null) !==
(stored.parentHostId ?? null))),
ownerId: userId,
});
}
await audit("hosts_update", `host ${hostId}`, {
success: updated !== null,
});
+35 -1
View File
@@ -816,7 +816,40 @@ export function createPluginContext(
getHost: (hostId, key) =>
pluginSettings.getSetting(manifest, "host", hostId, key) as never,
setHost: async (hostId, key, value) => {
getHostFor: async (hostId, userId, key) => {
const { personalHostValue } =
await import("../hosts/defaults/personal.js");
const personal = await personalHostValue(
manifest,
Number(hostId),
userId,
key,
);
return (
personal.applies
? personal.value
: await pluginSettings.getSetting(manifest, "host", hostId, key)
) as never;
},
getHostDefault: async (userId, key) => {
const { userHostDefault } =
await import("../hosts/defaults/personal.js");
return (await userHostDefault(manifest, userId, key)) as never;
},
setHost: async (hostId, key, value, options) => {
const { changeHostOverrides } =
await import("../hosts/defaults/overrides.js");
if (options?.inherit) {
if (!pluginSettings.findField(manifest, "host", key)) {
throw new Error(
`"${key}" is not a host setting this plugin declares`,
);
}
await changeHostOverrides([Number(hostId)], {
inherit: [[pluginId, key]],
});
return;
}
const error = await pluginSettings.setSetting(
manifest,
"host",
@@ -825,6 +858,7 @@ export function createPluginContext(
value,
);
if (error) throw new Error(error);
await changeHostOverrides([Number(hostId)], { own: [[pluginId, key]] });
},
listHostValues: async (key) => {
+12 -3
View File
@@ -75,7 +75,11 @@ export function onSettingsChange(
};
}
function notify(pluginId: string, key: string, value: unknown): void {
export function notifySettingChange(
pluginId: string,
key: string,
value: unknown,
): void {
for (const listener of listeners.get(pluginId)?.get(key) ?? []) {
try {
listener(value);
@@ -92,7 +96,10 @@ function notify(pluginId: string, key: string, value: unknown): void {
export type SettingsValidator = (
values: Record<string, unknown>,
context: { hostId?: number },
context: {
hostId?: number;
defaults?: { level: "admin" | "user" | "folder" };
},
) =>
| Record<string, string>
| void
@@ -131,6 +138,7 @@ export async function validateSettingsSave(
scope: PluginSettingsScope,
scopeId: string | null,
values: Record<string, unknown>,
defaults?: { level: "admin" | "user" | "folder" },
): Promise<Record<string, string>> {
const errors: Record<string, string> = {};
for (const entry of validators.get(pluginId) ?? []) {
@@ -138,6 +146,7 @@ export async function validateSettingsSave(
try {
const result = await entry.run(values, {
hostId: scope === "host" && scopeId ? Number(scopeId) : undefined,
...(defaults ? { defaults } : {}),
});
Object.assign(errors, result ?? {});
} catch (error) {
@@ -299,7 +308,7 @@ export async function setSetting(
shouldEncrypt,
);
notify(manifest.id, key, coerced);
notifySettingChange(manifest.id, key, coerced);
return null;
}