import { z } from "zod"; import { moduleIdSchema } from "./identifiers.js"; import { physicalModuleTableNameSchema } from "./module-schema-names.js"; export const moduleSchemaForeignKeyMetadataSchema = z .object({ table: physicalModuleTableNameSchema }) .strict(); export const moduleSchemaTableMetadataSchema = z .object({ name: physicalModuleTableNameSchema, foreignKeys: z.array(moduleSchemaForeignKeyMetadataSchema).default(() => []), }) .strict(); /** The ABI-v1 schema entrypoint: inert Ekklesia-owned data, with no ORM object or executable * callback anywhere in its shape. Ownership and foreign-key targets are validated by Core against * the manifest module id and the complete table list. */ export const moduleSchemaMetadataDefinitionSchema = z .object({ moduleId: moduleIdSchema, tables: z.array(moduleSchemaTableMetadataSchema).min(1), }) .strict(); export interface ModuleSchemaForeignKeyMetadata { readonly table: string; } export interface ModuleSchemaTableMetadata { readonly name: string; readonly foreignKeys: readonly ModuleSchemaForeignKeyMetadata[]; } export interface ModuleSchemaMetadataDefinition { readonly moduleId: z.output; readonly tables: readonly ModuleSchemaTableMetadata[]; } function isPlainData(value: unknown): boolean { if (value === null || typeof value === "string") { return true; } if (Array.isArray(value)) { return value.every(isPlainData); } if (typeof value !== "object") { return false; } const prototype = Object.getPrototypeOf(value); return ( (prototype === Object.prototype || prototype === null) && Reflect.ownKeys(value).every( (key) => typeof key === "string" && isPlainData((value as Record)[key]), ) ); } export function parseModuleSchemaMetadataDefinition( value: unknown, ): ModuleSchemaMetadataDefinition { if (!isPlainData(value)) { throw new TypeError("Invalid Ekklesia module schema metadata definition."); } const parsed = moduleSchemaMetadataDefinitionSchema.safeParse(value); if (!parsed.success) { throw new TypeError("Invalid Ekklesia module schema metadata definition."); } return Object.freeze({ moduleId: parsed.data.moduleId, tables: Object.freeze( parsed.data.tables.map((table) => Object.freeze({ foreignKeys: Object.freeze( table.foreignKeys.map((foreignKey) => Object.freeze(foreignKey)), ), name: table.name, }), ), ), }); }