From 54eee8cd342b0dae20246eb515f165843021ef14 Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 23 Sep 2025 13:43:37 +0200 Subject: [PATCH] feat: add schema marking and validation skip mechanism --- app/src/core/utils/schema/index.ts | 36 +++++++++++++++++++++++++-- app/src/modules/ModuleManager.ts | 19 +++++++++++--- app/src/modules/db/DbModuleManager.ts | 4 +-- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/src/core/utils/schema/index.ts b/app/src/core/utils/schema/index.ts index 1b5d20f..3d3692c 100644 --- a/app/src/core/utils/schema/index.ts +++ b/app/src/core/utils/schema/index.ts @@ -12,6 +12,7 @@ export { getMcpServer, stdioTransport, McpClient, + logLevels as mcpLogLevels, type McpClientConfig, type ToolAnnotation, type ToolHandlerCtx, @@ -21,8 +22,35 @@ export { secret, SecretSchema } from "./secret"; export { s }; -export const stripMark = (o: O): O => o; -export const mark = (o: O): O => o; +const symbol = Symbol("bknd-validation-mark"); + +export function stripMark(obj: O) { + const newObj = structuredClone(obj); + mark(newObj, false); + return newObj as O; +} + +export function mark(obj: any, validated = true) { + try { + if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) { + if (validated) { + obj[symbol] = true; + } else { + delete obj[symbol]; + } + for (const key in obj) { + if (typeof obj[key] === "object" && obj[key] !== null) { + mark(obj[key], validated); + } + } + } + } catch (e) {} +} + +export function isMarked(obj: any) { + if (typeof obj !== "object" || obj === null) return false; + return obj[symbol] === true; +} export const stringIdentifier = s.string({ pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$", @@ -74,6 +102,10 @@ export function parse : s.Static { + if (!opts?.forceParse && !opts?.coerce && isMarked(v)) { + return v as any; + } + const schema = (opts?.clone ? cloneSchema(_schema as any) : _schema) as s.Schema; let value = opts?.coerce !== false diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index ffb581d..82c447d 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -1,4 +1,12 @@ -import { objectEach, transformObject, McpServer, type s, SecretSchema, setPath } from "bknd/utils"; +import { + objectEach, + transformObject, + McpServer, + type s, + SecretSchema, + setPath, + mark, +} from "bknd/utils"; import { DebugLogger } from "core/utils/DebugLogger"; import { Guard } from "auth/authorize/Guard"; import { env } from "core/env"; @@ -65,7 +73,7 @@ export type ModuleManagerOptions = { // callback after server was created onServerInit?: (server: Hono) => void; // doesn't perform validity checks for given/fetched config - trustFetched?: boolean; + skipValidation?: boolean; // runs when initial config provided on a fresh database seed?: (ctx: ModuleBuildContext) => Promise; // called right after modules are built, before finish @@ -124,7 +132,12 @@ export class ModuleManager { this.emgr = new EventManager({ ...ModuleManagerEvents }); this.logger = new DebugLogger(debug_modules); - this.createModules(options?.initial ?? {}); + const config = options?.initial ?? {}; + if (options?.skipValidation) { + mark(config, true); + } + + this.createModules(config); } protected onModuleConfigUpdated(key: string, config: any) {} diff --git a/app/src/modules/db/DbModuleManager.ts b/app/src/modules/db/DbModuleManager.ts index 45f597c..f35d34b 100644 --- a/app/src/modules/db/DbModuleManager.ts +++ b/app/src/modules/db/DbModuleManager.ts @@ -380,8 +380,8 @@ export class DbModuleManager extends ModuleManager { } } - if (this.options?.trustFetched === true) { - this.logger.log("trusting fetched config (mark)"); + if (this.options?.skipValidation === true) { + this.logger.log("skipping validation (mark)"); mark(result.configs.json); }