strengthened schema ensuring for system entities

This commit is contained in:
dswbx
2025-01-18 12:39:34 +01:00
parent 145b47e942
commit db10188945
12 changed files with 118 additions and 36 deletions

View File

@@ -14,6 +14,7 @@ import {
} from "data";
import { Entity } from "data";
import type { Hono } from "hono";
import { isEqual } from "lodash-es";
export type ServerEnv = {
Variables: {
@@ -154,28 +155,33 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
}
protected ensureEntity(entity: Entity) {
const instance = this.ctx.em.entity(entity.name, true);
// check fields
if (!this.ctx.em.hasEntity(entity.name)) {
if (!instance) {
this.ctx.em.addEntity(entity);
this.ctx.flags.sync_required = true;
return;
}
const instance = this.ctx.em.entity(entity.name);
// if exists, check all fields required are there
// @todo: check if the field also equal
for (const field of instance.fields) {
const _field = entity.field(field.name);
if (!_field) {
entity.addField(field);
for (const field of entity.fields) {
const instanceField = instance.field(field.name);
if (!instanceField) {
instance.addField(field);
this.ctx.flags.sync_required = true;
} else {
const changes = this.setEntityFieldConfigs(field, instanceField);
if (changes > 0) {
this.ctx.flags.sync_required = true;
}
}
}
// replace entity (mainly to keep the ensured type)
this.ctx.em.__replaceEntity(
new Entity(entity.name, entity.fields, instance.config, entity.type)
new Entity(instance.name, instance.fields, instance.config, entity.type)
);
}
@@ -193,6 +199,21 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
return schema;
}
protected setEntityFieldConfigs(
parent: Field,
child: Field,
props: string[] = ["hidden", "fillable", "required"]
) {
let changes = 0;
for (const prop of props) {
if (!isEqual(child.config[prop], parent.config[prop])) {
child.config[prop] = parent.config[prop];
changes++;
}
}
return changes;
}
protected replaceEntityField(
_entity: string | Entity,
field: Field | string,
@@ -202,6 +223,10 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
const name = typeof field === "string" ? field : field.name;
const newField =
_newField instanceof FieldPrototype ? make(name, _newField as any) : _newField;
// ensure keeping vital config
this.setEntityFieldConfigs(entity.field(name)!, newField);
entity.__replaceField(name, newField);
}
}

View File

@@ -88,6 +88,7 @@ export type ModuleManagerOptions = {
};
type ConfigTable<Json = ModuleConfigs> = {
id?: number;
version: number;
type: "config" | "diff" | "backup";
json: Json;
@@ -236,10 +237,10 @@ export class ModuleManager {
private async fetch(): Promise<ConfigTable> {
this.logger.context("fetch").log("fetching");
const startTime = performance.now();
// disabling console log, because the table might not exist yet
return await withDisabledConsole(async () => {
const startTime = performance.now();
const result = await withDisabledConsole(async () => {
const { data: result } = await this.repo().findOne(
{ type: "config" },
{
@@ -251,9 +252,16 @@ export class ModuleManager {
throw BkndError.with("no config");
}
this.logger.log("took", performance.now() - startTime, "ms", result.version).clear();
return result as ConfigTable;
return result as unknown as ConfigTable;
}, ["log", "error", "warn"]);
this.logger
.log("took", performance.now() - startTime, "ms", {
version: result.version,
id: result.id
})
.clear();
return result;
}
async save() {
@@ -390,6 +398,7 @@ export class ModuleManager {
}
private setConfigs(configs: ModuleConfigs): void {
this.logger.log("setting configs");
objectEach(configs, (config, key) => {
try {
// setting "noEmit" to true, to not force listeners to update

View File

@@ -44,6 +44,11 @@ export class SystemController extends Controller {
hono.use(permission(SystemPermissions.configRead));
hono.get("/raw", permission([SystemPermissions.configReadSecrets]), async (c) => {
// @ts-expect-error "fetch" is private
return c.json(await this.app.modules.fetch());
});
hono.get(
"/:module?",
tb("param", Type.Object({ module: Type.Optional(StringEnum(MODULE_NAMES)) })),