improve typing

This commit is contained in:
Josh Hawkins
2026-02-27 09:40:20 -06:00
parent 4dc039072a
commit 8b7156438e
11 changed files with 169 additions and 114 deletions
+13 -11
View File
@@ -3,23 +3,25 @@ import { useMemo } from "react";
import isEqual from "lodash/isEqual";
import get from "lodash/get";
import set from "lodash/set";
import type { FrigateConfig } from "@/types/frigateConfig";
import { FrigateConfig } from "@/types/frigateConfig";
import { JsonObject, JsonValue } from "@/types/configForm";
import { isJsonObject } from "@/lib/utils";
const INTERNAL_FIELD_SUFFIXES = ["enabled_in_config", "raw_mask"];
function stripInternalFields(value: unknown): unknown {
function stripInternalFields(value: JsonValue): JsonValue {
if (Array.isArray(value)) {
return value.map(stripInternalFields);
}
if (value && typeof value === "object") {
const obj = value as Record<string, unknown>;
const cleaned: Record<string, unknown> = {};
if (isJsonObject(value)) {
const obj = value;
const cleaned: JsonObject = {};
for (const [key, val] of Object.entries(obj)) {
if (INTERNAL_FIELD_SUFFIXES.some((suffix) => key.endsWith(suffix))) {
continue;
}
cleaned[key] = stripInternalFields(val);
cleaned[key] = stripInternalFields(val as JsonValue);
}
return cleaned;
}
@@ -27,8 +29,8 @@ function stripInternalFields(value: unknown): unknown {
return value;
}
export function normalizeConfigValue(value: unknown): unknown {
return stripInternalFields(value);
export function normalizeConfigValue(value: unknown): JsonValue {
return stripInternalFields(value as JsonValue);
}
export interface OverrideStatus {
@@ -51,15 +53,15 @@ export interface UseConfigOverrideOptions {
compareFields?: string[];
}
function pickFields(value: unknown, fields: string[]): Record<string, unknown> {
function pickFields(value: unknown, fields: string[]): JsonObject {
if (!fields || fields.length === 0) {
return {};
}
const result: Record<string, unknown> = {};
const result: JsonObject = {};
fields.forEach((path) => {
if (!path) return;
const fieldValue = get(value as Record<string, unknown>, path);
const fieldValue = get(value as JsonObject, path);
if (fieldValue !== undefined) {
set(result, path, fieldValue);
}
+28 -24
View File
@@ -3,12 +3,23 @@
import { useMemo } from "react";
import useSWR from "swr";
import type { RJSFSchema } from "@rjsf/utils";
import { RJSFSchema } from "@rjsf/utils";
import { resolveAndCleanSchema } from "@/lib/config-schema";
// Cache for resolved section schemas - keyed by schema reference + section key
const sectionSchemaCache = new WeakMap<RJSFSchema, Map<string, RJSFSchema>>();
type SchemaWithDefinitions = RJSFSchema & {
$defs?: Record<string, RJSFSchema>;
definitions?: Record<string, RJSFSchema>;
properties?: Record<string, RJSFSchema>;
};
const getSchemaDefinitions = (schema: RJSFSchema): Record<string, RJSFSchema> =>
(schema as SchemaWithDefinitions).$defs ||
(schema as SchemaWithDefinitions).definitions ||
{};
/**
* Extracts and resolves a section schema from the full config schema
* Uses caching to avoid repeated expensive resolution
@@ -32,50 +43,43 @@ function extractSectionSchema(
return schemaCache.get(cacheKey)!;
}
const schemaObj = schema as Record<string, unknown>;
const defs = (schemaObj.$defs || schemaObj.definitions || {}) as Record<
string,
unknown
>;
const defs = getSchemaDefinitions(schema);
const schemaObj = schema as SchemaWithDefinitions;
let sectionDef: Record<string, unknown> | null = null;
let sectionDef: RJSFSchema | null = null;
// For camera level, get section from CameraConfig in $defs
if (level === "camera") {
const cameraConfigDef = defs.CameraConfig as
| Record<string, unknown>
| undefined;
const cameraConfigDef = defs.CameraConfig;
if (cameraConfigDef?.properties) {
const props = cameraConfigDef.properties as Record<string, unknown>;
const props = cameraConfigDef.properties;
const sectionProp = props[sectionPath];
if (sectionProp && typeof sectionProp === "object") {
const refProp = sectionProp as Record<string, unknown>;
if (refProp.$ref && typeof refProp.$ref === "string") {
const refPath = (refProp.$ref as string)
if ("$ref" in sectionProp && typeof sectionProp.$ref === "string") {
const refPath = sectionProp.$ref
.replace(/^#\/\$defs\//, "")
.replace(/^#\/definitions\//, "");
sectionDef = defs[refPath] as Record<string, unknown>;
sectionDef = defs[refPath] || null;
} else {
sectionDef = sectionProp as Record<string, unknown>;
sectionDef = sectionProp;
}
}
}
} else {
// For global level, get from root properties
if (schemaObj.properties) {
const props = schemaObj.properties as Record<string, unknown>;
const props = schemaObj.properties;
const sectionProp = props[sectionPath];
if (sectionProp && typeof sectionProp === "object") {
const refProp = sectionProp as Record<string, unknown>;
if (refProp.$ref && typeof refProp.$ref === "string") {
const refPath = (refProp.$ref as string)
if ("$ref" in sectionProp && typeof sectionProp.$ref === "string") {
const refPath = sectionProp.$ref
.replace(/^#\/\$defs\//, "")
.replace(/^#\/definitions\//, "");
sectionDef = defs[refPath] as Record<string, unknown>;
sectionDef = defs[refPath] || null;
} else {
sectionDef = sectionProp as Record<string, unknown>;
sectionDef = sectionProp;
}
}
}
@@ -84,10 +88,10 @@ function extractSectionSchema(
if (!sectionDef) return null;
// Include $defs for nested references and resolve them
const schemaWithDefs = {
const schemaWithDefs: RJSFSchema = {
...sectionDef,
$defs: defs,
} as RJSFSchema;
};
// Resolve all references and strip $defs from result
const resolved = resolveAndCleanSchema(schemaWithDefs);