{{breadcrumbs}}
HUB / mfdb_validators.ts
mfdb_validators.ts
- Runtime
- TypeScript
- Category
- TypeScript
- Path
- /storage/emulated/0/Projects/Management/Libraries/ts/mfdb_validators.ts
FILE // mfdb_validators.ts
/*
Library: mfdb_validators.ts
MFDB Version: 1.3.1
Format_Creator: Elton Boehnen
Status: OFFICIAL - v1.3.1
Date: 2026-05-06
*/
/**
* Library: mfdb_validators.ts
* Jurisdiction: ["TYPESCRIPT", "CORE_COMMAND"]
* Status: OFFICIAL — Core-Command/Lib (v1.1)
* Author: Elton Boehnen
* Version: 1.1 (OFFICIAL)
* Date: 2026-04-23
* Description: (obj["description"] as string | null) ?? null,
record_count: (obj["record_count"] as number | null) ?? null,
schema_version: (obj["schema_version"] as string | null) ?? null,
primary_key: (obj["primary_key"] as string | null) ?? null,
} as MFDBManifestRecord;
});
}
/**
Extract database-level metadata from manifest custom headers.
*/
import {
BEJSONDocument,
BEJSONValue,
MFDBManifestRecord,
MFDBDatabaseMeta,
MFDBFileRole,
ValidationResult,
MFDBValidationError,
MFDB_VALIDATION_CODES as E,
MFDB_CORE_CODES,
} from "./bejson_types";
import { validateDocument as validateBEJSON } from "./bejson_validators";
import { _emitError, _emitWarning, _makeResult } from "./bejson_validators";
// ---------------------------------------------------------------------------
// Discovery algorithm
// ---------------------------------------------------------------------------
export function discoverRole(doc: unknown, filename: string): MFDBFileRole {
if (doc === null || doc === undefined || typeof doc !== "object" || Array.isArray(doc)) {
return "standalone";
}
const d = doc as BEJSONDocument;
if (d.Format_Version === "104a" && filename.endsWith(".mfdb.bejson")) {
return "manifest";
}
if (d.Format_Version === "104" && "Parent_Hierarchy" in d) {
return "entity";
}
return "standalone";
}
// ---------------------------------------------------------------------------
// Level 1 — Manifest validation
// ---------------------------------------------------------------------------
export function validateManifest(
doc: unknown,
options: {
resolvedPaths?: Set<string>;
} = {}
): ValidationResult {
const r = _makeResult();
// Must be valid BEJSON 104a
const bejsonResult = validateBEJSON(doc);
if (!bejsonResult.valid) {
for (const e of bejsonResult.errors) {
_emitError(r, e.code, "[BEJSON] " + e.message, e.field, e.recordIndex);
}
return r;
}
const manifest = doc as BEJSONDocument;
if (manifest.Format_Version !== "104a") {
_emitError(r, E.NOT_A_MANIFEST, "Manifest must be Format_Version \"104a\", got \"" + manifest.Format_Version + "\".");
return r;
}
// Records_Type must be exactly ["mfdb"]
if (
!Array.isArray(manifest.Records_Type) ||
manifest.Records_Type.length !== 1 ||
manifest.Records_Type[0] !== "mfdb"
) {
_emitError(r, E.MANIFEST_RECORDS_TYPE_INVALID, "Manifest Records_Type must be exactly [\"mfdb\"].", "Records_Type");
}
// Required custom headers
if (typeof manifest["MFDB_Version"] !== "string" || (manifest["MFDB_Version"] as string).trim() === "") {
_emitError(r, MFDB_CORE_CODES.INVALID_MFDB_VERSION as unknown as number,
"Manifest is missing required header MFDB_Version.", "MFDB_Version");
}
if (typeof manifest["DB_Name"] !== "string" || (manifest["DB_Name"] as string).trim() === "") {
_emitError(r, MFDB_CORE_CODES.MISSING_DB_NAME as unknown as number,
"Manifest is missing required header DB_Name.", "DB_Name");
}
// entity_name and file_path field presence
const fieldNames = manifest.Fields.map((f) => f.name);
if (!fieldNames.includes("entity_name")) {
_emitError(r, E.MISSING_REQUIRED_MANIFEST_FIELD, "Manifest Fields must include \"entity_name\".", "entity_name");
}
if (!fieldNames.includes("file_path")) {
_emitError(r, E.MISSING_REQUIRED_MANIFEST_FIELD, "Manifest Fields must include \"file_path\".", "file_path");
}
if (!r.valid) return r; // can't proceed without required fields
const enIdx = fieldNames.indexOf("entity_name");
const fpIdx = fieldNames.indexOf("file_path");
const seenNames = new Set<string>();
const seenPaths = new Set<string>();
for (let i = 0; i < manifest.Values.length; i++) {
const row = manifest.Values[i];
const entityName = row[enIdx];
const filePath = row[fpIdx];
// Null checks
if (entityName === null) {
_emitError(r, E.NULL_IN_REQUIRED_MANIFEST_FIELD,
"Values[" + i + "].entity_name must not be null.", "entity_name", i);
}
if (filePath === null) {
_emitError(r, E.NULL_IN_REQUIRED_MANIFEST_FIELD,
"Values[" + i + "].file_path must not be null.", "file_path", i);
}
// Uniqueness
if (typeof entityName === "string") {
if (seenNames.has(entityName)) {
_emitError(r, E.DUPLICATE_ENTRY,
"Duplicate entity_name: \"" + entityName + "\".", "entity_name", i);
} else {
seenNames.add(entityName);
}
}
if (typeof filePath === "string") {
if (seenPaths.has(filePath)) {
_emitError(r, E.DUPLICATE_ENTRY,
"Duplicate file_path: \"" + filePath + "\".", "file_path", i);
} else {
seenPaths.add(filePath);
}
// File existence (optional — caller must provide resolvedPaths)
if (options.resolvedPaths && !options.resolvedPaths.has(filePath)) {
_emitError(r, E.ENTITY_FILE_NOT_FOUND,
"file_path \"" + filePath + "\" does not exist on disk.", "file_path", i);
}
}
}
return r;
}
// ---------------------------------------------------------------------------
// Level 2 — Entity file validation
// ---------------------------------------------------------------------------
export interface EntityValidationOptions {
expectedEntityName: string;
expectedParentHierarchy?: string;
manifestRelativePath?: string;
entityRelativePath?: string;
}
export function validateEntityFile(
doc: unknown,
options: EntityValidationOptions
): ValidationResult {
const r = _makeResult();
// Must be valid BEJSON 104
const bejsonResult = validateBEJSON(doc);
if (!bejsonResult.valid) {
for (const e of bejsonResult.errors) {
_emitError(r, e.code, "[BEJSON] " + e.message, e.field, e.recordIndex);
}
return r;
}
const entity = doc as BEJSONDocument;
if (entity.Format_Version !== "104") {
_emitError(r, E.NOT_AN_ENTITY, "Entity file must be Format_Version \"104\", got \"" + entity.Format_Version + "\".");
return r;
}
// Parent_Hierarchy required
if (!("Parent_Hierarchy" in entity) || entity.Parent_Hierarchy === undefined || entity.Parent_Hierarchy === null) {
_emitError(r, E.MISSING_PARENT_HIERARCHY, "Entity file must contain Parent_Hierarchy key.", "Parent_Hierarchy");
} else if (typeof entity.Parent_Hierarchy !== "string" || entity.Parent_Hierarchy.trim() === "") {
_emitError(r, E.MISSING_PARENT_HIERARCHY, "Parent_Hierarchy must be a non-empty string.", "Parent_Hierarchy");
}
// Records_Type must be exactly one string
if (!Array.isArray(entity.Records_Type) || entity.Records_Type.length !== 1) {
_emitError(r, E.NOT_AN_ENTITY, "Entity file Records_Type must contain exactly one entry.", "Records_Type");
return r;
}
// Records_Type[0] must match the registered entity_name (case-sensitive)
const actualName = entity.Records_Type[0];
if (actualName !== options.expectedEntityName) {
_emitError(r, E.ENTITY_NAME_MISMATCH,
"Entity file Records_Type[0] is \"" + actualName + "\" but manifest expects \"" + options.expectedEntityName + "\".",
"Records_Type");
}
// Parent_Hierarchy path check (if caller provided expected value)
if (
options.expectedParentHierarchy !== undefined &&
typeof entity.Parent_Hierarchy === "string" &&
entity.Parent_Hierarchy !== options.expectedParentHierarchy
) {
_emitError(r, E.MANIFEST_FILE_NOT_FOUND,
"Parent_Hierarchy \"" + entity.Parent_Hierarchy + "\" does not match expected \"" + options.expectedParentHierarchy + "\".",
"Parent_Hierarchy");
}
// Bidirectional check: entity's declared path must equal what the manifest recorded
if (options.entityRelativePath !== undefined && options.manifestRelativePath !== undefined) {
// The manifest says this entity lives at entityRelativePath.
// The entity's Parent_Hierarchy + its own path should resolve back to manifestRelativePath.
// We do a lightweight string-based check here — full path resolution is the caller's job.
// We emit a warning rather than an error because resolution is environment-dependent.
if (typeof entity.Parent_Hierarchy === "string") {
_emitWarning(r, E.BIDIRECTIONAL_PATH_FAILED,
"Bidirectional path check: verify that \"" + options.entityRelativePath +
"\" + Parent_Hierarchy \"" + entity.Parent_Hierarchy +
"\" resolves to manifest at \"" + options.manifestRelativePath + "\".",
"Parent_Hierarchy");
}
}
// No path escaping
if (typeof entity.Parent_Hierarchy === "string") {
if (entity.Parent_Hierarchy.includes("..") && _escapesRoot(entity.Parent_Hierarchy)) {
_emitError(r, E.MISSING_PARENT_HIERARCHY,
"Parent_Hierarchy must not escape the database root directory.", "Parent_Hierarchy");
}
}
return r;
}
// ---------------------------------------------------------------------------
// Level 3 — Database-wide validation
// ---------------------------------------------------------------------------
export interface DatabaseValidationOptions {
strict?: boolean;
resolvedPaths?: Set<string>;
}
export function validateDatabase(
manifest: unknown,
entityDocs: Map<string, unknown>,
options: DatabaseValidationOptions = {}
): ValidationResult {
const r = _makeResult();
// Level 1
const l1 = validateManifest(manifest, { resolvedPaths: options.resolvedPaths });
for (const e of l1.errors) _emitError(r, e.code, "[L1] " + e.message, e.field, e.recordIndex);
for (const w of l1.warnings) _emitWarning(r, w.code, "[L1] " + w.message, w.field, w.recordIndex);
if (!r.valid) return r;
const manifestDoc = manifest as BEJSONDocument;
const records = decodeManifestRecords(manifestDoc);
// Level 2 — per entity
for (const record of records) {
const entityDoc = entityDocs.get(record.file_path);
if (!entityDoc) {
_emitError(r, E.ENTITY_FILE_NOT_FOUND,
"[L2] Entity document not provided for file_path \"" + record.file_path + "\".", "file_path");
continue;
}
const l2 = validateEntityFile(entityDoc, {
expectedEntityName: record.entity_name,
entityRelativePath: record.file_path,
});
for (const e of l2.errors) _emitError(r, e.code, "[L2:" + record.entity_name + "] " + e.message, e.field, e.recordIndex);
for (const w of l2.warnings) _emitWarning(r, w.code, "[L2:" + record.entity_name + "] " + w.message, w.field, w.recordIndex);
}
// Level 3 — record_count advisory check + FK resolution (warnings only unless strict)
if (r.valid) {
_checkRecordCounts(manifestDoc, records, entityDocs, r);
_checkFKResolution(records, entityDocs, options.strict === true, r);
}
return r;
}
// ---------------------------------------------------------------------------
// Helper — decode manifest Values into MFDBManifestRecord objects
// ---------------------------------------------------------------------------
export function decodeManifestRecords(manifest: BEJSONDocument): MFDBManifestRecord[] {
const fieldNames = manifest.Fields.map((f) => f.name);
return manifest.Values.map((row) => {
const obj: Record<string, BEJSONValue> = {};
for (let i = 0; i < fieldNames.length; i++) {
obj[fieldNames[i]] = row[i];
}
return {
entity_name: obj["entity_name"] as string,
file_path: obj["file_path"] as string,
description: (obj["description"] as string | null) ?? null,
record_count: (obj["record_count"] as number | null) ?? null,
schema_version: (obj["schema_version"] as string | null) ?? null,
primary_key: (obj["primary_key"] as string | null) ?? null,
} as MFDBManifestRecord;
});
}
export function decodeDatabaseMeta(manifest: BEJSONDocument): MFDBDatabaseMeta {
return {
mfdb_version: (manifest["MFDB_Version"] as string) ?? "",
db_name: (manifest["DB_Name"] as string) ?? "",
db_description: (manifest["DB_Description"] as string) ?? undefined,
schema_version: (manifest["Schema_Version"] as string) ?? undefined,
author: (manifest["Author"] as string) ?? undefined,
created_at: (manifest["Created_At"] as string) ?? undefined,
};
}
// ---------------------------------------------------------------------------
// Level 3 sub-checks
// ---------------------------------------------------------------------------
function _checkRecordCounts(
manifestDoc: BEJSONDocument,
records: MFDBManifestRecord[],
entityDocs: Map<string, unknown>,
r: ValidationResult
): void {
const rcIdx = manifestDoc.Fields.findIndex((f) => f.name === "record_count");
if (rcIdx === -1) return; // field not declared — skip
for (let i = 0; i < records.length; i++) {
const record = records[i];
if (record.record_count === null) continue;
const entityDoc = entityDocs.get(record.file_path) as BEJSONDocument | undefined;
if (!entityDoc) continue;
const actualCount = entityDoc.Values.length;
if (actualCount !== record.record_count) {
_emitWarning(r, 0,
"[L3] record_count mismatch for \"" + record.entity_name + "\": manifest says " +
record.record_count + ", file has " + actualCount + " rows.",
"record_count", i);
}
}
}
function _checkFKResolution(
records: MFDBManifestRecord[],
entityDocs: Map<string, unknown>,
strict: boolean,
r: ValidationResult
): void {
// Build a map of primary_key field names to entity names
const pkMap = new Map<string, string>(); // pk_field → entity_name
for (const rec of records) {
if (rec.primary_key) {
pkMap.set(rec.primary_key, rec.entity_name);
}
}
// For each entity, find FK fields (ending in _fk) and try to resolve them
for (const rec of records) {
const entityDoc = entityDocs.get(rec.file_path) as BEJSONDocument | undefined;
if (!entityDoc) continue;
for (const field of entityDoc.Fields) {
if (!field.name.endsWith("_fk")) continue;
// Derive expected PK field name: strip _fk suffix
const expectedPK = field.name.slice(0, -3); // e.g. user_id_fk → user_id
if (!pkMap.has(expectedPK)) {
const msg = "[L3] FK field \"" + field.name + "\" in entity \"" + rec.entity_name +
"\" cannot resolve to any manifest primary_key \"" + expectedPK + "\".";
if (strict) {
_emitError(r, E.FK_UNRESOLVED, msg, field.name);
} else {
_emitWarning(r, E.FK_UNRESOLVED, msg, field.name);
}
}
}
}
}
// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------
function _escapesRoot(relPath: string): boolean {
const parts = relPath.replace(/\\/g, "/").split("/");
let depth = 0;
for (const part of parts) {
if (part === "..") {
depth--;
if (depth < 0) return true;
} else if (part !== "." && part !== "") {
depth++;
}
}
return false;
}